【TUM】PR:Praktikum Industrielle Softwareentwicklung für Ingenieure C++

Cpp 备考

  1. Nennen Sie zwei Gründen für die Verwendung von Zeigern.
  • Arrays kann man in C++ nur mit Hilfe von Zeigern ansprechen.
  • Zeigern erlauben den Zugrif auf dynamische …
  1. Nennen Sie den essentiellen Untershied zwischen iner while-Schleife und do-while-Schleife. Wie oft werden diese Schleifen mindestens durchlaufen?

Bei der while-Schleife wird die Wiederholuingsbedingun, erst vor jede, Durchlauf geprüft.
Bei der do-while-Schleife wird die Wiederholuingsbedingung, erst nach jedem Durchlauf geprüft
wie oft?:
while-Schleife: mindestens 0
do-while-Schleife: 1

3.Erlären Sie kurz, was eine Referenz ist und worin ihr Unterschied zu einer üblichen Variable und einem Zeiger liegt.
Eine Referenz ist eine Variable, deren Wert zu jeder Zeit dem Wert einer anderen Variablen entspricht. Änderungen am Wert dieser Variablen haben Änderungen am Wert der Referenz zur Folge. Umgekehrt wirken sich Änderungen am Wert der Referenz auch auf den Wert der referenzierten Variablen aus.

Übliche Variable:
Eine übliche Variable speichert einen Wert und wird durch ihren Namen identifiziert.
Bei der Verwendung von üblichen Variablen werden Kopien der Werte übergeben oder zugewiesen.
Änderungen an einer üblichen Variablen beeinflussen nicht automatisch andere Variablen mit demselben Wert.
参数以副本形式传递给函数,函数执行完毕后删除副本。werden Parameter an Funktionen als Kopien übergeben, die Kopien werden nach abgeschlossener Funktionsausführung gelöscht

*Der Vorteil gegenüber Pointern besteht im Sicherheitsgewinn. Erstens können Pointer auch noch existieren, wenn die referenzierte Variable gelöscht ist. Greift man dann auf den Pointer zu, dereferenziert man undefinierten Speicherbereich, was unweigerlich zu einem Fehler führt.
当被引用的变量被删除时,指针仍然存在。如果这时访问指针,就会取消引用未定义的内存空间,从而不可避免地导致错误。
*Die Verwendung von Zeigern erfordert die Arbeit mit Speicheradressen und dereferenzierende Operationen.
Zweitens kann man Referenzen im Normalfall nicht „umbiegen“, was die damit verbundenen Manipulationsmöglichkeiten verhindert.
Ein weiterer Vorteil gegenüber Pointern liegt in der vereinfachten Parameterübergabe beim Aufruf von Funktionen.

  1. Erklären Sie die Aufgabe, die folgender Code erfüllt. Nennen und erklären Sie anhand der gegeben Funktion das Prinizip, das bei der Implementirung der Ufnciotn verwende wurde. Es seiem x =3 and y=4. Welchen Wert gibt die Funktion zurück?
#include <iostream>

using namespace std;


int some_method(int x, int y){
    if(y == 1){
        return x;
    }else{
        return x*some_method(x, y-1);
    }
}


int main()
{
    cout<<"Hello World";
    cout<<some_method(3,4);
    return 0;
}

1. Prinzip: Rekusion.Während bei Iteration die Programmteile nacheinander abgearbeitet werden, ruft sich bei einer Rekursion die Funktion immer wieder selbst auf, bis ein bestimmtes Abbruchkriterium erreicht wird. Da die aufrufende Funktion warten muss, bis die aufgerufene Funktion das Ergebnis zurückliefert, wächst der call stack stetig an. Erst wenn die aufgerufenen Funktionen ihren Wert zurückliefern, werden die Funktionen und ihre Daten vom Stack entfernt.

2. dieese Code berechnet 3 hoch 4(3^4)
3. Die Funktion gibt 81 zurück

  1. Erklären Sie die Aufgabe des try-Blocks und des catch-Blocks bei der Behandlung von Ausnahmen. Erklären Sie in diesem Zusammenhang auch, wofür das Schlüsselwort throw verwendet wird.

Möchte eine Komponente einen bestimmten Fehler behandeln, so wird der kritische Codeabschnitt, in dem Fehler auftreten können, in einem try{} - block eingeschlossen. Die entsprechenden Fehler können in einem anschließenden catch{} -block abgefangen und behandelt werden.

Tritt in einer Komponente ein Fehler auf, den sie nicht selbst behandeln kann, so bricht sie ihre Ausführung ab und wirft eine Ausnahme (throw object). Diese Ausnahme wird an die aufrufende Komponente weitergeben. Dabei enthält Objekt Informationen über den aufgetretenen Fehler.

  1. Nennen Sie einen Vorteil des der Objectorientireung zu Grunde liegenden Prinzips der Kapselung. Nennen und beschreiben Sie möchglich Zugriffsspezifizierer von Attributen und Methoden.
    封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。Die Kapselung ist ein Konzept in der objektorientierten Programmierung, das Daten und die Funktionen, die sie bearbeiten, miteinander verbindet, so dass sie vor äußeren Einflüssen und Missbrauch geschützt sind.
    Vorteil: die Attribute eines Objektes nur von ihm selbst und keinesfalls von außen geändert werden dürfen.Dadurch wird die Sicherheit gewährleistet, indem Störungen und Missbrauch von außen vermieden werden.
    这个题我不是很确定
    Zugriffsspezifizierer von Attributen:
    public Das Attribut oder die Methode kann in jeder anderen Methode, auch außerhalb des Objekts aufgerufen werden. Dieser Aufruf geschieht durch:
    ..

    private Das Attribut/die Methode kann nur innerhalb der Methoden der eigenen Klassen aufgerufen werden. Der Aufruf erfolgt genauso wie oben. Eine Verwendung ist jedoch innerhalb der eigenen Methoden möglich.

Mithilfe der get()-Methoden kann man Attributwerte auslesen und mit den set()-
Methoden Attributwerte manipulieren

*Projected Alle Attribute und Methoden, die in der Elternklasse als protected deklariert wurden, lassen sich nur innerhalb der Eltern- und Kindklasse aufrufen. Fremde Objekt haben darauf keinen Zugriff.

  1. Erklären Sie kurz due statische und die dynamische Speicherverwaltung. Wann und warum kommt welche Art der Spercherverwaltung zum Einsatz? Schreiben Sie den Code für eine dynamsiche Spericherreservierung für das feld dArray mit der Dimension 72 vom Datentyp double.

Alle Variablen und Funktionen, die Sie im Quelltext nicht über Pointer definieren, werden statisch angelegt.
Der Arbeitsspeicherbereich, in dem sie gespeichert werden, heißt Stapelspeicher (Stack)

Nicht immer ist vor der Laufzeit eines Programms bekannt, wie viel Speicher für die Programmausführung notwendig ist
Diesem Problem begegnet man, indem man zur Laufzeit Speicher reserviert. Dieser Vorgang wird als dynamische Speicherreservierung bezeichnet und wird mit den Operatoren new und delete umgesetzt.
Stack greift nur auf lokale Variablen zu, während Heap Ihnen den globalen Zugriff auf Variablen ermöglicht.
Stack-Variablen können nicht in der Größe geändert werden, während Heap-Variablen in der Größe geändert werden können.
Stack-Speicher wird in einem zusammenhängenden Block zugewiesen, während Heap-Speicher in beliebiger Reihenfolge zugewiesen wird. Heap ist bei Variablenzugriff langsamer als Stack

double* dArray = new double[72];
    
//When the dArray is not needed anymore:

delete dArray[];
  1. Was ist der essentielle Unterschied zwischen statische und nicht statischen Variablen? Geben Sie ein Beispiel für den sinnvollen Einsatz einer statischen Variable.

