Lesehinweise zu „C++ Advanced Programming“ (vier: Entwerfen professioneller C++-Programme)

1. Referenzen

2. Für den Einstieg wird empfohlen, das Buch „21 Days to Learn C++“ zu lesen. Der Link zu den Notizen lautet wie folgt

1. Überblick über das Programmdesign

  • Bei der Einführung eines neuen Programms (oder neuer Funktionen für ein bestehendes Programm) besteht der erste Schritt darin, die Anforderungen zu analysieren
    • Ein wichtiges Ergebnis der Analysephase ist das Dokument „Funktionale Anforderungen“ : Es beschreibt, was genau der neue Code tut, erklärt aber nicht, wie
    • Nach der Anforderungsanalyse können auch Dokumente mit „nichtfunktionalen Anforderungen“ vorliegen , die beschreiben, was das Endsystem ist, und nicht, was es tut. Beispiele für nichtfunktionale Anforderungen sind: Das System muss sicher und skalierbar sein, bestimmte Leistungskriterien erfüllen usw.
  • Im zweiten Schritt beginnt die Entwurfsphase des Projekts . Programmdesign (oder Softwaredesign) ist die strukturelle Spezifikation, die implementiert wird, um alle funktionalen und nichtfunktionalen Anforderungen eines Programms zu erfüllen
  • Die meisten Designdokumente bestehen hauptsächlich aus zwei Teilen
    • (1) Teilen Sie das Gesamtprogramm in Subsysteme auf, einschließlich Schnittstellen und Abhängigkeiten zwischen Subsystemen, Datenfluss zwischen Subsystemen, Eingabe und Ausgabe jedes Subsystems und allgemeines Threading-Modell
    • (2) Einzelheiten zu jedem Subsystem, einschließlich Klassenunterteilung, Klassenhierarchie, Datenstrukturen, Algorithmen, spezifischen Threading-Modellen und Einzelheiten zur Fehlerbehandlung

    Designdokumente enthalten häufig Diagramme und Tabellen, um Subsysteminteraktionen und Klassenhierarchien darzustellen

2. Die Bedeutung des Programmdesigns

  • Ein diszipliniertes Design vor dem Schreiben von Code hilft dabei, herauszufinden, wie alles zusammenpasst. Das Design des Programms zeigt die Beziehung und Zusammenarbeit zwischen den Programmsubsystemen, um die Anforderungen der Software zu erfüllen. Ohne Entwurfsplanung können Verbindungen zwischen Subsystemen fehlen, Informationen können wiederverwendet oder geteilt werden und die Möglichkeit, Aufgaben auf einfachste Weise zu erledigen, geht möglicherweise verloren. Wenn es kein „Entwurfs-Gesamtbild“ gibt, kann es sein, dass Sie sich so sehr in ein bestimmtes Implementierungsdetail vertiefen, dass Sie die Gesamtstruktur und die Ziele vergessen

3. Zwei Prinzipien des C++-Designs

3.1 Abstraktion

  • Der Schlüssel besteht darin, die Schnittstelle von der Implementierung zu trennen
  • abstrakte Rolle
    • Der Code kann verwendet werden, ohne die zugrunde liegende Implementierung zu kennen . Hier ist ein einfaches Beispiel, bei dem ein Programm die in der Headerdatei <cmath> deklarierte Funktion sqrt() aufrufen kann, ohne zu wissen, welchen Algorithmus diese Funktion zum Ermitteln der Quadratwurzel verwendet. Tatsächlich kann die zugrunde liegende Implementierung der Quadratwurzelberechnung zwischen den Bibliotheksversionen variieren: Solange sich die Schnittstelle jedoch nicht ändert, funktioniert der Funktionsaufruf wie gewohnt
    • Das Prinzip der Abstraktion kann auch auf Klassen ausgeweitet werden: Sie können das cout-Objekt der ostream-Klasse verwenden, um Daten in die Standardausgabe zu übertragen. Sie müssen nicht wissen, wie cout Text auf dem Bildschirm ausgibt, Sie müssen nur wissen, wie öffentliche Schnittstelle
  • Abstraktion im Design nutzen
    • Sie sollten Ihre Funktionen und Klassen so entwerfen, dass Sie und andere Programmierer sie verwenden können, ohne die zugrunde liegende Implementierung zu kennen (oder davon abhängig zu sein).

