C++

Taxonomie der Ausdruckskategorie in C++

Taxonomie der Ausdruckskategorie in C++

Eine Berechnung ist jede Art von Berechnung, die einem wohldefinierten Algorithmus folgt. Ein Ausdruck ist eine Folge von Operatoren und Operanden, die eine Berechnung angibt. Mit anderen Worten, ein Ausdruck ist ein Bezeichner oder ein Literal oder eine Folge von beiden, verbunden durch Operatoren.Bei der Programmierung kann ein Ausdruck zu einem Wert führen und/oder etwas bewirken. Wenn es zu einem Wert führt, ist der Ausdruck ein glvalue, rvalue, lvalue, xvalue oder prvalue. Jede dieser Kategorien ist ein Satz von Ausdrücken. Jeder Satz hat eine Definition und bestimmte Situationen, in denen seine Bedeutung vorherrscht, und unterscheidet ihn von anderen Sätzen. Jede Menge wird als Wertkategorie bezeichnet.

Hinweis: Ein Wert oder Literal ist immer noch ein Ausdruck, daher klassifizieren diese Begriffe Ausdrücke und nicht wirklich Werte.

glvalue und rvalue sind die beiden Teilmengen aus dem großen Mengenausdruck. glvalue existiert in zwei weiteren Teilmengen: lvalue und xvalue. rvalue, die andere Teilmenge von expression, existiert auch in zwei weiteren Teilmengen: xvalue und prvalue. xvalue ist also eine Teilmenge von glvalue und rvalue: Das heißt, xvalue ist der Schnittpunkt von glvalue und rvalue. Das folgende Taxonomie-Diagramm aus der C++-Spezifikation veranschaulicht die Beziehung aller Mengen:

prvalue, xvalue und lvalue sind die primären Kategoriewerte. glvalue ist die Vereinigung von lvalues ​​und xvalues, während rvalues ​​die Vereinigung von xvalues ​​und prvalues ​​ist.

Sie benötigen Grundkenntnisse in C++, um diesen Artikel zu verstehen; du brauchst auch Scope-Kenntnisse in C++.

Artikelinhalt

Grundlagen

Um die Taxonomie der Ausdruckskategorie wirklich zu verstehen, müssen Sie sich zuerst an die folgenden grundlegenden Funktionen erinnern oder diese kennen: Ort und Objekt, Speicher und Ressource, Initialisierung, Bezeichner und Referenz, Lvalue- und Rvalue-Referenzen, Zeiger, freier Speicher und Wiederverwendung von a Ressource.

Standort und Objekt

Betrachten Sie die folgende Erklärung:

int-Kennung;

Dies ist eine Deklaration, die einen Speicherort identifiziert. Eine Position ist ein bestimmter Satz aufeinanderfolgender Bytes im Speicher. Ein Speicherort kann aus einem Byte, zwei Bytes, vier Bytes, vierundsechzig Bytes usw. bestehen. Die Position für eine ganze Zahl für eine 32-Bit-Maschine beträgt vier Bytes. Außerdem kann der Standort durch eine Kennung identifiziert werden.

In der obigen Erklärung hat der Standort keinen Inhalt. Es bedeutet, dass es keinen Wert hat, da der Inhalt der Wert ist. Eine Kennung identifiziert also einen Standort (kleiner zusammenhängender Raum). Wenn dem Standort ein bestimmter Inhalt gegeben wird, identifiziert die Kennung dann sowohl den Standort als auch den Inhalt; das heißt, der Bezeichner identifiziert dann sowohl den Ort als auch den Wert.

Betrachten Sie die folgenden Aussagen:

int ident1 = 5;
int ident2 = 100;

Jede dieser Aussagen ist eine Deklaration und eine Definition. Der erste Bezeichner hat den Wert (Inhalt) 5 und der zweite Bezeichner hat den Wert 100. In einer 32-Bit-Maschine ist jeder dieser Orte vier Byte lang by. Der erste Bezeichner identifiziert sowohl einen Standort als auch einen Wert. Der zweite Bezeichner identifiziert auch beide.

Ein Objekt ist ein benannter Speicherbereich im Speicher. Ein Objekt ist also entweder ein Ort ohne Wert oder ein Ort mit einem Wert.

Objektspeicher und Ressourcen