statische Variable sind auf dem Stack während dynamiische Variable sind auf dem Heap.
Stack greift nur auf lokale Variablen zu, während Heap Ihnen den globalen Zugriff auf Variablen ermöglicht.
Stack-Variablen können nicht in der Größe geändert werden, während Heap-Variablen in der Größe geändert werden können.
Beispiel:
1: Wenn man ein for loop schreibt, es macht mehr Sinn die Variable als statisch zu declarieren
2: Wenn man eine Klassenattribute deklariert, es ist sinnvoller die Variabler als statisch zu deklarieren.

  1. Was sind die Hauptintentionen bei der Vererbung?

Durch das Prinzip der Vererbung wird ermöglicht, dass Klassen mit ähnlichen Methoden und Attributen voneinander abgeleitet werden können, sie müssen somit nur einmal implementiert werden.

  1. Implementieren Sie eine Funktion in C++, die zwei als Zeiter übergebende Interger Zahlen vertauscht
#include <iostream>

void swap(int *a, int *b){
    
    int c = *a;
    *a = *b;
    *b = c;
    return;
}


int main()
{
    int a =1;
    int b = 2;
    std::cout<<"Now a is: "<<a<<"Now b is: "<<b<<std::endl;
    swap(&a,&b);
    
    std::cout<<"Now a is: "<<a<<"Now b is: "<<b<<std::endl;
    return 0;
}
  1. Erklären Sie, was Iteratoren sind und wozu sie im Zusammenhang mit listen verwendet werden. Welche zwei Iteratoren (bzw. Methoden) stelle eine List immer bereit? Worauf zeigen Sie?

Mit einem Iterator wird eine Stelle in der Liste bezeichnet. Das Element an dieser Stelle kann dann zum Beispiel ausgelesen, verändert oder gelöscht werden. Außerdem können dort neue Elemente eingefügt werden. Ein Iterator wird über das Schlüsselwort container::iterator definiert.

Damit der Iterator benutzt werden kann, muss er initialisiert werden. Typischerweise initialisiert man einen Iterator auf die Position des ersten Elementes einer Liste. Die Funktion listenname.begin() liefert einen Iterator, der auf die Position des ersten Elements der Liste zeigt. Die Position hinter dem letzten Element der Liste liefert die Funktion listenname.end().

std::list<float>::iterator myiterator

for(myiterator = list1.begin(); myiterator != list1.end(); myiterator++)
{
*(myiterator) += 2.5;
}
  1. Geben Sie zu erwartended Ausgabe für folgende Code an.
#include <iostream>

class A
{
public:
  virtual void f ()
  {
    std::cout << "A.f";
    g ();

  }

  void g ()
  {
    std::cout << "A.g";
  }

};

class B:A
{
public:
  void f ()
  {
    std::cout << "\nB.f";\
    A::f ();
    g ();
  }

  void g ()
  {
    std::cout << "B.g";
  }

};


int
main ()
{
  A a = A ();
  a.f ();
  B b = B ();
  b.f ();

  return 0;
}    

Die Ausgage ist:


A.fA.g
B.fA.fA.gB.g


书上知识点

