Technologieaustausch

C-Grundlagen (2)

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Inhaltsverzeichnis

1. Klassen und Objekte

1.1 Klassendefinition

1.2 Zugangsqualifikationen

1.3 Klassendomäne

2. Instanziierung

2.1 Instanziierungskonzept

2.2 Objektgröße

3.Dieser Zeiger

4.Standard-Memberfunktionen von Klassen

4.1Konstrukteur

4.2 Destruktor

4.5 Überlastung des Bedieners


1. Klassen und Objekte

1.1 Klassendefinition

Klassendefinitionsformat

class ist das Schlüsselwort, das die Klasse definiert, Stack ist der Name der Klasse und {} ist der Hauptteil der Klasse. Beachten Sie, dass das Semikolon am Ende der Definition nicht weggelassen wird. Die Inhalte im Klassenkörper werden als Mitglieder der Klasse bezeichnet: Variablen in der Klasse werden als Attribute oder Mitgliedsvariablen der Klasse bezeichnet; Funktionen in der Klasse werden als Methoden oder Mitgliedsfunktionen der Klasse bezeichnet.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. //成员变量
  7. int* a;
  8. int top;
  9. int capacity;
  10. //成员函数
  11. void Push()
  12. {
  13. }
  14. void Pop()
  15. {
  16. }
  17. };//分号不能省略
  18. int main()
  19. {
  20. return 0;
  21. }

  • Um Mitgliedsvariablen zu unterscheiden, ist es im Allgemeinen üblich, der Mitgliedsvariablen eine spezielle Kennung hinzuzufügen, z. B. beginnend mit _ oder m_ vor oder nach der Mitgliedsvariablen.Für diese C++-Syntax gibt es keine Vorschriften, sie ist lediglich eine Frage der persönlichen oder geschäftlichen Präferenz.
  1. //为区分成员变量,一般前面加_
  2. //成员变量
  3. int* _a;
  4. int _top;
  5. int _capacity;
  • Struct in C++ kann auch Klassen definieren. Gleichzeitig wurde struct zu einer Klasse erweitert. Unter normalen Umständen können wir auch Funktionen definieren Ich empfehle weiterhin, Klassen zum Definieren von Klassen zu verwenden.

  • In einer Klasse definierte Mitglieder sind standardmäßig inline

1.2 Zugangsqualifikationen

C++ ist eine Möglichkeit, die Kapselung zu implementieren, indem Klassen verwendet werden, um die Eigenschaften und Methoden eines Objekts zu kombinieren, um das Objekt vollständiger zu machen und seine Schnittstelle über Zugriffsrechte selektiv externen Benutzern bereitzustellen.

  • Auf durch public (public) geänderte Mitglieder kann außerhalb der Klasse direkt zugegriffen werden, durch protected (protected) und private (private) geänderte Mitglieder können außerhalb der Klasse nicht direkt zugegriffen werden, protected und private sind dasselbe
  • Der Zugriffsberechtigungsbereich beginnt an der Position, an der die Zugriffsberechtigung angezeigt wird, bis zum Erscheinen des nächsten Zugriffsqualifizierers. Wenn kein nachfolgender Zugriffsqualifizierer vorhanden ist, endet der Bereich bei }, dem Ende der Klasse.
  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. ///
  7. void Push()
  8. {
  9. }
  10. //Push 没给限定符 class默认私有 private
  11. ///
  12. public:
  13. void Pop()
  14. {
  15. }
  16. int Swap()
  17. {
  18. }
  19. //Pop和Swap 被public修饰,直到下一个限定符出现之前都为公有
  20. ///
  21. protected:
  22. int add();
  23. //add 被public修饰,直到下一个限定符出现之前都为保护
  24. /// /
  25. private:
  26. int* _a;
  27. int _top;
  28. int _capacity;
  29. //成员变量被private修饰,直到}结束都为私有
  30. };
  31. int main()
  32. {
  33. Stack st;
  34. //公有可以访问
  35. st.Pop();
  36. st.Swap();
  37. //私有不可访问
  38. st._top;
  39. return 0;
  40. }

