理解C++中的this指针

突然感觉this指针在C++中太常见了,但是从来没有很仔细的去思考过这个东西,(也可能是思考过但是忘了,没错,脑子就是这么不好用),所以来记录一下。


参考博客:

https://www.cnblogs.com/duwenxing/p/7410687.html

https://www.cnblogs.com/liushui-sky/p/5802981.html

https://blog.csdn.net/fuzhongmin05/article/details/59112081/


一、什么是this指针

思考:C++是如何给一个类对象分配内存的?看如下代码:

#include <iostream>
using namespace std;

class Student
{
public:
    void CountTotal();
    void GetName(char name[]);
    void GetAmount();
    void GetPrice();
    void GetTotal();
private:
    char Name[24]; //24 bytes
    int Amount; //4 bytes
    float Price; //4 bytes
    float Total; //4 bytes
};

int main()
{
    Student stu;
    cout << "对象stu占的字节数: " << sizeof(stu) << endl;
    system("pause");
    return 0;
}

由上例可以看出,当用类Student定义一个对象stu时,内存开辟36个字节的空间用来存储这个对象,而内存开辟的空间刚好分配给了这个对象的四个成员变量(Name、Amount、Price、Total),那么问题来了,成员函数所占的内存空间为什么在对象定义时没有分配给所定义的对象呢?

其实原因很简单,并不是一个对象对应一个单独的成员函数体,而是此类的所有对象共用这个函数体。当程序被编译之后,此成员函数的地址就已经确定了。如果为每个类的对象都开辟空间来存储这个对象的成员函数,由于每个对象的成员函数的函数体代码都相同,只是所要传递的实参不一样,这样做会造成内存的浪费。那么类的成员函数又是如何识别不同对象传递过来的实参呢?没错,就是靠this指针。

二、this指针的本质

我们先来看一个struct的例子

#include <iostream>
#include <string.h>
using namespace std;

struct Student 
{
	string Name;
	int Age;
	string Gender;
};

//对对象进行初始化
void Init(Student *_this, string name, int age, string gender)
{
	_this->Name = name;
	_this->Age = age;
	_this->Gender = gender;
}

int main()
{
	Student stu1, stu2, stu3;
	Init(&stu1, "Tomwenxing", 23, "male");
	Init(&stu2, "Jack", 22, "male");
	Init(&stu3, "Ellen", 18, "female");

	system("pause");
	return 0;
}

如上例,_this指针的作用是指向需要进行操作的对象,其作用和C++类中的this指针相似,只不过类中的this指针是隐式的插入在类中的成员函数中,不需要编程者手动的定义,以类Student中的成员函数GetAmount为例:

#include <iostream>
using namespace std;


class Student
{
public:
	void CountTotal();
	void GetName(char name[]);
	int GetAmount();
	void GetPrice();
	void GetTotal();

private:
	char Name[24];
	int Amount;
	float Price;
	float Total;
};

//相当于void GetAmount(Student* this)
int Student::GetAmount()
{
	//相当于return this->return
	return Amount;
}

int main()
{
	Student stu;
	cout << "对象stu占的字节数:" << sizeof(stu) << endl;
	cout << stu.GetAmount() << endl;
	//相当于GetAmount(&stu)
	system("pause");
	return 0;
}

那这就是this指针的本质的全部么?既然this指针的作用是指向需要进行操作的对象,并且是隐式的自动插入在类中的成员函数中,那我们在定义类的成员函数时是否可以手动的更改this指针的指向呢?

#include <iostream>
using namespace std;

class Student
{
public:
    void CountTotal();
    void GetName(char name[]);
    int GetAmount();
    void GetPrice();
    void GetTotal();
private:
    char Name[24]; //24 bytes
    int Amount; //4 bytes
    float Price; //4 bytes
    float Total; //4 bytes
};

int Student::GetAmount()
{
    this = NULL; //错误,C++不允许更改this指针的指向
    return Amount;
}

int main()
{
    Student stu;
    cout << "对象stu占的字节数: " << sizeof(stu) << endl;
    system("pause");
    return 0;
}

由上可知是不允许的,因为this指针是一个指针常量,即this指针一旦初始化,它就再也不能改变自己的指向,将会始终指向自己需要操作的对象,仍以Student类中的GetAmount为例:

//相当于void GetAmount(Student * const this)
int Student::GetAmount()
{
    //相当于return this->return;
    return Amount;
}

(常量指针和指针常量参考这篇博客)

