C++类与对象第一篇:类的介绍及this指针

Posted 无聊的马岭头

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++类与对象第一篇:类的介绍及this指针相关的知识,希望对你有一定的参考价值。

面向过程和面向对象的初步认识

我们先初步认识一下面向过程和面向对象:

  1. 我们学c语言,c语言就是面向过程,关注的是过程,分析求解问题的步骤,通过对函数的调用逐步解决问题。
  2. 我们学c++,c++就是面向对象,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。

在现在这个时代,点外卖已经成为我们的日常了,以外卖为例来简单区别过程和对象:

在c语言(面向过程)中,会把点外卖拆分为:下单、接单、送餐等三个过程。
在c++(面向对象)中,会把点外卖拆分为:客户、商家、骑手三个对象。

1.类的引入

我们学c语言时,写一个栈的时候,先写一个结构体,然后再写一些函数来实现功能,其中结构体只能定义变量。
而在c++中,结构体内不仅可以定义变量,还可以定义函数(我们称之为)。

struct stack
{
	void InitStake(int capacity=4)
	{
		a=(int*)malloc(sizeof(int)*capacity);
		_capacity=capacity;
	}
		
	int *a;
	int _capacity;
};

类中的函数也可以写在类的外面,类中必须声明。(这里需要指定InitStake是属于stack这个类域

struct stack
{
	void InitStake(int capacity=4);
		
	int *a;
	int _capacity;
};
//这里需要指定InitStake是属于stack这个类域
void stack::InitStake(int capacity=4)
	{
		a=(int*)malloc(sizeof(int)*capacity);
		_capacity=capacity;
	}	

上面是c语言中结构体的定义,在c++中是兼容c语言的,当然,在c++中更喜欢用class来代替。

2.类的定义

class stack
{
//{}中的为类体:由成员函数和成员变量组成
	void InitStake(int capacity=4)
	{
		a=(int*)malloc(sizeof(int)*capacity);
		_capacity=capacity;
	}
		
	int *a;
	int _capacity;
};//一定要注意后面的;

class为定义类的关键字stack为类的名字{}中为类的主体注意类定义结束后面的分号
类中的元素称为类的成员:类中的数据称为类的属性或者成员变量;类中的函数称为类的方法或者成员函数

定义类的两种方式

  1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。 在这里插入图片描述

  2. 声明放在.h文件中,类的定义放在.cpp文件中
    在这里插入图片描述
    一般情况下,更期望采用第二种方式。

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

3.1访问限定符

c++实现封装的方式:用类将对象的属性与方法结合在一起,让对象更加完善,通过访问限定选择性的将其接口提供给外部的用户使用。
在这里插入图片描述
c++是兼容c语言的,在用struct来写类时,c++是默认public(公有)的,class是默认private(私有)的。
(暂时我们认为protected和private作用一样,他们的区别要在继承才能体现)

【访问限定符说明】

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

注意访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

用例子来感受一下:
在这里插入图片描述
class默认private,在没有其他访问限定符的情况下,是无法访问change函数。
想要访问就通过是public的函数来访问:
在这里插入图片描述

3.2封装

【面试题】面向对象的三大特性:封装、继承、多态。

在类和对象阶段,我们只研究类的封装特性,那什么是封装呢?

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

封装本质是一种管理:以陕西的兵马俑为例,兵马俑为世界八大奇迹之一,在我们中国参观兵马俑的人很多,如果不管理这些参观的人,想必兵马俑现在已经没了吧。那么我们就应该先建个房子把兵马俑封装起来,然后我们开放了售票通道,可以买票突破封装在合理的监管机制下进行参观。也是一样,我们使用数据和方法都封装一下。不想别人看的用protected和private吧成员封装起来。开放一些共有的成员函数对成员合理的访问。所以封装本质是一种管理。

4.类的作用域

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

struct stack
{
	void InitStake(int capacity=4);
		
	int *a;
	int _capacity;
};
//这里需要指定InitStake是属于stack这个类域
void stack::InitStake(int capacity=4)
	{
		a=(int*)malloc(sizeof(int)*capacity);
		_capacity=capacity;
	}	

5.类的实例化

用类类型创建对象的过程,称之为类的实例化

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

6.类的对象大小的计算

6.1 类对象的存储方式猜测

  • 对象中包含类的各个成员(猜测一)

缺陷:每个对象成员变量不同,但是调用同一份函数,如果按照此方法存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。

  • 只保存成员变量,成员函数存放在公共的代码段(猜测二)
    在这里插入图片描述

问题:对于上述两种存储方式,那计算机到底是按照那种方式来存储的呢?

//类中既有成员函数,又有成员变量
class A1
{
public:
	void f1(){}
private:
	int _a;
};
//类中仅有成员函数
class A2
{
public:
	void f2(){}
};
//类中啥也没有...空类
class A3
{};

sizeof(A1): 4byte
sizeof(A2):1byte
sizeof(A3):1byte

我们来找类的时候是通过地址来找的,所以空类给一个字节不是用来存储数据,是占位,表示对象存在过。

结论一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类

6.2 结构体内存对齐

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

👉戳这里

6.3 计算类对象的大小

6.1和6.2结合起来就是计算类对象的大小。

7.类的成员函数的this指针

7.1 this指针的引出

我们先通过定义一个日期类Date来引出this指针

#include<iostream>
using namespace std;

class Date
{
public:
	void Display()
	{
		cout<<_year<<"."<<_month<<"."<<_day<<endl;
	}

	void SetDate(int year,int month,int day)
	{
		_year=year;
		_month=month;
		_day=day;
	}
private:
	int _year;//年
	int _month;//月
	int _day;//日
};

int main()
{
	Date A1;
	Date A2;
	A1.SetDate(2021,5,1);
	A2.SetDate(2021,6,1);
	A1.Display();
	A2.Display();
	return 0;
}

在这里插入图片描述
代码是没问题的。

但是有这样一个疑惑👀?
Date类中有SetDate与Display两个成员函数,函数体中没有关于不同对象的区分,那当A2调用SetDate函数时,该函数是如何知道应该设置A1对象,而不是设置A2对象呢?

可能有很多人对这个问题有点懵,这不是理所应当的吗?什么什么如何知道应该设置A1而不是A2。我第一次遇到这个问题也是这样回答的,但这显然不是答案。这就开始要说到this指针了。

C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
在这里插入图片描述
总结:

  1. this指针是隐含的,是编译器编译时加的,我们不能显示的在调用和函数定义中加
  2. 可以在成员函数中使用this指针在这里插入图片描述
  3. this一般是存在栈上,不同的编译器不同,vs是使用ecx寄存器存储的

7.2 this指针的特性

  1. this指针的类型:类类型* const
  2. 只能在“成员函数”的内部使用
  3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针
  4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
  • 最后让我们来更加了解this指针

【笔试题】
1.下面程序能编译通过吗?
2.下面程序会崩溃吗?在哪里崩溃?
在这里插入图片描述
能编译通过,但不能成功运行。
在这里插入图片描述
调试会运行中断。
在这里插入图片描述

在这里插入图片描述
【为什么】

p->Show()等等这些函数,没有对p这个空指针解引用,所以这里的空指针不会引发访问的崩溃。

因为Show等成员函数的地址没有存到对象里面,所以成员函数中接收到的this是空指针,所以p->PrintA()会报错。

最后;this是关键字

总结

以上是这期的全部内容,如果有错误的地方请留言评论区或私信俺,共同学习、进步。爆赞👍

以上是关于C++类与对象第一篇:类的介绍及this指针的主要内容,如果未能解决你的问题,请参考以下文章

C++ 类与对象(上)

初识c++——类和对象

C++——类与对象

[ C++ ] 类与对象(下)日期类Date补充及流提取和流插入运算符重载

C++入门篇之类和对象上

C++02_类与对象(上)