【C++11 笔记】关键字剖析 —— this

目录

一、this 和 Python-self

二、this 指针

2.1 this 与 类成员

2.2 this 与 const 成员函数

2.3 在成员函数中返回 this

三、小结

3.1 this 指针再叙

3.2 this 指针用途

3.3 this 指针使用


一、this 和 Python-self

在 Python 中定义类时,无论是显式创建类的构造方法,还是向类中添加实例方法,都要求将 self 参数作为方法的第一个参数。当然,之所以命名为 self,只是程序员间约定俗成的一种习惯,使代码更具可读性。同一个类可实例化为多个对象,当某个类实例对象调用方法 (即“成员函数”) 时,它会把自身的引用作为第一个参数自动传给该方法,换言之,Python 会自动绑定类方法的第一个参数 self 指向调用该方法的对象。如此,Python 解释器就能知道到底要操作哪个对象的方法了。因此,程序在调用类实例方法和构造方法时,无需手动为第一个参数传值。抛砖引玉,Python 类方法中的 self 参数就相当于 C++ 中的 this 指针。更具体地:

在 C++ 中,每一个类实例对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数 (即“方法”) 的隐含参数。因此,在成员函数内部,this 指针 可用于指向调用该成员函数的类实例对象

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


二、this 指针

2.1 this 与 类成员

首先,看个例子:

#include <iostream>

using namespace std;

class Banana
{
public:
    // const member function
    int show_num_banana() const { return num_banana; }

private:
    // data member
    int num_banana = 0;
};

int main()
{   
    Banana obj1;
    cout << obj1.show_num_banana() << endl;

    system("pause");
    return 0;
}
0

在上例中,先实例化一个 Banana 类对象 obj1,然后通过点运算符来访问 obj1 对象的常成员函数(即“访问函数”)show_num_banana,然后调用它来访问私有数据成员 num_banana。

其实,当调用类的成员函数时,实质是替某个类的实例对象调用它。如果 show_num_banana 指向 Banana 的成员(例如 num_banana),则它 隐式地指向调用该成员函数的类实例对象的成员。在上例中,当 show_num_banana 返回 num_banana 时,实际上它隐式地返回了 obj1.num_banana

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

// 伪代码, 用于说明调用成员函数的实际执行过程
Banana::show_num_banana(&obj1)
// 其中, 调用 Banana 的 show_num_banana 成员时传入了类实例 obj1 的地址

在成员函数内部,则可直接使用调用该成员函数的对象的成员,而无须通过成员访问运算符来做到这一点,因为 this 所指的正是这个对象任何对类成员的直接访问都被看作是对 this 的隐式引用。换言之,当 show_num_banana 使用 num_banana 时,它 隐式地使用 this 所指向的成员(即 调用 show_num_banana 的类实例对象的成员),就像是书写了 this->num_banana 一样。

对用户而言,this 形参是隐式定义的。实际上,任何自定义名为 this 的参数或变量的行为都是非法的。成员函数体内,用户可显式地使用 this。因此,尽管没必要,还是能把 show_num_banana 定义成如下形式:

int show_num_banana() const { return this->num_banana; }

因为 this 的目的是 总指向 调用类成员的类实例对象,所以 this 是一个 (顶层) 常量指针,不允许改变 this 中保存的地址


2.2 this 与 const 成员函数

注意,常成员函数中,紧随形参列表之后的 const 的关键字,其作用是 修改隐式 this 指针的类型

默认情况下,this 的类型是 指向非常量类类型的常量指针(即顶层常量指针,指针本身是常量,指向非常量)。例如,Banana 中,若 show_num_banana 是非常成员函数,则其 this 的类型是 Banana *const(切记不是 Banana const* !!! 二者完全不同)。尽管 this 是隐式的,也仍需遵循初始化规则,因此默认情况下,不能把 this 绑定/指向到一个常量对象上,导致 无法在一个常量对象上调用普通的成员函数(注意,只有常成员函数可以操作常量/常对象)

为此,需要把 this 声明成 const Banana *const 类型 以便于 this 可以指向常量。然而,this 不会出现在成员函数的参数列表中。于是,C++ 允许令 const 紧随成员函数的参数列表之后,以表明 this 是一个指向常量的指针,此时的 const 成员函数即为 常成员函数 (const member function)。这也是常成员函数声明中,形参列表后 const 关键字的由来。

此时,不妨把 show_num_banana 的函数体想象成如下形式以便理解:

// 伪代码, 用于说明隐式 this 指针如何使用
// 以下代码非法, 因为用户无法显式自定义 this 指针, 当然也不会出现在成员函数的参数列表中
// 谨记此处的 this 是一个指向常量的指针,因为 show_num_banana 是一个常量成员
int Banana::show_num_banana(const Banana *const this) { return this->show_num_banana; }  // 形参列表中, 靠右的是顶层 const, 靠左的是底层 const