Zusätzliche Bemerkungen:

  1. Wenn Klassendefinitionsmitglieder nicht durch Zugriffsqualifizierer geändert werden, sind sie standardmäßig „privat“ und die Struktur standardmäßig „öffentlich“.
  2. Im Allgemeinen werden Mitgliedsvariablen auf privat/geschützt beschränkt und Mitgliedsfunktionen, die von anderen verwendet werden müssen, werden als öffentlich platziert.

1.3 Klassendomäne

Die Klasse definiert einen neuen Bereich. Wenn Sie Mitglieder außerhalb der Klasse definieren, müssen Sie den ::scope-Operator verwenden, um anzugeben, zu welchem ​​Klassenbereich das Mitglied gehört.

Die Klassendomäne wirkt sich auf die Suchregeln der Kompilierung aus. Wenn Init im folgenden Programm die Klassendomäne Stack nicht angibt, behandelt der Compiler Init als globale Funktion. Wenn er während der Kompilierung keine Mitglieder wie _top findet, wechselt er zu Klassendomäne, um sie zu finden.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. public:
  7. void Init(int x, int y);
  8. };
  9. void Stack::Init(int x, int y)
  10. {
  11. _top = x;
  12. _capacity = y;
  13. }
  14. int main()
  15. {
  16. return 0;
  17. }

Beachten:

  1. Die Funktionsdeklarationen und Definitionen in der Klasse werden getrennt. Nachdem die Klasse erstellt wurde, muss eine neue Klassendomäne angegeben werden, andernfalls ist sie nicht zugänglich.

2. Instanziierung

2.1 Instanziierungskonzept

  • Der Prozess der Erstellung eines Typs im physischen Speicher wird als Klasseninstanziierung bezeichnet.
  • Eine Klasse ist eine abstrakte Beschreibung eines Objekts. Sie begrenzt die Mitgliedsvariablen der Klasse und es wird kein Speicherplatz zugewiesen.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Stack
  5. {
  6. //声明
  7. int* _a;
  8. int _top;
  9. int _capacity;
  10. };
  11. int main()
  12. {
  13. Stack::_top = 2024;
  14. //编译器报错,_top只是声明,并未实例化
  15. return 0;
  16. }
  • Eine Klasse kann mehrere Objekte instanziieren, und die instanziierten Objekte belegen tatsächlichen physischen Raum und speichern Mitgliedsvariablen. Zum Beispiel: Das Instanziieren von Objekten aus einer Klasse ist wie die Verwendung von Architekturzeichnungen zum Bau eines Hauses in der Realität. Klassen sind wie Entwurfszeichnungen, die die Anzahl der Räume, die Größe des Hauses usw. planen, aber es gibt keine physischen Gebäude, und es kann nicht in Menschen leben, nur wenn ein Haus anhand der Entwurfszeichnungen gebaut wird, können Menschen in dem Haus leben.Die gleiche Klasse ähnelt einer Entwurfszeichnung. Sie teilt dem Compiler lediglich mit, wie viel Speicher geöffnet werden soll, aber sie öffnet nicht den Speicher. Nur den instanziierten Objekten wird physischer Speicher zum Speichern von Daten zugewiesen.
    1. //text.cpp
    2. #include<iostream>
    3. using namespace std;
    4. class Stack
    5. {
    6. //声明
    7. int* _a;
    8. int _top;
    9. int _capacity;
    10. };
    11. int main()
    12. {
    13. Stack st;
    14. st._top=2024;
    15. //Stack实例化出st,系统已经给st分配内存了,可以存储数据,编译通过
    16. return 0;
    17. }

    2.2 Objektgröße

  • Analysieren Sie, welche Mitglieder das Klassenobjekt hat? Jedes von einer Klasse instanziierte Objekt verfügt über einen unabhängigen Datenraum, daher muss das Objekt Mitgliedsvariablen enthalten. Sind also Mitgliedsfunktionen enthalten? Erstens handelt es sich nach der Kompilierung um einen Abschnitt mit Anweisungen, die nicht im Objekt gespeichert werden können. Diese Anweisungen werden in einem separaten Bereich (Codesegment) gespeichert. Wenn das Objekt gespeichert werden muss, kann es sich nur um einen Zeiger handeln eine Memberfunktion. Ist es notwendig, Zeiger im Objekt zu speichern? Date instanziiert zwei Objekte d1 und d2. Sowohl di als auch d2 verfügen über unabhängige Mitgliedsvariablen _year/_month/_day, aber die Mitgliedsfunktionen Init von d1 und d2 sind die /Print-Zeiger ist dasselbe und wird verschwendet, wenn es im Objekt gespeichert wird. Wenn Sie Date verwenden, um 100 Objekte zu instanziieren, wird der Mitgliedsfunktionszeiger 100 Mal wiederholt gespeichert, was zu verschwenderisch ist. Tatsächlich muss der Funktionszeiger nicht gespeichert werden. Die aufrufende Funktion wird in eine Assembly-Anweisung kompiliert. Tatsächlich muss der Compiler die Adresse der Funktion beim Kompilieren und Verknüpfen finden . Es kann zur Laufzeit nicht gefunden werden. Der Status wird zur Laufzeit gefunden, daher muss die Funktionsadresse gespeichert werden.

