类和对象(上)
创始人
2024-05-23 13:26:55
0

类和对象(上)

  • 面向过程和面向对象初识
  • 类的引入
  • 类的定义
  • 类的访问限定符及封装
  • 封装
  • 类的实例化
  • 类对象模型
    • 空类的大小
  • this指针
    • this指针的引出
    • this指针的特性
    • 关于this指针的面试题

面向过程和面向对象初识

C语言是一个面向过程的语言,关注的是过程,分析出求解问题的步骤,通过函数调用逐步完成问题;
比如洗衣服:
在这里插入图片描述
在这里插入图片描述
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成!
比如还是洗衣服的例子:
对象:人、洗衣机、洗衣粉、水、衣服!
整个洗衣过程:将人将衣服、水、洗衣粉放进洗衣机,洗衣机开始洗衣服,洗衣机开始脱水,洗衣完成!
同样是洗衣服,我们只需要将衣服和洗衣服、水交给洗衣机就能完成,至于洗衣机是顺时针旋转洗衣、还是逆时针旋转洗衣或脱水,我们都不需要关心,我们只需要知道,只要将衣服、洗衣服、水交给洗衣机,洗衣机就能完成洗衣服!
在这里插入图片描述

类的引入

在C语言中定义结构体我们利用struct 关键字,且在结构体中只能定义变量,C++作为C语言的扩展,C++规定在struct定义的结构体中不仅能定义变量,也能定义函数!
就比如我们在数据结构阶段学过的栈这一数据结构,用C语言实现的栈,只能在结构体中定义变量;现在用C++的方式来实现,会发现在struct中也可以定义函数!

}
struct Stack
{//变量int _capcity;int* a;int top;///函数void Init() {cout << "Init()" << endl;}void Push(int x) {cout << "Push(int x)" << endl;}void Pop() {cout << "Pop()" << endl;}int Size() {cout << "Size()" << endl;return 0;}int Top() {cout << "Top()" << endl;return 0;}bool Empty() {cout << "Empty()" << endl;return true;}void Destroy() {cout << "Destroy()" << endl;}
};
void test2()
{Stack st;//在C++中可以不用在加上struct来表示类型了!加上也没问题!st.Init();//函数的访问方式与成员变量的访问方式一样,可以用“.”,也可以用"->"访问!st.Empty();st.Push(3);st.Pop();
}

在这里插入图片描述
像这样的结构体,在C++中我们称它为类。同时声明一个类在C++中通常用class关键字来代替struct;

类的定义

class className
{
//类体:由成员变量和成员函数组成!
};//注意一定不要忘了";"

class:定义类的关键字;
className: 类的名字,可以随便取!
类中的变量被称为类的属性或成员变量;
类中的函数被称为类的方法或成员函数;
细节: 在类中,成员变量可以在任意位置声明!不必按照原来C语言的习惯将成员变量声明在最前面,成员函数在其下面实现!;
eg:
在这里插入图片描述
当我们我们成员函数使用到成员变量时,编译器会去这个类域里面寻找对应的成员变量!而不是从上往下顺序检查,所以不用担心会出现语法错误的问题!

类的定义方式:
1、成员函数在类中定义!那么这时候对于那些比较短小的、逻辑简单的成员函数,编译器可能将他们优化成内联函数;
在这里插入图片描述
同时我们发现,利用这种方式定义类的话,类中的比较短小的程序确实被优化成了内联函数:
在这里插入图片描述
2、成员函数不在类中定义,只在类中声明,类外定义!(这种情况编译器就把成员函数当作普通函数来看待,不会优化成内联函数!),只不过在类外定义的时候要指定对应的类,就是告诉编译器,你是那个类域下的成员函数!
在这里插入图片描述
同时对于这种定义类的方式,编译器不会开启内联函数的优化,只是将其当作普通函数来对待:
在这里插入图片描述

类的访问限定符及封装

C++实现封装的方式: 用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选
择性的将其接口提供给外部的用户使用。
在这里插入图片描述