可见,因为常成员函数的 this 是指向常量的指针(底层 const)所以常成员函数不能改变调用它的类实例对象的内容,这也是常成员函数又称为 “访问函数” 的原因(与之相对的是 “修改函数”)。相应地,上例的 show_num_coil 可读取调用它的类实例对象如 obj1 的数据成员,但不能更新或写入新值。

总之,常量对象、常量对象的引用、常量对象的指针,都只能调用常量成员函数


2.3 在成员函数中返回 this

现在,再看一个内容稍丰富些的例子:

#include <iostream>

using namespace std;

class Banana
{
public:
    // const member function
    int show_num_banana() const { return num_banana; }
    // common member function decleration
    Banana& combine(const Banana&);

public:
    // data member
    int num_banana = 0;
    double revenue_banana = 0.0;
};

Banana& Banana::combine(const Banana& rhs)
{   
    // 把 rhs 的数据成员加到 this 对象的数据成员上
    num_banana += rhs.num_banana;
    revenue_banana += rhs.revenue_banana;
    // 返回调用该成员函数的对象
    return *this;
}

int main()
{   
    Banana obj1;
    obj1.num_banana = 2;
    obj1.revenue_banana = 4.2;

    Banana obj2;
    obj2.num_banana = 3;
    obj2.revenue_banana = 8.3;

    obj1.combine(obj2);  // 相当于 in-place 操作
    cout << obj1.num_banana << " " << obj1.revenue_banana << endl;

    system("pause");
    return 0;
}
5 12.5

可见,成员函数 combine 的设计类似于 += 的用途,但针对的是类实例对象(的数据成员)。当执行 obj1.combine(obj2); 时,obj1 的地址被绑定到隐式的 this 参数上,而 rhs 绑定到了 obj2 上。因此,当成员函数内执行 num_banana += rhs.num_banana; 时,效果等同于 obj1.num_banana += obj2.num_banana。

其实,成员函数 combine 最值得关注的部分是其 返回类型 和 返回语句

一方面,当定义的函数类似某个内置运算符时,应令其行为尽量模仿该运算符。内置的赋值运算符将其左侧运算对象当成左值返回,因此,为保持一致,combine 必须返回引用类型,对应为 Banana&。

另一方面,无需使用隐式的 this 指针访问函数调用者的某个具体成员,而是需要把调用函数的对象当成一个整体来访问 —— return *this;  其中,return 解引用 this 指针以获取调用和执行本函数的类实例对象。换言之,上述语句返回 obj1 的引用。


三、小结

3.1 this 指针再叙

  1. this 实际是成员函数的一个形参,在调用成员函数时将类实例对象的地址作为实参传递给 this。但 this 形参是隐式的,并不出现在代码中,而是在编译阶段由编译器默默地将其添加到成员函数参数列表中。
  2. this 作为隐式形参,本质上是成员函数的局部变量,所以只能用在成员函数内,且只有在通过类实例对象调用成员函数时才给 this 赋值。此外,用户无法显式定义 this 指针。
  3. this 默认为指向非常量的常量指针(顶层 const),其保存的之(所指向的类实例对象的地址)不可修改。
  4. 只有当类实例对象被创建后 this 才有意义,因此不能在 static 成员函数中使用,因为 static 成员函数与类相关联,而与具体的类实例对象无关。
  5. 在《C++函数编译原理和成员函数的实现》一节中讲到:成员函数最终被编译成与对象无关的普通函数,会丢失除成员变量外的所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。而这个额外的参数就是 this,它是成员函数和成员变量关联的桥梁。

3.2 this 指针用途

  1. 一个类实例对象的 this 指针并非该对象本身的一部分,不会影响 sizeof(对象) 的结果。
  2. this 作用域限于类内部,当 在类的非静态成员函数中 访问类的非静态成员 时,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。换言之,即使没有显式写上 this 指针,编译器编译时也会加上 this。this 作为非静态成员函数的隐式形参,对各成员的访问均通过它进行。this 在成员函数的开始执行前构造,在成员的执行结束后清除。

3.3 this 指针使用

  1. 在类的非静态成员函数中返回类实例对象本身时,直接使用 return *this。
  2. 当参数与成员变量同名时,如 this->n = n (不能写成 n = n)。

参考文献

《C++ Primer 5th》

https://light-city.club/sc/basic_content/static/

https://www.runoob.com/cplusplus/cpp-this-pointer.html

http://c.biancheng.net/view/2226.html

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页