目录
一、类
🐥在C语言中,我们常会使用 struct结构体 来定义有多种属性的变量,例如person这个变量,其中就包括了名字、年龄、性别等变量。进入C++后,结构体就升级成了 class类 ,接下来我们就一起来看看,从 struct结构体 ➡️ class类 有怎样的变化
🍔类的定义
class className { // 类体:由成员函数和成员变量组成 }; // 一定要注意后面的分号
class 是定义类的关键字
className 是类的名字
{} 括号内是类体
🔥注意:类体 由成员函数和成员变量组成
是的,类中不仅仅有变量,升级后还可以定义函数,我们以 日期类Date 为例来定义一个
例如:
class Date { //成员函数 void Init(int year=1, int month=1, int day=1) { _year = year; _month = month; _day = day; } void Show() { cout << _year << ":" << _month << ":" << _day<< endl; } //成员变量 int _year; int _month; int _day; };
🍟定义和声明分离
学会了第一种定义后,我们来看一下我们更常用的定义方式---定义和声明分离 ⬇️
🌟方法:类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
例如:
// .h //声明放在.h中 class Date { //成员函数 void Init(int year = 1, int month = 1, int day = 1); void Show(); //成员变量 int _year; int _month; int _day; }; //.cpp //定义放在.cpp中 void Date::Init(int year, int month, int day) { _year = year; _month = month; _day = day; } void Date:: Show() { cout << _year << ":" << _month << ":" << _day << endl; }
🔥注意:成员函数名前需要加 类名::
这样才能知道这个函数是来自属于哪个类的
🌮成员变量风格
什么是成员变量的风格呢?
先来看一段代码:
//成员变量 int year; int month; int day; //成员函数 void Init(int year = 1, int month = 1, int day = 1) { year = year; month = month; day = day; }
如果我们成员变量的名字是这样给的,那么在函数中就会出现 year=year 的情况,那这里的year 到底是成员变量,还是形参呢?
👌答案:因为局部优先,所以两个year都是形参,无法给成员变量赋值
🌟解决:改变成员变量的风格
在以后的代码中我们会这样定义成员变量:
class Date { //1. int _year; //2. int month_; //3. int mDay; };
三种方法都可以,主要看公司要求,一般都是加个前缀或者后缀标识区分就行
二、访问限定符
我们用刚刚的代码创建一个对象试试看:
//创建一个Date对象 int main() { Date d1; d1.Init(); d1.Show(); return 0; }
⚠️报错了:
❓why:
原因是类中引入了访问限定符
🌟作用:在许多时候,我们不希望其他人直接访问我们定义的成员变量(直接访问可能会带来变量使用的错误,例如误解变量的具体含义),而是通过我们写接口提供给外部用户使用,这样操作更加规范。
🍔访问限定符特性
一共有三种访问限定符:
🔷public(公有) 🔷private(私有) 🔷protected(保护)
🌟特性:
1️⃣public 修饰的成员在类外可以直接被访问
2️⃣protected 和 private 修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3️⃣访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4️⃣如果后面没有访问限定符,作用域就到 } 即类结束
5️⃣class的默认访问权限为 private,struct为 public (因为struct要兼容C)
通常,我们提供的接口(函数)为 public,外部能够直接访问
成员变量为 private,不能被直接访问
修改后的类的定义:
class Date { //公有成员 public: //成员函数 void Init(int year = 1, int month = 1, int day = 1); void Show(); //私有成员 private: //成员变量 int _year; int _month; int _day; };
三、类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符 指明成员属于哪个类域。
//例如我们在定义类中函数时: void Date::Init(int year, int month, int day) { _year = year; _month = month; _day = day; } void Date:: Show() { cout << _year << ":" << _month << ":" << _day << endl; }
四、类的实例化
🍔区别定义和实例化
🚀类的实例化:用类类型创建对象的过程,称为类的实例化,实例化出的对象,占用实际的物理空间
做个比方:定义➡️画房子设计图
实例化➡️建房子
🍟计算类的大小
❓请计算一下日期类的大小:
class Date { public: //成员函数 void Init(int year = 1, int month = 1, int day = 1); void Show(); private: //成员变量 int _year; int _month; int _day; };
大小:成员变量 12字节 + 函数(??多少字节)
(下方有结构体内存对齐规则提示)
🫗直接来看答案:
12!!!
函数没有大小吗?
🌟答案:在C++中,类的成员函数都被存在了公共代码区中
这样存的原因是:成员变量可以分开来存,每个对象有自己的成员变量。但成员函数是可以相同类型的变量一起来使用的,这样存储就可以避免浪费空间。
注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
🌟小tips:结构体内存对齐的规则
1️⃣第一个成员在与结构体偏移量为0的地址处。
2️⃣ 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数为8
3️⃣ 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4️⃣ 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
五、this指针
先来看一段代码:
//省略其他部分 Date d1; Date d2; d1.Init(2024,1,1); d2.Init(2024,2,2);
我们创建了d1、d2两个对象,在调用Init函数时,并没有传给Init函数 d1的地址 或者 d2的引用,那么Init函数是怎么知道应该设置哪个对象呢?
🌟答案:C++中引入了this指针,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏 的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
简单来说:d1调用Init函数时,编译器悄悄把d1的地址传了过去,也就说明函数的参数其实还隐藏了一个指针参数
🍔this指针特性
🌟this指针的特性:
1️⃣不可以显示写在函数参数中,但可以在函数内部使用
2️⃣this 指针的类型: 类类型* const,所以我们不能改变this指针
3️⃣this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针
4️⃣this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传 递,不需要用户传递
🍟this指针面试题
❓问题一:this指针存在哪里?
🫗答案:this指针存储在函数调用的堆栈上,在vs中会用ecx寄存器传递
❓问题二:this指针可以为空吗?
🫗答案:可以
示例:
class Person { public: void Show() { cout << "Show()" << endl; } private: int ID; }; void test() { Person* a = nullptr; a->Show(); }
这段代码正常运行,因为Show函数中不需要解引用a取其中变量,所以不存在对空指针操作
六、C++封装
🌟封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
对比一下C++和C实现Stack:
对比两种语言,
C++的封装就像你的🏦银行账户:
💳数据:银行账户有多个属性,比如账户号码、账户持有人姓名、账户余额等。这些信息是账户的私有信息,不应该被随意访问。
💻操作数据的方法:银行账户提供一些操作,比如存款、取款、查询余额等。这些操作是账户持有人可以执行的,但具体的实现细节(如何更新账户余额,如何记录交易等)对用户是隐藏的
这就是C++的封装,即将数据以及操作数据的方法完美结合。
七、结语
🫡你的点赞和关注是作者前进的动力!
🌞最后,作者主页有许多有趣的知识,欢迎大家关注作者,作者会持续更新有意思的代码,在有趣的玩意儿中成长!