【访问限定符说明:】
1、public修饰的成员在类外可以直接被访问!
2、protected、prav在ate修饰的成员在类外不可以直接被访问!(目前我们可以认为protected、private功能暂时是一样的,关于其具体哪里不同,我们后续将会将讲解到!)
3、无论是哪一种限定符修饰,在类内都可以被访问!
4、访问权限的作用域是从该访问限定符到下一个访问限定符之间或"}"之间的作用域!
5、struct不加任何访问限定符,其成员默认权限是public;默认是public的原因是为了更好的与C语言兼容,因为在C语言中是没有访问限定符的说法的,在结构体外面可以直接访问其结构体里面的成员的!在C++中为了兼容这种情况就默认struct定义的结构体的权限是public的!
class不加任何访问限定符,其成员默认权限是private;class是C++中专门用来定义类的,为了安全起见,C++就默认class定义的类的默认权限是private;
struct、class虽然都可以定义类,但是出发点不同,struct是为了更好的兼容C,所以选择其默认权限为public;class是C++特有的定义类的关键字,是为了数据安全和隐私,所以选择其默认权限为private;

封装

面向对象的三大特性:封装、继承、多态

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来
和对象进行交互。

举个简单例子:就比如电视是由许多个电路板组成的吧,我们在操作电视的时候是直接操作电路板吗?
显然不是,我们想要直接操作电路板,不仅需要懂点电路知识,还容易出现操作失误的情况!不便于推广!
就比如简单的一个打开电视,如果直接操作电路板的话,可能需要将这个开关闭合,那个开关需要断开,很是麻烦,但是我们现在对这些电路板拿个“外壳”全部罩起来,将这些开关都集合到一个开关上!我们只需要按下这个开关就能实现电视的开关机!这样的操作方法更加简单方便我们不再需要烦恼需要打开几个开关,直接操作一个开关就行了、操作成本低下,便于用户管理!而这个“外壳”就相当于对这些电路板的封装!封装过后的电视操作更加简单;
总结:

1、 封装本质上就是管理!让用户更方便的管理类!
2、 在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来
隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

类的实例化

我们完成了对于类的定义,但是我们还无法使用这个类,因为现在这个类是抽象的,没有实际的例子!无法对类进行操作;
就比如:
你手里有一张别墅的设计图,但是现在你还没有修建这栋别墅,你就无法使用这栋别墅和别墅里面的东西,别墅被修出来是需要占用空间的,但是图纸不用!
类比过来就是:图纸就相当于类的定义,创建一个 别墅,就相当于实实在在的在内存中创建出一个对象出来!只有当对象在内存中占用了实实在在的空间,我们才能叫做类的实例化!
在这里插入图片描述
只有实例化出的对象,才能实际存储数据,占用物理空间;

类对象模型

对象模型猜测:

模型一:对象中包含类的各个成员
在这里插入图片描述
显然这个缺陷很明显:每个对象中成员变量是不同的,必须占用不同的内存空间,这个我们能理解,但是不同的对象调用的是同一份函数,注意是调用同一份函数!既然大家都是调用它,我们只需要将其成员函数保存一份就行了,每个对象都保存一份的话,显然造成了内存空间浪费!这显然不是理想的对象存储方式!
模型二:代码只保存一份,在对象中保存存放代码的地址
在这里插入图片描述
这个看起来有点靠谱;
模型三:只保存成员变量,成员函数存放在公共的代码段在这里插入图片描述

具体是哪一种呢?
首先模型一 一看就不是,所以我们只能在模型二、三中选取;
那么到底是模型二还是模型三?
我们可以通过计算一个具体类的大小,来判断?

class Person
{
public:void Init() {}
private:char _name[10];char _sex[5];int _age;
};