Vom C/CPP Code zum Programm/fertigen Maschinencode

 ---------------(*.c/*.h/*.cpp)  -----------     --------      -------
丨Quelltexteditor丨----------->丨Präprozessor丨->丨Compiler丨 ->丨Linker丨-->*.exe
 ---------------                 -----------    --------      -------

1. c/c++

Einer der wichtigsten Unterschiede zu C

  • Wechsel von imperativen zum objektorientierten Programmierparadigma.

1.6 Vom Code zum Programm

1.6.1. Quelltexteditor

(1). Den Programmcode schreiben in .cpp und .h Dateien. Mit Quelltextformatierungsfunktion

  • Note: Implementierungsdatei(.c/.cpp) und Headerdatei:
    Um die Übersichtigkeit eines Quelltextes zu Verbessern.
    • Implementierungsdatei enthalten:
      • Funktionsdefinitionen;
      • Einbindung der zugehörigen Headerdatei.
    • Headerdatei enthalten(*.h):
      • Funktionsdeklaration: Funktionskopf, der nur den Funktionsnamen, Rück- und Übergabeparameter beschreibt;
      • Strukturdefinition;
      • Einbindung anderer Header mit benötigten Funktionen und Bibliotheken;

(2).Quelltextformatierungsfunktion:

  • Syntaxhervorhebung: z.B. Datentypen in blau, etc;
  • Automatische Einrückungen;
  • Automatisches Schließen von Klammern;
  • Automatische Vervollständigung;
  • Automatische Eingabekorrektur.
1.6.2. Präprozessor

(1) Ein primitives Programm. Keine Analyse der Funktionalität des Codes, sondern geht nach dem Prinzip Suchen und Ersetzen.

(2) Aufgabe von Präprozessor

  • Ersetzen der Präprozessorkonstanten durch ihren Wert;

  • Entfernen der Kommentare;

  • Einbinden der Header- in die Implementierungsdateien;

    • Präprozessor wird durch Direktiven gesteuert, mit # beginnen. – #include;

    • Mehrfacheinbindung : die mehrfache Eindung eines Headers durch den Präprozesser in eine Datei.

    • Zur Verhinderung der Mehrfachereinbindung: jedem Header Präprozessoranweisungen voranstellt. Dadurch prüft der Präprozessor vor Einbindung eines Headers, ob dieser bereits definiert wurde.

    #ifndef FILENAME_H
    #define FILENAME_H
        // endif in the end
    #endif
1.6.3. Compiler

(1) die einzelnen zusammengebundenen Implementierungs- und Headerdateien in Maschinencode übersetzt. Dabei entstehen Objektdateien .o ist die entsprechende Dateiendung. Nicht ausführbar!!!

1.6.4. Linker

Aufgabe:

  • Die einzelnen Objektdateinen zu einem ausführbaren Programm bindet. Programm liegt als .exe datein
  • Bindet Linker externe Bibliotheken in Form von Objektdateinen ein, welche vorgefertigte Funktionen beinhalten.

1.7 Eclipse

  • Eine integrierte Entwicklungsumgebung ist eine umfangreiche Software, die für die Softwareentwicklung nötigen Programme beinhaltet.
    Dateiverwaltung, Vorlagen für unterschiedliche Anwendungen, Quelltexteditor mit Quelltextauszeichnungsfunktion, Präprozessor, Complier, Linker, Debugger.
MenüpunktErklärung
Build ProjectDas Projekt wird in ein ausführbares Programm umgewandelt. Dabei werden alle geänderten .cpp Dateinen neu erstellt
Clean ProjectLöscht die temporären Dateien der Projektmappe
PropertiesÖffnet ein Fenster, um Erstellungen beim Projekt vorzunehmen

1.8 Grundlagen

1.8.1 Variablen und Datentypen

(1) Datentype in C++ auf einem gängigen 32-bit System:

TypeSpeicherplatzWertebereich(decimal)
bool1 ByteTrue(1), false(0)
char1 Byte-128 bis 127
unsigned char1 Byte0 bis 255
short2 Byte-32.768 bis +32.767
unsigned short2Byte0 bis 65.535
int4 Byte-2.147.483.648 bis +2.147.483.647
unsigned int4 Byte0 bis 4.294.967.29
long4 Byte-2.147.483.648 bis +2.147.483.647
unsigned long4 Byte0 bis 4.294.967.29
float4 Byte±3,4* 10^38 Genauigkeit: 6 Stellen
double8 Byte±1,7 *10^308 Genauigkeit: 15 Stellen
long double10 Byte±1,1* 10^4932 Genauigkeit: 19 Stellen

(2) Deklaration und Initialisierung von Variablen
In C++ müssen Variablen deklariert werden, bevor sie verwendet werden können. Damit wird der Compiler angewiesen, entsprechend Speicherplatz für die Variable zu
reservieren.
Erst nach der Initialisierung, d.h., wenn der Variable das erste Mal ein Wert zugewiesen wurde, hat sie sicher einen definierten Wert

    int iNumber;   // Deklaration
    iNumber = 10;  // Initialisierung
    float fNumber = 2.8; // Deklaration und Initialisierung
    char chValue = 'A';  // Deklaration und Initialisierung

(3) Gültigkeitsbereich von Variablen
Variablen sind im Allgemeinen nur in dem Block gültig, in dem sie deklariert wurden. D.h. um eine Variable in einem bestimmten Block nutzen zu können, muss sie in
diesem oder in einem umschließenden Block zuvor definiert werden. Block wird durch geschweifte Klammern -{ und }-.

Variblen, die außerhalb einer Funktion (oder) Klasse definiert werden, sind globale Variablen, die
allen Funktionen in demselben Namespace zugänglich sind.

1.8.2 Ein- und Ausgabe auf der Konsole

In Bibiliothek .

OperationFunktion
std::coutErmöglicht die Ausgabe auf der Konsole. Die auszugebenden Inhalte (Text, Variablen, etc.) übergibt man der Konsole mithilfe des Schiebeoperators <<
std::cinErmöglicht das Einlesen von Benutzereingaben aus der Konsole mit Hilfe des Schiebeoperators >>: std::cin >> <Variablenname>
std::cin.fail()gibt an, ob das Failbit gesetzt ist (true, wenn Failbit tatsächlich gesetzt ist).
std::cin.clear()löscht ein zuvor gesetztes Failbit von cin.
std::cin.ignore(m, '\n')ignoriert maximal m Zeichen im Stream von cin bis zum ersten Auftreten des Zeichens \n (Zeilenumbruch).

1.9 Kontrollstrukturen

1.9.1 Verzweigungen

Verzweigungen bestehen aus einer oder mehreren Bedingungen und unterschiedlichen Codeblöcken. Erreicht die Programmausführung eine Verzweigung, wird zuerst die vorgegebene Bedingung überprüft. Danach wird, je nach Ergebnis, der entsprechende Codeblock ausgeführt.

(1) if-Verzweigung

   if(<Bedingung>)
   {
        // Anweisungen für wahre Bedingung 
   }
   else
   {
        // Anweisungen für falsche Bedingung
   }
  • Verkürzung der if-Verzweigungen, aber unübersichtlich.
    <Bedingung>?<Anweisung für wahre Bedingung> : <Answeigung für falsche Bedingung>
  • switch-Verzweigung. Je nach Wert einer Variablen, eine andere Aktion ausgeführt werden soll. Ermöglicht die Auswahl zwischen beliebig vielen Alternativen.
    switch(<Variable>)
    {
        case <Wert>:
            // Anweisung
            break;
        case <Wert>:
            // Anweisung
            break;
        case <Wert>:
            // Anweisung
            break;
        // weitere Case-Abfragen
        default:
            // Anweisung
    }     
* break: Die Anweisung break bedeutet das Verlassen der Verzweigung und muss nach jeder Anweisung aufgerufen werden, damit nicht zusätzlich die darauffolgenden Cases ausgeführt werden.
1.9.2 Schleifen

Schleifen bestehen aus einem Codeblock und einer Wiederholungsbedingung. Der Codeblock wird so lange ausgeführt, wie die Wiederholungsbedingung erfüllt ist.

Schleife TypePunkte
for-SchleifeErst im Schleifenkopf der Zähler initialisiert werden
while-Schleifewird die Wiederholungsbedingung vor jedem Durchlauf geprüft
do-while-Schleifewird die Wiederholungsbedingung erst nach jedem Durchlauf geprüft und die Schleifenausführung gegebenenfalls abgebrochen. Schleifen mindestens einmal durchlaufen werden; –; in the end
1.9.3 Funktionen

Definiert außerhalb main funktion, die in folgenden Block angeruft wird.

1.10 String

#include <string>

1.10.1 Methoden und Operatoren in
Methode/OperatorFunktion
char ch = s1[i]ist eine Referenz auf das i-te Zeichen von s1.
int n = s1.size();n ist die Anzahl der Zeichen in s als int.
s1.append(n, x);
s1.append(s2)
Fügt x n-Mal am Ende von s1 an;
Fügt s2 am Ende von s1 an
s1.replace(pos, n, s2)Ersetzt die Zeichen im Intervall [pos, pos+n] in s1 durch s2. s1 und s2 sind dabei vom Typ string.
int n = s1.compare(s2)Vergleicht s1 und s2: n ist 0, wenn s1 und s2 identisch; n kleiner 0, wenn s1 < \lt < s2
int pos = s.find(x)Findet die erste Position von x in s.
std::string s2 = s1.substr(pos, n)
Kopiert die Zeichen im Intervall [pos, pos+n] von s1 nach s2.
int x = stoi(s);Konvertiert den string s zu einem int.

1.11 Pointer

1.11.1 Definition

Ein Pointer ist eine Variable, die die Speicheradresse einer anderen Variablen enthalt. Ein Pointer gibt somit an, an welcher Stelle im Hauptspeicher die Variable liegt.

1.11.2 Gründe für den Einsatz von Pointern
  • In C und C++ werden Parameter an Funktionen als Kopien übergeben. Die Kopien werden nach abgeschlossener Funktionsausführung gelöscht. Ein Verändern des Übergabeparameters im Gültigkeitsbereich der aufrufenden Funktion durch Veränderung der Kopie des Parameters in der aufgerufenen Funktion ist somit nicht möglich.
    Möchte man nun die Übergabeparameter in der Funktion verändern, so kann man der Funktion einen Pointer auf die Variablen übergeben. Dies ermöglicht den Zugriff auf den Zugriff auf den Variablenwert über die Adresse, die der Pointer enthalt so kann man über die Adresse der Variablen im Hauptspeicher sowohl auf deren Inhalt zugreifen.
  • Arrays kann man in C und C++ generell nur mit Hilfe von Pointern ansprechen.
  • Pointer erlauben den Zugriff dynamisch reservierte Speicherbereiche.
1.11.3 Syntax für Pointer

(1) Einen Pointer deklarieren Sie durch Angabe des Datentypens der Variablen, auf die er zeigt, gefolgt von dem Stern-Operator und den Namen des Pointers.

Die Adresse einer bestehenden Variable erhalten Sie durch voranstellen des & \& &-operators vor den Variablenamen.

    // Deklaration eines Pointers auf einen char
    char* pchLabelList;
    // Deklaration und Initialisierung eines Pointers auf einen float mit der Adresse im Hauptspeicher
    float* pfWidth = 0x0013FE94;
    // Initiallisierung eines Pointers auf die Adresse einer int Variable
    int* piPieceNumber = $\&$ipiPieceNumber;

(2) Dereferenzierung

  • Erstens den Stern-Operator. Er steht vor dem zu deferenzierenden Pointer.
  • Zweitens den Klammern-Operator[], der dahinter steht.
     // Zugreifen auf den Wert den piPieceNumber referenziert
     int iNumber = *piPieceNumber;
     iNumber = piPieceNumber[];
  • Wenn auf den Wert der Hauptspeicheradresse vor oder nach der Addresse zugreifen möchten, die der Pointer referenziert.
    char chLetter = piPieceNumber[i] 
  • Point sehr häufig für die Übergabe von Parametern in Funktionen verwendet.

1.12 Referenzen

Ein Referenz ist eine Variable, deren Wert zu jeder Zeit dem Wert einer anderen Variablen entspricht. Änderungen am Wert dieser Variablen haben Änderungen am Wert der Referenz zur Folge. Umgekehrt wirken sich die Änderungen am Wert der Refernz auch auf der Wert der referenzierten Variablen aus..

  • Die Referenz ist während ihrer Lebensdauer mit genau einer Variablen verbunden. Diese Variable muss bereits existieren, wenn die Referenz initialisiert wird.
1.12.1 Gründe für den Einsatz
  • Der Vorteil von Referenzen gegenüber Kopien ist, dass man stets auf den aktuellen Wert der referenzierten Variablen zurückgreifen kann und der Wert deshalb nicht veraltet ist.

  • Der Vorteil gegenüber Pointern besteht im Sicherheitsgewinn. Erstens können Pointer auch noch existieren, wenn die referenzierte Variable gelöscht ist. Greift man dann auf den Pointer zu, dereferenziert man undefinierten Speicherbereich, was unweigerlich zu einem Fehler führt.

  • Zweitens kann man Referenzen im Normalfall nicht “umbiegen” , was die damit verbundenen Manipulationsmöglichkeiten verhindert.

  • Ein weiterer Vorteil gegenüber Pointern liegt in der vereinfachten Parameterübergabe beim Aufruf von Funktionen.

1.12.2 Syntax

Achtung: Referenzen können nur intialisiert werden. Weil eine Referenz während ihrer Lebensdauer mit der referenzierten Variablen verbunden. Eine Deklaration ist nicht möglich, weil die Referenz dann einen undefinierten Bezugswert hätte.

Deklaration
Macht dem compiler Datentyp, Bezeichner und Dimension einer Variablen bekannt, um Speicher für die Variable anzulegen
int iPieceNumber;
DefinitionZuweisung eines Wertes zu einer bereits deklarieten Variable
iPieceNumber = 35
InitialisierungErstmalige Zuweisung eines Werts an eine Variable.
int iPieceNumber = 35;
  • Eine Referenz wird durch Angabe des Datentyps der Bezugsvariable gefolgt von einem & \& &-Operator, dem Bezeichner und der Zuweisung initialisiert. Der & \& &-Operator sollte unmittelbar hinter dem Datentyp stehen, da er andernfalls eine andere Bedeutung impliziert.
1.12.3 Zugriff über Referenzen

Referenzen können im Code genauso behandelt werden, wie die Variablen, die sie referenzieren.
(1) Parameterübergabe per Referenz
Zu handhaben als Pointer

  • Man erstellt eine Funktion, die eine Referenz als Übergabeparameter erwartet.
  • Man ruft die Funktion auf und übergibt ihr eine Variable.
  • Die Funktion bildet eine Referenz auf die übergegebene Variable und arbeitet damit.
  • Alle Änderungen, die die Funktion an der Referenz vornimmt, finden automatisch auch an der Variable statt.

1.13 Speicherverwaltung

1.13.1 Statische Speicherverwaltung

Alle Variablen und Funktionen, die im Quelltext nicht über Pointer definiert werden, werden statisch angelegt.

  • Der Arbeitsspeicherbereich, im sie gespeichert werden, heißt Stapelspeicher(Stack).
  • Element in Stapelspeicher nur in umgekerhter Reihenfolge gelesen werden können, in der sie gespeichert wurden.
  • Die zuerst gespeicherten Elemente liegen ganz unten und nur das oberste Element kann abgenommen werden – Last-In-First-Out-Prinzip(LIFO) genannt.
1.13.2 Dynamische Speicherverwaltung

Wenn die nötige Speicher für die Programmausführung nicht bekannt, kann man zur Laufzeit Speicher reserviert.
Beispielsweise kann man bei einem Texteditor nict vorhersagen, wie groß der vom Bediener eingegebene Text wird.
(1) new-Operator. Mit new können Sie zur Programmlaufzeit beliebige Variablen anlegen. Sie übergeben dem Operator den Datentyp der zu erzeugenden Variablen. Der Operator liefert dann einen Pointer auf genau eine solche dynamisch im Speicher erzeugte Variable zurück.

        // Dynamische Speicherreservierung für eine Variable vom Type double
        double* pdWidth = new double;
        // Dynamische Speicherreservierung für ein Array der Dimension 80 vom Type char
        char* pchName = new char[80]

(2) delete-Operator. Werden die dynamishc erzeugten Variablen nicht mehr benötigt, müssen Sie sie explizit löschen.

       // Freigeben einer dynamisch angelegten Variable
        delete pdWidth
       // Freigeben eines dynamisch angelegten Arrays
        delete[] pchName      // Nur delete, wird lediglich der Kopf des Arrays freigegeben.
  • Unterschied zwischen dynamischen und statischen Speicherreservierung:
    • In statischer Speicherreservierung, der Speicherplatz ist schon davor fix definiert und kann nicht geändert werden. Durch dynamischer Speicherreservierung kann man zur laufzeit Speicher reserviert und darin beliebige Variablen anlegen.
    • Statische Speicherreservierung liegt in stack, und dynamische Speicherreservierung liegt in heap.

1.14 Idee der Objektorientierung

(1) Programmierparadigma ist ein übergeordnetes Denkmuster und legt damit die Struktur und den Ablauf eines Programmes fest.

  • Imperative Programmierparadigma. Funktionen werden nacheinander angerufen und abgearbeitet.

    • Nachteile: 1. Da globale Variablen überall zugänglich sind, können sie leicht manipuliert werden; 2. Da die Funktionen als unstrukturierte Liste vorliegen, geht mit zunehmender Größe des Projektes die Übersicht verloren.
  • Objektorientierte Programmierparadigma

(2) Objekte. Ein Objekt ist eine Datenstruktur mit Bezeichner, die sowohl Variablen als auch Funktionen enthält. Elemente:
1. Bezeichner ist der eindeutige Name des Objektes.
2. Attribute enthalten Daten, die sich auf das zugehörige Objekt beziehen.
3. Methoden eines Objektes manipulieren die Attribute dieses Objekts.

1.16 Klassen

1.16.1 Zusammenhang zwischne Klassen und Objekten

Jedes Objekt gehört genau einer Klasse an. Jedes Objekt kann eindeutig identifiziert werden. Die Klasse dient als Bauplan der zugehörigen Objekte. Mithilfe einer Klasse lassen sich beliebig viele Objekte erstellen.
Aufbau eines Objektes:

  • Anzahl der Attribute
  • Datentypen der Attribute
  • Namen der Attribute
  • Standardwerte der Attribute
  • Zugriffsbeschränkungen auf die Attribute
  • Anzahl der Methoden
  • Deklaration der Methoden
  • Implementierung der Methoden
  • Zugriffsbeschränkungen auf die Methoden
1.16.2 Implementierung einer Klasse

Die Implementierung einer Klasse teilt sich in C++ in zwei Dateien auf, eine Header- und eine cpp-Datei.

  • Die Headerdatei enthält sämtliche, d.h. in dieser Datei wird festgelegt:
    • Anzahl der Attribute;
    • Datentypen der Attribute;
    • Namentypen der Attribute;
    • Standardwerte der Attribute;
    • Zugriffsbeschränkungen auf die Attribute;
    • Anzahl der Methoden;
    • Deklaration der Methoden;
    • Zugriffsbeschränkungen auf die Methode.

Deklaration einer Klasse in Header Datei:

    1. Zuerst, die Mehrfacheinbindung verhindert werden.
    1. Danach binden die benötigten Headerdateien ein.
    1. Mit dem Schlüsselwort class leiten Sie die Definition einer Klasse ein. Der Bezeichner der Klasse steht direkt hinter dem Schlüsselwort.
    1. Die Deklaration der Attribute. private und public
    1. Die Deklaration der Methoden. Zuerst Konstruktor und Destruktor.
    1. Die Klassendeklaration mit einem Semikolon (😉 zu schließen.

Die Implementierungsdatei enthält lediglich noch die Implementierung der Methoden.

  • Note: Standardmäßig sollten alle Attribute hier mit einem Standardwert definiert werden, da sonst noch zufällige Werte im Bereich des Speichers vorhanden sein und unvorhergesehenes Verhalten auslösen können.
1.16.3 Namensräume

Warum Namensräume verwendet? In großen Projekt kann es sein, dass zwei Klassen, Methode etc. abstimmen aus unterschiedlichen Bibliotheken denselben Namen haben. Die Benutzung eines solchen mehrfach definierten Bezeichners ruft einen Fehler hervor.

  • Den Inhalt jeder Bibliothek einem eigenen Namensraum zuzuordnen.

(1) Schlüsselwort namespace:
(2) Bereichsoperator ::
Um auf einen Bezeichner eines Namensraumes zuzugreifen.

    <Namensraum>::<Bezeichner innerhalb dieses Namensraumes>

(3) using namespace Direktive: Nicht empfehlenswert, da sonst der Nutzen von Namensräumen ausgeschaltet wird.

1.16.4 Kapselungsprinzip

Kapselung bedeutet, dass die Attribute eines objektes nur von ihm selbst und keinefalls von außen geändert werden dürfen. D.h. alle Attribute müssen als private deklariert sein. (Um die Manipulierbarkeit von Daten zu verhindern)
=== Dadurch den Aufrufe der From . nicht mehr möglich

(1) Zugriffsspezifizierer: die Schlüsselwörter: public, private, protected. Die Zugriffsspezifizierer erlauben es für jede Methode und jedes Attribut anzugeben, wie darauf zugegriffen werden darf.

  • public: Das Attribut oder die Methode kann in jeder anderen Methode, auch außerhalb des Objekts aufgerufen werden. Dieser Aufruf geschieht durch:
    <Objektname>.<Attributbezeichnung>
  • private: Das Attribut/Die Methode kann nur innerhalb der Methoden der eigenen Klassen aufgerufen werden.
  • projected: Alle Attribute und Methoden, die in der Elternklasse als protected deklariert wurden, lassen sich nur innerhalb der Eltern- und Kindklasse aufrufen. Fremde Objekt haben darauf keinen Zugriff.

(2) get()- und set()- Methoden
Mithilfe der get()- Methoden kann man Attributwerte auslesen und den set()- Methoden Attributwerte manipulieren.

  • Der wesentliche Unterschied der get()- Methode zur direkten Ausgabe kann beispielsweise darin liegen, dass die Ausgabe formatiert wird.
  • Der wesentlcihe Unterschied der set()- Methode zur direkten Zuweisung liegt darin, dass die Werte nicht ohne Überprüfung übergenommen werden, sondern von Ihnen innerhalb der Methoden überprüft werden können.
1.16.5 Konstruktor und Destruktor

(1) Konstruktor sind spezielle Methoden einer Klasse, die nur ein einziges Mal beim Erzeugen eines Objektes aufgerufen werden. Sie haben den Zweck Speicher für das neue Objekt zu reservieren und die Attribute gegebenenfalls zu initialisieren.
Regeln für Konstruktoren:

  • Konstruktoren haben denselben Namen wie die Klasse.
  • Konstruktoren haben keinen Rückgabedatentyp, auch nicht void
  • Mann kann keine Pointer auf Konstruktoren definieren.
  1. Standardkonstruktor
  2. Überladener Konstruktor: Manchmal ist es notwendig Objekte mit unterschiedlichen Attributwerten zu initialisieren. – Mehre Konstruktoren für ein und dieselbe Klasse gleichzeitig existieren können. Diese Konstruktor unterscheiden sich lediglich durch die Übergabeparameter.
  3. Initialisierungsliste: Konstante Variablen und Referenzen können bekanntlich nicht definiert werden, sondern müssen gleich initialisiert werden. Initialisierungen sind mit überladenen oder Standard-Konstruktoren alleine nicht möglich.

Um Initialisierung vornehmen zu können / die Attribute gleich bei der Erzeugung des Objekts zu initialisieren, ergänzt man die Konstruktor-Definition um eine Initialisierungsliste.
Notwendig when

  • zum einen notwendig bei der Vererbung
  • falls ein Objekt nicht durch den Standardkonstruktor initialisiert werden soll.
    • Die Initialisierungsliste ist darüber hinaus effizienter als ein normaler Konstruktoraufruf ohne Initialisierungsaufruf. Dies liegt daran, dass anstatt der zwei Schritte, Deklaration und Definition, nur noch ein Schritt, die Initialisierung, nötig ist.
    • Syntax für Initialisierungslist:
      • Die Initialisierungsliste beginnt direkt hinter dem Konstruktor mit einem Doppelpunkt;
      • Darauf folgt der Name des ersten Attributes;
      • Hinter jedem Attribut steht in runden Klammern der Initialisierungswert;
      • Folgen weitere Attributinitialisierungen, so werden diese durch Komma getrennt.
      • Erst nach der Initialisierungsliste steht der Codeblock.
//Standard Konstruktor mit Initialisierungsliste
PhotoAlbum::PhotoAlbum():m_iNumberOfPhotos(0),m_sTitle("My Photo Album")
        
//Überladener Konstruktor mit Initialisierungsliste
PhotoAlbum::PhotoAlbum(int NumberofPhotos, std::string sTitle):
    m_iNumberOfPhotos(iuNumberOfPhotos),m_sTitle(sTitle)
  1. Destruktor zerstört die Objekte wieder. Er gibt den von den Objekten belegten Speicher wieder frei.
    Regeln für Destruktor:
  • Der Name des Destruktors ist ~<Klassenname>
  • Genau wie Konstruktor besítzt er keinen Rückgabeparameter, auch nicht void
  • Jede Klasse besitzt nur einen einzigen Destuktor
  • In der Regel erfolgt der Aufruf des Destruktors implizit durch den Compiler bei Beendigung des Programms.
1.16.6 Zugriff auf Methoden und Attribute

2 Arten des Zugriffs:

  • Zugriff innerhalb der Klassendefintion: eine Klasse innerhalb einer Methodendefinition auf ihre eigenen Attribute und Methode zugreift. ---- Benötige keinen speziellen Operator
  • Zugriff von außen(public): Aufruf eines Attributes oder einer Methode außerhalb der Klassendefinition, d.h. in den Definitionen anderer Klassen oder Funktionen.
    • ist durch Zugriffsspezifizierer geregelt. Auf Methoden kann man von außen zugreifen, falls sie public deklariert sind. Dafür wird der Punkt bzw. Pfeil-Operator benötigt.
    • Pfeiloperator: Hat man einen Pointer auf ein Objekt und will man über diesen auf die Attribute und Methoden eines Objektes zugreifen, benötigt man den Pfeiloperator ->
      BirthdayPhotos->m_iNumberOfPhotos
    • Punktoperator: Spricht man das Objekt direkt oder über eine Referenz an, so benötigt man den Punktoperator.
      <Objektname>.<Attribut/Methode>
1.16.7 Variablen static und const

Statische Inhalte einer Klasse sind in allen Instanzen der Klasse gleich. D.h. hat eine Klasse eine statische Methode oder Variable, so wird diese von allen Instanzen der Klasse “geteilt”
(1) Statische Variablen werden wie auch andere Variablen in der Header-Datei einer Klasse definiert. statischen Variablen müssen vor ihrer Verwendung initialisiert werden.

(2) Statische Methoden können auf statische Mitglieder einer Klasse zugreifen. Nichtstatische Inhalte können von statischen Methoden nicht verwendet werden (da statische Methoden auch ohne eine Instanz der Klasse verwendet werden können).
Eine häufige Verwendung von statischen Variablen und Methode ist die Identifikation von einzelnen Instanzen sowie das Zählen aller vorhandenen INstanzen eriner Klasse.

#include <iostream>
#include “Bike.h”

void main()
{
std::cout << “Anzahl der Fahrraeder: “ << Bike::getCount();
}

(3) const kennzeichnet Funktionen, die Member Variablen nicht verändert. Bei Aufruf kann auf die Variablen nur lesend zugegriffen werden. Dies kann dem Programmierer dienen, ist aber in bestimmten Fällen vom Compiler gefordert, bspw. bei Referenzen. Soll beispielsweise eine get-Funktion mit Hilfe einer konstanten Referenz auf ein Objekt aufgerufen werden, so muss die aufgerufene Funktion ebenfalls als const gekennzeichnet sein.

1.16.8

Überladenen Methode
In C++ ist möglich, ähnlich wie beim Konstrukor, merheren Methoden einer Klasse denselben Namen zu geben. Sie müssen sich nur in den Übergabeparametertypen unterscheiden. Diese Methoden mit gleichen Namen, aber unterschielicher Übergangsparameter, nennt man überladene Methode.

Nicht nur Methoden, sondern auch Operatoren (wie +, << usw.) können in C++
überladen werden. Dies ermöglicht es dem Programmierer, die entsprechenden
Operationen auch für selbst definierte Datentypen zur Verfügung zu stellen.

complex operator+(complex a, complex b)
{
return complex(a.getReTeil() + b.getReTeil(), a.getImTeil() + b.getImTeil());
}

int main()
{
complex a = complex(2.1,2.0);
complex b = complex(4.0,3.3);
complex c = a+b;
return 0;
}

1.17 Vererbung

Die Vererbung ist eine Beziehung zwischen zwei Klassen. Die eine Klasse nennt man Elternklasse, die andere Kindklasse. Die Kindklasse ist von der Elternklasse abgeleitet. Das heißt die Kindklasse enthält (erbt) alle Attribute und Methoden der Elternklasse. Zusätzlich können innerhalb der Kindklasse noch weitere Attribute und Methoden implementiert werden.

1.17.3 Überschreiben

Überschreiben geschieht, indem die Kindklasse eine vererbte Methode nochmals deklariert und neu definiert.

Überschreiben und Überladen
Überschreiben bezeichnet die Redefinition einer vererbten Methode in der Kindklasse. Die Signaturen der Methoden in Eltern und Kindklassen unterscheiden sich nicht, jedoch die Definitionen.
Überladen bezeichnet die Definition mehrerer Methoden mit demselben Namen jedoch unterschiedlichen Parametern. Die Methoden haben also verschiedene Signaturen.

1.17.4 Schnittstelen:

Methoden, die nicht definiert sind, nennt man rein virtuelle Methoden.

Rein abstrakte Klasse sind Klassen, die nur rein virtuelle Methoden beinhalten und somit immer Elternklassen sind. Ihre Kinder müssen alle (rein virtuellen) Methoden überschreiben, damit man von ihnen Objekte bilden kann. Die rein abstrakte Klasse erzwingt also die Implementierung all ihrer Methoden in der Kindklasse. Die Signaturen sind durch rein abstrakte Klassen bereits vorgegeben. Die Kindklassen können natürlich noch zusätzliche Attribute und Methoden besitzen.

Der große Vorteil der Schnittstellen liegt darin, dass ein Programm über eine solche Schnittstelle mit vielen Modulen auf definierte Weise kommunizieren kann. Diese Kommunikation geschieht durch den Aufruf der Methoden, die in der Schnittstelle deklariert sind.

Beispiel einer virtuellen Funktion

virtual bool chckForUpdates() = 0;
Pointer vom Typ der Elternklasse auf eine Kindklasse

Eine Besonderheit der Vererbung ist die Möglichkeit, Poiter vom Typ der Elternklasse auf Kindklassen zun erstellen.

parentClass* pChild = new ChildClass()

Der Pointer der Elternklasse kann auf die vererbten Methoden und Attribute zugreifgen.
Statt jede von AddOnabgelietete Klasse einzelnn anzusprechen, kann man sie nun alle über die Elternklasse Addon ansprechen. Dies führt zu einer Verringerung des Implementierungsaufwands und zu einer einfachen Intergration neuer Klassen, die die Schnittstellen implememtieren:

AddOn* AddOnList[2];
AddOnList[0] = new PasswordManager;
AddOnList[1] = new WeatherToolbar;
        

1.17.5 Abstrakte Klassen:

Die abstrakte Klass enthält rein virtuelle Mthoden. Der Unterschied zur rein abstrakten Klasse liegt darin, dass sie auch implementierte Methoden enthält. Eine abstrakte Klasse enthält mindestens eine rein virtuelle Methode. Dadurch kann auch von ihr kein Objekt erzeugen werden.

1.17.6 Virtuelle Methoden

Virtuelle Methoden benötiget man nur dann, wenn man mit Pointern vom Typ der Elternklasse auf die Kindklasse arbeitete.

#inlcude "Child.h"        

void main()
{
    Parent * pChild = new Child();
    pChild->method();
    pChild->virtual_method();
    
}        
Unterschied zwischen Polymorphie und Vererbung:

In der Vererbungsbeziehung ist es nicht erforderlich, dass die Basisklassenmethode eine virtuelle Funktion sein muss. Beim Polymorphismus muss die Basisklassenmethode eine virtuelle Funktion sein.

Polymorphie in C++ bedeutet, dass ein Funktionsaufruf unterschiedliche Funktionen ausführen kann, je nachdem welche Obkjekttype die Funktion aufruft.

Soll ein Objekt der Kindklasse eine Überschirebene Methode aufrufen, so kann diese wie inder Aufgabe “Verrerbung und Überschreibung” definiert und aufgerufen werden. Problematisch wird es, wenn das Objekt nicht direkt für den Aufruf zur Verfügung steht, sondern nur mittels Funktionsname aufgerufen wird. In diesem Fall wird die Methode der Elternklasse aufgerufen.

1.18 Rekursion

Während bei Iteration die Programmteile nacheinader abgeleitete werden, ruft sich bei einer Rekursion die Funktionummer wieder selbst aus, bis ein bestmmtes Abbruchkriterium erreicht wird. Da die aufrufende Funktion warten muss, bis die aufgerufenen Funktionen ihren Wert zurückleifert, wächst der call stack stetig an. Erst wenn die aufgrufenen Funktionen ihren Wert zurückliefern, werden die Funktionen und ihre Daten vom Stack entfernt.

1.19 Dateien lesen und schreiben

ofstream Klasse/Stream, um Dateien zu schreiben
ifstream Klasse/Stream. um Dataien zu lesen
fstream Klasse/Stream, um Dateien zu schreiben und zu lesen.

Um die Klassen verwenden zu können, muss die Bibiliothek fstream mit dem Befehl #inlcude <fstream> eingebunden werden.
std::cout ist vom Typ ostream
std::cin ist vom Typ istream
Dementsprechend funktioniert auch die Verwendung der Filestreamklassen ofstream, ifstream und fstream analog zu der von std::cout und std::cin. Einziger Unterschied ist, dass Filestreams mit den Dateien verknüpft werden, die sie bearbeiten.

1.19.1 Öffnen einer Datei

open(filename, mode)

filename ist ein C-string mit dem Namen der zu öffnenenden Datei. WEnn ein string aus C++ als Dateiname verwendet werden soll, so muss dieser mit der Methode c_str() in einen C-string umgewandelt werden.

//Umwandlung eines C++ strings in einen C-string
        
std::string filename;
//fileopen(filename.c_str());  //Umwandlung in C-String 
file.open(filename);     

mode ist ein optionaler Parameter, der aus einer Kombination von verschidenen “Flags” besteht. Die Flags werden über den bitweisen OR-Operator “|” kombiniert. Tabelle 6 zeigt die möglichen Flags.

Modusabbreviated
ios::ininputÖffnen einer Datei mit lesendem Zugriff
ios::outoutputÖffnen einer Datei mit schreibendem Zugriff
ios::ateat endSetzt dem Ponter zum Lesen/Schreiben ans Ende der Datei. Kann aber auch an andere Positionen verlegt werden um dort zu lesen oder Schreiben
ios::appappendNeuer Text wird am Ende der bestehenden Datei angehängt
ios::trunctruncateVorhandener Inhalt einer Datei wird gelöscht und neuer Inhalt wird hineingeschrieben

Will man zum Beispiel eine Datei mit Lese- und Schreibzugriff öffnen und den bishreigen Inhalt überschreiben, erzielt man dies durch:

//Datei öffnen mit Lese-und Schreibzugriff.
std::fsteam file;
file.open("beispiel.txt", std::ios::in|std::ios::out|std::ios::trunc)        
        

check if a file is open
if(file.is_open()){}

Schließen eier Datei
Ist die Bearbeitung einer Datei abgeschlossen, so muss der Befehl filestream.close()aufgerufen werden.

1.19.4 Zutände einer Datei
Status
good()Gibt an, ob der Stream fehlerfrei und nicht am Dateiende ist
eof()Gibt an, ob das Ende der Datei errricht wurde
fail()Gibt an, ob das Failbit gesetzt wurde. Das Failbit wird gesetzt, wenn ein logischer oder ein Lese/Schreibfehler aufgetreten ist
clear()Setzt das Failbit auf false zurück
Die Methode getline

in der Bibiliothek <string>git es die Methode

&istream getline(isream& is, string& str)

die es ermögliocht, eine gesamte Zeile aus einem Filestream in eine Stringvariable zu kopieren.
example code

#include <string>
#include <iostream>
        
int main()
{
  std::sting sName;
  std::cout<<"Bitte vollständigen Namen eingeben";
  getline(std::cin,sName);
  std::cout<<"Ihr Name ist:"<<sName<<std::endl;
}
        

1.20.1 Ausnahmen

Die Fehlerbehandlung durch Ausnahmen hat folgendes Prinzip: Wenn eine Programmkomponmente (z.B. eine Methode), auf ein auftretendes Problem nicht angemessen reagieren kann, kann sie Ausnahmesituation erzeugen. Diese Ausnahmensituation kann dann eventuell eine andere Komponente behandeln. Welche Fehler eine bestimmte Komponenten abfangen kann zeigt sie durch die “Catch” Abfrage.

Möchte eine Komponente einen bestimmten Fehler behandeln, so wird der kritische Codeabschnitt, in dem Fehler auftreten können, in einem try{} - block eingeschlossen. Die entsprechenden Fehler können in einem anschließenden catch{} -block abgefangen und behandelt werden.

Tritt in einer Komponente ein Fehler auf, den sie nicht selbst behandeln kann, so bricht sie ihre Ausführung ab und wirft eine Ausnahme (throw object). Diese Ausnahme wird an die aufrufende Komponente weitergeben. Dabei enthält Object Informationen über den aufgetretenen Fehler.

// Fehlerbehandlung durch Ausnahmen
void taskmaster()
{
    try
    {   // faengt auftretende Fehler ab
       int iResult = doTask();
        // mit result weiterarbeiten
    }
    catch(SomeError){
        // Fehler aufgetreten: Hier erfolgt die Fehlerbehandlung
    }
}

int doTask()
{
    if( /* Abfrage */ )
        return iResult;
    else
        throw SomeError{};
}
1.20.2 Der catch-Block