Der Ort für ein Objekt wird auch als Speicher oder Ressource des Objekts bezeichnet.

Initialisierung

Betrachten Sie das folgende Codesegment:

int-Kennung;
Kennung = 8;

Die erste Zeile deklariert einen Bezeichner. Diese Deklaration stellt einen Speicherort (Speicher oder Ressource) für ein Integer-Objekt bereit und identifiziert es mit dem Namen ident. In der nächsten Zeile wird der Wert 8 (in Bits) an die mit ident . identifizierte Stelle eingefügt. Das Setzen dieses Wertes ist Initialisierung.

Die folgende Anweisung definiert einen Vektor mit dem Inhalt 1, 2, 3, 4, 5, der durch vtr identifiziert wird:

std::vektor vtr1, 2, 3, 4, 5;

Hier erfolgt die Initialisierung mit 1, 2, 3, 4, 5 in der gleichen Anweisung der Definition (Deklaration). Der Zuweisungsoperator wird nicht verwendet. Die folgende Anweisung definiert ein Array mit dem Inhalt 1, 2, 3, 4, 5:

int arr[] = 1, 2, 3, 4, 5;

Diesmal wurde ein Zuweisungsoperator für die Initialisierung verwendet.

Kennung und Referenz

Betrachten Sie das folgende Codesegment:

int-Ident = 4;
int& ref1 = ident;
int& ref2 = ident;
cout<< ident <<"<< ref1 <<"<< ref2 << '\n';

Die Ausgabe ist:

4 4 4

ident ist ein Bezeichner, während ref1 und ref2 Referenzen sind; sie verweisen auf denselben Ort. Ein Verweis ist ein Synonym für einen Bezeichner. Herkömmlicherweise sind ref1 und ref2 unterschiedliche Namen eines Objekts, während ident der Bezeichner desselben Objekts ist. ident kann jedoch immer noch der Name des Objekts genannt werden, was bedeutet, dass ident, ref1 und ref2 denselben Ort benennen.

Der Hauptunterschied zwischen einem Bezeichner und einer Referenz besteht darin, dass bei der Übergabe als Argument an eine Funktion, bei der Übergabe durch den Bezeichner, eine Kopie für den Bezeichner in der Funktion erstellt wird, während bei der Übergabe als Referenz dieselbe Position innerhalb der Funktion verwendet wird Funktion. Die Übergabe per Bezeichner endet also mit zwei Orten, während die Übergabe per Referenz an derselben Stelle endet.

lvalue-Referenz und rvalue-Referenz

Der normale Weg, eine Referenz zu erstellen, ist wie folgt:

int-Kennung;
Kennung = 4;
int& ref = ident;

Der Speicher (Ressource) wird zuerst lokalisiert und identifiziert (mit einem Namen wie ident) und dann wird eine Referenz (mit einem Namen wie ref) erstellt. Bei der Übergabe als Argument an eine Funktion wird eine Kopie des Bezeichners in der Funktion erstellt, während bei einer Referenz die ursprüngliche Position in der Funktion verwendet (auf die verwiesen) wird.

Heute ist es möglich, nur eine Referenz zu haben, ohne sie zu identifizieren. Das bedeutet, dass es möglich ist, zuerst eine Referenz zu erstellen, ohne eine Kennung für den Standort zu haben. Dies verwendet &&, wie in der folgenden Anweisung gezeigt:

int&& ref = 4;

Hier gibt es keine vorangehende Identifizierung. Um auf den Wert des Objekts zuzugreifen, verwenden Sie einfach ref wie die obige Ident.

Bei der &&-Deklaration gibt es keine Möglichkeit, ein Argument per Bezeichner an eine Funktion zu übergeben. Die einzige Möglichkeit ist, als Referenz zu übergeben. In diesem Fall wird nur eine Stelle innerhalb der Funktion verwendet und nicht die zweite kopierte Stelle wie bei einer Kennung.

Eine Referenzdeklaration mit & heißt lvalue reference. Eine Referenzdeklaration mit && heißt Rvalue-Referenz, die auch eine Prvalue-Referenz ist (siehe unten).

Zeiger

Betrachten Sie den folgenden Code:

int ptdInt = 5;
int *ptrInt;
ptrInt = &ptdInt;
cout<< *ptrInt <<'\n';