因此this指针的本质是一个隐式插入类成员函数体中,用来指向需要操作的对象的指针常量。

ok,最后再来个简单明了的地址测试,毕竟搞C++,学会看地址行事是必须的本事~

#include <iostream>
using namespace std;

class A
{
public:
    int get() const
    {
        return i;
    }
    void set(int x)
    {
        this->i = x;
        cout << "this 指针保存的内存地址为:" << this << endl;
    }
private:
    int i;
};

int main()
{
    A a;
    a.set(9);
    cout << "对象a所在的内存地址为: " << &a << endl;
    cout << "对象a所保存的值为: " << a.get() << endl;
    cout << endl;

    A b;
    b.set(999);
    cout << "对象b所在的内存地址为: " << &b << endl;
    cout << "对象b所保存的值为: " << b.get() << endl;

    system("pause");
    return 0;
}

通过这个输出结果,我们可以看到,对象a的内存地址和this指针的一模一样(都是0017F7E8);而当运行到对象b的时候,它的内存地址又和它所对应的this指针指向的内存地址一模一样了(都是0017F7DC)。这就说明了this指针变量记录的是当前对象的内存地址,即this指针指向当前的对象!

总结:

  • 每个类对象都有自己的隐式this指针,编译器给你搞定了
  • this指针指向的内存地址就是这个类对象的内存地址(所以每个类对象的地址不一样,它们的this指针也不一样的哦)
  • 可以认为成员函数是属于类的,而不是属于对象的,从计算类对象所占据内存大小上可以看出,成员变量算上了,但是成员函数安全没有计算上。

 

其实说到了这里,还需要更加深入一下,那就是从内存的角度来理解这里的东西。毕竟C++的优势就是强大的内存管理能力。

C++程序的内存格局通常分为四个区:全局数据区(data area),代码区(code area),栈区(stack area),堆区(heap area)(即自由存储区)

全局数据区存放全局变量,静态数据和常量;所有类成员函数和非成员函数代码存放在代码区;为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放在栈区;余下的空间都被称为堆区。根据这个解释,我们可以得知在类的定义时,类成员函数是被放在代码区,而类的静态成员变量在类定义时就已经在全局数据区分配了内存,因而它是属于类的。对于非静态成员变量,我们是在类的实例化过程中(构造对象)才在栈区或者堆区为其分配内存,是为每个对象生成一个拷贝,所以它是属于对象的。

 应当说明,常说的“某某对象的成员函数”,是从逻辑的角度而言的,而成员函数的存储方式,是从物理的角度而言的,二者是不矛盾的。

下面我们再来讨论下类的静态成员函数和非静态成员函数的区别:静态成员函数和非静态成员函数都是在类的定义时放在内存的代码区的,因而可以说它们都是属于类的,但是类为什么只能直接调用静态类成员函数,而非静态类成员函数(即使函数没有参数)只有类对象才能调用呢?原因是类的非静态类成员函数其实都内含了一个指向类对象的指针型参数(即this指针),因而只有类对象才能调用(此时this指针有实值)。 

 

回答开头的问题,答案是输出“printA”后,程序崩溃。类中包括成员变量和成员函数。new出来的只是成员变量,成员函数始终存在,所以如果成员函数未使用任何成员变量的话,不管是不是static的,都能正常工作。需要注意的是,虽然调用不同对象的成员函数时都是执行同一段函数代码,但是执行结果一般是不相同的。不同的对象使用的是同一个函数代码段,它怎么能够分别对不同对象中的数据进行操作呢?原来C++为此专门设立了一个名为this的指针,用来指向不同的对象。

需要说明,不论成员函数在类内定义还是在类外定义,成员函数的代码段都用同一种方式存储。不要将成员函数的这种存储方式和inline(内联)函数的概念混淆。不要误以为用inline声明(或默认为inline)的成员函数,其代码段占用对象的存储空间,而不用inline声明的成员函数,其代码段不占用对象的存储空间。不论是否用inline声明(或默认为inline),成员函数的代码段都不占用对象的存储空间。用inline声明的作用是在调用该函数时,将函数的代码段复制插人到函数调用点,而若不用inline声明,在调用该函数时,流程转去函数代码段的入口地址,在执行完该函数代码段后,流程返回函数调用点。inline与成员函数是否占用对象的存储空间无关,它们不属于同一个问題,不应搞混。

至于什么是代码区、常量区、静态区(全局区)、堆区、栈区这些东西,看这个讲的比较好的博客

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 终极编程指南 设计师:CSDN官方博客 返回首页