【C++】类和对象初探:类的实例化与对象生命周期管理,解析this指针的奥秘

欢迎来到CILMY23的博客

本篇主题为: 类和对象初探:类的实例化与对象生命周期管理,解析this指针的奥秘

个人主页:CILMY23-CSDN博客

系列专栏:Python | C++ | C语言 | 数据结构与算法

感谢观看,支持的可以给个一键三连,点赞关注+收藏。


写在前头:

本文是继上篇的续文,可以从类和对象初探开始看。

链接:  

C语言转型之路:从C到C++的类与对象初探-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/sobercq/article/details/137756562?spm=1001.2014.3001.5501


目录

 一、类和对象

1.6 类的实例化

1.7 类对象模型

1.7.1 类对象的大小

1.7.2 类对象的存储方式 

1.7.3 特殊类的大小 

1.8 this指针

1.8.1 this 指针的概念 

1.8.2 this 指针的特性

1.8.3 this指针存在位置 

1.8.4 this指针可以为空吗


 一、类和对象

1.6 类的实例化

当谈到类的实例化时,通常指的是创建类的对象,即在内存中为该类分配存储空间,并创建对象实例的过程。

  1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没 有分配实际的内存空间来存储它,比如:入学时填写的学生信息表,表格就可以看成是一个 类,来描述具体学生信息。
  2. 类就像谜语一样,对谜底来进行描述,谜底就是谜语的一个实例。谜语:"年纪不大,胡子一把,主人来了,就喊妈妈"   谜底:山羊
  3.  一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量 
  4. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设 计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象 才能实际存储数据,占用物理空间
  5. 类用private限制的成员变量是声明,不是定义,定义是需要开空间,而声明不占用实际的物理空间

 对上述第五点我们来延申讲解一下:

假设我们有个日期类举例,我们在main函数中是无法访问year的,这说明它可能是个声明,也可能是个定义,从语法上,private就限制了访问,那我们把权限打开。 

 权限放开后,我们仍然看到以下情况:

 通过上述情况我们知道,无论是公有还是私有,类中的成员变量仍然是无法访问的,而我们可以访问d1.year++,这是因为类的实例化,创建了d1对象,才开辟了空间,从而能将year++。

1.7 类对象模型

类对象模型(Class Object Model,COM)是描述C++编译器如何在内存中组织和管理类和对象的底层机制的概念。COM 描述了类成员变量和成员函数的布局方式方法的调用方式以及派生和多重继承等问题。

它是 C++ 语言规范和实现之间的桥梁,对于理解面向对象编程的中间表示形式。

1.7.1 类对象的大小

 类对象的大小遵循C语言中结构体内存对齐规则(内存对齐可以看链接),所以我们看以下的日期类

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	d1.Init(2024, 04, 20);

	cout << sizeof(d1) << endl;

	return 0;
}

我们知道成员变量遵循内存对齐规则,那这个类要开辟空间就需要十二字节那函数到底算不算在内? 对象的大小要不要加两个指针?这就涉及下一个问题,类对象是如何存储的?

1.7.2 类对象的存储方式 

我们通过汇编来看,相同的函数,他们call的地址是一样的,这说明类的对象函数并没有单独开辟空间,而是共用一个空间

所以我们有两种方案设计,一种是类中有成员变量和成员函数地址,一种是类中有成员变量,在一个公共区域去存储成员函数地址

方案一: 类中有成员变量和成员函数地址

方案二: 一个公共区域去存储成员函数地址

 那C++选择了第二种方式,为什么?

 对于方案一,每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,就会浪费空间。而代码只保存一份,在对象中保存存放代码的地址,也是同理。因此选择了方案二。

 因此来回答第一小节中的问题

那这个类要开辟空间就需要十二字节那函数到底算不算在内? 对象的大小要不要加两个指针?

答案显而易见,这个类是十二字节大小函数不算在内,对象的大小不需要加两个指针。 

1.7.3 特殊类的大小 

接下来还有两个特殊类的大小:

class A1
{
public:
	void f1()
	{

	}
};

class A2
{

};


	cout << sizeof(A1) << endl;
	cout << sizeof(A2) << endl;

 结果:

这两个类都是没有成员变量的类,它们的大小理论来说都是0,而这里给了1字节,如果没有空间的话,就无法实例化,所以可以理解为是一个规定,是一个占位,标识对象实例化时,定义出来存在过。

1.8 this指针

1.8.1 this 指针的概念 

 首先我们先通过一个日期类来看一个例子

 有个问题,为什么d1对象打印的是0417,d2对象打印的是0418,明明二者用的都是相同的函数

这里面其实就涉及了一个C++中隐含的 this 指针。

C++编译器给每个“非静态的成员函数“增加了一个隐藏的 this 指针参数,这个 this 指针是一个指向当前对象(函数运行时调用该函数的对象)的指针,它是类的成员函数中的一个隐式参数。当类的成员函数被调用时,this 指针被自动传递给成员函数,指向调用该函数的对象实例。通过 this 指针,成员函数可以访问调用它的对象的所有成员变量和成员函数。

注意:this 指针是成员函数的第一个参数

例如:成员函数被编译器自动处理成如下形式

void Print(Date* const this)
{
	cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
}

 而调用的地方会被编译器处理成如下形式:

d1.Print(&d1);
d2.Print(&d2);

1.8.2 this 指针的特性

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

1.8.3 this指针存在位置 

根据上面的基本四点,我们可以了解this指针了,但有个问题,this指针不存在对象中,那存在哪里?

 a.堆  b.栈  c.静态区  d.常量区 e.对象中

 答案是b,因为this指针是函数的形参。

 

这里就必须掏出我们熟悉的内存区域划分图了, 过去我们在讲C语言动态内存分布的时候说过,栈区是局部变量和形式参数的区域,堆区是动态内存区域,静态区是数据段,这里是存放全局变量和静态数据的地方,常量区也就是代码段。

现在我们就将其整理详细点:

所以对this指针存的位置我们马上就可以分辨出来了,因为this指针是函数的形式参数,所以它存在栈上。

1.8.4 this指针可以为空吗

 首先我们来看一段代码

 下面程序编译运行结果是?  A、编译报错  B、运行崩溃  C、正常运行 

class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Print();

	return 0;
}

 答案是:正常运行

我们接着再看下一段代码: 

下面程序编译运行结果是?  A、编译报错  B、运行崩溃  C、正常运行 

class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->PrintA();
	return 0;
}

答案是: 运行崩溃

 这就涉及到 -> 会不会解引用的问题了

第一种情况,-> 不会解引用,因为 Print 不在 p 的空间内,此时它的意义就是给 this 指针传了 p 的地址,此时 this 指针是空指针,假设 _a 存在 p 的空间,是公有的,那么 -> 就会选择对 this 指针解引用,因为它要到 p 的空间里去找  _a ,所以此时就会报错,那么同理第二个 PrintA 就会运行崩溃。

总结:空指针的传递并不会发生问题,而空指针的访问会出现问题。  


 总结:

  • 类的成员变量是声明,不是定义,定义是需要开空间,而声明不占用实际的物理空间
  • 类的实例化是开辟空间,创建对象
  • 类的成员变量大小遵循结构体内存对齐原则
  • 一个类的大小,实际就是该类”成员变量”之和,遵循内存对齐。
  • 空类的大小,空类比较特殊,编译器给了空类1个字节来唯一标识这个类的对象。没有成员变量的类也是如此。
  • 内存对齐可以指定对齐数,用 #pragma pack()就可以指定对齐数
  • 编译器搜索从局部到全局
  • 关于this指针有以下四点
  1. 形参和实参的位置,我们不能显示写
  2. 成员函数内部可以使用
  3. this指针存在栈上,因为它是形式参数
  4. this指针在某些编译器如vs上会用寄存器传递,这是为了提高效率
  •  -> 会不会解引用,这还要取决于它要得到什么。

结构体内存对齐规则:

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


感谢各位同伴的支持,本期C++篇就讲解到这啦,如果你觉得写的不错的话,可以给个一键三连,点赞关注+收藏,若有不足,欢迎各位在评论区讨论。 

  • 31
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值