C++相关概念和易错语法(3)(类的声明和定义、空指针分析、this指针)

文章讲述了C++中类的声明与定义的区别,强调了封装思想,讨论了空指针如何避免编译和链接错误,以及this指针在区分不同对象和内存分配中的作用。还涉及了空指针对代码执行的影响以及this指针的存储位置。
摘要由CSDN通过智能技术生成

1.类的声明和定义

注意类的声明和定义分离的时候,在定义处要使用域作用限定符,否则函数声明链接时的定位不到函数的定义。

1b2a658133f843b588e0ef27bacdcf35.png

这些成员变量、函数的作用于这个类域,将功能集成在一起,这体现出封装的思想。

在区分类的定义和声明时,主要看有没有开辟空间

fadb2f62b8bc430f82d64960ba7f5010.png

在.h文件中,成员变量和成员函数都算作声明,只有在创建这个类的时候,才会为成员变量创建空间。但是,成员函数并不会被拷贝。这样可以防止空间的浪费。

为形象理解,可以将类的声明视为图纸,这个图纸可以实例出多个对象。

583ad0aee8564309afff382382f878c9.png

因此在计算对象的大小时,只会计算成员变量的大小,同时满足和结构体一样的对齐操作,当然你也可以用#pragma pack(1)来设置默认对齐数。

abb5da0e062d44e58ff06c7aa48757b4.png

注意这是64位平台测试,指针的大小是8个字节。

我们可以将成员函数视为类的公共区域,每个成员调用成员函数都是直接到类里面来找。注意刚刚的描述是便于形象理解成员函数的调用,和访问限定符(protected、struct默认公有public,class默认私有private,只要不写public都是私有)不同,不要混淆。208ae056096c408fadbedd86587e12ba.png

2.因空指针导致的程序崩溃

解释下面代码为什么不报编译错误:

f324d9a4ade44dc4bc26a3aa977393e8.png

我们需要通过编译的过程来解释这一现象:

cf54f11a320b4ebeab67bc931a3c052f.png

预处理是将所有的宏和头文件展开,生成的文件我们仍然能读懂。

编译是检查语法错误,语义是否能被正确解读。在这里,nullptr的使用并没有导致语法的错误,也不会产生歧义,所以编译这里不会报错。

汇编是将所有代码转化为二进制的机器指令。

 

同样,在链接时也不会出现问题,生成可执行程序。

2c8fc04bb2b749158c11caf25be24040.png

最终是因为越界访问导致程序崩溃。

注意导致程序崩溃的原因是对空指针的指向区域进行的访问或修改。如果不进行这些操作,那么就是可行的。引用就是个很好的例子。

49b2bf8382bc4d2797ee70ae58b23e66.png

因为引用的语法和实质层面的不一致,导致这里很容易被误解为对a进行了访问但因为引用的实质是指针,所以这里只是将a的值nullptr赋给了b,并没有对nullptr产生访问行为,所以这里的程序不会有任何问题。

669b1a8949fa435fa0a80110ea875e29.png

在观察程序崩溃时,先看语法层面上是否造成歧义,导致编译错误;再看要使用的函数是否都成功定义且定位,这关系到链接错误;最后从汇编的角度来看程序是否发生了越界访问等。

3.不同的this指针区分不同的对象

this是一个关键字。它是隐含在类中的一种指针,在对该类实例化出多个对象时,this指针就用来给每个对象贴上标签。

为了理解它,先看一段代码,解释为什么两次调用Add时都没有传参,但在类里面调用函数时还是能区分不同的对象:


#include <iostream>
using namespace std;

class C
{
public:

	void Init(int a = 0, int b = 0, int c = 0)
	{
		_a = a, _b = b, _c = c;
	}

	void Add()
	{
		cout << "add:" << _a + _b + _c << endl;
	}


private:

	int _a;
	int _b;
	int _c;
};

int main()
{
	C c1, c2;

	c1.Init(1, 2, 3);
	c2.Init(4, 5, 6);
	c1.Add();
	c2.Add();

	return 0;
}

结果是:

e366a1ee1fda40539969e4c09dd632db.png

其中虽然我们看上去没有传参,但是在汇编代码中我们就可以看到实际上Init和Add都多传了一个参数,这个参数就是this指针,在这里它的类型是C* const c1,C* const c2:

bcc4ad6f55be4795b92b2be5c4e36d35.png

70db2ff4719d43828331662fdfec1213.png

在代码语义上,它们相当于在参数中,多传了一个类的指针:

24f152a837b24123a120b93efabbebfb.png

e529727515bf495c92f9340fb7d87216.png

在实际写代码的时候,虽然this指针实际存在,但参数中却不能写this,因此这里使用注释来表示实际的执行情况。但是,在成员函数内部,可以显式写出this指针,因为this指针其实是类的指针,而类又和struct同源,所以用的是this->形式。

又因为this是成员的标签,它也不能随便更换其指向。

6602a6658e09482e9e60f6a4bd363687.png

 

this指针可能存在栈中,也可能存在寄存器中,不同编译器有不同的做法。但this指针都不会存在成员中。这可以用空类来进行验证。

b301fbf6401c46a2ac6fb6cb8c9d2440.png

如果指针确实存在成员中,那么这个类的大小至少是8(64位),所以this指针是单独在栈或寄存器存放的。而空类的大小为1是作为创建类成功的标志,对于没有成员变量的类都是如此

4.this指针为空指针的代码解读

先来看一段代码


#include <iostream>
using namespace std;

class C
{
public:
	void Print()
	{
		cout << "Hello,world!" << endl;
	}
private:
	int _a;
};

int main()
{
	C* c1 = nullptr;

	c1->Print();

	return 0;
}

运行结果是:

2176d1f027ec4888ac87a4ed853ee344.png

可以看出这个代码执行没有任何问题。

这要结合上面第2点对空指针的分析方法来判断。分析如下图:

425fab13e4ce4694a86eb4cc87eba162.png
 

 

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值