3.2 Wiederverwendung

  • Die Designidee der Wiederverwendung gilt für den Code, den Sie selbst schreiben und verwenden. Programme sollten so konzipiert sein, dass sie Klassen, Algorithmen und Datenstrukturen wiederverwenden
  • In C++ sind Vorlagen eine Sprachtechnik zum Schreiben von Mehrzweckcode
    // 可用于任何二维棋盘游戏的 泛型游戏棋盘类
    template <typename PieceType>
    class GameBoard {
          
          
    public:
        void setPieceAt(size_t x, size_t y, PieceType *piece);
        PieceType *getPieceAt(size_t x, size_t y);
        bool isEmpty(size_t x, size_t y) const;
    private:
        ...
    }
    

4. Code wiederverwenden

4.1 Hinweise zu den Begriffen

  • Es gibt drei wiederverwendbare Codes

    • Code, der in der Vergangenheit geschrieben wurde
    • Code, der von Kollegen geschrieben wurde
    • Code, der von anderen Dritten als der aktuellen Organisation oder Firma geschrieben wurde
  • Der verwendete Code kann auf verschiedene Arten strukturiert sein

    • Eigenständige Funktion oder Klasse : Dieser Typ tritt häufig auf, wenn Sie Ihren eigenen Code oder den eines Kollegen wiederverwenden
    • Bibliotheken: Eine Bibliothek ist eine Sammlung von Code für eine bestimmte Aufgabe (z. B. das Parsen von XML) oder für eine bestimmte Domäne (z. B. Kryptografie) . Viele weitere Funktionen wie Threading- und Synchronisationsunterstützung, Vernetzung und Bilder sind häufig in der Bibliothek zu finden
    • Framework : Ein Framework ist eine Sammlung von Code, auf deren Grundlage ein Programm entworfen wird. Beispielsweise bieten Microsoft Foundation Classes (Microsoft FoundationClasses, MFC) ein Framework zum Erstellen grafischer Benutzeroberflächenanwendungen in Microsoft Windows. Ein Framework legt in der Regel die Struktur eines Programms fest
  • Application Programming Interface (API) ist ein weiterer Begriff, der häufig auftaucht. Eine API ist eine Schnittstelle, die von einer Bibliothek oder einem Code für einen bestimmten Zweck bereitgestellt wird . Programmierer beziehen sich beispielsweise häufig auf die Socket-API, die sich auf die verfügbar gemachte Schnittstelle einer Socket-Netzwerkbibliothek bezieht, und nicht auf die Bibliothek selbst

  • Wenn Sie eine grafische Benutzeroberfläche (GUI) unter Microsoft Windows in C++ schreiben möchten, sollten Sie ein Framework wie MFC (Microsoft Foundation Class) oder Qt verwenden. Möglicherweise wissen Sie nicht, wie man Low-Level-Code schreibt, um eine GUI unter Windows zu erstellen, und was noch wichtiger ist: Sie möchten keine Zeit mit dem Lernen verschwenden

4.2 Strategien zur Wiederverwendung von Code

4.2.1 Funktionen und Einschränkungen verstehen
  • Ist der Code für Multithread-Programme sicher?
  • Erfordert die Bibliothek bestimmte Compilereinstellungen für den Code, der sie verwendet? Ist dies gegebenenfalls für das Projekt akzeptabel?
  • Welche Art von Initialisierungsaufrufen sind für die Bibliothek oder das Framework erforderlich? Welche Art von Bereinigung ist erforderlich?
  • Von welchen anderen Bibliotheken ist die Bibliothek oder das Framework abhängig?
  • Welcher Konstruktor sollte aufgerufen werden, wenn von einer Klasse geerbt wird? Welche virtuellen Methoden sollten überschrieben werden?
  • Wenn ein Aufruf einen Speicherzeiger zurückgibt, ist der Aufrufer oder die Bibliothek für die Freigabe des Speichers verantwortlich? Wenn die Bibliothek dafür verantwortlich ist, wann wird der Speicher freigegeben?
  • Es wird dringend empfohlen, zu prüfen, ob intelligente Zeiger zum Verwalten des von der Bibliothek zugewiesenen Speichers verwendet werden können. Intelligente Zeiger wurden in Kapitel 1 besprochen.
  • Auf welche Fehlerbedingungen prüft der Bibliotheksaufruf? Welche Annahmen werden an dieser Stelle getroffen? Wie werden Fehler behandelt? Wie werden Clientprogramme benachrichtigt, dass ein Fehler aufgetreten ist?
  • Bibliotheken, die Meldungsfelder öffnen, Nachrichten an stderr/cerr oder stdout/cout weiterleiten und Programme beenden, sollten vermieden werden.
  • Was sind die vollständigen Rückgabewerte (nach Wert oder nach Referenz) eines Aufrufs?
  • Welche möglichen Ausnahmen werden ausgelöst?
