HOWTO · C++

在 C++ 中使用 this 指標

本教程簡要介紹了此指標及其在 C++ 中的需求和用途。它通過示例進一步解釋了 this 和 *this 之間的區別。

在本教程中,我們將首先了解 this 指標的概念。

然後,我們將藉助示例演示其用途。最後,我們將看到 this*this 有何不同。

在 C++ 中使用 this 指標

this 指標只是一個隱式可用指標,在非靜態類成員函式範圍內,它引用或指向呼叫物件。如果你沒有通過定義得到它,那麼讓我們看一些基礎知識來理解這個概念。

我們知道所有非靜態資料成員對每個物件都有一個單獨的副本。但是,成員函式程式碼在所有物件之間共享。

同一類的所有物件在需要呼叫時都從程式碼段訪問相同的函式定義。編譯器需要知道呼叫物件(當前呼叫成員函式的物件)資訊以訪問或更新特定於該特定呼叫物件的正確資料成員。

那麼,在函式定義相同的情況下,編譯器如何獲取呼叫物件資訊呢?

簡單的回答是,在程式的編譯過程中,編譯器會自動在成員函式中新增一個隱藏的隱式指標引數。這個隱式指標被稱為 this 指標。

每當通過特定類物件呼叫成員函式時,呼叫物件會自動作為隱藏引數提供給 this 指標。現在,編譯器可以使用這個指標來訪問或修改特定於呼叫物件的正確資料成員。

注意
類的靜態成員不需要 this 指標,因為這些成員(函式和資料成員)在所有物件之間共享。

對靜態成員的更新或訪問並不特定於任何物件。我們甚至可以在不建立類物件的情況下訪問這些成員。

因此,編譯器不會對這些成員使用 this 運算子。

讓我們看一下程式碼示例,以瞭解為什麼我們說編譯器隱式使用 this 指標以及何時使用它很方便。

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

class Person {
 private:
  string Name;

 public:
  Person(string Name) {
    this->Name = Name;  // this->Name is the private member for this object
  }
  void PrintName() { cout << this->Name << endl; }
  void PrintName1() { cout << Name << endl; }
};
int main() {
  Person P("Alexa");
  P.PrintName();
  P.PrintName1();
  return 0;
}

上面的程式碼示例定義了一個具有兩個不同成員函式的 Person 類來列印私有資料成員 Namemain() 函式中的第一條語句生成一個 Person 物件並將 Alexa 作為引數傳遞給建構函式。

現在,建構函式中的 this->Name 幫助編譯器區分本地引數 Name 和私有資料成員 Name

main() 的後續程式碼通過 P 呼叫 printName()printName1()(即 P 成為呼叫者物件)。

輸出:

Alexa
Alexa

兩個函式的輸出是一樣的。這是因為編譯器在 PrintName1() 中隱含地在 this-> 前面加上 Name

C++ 中 this*this 的區別

到目前為止,我們已經清楚 this 是指向物件的指標的概念。對於 Person 型別的 objthisPerson*型別。

要記住的另一件事是 this 指標始終是一個 rvalue,不能修改。然而,*this 取消引用 this 指標。

在經歷了足夠的背景之後,讓我們看一個示例程式碼來了解 this*this 之間的區別。

#include <iostream>
using namespace std;

class Counter {
 private:
  int Count;

 public:
  Counter() { this->Count = 0; }
  void IncreaseCount() { Count++; }
  void PrintCount() { cout << this->Count << endl; }
  Counter* GetCount_Pointer() { return this; }
  Counter GetCount_Copy() { return *this; }
  Counter& GetCount_Reference() { return *this; }
};
int main() {
  // Section-A
  cout << "Sectio-A" << endl;
  Counter C1;
  C1.IncreaseCount();
  Counter* CounterPtr =
      C1.GetCount_Pointer();  // CounterObj will be pointing to C1
  CounterPtr->IncreaseCount();
  C1.PrintCount();

  // Section-B
  cout << "Section-B" << endl;
  Counter C2;
  C2 = C1.GetCount_Copy();
  C1.IncreaseCount();
  C1.PrintCount();
  C2.PrintCount();

  // Section-C
  cout << "Section-B" << endl;
  Counter& CounterRef = C1.GetCount_Reference();
  CounterRef.PrintCount();

  return 0;
}

此程式碼片段建立了一個 Counter 類,其中包含多個 GetCount 方法的方法。第一個方法(即 GetCount_Pointer)返回 this 指標的值,它只是呼叫物件的地址。

GetCount_Copy 方法返回 *this 而返回型別是 Counter 型別的物件。因此,此函式將返回撥用物件的深層副本,這意味著修改返回的副本不會影響原始副本。

最後一個 GetCount_Reference 方法也返回 *this,但此方法的返回型別是一個引用物件(即 Counter&)。編譯器不會建立新的深層副本,而是返回對原始物件的別名或引用。

通過別名所做的任何更改也將反映在原始物件中。

在討論主驅動程式碼之前,讓我們看一下程式的輸出。

輸出:

Sectio-A
2
Section-B
3
2
Section-B
3

main 方法的 A 部分首先宣告 Counter 型別的 C1 物件並通過 IncreaseCount() 遞增其 Count。稍後,它用 C1.GetCount_Pointer() 返回的地址初始化一個 CounterPtr 指標。

現在,CounterPtr 將指向 C1。因此,通過 CounterPtr 呼叫增量函式也會修改 C1

B 部分中的 C2 = C1.GetCount_Copy()C1 的所有當前內容深度複製到 C2,因為 C1.GetCount_Copy()C1 的副本替換。因此,增加 C1 的計數不會影響 C2

C 部分宣告瞭一個 Counter 型別的引用變數,並使用 C1.GetCount_Reference() 返回的任何內容對其進行初始化。由於 C1.GetCount_Reference() 返回 C1 的別名,因此 CounterRef 成為 C1 的另一個名稱。

語句 Counter& CounterRef = C1.GetCount_Reference(); 邏輯上等價於 Counter& CounterRef = C1