阿里面试复盘

C++中的this指针

在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。

友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。

下面给出一个案例帮助我们更好的理解:

#include 
#include 
using namespace std;

//CPerson类
class CPerson
{
	int m_nAge;//年龄
	string m_strName;//姓名
public:
	//构造函数
	CPerson(int age, string name) : m_nAge(age), m_strName(name) {	};
	//获取年龄
	void GetAge(void)
	{
		cout << "年龄:" << m_nAge << endl;
	}
	//获取姓名
	void GetName(void)
	{
		cout << "姓名:" << m_strName << endl;
	}
};

//结构体类型 TagPerson
struct TagPerson
{
	int m_nAge;//年龄
	string m_strName;//姓名
};
//获取年龄
void GetAge(TagPerson *per)
{
	cout << "年龄:" << per->m_nAge << endl;
}
//获取姓名
void GetName(TagPerson *per)
{
	cout << "姓名:" << per->m_strName << endl;
}

int main()
{
	CPerson 刘德华(55, "刘德华");
	CPerson 李连杰(57, "李连杰");

	TagPerson 周润发;
	周润发.m_nAge = 56;
	周润发.m_strName = "周润发";

	刘德华.GetAge();
	刘德华.GetName();

	GetAge(&周润发);
	GetName(&周润发);

	return 0;
}

 此段代码中声明了一个CPerson类和一个TagPerson结构体类型。在C语言中用一个函数来获取和设置结构体变量, 我们是通过传递这个变量的指针来实现的。那么C++中类成员函数是如何知道哪个对象调用了它?并正确显示调用它的对象的数据呢? 很多书上也都已经介绍了,当一个对象调用某成员函数时会隐式传入一个参数, 这个参数就是this指针。this指针中存放的就是这个对象的首地址。这和C中通过向函数传递结构体变量的地址是不是很像?!只是传参形式不一样罢了! 在C中我们是手工把结构体变量和函数关联起来的,而C++则是编译器帮我们把类数据和成员函数关联起来的并通过名称粉碎和编译时检查等形式防止外部的任意访问。

那么这个this指针存放在哪里呢?其实编译器在生成程序时加入了获取对象首地址的相关代码。并把获取的首地址存放在了寄存器ECX中(VC++编译器是放在ECX中,其它编译器有可能不同)。也就是成员函数的其它参数正常都是存放在栈中。而this指针参数则是存放在寄存器中。类的静态成员函数因为没有this指针这个参数,所以类的静态成员函数也就无法调用类的非静态成员变量。

当上面的代码被编译和执行时,它会产生下列结果:

// 编译执行结果:
年龄:55
姓名:刘德华
年龄:56
姓名:周润发

在C++Primer(第5版)一书中给出了this指针的定义(P231):


当我们调用成员函数时,实际上是替某个对象调用它。

成员函数通过一个名为 this 的额外隐式参数来访问调用它的那个对象,当我们调用一个成员函数时,用请求该函数的对象地址初始化 this。例如,如果调用 total.isbn()则编译器负责把 total 的地址传递给 isbn 的隐式形参 this,可以等价地认为编译器将该调用重写成了以下形式:

//伪代码,用于说明调用成员函数的实际执行过程
class Sales_data;
Sales_data::isbn(&total)

其中,调用 Sales_data 的 isbn 成员时传入了 total 的地址。

在成员函数内部,我们可以直接使用调用该函数的对象的成员,而无须通过成员访问运算符来做到这一点,因为 this 所指的正是这个对象。任何对类成员的直接访问都被看作是对 this 的隐式引用,也就是说,当 isbn 使用 bookNo 时,它隐式地使用 this 指向的成员,就像我们书写了 this->bookNo 一样。

对于我们来说,this 形参是隐式定义的。实际上,任何自定义名为 this 的参数或变量的行为都是非法的。我们可以在成员函数体内部使用 this,因此尽管没有必要,我们还是能把 isbn 定义成如下形式:

std::string isbn() const { return this->bookNo; }

因为this的目的总是指向"这个"对象,所以this是一个常量指针,我们不允许改变this中保存的地址。

C++中类的大小问题

类的实例化:所谓类的实例化就是在内存中分配一块地址

由于对象内可能包含指针,32位系统分配给指针的大小为4个字节,64位系统分配给指针的大小为8个字节

// 以下若无特殊说明均默认为32位系统下的测试结果分析 //

下面先给出一段测试程序:

#include "pch.h"
#include 
using namespace std;

class a {};
class b {};
class c :public a {
	virtual void fun() = 0;
};
class d :public b, public c {};
int main()
{
	cout << "sizeof(a)=" << sizeof(a) << endl;    // sizeof(a)=1
	cout << "sizeof(b)=" << sizeof(b) << endl;    // sizeof(b)=1
	cout << "sizeof(c)=" << sizeof(c) << endl;    // sizeof(c)=4
	cout << "sizeof(d)=" << sizeof(d) << endl;    // sizeof(d)=8
	return 0;
}

接下来我们分析这段代码:

  • 从代码中我们可以直观的分析出,类a与类b均为空类,均不包含任何成员。根据常理来说,两者的对象大小理应为0。但是程序返回结果大小为1。这是为什么呢?这是由于C++标准规定任何一个独立的对象(当然包括类对象)都必须具有非零大小,不同的对象不能具有相同的地址。也就是说类a与类b虽然为空类,但是依旧均可被实例化,每一个实例均有一个独一无二的地址。因此会为每一个实例化的空类申请一个字节的大小存储。
  • 接下来分析类c的大小:已知类c继承于类a,且其中包含一个虚函数,因此c中存有指向虚函数表的指针vptr,所以最后得到大小为4个字节。
  • 最后我们再来分析类d:已知类d多重继承于类b与类c,因此类d中包含类b与类c当中的内容,并且由于存在虚继承,因此类d中需要保存虚基类指针,即指向父类c的指针,记作vptr_d_c。

这里给出总结:

  • 为类的非静态成员数据的类型大小之和.
  • 有编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚函数的指针).
  • 为了优化存取效率,进行的边缘调整.
  • 与类中的构造函数,析构函数以及其他的成员函数无关.

你可能感兴趣的:(C++)