Die Ausgabe ist 5.

Hier ist ptdInt ein Bezeichner wie der obige Ident. Hier gibt es zwei Objekte (Orte) statt eines: das spitze Objekt, ptdInt, identifiziert durch ptdInt, und das Zeigerobjekt, ptrInt, identifiziert durch ptrInt. &ptdInt gibt die Adresse des angegebenen Objekts zurück und legt sie als Wert in das Zeigerobjekt ptrInt. Um den Wert des Pointer-Objekts zurückzugeben (zu erhalten), verwenden Sie den Bezeichner für das Pointer-Objekt, wie in „*ptrInt“.

Hinweis: ptdInt ist ein Bezeichner und keine Referenz, während der zuvor erwähnte Name ref eine Referenz ist.

Die zweite und dritte Zeile im obigen Code können auf eine Zeile reduziert werden, was zu folgendem Code führt:

int ptdInt = 5;
int *ptrInt = &ptdInt;
cout<< *ptrInt <<'\n';

Hinweis: Wenn ein Zeiger inkrementiert wird, zeigt er auf die nächste Stelle, die keine Addition des Wertes 1 . ist. Wenn ein Zeiger dekrementiert wird, zeigt er auf die vorherige Position, die keine Subtraktion des Werts 1 . ist.

Kostenloser Shop

Ein Betriebssystem weist jedem laufenden Programm Speicher zu. Ein Speicher, der keinem Programm zugeordnet ist, wird als freier Speicher bezeichnet. Der Ausdruck, der eine Position für eine ganze Zahl aus dem freien Speicher zurückgibt, lautet:

neue int

Dies gibt eine Position für eine ganze Zahl zurück, die nicht identifiziert wurde. Der folgende Code veranschaulicht, wie der Zeiger mit dem kostenlosen Speicher verwendet wird:

int *ptrInt = neues int;
*ptrInt = 12;
cout<< *ptrInt  <<'\n';

Die Ausgabe ist 12.

Um das Objekt zu zerstören, verwenden Sie den Löschausdruck wie folgt:

ptrInt löschen;

Das Argument für den Löschausdruck ist ein Zeiger. Der folgende Code veranschaulicht seine Verwendung:

int *ptrInt = neues int;
*ptrInt = 12;
ptrInt löschen;
cout<< *ptrInt <<'\n';

Die Ausgabe ist 0, und nichts wie null oder undefined. delete ersetzt den Wert für den Standort durch den Standardwert des bestimmten Standorttyps und ermöglicht dann die Wiederverwendung des Standorts. Der Standardwert für einen int-Speicherort ist 0.

Wiederverwendung einer Ressource

In der Ausdruckskategorie-Taxonomie entspricht die Wiederverwendung einer Ressource der Wiederverwendung eines Standorts oder Speichers für ein Objekt. Der folgende Code veranschaulicht, wie ein Standort aus einem kostenlosen Geschäft wiederverwendet werden kann:

int *ptrInt = neues int;
*ptrInt = 12;
cout<< *ptrInt <<'\n';
ptrInt löschen;
cout<< *ptrInt <<'\n';
*ptrInt = 24;
cout<< *ptrInt <<'\n';

Die Ausgabe ist:

12
0
24

Dem nicht identifizierten Standort wird zunächst ein Wert von 12 zugewiesen. Dann wird der Inhalt des Ortes gelöscht (theoretisch wird das Objekt gelöscht). Der Wert 24 wird dem gleichen Standort neu zugewiesen.

Das folgende Programm zeigt, wie eine von einer Funktion zurückgegebene Integer-Referenz wiederverwendet wird:

#einschließen
Verwenden von Namespace-Std;
int& fn()

int i = 5;
int& j = i;
Rückkehr j;

int main()

int& myInt = fn();
cout<< myInt <<'\n';
myInt = 17;
cout<< myInt <<'\n';
0 zurückgeben;

Die Ausgabe ist:

5
17

Ein Objekt wie i, das in einem lokalen Gültigkeitsbereich (Funktionsbereich) deklariert ist, hört am Ende des lokalen Gültigkeitsbereichs auf zu existieren. Die obige Funktion fn() liefert jedoch die Referenz von i. Durch diese zurückgegebene Referenz verwendet der Name myInt in der main()-Funktion den durch i identifizierten Ort für den Wert 17.