4.2.2 Big-O-Notation
  • Die Leistung von Algorithmen und Bibliotheken wird üblicherweise in der Big-O-Notation dokumentiert
  • Die Big-O-Notation drückt die relative Leistung und nicht die absolute Leistung aus . Beispielsweise gibt die Big-O-Notation nicht an, wie lange die Ausführung eines Algorithmus dauert, sondern vielmehr, wie sich der Algorithmus verhält, wenn die Anzahl der Eingaben zunimmt. Die Big-O-Notation eignet sich nur für Algorithmen, deren Geschwindigkeit von der Eingabe abhängt, nicht für Algorithmen ohne Eingabe oder mit zufälliger Laufzeit
  • Die Big-O-Notation drückt die Leistung dieses Sortieralgorithmus in O(n) aus. bedeutet, dass bei Verwendung der Big-O-Notation n die Anzahl der Eingaben ist. O(n) bedeutet, dass die Geschwindigkeit des Sortieralgorithmus eine direkte lineare Funktion der Eingabe ist. Nicht alle Algorithmen haben einen linearen Zusammenhang zwischen der Leistung und der Eingabe. Die folgende Tabelle ist nach der Leistung sortiert

Fügen Sie hier eine Bildbeschreibung ein

4.2.3 Ein paar Tipps zum Verständnis der Leistung
  • Wenn sich die Datenmenge verdoppelt, verdoppelt sich auch die für den Algorithmus benötigte Zeit. Das sagt aber überhaupt nicht aus, wie lange es dauert! Wenn ein schlechter Algorithmus groß ist, ist dies niemals wünschenswert. Wenn der Algorithmus beispielsweise unnötige Festplattenzugriffe vornimmt, hat dies möglicherweise keinen Einfluss auf die große O-Notation, aber die Leistung ist sehr schlecht
  • Folgt man diesem Gedankengang, ist es schwierig, zwei Algorithmen zu vergleichen, die die gleiche Big-Oh-Laufzeit haben. Beispielsweise beanspruchen zwei verschiedene Sortieralgorithmen beide O (nlogn) O(nlogn)O ( n log n ) ist ohne Tests schwer zu sagen, welcher Algorithmus tatsächlich schneller ist
  • Die Big-O-Notation beschreibt die asymptotische Zeitkomplexität eines Algorithmus, wenn die Anzahl der Eingaben unendlich wächst . Bei kleinen Eingaben ist die Big-O-Zeit irreführend: O ( n 2 ) O(n^2), wenn die Eingabegröße klein istO ( n2 )Die tatsächliche Ausführungsleistung des Algorithmus ist möglicherweise besser alsO (logn) O(logn)O ( log n ) Algorithmus _ _
4.2.4 Plattformbeschränkungen verstehen
  • Bevor Sie mit der Verwendung von Bibliothekscode beginnen, ist es wichtig, die Plattform zu verstehen, auf der die Bibliothek ausgeführt wird. Das mag offensichtlich erscheinen, aber selbst die Bibliotheken, die behaupten, plattformübergreifend zu sein, weisen auf verschiedenen Plattformen subtile Unterschiede auf . Darüber hinaus umfassen Plattformen nicht nur unterschiedliche Betriebssysteme, sondern auch unterschiedliche Versionen desselben Betriebssystems