In den meisten Programmen können verschiedene Fehlertypen auftreten. Inn diesme Fall können mehrere catch-Blöcke für verschiedene Fehler an einen try-Block angefügt werden. Es müssen zunächst spezielle Fehler und erst dann allgemeinte Fehler abgefangen werden, da sonst ggf. ein catch-Block nie erreicht wird.

Um alle in einem try-Block auftretenden Fehler abzufangen, gibt es den Block:

catch(...){/*Abweisungen*/}   
        
1.20.3 Ausnahmen und Ressourcen

Vorteil von Ausnahmen. Geöffnete Files können beim Auftreten eines Fehlers nicht wieder geschlossen werden. Oder wie zum beispiel wenn Speicherplatz über new zugeteilt werden. Um solche Fehler zu vermeiden, können Ausnahmen verwendet werden, somit wird das Schließen einer Datei garantiert.

// Sicherer Dateizugriff
#include <fstream>

void doSth()
{
std::ofstream ofile;

// Fange Fehler beim Dateizugriff ab
try
{
ofile.open("beispiel.txt");
// Datei bearbeiten…
ofile.close();
}
catch(…)
  {
    // Schließt die Datei auf jeden Fall
    if(ofile.is_open())
    {
        ofile.close();
    }
  }
}    
        
1.20.3 Ausnahmenklassen aus der Standardbibliothek

