Verwendung des this-Zeigers in C++

Muhammad Husnain 12 Oktober 2023
  1. Verwendung des this-Zeigers in C++
  2. Unterschied zwischen this und *this in C++
Verwendung des this-Zeigers in C++

In diesem Tutorial werden wir zuerst das Konzept des this-Zeigers verstehen.

Anschließend demonstrieren wir die Verwendung anhand von Beispielen. Zuletzt werden wir sehen, wie sich this von *this unterscheidet.

Verwendung des this-Zeigers in C++

Der this-Zeiger ist einfach ein implizit verfügbarer Zeiger, der in einem nicht statischen Funktionsumfang einer Klasse auf das aufrufende Objekt verweist oder darauf zeigt. Wenn Sie es per Definition nicht verstanden haben, schauen wir uns einige Grundlagen an, um das Konzept zu verstehen.

Wir wissen, dass alle nicht statischen Datenelemente eine separate Kopie für jedes Objekt haben. Der Member-Funktionscode wird jedoch von allen Objekten gemeinsam genutzt.

Alle Objekte derselben Klasse greifen auf dieselbe Funktionsdefinition aus dem Codesegment zu, wenn sie es aufrufen müssen. Der Compiler muss die Informationen des aufrufenden Objekts (das gerade eine Elementfunktion aufruft) kennen, um auf das richtige Datenelement zuzugreifen oder es zu aktualisieren, das für dieses bestimmte aufrufende Objekt spezifisch ist.

Wie erhält der Compiler also die aufrufenden Objektinformationen, wenn die Funktionsdefinitionen gleich sind?

Die einfache Antwort lautet, dass der Compiler während der Kompilierung des Programms automatisch einen versteckten impliziten Zeigerparameter in den Memberfunktionen hinzufügt. Dieser implizite Zeiger ist als this-Zeiger bekannt.

Immer wenn eine Mitgliedsfunktion durch ein bestimmtes Klassenobjekt aufgerufen wird, wird das aufrufende Objekt automatisch als verstecktes Argument an den this-Zeiger geliefert. Jetzt kann der Compiler diesen Zeiger verwenden, um auf das richtige Datenelement zuzugreifen oder es zu modifizieren, das für das aufrufende Objekt spezifisch ist.

Notiz
Der this-Zeiger ist bei den statischen Mitgliedern der Klasse nicht erforderlich, da diese Mitglieder (Funktionen und Datenmitglieder) von allen Objekten gemeinsam genutzt werden.

Die Aktualisierung oder der Zugriff auf die statischen Mitglieder ist nicht objektspezifisch. Wir können sogar auf diese Mitglieder zugreifen, ohne ein Objekt der Klasse zu erstellen.

Daher verwendet der Compiler bei diesen Membern nicht den Operator this.

Schauen wir uns das Codebeispiel an, um zu verstehen, warum wir sagen, dass der Compiler implizit den Zeiger this verwendet, und wann es praktisch ist.

#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;
}

Das obige Codebeispiel definiert eine Person-Klasse mit zwei verschiedenen Elementfunktionen, um das private Datenelement Name zu drucken. Die erste Anweisung in der Funktion main() erzeugt ein Person-Objekt und übergibt Alexa als Argument an den Konstruktor.

Jetzt hilft this->Name im Konstruktor dem Compiler, zwischen dem lokalen Parameter Name und dem privaten Datenelement Name zu unterscheiden.

Der nachfolgende Code von main() ruft printName() und printName1() über P auf (d. h. P wird zum aufrufenden Objekt).

Ausgabe:

Alexa
Alexa

Die Ausgabe beider Funktionen ist die gleiche. Denn der Compiler stellt this-> implizit Name in PrintName1() voran.

Unterschied zwischen this und *this in C++

Bisher sind wir mit dem Konzept klar, dass this ein Zeiger auf ein Objekt ist. Bei einem obj vom Typ Person ist this vom Typ Person*.

Beachten Sie auch, dass der this-Zeiger immer ein rvalue ist, der nicht geändert werden kann. *this dereferenziert jedoch den this-Zeiger.

Nachdem wir einen ausreichenden Hintergrund durchgegangen sind, schauen wir uns einen Beispielcode an, um den Unterschied zwischen this und *this zu verstehen.

#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;
}

Dieses Code-Snippet erstellt eine Counter-Klasse mit mehreren Methoden zu GetCount-Methoden. Die erste Methode (d. h. GetCount_Pointer) gibt den Wert des this-Zeigers zurück, der einfach eine Adresse des aufrufenden Objekts ist.

Die Methode GetCount_Copy gibt *this zurück, während der Rückgabetyp ein Objekt vom Typ Counter ist. Diese Funktion gibt also eine tiefe Kopie des aufrufenden Objekts zurück, was bedeutet, dass das Ändern der zurückgegebenen Kopie keine Auswirkungen auf die ursprüngliche Kopie hat.

Die letzte Methode GetCount_Reference gibt ebenfalls *this zurück, aber der Rückgabetyp dieser Methode ist ein Referenzobjekt (d. h. Counter& ). Anstatt eine neue tiefe Kopie zu erstellen, gibt der Compiler einen Alias ​​oder Verweis auf das ursprüngliche Objekt zurück.

Alle Änderungen, die über den Alias ​​vorgenommen werden, werden auch im ursprünglichen Objekt widergespiegelt.

Bevor wir den Haupttreibercode besprechen, schauen wir uns die Ausgabe des Programms an.

Ausgabe:

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

Abschnitt-A der Methode main deklariert zuerst das Objekt C1 vom Typ Counter und erhöht dessen Count durch IncreaseCount(). Später initialisiert es einen CounterPtr-Zeiger mit der von C1.GetCount_Pointer() zurückgegebenen Adresse.

Nun zeigt der CounterPtr auf C1. Daher ändert der Aufruf der Inkrementfunktion durch CounterPtr auch das C1.

C2 = C1.GetCount_Copy() in Abschnitt-B kopiert den gesamten aktuellen Inhalt von C1 tief in C2, da C1.GetCount_Copy() durch eine Kopie von C1 ersetzt wird. Daher wirkt sich eine Erhöhung der Anzahl für C1 nicht auf C2 aus.

Der Abschnitt-C deklariert eine Referenzvariable vom Typ Counter und initialisiert sie mit dem, was von C1.GetCount_Reference() zurückgegeben wird. Da C1.GetCount_Reference() einen Alias von C1 zurückgibt, wird CounterRef zu einem anderen Namen für C1.

Die Anweisung Counter& CounterRef = C1.GetCount_Reference(); wird logisch äquivalent zu Counter& CounterRef = C1.

Muhammad Husnain avatar Muhammad Husnain avatar

Husnain is a professional Software Engineer and a researcher who loves to learn, build, write, and teach. Having worked various jobs in the IT industry, he especially enjoys finding ways to express complex ideas in simple ways through his content. In his free time, Husnain unwinds by thinking about tech fiction to solve problems around him.

LinkedIn

Verwandter Artikel - C++ Pointer