目录
一、前言
二、正文
1.类和对象
1.1类定义格式
1.2访问限定符
1.3类域
2.实例化
2.1实例化概念
2.2对象大小
3.this指针
三、结语
一、前言
前面我们已经了解了缺省参数和函数重载等,今天让我们来学习类与对象吧,因为类与对象的内容很多,所以我们迫不得已我们只能拆开几部分来讲。
二、正文
1.类和对象
1.1类定义格式
- Date为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省的,类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数。
- 为了区分成员变量,一般习惯上成员变量会加一个特殊标识,如成员变量前面或者后面加_或者 m 开头,注意C++中这个并不是强制的,只是一些惯例,具体看公司的要求
- C++中struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化是stout中可以定义函数,一般情况下我们还是推荐用class定义类。
- 定在类面的成员函数默认为inline。
#include<iostream> using namespace std; class Date//Date是类的名字 { public://public是访问限定符,后面我们会讲到。void Init(int year=2024,int month=11,int day=2)//Init是成员函数{_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;} private://private和public一样也是访问限定符。int _year;int _month;//_year/_month/_day都是成员变量int _day; };//分号不能省略。 int main() {Date d1;d1.Init();d1.Print();return 0;//程序结果是成功打印:2024-11-2 }
#include<iostream> using namespace std; //这里我们简单写个单链表 struct SList {SList* next;//如果在C语言中这里会报错,在C语言中这里的类型必须是struct SList//但是C++中并不需要这么写,struct的名称就是类型,如这里的SList。int data;int ADD(int x,int y)//除此之外,在C++中struct被升级成为了类,我们还可以在struct里面定义函数,如这里的ADD。{return x + y;} };int main() {return 0; }
1.2访问限定符
- C++一种实现封装的方式,用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
- public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private是一样的,以后继承章节才能体现出他们的区别。
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到 } 即类结束。
- class定义成员没有被访问限定符修饰时默认为private, struct默认为public。
- 一般成员变量都会被限制为private/protected,需要给别人使用的成员函数会放为public。
所以访问限定符共有三个:public,private,protected。
由上面两张图可以看出,当private修辞成员变量时,成员变量在类外面无法被访问。
但是当成员变量没有private修辞时,在类外面可以被访问。
其实只要是private修辞的内容都不能在类外面访问,同理当一个成员函数被private修辞时,在类外面也访问不了这个成员函数。
1.3类域
- 类定义了一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
- 类域影响的是编译的查找规则,下面程序中Init如果不指定类域Stack,那么编译器就把Init当成全局函数,那么编译时,找不到array等成员的声明/定义在哪里,就会报错。指定类域Stack,就是知道init是成员函数,当前域找不到的array等成员,就会到类域中去查找。
#include<iostream> using namespace std; class Stack { public: void Init(int n = 4); private:int* array;int capacity;int top; }; void Stack::(int n) {array = (int*)malloc(sizeof(int) * n);//arry是被private修辞过的,不能在类外面访问,这里是错误示范。if (nullptr == array){perror("malloc 申请空间失败!");return;}capacity = n;top = 0;//同理capacity和top同上面arry一样,都是不能在类外面访问的。 } int main() {Stack st;st.Init();return 0; }
因此,函数最好完整的定义在类里面,因为在类外面完成函数功能的话,需要涉及成员变量部分,因为成员变量被private修辞,在类外面找不到成员变量,编译器就会报错。
2.实例化
2.1实例化概念
- 用类类型在物理内存中创建对象的过程,称为类实例化出对象。
- 类是对象进行一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,用类实例化出对象时,才会分配空间。
- 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量。打个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,设计图规划了有多少个房间,房间大小功能等,但是并没有实体的建筑存在,也不能住人,用设计图修建出房子,房子才能住人。同样类就像设计图一样,不能存储数据,实例化出的对象分配物理内存存储数据。
#include<iostream> using namespace std; class Date { public:void Init(int year=2024,int month=11,int day=2){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;} private:int _year;//其实这里只是声明对象,并没有实质的开空间。int _month;int _day; }; int main() {//这里通过类的实例化,实例化出了d1和d2。Date d1;Date d2;printf("%d\n", sizeof(d1));//d1和d2的大小都是12return 0; }
2.2对象大小
分析一下类对象中哪些成员呢?类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?首先函数被编译后是一段指令,对象中没办法存储,这些指令存储在一个单独的区域(代码段),那么对象中非要存储的话,只能是成员函数的指针。再分析一下,对象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各自独立的成员变量_year/_month/_day存储各自的数据,但是d1和d2的成员函数Init/Print指针却是一样的,存储在对象中就浪费了。如果用Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。这里需要再额外哆嗦一下,其实函数指针是不需要存储的,函数指针是一个地址,调用函数被编译成汇编指令[call地址],其实编译器在编译链接时,就要找到函数的地址,不是在运行时找,只有动态多态是在运行时找,就需要存储函数地址,这个我们以后会讲解。
上面那么一段话的总结就是:类中成员函数不占空间,实例化对象的大小只与成员变量有关。如下图:
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
- 注意:对齐数=编译器默认的一个对齐数与该成员大小的较小值。
- VS中默认的对齐数为8
- 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
对这部分忘记或不熟悉的小伙伴们可以看看我们往期关于这部分知识的分享:https://blog.csdn.net/yiqingaa/article/details/136966545?sharetype=blog&shareId=136966545&sharerefer=APP&sharesource=yiqingaa&sharefrom=link
#include<iostream> using namespace std; class A { public:void Print(){cout << _ch << endl;} private:char _ch;int _i; }; class B { public:void Print(){} }; class C {}; int main() {cout << sizeof(A) << endl;// 8cout << sizeof(B) << endl;// 1cout << sizeof(C) << endl;// 1return 0; }
上面的程序运行后,我们看到没有成员变量的B和C对象的大小是1,为什么没有成员变量还要给1个字节呢?因为如果1个字节都不给,还怎么证明对象存在过呢!所以还是给1字节,纯粹是为了占位标识对象存在。
3.this指针
- Date类中有Init与 Print两个成员函数,函数体中没有关于不同对象的区分,那当d1调用Init和Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这里就要看到C++给了一个隐含的this指针解决这里的问题
- 编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的指针,叫做this 指针。比如Date类的Init的真实原型为,void Init(Date* const this, int year,int month, int day)
- 类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值,this->_year = year;
- C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针。
#include<iostream> using namespace std; class Date { public://实质: void Init(Date* const this,int year=2024,int month=11,int day=2)void Init(int year=2024,int month=11,int day=2){_year = year;//可以写: this->_year=year;_month = month;//可以写: this->_month=month;_day = day;//可以写: this->_day=day;//this不可以出现在实参和形参中出现,但可以在函数体中出现。} private:int _year;int _month;int _day; }; int main() {Date d1;printf("%d\n", sizeof(d1));return 0; }
#include<iostream> using namespace std; class Date { public://实质: void Init(Date* const this,int year=2024,int month=11,int day=2)void Init(int year=2024,int month=11,int day=2){this->_year=year;this->_month=month;this->_day=day;//this不可以出现在实参和形参中出现,但可以在函数体中出现。} private:int _year;int _month;int _day; }; int main() {Date d1;printf("%d\n", sizeof(d1));return 0; }
三、结语
今天的分享就到此结束了,小伙伴们要是对你有所帮助的话,帮忙给个三连,作者在这谢谢啦。当然也欢迎大佬们的指正,拜拜~