Ausnahmenklassen werden von exception abgeleitet und verwendet.

// Benutzung von eigenen Ausnahmeklassen
#include <iostream>
#include <exception>

// Definition einer eigenen Ausnahmeklasse
class myFatalError : public std::exception
{
    public:
    virtual const char* what() const throw()
    {
        return "Fataler Fehler aufgetreten";
    }
};

int main()
{
try
{
    // … hier steht der kritische Codeteil und die Fehlerabfrage
    throw myFatalError{}; // Wird „geworfen“ wenn fehler im kritischen Codeteil auftritt
    // … Folgeanweisungen
catch (myFatalError& e){
    std::cout << e.what() << std::endl;
}

return 0;
}        
        
        
Ausnahme
bad_allocWird von new geworfen, wenn ein Fehler bei der Speicherzuweisung auftritt
bad_castwird von dynamic_cast geworfen, wenn die Typumwandlung fehlschlägt
bad_typeidwird von typeid geworfen, wenn ein Fehler auftritt

1.21 Doppelt verkettete Listen

In der Standardbibliothek von C++ werdn verschiedene Container zum Ausnehmen von Elementen eines oder mehrer Typen bereitgestellt. Der Typ list<typename T> ist eine doppelt verkettete Liste.

1.21.1

“Doppelt verkettetet” bedeutet, dass jedes Element der Liste einen Pointer auf das vorhergehende und das nachfolgende Element enthält. So kann eine doppelt verkettete liste sehr eifach in beide Richtungen durchlaufen werden. Die erhöhte Felixibilität geht auf Kosten von Speicherplatz und Komplexität, die aber für einfache Anwendungen weniger relevant sind.

1.21.2 Itertoren

Mit einem Iterator wird eine Stelle in der Liste bezeichnet. Das Element an dieser Stelle
kann dann zum Beispiel ausgelesen, verändert oder gelöscht werden. Außerdem
können dort neue Elemente eingefügt werden. Ein Iterator wird über das Schlüsselwort
container::iterator definiert. Die folgende Zeile definiert einen Iterator für eine
float-Liste mit dem Namen myiterator:

std::list<float>::iterator myiterator;

Dieb Funktion listenname.begin() liefert einen Iterator, der auf die Position des ersten Elements der Liste zeigt. Die Position hinter dem letzten Element der Liste liefert die Funktion listenname.end().

1.21.3 Konstruktion

Der COntainer list betet mehrere Konstruktion zur Inizitlisierung an.

Konstruktor
std::list<T> mylist;Erstellung einer leeren Liste
std::listy<float> mylist(n,val);Intialisiert mylist n Kopien von val
std::list<float> mylist(list1.begin(),list1.end());Initialisiert mylist mit den Elementen [list.begin(), list1.end()-1]
std::list<float> mylist (list1)Kopierkonstruktor, Kopiert alle Elemente nach mylist

1.21.4 Methoden von list

Der Container list bietet umfangreiche Möglichkeiten um Elemente einzufügfen, zu entfernen, anzuhängen, etc. Tabelle 10 listet die wichtzigsten auf.

Konstruktor
x = mylist.size()x erhält die Länge der Liste
mylist.push_front(value)Erstellt Element mit Wert value am Anfang der Liste
mylist.push_back(value)Erstellt Element mit Wert value am Ende der Liste
mylist.insert(Iterator, value)Fügt Element mit Wert vlaue von der Posiion ein, auf die Iterator zeigt
mylist.pop_front()Löscht das erste Element der Liste
Iterator = mylist.erase(Iterator)Entfernt das Elementaus der Liste, auf das Iterator zeigt. erase(...) gibt die Position des nächsten Elements an Iterator zurück
mylist.erase(Iterator_1,Iterator_2)Löscht Elemente von Iterator_1(einschließlich) gis zu Iterator_2(ausschließlich). Nur Iterator_2bleibt gültig
mylist.clear();löscht die gesamte Liste

for Schleife

std::list<float>::iterator myiterator

for(myiterator = list1.begin(); myiterator != list1.end(); myiterator++)
{
    *(myiterator) += 2.5;
}

 #include <list>

 int main()
{
 // Erstellen von Listen
 std::list<float> flstList(3,2.0); // Liste mit 3 Elementen: 2.0 2.0 2.0
 std::list<float> mylist(++flstList.begin(), flstList.end()); // Kopiert Elemente 2 und 3
 // mylist = 2.0 2.0
 // Einfügen von Elementen
 mylist.push_front(4.0); // mylist = 4.0 2.0 2.0
 mylist.push_back(6.0); // mylist = 4.0 2.0 2.0 6.0
 mylist.insert(++mylist.begin(),3.0); // mylist = 4.0 3.0 2.0 2.0 6.0

 // Löschen von Elementen
 mylist.pop_front(); // mylist = 3.0 2.0 2.0 6.0

 // Löschen mit Iterator
 std::list<float>:: iterator myiter = mylist.begin(); // Iterator zeigt auf Anfang
 myiter += 2; // Iterator zeigt auf Element 3
 myiter =mylist.erase(myiter); // Löscht Element 3

 // Zugriff auf Elemente
 std::list<float>::iterator myiterator;

 for(myiterator = mylist.begin(); myiterator != mylist.end(); myiterator++)
 *(myiterator) += 2.5;

 // Löschen der gesamten Listen
 flstList.clear(); // löscht Liste flstList
 mylist.clear(); // löscht liste mylist

 return 0;
 }
        

Struct

Ein Struct verhält sich in C++ wie eine normale Klasse. Der eingzie Unterschied besteht darin, dass alle Attribute automatisch auf public gesetzt sind. Die Impemetierung erfolgt somit ähnlich zu der einer Klasse. Dabei wird statt dem Schlüsselwort class das Schlüsselwort class das Schlüsselwort struct verwendet.

struct Wertepaar{
    int m_iX1;
    int m_iX2;
    
    Wertepaar(int x, int y){
        m_iX1 = x;
        m_iX2 = y;
        
    }

}
1.22.1 Statische vs. Dynamische Biblitheken

Eine statische Bibiliothek besteht aus Routine, die ein Programm kompiliert und gelinkt werden. Wird ein Programm mit einer statischen Bibliothkn kompiliert, so wird die gesmate Funktionalität der Bibliothek Teil des Programmes.



Ein Vorteil von statischen Bibliotheken (gegenüber dynamischen) ist, dass nur eine ausführbare Datei weitergegeben werden muss, um das Programm auszuführen. Die Bibliothek ist Teil des Programmes und muss nicht extra an den Benutzer des Programmes gegeben werden.

Statische Bibliotheken haben den Nachteil, dass sie den Speicherbedarf eines Programmes erhöhen (da sie Teil des Programmes sind) und dass sie nur schwer aktualisierbar sind (da jedes Mal das Programm neu kompiliert werden muss). Im Rahmen dieses Praktikums wird ausschließlich mit statischen Bibliotheken gearbeitet.



Dynamische Bibliotheken bestehen aus Routinen, die während der Ausführung eines Programmes in das Programm geladen werden. Wird ein Programm mit einer dynamischen Bibliothek kompiliert, so wird die Bibliothek NICHT Teil des Programmes. Sie bleibt weiterhin eine separate Datei.


Ein Vorteil von dynamischen Bibliotheken ist, dass mehrere Programme sie(gleichzeitig) verwenden können. Man braucht nicht mehrere Kopien, wie bei statischen Bibliotheken. Ein noch größerer Vorteil ist, dass dynamische Bibliotheken auf neue Versionen aktualisiert werden können, ohne die Programme verändern zu müssen.


1.22.3 nucures

ncurses ist eine Bibliothek, die das Erstellen von graphischen Benutzerschnittstellen
in der Konsole ermöglicht (d.h. man kann ganze Fenster und Dialoge in der Konsole
erstellen). Für die Steuerung des Roboters/der Simulation, die in den nächsten Tagen
implementiert wird, sind aber nur wenige und periphere Funktionen von ncurses
notwendig.

1.22.3.1 Verwendung der ncureses

Die Verwendung von Ncureses geschieht in drei Schritten

  1. Vor der Verwendung muss ncurses mit den gewünschten Funktionen
    eingeschalten werden. Hierfür gibt es folgende Methoden:
    • initscr(); // Initialisiert das Standardfenster für ncurses
    • nodelay(stdscr, TRUE); // Eingaben werden sofort verarbeitet.
    • noecho(); // Eingaben werden nicht angezeigt

  2. Nach Abschluss der Wendung muss ncurses ausgeschalten werden. Dies
    geschieht durch einen Aufruf von:
    • endwin(); // beendet ncurses und kehrt zur Standardeingabe zurück.

  3. Die eigentliche Verwendung geschieht über folgende drei Funktionen:
    • int getch(); //Liest ein Zeichen aus der Konsole als
    ASCII. Wird kein Zeichen eingegeben so gibt getch() -1 zurück (Kann
    auch direkt als char verwendet werden, siehe Listing 85).
    • clear(); // Löscht alle Ausgaben in der Konsole
    • printw(“abc, %f”, fnum) // Gibt einen Text in der Konsole aus
    Die Funktion printw() funktioniert wie die aus C bekannte Funktion printf(). Der

auszugebende Text wird dabei als C-string übergeben.

#include "ncurses.h"

int main()
{
// Irgendwelcher Code der mit der Standardausgabe (cin, cout) arbeitet.
int iNumber = 0;
double dNumber = 0.0;
char chValue = ‘A’;
std::string sValue = "Ich bin ein String";

 // Umschalten auf ncurses und Initialisierung
 initscr();
 nodelay(stdscr,TRUE);
 noecho();

 while ( /* Bedingung */ )
 {
 iNumber = getch(); // Einlesen eines Zeichens

 if (iNumber != -1) // stellt sicher das ein Zeichen eingegeben wurde.
 {
 // Verarbeitung der Eingabe
 // …irgendwelcher Code

 clear(); // Löschen aller Ausgaben in der Konsole
 // Folgende Zeilen geben eine Textausgabe in der Konsole aus
 printw("Eingabe ist: %i", iNumber); // Ausgabe: integer (Alternativ: %d)
 printw("Eingabe ist: %f", dNumber); // Ausgabe: double
 printw("Eingabe ist: %c", chValue); // Ausgabe: char
 printw("Eingabe ist: %s", sValue); // Ausgabe: string
 }
 }
 endwin(); // Zurückschalten auf Standardausgabe (cout)
 }        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值