自引用:this

在每个类的上下文中,其方法代码中都存在一个特殊引用,指向当前对象本身:this.它本质上是一个隐式定义的变量。所有使用对象变量的方法也同样适用于 this。具体而言,您可以通过取消引用 this 来访问对象字段或调用某个方法。例如,在 Shape 类的一个方法中,以下两条语句是等效的(为方便演示,我们仅以 draw 方法为例):

class Shape
{
   ...
   void draw()
   {
      backgroundColor = clrBlue;
      this.backgroundColor = clrBlue;
   }
};

如果同一上下文内存在其他同名的变量或参数,可能需要使用长形式。虽然通常不建议这样做,但在必要时,this 关键字可让您引用对象的重写成员。

如果任何局部变量或方法参数的名称与类成员变量的名称重叠,则编译器会发出警告。

在以下假设示例中,我们实现了 draw 方法,该方法接受一个可选的字符串参数 backgroundColor,表示颜色名称。由于参数名称与 Shape 类成员同名,编译器会发出第一个警告“'backgroundColor’ 的定义隐藏了该字段”。

这种重叠的后果是,后面错误地赋值 clrBlue 时,实际操作的是参数而不是类成员。由于值和参数类型不匹配,编译器将发出第二个警告“数字到字符串隐式转换”(这里的数字是常量 clrBlue)。但是,this.backgroundColor = clrBlue 行将值写入对象的字段。

   void draw(string backgroundColor = NULL//warning 1:
                // declaration of 'backgroundColor' hides member
   {
      ...
      backgroundColor = clrBlue// warning 2:
        // implicit conversion from 'number' to 'string'
      this.backgroundColor = clrBlue// ok
      
      {
         bool backgroundColor = false// warning 3:
             // declaration of 'backgroundColor' hides local variable
         ...
         this.backgroundColor = clrRed// ok
      }
      ...
   }

随后(在嵌套的花括号块中)定义的局部布尔变量 backgroundColor 再次重写了之前对该名称的定义(这就是我们收到第三个警告的原因)。然而,通过取消引用 thisthis.backgroundColor = clrRed 语句仍会引用对象字段。

如果没有指定 this,编译器会一律选择上下文中最接近的名称定义。

还有另一种需要使用 this 的情况:将当前对象作为参数传递给另一个函数。特别是,采用了一种方法,其中同一个类的对象负责创建/删除另一个类的对象,而从属对象必须知道其“上级”。然后,使用构造函数在“上级”类中创建从属对象,并将“上级”对象的 this 传递给它。这种技术通常使用动态对象分配和指针,因此 指针一节中显示了相关示例。

this 的另一个常见用途是从成员函数返回指向当前对象的指针。这使得我们可以链式调用多个成员函数。由于我们尚未深入学习指针,目前只需要了解,指向某个类对象的指针是通过在该类名后添加星号 * 来声明的,并且可以通过指针像直接操作对象一样对其进行操作。

例如,我们可以为用户提供几种单独设置形状属性的方法:更改颜色、水平或垂直移动。其中每个方法都将返回指向当前对象的指针。

   Shape *setColor(const color c)
   {
      backgroundColor = c;
      return &this;
   }
   
   Shape *moveX(const int x)
   {
      coordinates.x += x;
      return &this;
   }
 
   Shape *moveY(const int y)
   {
      coordinates.y += y;
      return &this;
   }

然后,可以方便地链式调用这些方法。

   Shape s;
   s.setColor(clrWhite).moveX(80).moveY(-50);

当一个类拥有大量属性时,这种方式能够让您以简洁且有选择性地配置对象。

类定义一节中,我们曾尝试记录一个对象变量,但发现只能在 Print 调用中使用其名称并加上 & 符号来获取一个指针,或者更确切地说,是一个唯一的数字标识符(句柄)。在对象上下文中,同样的句柄也可以通过 &this 来获取。

为了方便调试,您可以通过对象的描述符来识别对象。接下来我们将探讨类继承,尤其是存在多个类继承时,这种识别机制将非常有用。因此,在所有的构造函数和析构函数中,我们都添加(并且日后在派生类中也会添加)以下 Print 调用:

   ~Shape()
   {
      Print(__FUNCSIG__" ", &this);
   }

现在,所有创建和删除步骤都将在日志中标记上类名和对象编号。

我们在 Pair 结构体中实现了类似的构造函数和析构函数,但遗憾的是,结构体不支持指针,即无法写入 &this。因此,我们只能通过其内容(在本例中,是通过其坐标)来识别这些结构体:

struct Pair
{
   int xy;
   Pair(int aint b): x(a), y(b)
   {
      Print(__FUNCSIG__" "x" "y);
   }
   ...
};