如果是模型二的话,那么这个类的大小算出来就是24字节,因为我们还要考虑类函数地址表(这是一个指针)所占的空间!
如果是模型三的话,我们就不必考虑,这个类算出来的大小就是20字节;
在这里插入图片描述
通过运行结果,我们可以知道,一个对象中存的只有其成员变量,并没有存各种成员函数和成员函数的地址表的地址!也就是说一个对象中的成员变量与成员函数是单独存储的!对象采用模型三进行存储!
成员变量存储在对象中,成员函数存在与代码段中!

空类的大小

在C++中是允许“空类”存在的!
那什么是空类?
就是一个类它没有成员变量,只有成员函数!或者一个类中什么都没有;
在这里插入图片描述

那么空类的大小是多少呢?
根据上面推出的对象存储模型来说,一个对象的大小就是其所有成员变量的大小,当然要考虑内存对齐!那么按照这个理论,空类中没有成员变量,那么空类的大小就是0字节!
那么结果是不是呢?
我们可以通过sizeof验证一遍:
在这里插入图片描述
我们可以发现,编译器为空类还是分配了空间,这是为什么呢?
我们可以先看一段代码:

class Hero
{
public:
int age;
};
Hero m;
Hero n;

m和n是两个独立的对象吧!
那么编译器是如何确定m和n是两个独立的对象的?m和n对象所拥有的属性是一样的啊!!
我们别忘了,m和n这两个对象虽然属性一样,但是都是要占用空间的,m和n不可能占用同一块空间吧!既然是不同的空间,地址也就不一样的!编译器就是拿这两个对象的地址不同,来确定m和n是两个独立的对象的!
我们再回来:

A1 m;
A1 n;

A1是个空类吧!如果按照我们所想的空类不占用空间的话,那么编译器怎么来确定m和n是两个独立的对象的呢?还根据地址?不可能!空间都没有,哪来的地址!我们会发现编译器无法确定这是两个独立的对象!为了解决这个问题,编译器就象征性的给空间分配一个空间,这个空间不起存储作用,只是起着标识的作用!这样编译器就能与之前一样利用两个对象所占空间的地址不同来判断这是两个独立的对象了,这也是为什么空类的大小不是0,而是1了!;

this指针

this指针的引出

我们先看一段代码:

class Date
{
public:void Init(int year,int month,int day){_year = year;_month = month;_day = day;}void Printf(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2;d1.Init(2022,1,1);d2.Init(2023,2,3);d1.Printf();d2.Printf();return 0;
}

这段代码的运行结果:
在这里插入图片描述
运行结果很正常,我们能理解,可是我现在有个疑问?
就是根据上文所讲的对象的存储模型,所用同类型的独立对象都是使用的同一份成员函数,那么我们在调用这些成员函数的时候,成员函数怎么知道要具体给那个对象设置什么值并且保证不会设置出错呢?就比如:我们d1对象调用了Init函数,那么Init函数怎么知道此时是d1调用的它或者说它是如何知道该给d1对象设置值,而不是给d2对象设置值呢?

实际上呢成员函数之所以能精确的对每个对象进行操作,是因为成员函数拿到了调用该成员函数的对象的地址,也就是说在成员函数参数一栏,有一个专门接收调用该成员函数的对象的地址的形参选项!
看到这我们或许会懵逼????
这个形参选项在哪里,我们在写成员函数的时候好像没写过这个参数啊!
当然,我们当然没写过这个参数!这些参数对于我们用户来说是隐藏起来的,但是对于编译器来说就是透明的;这是编译器自动帮我们写的,并且自动帮助我们完成了传参!有了调用该成员函数的对象的地址,成员函数就能精确的知道是那个对象在调用它,它就能精确的为那个对象“服务”;
为了更加深刻的理解,我们举个具体的例子:
就比如Date类中的成员函数Init,我们看到的函数是这样的void Init(int year,int month,int day);
但是在编译阶段,编译器为我们Date类中的成员函数的参数选项都加了一个Date *const this的形参选项,为此在编译器的眼中Init函数长这个样子:void Init(Date*const this ,int year,int month,int day);增加的这个形参的名字编译器也帮我们取好了,就叫thisthis指针不能改变其指向=
同时我们在利用d1对象调用Init函数时,我们是使用这样的方式:d1.Init(2022,1,1);
但是编译器在编译阶段,会自动帮我们识别到调用成员函数的对象的地址,并且当作参数来传递,因此编译器眼中的d1对象调用Init函数的方式是:d1.Init(&d1,2022,1,1);

这也就解释了为什么在d1对象在调用Init函数时,Init函数能够精确无误的为d1对象服务,而不是为d2对象服务!

this指针的特性

1、this指针不能更改其指向,不能给this指针赋值;
在这里插入图片描述
2、我们不能手动的在成员函数参数一栏添加this指针选项,不是说我们不能在成员函数传递Date类的指针,而是说我们在设置Date类的指针时,指针名字不能叫this,这会因为this已经被C++指定为关键字了!我们在命名方面要注意!
在这里插入图片描述
3、this指针只能在“成员函数”的内部使用,为此我们可以通过this指针显示访问成员变量:
在这里插入图片描述
隐式访问在底层,也会被转换为显示访问!
4、this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
this形参。所以对象中不存储this指针
5、this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
递,不需要用户传递

关于this指针的面试题

1、this指针存在哪里?
首先this指针作为成员函数的一个隐含参数,在调用成员函数的时候,会建立栈帧,首先进行的就是将参数压栈,this虽然是成员函数的隐含参数,但是也是一个参数,也会进行压栈,所以this指针一般存在与栈中,但是在vs环境下,编译器认为this会被大量的用到,为了提高this指针的读取速度,this会被存在ecx寄存器中,这算是对this指针的一个优化!

2、this指针是否可以为空指针?
可以先看以下代码:

class A
{
public:void Show(){cout << "Show()" << endl;}void Printf(){cout << _a << endl;}
private:int _a;int _b;int _c;
};
int main()
{A* p = nullptr;p->Show();//正常运行or程序崩溃?p->Printf();//正常运行or程序崩溃?return 0;
}

p->Show();//正常运行
在这里插入图片描述
p->Printf();//程序崩溃
在这里插入图片描述

那么为什么会出现这种情况呢?
我们的第一反映应该是两个函数都应该崩溃啊,因为p是空指针,"p->“已经对空指针解引用了;
事实真的如此吗?
首先我们在这里对解引用是有误解的,并不是指针和“ * ”、“->”操作出现在一起就一定会进行解引用操作, 当我们的目的不是通过指针访问指针指向的空间中的数据时,编译器是不会对指针进行解引用的,尽管此时指针与“ * ”、"->"操作符组合在一起, 编译器是很聪明的!
就好比本段代码中的,p->Show();和p->Printf();我们的目的是调用成员函数Show、Printf,那么这些成员函数存在于A类型对象中吗?显然不是的,Show、Printf等成员函数被存储于代码段中,根本不存在于对象中!为此尽管这里p指针与”->"结合在了一起,但是我们的目的不是访问p指针指向的空间的数据,为此编译器不会对p指针进行解引用操作,也就不存在对于空指针的解引用!那么“p->”在这里有什么作用,主要是告诉编译器,我们要调用A类域下的Show()函数,同时将p作为this指针的实参进行传递!为此我们可以成功调用Show()函数;没有“p->”还是不行的,如果直接裸的调用Show(Show();)编译器会在全局域去寻找Show()函数,找到了就调用,找不到就报错!
那么为什么调用Printf函数为什么会导致程序崩溃?
首先我们得明白,调用Printf这个函数是没有问题的,编译器允许的,因为编译器并没有对p指针进行解引用,问题就处在Printf内部:
在这里插入图片描述
此时this指针是空指针,我们的目的是访问this指针指向的空间的数据,编译器当然要对this解引用,这就造成了对于空指针的解引用!程序自然会崩溃!因此p->Printf();问题不是出在调用Printf函数身上,而是出现于Printf内部!

好现在我们明白了上面的原理,我们再来看一个:

在这里插入图片描述
根据上面的原理,(*p).Show();本质是想调用Show()成员函数,Show()又不存在于p所指向的对象中,编译器不会对p进行解引用,同时Show内部也没有利用this指针访问其指向空间的数据的情况,为此编译器不会报错,会正常运行Show函数,同理对于 (*p).Printf() 也是一样,只不过在Printf内部利用了this访问了其指向的空间的数据,造成了对nullptr的解引用,编译器不干,程序自然崩溃

相关内容

热门资讯

满洲里俄罗斯套娃广场导游词 满洲里俄罗斯套娃广场导游词  俄罗斯套娃广场是满洲里标志性旅游景区,广场集中体现了满洲里中、俄、蒙三...
连云港大伊山导游词 连云港大伊山导游词  作为一名优秀的导游,有必要进行细致的导游词准备工作,导游词是导游员同游客交流思...
介绍丽江古城的导游词 介绍丽江古城的导游词  丽江古城是联合国教科文组织确认的“世界文化遗产”和国务院公布的“中国历史文化...
汾河公园导游词 汾河公园导游词7篇  作为一位兢兢业业的旅游从业人员,时常需要用到导游词,导游词由引言、主体和结语三...
陕西大雁塔导游词 陕西大雁塔导游词7篇  作为一名尽职尽责的导游,时常要开展导游词准备工作,导游词是导游员同游客交流思...
北京八达岭长城旅游导游介绍词 北京八达岭长城旅游导游介绍词  各位游客,你们好,欢迎来到八达岭长城。今天由我为大家做导游,在这里祝...
灵山大佛完整导游词 灵山大佛完整导游词  灵山大佛坐落于无锡马山秦履峰南侧的小灵山地区,该处原为唐宋名刹祥符寺之旧址。下...
敦煌市鸣沙山和月牙泉风景名胜... 敦煌市鸣沙山和月牙泉风景名胜区导游词  鸣沙山和月牙泉风景名胜区位于甘肃省河西走廊西端的敦煌市。敦煌...
虎山长城导游词 虎山长城导游词  各位游客,大家好!  欢迎大家来到虎山长城观光旅游。很高兴能陪大家一起参观,希望大...
张家界天子山索道的导游词 张家界天子山索道的导游词  尊敬的各位来宾,各位朋友:  大家好!  今天,我们游览的是张家界武陵源...
雅鲁藏布大峡谷的导游词 雅鲁藏布大峡谷的导游词范文(通用12篇)  导游词是导游人员引导游客观光游览时的讲解词,是导游员同游...
广西著名德天瀑布导游词 广西著名德天瀑布导游词  作为一名专门为游客提供帮助的导游,可能需要进行导游词编写工作,导游词是导游...
华山导游词 华山导游词范文  导游词范文一  朋友们:大家好!  欢迎大家来华山观光旅游!今天由我给大家做导游服...
广东顺德清晖园概况讲解词 广东顺德清晖园概况讲解词  作为一位出色的导游人员,就难以避免地要准备导游词,导游词是导游员在游览时...
贵州兴义万峰湖导游词 贵州兴义万峰湖导游词  万峰湖位于贵州省黔西南自治州首府兴义市东南部,是中华人民共和国兴义国家地质公...
扬州个园简介导游词 扬州个园简介导游词  导语:扬州的个园,是一座独具风格的名园。它是清嘉庆、道光年间兴建起来的。当时园...
千岛湖导游词 千岛湖导游词  作为一名导游,有必要进行细致的导游词准备工作,导游词是我们引导游览时使用的讲解词。那...
四川峨眉山导游词 四川峨眉山导游词15篇  作为一名优秀的旅游从业人员,通常会被要求编写导游词,导游词具有极强的实用性...
四川经典导游词 四川经典导游词(通用13篇)  作为一位无私奉献的导游,通常需要用到导游词来辅助讲解,导游词是导游员...
鹿邑老君台导游词 鹿邑老君台导游词  老君台原名升仙台或拜仙台,原为明道宫的一部分,位于老子故里鹿邑县城内东北隅,老君...