4.2.5 Lizenzen und Support verstehen
  • Die Verwendung von Bibliotheken von Drittanbietern bringt häufig komplexe Lizenzprobleme mit sich . Für die Nutzung von Bibliotheken Dritter ist teilweise die Zahlung einer Lizenzgebühr erforderlich. Darüber hinaus können weitere Lizenzbeschränkungen, einschließlich Exportbeschränkungen, gelten. Außerdem verlangen Open-Source-Bibliotheken manchmal, dass der damit verbundene Code Open-Source-Code ist
  • Die Verwendung von Bibliotheken von Drittanbietern führt ebenfalls zu Supportproblemen . Bevor Sie eine Bibliothek verwenden, ist es wichtig, den Prozess der Übermittlung von Fehlern und die Zeit zu verstehen, die für deren Behebung erforderlich ist. Bestimmen Sie nach Möglichkeit, wie lange die Bibliothek unterstützt wird, damit Sie entsprechend planen können
4.2.6 Prototyp
  • Wenn Sie eine neue Bibliothek oder ein neues Framework zum ersten Mal verwenden, ist es eine gute Idee, schnell einen Prototyp zu schreiben . Das Testen des Codes ist die beste Möglichkeit, sich mit der Funktionalität der Bibliothek vertraut zu machen. Es sollte in Betracht gezogen werden, die Bibliothek vor dem Programmentwurf zu testen, damit man sich mit den Fähigkeiten und Einschränkungen der Bibliothek vertraut machen kann. Mit diesem praktischen Test können auch die Leistungsmerkmale der Bibliothek ermittelt werden
  • Selbst wenn der Prototyp der Anwendung keine Ähnlichkeit mit der endgültigen Anwendung aufweist, wird die für die Prototypenerstellung aufgewendete Zeit nicht verschwendet. Finden Sie es nicht schwierig, einen Prototyp einer tatsächlichen Anwendung zu erstellen. Schreiben Sie ein Dummy-Programm, um die Bibliotheksfunktionalität zu testen, die Sie verwenden möchten. Dies geschieht, um sich mit der Bibliothek vertraut zu machen

5. Entwerfen Sie ein Schachprogramm

5.1 Anforderungen

  • Bevor Sie mit dem Design beginnen, sollten Sie die Anforderungen an die Funktionalität und Leistung des Programms klären . Idealerweise sollten diese Anforderungen in Form einer Anforderungsspezifikation dokumentiert werden. Die Anforderungen an ein Schachprogramm sollten die folgenden Arten von Spezifikationen enthalten. Natürlich sollten die tatsächlichen Anforderungsspezifikationen detaillierter sein als die folgenden und mehr Elemente enthalten
    • Das Programm unterstützt Standardschachregeln
    • Das Programm unterstützt zwei Spieler. Das Programm stellt Computerspielern keine künstliche Intelligenz zur Verfügung
    • Das Programm bietet eine textbasierte Schnittstelle
      • Das Programm stellt die Tafel und die Figuren im Klartext bereit
      • Die Spieler bewegen Figuren auf dem Brett, indem sie Zahlen eingeben, die die Positionen darstellen

5.2 Designschritte

Der Entwurf sollte bei Bedarf Diagramme und Tabellen enthalten. Der Industriestandard für die Erstellung von Diagrammen heißt UML (Unified Modeling Language) . UML definiert eine Reihe von Standarddiagrammen, die zur Veranschaulichung des Softwaredesigns verwendet werden können (z. B. Klassendiagramme, Sequenzdiagramme usw.). .) . Es wird empfohlen, UML zu verwenden oder zumindest zu versuchen, UML-ähnliche Diagramme zu verwenden. Es ist jedoch nicht notwendig, sich strikt an die UML-Syntax zu halten, da das Diagramm klar und leicht verständlich ist, was wichtiger ist als die korrekte Syntax

