#include #include „complex.h“
Verwenden des Namensraums std;
ostream& Operator << (ostream& os, const complex& x) { return os << '(' << real (x) << ',' << imag (x) << ')' }
int main() { complex c1(2, 1); complex c2(4, 0);
cout << c1 << endl; cout << c2 << endl;
cout << c1+c2 << endl; cout << c1-c2 << endl;
cout << conj(c1) << endl; cout << norm(c1) << endl;
cout << (c1 += c2) << endl;
cout << (c1 == c2) << endl; cout << (c1 != c2) << cout << +c2 << endl;
cout << (c2 - 2) << endl; cout << (5 + c2) << endl;
return 0; }
Wie in der Abbildung oben gezeigt, muss die Selbstzuweisung beim Überladen des Zuweisungsoperators „=" aus folgenden Gründen überprüft werden:
Wenn keine Selbstzuweisungserkennung erfolgt, werden die m_data des eigenen Objekts freigegeben und der Inhalt, auf den m_data verweist, ist nicht vorhanden, sodass beim Kopieren Probleme auftreten.
Hier sind zwei Klassen zur Erklärung:
class complex
{
public:
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
complex& operator += (const complex&);
double real () const { return re; }
double imag () const { return im; }
private:
double re, im;
friend complex& __doapl (complex*,
const complex&);
};
class String
{
public:
String(const char* cstr = 0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
Nach dem Erstellen dieser beiden Objekte weist der Compiler (VC) den beiden Objekten Speicher wie folgt zu:
Die beiden links sind die Compiler-Speicherzuordnung des Klassenkomplexes im Debug-Modus und im Release-Modus. Im Debug-Modus fügt der Compiler den Kopf- und Schwanzteil (roter Teil) sowie den 4*8 + 4-Größeninformationsteil (grauer Teil) in den Speicher des komplexen Objekts ein. Der grüne Teil ist der tatsächlich vom komplexen Objekt belegte Bereich. Es gibt nur einen Abschnitt mit 52 Wörtern, aber der VC ist auf 16 Bytes ausgerichtet, sodass das nächste 16-fache von 52 64 ist und die 12-Byte-Lücke gefüllt werden sollte (Cyan-Pad-Teil). Für das komplexe Objekt im Release-Teil werden nur der Informationsheader und der Header-Teil hinzugefügt. Die Analyse der String-Klasse ist grundsätzlich dieselbe.
Schauen wir uns als Nächstes an, wie der VC-Compiler Array-Objekte zuweist:
In ähnlicher Weise fügt der Compiler dem Objekt einige redundante Informationen hinzu, da das Array aus drei Objekten besteht. Anschließend fügt der Compiler „3“ vor den drei komplexen Objekten ein, um die einzelnen Objekte zu kennzeichnen . Die Analysemethode der String-Klasse ist ebenfalls ähnlich.
Das Bild unten zeigt, warum zum Löschen eines Array-Objekts die Methode delete[] erforderlich ist:
String.h
#ifndef __MYSTRING__
#define __MYSTRING__
class String
{
public:
String(const char* cstr=0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
#include <cstring>
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = ' ';
}
}
inline
String::~String()
{
delete[] m_data;
}
inline
String& String::operator=(const String& str)
{
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
#include <iostream>
using namespace std;
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
string_test.cpp
#include "string.h"
#include <iostream>
using namespace std;
int main()
{
String s1("hello");
String s2("world");
String s3(s2);
cout << s3 << endl;
s3 = s1;
cout << s3 << endl;
cout << s2 << endl;
cout << s1 << endl;
}
In C++ spielt ein Ein-Parameter-Konstruktor (oder ein Mehrparameter-Konstruktor mit Standardwerten für alle Parameter außer dem ersten Parameter) zwei Rollen. 1 ist ein Konstruktor, 2 ist ein standardmäßiger und impliziter Typkonvertierungsoperator.
Wenn wir daher manchmal Code wie AAA = XXX schreiben und der Typ von XXX zufällig der Parametertyp des AAA-Einzelparameterkonstruktors ist, ruft der Compiler diesen Konstruktor automatisch auf, um ein AAA-Objekt zu erstellen.
Das sieht cool aus und ist sehr praktisch. Aber in einigen Fällen (siehe das maßgebliche Beispiel unten) widerspricht es unseren ursprünglichen Absichten (den Programmierern). Zu diesem Zeitpunkt müssen Sie vor dem Konstruktor eine explizite Änderung hinzufügen, um anzugeben, dass dieser Konstruktor nur explizit aufgerufen/verwendet werden kann und nicht implizit als Typkonvertierungsoperator verwendet werden kann.
Der explizite Konstruktor wird verwendet, um implizite Konvertierungen zu verhindern. Bitte schauen Sie sich den folgenden Code an:
class Test1
{
public:
Test1(int n)
{
num=n;
}//普通构造函数
private:
int num;
};
class Test2
{
public:
explicit Test2(int n)
{
num=n;
}//explicit(显式)构造函数
private:
int num;
};
int main()
{
Test1 t1=12;//隐式调用其构造函数,成功
Test2 t2=12;//编译错误,不能隐式调用其构造函数
Test2 t2(12);//显式调用成功
return 0;
}
Der Konstruktor von Test1 akzeptiert einen int-Parameter und Zeile 23 des Codes wird implizit in den Aufruf des Konstruktors von Test1 umgewandelt. Der Konstruktor von Test2 ist als explizit deklariert, was bedeutet, dass der Konstruktor nicht durch implizite Konvertierung aufgerufen werden kann, sodass in Zeile 24 des Codes ein Kompilierungsfehler auftritt.
Gewöhnliche Konstruktoren können implizit aufgerufen werden. Der explizite Konstruktor kann nur explizit aufgerufen werden.
Wenn eine Fraktion in den Double-Typ konvertiert werden muss, wird die Funktion double() automatisch zur Konvertierung aufgerufen. Wie in der obigen Abbildung gezeigt, stellt der Compiler bei der Analyse von double d = 4 + f fest, dass 4 eine ganze Zahl ist, und ermittelt dann weiterhin f. Es wird beobachtet, dass f die Funktion double () bereitstellt und dann das Double ausführt () Operation für f, berechnet 0,6, addiert es dann zu 4 und erhält schließlich den Doppeltyp 4,6.
In der Abbildung oben ist eine Klasse namens Fraction definiert. Der „+“-Operator wird in der Klasse „4“ implizit vom Compiler in ein Fraction-Objekt umgewandelt und dann übergeben durch Bruch Der überladene „+“-Operator ist an der Operation beteiligt.
Wie in der Abbildung oben gezeigt, wird die Funktion double () zu Fraction hinzugefügt, die die beiden Mitgliedsvariablen von Fraction teilt, sie dann zwangsweise in den Double-Typ konvertiert und das Ergebnis zurückgibt. Während des f + 4-Überladungsprozesses wird der Compiler dies tun Einen Fehler melden Sie können die folgende Analyse durchführen:
1. Zuerst wird 4 implizit in ein Fraction-Objekt konvertiert (Konstruktor), und dann wird der überladene „+“-Operator mit „f“ betrieben, um ein Fraction-Objekt zurückzugeben.
2. Zuerst wird 4 implizit in ein Fraction-Objekt konvertiert (Konstruktor), dann wird eine Doppeloperation für das Paar durch den überladenen „+“-Operator und die „f“-Operation ausgeführt und schließlich wird ein Fraction-Objekt zurückgegeben;
3. . .
Der Compiler hat also mindestens zwei Möglichkeiten, was zu Unklarheiten führt und einen Fehler meldet. Wie in der Abbildung oben gezeigt, wird die implizite Konvertierung abgebrochen, wenn das Schlüsselwort explict vor dem Konstruktor Franction hinzugefügt wird. Daher ruft f während der Ausführung von d2 = f + 4 die Double-Funktion auf, um sie in 0,6 zu konvertieren Fügen Sie es zu 4 hinzu, um 4.6 zu erhalten. Da der Konstruktor die implizite Formelkonvertierung abbricht, kann 4.6 nicht in Fraction konvertiert werden, sodass ein Fehler gemeldet wird.
Die folgende Abbildung zeigt eine Anwendung von Operatorüberladungs- und Konvertierungsfunktionen in C++ stl:
Das Bild unten veranschaulicht die interne Struktur und Verwendung von Smart Pointern sehr gut: Es gibt drei Schlüsselpunkte in der Syntax von Smart Pointern. Der erste ist der gespeicherte externe Zeiger, der T* px im Bild oben entspricht Der zweite Schritt besteht darin, den Operator „*“ zu überladen und ein Objekt zurückzugeben, auf das der Zeiger zeigt return Ein Zeiger, der dem Bild oben entspricht, ist px.
Iteratoren sind ebenfalls eine Art intelligenter Zeiger. Es gibt auch die drei oben erwähnten Elemente von intelligenten Zeigern, die der roten Schriftart und den gelb markierten Teilen in der folgenden Abbildung entsprechen:
Die Überladungszeichen „*“ und „->“ der Iteratorüberladung werden im Folgenden sorgfältig analysiert:
Erstellen Sie ein Listeniteratorobjekt, list::iterator ite. Die Liste hier wird zum Speichern des Foo-Objekts verwendet, bei dem es sich um die Klasse in der Listenvorlagendefinition handelt T, Operator*() gibt ein (*node).data-Objekt zurück, node ist vom Typ __link_type, aber __link_type ist vom Typ __list_node<T>*. T ist hier Foo, also ist node vom Typ __list_node<Foo >*, also ( *node).data ruft ein Objekt vom Typ Foo ab und &(operator*()) ruft schließlich eine Adresse des Foo-Objekts ab, d. h. es gibt Foo* zurück Ein Zeiger auf den Typ.
Wie Sie der obigen Abbildung entnehmen können, ist jeder Funktor eine Klasse, die den „()“-Operator überlädt und dann zu einem „Funktor“ wird. Es handelt sich tatsächlich um eine Klasse, aber sie scheint die Eigenschaften einer Funktion zu haben. Jeder Funktor integriert tatsächlich eine seltsame Klasse dahinter, wie in der folgenden Abbildung gezeigt. Diese Klasse muss vom Programmierer nicht explizit manuell deklariert werden. Der Funktor in der Standardbibliothek erbt auch eine seltsame Klasse: Der Inhalt dieser Klasse ist in der folgenden Abbildung dargestellt. Sie deklariert nur einige Dinge und enthält keine tatsächlichen Variablen oder Funktionen. Der spezifische Inhalt wird in STL besprochen.
Im Gegensatz zu Klassenvorlagen müssen Funktionsvorlagen bei ihrer Verwendung den Typ der eingehenden Parameter nicht explizit deklarieren. Der Compiler leitet den Typ automatisch ab.
Um eine bessere Skalierbarkeit zu erreichen, ist T1 häufig die Basisklasse von U1 und T2 häufig die Basisklasse von U2. Auf diese Weise können Vererbung und Polymorphismus durch diese Methode geschickt genutzt werden, solange die übergeordneten oder Vorfahrenklassen von U1 und U2, die übergeben werden, T1 und T2 sind, aber nicht umgekehrt. Diese Methode wird in STL häufig verwendet:
Wie der Name schon sagt, bezieht sich die Vorlagenpartialisierung auf die Angabe bestimmter Datentypen in der Vorlage, was sich von der Generalisierung unterscheidet: Natürlich hat die Vorlagenpartialisierung auch Grade und es können Teiltypen angegeben werden, was als Teilspezialisierung bezeichnet wird:
Im C++11-Kurs werden zu viele Inhalte erklärt, daher werde ich sie hier vorerst nur vorstellen.
Zu viele Inhalte werden im C++11-Kurs erklärt, ich werde sie hier vorerst nur vorstellen.
Eine Referenz kann als Alias für eine referenzierte Variable angesehen werden.
Wie in der Abbildung oben gezeigt, sind drei Klassen definiert: A, B und C. B erbt von A und C erbt von B. Es gibt zwei virtuelle Funktionen in A, eine in B und eine in C. Der Compiler weist das Objekt a von A im Speicher zu, wie in der Abbildung oben gezeigt. Es gibt nur zwei Mitgliedsvariablen m_data1 und m_data2. Da Klasse A gleichzeitig über virtuelle Funktionen verfügt, weist der Compiler dem Objekt a Platz zu Um die virtuelle Funktionstabelle zu speichern, verwaltet diese Tabelle die virtuelle Funktionsadresse (dynamische Funktion) dieser Klasse Da Klasse A zwei virtuelle Funktionen hat, gibt es in der virtuellen Funktionstabelle von a zwei Leerzeichen (gelbe und blaue Leerzeichen), die auf A::vfunc1() bzw. A::vfunc2() verweisen ist ein Objekt der Klasse B, da Klasse B die Funktion vfunc1() von Klasse A überschreibt. , sodass die virtuelle Funktionstabelle von B (cyanfarbener Teil) auf B::vfunc1() zeigt und B vfunc2() von Klasse A erbt, sodass die virtuelle Funktionstabelle von B (blauer Teil) auf A der übergeordneten Klasse A::vfunc2 zeigt () Funktion; In ähnlicher Weise ist c ein Objekt der Klasse C, dargestellt durch Klasse C schreibt die Funktion vfunc1() der übergeordneten Klasse neu, sodass die virtuelle Funktionstabelle von C (gelber Teil) auf C::vfunc1() verweist. Gleichzeitig erbt C vfunc2() von Superklasse A, also die virtuelle Funktion von B Die Tabelle (blauer Teil) zeigt auf die Funktion A::vfunc2(). Gleichzeitig wird in der obigen Abbildung auch C-Sprachcode verwendet, um zu veranschaulichen, wie die unterste Ebene des Compilers diese Funktionen aufruft. Dies ist die Essenz des objektorientierten Vererbungspolymorphismus.
Der this-Zeiger kann tatsächlich als Zeiger betrachtet werden, der auf die Speicheradresse des aktuellen Objekts zeigt, da es in der Basisklasse und der Unterklasse virtuelle Funktionen gibt, wird this->Serialize() dynamisch gebunden. was äquivalent zu (*(this- >vptr)[n])(this) ist. Durch die Kombination des virtuellen Zeigers und der virtuellen Funktionstabelle im vorherigen Abschnitt kann die folgende Zusammenfassung erläutert werden.