C++之类和对象(上)

Will Rogers也一定会说:“没有自由变量这种东西。”
——《C现代》

1.面向过程和面向对象初步认识

C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。

因为是初学者,博主无法把这些编程思想讲得太深入,大家有兴趣可以看下面那篇文章。
面向过程 VS 面向对象

2.类的引入

大家都知道C++最开始叫C with Classes,意思是带类的C。所以C++最开始引入了类,你知道写C++的大佬一开始是怎么引入类的吗?他是用struct来引入的。
我们C语言以前要定义一个学生是用结构体,然后定义姓名、性别、年龄等成员变量,这种方式只能定义成员。而C++在兼容C struct的用法的基础上,同时把struct升级成了类。这个意义有两方面:

  1. 结构体的名称可以做类型。
    在这里插入图片描述
  1. 里面可以定义函数。
    注:我们定义成员变量习惯在变量名前面加_,用来标识成员变量。防止调用函数时和形参冲突。在这里插入图片描述

这个时候,可能有小可爱要问了,为什么成员变量声明在函数后面,C++编译器编译时不是向上找的吗?
其实成员变量声明在类的任意位置都可以,这里不受影响。因为类有一个概念:类是一个整体。比如说strcpy中有一个_name,它会在类的这个整体里去搜索,所以成员变量声明在哪不受影响。

3.类的定义

虽然C++可以用struct做类,但是C++还是更喜欢用一个新的关键字:class

class className
{
    
    
	// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号

class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意定义结束时后面分号
类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数
在这里插入图片描述

这个时候Student就是类型,s2我们以前喜欢叫它变量,但是从现在开始,我们就不喜欢叫变量了,我们喜欢叫它对象
那用struct和class定义类有没有什么区别呢?ok,有的。

4.类的访问限定符及封装

4.1.访问限定符

C++中有一个概念叫封装,那它就提出一个东西叫访问限定符,它的意思是我类里面的东西不是全部都可以给外部用户访问或者使用的。
在这里插入图片描述
如果刚刚我们用class写的类没有加public,会编译不通过。
在这里插入图片描述
诶,我们看见报错说在两个函数是私有的,可是我没有说他们是私有的呀。
可见class的默认访问权限为private,struct默认为public(因为struct要兼容C)。
从字面意思可见,public就是可以直接在类外面进行访问,private就是在类外面访问不了。
那现在大家猜一猜struct能不能加访问限定符?ok是可以的。
在这里插入图片描述
我们一般定义访问限定符不是说只能定义一个,一般喜欢定义多个访问限定符。比如说在学生这个类里我不想有人来改学生的姓名、性别、年龄,我们就把它们设置为私有的,而只把函数提供给外部用户。
在这里插入图片描述

访问限定符除了public、private,还有一个protected。现阶段我们认为protected和private是一样的,protected和private修饰的成员在类外不能直接被访问。 我们以后学到继承的时候,它们的意义就不一样。
总结:

  1. public修饰的成员在类外可以直接被访问。
  2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)。
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符就到};为止。
  4. class的默认访问权限为private,struct为public(因为struct要兼容C)。

4.2 封装

**面向对象的三大特性:封装、继承、多态。**继承和多态是我们以后要学的,我们今天先来讲一讲封装。
封装的意义是什么呢,这就要和C语言进行对比。这里博主分别用C语言和C++来定义一个简单的栈来讲解。

  1. C语言:C语言数据和方法是分离的,分离的结果就是太过自由,这就会导致我们对它没办法很好的管理。
    在这里插入图片描述
    假设我们要取栈顶数据,那么65行是规范的,66、67行可能会存在误用。因为这时就有一个问题,你的top是栈顶元素的位置还是栈顶元素的下一个位置?这就要取决于你初始化时给的是什么值,如果是-1那么top就是当前栈顶位置,如果是0那么top就是栈顶元素的下一个位置(因为-1是先++再插入数据,0是先插入数据再++)。如果用户直接用后两者的方式去取栈顶元素,就会存在误用。
  1. C++:在类里面把数据和方法封装到一起,想给你自由访问的设计成共有,不想给你直接访问的设计成私有。 一般情况设计类,成员数据都是私有或者保护,想给访问的函数是公有有,不想给你访问时私有或保护
    在这里插入图片描述
    这个时候我们哪怕把栈的顺序结构改成链式结构,也不会影响外部用户的使用,用户也无法访问我的成员变量来取栈顶元素。这个其实在软件工程里面叫作低耦合:关联关系低

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
封装本质上是一种管理:我们如何管理兵马俑呢?比如如果什么都不管,兵马俑就被随意破坏了。那么我们首先建了一座房子把兵马俑给封装起来。但是我们目的全封装起来,不让别人看。所以我们开放了售票通道,可以买票突破封装在合理的监管机制下进去参观。类也是一样,我们使用类数据和方法都封装到一下。不想给别人看到的,我们使用protected/private把成员封装起来。开放一些共有的成员函数对成员合理的访问。所以封装本质是一种管理。

