类内的数据和操作是分开存储的,并且每一个非内联成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共享一块代码。
这里会引出内存对齐的概念,如果不修改对齐方式,则默认为内存对齐。内存对齐是什么意思呢?接着上面的例子,空类的大小为1,空位中加入了一个int型变量之后大小一共为4,那么此时在加入一个double变量之后,大小不是12,而是16。内存对齐就是把变量中内存最大的大小作为一个基准值,比如这里的double是8,基准值是8。空类中加入一个int之后,大小是4,对于8的基准值来说,一块基准值还剩下
(8- 4 )= 4
的大小,然而剩下的这4放不下一个double,因此需要去下一块基准值去存放这个double。加入了double之后,也就是16的大小了。这16的大小里面分为了两块基准值,第一块是int+4个空,第二块是存放double。以此类推……
this指针指向被调用的成员函数所属的对象。
#include <iostream> using namespace std; //创建一个人类 class Person { public: void func() {} //成员函数 string m_name; int age; }; int main () { Person p1; p1.func();//编译器回偷偷加入一个this指针 Person *this //实际上执行的是 void func(Person *this),this此时代表p1这个对象,以此来区分是哪个对象访问该成员函数 return 0; }
在上述代码中:当创建了一个p1对象之后,当p1调用成员函数func时,因为实际上func这个成员函数是只有一个,因此编译器会借助this指针偷偷传入p1对象,也就是 void func(Person *this),因为是p1调用的(p1.func();
),所以这个this是代表着p1这个对象。如果是其他对象调用func也是同理,虽然不同的对象都是共用一个func函数,但是依靠this指针可以让func区分是哪个对象在调用。
this指针是隐含在对象成员函数内的一种指针(构造拷贝析构函数中也有)。当一个对象被创建后,该对象的每一个成员函数都含有一个系统自动生成的隐含this指针,用以保存该对象的地址。也就是说虽然我们没有在成员函数的实现内写上this指针,但是编译器在编译时是会自动加上的。this指针称为“指向本对象的指针”,永远指向当前对象。
this指针是隐含在每一个类的非静态成员函数中的
,对象的非静态成员函数是不属于对象的,因此this指针当然也不属于对象,所以this指针不会影响sizeof(对象)
的结果。
this指针是C++实现封装的一种机制,它将对象和调用该对象的成员函数连接在一起。在外部看来,每一个对象都拥有自己的成员函数。一般情况下,并不用写this指针,系统会默认帮忙处理。
因为静态成员函数只有静态成员变量可以访问,而且静态成员函数是共享数据的,所以静态成员函数是没有this指针的。
class Person { public: person(int age) { this->age = age; } int age; public: Person& plusAge(Person &p) { this->age += p.age; return *this; } }; int main() { Person p1(10); Person p2(10); p1.plusAge(p2).plusAge(p2);//链式编程思想,可以一直链接下去 //plusAge返回值是调用他的对象,所以p1.plusAge(p2)的返回值是加上p2年龄之后的p1对象,所以可以继续调用成员函数。 return 0; }
注意:plusAge成员函数的返回值是引用,表明返回的是对象本身。如果把引用去掉,就是值拷贝的返回,也就是说会返回一个跟对象本身的值一样的
另一个不存在的对象
(编译器自动做了浅拷贝)。如果把引用去掉之后,第一次调用会给p1成功添加年龄,但是由于第一次调用成功之后返回的已经不是p1对象本身了,所以后面的链式编程已经不会对p1有影响。
成员函数的返回值是引用还是非引用,看业务需要来定。
class Person { public: Person (int age) { this->m_age = age; } void show1() { cout << "该函数没用到this指针,所以空指针也可以访问"<< endl; } void show2(int age) { if (this == NULL) return;//用到了this指针,要做个判断 this->m_age = age;//相当于 NULL->m-age = age; 访问了不存在的地址。 cout << "该函数用到this指针,所以要做安全处理"<< endl; } int m_age; }; int main () { Person *p = NULL; p->show1(); //可以成功访问,show1里面没用到this指针 p->show2(10); //用到了this指针,如果不加安全判断,则程序会崩溃 return 0; }
①如果成员函数没有使用到this指针,则空指针可以方法该成员函数
②如果成员函数有使用到this指针,则空指针不能访问该成员函数(加上空判断防止该情况)
上面说的this指针,之所以this指针永远指向它的本体对象,
是因为this本质上是一个指针常量Person * const this
,指针的指向不能改变。
也就是说this指针绑定了本体对象,指向不能变,但是这个对象的变量可以变。
如果希望this指针指向的对象的变量也不要变,那么就要改成const Person * const this
既然this指针是编辑器自动会帮忙处理,那么我们就要在代码中给编译器一个提示,所以就引入了一个常函数的概念,就是在成员函数的后面加上const,那么当某个对象调用这个成员函数的时候,会自动为this指针加上const修饰,然后再传入成员函数。
**普通成员函数** class Person { public: void showAge() { //普通成员函数 this->m_age = 100;//该this指针是一个指针常量,永远指向p,但是p的成员属性可以改变。 } int m_age; }; int main() { Person p; p.showAge();//这里相当于是传入了 Person * const this 指针; //p.showAge(Person * const this); return 0; }
**常函数** class Person { public: void showAge() const { this->m_age = 100;//该操作错误 //加上了const之后,showAge这个成员函数就变成了常函数,在常函数内不能修改成员的属性 this->m_mutable_age = 100;//加了mutable关键字之后可以修改 } int m_age; mutable int m_mutable_age; }; int main() { Person p; p.showAge();//这里相当于是传入了 const Person * const this 指针; //p.showAge(const Person * const this); return 0; }
**常对象,常对象只能调用常函数,因为常对象的成员属性是不能改变的。 但是普通的成员函数是有可能会修改成员属性,而常函数是无法修改成员属性的。 所以一般常对象跟常函数是搭配使用的。 const Person p;//在类名前加const,变成了常对象 p.m_age = 100; //错误操作,常对象的成员属性不能改变 p.m_mutable_age = 100;//正确操作,常对象的成员属性不能改变,除非该成员属性有mutable修饰