Cpp 备考
- 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 …
- 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.
- 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
- 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.
- 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.
- 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[];
- 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.
- 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.
- 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;
}
- 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;
}
- 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;
- Implementierungsdatei enthalten:
(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üpunkt | Erklärung |
---|---|
Build Project | Das Projekt wird in ein ausführbares Programm umgewandelt. Dabei werden alle geänderten .cpp Dateinen neu erstellt |
Clean Project | Lö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:
Type | Speicherplatz | Wertebereich(decimal) |
---|---|---|
bool | 1 Byte | True(1), false(0) |
char | 1 Byte | -128 bis 127 |
unsigned char | 1 Byte | 0 bis 255 |
short | 2 Byte | -32.768 bis +32.767 |
unsigned short | 2Byte | 0 bis 65.535 |
int | 4 Byte | -2.147.483.648 bis +2.147.483.647 |
unsigned int | 4 Byte | 0 bis 4.294.967.29 |
long | 4 Byte | -2.147.483.648 bis +2.147.483.647 |
unsigned long | 4 Byte | 0 bis 4.294.967.29 |
float | 4 Byte | ±3,4* 10^38 Genauigkeit: 6 Stellen |
double | 8 Byte | ±1,7 *10^308 Genauigkeit: 15 Stellen |
long double | 10 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 .
Operation | Funktion |
---|---|
std::cout | Ermöglicht die Ausgabe auf der Konsole. Die auszugebenden Inhalte (Text, Variablen, etc.) übergibt man der Konsole mithilfe des Schiebeoperators << |
std::cin | Ermö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 Type | Punkte |
---|---|
for-Schleife | Erst im Schleifenkopf der Zähler initialisiert werden |
while-Schleife | wird die Wiederholungsbedingung vor jedem Durchlauf geprüft |
do-while-Schleife | wird 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/Operator | Funktion |
---|---|
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; | |
Definition | Zuweisung eines Wertes zu einer bereits deklarieten Variable |
iPieceNumber = 35 | |
Initialisierung | Erstmalige 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:
-
- Zuerst, die Mehrfacheinbindung verhindert werden.
-
- Danach binden die benötigten Headerdateien ein.
-
- Mit dem Schlüsselwort class leiten Sie die Definition einer Klasse ein. Der Bezeichner der Klasse steht direkt hinter dem Schlüsselwort.
-
- Die Deklaration der Attribute. private und public
-
- Die Deklaration der Methoden. Zuerst Konstruktor und Destruktor.
-
- 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.
- Standardkonstruktor
- Ü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.
- 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)
- 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 AddOn
abgelietete 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.
Modus | abbreviated | |
---|---|---|
ios::in | input | Öffnen einer Datei mit lesendem Zugriff |
ios::out | output | Öffnen einer Datei mit schreibendem Zugriff |
ios::ate | at end | Setzt dem Ponter zum Lesen/Schreiben ans Ende der Datei. Kann aber auch an andere Positionen verlegt werden um dort zu lesen oder Schreiben |
ios::app | append | Neuer Text wird am Ende der bestehenden Datei angehängt |
ios::trunc | truncate | Vorhandener 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_alloc | Wird von new geworfen, wenn ein Fehler bei der Speicherzuweisung auftritt |
bad_cast | wird von dynamic_cast geworfen, wenn die Typumwandlung fehlschlägt |
bad_typeid | wird 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_2 bleibt 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
-
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 -
Nach Abschluss der Wendung muss ncurses ausgeschalten werden. Dies
geschieht durch einen Aufruf von:
• endwin(); // beendet ncurses und kehrt zur Standardeingabe zurück. -
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)
}