lWert

Ein lvalue ist ein Ausdruck, dessen Auswertung die Identität eines Objekts, Bitfelds oder einer Funktion bestimmt. Die Identität ist eine offizielle Identität wie oben ident oder ein lvalue-Referenzname, ein Zeiger oder der Name einer Funktion. Betrachten Sie den folgenden Code, der funktioniert:

int myInt = 512;
int& myRef = myInt;
int* ptr = &myInt;
int fn()

++ptr; --ptr;
Rückgabe von myInt;

Hier ist myInt ein lvalue; myRef ist ein Lvalue-Referenzausdruck; *ptr ist ein lvalue-Ausdruck, da sein Ergebnis mit ptr identifizierbar ist; ++ptr oder -ptr ist ein lvalue-Ausdruck, weil sein Ergebnis mit dem neuen Zustand (Adresse) von ptr identifizierbar ist, und fn ist ein lvalue (Ausdruck).

Betrachten Sie das folgende Codesegment:

int a = 2, b = 8;
intc = a + 16 + b + 64;

In der zweiten Anweisung hat der Ort für 'a' 2 und ist durch 'a' identifizierbar, ebenso ein lvalue. Der Ort für b hat 8 und ist durch b identifizierbar, ebenso ein lvalue. Der Ort für c hat die Summe und ist durch c identifizierbar, ebenso wie ein lvalue. In der zweiten Anweisung sind die Ausdrücke oder Werte von 16 und 64 rvalues ​​(siehe unten).

Betrachten Sie das folgende Codesegment:

Zeichenfolge[5];
seq[0]='l', seq[1]='o', seq[2]='v', seq[3]='e', seq[4]='\0';
cout<< seq[2] <<'\n';

Die Ausgabe ist 'v';

seq ist ein Array. Die Position für 'v' oder einen ähnlichen Wert im Array wird durch seq[i] identifiziert, wobei i ein Index ist. Der Ausdruck seq[i] ist also ein lvalue-Ausdruck. seq, der Bezeichner für das gesamte Array, ist ebenfalls ein lvalue.

prwert

Ein prvalue ist ein Ausdruck, dessen Auswertung ein Objekt oder ein Bitfeld initialisiert oder den Wert des Operanden eines Operators berechnet, wie durch den Kontext, in dem er erscheint, angegeben.

In der Aussage,

int myInt = 256;

256 ist ein prvalue (prvalue-Ausdruck), der das durch myInt . identifizierte Objekt initialisiert. Dieses Objekt ist nicht referenziert.

In der Aussage,

int&& ref = 4;

4 ist ein prvalue (prvalue-Ausdruck), der das von ref referenzierte Objekt initialisiert. Dieses Objekt ist nicht offiziell identifiziert. ref ist ein Beispiel für einen rvalue-Referenzausdruck oder einen prvalue-Referenzausdruck; es ist ein Name, aber kein offizieller Identifikator.

Betrachten Sie das folgende Codesegment:

int-Kennung;
Kennung = 6;
int& ref = ident;

6 ist ein pr-Wert, der das durch ident identifizierte Objekt initialisiert; das Objekt wird auch von ref . referenziert. Hier ist die ref eine lvalue-Referenz und keine prvalue-Referenz.

Betrachten Sie das folgende Codesegment:

int a = 2, b = 8;
intc = a + 15 + b + 63;

15 und 63 sind jeweils eine Konstante, die sich selbst berechnet und einen Operanden (in Bits) für den Additionsoperator erzeugt. 15 oder 63 ist also ein Prvalue-Ausdruck.

Jedes Literal, mit Ausnahme des String-Literals, ist ein Prvalue (i.e., ein Prvalue-Ausdruck). Also ein Literal wie 58 oder 58.53, oder wahr oder falsch, ist ein Prvalue. Ein Literal kann verwendet werden, um ein Objekt zu initialisieren, oder würde sich selbst (in einer anderen Form in Bits) als den Wert eines Operanden für einen Operator berechnen. Im obigen Code initialisiert das Literal 2 das Objekt, a. Es berechnet sich auch selbst als Operand für den Zuweisungsoperator.

Warum ist ein String-Literal kein Prvalue?? Betrachten Sie den folgenden Code:

char str[] = "lieben nicht hassen";
cout << str <<'\n';
cout << str[5] <<'\n';

Die Ausgabe ist:

lieben nicht hassen
nein

str identifiziert den ganzen String. Der Ausdruck str und nicht das, was er identifiziert, ist also ein lvalue. Jedes Zeichen in der Zeichenfolge kann durch str[i] identifiziert werden, wobei i ein Index ist. Der Ausdruck str[5] und nicht das von ihm identifizierte Zeichen ist ein lvalue. Das String-Literal ist ein lvalue und kein prvalue.

In der folgenden Anweisung initialisiert ein Array-Literal das Objekt arr:

ptrInt++ oder  ptrInt-- 

Hier ist ptrInt ein Zeiger auf eine Integer-Position. Der gesamte Ausdruck und nicht der endgültige Wert der Position, auf die er zeigt, ist ein prvalue (Ausdruck). Dies liegt daran, dass der Ausdruck ptrInt++ oder ptrInt- den ursprünglichen ersten Wert seiner Position identifiziert und nicht den zweiten Endwert derselben Position. Andererseits ist -ptrInt oder  -ptrInt ein lvalue, weil er den einzigen Wert des Interesses am Standort identifiziert identifies. Eine andere Betrachtungsweise ist, dass der ursprüngliche Wert den zweiten Endwert berechnet.

In der zweiten Anweisung des folgenden Codes kann a oder b immer noch als prvalue betrachtet werden:

int a = 2, b = 8;
intc = a + 15 + b + 63;

Also ist a oder b in der zweiten Anweisung ein lvalue, weil sie ein Objekt identifiziert. Es ist auch ein prvalue, da es die ganze Zahl eines Operanden für den Additionsoperator berechnet.

(new int), und nicht der Ort, den es erstellt, ist ein Prvalue. In der folgenden Anweisung wird einem Zeigerobjekt die Rücksprungadresse des Ortes zugewiesen:

int *ptrInt = neue int

Hier ist *ptrInt ein lvalue, während (new int) ein prvalue ist. Denken Sie daran, ein lvalue oder ein prvalue ist ein Ausdruck. (new int) identifiziert kein Objekt. Die Rückgabe der Adresse bedeutet nicht, das Objekt mit einem Namen zu identifizieren (wie ident, oben). In *ptrInt identifiziert der Name ptrInt das Objekt wirklich, also ist *ptrInt ein lvalue. Andererseits ist (new int) ein prvalue, da es eine neue Position zu einer Adresse mit Operandenwert für den Zuweisungsoperator = . berechnet.

xWert

Heute steht lvalue für Location Value; prvalue steht für „reinen“ rvalue (siehe unten, wofür rvalue steht). Heute steht xvalue für „eXpiring“ lvalue.

Die Definition von xvalue, zitiert aus der C++-Spezifikation, lautet wie folgt:

„Ein xvalue ist ein glvalue, der ein Objekt oder Bitfeld bezeichnet, dessen Ressourcen wiederverwendet werden können (normalerweise weil es sich dem Ende seiner Lebensdauer nähert). [Beispiel: Bestimmte Arten von Ausdrücken, die Rvalue-Referenzen beinhalten, liefern xvalues, wie z

Das bedeutet, dass sowohl lvalue als auch prvalue ablaufen können. Der folgende Code (von oben kopiert) zeigt, wie der Speicher (Ressource) des lvalue, *ptrInt nach dem Löschen wiederverwendet wird.

int *ptrInt = neues int;
*ptrInt = 12;
cout<< *ptrInt <<'\n';
ptrInt löschen;
cout<< *ptrInt <<'\n';
*ptrInt = 24;
cout<< *ptrInt <<'\n';

Die Ausgabe ist:

12
0
24

Das folgende Programm (von oben kopiert) zeigt, wie die Speicherung einer ganzzahligen Referenz, die eine von einer Funktion zurückgegebene Lvalue-Referenz ist, in der main()-Funktion wiederverwendet wird:

#einschließen
Verwenden von Namespace-Std;
int& fn()

int i = 5;
int& j = i;
Rückkehr j;

int main()

int& myInt = fn();
cout<< myInt <<'\n';
myInt = 17;
cout<< myInt <<'\n';
0 zurückgeben;

Die Ausgabe ist:

5
17

Wenn ein Objekt wie i in der Funktion fn() den Gültigkeitsbereich verlässt, wird es natürlich zerstört. In diesem Fall wurde der Speicher von i immer noch in der main()-Funktion wiederverwendet.

Die beiden obigen Codebeispiele veranschaulichen die Wiederverwendung der Speicherung von lvalues. Es ist möglich, eine Speicherwiederverwendung von prvalues ​​(rvalues) zu haben (siehe später).

Das folgende Zitat zu xvalue stammt aus der C++-Spezifikation:

„Im Allgemeinen bewirkt diese Regel, dass benannte R-Wert-Referenzen als L-Werte behandelt werden und unbenannte R-Wert-Referenzen auf Objekte als x-Werte behandelt werden. Rvalue-Referenzen auf Funktionen werden als Lvalues ​​behandelt, egal ob benannt oder nicht." (später sehen).

Ein xvalue ist also ein lvalue oder ein prvalue, dessen Ressourcen (Speicher) wiederverwendet werden können. xvalues ​​ist die Schnittmenge von lvalues ​​und prvalues.

xvalue hat mehr zu bieten, als in diesem Artikel behandelt wurde. Allerdings verdient xvalue einen eigenen Artikel, und daher werden die zusätzlichen Spezifikationen für xvalue in diesem Artikel nicht behandelt.

Taxonomiesatz der Ausdruckskategorie

Ein weiteres Zitat aus der C++-Spezifikation:

Hinweis: Historisch wurden lvalues ​​und rvalues ​​so genannt, weil sie auf der linken und rechten Seite einer Zuweisung erscheinen konnten (obwohl dies im Allgemeinen nicht mehr gilt); glvalues ​​sind „verallgemeinerte“ lvalues, prvalues ​​sind „reine“ rvalues ​​und xvalues ​​sind „eXpiring“ lvalues. Trotz ihrer Namen klassifizieren diese Begriffe Ausdrücke, keine Werte. - Endnote“

glvalues ​​ist also die Vereinigungsmenge von lvalues ​​und xvalues ​​und rvalues ​​ist die Vereinigungsmenge von xvalues ​​und prvalues. xvalues ​​ist die Schnittmenge von lvalues ​​und prvalues.

Ab sofort wird die Taxonomie der Ausdruckskategorie besser mit einem Venn-Diagramm wie folgt veranschaulicht:

Fazit

Ein lvalue ist ein Ausdruck, dessen Auswertung die Identität eines Objekts, Bitfelds oder einer Funktion bestimmt.

Ein prvalue ist ein Ausdruck, dessen Auswertung ein Objekt oder ein Bitfeld initialisiert oder den Wert des Operanden eines Operators berechnet, wie durch den Kontext, in dem er erscheint, angegeben.

Ein xvalue ist ein lvalue oder ein prvalue, mit der zusätzlichen Eigenschaft, dass seine Ressourcen (Speicher) wiederverwendet werden können.

Die C++-Spezifikation veranschaulicht die Taxonomie der Ausdruckskategorien mit einem Baumdiagramm, das darauf hinweist, dass es eine gewisse Hierarchie in der Taxonomie gibt. Da es derzeit keine Hierarchie in der Taxonomie gibt, wird von einigen Autoren ein Venn-Diagramm verwendet, da es die Taxonomie besser darstellt als das Baumdiagramm.

Emulieren Sie Mausklicks, indem Sie den Mauszeiger mit der klicklosen Maus in Windows 10 bewegen
Die Verwendung einer Maus oder Tastatur in der falschen Haltung bei übermäßiger Nutzung kann zu vielen gesundheitlichen Problemen führen, einschließli...
Fügen Sie mit diesen kostenlosen Tools Mausgesten zu Windows 10 hinzu
In den letzten Jahren haben sich Computer und Betriebssysteme stark weiterentwickelt. Es gab eine Zeit, in der Benutzer Befehle verwenden mussten, um ...
Steuern und verwalten Sie Mausbewegungen zwischen mehreren Monitoren in Windows 10
Dual-Display-Maus-Manager lässt Sie die Mausbewegung zwischen mehreren Monitoren steuern und konfigurieren, indem Sie ihre Bewegungen in der Nähe der ...