5.类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。这个和命名空间域类似。

// 两个类域
class Stack
{
    
    
public:
	void Push(int x)
	{
    
    }
};

class Queue
{
    
    
public:
	void Push(int x)
	{
    
    }
};

那么大家想想这两个Push能同时存在吗?ok,可以,因为它俩不在同一个作用域,分别属于不同的两个类域。

如果我们写项目,代码多一些的时候,我们要声明和定义分离。像C语言我们在.h文件里可以看见数据的结构、我定义了哪些函数、我定义了哪些宏等等。我们想看具体实现时,我们才会去看.c文件。
那C++是怎么分离的呢?
C++主要是定义类,那我们怎么声明呢?如果按照以前的方法会报一堆错误:
在这里插入图片描述
有的小可爱会说,这里的报错是因为我们把成员变量设置为了私有。不是这样的,真正的原因是我们在定义的时候没有指明作用域
在这里插入图片描述
此时尽管成员变量是私有的,但是一旦指明类域,那么编译器就知道定义的函数是属于类这一个整体,在类里面是不会限定私有,公有的,类外面才会限定。

当然短小函数你也可以直接在类里面定义:
在这里插入图片描述
在这个地方又有一个小的概念:在类里面定义的函数默认是内联函数。 所以一般情况,短小函数可以直接在类里面定义,长一点的函数声明和变量分离。

6.类的实例化

那么再问大家另外一个问题,我们写的类里面的成员变量是声明还是定义?
ok,大家想清楚变量的定义和声明的区别是什么?
有没有开辟k空间,没有开辟空间就是声明,开辟了空间就是定义。
所以类成员变量仅仅只是声明,只有建立对象(以前所说的变量)时才被定义。
用类创建对象的过程,称为类的实例化

  1. 类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。
  2. 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量。
  3. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。
    在这里插入图片描述

7.类对象模型

7.1 如何计算类对象的大小

大家想想我们刚才写的栈类是多大呢?类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?我们先来看看结果:
在这里插入图片描述

7.2类对象的存储方式

通过结果来看,类对象的存储方式是只保存成员变量,成员函数存放在公共的代码段。
在这里插入图片描述
为什么这样设计?
因为每个对象中成员变量是不同的,但是调用同一份函数,如果存储函数,当一个类创建多
个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。

在这里插入图片描述
在这里插入图片描述
我们再通过对下面的不同对象分别获取大小来分析看下:
在这里插入图片描述

结论:一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节(不是为了存储有效数据,而是占位,表示对象存在过)来唯一标识这个类。

7.3 结构体内存对齐规则

这个其实学习结构体时我们就讲过了,复习一下:

  1. 第一个成员在与结构体偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
    VS中默认的对齐数为8
  3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

8.this指针

8.1 this指针的引出

我们先来定义一个日期类Date

在这里插入图片描述

在这里插入图片描述
我们知道这两个对象调用的Init、Print是同一个函数,那么问题来了:那编译器怎么知道第一个就是2022-5-14,第二个就是2022-5-12呢?编译器是如何知道处理的应该是哪个对象的呢?
C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

8.2 this指针的特性

this指针处理后是这样:
在这里插入图片描述
在这里插入图片描述

那形参和实参我能不能自己显示地写出来呢?这是不行的,因为这是编译器隐式处理的,你不能抢了编译器的饭碗。
this指针的特性:

  1. this指针的类型:类的类型* const
    如上所示的this指针应该是这样Date* const this,const加在*之后表示指针本身不能被修改,指针指向的对象可以被修改。
    在这里插入图片描述

  2. 只能在“成员函数”的内部使用。
    在这里插入图片描述

  3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。

  4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。

大家再来看看两道面试题:

  1. this指针存在哪里?
    在这里插入图片描述
    空指针是不能进行访问的。
  1. this指针可以为空吗?
    在这里插入图片描述
    p->Show();这行代码是没有问题的,因为Show不在这个对象里面,它在一个公共的代码区域(即没有解引用p)。意味着它跟普通函数的调用是一样的,拿Show这个函数名去公共的代码区域找到函数的地址,去call函数的地址。call了之后把p传参给this指针,this指针不能被修改,但是可以初始化,传参不会报错。
    在这里插入图片描述
    在这里插入图片描述
    这道题是不是必然选B啊,因为cout<<_a<<endl;实际上是cout<< this->_a <<endl;而this指针为空,就出现了非法访问的问题。

猜你喜欢

转载自blog.csdn.net/iwkxi/article/details/124764902