7、类对象模型
如何计算类对象的大小呢?
例如:
class A { public: void AInit(int a, int b) { cout << "void AInit(int a, int b)" << endl; } private: int _a; int _b; }; class B { private: int _a; int _b; }; class C {}; int main() { cout << "类A的大小:" << sizeof(A) << endl; cout << "类B的大小:" << sizeof(B) << endl; cout << "类C的大小:" << sizeof(C) << endl; return 0; }
运行结果:
总结:
1、成员函数不算在类的大小中;
2、类的大小只与成员变量有关,并遵循结构体对齐规则;
3、空类的大小为1字节(不存储数据,只是占位,表示对象存在过)。
7.1 内存对齐规则
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
7.1 类对象的存储方式
为什么类中的成员函数不占空间?那成员函数是存在哪里呢?
实例化后的类中只存储类成员变量
成员函数保存在公共的代码段。
我们画图来理解一下:
8、this指针
我们这里写一个日期类来看一下:
class Date { public: void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; int main() { Date d1; d1.Init(2023, 7, 30); return 0; }
这里看似 Init 函数只有三个形参
调用的时候传了三个参数
实际上,这里还隐含了一个 this 指针。
我们这里画图来看一下:
这里对成员变量赋值的时候,前后都会加一个 this-> 来接引用访问。
8.1 this指针的特性
1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
2. 只能在“成员函数”的内部使用。
3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
因此,我们写的时候就不可以这样写:
class Date { public: void Init(Date* this, int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; int main() { Date d1; d1.Init(&d1, 2023, 7, 30); return 0; }
this指针隐含着,如果我们自己加上就是错误的。
this在实参和形参的位置上不能显示写
但是在类里面可以显示的用
如下:
class Date { public: //this在实参和形参的位置上不能显示写 //但是在类里面可以显示的用 void Init(int year, int month, int day) { this->_year = year; this->_month = month; this->_day = day; } private: int _year; int _month; int _day; }; int main() { Date d1; d1.Init(2023, 7, 30); return 0; }
8.2 this指针是否可以为空
我们来看下面几段代码:
class Person { public: void PersonInit() { cout << "void PersonInit()" << endl; } private: int _age; //声明 char _name[20]; }; int main() { Person* p = nullptr; //初始化为空指针 p->PersonInit(); return 0; }
运行结果:
这里我们就会产生疑问,p是空指针为什么可以解引用呢?还是正常运行。
这里对于函数定义在类里面且短小,编译器会当作内联函数,直接展开,并不会解引用;
而如果声明与定义分离或者编译器不将其当作内联函数,就是call Init函数(调用函数)的地址,也不是解引用。
我们继续看:
class Person { public: void PersonInit() { cout << "void PersonInit()" << endl; } //private: int _age; //声明 char _name[20]; }; int main() { Person* p = nullptr; //初始化为空指针 p->PersonInit(); p->_age = 1; return 0; }
这就会导致运行崩溃,对空指针的内容进行解引用。
我们接着上面看:
class Person { public: void PersonInit() { cout << _age << endl; } //private: int _age; //声明 char _name[20]; }; int main() { Person* p = nullptr; //初始化为空指针 p->PersonInit(); return 0; }
这里在调用Init函数的时候,函数里面产生了解引用,但是this是空指针,这里就会运行崩溃。
空指针不会编译错误,只会导致运行崩溃。