5.2.1 Teilen Sie das Programm in Subsysteme auf
  • Der erste Schritt beim Entwurf besteht darin, das Programm in allgemeine funktionale Subsysteme zu unterteilen und die Schnittstelle und Interaktionsbeziehung zwischen den Subsystemen festzulegen . An diesem Punkt müssen Sie nicht über bestimmte Datenstrukturen und Algorithmen oder sogar Klassen nachdenken, sondern versuchen Sie einfach, die verschiedenen Teile des Programms und die Interaktion zwischen ihnen zu spüren
  • Es wird empfohlen , das Model-View-Control (MVC)-Muster zu verwenden , um Datenspeicherung und Datenanzeige klar zu trennen
    • Das MVC-Muster begründet die Idee, dass viele Anwendungen häufig mit einem Datensatz arbeiten, eine oder mehrere Ansichten dieser Daten bearbeiten und diese Daten bearbeiten. In MVC wird dieser Datensatz als Modell bezeichnet, die Ansicht ist eine spezifische Schnittstelle zum Modell und der Controller ändert den Code als Reaktion auf ein Ereignis. Die drei Komponenten von MVC interagieren in einer Rückkopplungsschleife, die Aktion wird vom Controller verarbeitet, der Controller passt das Modell an und gibt die Änderungen an die Ansicht zurück
    • Im MVC-Design sind die Subsysteme ChessBoard und ChessPiece im Index der Modellteil, ChessBoardView und ChessPieceView der Ansichtsteil und der Player der Controller-Teil

Fügen Sie hier eine Bildbeschreibung ein

  • Da Tabellen die Beziehungen zwischen Subsystemen nicht visuell darstellen können, werden Diagramme normalerweise verwendet, um die Subsysteme eines Programms anzuzeigen, wobei Pfeile Aufrufe von einem Subsystem zu einem anderen darstellen. Die folgende Abbildung zeigt die verschiedenen Subsysteme des Schachspiels mit einem UML-Anwendungsfalldiagramm

Fügen Sie hier eine Bildbeschreibung ein

5.2.2 Gewindeschneidmodell auswählen
  • Während der Entwurfsphase können Sie die Anzahl der High-Level-Threads in Ihrem Programm auswählen und angeben, wie die Threads interagieren
    • Beispiele für High-Level-Threads sind UI-Threads, Audiowiedergabe-Threads, Netzwerkkommunikations-Threads usw.
    • Bei einem Multithread-Design sollte die gemeinsame Nutzung von Daten so weit wie möglich vermieden werden , was das Programm einfacher und sicherer machen kann
    • Wenn gemeinsame Daten nicht vermieden werden können, sollten Sperranforderungen festgelegt werden . Wenn Sie nicht damit vertraut sind oder die Plattform kein Multithreading unterstützt, sollte das Programm Singlethreading sein
    • Wenn das Programm jedoch mehrere unterschiedliche Aufgaben hat, die jeweils parallel laufen, ist Multithreading eine gute Wahl. Beispiel: Bei GUI-Programmen führt häufig ein Thread das Hauptprogramm aus, während andere Threads darauf warten, dass der Benutzer eine Taste drückt oder einen Menüpunkt auswählt
    • Ein Schachprogramm benötigt nur einen Thread, um den Spielfluss zu steuern
5.2.3 Angeben der Klassenhierarchie für jedes Subsystem
  • Ein Schachprogramm benötigt eine Klassenhierarchie zur Darstellung von Schachfiguren , wie in der folgenden Abbildung dargestellt. In dieser Klassenhierarchie fungiert die generische Klasse ChessPiece als abstrakte Basisklasse , und die Klasse ChessPieceView verfügt über eine ähnliche Klassenhierarchie

Fügen Sie hier eine Bildbeschreibung ein

  • Eine andere Klassenhierarchie wird für die ChessBoardView-Klasse verwendet, um die Textschnittstelle oder grafische Benutzeroberfläche des Spiels zu implementieren . Die folgende Abbildung zeigt diese Klassenhierarchie, mit der das Schachbrett im Textmodus in der Konsole oder in 2D- oder 3D-Grafiken angezeigt werden kann. Die verschiedenen Klassen der Player-Controller- und ChessPieceView-Klassenhierarchie benötigen ebenfalls eine ähnliche Klassenhierarchie

Fügen Sie hier eine Bildbeschreibung ein

5.2.4 Geben Sie Klassen, Datenstrukturen, Algorithmen und Muster für jedes Subsystem an

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

5.2.5 Festlegen der Fehlerbehandlung für jedes Subsystem

Fügen Sie hier eine Bildbeschreibung ein

Acho que você gosta

Origin blog.csdn.net/qq_42994487/article/details/131083710
Recomendado
Clasificación