Regeln zur Speicherausrichtung

  • Das erste Mitglied befindet sich am Adressoffset 0 von der Struktur
  • Andere Mitgliedsvariablen müssen an Adressen ausgerichtet werden, die ein Vielfaches der Ausrichtungsnummer sind.
  • Ausrichtungsnummer = Der kleinere Wert aus der Standard-Ausrichtungsnummer des Compilers und der Größe des Mitglieds
  • Die Standardausrichtungsnummer der VS x64-Plattform ist 4 und die Standardausrichtungsnummer von x86 ist 8
  • Die Gesamtgröße der Struktur beträgt: ein ganzzahliges Vielfaches der maximalen Alignment-Nummer (die größte aller Typvariablen und die kleinste Standard-Alignment-Nummer)
  • Wenn Strukturen verschachtelt sind, wird die verschachtelte Struktur auf ein ganzzahliges Vielfaches ihrer eigenen maximalen Ausrichtungszahl ausgerichtet, und die Gesamtgröße der Struktur ist ein ganzzahliges Vielfaches aller maximalen Ausrichtungszahlen (einschließlich der Ausrichtungszahl verschachtelter Strukturen).
  1. class A
  2. {
  3. public:
  4. void Print()
  5. {
  6. cout << _ch << endl;
  7. }
  8. private:
  9. char _ch;
  10. int _i;
  11. };
  12. //_ch 是一个字节,默认对齐数是4,最大对齐数是4,所以开辟4个字节用来存在_ch
  13. // _i是4个字节,默认对齐数是4,最大对齐数是4,所以开辟4个字节用来存储_i
  14. class B
  15. {
  16. public:
  17. void Print()
  18. {
  19. //。。。
  20. }
  21. };
  22. class B
  23. {
  24. };
  25. //B和C里面没有存储任何成员变量,只有一个函数,可成员函数不存对象里面
  26. // 按理来说是0,但是结构体怎么会没大小,为表示对象存在C++对这种规定大小为1,为了占位标识对象存在

3.Dieser Zeiger

Nachdem der Compiler kompiliert wurde, fügen die Mitgliedsfunktionen der Klasse standardmäßig einen Zeiger auf die aktuelle Klasse an der ersten Position des formalen Parameters hinzu, der als dieser Zeiger bezeichnet wird.

Der Init-Prototyp in der Date-Klasse ist beispielsweise void Init (Date * const this, int year, int month, int day). Beim Zugriff auf Mitgliedsvariablen in den Mitgliedsfunktionen der Klasse wird über den This-Zeiger auf die Essenz zugegriffen. wie _year in der Init-Funktion, this-&gt;_year=year

Prototyp:

  1. class Date
  2. {
  3. void Print()
  4. {
  5. cout << _year << "n" << _month << "n" << _day << endl;
  6. }
  7. void Init( int year, int month,int day)
  8. {
  9. _year = year;
  10. _month = month;
  11. _day = day;
  12. }
  13. private:
  14. int _year;
  15. int _month;
  16. int _day;
  17. };

  1. Date d1;
  2. d1.Init(2024,7,10);
  3. d1.Print();
  4. Date d2;
  5. d2.Init(2024, 7, 9);
  6. d2.Print();

