博主:代码菌@-CSDN博客
专栏:C++杂货铺_代码菌@的博客-CSDN博客
目录
🌈前言🌈
📁 面向对象语言的特性
📁 类
📂 概念
📂 定义
📁 访问限定符
📂分类
📂 封装
📁 类的作用域
📁 类的实例化
📁 类的模型
📂 类的存储
📁 面试题
📁this指针(灰常重要)
📂 this指针的引入
📂 this指针的特性
📂面试题
📁 总结
🌈前言🌈
欢迎收看本期【C++杂货铺】,这期内容,我们将围绕C++中类和对象部分内容进行讲解,包括了 封装,类的概念,如何定义类和类的大小等内容,最后我们给出工作面试出的一道题目进行讲解。
如果你想学习C++,或者刚学完C语言衔接C++,那么这篇文章将会非常有效的帮助你理解。
📁 面向对象语言的特性
面向对象语言有三大特性:封装,继承,多态。在类和对象中,我们重点讲解一下,什么是封装。
例如,我们使用电脑,用户只需要知道如何与鼠标,键盘等进行交互,而不需要知道机器内部CPU,内存等是如何工作的,厂商都将其隐藏起来,封装在机箱中,这就是封装的过程。而这个机箱就是 类。
📁 类
📂 概念
不知道你是否学习过C语言中的结构体struct,来存储不同类型的数据。类就是结构体的进阶版,类 中不仅可以定义变量(成员变量),还可以定义 函数(成员函数)。
📂 定义
class className
{// 类体:由成员函数和成员变量组成 ...
};struct Name
{// 类体:由成员函数和成员变量组成...
}
需要注意的是类,不仅可以是class,也可以是struct。class定义时{}后面有 ; ,这点需要注意。
class为定义类的关键字,ClassName为类的名字,{}内为类的主体。
类体中的内容称为类的成员:类中的变量称为类的属性或者成员变量;类中的函数称为类的方法或成员函数。
类的两种定义方式:
1. 声明和定义都在类体中,需要注意的是:成员函数如果在类中定义,编译器可能会将其当做是内联函数处理。(如果不懂内联函数,可以参考下面这篇文章)
【C++杂货铺】三分钟彻底搞懂函数重载-CSDN博客
下图中,public 和 private是限定操作符。
2. 类的声明放在 .h 文件中, 成员函数定义在 .cpp文件中。
一般情况是,在学习竞赛中使用第一种情况;工作过程,采用第二种。
此外,还有一个约定俗成,就是成员变量一般以 _开头,也是为了方便区分。
📁 访问限定符
什么是访问限定符呢,就是一种限制。例如,我们的电脑,对于一个小白,那就限制他只能使用鼠标键盘等操作,不允许触碰机器内部的元器件。鼠标键盘就是共有的,机器元器件就是私有的。
📂分类
【 访问限定符说明 】:
1. public 修饰的成员在类外可以直接被访问。2. protected 和 private 修饰的成员在类外不能直接被访问 ( 此处 protected 和 private 是类似的 )。3. 访问权限 作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。4. 如果后面没有访问限定符,作用域就到 } 即类结束。5. class 的默认访问权限为 private , struct 为 public( 因为 struct 要兼容 C)。注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
C++需要兼容C语言,所以C语言中的struct可以当成结构体来使用。另外C++中struct还可以用来定义类。和class一样,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。
(注意:在继承和模板参数列表位置也有区别,后续给大家介绍)
📂 封装
C++中实现封装的方式: 用类将对象的属性与方法结合在一起,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
封装具有一下特性:
1. C++中数据和方法都在类中
2. 通过C++访问限定符对成员进行限制,可以访问在公有作用域下,不可以访问在私有作用域下。
封装的本质是一种管理,让用户更加方便的使用类。
📁 类的作用域
类定义了一个新的作用域,类的所有成员都在这个类中。在类体外定义成员时,需要使用 :: 来标明作用域操作符指明成员属于哪个类域。
class Person
{
public:void PrintPersonInfo();
private:char _name[20];char _gender[3];int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{cout << _name << " "<< _gender << " " << _age << endl;
}
📁 类的实例化
用类 来创建对象的过程,叫做类的实例化。
1. 类是对对象进行描述的,是一个模型,限定了类有哪些成员,定义一个类并没有分配具体实际的内存空间来存储。
2. 类 可以实例化出多个对象,实例化的对象 占用实际的物理空间,存储成员变量。
📁 类的模型
类中既有成员变量,又有成员函数,那么如何计算类的大小呢?
📂 类的存储
● 对象中包含类的各个成员
● 代码只保存一份,在对象中保存存放代码的地址
● 只保存成员变量,成员函数存放在公共的代码段
目前,是采用第三种方法。也就是说,成员变量存在对象中,而成员函数存在代码区。因为大家想想,如果每个成员函数都存储一份相同的成员函数,会造成很大的空间浪费。
求类的对象大小,就是看里面的成员变量。
// 类中既有成员变量,又有成员函数
class A1 {
public:void f1(){}
private:int _a;
};
// 类中仅有成员函数
class A2 {
public:void f2() {}
};
// 类中什么都没有---空类
class A3
{};
结论:一个类的大小,实际就是该类中 ” 成员变量 ” 之和,当然要注意内存对齐注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
📁 面试题
1. 结构体怎么对齐? 为什么要进行内存对齐?(1). 第一个成员在与结构体偏移量为 0 的地址处。(2). 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数为8(3). 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数 倍。(4). 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
2. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?#pragma pack( ~ ) 这个预处理指令,可以改变编译器的默认对齐数。
📁this指针(灰常重要)
📂 this指针的引入
如果我们实例化两个相同类的对象A,B。同时调用成员函数初始化和打印这两个对象,如下。
class Date
{
private:int _year;int _month;int _day
public:void Init(int year,int month,int day){_yeaar = year;_month = month;_day = day;}void Print(){cout<<_year<<'/'<<_month<<'/'<<_day<<endl;}
}int main()
{Date A;A.Init(1,1,1);A.Print();Date B;B.Init(2,2,2);B.Print();
}
编译器如何识别A和B呢?通过类的模型,我们知道,A和B的所调用的函数都是同一个,都是执行相同的指令,那编译器怎么识别year,month,day是A还是B的成员变量了。
你会发现,谁调用了,就打印,赋值给谁的成员变量。
这就引入了this指针的概念,隐含的this指针。所有的成员函数都隐藏一个形参,调用的地方,编译器也会处理。
void Print(Date* const this)
{cout<<_year<<'/'<<_month<<'/'<<_day<<endl;
}A.Print(&A);
C++编译器会给每个“非静态的成员函数”增加1个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有操作对用户是透明的,即不用不需要传递,编译器自动处理。
📂 this指针的特性
1. this指针的类型:类 类型 * const 。即在成员函数中,this1指针不能被修改指向。
2. 在形参和实参的位置不能显示写。即你不能写this,这是编译器做的事,你做了,编译器会报错。
3. this指针只能在成员函数中内部使用。
4. this指针的本质是 成员函数的形参,当对象调用成员函数时,将对象地址作为实参传递给this这个形参,所以对象不存储this指针。
5. this指针是成员函数第一个隐含的指针形参,一般情况下由编译器通过ecx寄存器自动传递,不需要用户传递。
📂面试题
this指针存在在哪里?(B)
A. 堆 B. 栈 C. 静态区 D. 常量区 E. 对象里面
解析:
首先排除E,因为对象实例化后,只存储成员变量,这是在类的模型中讲解的。
其次,排除常量区,cosnt修饰的常变量本质上还是变量。
然后,排除静态区,静态区只存储 static静态变量和全局变量。
最后,排除堆,堆是程序员自己开辟管理的空间。
this指针可以为空吗:可以,只要不在成员函数中解引用。
我么创建一个类的空指针,空指针作为实参传递给形参。
( mov的意思就是数据赋值,lea的意思就是取地址。)
下面为案例,成员函数不存储在对象中,所以A->Print不是解引用,编译器会识别成Date::Print,即通过A的类找到Print这个成员函数。其他成员函数也是同理。
① 在成员函数不解引用this指针。
class Date { private:int _year;int _month;int _day public:void Init(int year,int month,int day){_yeaar = year;_month = month;_day = day;}void Print(){cout<<_year<<'/'<<_month<<'/'<<_day<<endl;} } int main() {Date* A = nullptr; A->Print(); }
上面代码,会将A的值传递给this指针,但在Print中并没有解引用空指针,所以程序正常运行。
② 如果this指针为空,在成员函数解引用this指针。会发生运行错误。
class Date { private:int _year;int _month;int _day public:void Init(int year,int month,int day){_yeaar = year;_month = month;_day = day;}void Print(){cout<<_year<<'/'<<_month<<'/'<<_day<<endl;} }int main() {Date* A = nullptr; A->Init(); }
上面代码,会将A的值传递给this指针,但在Init中解引用空指针,所以程序运行错误。
总结一下,可以定义了一个类的指针为空指针,通过空指针,可以找到类的成员函数,此时,并不是空指针的解引用,此外,空指针将赋值给this指针。
this指针可以为空,但在成员函数中解引用this指针会运行错误。
📁 总结
总结,类就类似于struct结构体,不过可以再内部定义成员函数。
通过学习类,了解什么是封装,即通过类将对象的属性和方法结合在一起,通过访问限定符有选择的接口提供给外部用户使用。封装的本质就是一种管控。
当然这只是类的初窥门槛,之后的文章将逐渐揭开类的神秘面纱。
如果这篇文章对你有所收获,欢迎点赞,评论,收藏。Thanks♪(・ω・)ノ