13 对象模型和this指针
- 本章围绕四个知识点展开:成员变量和成员函数分开存储、this 指针、空指针访问成员函数、const 修饰成员函数。
- 每个小节都配有完整可编译的示例代码,建议边看边动手,用
sizeof和编译器的报错信息逐条验证结论。
13.1 成员变量和成员函数分开存储
作用:弄清楚一个类的对象到底占用多少内存,理解 ==成员变量和成员函数是分开存储的== 这一 C++ 对象模型的基本规则。
语法:使用 sizeof(类名) 或 sizeof(对象) 可以查看对象占用的字节数。存储规则如下:
- 非静态成员变量:属于类的对象上,每个对象各持有一份,==占用对象空间==。
- 静态成员变量:不属于类的对象上,所有对象共享一份,==不占对象空间==。
- 非静态成员函数:不属于类的对象上,所有对象共用同一份函数代码,==不占对象空间==。
- 静态成员函数:同样不属于类的对象上,==不占对象空间==。
注意:空对象占用 1 个字节。C++ 编译器会给每个空对象也分配 1 个字节的空间,目的是==区分不同对象占用的内存位置==——每个空对象也应该有独一无二的内存地址;如果空对象占 0 个字节,多个空对象的地址就无法区分了。
示例:下面定义四个类,用 sizeof 逐项验证上面的规则:
1 |
|
总结:只有非静态成员变量占用对象空间;空对象占 1 字节用于区分不同对象的地址;静态成员变量、静态成员函数、非静态成员函数都不占对象空间。
13.2 this 指针的概念
作用:this 指针是每一个非静态成员函数中隐含的一个指针,==指向被调用的成员函数所属的对象==,即”谁调用函数,this 就指向谁”。
语法:this 指针的本质是 Person* const this 指针常量——指针的指向不可以修改,指向的数据可以修改。两大用途如下:
- 用途1:当==形参名和成员变量同名==时,用
this->成员变量 = 形参区分,解决名称冲突,如this->age = age;。 - 用途2:在类的非静态成员函数中返回对象本身,使用
return *this;,可以实现==链式编程==。
示例:下面演示 this 解决形参与成员变量的命名冲突,并通过返回 *this 实现链式编程:
1 |
|
注意:如果
PersonAddPerson的返回类型写成Person(值返回)而不是Person&(引用返回),第二次调用就作用在返回的==拷贝副本==上,链式累加会失效。==返回对象本身必须用引用方式返回==。
提示:构造函数形参名与成员变量同名时,
this->age = age;是最常用的写法;若不加 this,age = age;只是形参自己给自己赋值,成员变量并没有被赋值。
13.3 空指针访问成员函数
作用:弄清楚用空指针调用成员函数时程序的行为,学会必要的==空指针防护==写法。
语法:C++ 中==空指针可以调用成员函数==,因为成员函数不存储在对象上,调用时并不依赖具体的对象内存;但如果函数内用到了 this 指针(例如访问成员变量),this 为空程序就会崩溃。防护写法如下:
- 在成员函数开头加
if (this == NULL) return;,一旦发现 this 为空就直接返回,避免访问成员变量时崩溃。
示例:showClassName() 没有用到 this,空指针可以正常调用;showPersonAge() 访问了成员变量,函数内部加入了空指针防护:
1 |
|
注意:成员函数里访问成员变量
m_Age,编译器实际处理的是this->m_Age;只要函数中用到了成员变量,空指针调用就会崩溃,所以==访问成员变量的成员函数建议都加空指针防护==。
13.4 const 修饰成员函数
作用:用 const 修饰成员函数和对象,防止在函数内部或对象外部==误改不该修改的数据==。
语法:成员函数后加 const 称为==常函数==,对象前加 const 称为==常对象==,规则如下:
- 常函数:
void showPerson() const,常函数内==不可以修改普通成员变量==。 - mutable:成员变量声明时加 mutable 关键字,即使在常函数中==也可以修改==。
- 常对象:
const Person p,常对象==只能调用常函数==,不可以调用普通成员函数;常对象不能修改普通成员变量,但可以修改 mutable 成员变量。 - 本质:this 指针本质是
Person* const this指针常量;常函数的本质是把 this 从Person* const变成 ==const Person* const==,连指向的数据也不允许修改,所以常函数内修改普通成员变量会报错。
示例:下面代码演示常函数、mutable 成员变量与常对象的行为:
1 |
|
提示:常对象为什么只能调用常函数?常对象的 this 是
const Person* const类型,只能传给 this 同为const Person* const的常函数;普通成员函数的 this 是Person* const,类型不匹配,编译器会直接禁止调用。
总结:常函数内不能修改普通成员变量,mutable 成员变量是例外;常对象只能调用常函数,mutable 成员变量同样是例外。