echter Prototyp

  1. class Date
  2. {
  3. void Init(Date* const this,int year, int month,int day)
  4. {
  5. this->_year = year;
  6. this->_month = month;
  7. this->_day = day;
  8. }
  9. void Printf(Date* const this)
  10. {
  11. cout << this->_year << "n" <<this-> _month << "n" << this->_day << endl;
  12. }
  13. private:
  14. int _year;
  15. int _month;
  16. int _day;
  17. };

  1. Date d1;
  2. d1.Init(&d1,2024,7,10);
  3. d1.Print(&d1);
  4. Date d2;
  5. d2.Init(&d2,2024, 7, 9);
  6. d2.Print();

C++ legt fest, dass dieser Zeiger nicht an die Stelle tatsächlicher Parameter und formaler Parameter geschrieben werden darf (der Compiler wird ihn beim Kompilieren verarbeiten), dieser Zeiger kann jedoch explizit im Funktionskörper verwendet werden Der Inhalt, auf den dieser Zeiger zeigt, kann

Dieser Zeiger wird auf dem Stapel gespeichert

4.Standard-Memberfunktionen von Klassen

Die Standard-Member-Funktion ist eine Member-Funktion, die nicht explizit vom Benutzer definiert wird und automatisch vom Compiler generiert wird. Sie wird als Standard-Member-Funktion bezeichnet.

4.1Konstrukteur

Der Konstruktor ist eine spezielle Mitgliedsfunktion. Es ist zu beachten, dass der Konstruktor zwar als Konstruktor bezeichnet wird, der Hauptinhalt des Konstruktors jedoch nicht darin besteht, Platz zum Erstellen von Objekten zu schaffen (das lokale Objekt, das wir normalerweise verwenden, ist der Platz, der geöffnet wird). Der Stapelrahmen wird erstellt) ), aber das Objekt wird initialisiert, wenn das Objekt instanziiert wird. Der Kern des Konstruktors besteht darin, die Funktion der Init-Funktion zu ersetzen, die wir zuvor in den Klassen Stack und Date geschrieben haben. Der automatische Aufruf des Konstruktors ersetzt die Init-Funktion perfekt.

Merkmale des Konstruktors:

  1. Der Funktionsname ist derselbe wie der Klassenname
  2. Kein Rückgabewert (Sie müssen nichts als Rückgabewert angeben und nicht void schreiben. Dies ist die Regel von C++)
  3. Wenn das Objekt instanziiert wird, ruft das System automatisch den entsprechenden Konstruktor auf.
  4. Konstruktoren können überladen sein
  5. Wenn in der Klasse kein expliziter Konstruktor definiert ist, generiert der C++-Compiler automatisch einen Parameterlosen Standardkonstruktor. Sobald der Benutzer den Konstruktor explizit definiert, generiert der Compiler ihn nicht mehr.

  1. class Date
  2. {public:
  3. //1.无参构造函数
  4. Date()
  5. {
  6. _year = 1;
  7. _month = 1;
  8. _day = 1;
  9. }
  10. //2.带参构造函数
  11. Date(int year, int month, int day)
  12. {
  13. _year = year;
  14. _month = month;
  15. _day = day;
  16. }
  17. //3.全缺省构造函数
  18. Date(int year = 1, int month = 1, int day = 1)
  19. {
  20. _year = year;
  21. _month = month;
  22. _day = day;
  23. }
  24. private:
  25. int _year;
  26. int _month;
  27. int _day;
  28. };

Der parameterlose Konstruktor, der Standardkonstruktor und der vom Compiler standardmäßig generierte Konstruktor, wenn wir keinen Konstruktor schreiben, werden alle als Standardkonstruktoren bezeichnet. Aber nur einer dieser drei kann existieren, nicht gleichzeitig. Obwohl der parameterlose Konstruktor und der vollständige Standardkonstruktor eine Funktionsüberladung darstellen, kommt es beim Aufruf zu Mehrdeutigkeiten.Beachten Sie, dass der Standardkonstruktor nicht nur der vom Compiler standardmäßig generierte ist, sondern auch der Konstruktor, der parameterlose Konstruktor, und der vollständige Standardkonstruktor ist auch der Standardkonstruktor. Zusammenfassend lässt sich sagen, dass er ohne Übergabe von Parametern aufgerufen werden kann.

