类成员函数的this指针
前言
因为目前很多小伙伴对类的构造函数不是很理解,为了大家便于理解我这里暂且使用成员函数对类成员变量赋初值,下一篇博客我会详细介绍构造函数,敬请期待!最后本人能力有限,如果文章有出现错误的地方欢迎指出。
一、this指针的引出
下面我们先看这段代码:
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void func()
{
cout << this << endl;
cout << "func" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2;
d1.Init(2023, 7, 10);
d2.Init(2022, 5, 14);
d1.func();
cout << "----------------------------" << endl;
d2.func();
return 0;
}
运行结果:
对于上述类,有这样一个问题:
问题:Date类函数体中没有关于不同对象的区分,那当d1调用Init函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?
解答:C++通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数”(静态成员函数没有this指针)增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户都是透明的,即用户不需要来传递,编译器自动完成。
我们也可以从上面的运行结果中确实每个成员函数都有一个隐藏的this指针,并且不同对象里成员函数的this指针也不一样。
下面我将类里面的this指针标出,大家可以以此借鉴。
class Date
{
public:
void Init(Date* this, int year, int month, int day)
{
//因为这里
this._year = year;
this._month = month;
this._day = day;
}
void func(Date* this)
{
cout << this << endl;
cout << "func" << endl;
}
private:
int _year;
int _month;
int _day;
};
二、 this指针的特性
1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
2. 只能在“成员函数”的内部使用
3. this指针的本质上是“成员函数”的形参,当对象调用成员函数时,会将对象的地址作为实参传递给this形参。所以对象不存储this指针。
4. this指针是“成员函数“第一个隐含的指针形参,一般情况下由编译器通过ecx寄存器自动传递,不需要用户传递。
【典型问题】
- this指针存放在哪里?
this指针是成员函数的隐含形参,所以存放在栈上/ vs下是通过ecx寄存器 - 空指针问题
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void func()
{
cout << this << endl;
cout << "func" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date* ptr = nullptr;
ptr->Init(2022, 2, 2);//运行崩溃
ptr->func(); //正常运行
(*ptr).func;
return 0;
}
【解释】
问题提出:为什么会出现上面的两种不一样的结果呢?同样是拿空指针调用类里面的成员函数,一个是运行崩溃,一个是正常运行。这里我们要分成两点来解释。
解答:我们知道空指针解引用会报错,那为什么ptr -> func()不会报错呢?这里需要提出一个点,就是并不是我们在写代码的时候编写了解引用的代码,编译器在编译阶段就会将指针解引用,我们知道这里ptr -> func()是调用类里的成员函数,所以会将对象的地址作为实参传递给this形参,然而在func()函数中并没有对this指针的解引用,所以这里并不会报错,其实它的含义仅仅是将对象的地址作为实参传递给成员函数的this指针。因此我如果将Init()函数也稍加修改,那么就不会运行崩溃了。
class Date
{
public:
void Init(int year, int month, int day)
{
cout << this << endl;
/*_year = year;
_month = month;
_day = day;*/
}
void func()
{
cout << this << endl;
//cout << "func" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date* ptr = nullptr;
ptr->Init(2022, 2, 2);
ptr->func();
return 0;
}
运行结果:
我们发现现在代码可以编译运行了,并且打印出了我们传递进去的ptr的地址。
那我们现在再来看看,如果Init()函数如果不改,是在哪里发生编译错误。
下面是调试的图片:
我们发现其实并没有在ptr解引用的地方报错,反而是在Init()函数里面因为 this 指针为空指针解引用报错,因此也就证明了我们的猜想是正确的,当对象在调用成员函数的时候,并不会发生解引用,而是将对象的地址作为实参传递给成员函数的this指针。