类和对象

C++对象模型和this指针

成员变量和成员函数的存储

C++中的成员变量和成员函数是分开存储的,只有非静态成员变量才属于类的对象上

class Person{

    int m_Age;//非静态成员变量
    static  int m_B;//静态成员变量
    void func(){ //不属于类的对象上

    }
    static void func2(){}
};
int Person::m_B=15;
void test01(){
    Person p;
    cout<<sizeof(p) <<endl;
    //空对象占用内存空间为1
    //C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置,
    //每个空对象也应该有一个独一无二的内存地址
}

Person 的对象p只有 m_Age的大小也就是4个字节。

this指针:

在之前的笔记中写过, 每个非静态成员变量只诞生一份实例,也就是说多个同类对象共用一块代码,那么问题来了,这一块代码是如何区分到底是哪个对象调用了自己的?

那么C++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用成员函数所属的对象

this指针特性:

this指针是隐含每一个非静态成员函数内的一个指针

this指针不需要定义,直接使用即可

用途:

1.当行参和成员变量同名时,可以用this来区分。

class Person{
    public:
    Person(int age){
        age=age;
    }
    int  age;
};
void test01(){
    Person p;
    cout<<p.age<<endl;

}
int main(){
    test01();
    return 0;
}

上述例子中运行后会提示错误,这个错误就是因为age并不清楚哪个是成员变量哪个是参数传入的。

那就可以用this来区分一下。

//把     
   age=age;
//改为
this->age=age;


在类的非静态成员函数中返回对象本身,可使用return *this

class Person{
public:
    Person(int age){
        this->age=age;
    }

    void addAge(Person p){
        this->age+=p.age;
    }
    int  age;
};
//2.返回对象本身用*this
void test02(){
    Person p1(10);
    Person p2(15);
    p2.addAge(p1);
    cout<<p2.age<<endl;

}

此时的运行结果是10+15=25,那如果我想要多加几次,可不可以直接:

p2.addAge(p1).addAge(p1);

这样肯定是不行,从语法来说就不对因为我们返回值是void,但是我们最终目的是多加几次,应该如何做呢?

如果我们让这个函数执行结果return的是p2本身,执行一次后就保存在p2了,是否就可以多次执行了

class Person{
public:
    Person(int age){
        this->age=age;
    }

    Person& addAge(Person &p){
        this->age+=p.age;
        return *this;
    }
    int  age;
};
void test02(){
    Person p1(10);
    Person p2(15);
    p2.addAge(p1).addAge(p1);
    cout<<p2.age<<endl;

}

这个时候代码就不报错了,结果就变成了35了。当然依次类推,由于返回的是本身。所以如果我们这样加结果是多少呢?

    p2.addAge(p1).addAge(p1).addAge(p2);

后面再跟一个参数是p2的addAge,结果应该是70,因为前面p2年龄已经是35了,又传入了35加起来就是70了。

如果上面的返回值不是引用了,而是值,那么结果就会变为25,因为每次 addAge 都返回一个临时对象,并且每次调用都在新的临时对象上进行,因此这些操作都不会影响 p2 本身。

p2 的 age 在第一次调用后变为 25,之后的操作不再对 p2 本身产生影响。

这被称为是链式编程思维

空指针访问成员函数

class Person{
public:

    void showClassName(){
        cout<<"1111 Person Class"<<endl;
    }
    void  showPersonAge(){
        cout<<m_Age<<endl;
    }

    int m_Age;
};
void test01(){
    Person *p =NULL;
    p->showClassName();
    p->showPersonAge();
}

这里我们运行后程序会中断,原因在于showPersonAge() 这个函数身上。因为在

        cout<<m_Age<<endl;

其实默认是有一个this->的

        cout<<this->m_Age<<endl;

但是因为p是个空指针,所以会找不到这个数据,这当然会出错。

为了程序不断掉,我们可以这样做

    void  showPersonAge(){
        if(this==NULL)
            return;
        cout<<m_Age<<endl;
    }

加一个空指针的判断。

const修饰成员函数

常函数:

  • 加上const修饰的函数称之为常函数
  • 常函数不能修改成员属性(只读)
  • 成员属性声明时加上关键字mutable后,在常函数中依然可以修改
class  Person{
    
public:
    void showPerson()const
    {
        m_A=100;
    }
    int m_A;
};

const修饰后,我们就无法对m_A进行修改了。

我们怎么理解呢?

其实原函数也可以写为

    void showPerson()const
    {
        this->m_A=100;
    }

每个成员函数都有一个this指针,this指针的本质就是指针常量,指针的指向是不可修改的。如果你在函数内部对this指针赋值,会报错,因为它是const的其实对于本例this等价于Person * const this。那如果想要this指向的值都不可修改就要在Person前面再加上const,就变成了:

const Person * const this

这个const其实就是void showPerson() const。其本质修饰的就是this指针指向的值。

同样,我们想要在常函数中也可以修改这个值的话。就加上mutable关键字

class  Person{

public:
    void showPerson()const
    {
        m_A=100;
    }
    mutable int m_A;
};


常对象:

  • 加上const修饰的对象称为常对象
  • 常对象只能调用常函数

 

class  Person{

public:
    void showPerson()const
    {
        m_A=100;
//        m_B=1000;
    }
    mutable int m_A;
    int m_B;
};

//常对象
void test01(){

    const Person p;
    p.m_B=100;//报错
}

由于const修饰了p,m_B并不是const修饰的,所以这里也会报错。

但是

p.m_A=100;//就可以了因为有mutable修饰。

接下来我们在Person类中加上一个空的函数

    void func(){
        
    }

使用静态修饰的对象p来调用,同样也会报错,因为常对象只能调用常函数,由于普通成员函数可以修改属性值,但是常函数不能修改值,如果允许我们这样做就会发生冲突了。