Wir schreiben es nicht. Das vom Compiler generierte Konstrukt stellt keine Anforderungen an die Initialisierung integrierter Typ-Member-Variablen. Das heißt, ob es initialisiert wird oder nicht, hängt vom Compiler ab.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. typedef int STDataType;
  5. class Stack
  6. {
  7. public:
  8. Stack(int n = 4)
  9. {
  10. _a = (STDataType*)malloc(sizeof(STDataType) * n);
  11. if (nullptr == _a)
  12. {
  13. perror("malloc申请失败");
  14. }
  15. _capacity = n;
  16. _top = 0;
  17. }
  18. private:
  19. STDataType* _a;
  20. size_t _capacity;
  21. size_t _top;
  22. };
  23. //两个Stack实现队列
  24. class MyQueue
  25. {
  26. private:
  27. int size;
  28. Stack pushst;
  29. Stack popst;
  30. };
  31. int main()
  32. {
  33. MyQueue my;
  34. return 0;
  35. }

C++ unterteilt Typen in benutzerdefinierte Typen und integrierte Typen (Basistypen). Integrierte Typen sind die nativen Datentypen, die von der Sprache bereitgestellt werden, z. B. int/char/double/pointer usw. Benutzerdefinierte Typen sind Typen, die wir selbst mithilfe von Schlüsselwörtern wie class/struct definieren.Der Konstruktor wird hier automatisch initialisiert, und VS initialisiert auch die integrierte Typgröße. Verschiedene Compiler haben unterschiedliche Initialisierungswerte, und C++ gibt diese nicht an.

Für benutzerdefinierte Mitgliedsvariablen ist es erforderlich, den Standardkonstruktor dieser Mitgliedsvariablen aufzurufen, um sie zu initialisieren.Wenn diese Mitgliedsvariable keinen Standardkonstruktor hat, wird ein Fehler gemeldet. Wenn wir diese Mitgliedsvariable initialisieren möchten, müssen wir zur Lösung eine Initialisierungsliste verwenden.

Zusammenfassung: In den meisten Fällen müssen wir den Konstruktor selbst implementieren. In einigen Fällen ähnelt er MyQueue, und wenn Stack über einen Standardkonstruktor verfügt, kann MyQueue automatisch generiert und verwendet werden.

4.2 Destruktor

  1. ~Stack()
  2. {
  3. free(_a);
  4. _a = nullptr;
  5. _top = _capacity = 0;
  6. }

Eigenschaften des Destruktors:

1. Dem Destruktornamen werden die Zeichen ~ vorangestellt

2. Keine Parameter und kein Rückgabewert (konsistent mit dem Konstruktor)

3. Eine Klasse kann nur einen Destruktor haben.Wenn die Definition nicht angezeigt wird, generiert das System automatisch einen Standarddestruktor.

4. Wenn der Objektdeklarationszyklus endet, ruft das System automatisch den Destruktor auf.

5. Ähnlich wie beim Konstruktor schreiben wir den vom Compiler automatisch generierten Destruktor nicht und verarbeiten nicht die integrierten Typmitglieder. Benutzerdefinierte Typmitglieder rufen andere Destruktoren auf.

