6.类的实例化
什么叫类的 实例化??
首先, 我们应该关注这个"实" — 实际存在的, 它的反义词是 “虚” — 不存在的. ==> 类中的成员变量是虚的(相当于声明), 在类外面创建的对象是实际存在的(相当于定义).
用类类型创建对象的过程, 成为类的实例化.
1.类是对对象进行描述的, 是一种模型一样的东西, 限定了类有哪些成员. 类是没有分配实际的内存空间,变相地说明了类中的成员变量是一种声明哦
class PersonInfor { public: void PrintPersonInfor() { cout << _name << endl; cout << _adress << endl; cout << age << endl; } private: char _name[20]; char _adress[30]; int _age; }; int main() { PersonInfor::_name; // error C2597: 对非静态成员“PersonInfor::_name”的非法引用 // message : 参见“PersonInfor::_name”的声明 PersonInfor::_adress; // error C2597: 对非静态成员“PersonInfor::_adress”的非法引用 // message : 参见“PersonInfor::_adress”的声明 PersonInfor::_age; // error C2597: 对非静态成员“PersonInfor::_age”的非法引用 // message : 参见“PersonInfor::_age”的声明 }
肯定有些老铁会尝试上面的做法, 很明显是错误的!!!
1.这些老铁应该是这样想的: PersonInfor 是一个类名, 那么就存在类域, 我就可以用域作用限定符 (😃 来访问类中的成员变量~~
2.错误原因: 其实这种说法就是错误的~ ,我们想一想什么叫做访问? 访问的东西肯定要有内存空间吧, 要不访问个啥呢~~
其实,很多词语就暗着内存空间: 访问, 初始化, 有可能你现在看这些一点反应都没有, 学到后面这些用途大着嘞.
2.一个类可以实例化多个对象, 这点应该不用多说(类是一种变量类型, 相当于我们常见的 int, double, char…). 但是实例化出的对象 占用的物理空间只存储了成员变量, 即成员函数是不在对象中的.
class PersonInfor { public: void PrintPersonInfor() { cout << _name << endl; cout << _adress << endl; cout << age << endl; } private: char _name[20]; char _adress[30]; int _age; }; int main() { PersonInfor p; printf("类的大小 = %d\n", sizeof(PersonInfor)); // 56 printf("对象的大小 = %d\n", sizeof(p)); // 56 }
得到的知识点:
1.根据结构体的所学知识, 我们不难知道成员变量的大小就是 56, 而对象 和 类的大小也是56, 验证了成员函数不是包含在对象中的.
2.sizeof(对象) = sizeof(类)
3.以 arr.Push( ) 为例子: 调用这个函数, 是不会在对象里面去寻找的, 因为成员函数没在对象里.
疑惑点:
1.成员函数到底在哪里??
2.上面的代码, 我这样调用可以吗? PersonInfor :: PrintPersonInfor()??
先对类的实例化打一个形象地比喻:
声明就好比是一张房屋的设计图纸, 成员变量就是那图纸上的一个个房间, 成员函数先靠边~~
设计图纸终归也就是一张纸而已, 里面的设计的东西再怎么好也是不存在的.
而我们的对象就是把这个图纸实际化 ==> 真真切切地造一个房子出来. 房子是按照图纸建造的, 里面的东西肯定只有也只能有这些房间(成员变量) ==> 类是对对象进行描述的, 是一种模型一样的东西, 限定了类有哪些成员.
这时候, 我们想到那娱乐设施呢, 就比如篮球场~, 篮球场是一家建一个 还是 一个小区建一个好一些?? 答案不言而喻, 一个小区建一个, 不仅经济实惠, 还能融洽小区关系呢~~ 成员函数就好比是这篮球场, 它是不会存放在一个个对象中的(占空间), 而是存放在一个公共位置, 大家都可以使用哦.
回答:
1.成员函数的存放位置在一个公共位置, 同一个类型的对象都可以调用
2.PersonInfor :: PrintPersonInfor() 是不可以的:
老铁是这样想的: 反正成员函数是不在对象中的, 那我就可以不用对象去访问它了吧
错误原因: 其实这里有个 this指针,这个后面会讲~
或者大家有么有想过下面的几个问题:
1.每个对象调用的成员函数都是一样的吗?
2.如果是一样的, 那么为什么多个对象调用有多个不同的结果??
7.类对象模型
一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐
注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象
内存对齐的规则:
1.第一个成员在与结构体偏移量为0的地址处
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8
3.结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是 所有最大对齐数(含嵌套结构体的对齐数)的整数倍
小问题:
1.结构体怎么对齐? 为什么要进行内存对齐?
2.如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?
3.什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景?
8.this指针
通过前面的学习, 相信大家对this指针充满了好奇心
this指针对我们以后的学习有着无与伦比的作用
8.1this指针是什么
class Data { public: void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } void Print() { cout << _year << " " << _month << " " << _day << endl; } private: int _year; int _month; int _day; }; int main() { // 对象实例化 Data d1; Data d2; // 调用函数 d1.Init(2023, 12, 1); d2.Init(2023, 12, 12); d1.Print(); d2.Print(); }
this指针, C++编译器给每个"非静态的成员函数" 增加了一个隐藏的指针参数, 让该指针指向当前对象(函数运行时调用该函数的对象), 在函数体中所有"成员变量"的操作, 都是通过该指针去访问. 只不过所有的操作对用户是透明的, 即不需要用户来传递, 编译器自己完成.
上面, 我们留了一个问题: “如果每个对象调用的成员函数是一样的, 那么为什么多个对象调用有多个不同的结果??”, 这个时候就能过给大家解答了:
让该指针指向当前对象(函数运行时调用该函数的对象), 在函数体中所有"成员变量"的操作, 都是通过该指针去访问 ==>
d1.Init() ==> d1.Init(&d1), d1.Print( ) ==> d1.Print(&d1)
这样大家就明白了: 为什么调用的同一个函数, 为什么结果不一样~~
补充:
不同对象调用的成员函数是相同的原因:
8.2this指针的特性
1.this指针的原型是:对象类型* const this, 即在成员函数中, 不能给this指针赋值, 不能改变this指针.
2.this指针不能在形参 和 实参显示传参, 但是能够在成员函数中使用(这个以后还有大用途)
3.this指针本质是成员函数的形参, 当对象调用成员函数时, 将对象的地址作为实参传递给this形参, 所以对象是不存储this指针的.
4.切记: this指针是成员函数的"第一个隐含的指针形参", 用户不需要也不能传递
解决上面的两个疑问:
1.“PersonInfor :: PrintPersonInfor() 是不可以的”
这个是因为成员函数不知道该传递啥~, :: 是访问的意思哎, 并没有传递对象的地址哎
2.那有些老铁就会说: PersonInfor :: PrintPersonInfor(对象的地址) , 这样行吧??
你的这些小聪明, 我都是看在眼里的哈哈~~
这里虽然传递了对象的地址, 但是你忽略了一点:this指针在形参和实参是不显示的哎~~
小问题:
1.this指针存放在哪里?
因为this指针是成员函数的形参, 所以this指针是跟普通函数一样存放在函数调用的栈区的, 函数调用结束就销毁了
2.this指针可以为空吗?
分情况:
1.如果this指针传过去没有访问this所指向的内容, 这样是可以的
2.如果this指针传过去有访问this所指向的内容, 这样会报运行错误
通过下面的俩个例子来解释一下:
class test { public: void test1() { cout << "test1(_)" << endl; // 并没有访问this指针 } void test2() { cout << _a << endl; // 访问了this指针 } private: int _a; }; int main() { test* a = nullptr; a->test1(); // test1(_) a->test2(); // error return 0; }