6. Es ist auch zu beachten, dass bei der Anzeige des Destruktors auch der Destruktor des benutzerdefinierten Typmitglieds aufgerufen wird, was bedeutet, dass der Destruktor des benutzerdefinierten Typmitglieds unabhängig von der Situation automatisch aufgerufen wird.

  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. typedef int STDataType;
  5. class Stack
  6. {
  7. public:
  8. Stack(int n = 4)
  9. {
  10. _a = (STDataType*)malloc(sizeof(STDataType) * n);
  11. if (nullptr == _a)
  12. {
  13. perror("malloc申请失败");
  14. }
  15. _capacity = n;
  16. _top = 0;
  17. }
  18. ~Stack()
  19. {
  20. free(_a);
  21. _a = nullptr;
  22. _top=_capacity=0;
  23. }
  24. private:
  25. STDataType* _a;
  26. size_t _capacity;
  27. size_t _top;
  28. };
  29. //两个Stack实现队列
  30. class MyQueue
  31. {public:
  32. //编译器默认生成MyQueue的构造函数调用了Stack的构造,完成了两个成员的初始化
  33. //编译器默认生成MyQueue的析构函数调用了Stack的析构,释放了Stack内部的资源
  34. //显示写析构也会调用Stack的析构
  35. ~MyQueue()
  36. {
  37. cout << "~MyQueue" << endl;
  38. }
  39. private:
  40. Stack pushst;
  41. Stack popst;
  42. };
  43. int main()
  44. {
  45. MyQueue my;
  46. return 0;
  47. }

Der Destruktor in MyQueue macht nichts, aber C++ schreibt vor, dass andere Destruktoren aufgerufen werden, um den Speicher freizugeben.

Wenn keine Ressourcen angefordert werden, muss der Destruktor nicht geschrieben werden und der vom Compiler generierte Standarddestruktor kann direkt verwendet werden. Wenn der generierte Standarddestruktor verwendet werden kann, ist es nicht erforderlich, den Destruktor explizit zu schreiben B. MyQueue, aber es gibt eine Ressourcenanwendung. Achten Sie beim Zerstören darauf, den Destruktor direkt zu schreiben, da es sonst zu Ressourcenverlusten wie Stack kommt

4.5 Überlastung des Bedieners

  • Wenn Operatoren für typisierte Objekte verwendet werden, können wir mit der Sprache C++ neue Bedeutungen in Form einer Operatorüberladung angeben. C++ legt fest, dass ein Klassentypobjekt, das einen Operator verwendet, in einen Aufruf der entsprechenden Operatorüberladung umgewandelt werden muss. Andernfalls meldet der Compiler einen Fehler.
  • Beim Überladen von Operatoren handelt es sich um eine Funktion mit einem bestimmten Namen. Ihr Name setzt sich aus dem Operator und dem später zu definierenden Operator zusammen.Wie andere Funktionen verfügt auch sie über einen Rückgabetyp und eine Parameterliste sowie einen Funktionskörper
  1. bool operator<(Date d1, Date d2)
  2. {
  3. }
  4. bool operator==(Date d1,Date d2)
  5. {
  6. return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
  7. }
  • Eine überladene Operatorfunktion benötigt so viele Parameter wie Parameter, auf die der Operator einwirkt.Der unäre Transportkomfort hat einen Parameter und der binäre Operator hat zwei Parameter. Der linke Operand des binären Operators wird an den ersten Parameter und der rechte Operand an den zweiten Parameter übergeben.
  1. //text.cpp
  2. #include<iostream>
  3. using namespace std;
  4. class Date
  5. {
  6. public:
  7. Date(int year, int month, int day)
  8. {
  9. _year= year;
  10. _month = month;
  11. _day = day;
  12. }
  13. int _year;
  14. int _month;
  15. int _day;
  16. };
  17. bool operator<(Date d1, Date d2)
  18. {
  19. }
  20. bool operator==(Date d1,Date d2)
  21. {
  22. return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
  23. }
  24. int main()
  25. {
  26. Date d1(2024, 7, 10);
  27. Date d2(2024,7,9);
  28. //两种用法都可以
  29. d1 == d2;
  30. operator==(d1 , d2);
  31. return 0;
  32. }
  • Wenn eine überladene Operatorfunktion eine Mitgliedsfunktion ist, wird ihr erster Operand standardmäßig an den impliziten Zeiger this übergeben. Wenn der Operator als Mitgliedsfunktion überladen ist, hat er daher einen Parameter weniger als der Operand.
  • Nachdem ein Operator überladen wurde, bleiben seine Priorität und Assoziativität mit den integrierten Typoperationen konsistent.
  • Sie können keinen sexuellen Operator erstellen, indem Sie Übereinstimmungen verketten, die in der Syntax nicht vorhanden sind: z. B.