Interpreter gehen den Quellcode durch, übersetzen ihn und führen den Code des Programmierers respektive die Programmanweisungen direkt aus. Compiler übersetzen den Quellcode in ein ausführbares Programm, das später ausgeführt werden kann.
Die Aufgabe des Linkers besteht darin, Ihren kompilierten Code mit den Bibliotheken Ihres Compiler-Herstellers und anderen Quellen zu verbinden. Der Linker ermöglicht es, Programme in Form einzelner Blöcke zu erstellen und zum Schluß diese Blöcke zu einem großen Programm zu vereinigen.
include
(erste Zeile des Programms) das Zeichen # stellen.
Hello World
auf den Bildschirm aus, gefolgt von einem Zeilenumbruch.
Jedesmal wenn Sie den Compiler aufrufen, wird zuerst der Präprozessor ausgeführt. Der Präprozessor geht Ihren Quellcode durch, kopiert die von Ihnen gewünschten Dateien in den Quelltext und führt noch einige andere, für den Programmierer lästige Arbeiten aus.
main()
-Funktion einen Sonderstatus ein?
C++-Kommentare bestehen aus zwei Schrägstrichen und kommentieren den nachfolgenden Text bis zum Zeilenende aus. C-Kommentare bestehen immer aus Paaren öffnender und schließender Symbole, /* */, und alles, was innerhalb eines solchen Paares steht, ist auskommentiert. Bei ihrer Verwendung muß man darauf achten, daß man zu jedem öffnenden Symbol auch ein schließendes Symbol eingibt, und umgekehrt.
Ja, C++-Kommentare können innerhalb von C-Kommentaren verwendet werden. Prinzipiell können Sie auch C-Kommentare in C++-Kommentaren verwenden, solange Sie nicht vergessen, daß C++-Kommentare mit dem Zeilenende abschließen.
C-Kommentare können problemlos länger als eine Zeile sein. Wenn Sie einen C++-Kommentar in eine zweite Zeile ausdehnen wollen, müssen Sie ein zweites Paar von Schrägstrichen,
//
, setzen.
1: #include <iostream.h>
2:
3: int main()
4: {
5: cout << "Ich liebe C++\n";
6: return 0;
7: }
int main() { return 0; }
1: #include <iostream.h>
2: int main()
3: {
4: cout << Ist hier ein Fehler?";
5: return 0;
6: }
1: #include <iostream.h>
2: int main()
3: {
4: cout << "Ist hier ein Fehler?";
5: return 0;
6: }
Integer-Variablen sind ganze Zahlen, Fließkommazahlen sind »reelle« Zahlen mit einem »fließenden« Dezimalpunkt. Fließkommazahlen können als Kombination von Mantisse und Exponent dargestellt werden.
unsigned short int
und einer Variablen vom Typ long int
?
Das Schlüsselwort
unsigned
bewirkt, daß der Integer nur positive Werte aufnimmt. Auf den meisten Computern sindshort
-Integer 2 Byte groß undlong
-Integer 4 Byte.
Symbolische Konstanten sind weitgehend selbsterklärend; der Name der Konstante besagt, wofür die Konstante steht. Darüber hinaus braucht man zur Änderung einer symbolischen Konstanten nur zur Definition im Quellcode zu gehen und muß nicht den gesamten Quellcode durchgehen, um jede Verwendung der Konstanten zu editieren.
const
verglichen zu #define
?
const
-Variablen sind »typisiert«, so daß der Compiler Fehler bei ihrer Vewendung erkennen kann. Zudem überleben sie den Präprozessor und sind daher auch beim Debuggen zugänglich.
Gute Variablennamen erklären, wofür die Variable gebraucht wird; schlechte Variablennamen enthalten keine Information.
meinAlter
undLeuteImBus
sind gute Variablennamen,xjk
undprndl
sind weniger geeignet.
BLAU
in dem folgenden Aufzählungstyp?
float
und initialisieren Sie sie mit Ihrer PI-Konstanten.
meinAlter
, a
und b
Integer-Variablen sind, wie lauten Ihre Werte nach
meinAlter = 39;
a = meinAlter++;
b = ++meinAlter;
meinAlter: 41, a: 39, b: 41
if(x = 3)
und if(x == 3)
?
if(x = 3)
weist der Variablen den Wert 3 zu und lieferttrue
zurück.if(x == 3)
lieferttrue
, wenn der Wert von x gleich 3 ist, ansonstenfalse
.
true
oder false
?
e. x == 0 // angenommen x hat den Wert 0
if
-Anweisung, die zwei Integer-Variablen überprüft und die größere in die kleinere umwandelt. Verwenden Sie nur eine else
-Klausel.
if (x > y)
x = y;
else // y > x || y == x
y = x;
1: #include <iostream.h>
2: int main()
3: {
4: int a, b, c;
5: cout << "Bitte drei Zahlen eingeben:\n";
6: cout << "a: ";
7: cin >> a;
8: cout << "\nb: ";
9: cin >> b;
10: cout << "\nc: ";
11: cin >> c;
12:
13: if (c = (a-b))
14: {cout << "a: ";
15: cout << a;
16: cout << "minus b: ";
17: cout << b;
18: cout << "gleich c: ";
19: cout << c << endl;}
20: else
21: cout << "a-b ist nicht gleich c: " << endl;
22: return 0;
23: }
1: #include <iostream.h>
2: int main()
3: {
4: int a = 1, b = 1, c;
5: if (c = (a-b))
6: cout << "Der Wert von c ist: " << c;
7: return 0;
8: }
Da in Zeile 5 der Wert von
a-b
anc
zugewiesen wird, ist der Wert der Zuweisunga
(1) minusb
(1), sprich0
. Da0
zufalse
ausgewertet wird, unterbleibt die Ausgabe.
Der Funktionsprototyp deklariert die Funktion, die Definition definiert sie. Der Prototyp endet mit einem Semikolon, die Definition nicht. Die Deklaration kann das Schlüsselwort
inline
spezifizieren und Standardwerte für die Parameter angeben, die Definition kann dies nicht. Die Deklaration muß keine Namen für die Parameter angeben, die Definition muß.
Eine lokale Variable ist eine Variable, die in einem Block deklariert wird (üblicherweise der Rumpf einer Funktion). Sie ist nur innerhalb des Blocks sichtbar.
Gültigkeitsbereiche beziehen sich auf die Sichtbarkeit und Lebensdauer lokaler und globaler Variablen. Gültigkeitsbereiche werden üblicherweise durch geschweifte Klammern eingeführt.
Globale Variablen werden meist dann verwendet, wenn mehrere Funktionen auf die gleichen Daten zugreifen müssen. In C++ werden globale Daten selten eingesetzt; wenn Sie erst einmal wissen, wie man statische Klassenvariablen erzeugt, werden Sie in C++ nur noch selten globale Variablen erzeugen.
Als Funktionenüberladung bezeichnet man die Möglichkeit, mehrere Funktionen mit dem gleichen Namen aufzusetzen, die sich lediglich in der Anzahl oder den Typen der Parameter unterscheiden.
Polymorphie ist ein Konzept, das es erlaubt, mehrere Objekte verschiedener, aber verwandter Typen ohne Rücksicht auf den jeweiligen Typ zu behandeln. In C++ wird die Polymorphie durch Vererbung und virtuelle Funktionen implementiert.
Perimeter()
auf, die einen vorzeichenlosen Integer (unsigned long int
) zurückgibt und zwei Parameter, beide vom Typ unsigned short int
, übernimmt.
unsigned long int Perimeter(unsigned short int, unsigned short int);
Perimeter()
, wie in Übung 1 beschrieben. Die zwei Parameter stellen die Länge und Breite eines Rechtecks dar. Die Funktion soll den Umfang (zweimal die Länge plus zweimal die Breite) zurückliefern.
unsigned long int Perimeter(unsigned short int length,
unsigned short int width)
{
return 2*length + 2*width;
}
#include <iostream.h>
void myFunc(unsigned short int x);
int main()
{
unsigned short int x, y;
y = myFunc(int);
cout << "x: " << x << " y: " << y << "\n";
}
void myFunc(unsigned short int x)
{
return (4*x);
}
Die Funktion ist als
void
deklariert und kann daher keinen Wert zurückliefern. Stattint
müßtex
anmyFunc()
übergeben werden.
#include <iostream.h>
int myFunc(unsigned short int x);
int main()
{
unsigned short int x, y;
y = myFunc(x);
cout << "x: " << x << " y: " << y << "\n";
}
int myFunc(unsigned short int x);
{
return (4*x);
}
unsigned short
übernimmt und das Ergebnis der Division des ersten Arguments durch das zweite Argument zurückliefert. Führen Sie die Division nicht durch, wenn die zweite Zahl Null ist, sondern geben Sie -1 zurück.
short int Divider(unsigned short int valOne, unsigned short int valTwo)
{
if (valTwo == 0)
return -1;
else
return valOne / valTwo;
}
#include <iostream.h>
typedef unsigned short int USHORT;
typedef unsigned long int ULONG;
short int Divider(
unsigned short int valone,
unsigned short int valtwo);
int main()
{
USHORT one, two;
short int answer;
cout << "Enter two numbers.\n Number one: ";
cin >> one;
cout << "Number two: ";
cin >> two;
answer = Divider(one, two);
if (answer > -1)
cout << "Answer: " << answer;
else
cout << "Error, can't divide by zero!";
return 0;
}
#include <iostream.h>
typedef unsigned short USHORT;
typedef unsigned long ULONG;
ULONG GetPower(USHORT n, USHORT power);
int main()
{
USHORT number, power;
ULONG answer;
cout << "Geben Sie eine Zahl ein: ";
cin >> number;
cout << "Zu welcher Potenz? ";
cin >> power;
answer = GetPower(number,power);
cout << number << " hoch " << power << "ist gleich " <<
answer << endl;
return 0;
}
ULONG GetPower(USHORT n, USHORT power)
{
if(power == 1)
return n;
else
return (n * GetPower(n,power-1));
}
Die Deklaration einer Klasse ist ihre Schnittstelle, die den Klienten darüber informiert, wie er mit der Klasse interagieren und arbeiten kann. Die Implementierung der Klasse ist der Satz an vorgesehenen Elementfunktionen, die üblicherweise in einer CPP-Datei aufgesetzt werden.
public
) und privaten (private
) Datenelementen?
Öffentliche Datenelemente können von den Klienten der Klasse direkt angesprochen werden. Auf private Datenelemente kann man nur über (
public
) Elementfunktionen der Klasse zugreifen.
Datenelemente können zwar öffentlich gemacht werden, doch ist es guter Programmierstil, die Daten privat zu halten und öffentliche Zugriffsfunktionen zur Verfügung zu stellen.
Cat
-Objekte deklarieren, können diese unterschiedliche Werte in ihren itsAge
-Datenelementen haben?
Deklarationen enden mit einem Semikolon nach der schließenden geschweiften Klammer. Funktionsdefinitionen enden nicht mit Semikolon.
Cat
-Funktion Meow()
aussehen, die keine Parameter übernimmt und void
zurückliefert?
void Cat::Meow()
Employee
(Angestellter) mit folgenden Datenelementen deklariert: age
, YearsOfService
und Salary
class Employee
{
int Age;
int YearsOfService;
int Salary;
};
Employee
neu, mit privaten Datenelementen und zusätzlichen öffentlichen Zugriffsmethoden, um jedes der Datenelemente zu lesen und zu setzen.
class Employee
{
public:
int GetAge() const;
void SetAge(int age);
int GetYearsOfService()const;
void SetYearsOfService(int years);
int GetSalary()const;
void SetSalary(int salary);
private:
int Age;
int YearsOfService;
int Salary;
};
Employee
-Klasse ein Programm, das zwei Angestellte erzeugt. Setzen Sie deren Alter (age
), Beschäftigungszeitraum (YearsOfService
) und Gehalt (Salary
), und geben Sie diese Werte aus.
int main()
{
Employee John;
Employee Sally;
John.SetAge(30);
John.SetYearsOfService(5);
John.SetSalary(50000);
Sally.SetAge(32);
Sally.SetYearsOfService(8);
Sally.SetSalary(40000);
cout << "John und Sally versehen bei AcmeSexist den gleichen Job\n";
cout << "John ist " << John.GetAge() << " Jahre alt und seit ";
cout << John.GetYearsOfService() << " Jahren in der Firma.\n";
cout << "John verdient $" << John.GetSalary() << " im Jahr.\n\n";
cout << "Sally ist " << Sally.GetAge() << " Jahre alt und seit ";
cout << Sally.GetYearsOfService() << " Jahren in der Firma.\n";
cout << "Sally verdient $" << Sally.GetSalary)= << " im Jahr.\n";
cout << "Manchmal ist das Leben ungerecht\n";
return 0;
}
Employee
auf, die berichtet, wieviel tausend Dollar der Angestellte verdient, aufgerundet auf die nächsten 1000 Dollar.
float Employee:GetRoundedThousands()const
{
return (Salary+500) / 1000;
}
Employee
so, daß Sie age
, YearsOfService
und Salary
initialisieren können, wenn Sie einen neuen Angestellten anlegen.
class Employee
{
public:
Employee(int Age, int yearsOfService, int salary);
int GetAge()const;
void SetAge(int Age);
int GetYearsOfService()const;
void SetYearsOfService(int years);
int GetSalary()const;
void SetSalary(int salary);
private:
int Age;
int YearsOfService;
int Salary;
};
class Square
{
public:
int Side;
}
class Cat
{
int GetAge()const;
private:
int itsAge;
};
Die Zugriffsfunktion
GetAge()
ist privat. Standardmäßig sind alle Elemente privat, es sei denn, man sieht explizit einen anderen Zugriffsspezifizierer vor.
class TV
{
public:
void SetStation(int Station);
int GetStation() const;
private:
int itsStation;
};
int main()
{
TV myTV;
myTV.itsStation = 9;
TV.SetStation(10);
TV myOtherTv(2);
return 0;
}
Auf das Element
itsStation
kann nicht direkt zugegriffen werden, da esprivate
ist.
Die Methode
SetStation()
kann nicht für die Klasse selbst, sondern nur für Objekte der Klasse aufgerufen werden.
Das Element
itsStation
kann nicht initialisiert werden, da es keinen passenden Konstruktor gibt.
for
-Schleife?
for (x = 0, y = 10; x < 100; x++, y++)
goto
vermeiden?
Mit
goto
kann man in jede Richtung zu jeder beliebigen Stelle im Code springen, was den Quellcode schwierig zu verstehen und zu warten macht.
for
-Schleife zu schreiben, deren Rumpf niemals ausgeführt wird?
Ja. Wenn die Bedingung, die auf die Initialisierung folgt, zu
false
ausgewertet wird, wird der Rumpf derfor
-Schleife nicht ausgeführt. Hier ein Beispiel:
for (int x = 100; x < 100; x++)
while
-Schleifen in for
-Schleifen zu verschachteln?
for(;;)
{
// Diese for-Schleife endet niemals!
}
while(1)
{
// Diese while-Schleife endet niemals!
}
x
, wenn die folgende for
-Schleife durchlaufen ist?
for (int x = 0; x < 100; x++)
for
-Schleife, die ein Muster von 10 x 10 Nullen (0) ausgibt.
for (int i = 0; i< 10; i++)
{
for ( int j = 0; j< 10; j++)
cout << "0";
cout << "\n";
}
for
-Anweisung, die in Zweierschritten von 100 bis 200 zählt.
for (int x = 100; x<=200; x+=2)
while
-Schleife, die in Zweierschritten von 100 bis 200 zählt.
int x = 100;
while (x <= 200)
x+= 2;
do...while
-Schleife, die in Zweierschritten von 100 bis 200 zählt.
int x = 100;
do
{
x+=2;
} while (x <= 200);
int counter = 0;
while (counter < 10)
{
cout << "Zaehler: " << counter;
}
for (int counter = 0; counter < 10; counter++);
cout << counter << " ";
Hinter dem Schleifenkopf steht ein Semikolon, das dafür verantwortlich ist, daß die Schleife nichts macht. Der Programmierer könnte dies so beabsichtigt haben, aber wenn er - was wahrscheinlich ist - die einzelnen Werte ausgeben wollte, ist es ein Fehler.
int counter = 100;
while (counter < 10)
{
cout << "Zaehler: " << counter;
counter--;
}
Da
counter
zu 100 initialisiert wird, die Bedingung aber prüft, obcounter
kleiner 10 ist, scheitert der Test und der Rumpf wird niemals beendet. Würde mancounter
in der ersten Zeile mit dem Wert 5 initialisieren, würde die Schleife nicht enden, bis zum kleinstmöglichenint
-Wert heruntergezählt wurde. Daint
standardmäßigsigned
ist, wird dies wohl ebensowenig der Intention des Programmierers entsprechen.
cout << "Geben Sie eine Zahl zwischen 0 und 5 ein: ";
cin >> theNumber;
switch (theNumber)
{
case 0:
doZero();
case 1: // Weiter mit naechstem case
case 2: // Weiter mit naechstem case
case 3: // Weiter mit naechstem case
case 4: // Weiter mit naechstem case
case 5:
doOneToFive();
break;
default:
doDefault();
break;
}
Die Klausel
case 0:
benötigt einebreak
-Anweisung. Ist sie absichtlich ohnebreak
- Anweisung, sollte dies durch einen Kommentar angezeigt werden.
Mit Hilfe des Dereferenzierungsoperators (*) kann man auf den Wert an einer Adresse zugreifen, die in einem Zeiger abgelegt ist.
Die im Zeiger gespeicherte Adresse ist die Adresse einer anderen Variablen. Der Wert, der an dieser Adresse gespeichert ist, ist ein beliebiger Wert, wie er in jeder Variablen gespeichert werden kann. Der Indirektionsoperator (*) liefert den Wert, der an dieser Adresse gespeichert ist.
Der Indirektionsoperator liefert den Wert an einer Adresse, die in einem Zeiger abgelegt ist. Der Adreßoperator (&) liefert die Speicheradresse einer Variablen.
const int * pEins
und int * const pZwei
?
const int * pEins
deklariertpZwei
als Zeiger auf einen konstantenint
-Wert. Derint
-Wert kann über den Zeiger nicht verändert werden.
int * const pZwei
deklariertpZwei
als konstanten Zeiger auf einenint
-Wert. Einmal initialisiert, kann man dem Zeiger keine andere Adresse mehr zuweisen.
c)
int * pDrei = &wZwei;
deklariert einen Zeiger auf einenint
-Wert und initialisiert den Zeiger mit der Adresse einer anderen Variablen.
ihrAlter
vom Typ unsigned short
verweist?
unsigned short *pAlter = &ihrAlter;
ihrAlter
den Wert 50 zu. Verwenden Sie dazu den Zeiger, den Sie in Übung 2 deklariert haben.
*pAlter = 50;
int derInteger;
int *pInteger = &derInteger;
*pInteger = 5;
#include <iostream.h>
int main()
{
int *pInt;
*pInt = 9;
cout << "Der Wert von pInt: " << *pInt;
return 0;
}
pInt
wurde nicht initialisiert. Entscheidend ist dabei, daßpInt
wegen der fehlenden Initialisierung und Zuweisung einer Speicheradresse auf eine zufällige Speicheradresse verweist. An diese Speicheradresse den Wert 9 zu schreiben, ist ein gefährlicher Bug.
int main()
{
int SomeVariable = 5;
cout << "SomeVariable: " << SomeVariable << "\n";
int *pVar = & SomeVariable;
pVar = 9;
cout << "SomeVariable: " << *pVar << "\n";
return 0;
}
Hier wollte der Programmierer wohl der Variablen, auf die
pVar
verweist, den Wert 9 zuweisen. Unglücklicherweise hat er aber den Indirektionsoperator (*) vergessen, und daher den Wert 9 als neuen Wert fürpVar
zugewiesen. WirdpVar
danach dereferenziert, gibt dies eine Katastrophe.
Eine Referenz ist ein Alias, ein Zeiger ist eine Variable, die eine Adresse enthält. Referenzen können nicht Null sein und können nicht reinitialisiert werden.
Wenn Sie im Laufe des Programms auf zwei oder mehrere verschiedene Objekte verweisen wollen, oder wenn Sie den Wert NULL zuweisen wollen.
new
, wenn nicht genug Speicher für Ihr new
-Objekt vorhanden ist?
Übergabe als Referenz bedeutet, daß keine lokale Kopie angelegt wird. Dies kann durch Übergabe einer Referenz oder durch Übergabe eines Zeigers erreicht werden.
int
, eine Referenz auf int
und einen Zeiger auf int
deklariert. Verwenden Sie den Zeiger und die Referenz, um den Wert in int
zu manipulieren.
int main()
{
int varOne;
int& rVar = varOne;
int* pVar = &varOne;
rVar = 5;
*pVar = 7;
return 0;
}
varOne
. Weisen Sie varOne
den Wert 6
zu. Weisen Sie mit Hilfe des Zeigers varOne
den Wert 7
zu. Erzeugen Sie eine zweite Integer-Variable varTwo
. Richten Sie den Zeiger auf die Variable varTwo
. Kompilieren Sie diese Übung noch nicht.
int main()
{
int varOne;
const int * const pVar = &varOne;
*pVar = 7;
int varTwo;
pVar = &varTwo;
return 0;
}
Einem konstanten Objekt kann man keinen Wert zuweisen und einen konstanten Zeiger kann man nicht auf ein anderes Objekt richten.
int main()
{
int * pVar;
*pVar = 9;
return 0;
}
int main()
{
int VarOne;
int * pVar = &varOne;
*pVar = 9;
return 0;
}
#include <iostream.h>
int * FuncOne();
int main()
{
int * pInt = FuncOne();
cout << "Wert von pInt nach Rueckkehr in main: " << *pInt << endl;
return 0;
}
int * FuncOne()
{
int * pInt = new int (5);
cout << "Wert von pInt in FuncOne: " << *pInt << endl;
return pInt;
}
#include <iostream.h>
int FuncOne();
int main()
{
int theInt = FuncOne();
cout << "Wert von pInt nach Rueckkehr in main: " << theInt << endl;
return 0;
}
int FuncOne()
{
int * pInt = new int (5);
cout << "Wert von pInt in FuncOne: " << *pInt << endl;
delete pInt;
return temp;
}
1: #include <iostream.h>
2:
3: class CAT
4: {
5: public:
6: CAT(int age) { itsAge = age; }
7: ~CAT(){}
8: int GetAge() const { return itsAge;}
9: private:
10: int itsAge;
11: };
12:
13: CAT & MakeCat(int age);
14: int main()
15: {
16: int age = 7;
17: CAT Boots = MakeCat(age);
18: cout << "Boots ist " << Boots.GetAge() << " Jahre alt\n";
return 0;
19: }
20:
21: CAT & MakeCat(int age)
22: {
23: CAT * pCat = new CAT(age);
24: return *pCat;
25: }
MakeCat
liefert eine Referenz auf ein Objekt zurück, das auf dem Heap allokiert wurde. Da keine Möglichkeit vorgesehen wurde, diesen Speicher wieder freizugeben, entsteht eine Speicherlücke.
1: #include <iostream.h>
2:
3: class CAT
4: {
5: public:
6: CAT(int age) { itsAge = age; }
7: ~CAT(){}
8: int GetAge() const { return itsAge;}
9: private:
10: int itsAge;
11: };
12:
13: CAT * MakeCat(int age);
14: int main()
15: {
16: int age = 7;
17: CAT * Boots = MakeCat(age);
18: cout << "Boots ist " << Boots->GetAge() << " Jahre alt\n";
19: delete Boots;
20: return 0;
21: }
22:
23: CAT * MakeCat(int age)
24: {
25: return new CAT(age);
26: }
Überladene Elementfunktionen sind Funktionen einer Klasse, die den gleichen Namen haben, aber sich in den Typen oder der Anzahl der Parameter unterscheiden.
Eine Definition geht mit der Reservierung von Speicher einher, die Deklaration nicht. Nahezu alle Deklarationen sind Definitionen; die wichtigsten Ausnahmen sind Klassendeklarationen, Funktionsprototypen und
typedef
-Anweisungen.
Wann immer eine temporäre Kopie eines Objekts erzeugt wird. Dies geschieht jedesmal, wenn ein Objekt als Wert übergeben wird.
Der Destruktor wird jedesmal aufgerufen, wenn ein Objekt aufgelöst wird, sei es, daß der Gültigkeitsbereich des Objekts verlassen wird, sei es, daß der
delete
-Operator auf einen Zeiger auf das Objekt angewendet wird.
=
)?
Der Zuweisungsoperator arbeitet mit existierenden Objekten, der Kopierkonstruktor erzeugt ein neues Objekt.
this
-Zeiger?
Der
this
-Zeiger ist ein verborgener Parameter, der intern allen Elementfunktionen übergeben wird und auf das aktuelle Objekt verweist.
Der Präfix-Operator übernimmt keine Parameter. Der Postfix-Operator übernimmt einen einzigen
int
-Parameter, der nur als Kennzeichnung für den Compiler dient.
+
-Operator für Operanden vom Typ short
überladen?
+
-Operator zu überladen, so daß er einen Wert Ihrer Klasse dekrementiert?
Es ist erlaubt, aber keine gute Idee. Operatoren sollten in einer Weise überladen werden, daß jeder, der den Code liest, intuitiv versteht, wie der Operator einzusetzen ist.
SimpleCircle
mit (nur) einer Elementvariablen: itsRadius
. Sehen Sie einen Standardkonstruktor, einen Destruktor und Zugriffsmethoden für radius
vor.
class SimpleCircle
{
public:
SimpleCircle();
~SimpleCircle();
void SetRadius(int);
int GetRadius();
private:
int itsRadius;
};
itsRadius
mit dem Wert 5
.
SimpleCircle::SimpleCircle():
itsRadius(5)
{}
itsRadius
zu.
SimpleCircle::SimpleCircle(int radius):
itsRadius(radius)
{}
SimpleCircle
-Klasse einen Präfix- und einen Postfix-Inkrementoperator, die itsRadius
inkrementieren.
const SimpleCircle& SimpleCircle::operator++()
{
++(itsRadius);
return *this;
}
// Postfix-Operator ++(int).
const SimpleCircle SimpleCircle::operator++ (int)
{
// lokales SimpleCircle-Objekt deklarieren und mit *this initialisieren
SimpleCircle temp(*this);
++(itsRadius);
return temp;
}
SimpleCircle
so, daß itsRadius
auf dem Heap gespeichert wird, und passen Sie die bestehenden Methoden an.
class SimpleCircle
{
public:
SimpleCircle();
SimpleCircle(int);
~SimpleCircle();
void SetRadius(int);
int GetRadius();
const SimpleCircle& operator++();
const SimpleCircle operator++(int);
private:
int *itsRadius;
};
SimpleCircle::SimpleCircle()
{itsRadius = new int(5);}
SimpleCircle::SimpleCircle(int radius)
{itsRadius = new int(radius);}
SimpleCircle::~SimpleCircle()
{
delete itsRadius;
}
const SimpleCircle& SimpleCircle::operator++()
{
++(*itsRadius);
return *this;
}
// Postfix-Operator ++(int).
const SimpleCircle SimpleCircle::operator++ (int)
{
// lokales SimpleCircle-Objekt deklarieren und mit *this initialisieren
SimpleCircle temp(*this);
++(*itsRadius);
return temp;
}
SimpleCircle
hinzu.
SimpleCircle::SimpleCircle(const SimpleCircle & rhs)
{
int val = rhs.GetRadius();
itsRadius = new int(val);
}
SimpleCircle
hinzu.
SimpleCircle& SimpleCircle::operator=(const SimpleCircle & rhs)
{
if (this == &rhs)
return *this;
delete itsRadius;
itsRadius = new int;
*itsRadius = rhs.GetRadius();
return *this;
}
SimpleCircle
-Objekte erzeugt. Verwenden Sie den Standardkonstruktor zur Erzeugung des einen Objekts, und instantiieren Sie das andere mit dem Wert 9. Wenden Sie den Inkrement-Operator auf beide Objekte an und geben Sie dann die Werte beider Objekte aus. Abschließend weisen Sie dem ersten Objekt das zweite zu und geben Sie nochmals die Werte beider Objekte aus.
#include <iostream.h>
class SimpleCircle
{
public:
// Konstruktoren
SimpleCircle();
SimpleCircle(int);
SimpleCircle(const SimpleCircle &);
~SimpleCircle() {}
// Zugriffsfunktionen
void SetRadius(int);
int GetRadius()const;
// Operatoren
const SimpleCircle& operator++();
const SimpleCircle operator++(int);
SimpleCircle& operator=(const SimpleCircle &);
private:
int *itsRadius;
};
SimpleCircle::SimpleCircle()
{itsRadius = new int(5);}
SimpleCircle::SimpleCircle(int radius)
{itsRadius = new int(radius);}
SimpleCircle::SimpleCircle(const SimpleCircle & rhs)
{
int val = rhs.GetRadius();
itsRadius = new int(val);
}
SimpleCircle::~SimpleCircle()
{
delete itsRadius;
}
SimpleCircle& SimpleCircle::operator=(const SimpleCircle & rhs)
{
if (this == &rhs)
return *this;
*itsRadius = rhs.GetRadius();
return *this;
}
const SimpleCircle& SimpleCircle::operator++()
{
++(*itsRadius);
return *this;
}
// Postfix-Operator ++(int).
const SimpleCircle SimpleCircle::operator++ (int)
{
// lokales SimpleCircle-Objekt deklarieren und mit *this initialisieren
SimpleCircle temp(*this);
++(*itsRadius);
return temp;
}
int SimpleCircle::GetRadius() const
{
return *itsRadius;
}
int main()
{
SimpleCircle CircleOne, CircleTwo(9);
CircleOne++;
++CircleTwo;
cout << "CircleOne: " << CircleOne.GetRadius() << endl;
cout << "CircleTwo: " << CircleTwo.GetRadius() << endl;
CircleOne = CircleTwo;
cout << "CircleOne: " << CircleOne.GetRadius() << endl;
cout << "CircleTwo: " << CircleTwo.GetRadius() << endl;
return 0;
}
SQUARE SQUARE ::operator=(const SQUARE & rhs)
{
itsSide = new int;
*itsSide = rhs.GetSide();
return *this;
}
Sie müssen sicherstellen, daß
rhs
nicht gleichthis
ist, oder eine Zuweisunga = a
wird Ihr Programm zum Absturz bringen.
VeryShort VeryShort::operator+ (const VeryShort& rhs)
{
itsVal += rhs.GetItsVal();
return *this;
}
Der Operator ändert den Wert eines der Operanden, statt ein neues
VeryShort
- Objekt zu erzeugen und mit der Summe zu initialisieren. Korrekt implementiert sähe der Operator wie folgt aus:
{
return VeryShort(itsVal + rhs.GetItsVal());
}
Die meisten Compiler verwenden V- oder virtuellen Tabellen, um virtuelle Funktionen zu verwalten. In der Tabelle werden die Adressen sämtlicher virtueller Funktionen abgelegt, so daß für Zeiger auf Objekte zur Laufzeit ermittelt werden kann, welche Version einer Funktion für das Objekt korrekterweise aufzurufen ist.
Für jede Klasse kann der Destruktor als virtuell deklariert werden. Wird ein Zeiger auf ein Objekt der Klasse gelöscht, wird der Laufzeittyp des Objekts bestimmt und der passende, abgeleitete Destruktor aufgerufen.
Basis::Funktionsname();
Funktionsname();
protected
verwendet?
void
zurückliefert.
virtual void EineFunktion(int);
Square
an, die sich von Rectangle
ableitet, die wiederum eine Ableitung von Shape
ist.
class Square : public Rectangle
{};
Shape
keine Parameter, Rectangle
übernimmt zwei (length
und width
), aber Square
übernimmt nur einen (length
). Wie sieht die Konstruktorinitialisierung für Square
aus?
Square::Square(int length):
Rectangle(length, length){}
Square
(aus Übung 3).
class Square
{
public:
// ...
virtual Square * clone() const { return new Square(*this); }
// ...
};
void EineFunktion (Shape);
Shape * pRect = new Rectangle;
EineFunktion(*pRect);
Unter Umständen gar nichts.
EineFunktion()
erwartet einShape
-Objekt. Es wurde aber einRectangle
übergeben, daß zu einemShape
reduziert wurde. Solange keine speziellen Elemente vonRectangle
benötigt werden, ist alles in Ordnung. Andernfalls muß man die FunktionEineFunktion()
so umschreiben, daß sie einen Zeiger oder eine Referenz aufShape
übernimmt.
class Shape()
{
public:
Shape();
virtual ~Shape();
virtual Shape(const Shape&);
};
EinArray[25]
?
EinArray[0], EinArray[24]
Indem man für jede Dimension einen eigenen Index vorsieht. So wäre beispielsweise
EinArray[2][3][2]
ein dreidimensionales Array. Die erste Dimension enthält 2 Elemente, die zweite 3, die dritte wieder 2.
EinArray[2][3][2] = { { {1,2},{3,4},{5,6} }, { {7,8},{9,10},{11,12} } };
EinArray[10][5][20]
?
Es gibt keine feste maximale Anzahl. Die Anzahl der möglichen Elemente hängt von der zur Verfügung stehenden Speicherkapazität ab.
Um die Index-Notation für eine verkettete Liste verwenden zu können, müssen Sie Ihre eigene Klasse für die Liste aufsetzen und den Index-Operator überladen.
int Spielbrett[3][3];
0
initialisiert.
int Spielbrett[3][3] = { {0,0,0},{0,0,0},{0,0,0} }
Node
-Klasse, die Integer-Werte aufnimmt.
class Node
{
public:
Node ();
Node (int);
~Node();
void SetNext(Node * node) { itsNext = node; }
Node * GetNext() const { return itsNext; }
int GetVal() const { return itsVal; }
void Insert(Node *);
void Display();
private:
int itsVal;
Node * itsNext;
};
unsigned short EinArray[5][4];
for (int i = 0; i<4; i++)
for (int j = 0; j<5; j++)
EinArray[i][j] = i+j;
unsigned short EinArray[5][4];
for (int i = 0; i<=5; i++)
for (int j = 0; j<=4; j++)
EinArray[i][j] = 0;
Sie wollten
i < 5
schreiben, haben aberi <= 5
eingetippt. Der Code wird auch füri == 5
undj == 4
ausgeführt, aber es gibt kein ElementEinArray[5][4]
.
Abwärts gerichtete Typenumwandlungen betreffen Zeiger, die als Zeiger auf Basisklassen deklariert sind und als Zeiger auf abgeleitete Objekte behandelt werden sollen.
vptr ist der Virtuelle-Funktionen-Zeiger, der zur internen Implementierung virtueller Funktionen gehört. Jedes Objekt einer Klasse mit virtuellen Funktionen, verfügt über einen vptr, der auf die Tabelle der virtuellen Funktionen der Klasse verweist.
RoundRect
für Rechtecke mit abgerundeten Ecken, die sowohl von Rectangle
als auch von Circle
abgeleitet ist. Rectangle
und Circle
sind ihrerseits von Shape
abgeleitet. Wie viele Shape
s werden dann bei der Instanziierung eines RoundRects
erzeugt?
Wenn keine der Klassen bei der Vererbung das Schlüsselwort
virtual
verwendet, werden zweiShape
-Objekte erzeugt: eines fürRectangle
und eines fürCircle
. Wird das Schlüsselwortvirtual
für beide Klassen verwendet, wird nur ein gemeinsamesShape
-Objekte erzeugt.
Horse
und Bird
von der Klasse Animal
als publc virtual
Basisklasse abgeleitet sind, rufen dann ihre Konstruktoren den Animal
-Konstruktor auf? Wenn Pegasus
sowohl von Horse
als auch von Bird
abgeleitet ist, wie kann Pegasus
den Animal
-Konstruktor aufrufen?
Sowohl
Horse
als auchBird
initialisieren ihre BasisklasseAnimal
in ihren Konstruktoren. EbensoPegasus
, wobei bei der Erzeugung einesPegasus
-Objekts die Initialisierungen der KlassenHorse
undBird
ignoriert werden.
Vehicle
und machen Sie die Klasse zu einem abstrakten Datentyp.
class Vehicle
{
virtual void Move() = 0;
}
Grundsätzlich müssen Sie keine der Funktionen überschreiben. Nur wenn die abgeleitete Klasse nicht auch abstrakt sein soll, müssen Sie die Funktionen überschreiben - dann allerdings alle drei geerbten abstrakten Funktionen.
JetPlane
auf, die von Rocket
und Airplane
abgeleitet ist.
class JetPlane : public Rocket, public Airplane
747
auf, die von der Klasse JetPlane
aus Übung 1 abgeleitet ist.
class 747 : public JetPlane
Car
und Bus
von der Klasse Vehicle
ableitet. Deklarieren Sie Vehicle
als ADT mit zwei abstrakten Funktionen. Car
und Bus
sollen keine abstrakten Datentypen sein.
class Vehicle
{
virtual void Move() = 0;
virtual void Haul() = 0;
};
class Car : public Vehicle
{
virtual void Move();
virtual void Haul();
};
class Bus : public Vehicle
{
virtual void Move();
virtual void Haul();
};
Car
ein abstrakter Datentyp ist, von dem die Klassen SportsCar
und Coupe
abgeleitet werden. Die Klasse Car
soll für eine der abstrakten Funktionen von Vehicle
einen Anweisungsteil vorsehen, so daß die Funktion nicht mehr abstrakt ist.
class Vehicle
{
virtual void Move() = 0;
virtual void Haul() = 0;
};
class Car : public Vehicle
{
virtual void Move();
};
class Bus : public Vehicle
{
virtual void Move();
virtual void Haul();
};
class SportsCar : public Car
{
virtual void Haul();
};
class Coupe : public Car
{
virtual void Haul();
};
Ja. Hinsichtlich der Vergabe von Zugriffsrechten sind statische Elementvariablen ganz normale Elementvariablen. Werden Sie als
private
deklariert, kann man nur über Elementfunktionen oder statische Elementfunktionen (die man ohne Objekt der Klasse aufrufen kann) auf sie zugreifen.
static int staticElement;
static int EineFunktion();
long
zurückgibt und zwei int
-Parameter übernimmt.
long (* funktion)(int);
Car
ist.
long ( Car::*funktion)(int);
long ( Car::*funktion)(int) dasArray[10];
1: class myClass
2: {
3: public:
4: myClass();
5: ~myClass();
6: private:
7: int itsMember;
8: static int itsStatic;
9: };
10:
11: myClass::myClass():
12: itsMember(1)
13: {
14: itsStatic++;
15: }
16:
17: myClass::~myClass()
18: {
19: itsStatic--;
20: }
21:
22: int myClass::itsStatic = 0;
23:
24: int main()
25: {}
1: #include <iostream.h>
2:
3: class myClass
4: {
5: public:
6: myClass();
7: ~myClass();
8: void ShowMember();
9: void ShowStatic();
10: private:
11: int itsMember;
12: static int itsStatic;
13: };
14:
15: myClass::myClass():
16: itsMember(1)
17: {
18: itsStatic++;
19: }
20:
21: myClass::~myClass()
22: {
23: itsStatic--;
24: cout << "In Destruktor. ItsStatic: " << itsStatic << endl;
25: }
26:
27: void myClass::ShowMember()
28: {
29: cout << "itsMember: " << itsMember << endl;
30: }
31:
32: void myClass::ShowStatic()
33: {
34: cout << "itsStatic: " << itsStatic << endl;
35: }
36: int myClass::itsStatic = 0;
37:
38: int main()
39: {
40: myClass obj1;
41: obj1.ShowMember();
42: obj1.ShowStatic();
43:
44: myClass obj2;
45: obj2.ShowMember();
46: obj2.ShowStatic();
47:
48: myClass obj3;
49: obj3.ShowMember();
50: obj3.ShowStatic();
51: return 0;
52: }
1: #include <iostream.h>
2:
3: class myClass
4: {
5: public:
6: myClass();
7: ~myClass();
8: void ShowMember();
9: static int GetStatic();
10: private:
11: int itsMember;
12: static int itsStatic;
13: };
14:
15: myClass::myClass():
16: itsMember(1)
17: {
18: itsStatic++;
19: }
20:
21: myClass::~myClass()
22: {
23: itsStatic--;
24: cout << "In Destruktor. ItsStatic: " << itsStatic << endl;
25: }
26:
27: void myClass::ShowMember()
28: {
29: cout << "itsMember: " << itsMember << endl;
30: }
31:
32: int myClass::itsStatic = 0;
33:
34: void myClass::GetStatic()
35: {
36: return itsStatic;
37: }
38:
39: int main()
40: {
41: myClass obj1;
42: obj1.ShowMember();
43: cout << "itsStatic: " << myClass::GetStatic() << endl;
44:
45: myClass obj2;
46: obj2.ShowMember();
47: cout << "itsStatic: " << myClass::GetStatic() << endl;
48:
49: myClass obj3;
50: obj3.ShowMember();
51: cout << "itsStatic: " << myClass::GetStatic() << endl;
52: return 0;
53: }
1: #include <iostream.h>
2:
3: class myClass
4: {
5: public:
6: myClass();
7: ~myClass();
8: void ShowMember();
9: static int GetStatic();
10: private:
11: int itsMember;
12: static int itsStatic;
13: };
14:
15: myClass::myClass():
16: itsMember(1)
17: {
18: itsStatic++;
19: }
20:
21: myClass::~myClass()
22: {
23: itsStatic--;
24: cout << "In Destruktor. ItsStatic: " << itsStatic << endl;
25: }
26:
27: void myClass::ShowMember()
28: {
29: cout << "itsMember: " << itsMember << endl;
30: }
31:
32: int myClass::itsStatic = 0;
33:
34: int myClass::GetStatic()
35: {
36: return itsStatic;
37: }
38:
39: int main()
40: {
41: void (myClass::*PMF) ();
42:
43: PMF=myClass::ShowMember;
44:
45: myClass obj1;
46: (obj1.*PMF)();
47: cout << "itsStatic: " << myClass::GetStatic() << endl;
48:
49: myClass obj2;
50: (obj2.*PMF)();
51: cout << "itsStatic: " << myClass::GetStatic() << endl;
52:
53: myClass obj3;
54: (obj3.*PMF)();
55: cout << "itsStatic: " << myClass::GetStatic() << endl;
56: return 0;
57: }
1: #include <iostream.h>
2:
3: class myClass
4: {
5: public:
6: myClass();
7: ~myClass();
8: void ShowMember();
9: void ShowSecond();
10: void ShowThird();
11: static int GetStatic();
12: private:
13: int itsMember;
14: int itsSecond;
15: int itsThird;
16: static int itsStatic;
17: };
18:
19: myClass::myClass():
20: itsMember(1),
21: itsSecond(2),
22: itsThird(3)
23: {
24: itsStatic++;
25: }
26:
27: myClass::~myClass()
28: {
29: itsStatic--;
30: cout << "In Destruktor. ItsStatic: " << itsStatic << endl;
31: }
32:
33: void myClass::ShowMember()
34: {
35: cout << "itsMember: " << itsMember << endl;
36: }
37:
38: void myClass::ShowSecond()
39: {
40: cout << "itsSecond: " << itsSecond << endl;
41: }
42:
43: void myClass::ShowThird()
44: {
45: cout << "itsThird: " << itsThird << endl;
46: }
47: int myClass::itsStatic = 0;
48:
49: int myClass::GetStatic()
50: {
51: return itsStatic;
52: }
53:
54: int main()
55: {
56: void (myClass::*PMF) ();
57:
58: myClass obj1;
59: PMF=myClass::ShowMember;
60: (obj1.*PMF)();
61: PMF=myClass::ShowSecond;
62: (obj1.*PMF)();
63: PMF=myClass::ShowThird;
64: (obj1.*PMF)();
65: cout << "itsStatic: " << myClass::GetStatic() << endl;
66:
67: myClass obj2;
68: PMF=myClass::ShowMember;
69: (obj2.*PMF)();
70: PMF=myClass::ShowSecond;
71: (obj2.*PMF)();
72: PMF=myClass::ShowThird;
73: (obj2.*PMF)();
74: cout << "itsStatic: " << myClass::GetStatic() << endl;
75:
76: myClass obj3;
77: PMF=myClass::ShowMember;
78: (obj3.*PMF)();
79: PMF=myClass::ShowSecond;
80: (obj3.*PMF)();
81: PMF=myClass::ShowThird;
82: (obj3.*PMF)();
83: cout << "itsStatic: " << myClass::GetStatic() << endl;
84: return 0;
85: }
Durch Einbettung, das heißt, man richtet in einer Klasse ein Datenelement vom Typ einer anderen Klasse ein.
Einbettung bedeutet, daß eine Klasse ein Datenelement vom Typ einer anderen Klasse enthält. Delegierung bedeutet, daß eine Klasse eine andere Klasse zur Erledigung einer Aufgabe beziehungsweise zum Erreichen eines bestimmten Ziels verwendet. Delgierung wird häufig durch Einbettung implementiert.
Delegierung bedeutet, daß eine Klasse eine andere Klasse zur Erledigung einer Aufgabe beziehungsweise zum Erreichen eines bestimmten Ziels verwendet. »Implementiert mit Hilfe von« bedeutet, daß ein Code von einer anderen Klasse geerbt wird.
friend
-Funktion?
Eine Friend-Funktion ist eine Funktion, der Zugriff auf die geschützten und privaten Elemente einer Klasse eingeräumt wurde.
Eine Friend-Klasse ist eine Klasse, deren Elementfunktionen Friend-Funktionen einer anderen Klasse sind.
Dog
ein Freund (friend
) von Boy
ist, ist Boy
dann auch ein Freund von Dog
?
Dog
ein Freund von Boy
ist und Terrier
sich von Dog
ableitet, ist Terrier
dann auch ein Freund von Boy
?
Dog
ein Freund von Boy
ist und Boy
ein Freund von House
ist, ist Dog
dann auch ein Freund von House
?
friend
-Funktion deklarieren?
Irgendwo. Es macht keinen Unterschied, ob Sie die Deklaration in einen
public
-,protected
- oderprivate
-Abschnitt stellen.
Animal
auf, die ein String-Objekt als Datenelement enthält.
class Animal:
{
private:
String itsName;
};
BoundedArray
, die ein Array
darstellt.
class BoundedArray : public Array
{
//...
}
Menge
auf der Grundlage der Klasse Array
.
class Menge : private Array
{
// ...
}
>>
) für die String
-Klasse.
1: #include <iostream.h>
2: #include <string.h>
3:
4: class String
5: {
6: public:
7: // Konstruktoren
8: String();
9: String(const char *const);
10: String(const String &);
11: ~String();
12:
13: // Überladene Operatoren
14: char & operator[](int offset);
15: char operator[](int offset) const;
16: String operator+(const String&);
17: void operator+=(const String&);
18: String & operator= (const String &);
19: friend ostream& operator<<
20: ( ostream& _theStream,String& theString);
21: friend istream& operator>>
22: ( istream& _theStream,String& theString);
23: // Allgemeine Zugriffsfunktionen
24: int GetLen()const { return itsLen; }
25: const char * GetString() const { return itsString; }
26: // static int ConstructorCount;
27:
28: private:
29: String (int); // privater Konstruktor
30: char * itsString;
31: unsigned short itsLen;
32:
33 };
34:
35: ostream& operator<<( ostream& theStream,String& theString)
36: {
37: theStream << theString.GetString();
38: return theStream;
39: }
40:
41: istream& operator>>( istream& theStream,String& theString)
42: {
43: theStream >> theString.GetString();
44: return theStream;
45: }
46:
47: int main()
48: {
49: String theString("Hello world.");
50: cout << theString;
51: return 0;
52: }
1: #include <iostream.h>
2:
3: class Animal;
4:
5: void setValue(Animal& , int);
6:
7:
8: class Animal
9: {
10: public:
11: int GetWeight()const { return itsWeight; }
12: int GetAge() const { return itsAge; }
13: private:
14: int itsWeight;
15: int itsAge;
16: };
17:
18: void setValue(Animal& theAnimal, int theWeight)
19: {
20: friend class Animal;
21: theAnimal.itsWeight = theWeight;
22: }
23:
24: int main()
25: {
26: Animal peppy;
27: setValue(peppy,5);
28: }
Die Friend-Deklaration gehört nicht in die Funktion, sondern in die Klasse, zu der die Funktion ein Friend sein soll.
1: #include <iostream.h>
2:
3: class Animal;
4:
5: void setValue(Animal& , int);
6:
7:
8: class Animal
9: {
10: public:
11: friend void setValue(Animal&, int);
12: int GetWeight()const { return itsWeight; }
13: int GetAge() const { return itsAge; }
14: private:
15: int itsWeight;
16: int itsAge;
17: };
18:
19: void setValue(Animal& theAnimal, int theWeight)
20: {
21: theAnimal.itsWeight = theWeight;
22: }
23:
24: int main()
25: {
26: Animal peppy;
27: setValue(peppy,5);
28: return 0;
29: }
1: #include <iostream.h>
2:
3: class Animal;
4:
5: void setValue(Animal& , int);
6: void setValue(Animal& ,int,int);
7:
8: class Animal
9: {
10: friend void setValue(Animal& ,int);
11: private:
12: int itsWeight;
13: int itsAge;
14: };
15:
16: void setValue(Animal& theAnimal, int theWeight)
17: {
18: theAnimal.itsWeight = theWeight;
19: }
20:
21:
22: void setValue(Animal& theAnimal, int theWeight, int theAge)
23: {
24: theAnimal.itsWeight = theWeight;
25: theAnimal.itsAge = theAge;
26: }
27:
28: int main()
29: {
30: Animal peppy;
31: setValue(peppy,5);
32: setValue(peppy,7,9);
33: }
Die Funktion
setValue(Animal&, int)
wurde als Freund deklariert, nicht aber die überladene FunktionsetValue(Animal&, int, int)
.
1: #include <iostream.h>
2:
3: class Animal;
4:
5: void setValue(Animal& , int);
6: void setValue(Animal& ,int,int);
7:
8: class Animal
9: {
10: friend void setValue(Animal& ,int);
11: friend void setValue(Animal& ,int,int);
12: private:
13: int itsWeight;
14: int itsAge;
15: };
16:
17: void setValue(Animal& theAnimal, int theWeight)
18: {
19: theAnimal.itsWeight = theWeight;
20: }
21:
22:
23: void setValue(Animal& theAnimal, int theWeight, int theAge)
24: {
25: theAnimal.itsWeight = theWeight;
26: theAnimal.itsAge = theAge;
27: }
28:
29: int main()
30: {
31: Animal peppy;
32: setValue(peppy,5);
33: setValue(peppy,7,9);
34: return 0;
35: }
Der Ausgabe-Operator (<<) ist ein Element eines
ostream
-Objekts und wird zum Schreiben in das Ausgabegerät verwendet.
Der Eingabe-Operator (>>) ist ein Element eines
istream
-Objekts und wird zum Schreiben in die Variablen Ihrer Programme verwendet.
cin.get(),
und wo liegen die Unterschiede?
Die erste Form von
get()
ist ohne Parameter. Diese Version liefert einen Wert des vorgefundenen Zeichens zurück (EOF, wenn das Dateiende erreicht wurde).
Die zweite Form von
cin.get()
übernimmt eine Zeichenreferenz als Parameter. In diesem Zeichen wird das nächste Zeichen im Eingabestream abgelegt. Der Rückgabewert ist einiostream
-Objekt.
Die dritte Form von
get()
übernimmt drei Parameter. Der erste Parameter ist ein Zeiger auf einen Zeichen-Array, der zweite Parameter die maximale Anzahl der einzulesenden Zeichen plus eins und der dritte Parameter das Terminierungszeichen.
cin.read()
und cin.getline()
?
cin.read()
wird zum Einlesen binärer Datenstrukturen verwendet.
cin.getline()
wird zum Einlesen aus demistream
-Puffer verwendet.
long
mit Hilfe des Ausgabe-Operators?
ios::ate
?
ios::ate
springt nach dem Öffnen an das Ende der Datei; es ist aber auch möglich an jede beliebige Position in der Datei zu schreiben.
cin
, cout
, cerr
und clog
verwendet.
1: #include <iostream.h>
2: int main()
3: {
4: int x;
5: cout << "Geben Sie eine Zahl ein: ";
6: cin >> x;
7: cout << "You entered: " << x << endl;
8: cerr << "Uh oh, this to cerr!" << endl;
9: clog << "Uh oh, this to clog!" << endl;
10: return 0;
11: }
1: #include <iostream.h>
2: int main()
3: {
4: char name[80];
5: cout << "Geben Sie Ihren vollstaendigen Namen ein: ";
6: cin.getline(name,80);
7: cout << "\nSie heissen: " << name << endl;
8: return 0;
9: }
putback()
und ignore()
auskommt.
1: // Listing
2: #include <iostream.h>
3:
4: int main()
5: {
6: char ch;
7: cout << "Geben Sie einen Satz ein: ";
8: while ( cin.get(ch) )
9: {
10: switch (ch)
11: {
12: case '!':
13: cout << '$';
14: break;
15: case '#':
16: break;
17: default:
18: cout << ch;
19: break;
20: }
21: }
22: return 0;
23: }
1: #include <fstream.h>
2: enum BOOL { FALSE, TRUE };
3:
4: int main(int argc, char**argv) // liefert 1 bei Fehler
5: {
6:
7: if (argc != 2)
8: {
9: cout << "Aufruf: argv[0] <eingabedatei>\n";
10: return(1);
11: }
12:
13: // Eingabestream oeffnen
14: ifstream fin (argv[1],ios::binary);
15: if (!fin)
16: {
17: cout << argv[1] <<
" kann nicht zum Lesen geoeffnet werden.\n";
18: return(1);
19: }
20:
21: char ch;
22: while ( fin.get(ch))
23: if ((ch > 32 && ch < 127) || ch == '\n' || ch == '\t')
24: cout << ch;
25: fin.close();
26: }
1: #include <fstream.h>
2:
3: int main(int argc, char**argv) // liefert 1 bei Fehler
4: {
5: for (int ctr = argcÐ1; ctr ; ctr--)
6: cout << argv[ctr] << " ";
7: return 0;
8: }
using
-Anweisung verwenden?
Für unbenannte Namensbereiche fügt der Compiler intern eine implizite
using
-Direktive ein, die es dem Programmierer ermöglicht, auf die Namen in dem Namensbereich ohne Namensbereichqualifizierer zuzugreifen. Für normale Namensbereiche gibt es keine impliziteusing
-Direktive. Um Namen aus normalen Namensbereichen verwenden zu können, müssen Sie entweder eineusing
-Direktive oder eineusing
-Deklaration aufsetzen oder die Namen über einen Namensbereichqualifizierer ansprechen.
Namen aus normalen Namensbereichen können nicht nur in der Übersetzungeinheit, in der der Namensbereich deklariert ist, verwendet werden, sondern auch in anderen Übersetzungeinheiten. Namen aus einem unbenannten Namensbereich können nur in der Übersetzungeinheit verwendet werden, in der der Namensbereich deklariert ist.
Der Standardnamensbereich
std
ist von der C++-Standardbibliothek definiert. In ihm stehen die Deklarationen aller Elemente der Standardbibliothek.
#include <iostream>
int main()
{
cout << "Hello world!" << end;
return 0;
}
1. using namespace std;
2. using std::cout;
using std::endl;
3. std::cout << "Hello world!" << std::endl;
Die prozedurale Programmierung beruht auf der Trennung zwischen Daten und Funktionen. Die objektorientierte Programmierung vereint Daten und Funktionalität in Objekten und konzentriert sich auf die Interaktionen zwischen den Objekten.
Zu einem typischen Entwicklungszyklus gehören: Analyse, Design, Implementierung, Test und Programmierung sowie Rückmeldungen und Interaktionen zwischen diesen Phasen.
Sie bieten unterschiedliche Darstellungen ein und derselben Information und können ineinander überführt werden.
Welche Objekte sollten in der Simulation modelliert werden? Welche Klassen benötigt man für die Simulation?
Die Kreuzung wird von Autos, Lastwagen, Fahrrädern, Fußgängern und Notfallwagen (Polizei, Krankenwagen, Feuerwehr) überquert. Des weiteren gibt es eine Fußgängerampel.
Sollte man die Beschaffenheit der Straßendecke in die Simulation einbeziehen? Auf jeden Fall, schließlich kann sich die Qualität der Straßendecke auf den Verkehr auswirken. Für einen ersten Entwurf ist es aber einfacher die Straßendecke noch unberücksichtigt zu lassen.
Das erste Objekt ist natürlich die Kreuzung selbst. Vielleicht stattet man das Kreuzung-Objekt mit Listen der Autos und Fußgänger aus, die vor den Ampeln warten, bis sie die Kreuzung überqueren können. Methoden werden benötigt, die festlegen, wieviel Autos und Leute die Kreuzung passieren dürfen.
Da es nur eine Kreuzung gibt, müssen Sie sich Gedanken darüber machen, wie Sie verhindern, daß nur ein Objekt erzeugt wird (Tip: denken Sie an statischen Zugriff und geschützten Zugriff).
Zu den Nutzern der Kreuzung gehören Autos und Passanten. Beide haben eine Reihe gemeinsamer Eigenschaften: Sie können zu beliebigen Zeiten auftauchen, sie können in beliebiger Zahl auftauchen und sie müssen die Rotphasen abwarten (wenn auch vor unterschiedlichen Ampeln). Es liegt daher nahe, eine gemeinsame Basisklasse für Passanten und Autos zu erstellen.
class Nutzer; // ein Nutzer der Kreuzung
// Basisklasse fuer alle Fahrzeuge.
class Fahrzeug : Nutzer ...;
// Basisklasse fuer alle Fußgaenger
class Fussgaenger : Nutzer...;
class Auto : public Fahrzeug...;
class Lastwagen : public Fahrzeug...;
class Motorrad : public Fahrzeug...;
class Fahrrad : public Fahrzeug...;
class Notfallwagen : public Fahrzeug...;
// enthaelt Liste der wartenden Autos und Passanten
class Kreuzung;
Ortsansässige, die auch über Ampeln fahren, wenn diese schon auf Rot geschaltet haben, Touristen, die langsam und vorsichtig fahren (meist in angemieteten Wagen), und Taxen, deren Fahrverhalten sich im wesentlichen danach richtet, welche Art von Kunde im Taxi sitzt.
Daneben gibt es zwei Arten von Fußgängern: Ortsansässige, die nach Lust und Laune die Straße überqueren und selten Fußgängerübergänge benutzen, und Touristen, die immer die Fußgängerampeln benutzen.
Schließlich gibt es noch die Fahrradfahrer, die auf keine Ampeln achten.
Wie kann man diese Gegebenheiten in dem Modell berücksichtigen?
Ein vernünftiger Ansatz wäre die Einrichtung abgeleiteter Objekte, die die spezifischen Verhaltensweisen modellieren:
class Ortskundiges_Auto : public Auto...;
class Touristen_Auto : public Auto...;
class Taxi : public Auto...;
class Ortskundiger_Fussgaenger : public Fussgaenger...;
class Touristen_Fussgaenger : public Fussgaenger...;
class Bostoner_Fahrrad : public Fahrrad...;
Mit Hilfe virtueller Methoden können die einzelnen Klassen das allgemeine Verhalten anpassen. So würde beispielsweise der Bostoner Autofahrer auf eine rote Ampel anders reagieren als ein Tourist.
Für dieses Projekt müssen zwei Programme aufgesetzt werden: ein Client-Programm, das der Anwender ausführt, und ein Server-Programm, das auf einer separaten Maschine läuft. Zusätzlich muß die Client-Maschine über eine Verwaltungskomponente verfügen, die es dem Systemverwalter ermöglicht, neue Mitarbeiter und Räume aufzunehmen.
In diesem Client/Server-Modell akzeptiert der Client Eingaben seitens des Anwenders und schickt eine entsprechende Anforderung an den Server. Der Server verarbeitet die Anforderung und sendet das Ergebnis zurück an den Client. Nach diesem Model können mehrere Anwender gleichzeitig Treffen planen.
Auf der Client-Seite gibt es neben der Verwaltungskomponenten zwei wichtige Untersysteme: die Benutzerschnittstelle und das Kommunikationssystem. Die Server-Seite besteht aus drei Untersystemen: Kommunikation, Belegungsplan und E- Mail-Interface, das die Anwender informiert, wenn es Änderungen im Belegungsplan gibt.
Ein Treffen ist definiert als eine Gruppe von Mitarbeitern, die einen Raum für eine bestimmte Zeit reservieren. Die Person, die das Treffen plant, ist vielleicht an einem speziellen Raum oder einer bestimmten Zeit interessiert. Für den Planer ist wichtig, wie lange das Treffen dauert und welche Personen teilnehmen.
Zu den benötigten Objekten dürften die Anwender des Systems und die Konferenzräume gehören. Nicht zu vergessen die Klassen für den Kalender und vielleicht eine
Meeting
-Klasse, in der die Informationen über die einzelnen Ereignisse gekapselt sind.
class Calendar_Class; // Vorwaertsreferenz
class Meeting; // Vorwaertsreferenz
class Configuration
{
public:
Configuration();
~Configuration();
Meeting Schedule( ListOfPerson&, Delta Time duration );
Meeting Schedule( ListOfPerson&, Delta Time duration, Time );
Meeting Schedule( ListOfPerson&, Delta Time duration, Room );
ListOfPerson& People(); // public Zugriffsmethode
ListOfRoom& Rooms(); // public Zugriffsmethode
protected:
ListOfRoom rooms;
ListOfPerson people;
};
typedef long Room_ID;
class Room
{
public:
Room( String name, Room_ID id, int capacity,
String directions = "", String description = "" );
~Room();
Calendar_Class Calendar();
protected:
Calendar_Class calendar;
int capacity;
Room_ID id;
String name;
String directions; // Wo ist dieser Raum?
String description;
};
typedef long Person_ID;
class Person
{
public:
Person( String name, Person_ID id );
~Person();
Calendar_Class Calendar(); // Schnittstelle zum Hinzufuegen von
// Treffen
protected:
Calendar_Class calendar;
Person_ID id;
String name;
};
class Calendar_Class
{
public:
Calendar_Class();
~Calendar_Class();
void Add( const Meeting& ); // Treffen in Kalender aufnehmen
void Delete( const Meeting& );
Meeting* Lookup( Time ); // Ist fuer einen bestimmten
// Zeitraum ein Treffen angesetzt
Block( Time, Duration, String reason = "" ); // Zeit reservieren..
protected:
OrderedListOfMeeting meetings;
};
class Meeting
{
public:
Meeting( ListOfPerson&, Room room,
Time when, Duration duration, String purpose= "" );
~Meeting();
protected:
ListOfPerson people;
Room room;
Time when;
Duration duration;
String purpose;
};
Templates sind typensicher und Teil der Sprache C++. Makros werden vom Präprozessor verarbeitet und sind typenunsicher.
Der Parameter zu einem Template erzeugt für die einzelnen Datentypen Instanzen des Templates. Wenn Sie sechs Template-Instanzen erzeugen, werden sechs eigenständige Klassen oder Funktionen erzeugt. Die Parameter zu einer Funktion modifizieren das Verhalten oder die Daten der Funktion, aber es wird nur eine Funktion erzeugt.
Bei Verwendung einer allgemeinen Template-Funktion als Friend wird für jeden Typ der parametrisierten Klasse eine eigene Funktion erzeugt. Die typspezifische Funktion erzeugt für alle Instanzen der parametrisierten Klasse eine typspezifische Instanz.
Ja, erzeugen Sie eine spezialisierte Funktion für die betreffende Instanz. Beispielsweise können Sie zusätzlich zu
Array<T>::EineFunktion()
noch die FunktionArray<int>::EineFunktion()
aufsetzen, um ein eigenes Verhalten für Integer-Arrays
vorzusehen.
Iteratoren sind Verallgemeinerungen von Zeigern. Iteratoren können inkrementiert werden, um auf den nächsten Knoten in einer Folge von Elementen zu verweisen. Iteratoren können zudem dereferenziert werden und so den Knoten, auf den sie verweisen, zurückliefern.
Ein Funktionsobjekt ist die Instanz einer Klasse, in der der Operator
()
überladen ist. Funktionsobjekte können auf diese Weise wie normale Funktionen aufgerufen werden.
List
-Klasse basiert:
class List
{
private:
public:
List():head(0),tail(0),theCount(0) {}
virtual ~List();
void insert( int value );
void append( int value );
int is_present( int value ) const;
int is_empty() const { return head == 0; }
int count() const { return theCount; }
private:
class ListCell
{
public:
ListCell(int value, ListCell *cell = 0):val(value),next(cell){}
int val;
ListCell *next;
};
ListCell *head;
ListCell *tail;
int theCount;
};
Und so könnte die Implementierung des Templates aussehen:
template <class Type>
class List
{
public:
List():head(0),tail(0),theCount(0) { }
virtual ~List();
void insert( Type value );
void append( Type value );
int is_present( Type value ) const;
int is_empty() const { return head == 0; }
int count() const { return theCount; }
private:
class ListCell
{
public:
ListCell(Type value, ListCell *cell = 0):val(value),next(cell){}
Type val;
ListCell *next;
};
ListCell *head;
ListCell *tail;
int theCount;
};
List
auf.
void List::insert(int value)
{
ListCell *pt = new ListCell( value, head );
assert (pt != 0);
// leere Liste
if ( head == 0 ) tail = pt;
head = pt;
theCount++;
}
void List::append( int value )
{
ListCell *pt = new ListCell( value );
if ( head == 0 )
head = pt;
else
tail->next = pt;
tail = pt;
theCount++;
}
int List::is_present( int value ) const
{
if ( head == 0 ) return 0;
if ( head->val == value || tail->val == value )
return 1;
ListCell *pt = head->next;
for (; pt != tail; pt = pt->next)
if ( pt->val == value )
return 1;
return 0;
}
template <class Type>
List<Type>::~List()
{
ListCell *pt = head;
while ( pt )
{
ListCell *tmp = pt;
pt = pt->next;
delete tmp;
}
head = tail = 0;
}
template <class Type>
void List<Type>::insert(Type value)
{
ListCell *pt = new ListCell( value, head );
assert (pt != 0);
// leere Liste
if ( head == 0 ) tail = pt;
head = pt;
theCount++;
}
template <class Type>
void List<Type>::append( Type value )
{
ListCell *pt = new ListCell( value );
if ( head == 0 )
head = pt;
else
tail->next = pt;
tail = pt;
theCount++;
}
template <class Type>
int List<Type>::is_present( Type value ) const
{
if ( head == 0 ) return 0;
if ( head->val == value || tail->val == value )
return 1;
ListCell *pt = head->next;
for (; pt != tail; pt = pt->next)
if ( pt->val == value )
return 1;
return 0;
}
List
-Objekte: eine Liste von Strings, eine Liste von Cats
und eine Liste von Integern.
List<String> string_list;
List<Cat> Cat_List;
List<int> int_List;
List
-Template definiert ist und mit Cat
die Klasse aus den vorangehenden Kapiteln des Buches gemeint ist.)
List<Cat> Cat_List;
Cat Felix;
CatList.append( Felix );
cout << "Felix ist " <<
( Cat_List.is_present( Felix ) ) ? "" : "nicht " << "da\n";
Tip (denn dies ist eine schwierige Aufgabe): Was unterscheidet
Cat
vonint
?
Für
Cat
ist kein==
-Operator definiert. Alle Operationen, die Werte inList
-Zellen vergleichen (wie zum Beispielis_present()
), erzeugen einen Compiler-Fehler. Um dem vorzubeugen, sollten Sie der Template-Definition einen ausführlichen Kommentar voranstellen, der Auskunft darüber gibt, welche Operationen für die Datentypen definiert sein müssen, damit die Instanzen des Templates kompiliert werden.
friend
-Operator ==
für List
.
friend int operator==( const Type& lhs, const Type& rhs );
friend
-Operator ==
für List
.
template <class Type>
int List<Type>::operator==( const Type& lhs, const Type& rhs )
{
// zuerst die Laengen vergleichen
if ( lhs.theCount != rhs.theCount )
return 0; // unterschiedliche Laengen
ListCell *lh = lhs.head;
ListCell *rh = rhs.head;
for(; lh != 0; lh = lh.next, rh = rh.next )
if ( lh.value != rh.value )
return 0;
return 1; // Laengen gleich
}
==
die gleichen Probleme wie in Übung 5?
Ja, denn zum Vergleichen der Listen müssen die Elemente in den Listen verglichen werden. Dazu muß der
!=
-Operator für die Elemente definiert sein.
swap()
, die zwei Variablen austauscht.
// Template swap:
// fuer Type muessen Zuweisung und Kopierkonstruktor definiert sein
template <class Type>
void swap( Type& lhs, Type& rhs)
{
Type temp( lhs );
lhs = rhs;
rhs = temp;
}
SchoolClass
aus Listing 19.8 als list
-Container. Verwenden Sie die push_back()
-Funktion, um vier Studenten in den list
-Container aufzunehmen. Gehen Sie dann den Container durch, und setzen Sie das Alter der Schüler um jeweils ein Jahr herauf.
#include <list>
template<class T, class A>
void ShowList(const list<T, A>& aList); // list-Eigenschaften ausgeben
typedef list<Student> SchoolClass;
int main()
{
Student Harry("Harry", 18);
Student Sally("Sally", 15);
Student Bill("Bill", 17);
Student Peter("Peter", 16);
SchoolClass GrowingClass;
GrowingClass.push_back(Harry);
GrowingClass.push_back(Sally);
GrowingClass.push_back(Bill);
GrowingClass.push_back(Peter);
ShowList(GrowingClass);
cout << "Ein Jahr spaeter;\n";
for (SchoolClass::iterator i = GrowingClass.begin();
i != GrowingClass.end(); ++i)
i->SetAge(i->GetAge() + 1);
ShowList(GrowingClass);
return 0;
}
//
// list-Eigenschaften ausgeben
//
template<class T, class A>
void ShowList(const list<T, A>& aList)
{
for (list<T, A>::const_iterator ci = aList.begin();
ci != aList.end(); ++ci)
cout << *ci << "\n";
cout << endl;
}
#include <algorithm>
template<class T>
class Print
{
public:
void operator()(const T& t)
{
cout << t << "\n";
}
};
template<class T, class A>
void ShowList(const list<T, A>& aList)
{
Print<Student> PrintStudent;
for_each(aList.begin(), aList.end(), PrintStudent);
cout << endl;
}
Eine Exception ist ein Objekt, das bei Aufruf des Schlüsselwortes
throw
erzeugt wird. Es signalisiert das Eintreten eines außergewöhnlichen Umstands und wird an die Funktionen im Aufrufstack hochgereicht, bis sich einecatch
-Anweisung findet, die das Objekt abfängt und darauf reagiert.
try
-Block?
catch
-Anweisung?
Eine
catch
-Anweisung spezifiziert in ihrer Signatur den Typ von Exceptions, die sie abfängt und behandelt. Diecatch
-Anweisung folgt auf einentry
-Block und empfängt die Exceptions, die in diesemtry
-Block ausgelöst wurden.
Eine Exception ist ein Objekt und kann als solches jede beliebige Information enthalten, die man in einer benuzterdefinierten Klasse festhalten kann.
Generell sollten Exceptions als Referenzen übergeben werden. Wenn der Inhalt der Exception-Objekte nicht geändert werden soll, übergeben Sie die Exception- Objekte als
const
-Referenzen.
catch
-Anweisung eine abgeleitete Exception ab, wenn sie nach der Basisklasse sucht?
catch
-Anweisungen einzurichten, wenn die eine Objekte der Basisklasse und die andere Objekte der abgeleiteten Klasse abfängt?
Die
catch
-Anweisungen werden in der Reihenfolge durchgegangen, in der sie im Code aufeinanderfolgen. Die erstecatch
-Anweisungen deren Signatur zu der Exception paßt, wird ausgeführt.
catch(...)
?
try
-Block, eine catch
-Anweisung und eine einfache Exception.
#include <iostream.h>
class OutOfMemory {};
int main()
{
try
{
int *myInt = new int;
if (myInt == 0)
throw OutOfMemory();
}
catch (OutOfMemory)
{
cout << "Speicher konnte nicht reserviert werden!\n";
}
return 0;
}
catch
-Block.
#include <iostream.h>
#include <stdio.h>
#include <string.h>
class OutOfMemory
{
public:
OutOfMemory(char *);
char* GetString() { return itsString; }
private:
char* itsString;
};
OutOfMemory::OutOfMemory(char * theType)
{
itsString = new char[80];
char warning[] = "Nicht genuegend Speicher fuer: ";
strncpy(itsString,warning,60);
strncat(itsString,theType,19);
}
int main()
{
try
{
int *myInt = new int;
if (myInt == 0)
throw OutOfMemory("int");
}
catch (OutOfMemory& theException)
{
cout << theException.GetString();
}
return 0;
}
catch
-Block, um die abgeleiteten Objekte und die Basisobjekte zu benutzen.
1: #include <iostream.h>
2:
3: // Abstrakter Datentyp fuer Exceptions
4: class Exception
5: {
6: public:
7: Exception(){}
8: virtual ~Exception(){}
9: virtual void PrintError() = 0;
10: };
11:
12: // Abgleitete Klasse fuer Speicherprobleme.
13: // Achtung: Keine Speicherallokation in dieser Klasse!
14: class OutOfMemory : public Exception
15: {
16: public:
17: OutOfMemory(){}
18: ~OutOfMemory(){}
19: virtual void PrintError();
20: private:
21: };
22:
23: void OutOfMemory::PrintError()
24: {
25: cout << "Nicht genuegend Speicher!!\n";
26: }
27:
28: // Abgeleitete Klasse fuer ungueltige Zahlenwerte
29: class RangeError : public Exception
30: {
31: public:
32: RangeError(unsigned long number){badNumber = number;}
33: ~RangeError(){}
34: virtual void PrintError();
35: virtual unsigned long GetNumber() { return badNumber; }
36: virtual void SetNumber(unsigned long number) {
badNumber = number;}
37: private:
38: unsigned long badNumber;
39: };
40:
41: void RangeError::PrintError()
42: {
43: cout << "Wert " << GetNumber() <<
" ausserhalb des gueltigen Bereichs!!\n";
44: }
45:
46: void MyFunction(); // Prototyp
47:
48: int main()
49: {
50: try
51: {
52: MyFunction();
53: }
54: // Nur ein catch-Block erforderlich, nutzt virtuelle
55: // Funktionen.
56: catch (Exception& theException)
57: {
58: theException.PrintError();
59: }
60: return 0;
61: }
62:
63: void MyFunction()
64: {
65: unsigned int *myInt = new unsigned int;
66: long testNumber;
67: if (myInt == 0)
68: throw OutOfMemory();
69: cout << "Geben Sie einen int-Wert ein: ";
70: cin >> testNumber;
71: // dieser etwas seltsame Test sollte durch
72: // mehrere Tests ersetzt werden
73: if (testNumber > 3768 || testNumber < 0)
74: throw RangeError(testNumber);
75:
76: *myInt = testNumber;
77: cout << "OK. myInt: " << *myInt;
78: delete myInt;
79: }
1: #include <iostream.h>
2:
3: // Abstrakter Datentyp fuer Exceptions
4: class Exception
5: {
6: public:
7: Exception(){}
8: virtual ~Exception(){}
9: virtual void PrintError() = 0;
10: };
11:
12: // Abgleitete Klasse fuer Speicherprobleme.
13: // Achtung: Keine Speicherallokation in dieser Klasse!
14: class OutOfMemory : public Exception
15: {
16: public:
17: OutOfMemory(){}
18: ~OutOfMemory(){}
19: virtual void PrintError();
20: private:
21: };
22:
23: void OutOfMemory::PrintError()
24: {
25: cout << "Nicht genuegend Speicher!!\n";
26: }
27:
28: // Abgeleitete Klasse fuer ungueltige Zahlenwerte
29: class RangeError : public Exception
30: {
31: public:
32: RangeError(unsigned long number){badNumber = number;}
33: ~RangeError(){}
34: virtual void PrintError();
35: virtual unsigned long GetNumber() { return badNumber; }
36: virtual void SetNumber(unsigned long number) {
badNumber = number;}
37: private:
38: unsigned long badNumber;
39: };
40:
41: void RangeError::PrintError()
42: {
43: cout << "Wert " << GetNumber() <<
" ausserhalb des gueltigen Bereichs!!\n";
44: }
45:
46: // Prototypen
47: void MyFunction();
48: unsigned int * FunctionTwo();
49: void FunctionThree(unsigned int *);
50:
51: int main()
52: {
53: try
54: {
55: MyFunction();
56: }
57: // Nur ein catch-Block erforderlich, nutzt virtuelle
58: // Funktionen.
59: catch (Exception& theException)
60: {
61: theException.PrintError();
62: }
63: return 0;
64: }
65:
66: unsigned int * FunctionTwo()
67: {
68: unsigned int *myInt = new unsigned int;
69: if (myInt == 0)
70: throw OutOfMemory();
71: return myInt;
72: }
73:
74: void MyFunction()
75: {
76: unsigned int *myInt = FunctionTwo();
77:
78: FunctionThree(myInt);
79: cout << "OK. myInt: " << *myInt;
80: delete myInt;
81: }
82:
83: void FunctionThree(unsigned int *ptr)
84: {
85: long testNumber;
86: cout << "Geben Sie einen int-Wert ein: ";
87: cin >> testNumber;
88: // dieser etwas seltsame Test sollte durch
89: // mehrere Tests ersetzt werden
90: if (testNumber > 3768 || testNumber < 0)
91: throw RangeError(testNumber);
92: *ptr = testNumber;
93: }
class xOutOfMemory
{
public:
xOutOfMemory(){ theMsg = new char[20];
strcpy(theMsg,"Kein Speicher mehr");}
~xOutOfMemory(){ delete [] theMsg; cout
<< "Speicher wiederhergestellt." << endl; }
char * Message() { return theMsg; }
private:
char * theMsg;
};
main()
{
try
{
char * var = new char;
if ( var == 0 )
{
xOutOfMemory * px =
new xOutOfMemory;
throw px;
}
}
catch( xOutOfMemory * theException )
{
cout << theException->Message() <<endl;
delete theException;
}
return 0;
}
Hier wird im Zuge der Behandlung einer »Kein Speicher«-Exception im Konstruktor ein String-Objekt erzeugt. Da diese Exception nur dann ausgelöst wird, wenn dem Programm kein Speicher mehr zur Verfügung steht, muß diese Speicherallokation scheitern.
Es ist möglich, daß durch den Versuch, das String-Objekt zu erzeugen, nochmals die gleiche Exception ausgelöst wird, was einen Teufelskreis in Gang setzt, der erst mit dem Absturz des Programms endet. Wenn Sie nicht ohne den String auskommen können, reservieren Sie den benötigten Speicher zu Beginn des Programms in Form eines statischen Puffers, und verwenden Sie diesen Speicher, wenn die Exception ausgelöst wird.
Einen Weg zu verhindern, daß der Inhalt einer Header-Datei mehrfach in ein Programm eingebunden wird.
#define debug 0
und #undef debug
?
#define debug 0
setztdebug
mit0
(Null) gleich. Alle Vorkommen vondebug
werden durch das Zeichen0
ersetzt.#undef debug
löscht die Definition vondebug
. Der Präprozessor läßt alle Vorkommen vondebug
in der Datei unverändert stehen.
OR
und XOR
?
OR
lieferttrue
, wenn eines oder beide Bits gesetzt sind,XOR
liefert nur danntrue
, wenn ein Bit, und nicht beide Bits, gesetzt ist.
&
und &&
?
|
und ||
?
STRING.H
zu realisieren.
#ifndef STRING_H
#define STRING_H
...
#endif
assert
-Makro, das eine Fehlermeldung zusammen mit dem Dateinamen und der Zeilennummer ausgibt, wenn für die Fehlersuche die Ebene 2 definiert ist, und das ausschließlich eine Fehlermeldung (ohne Dateinamen und Zeilennummer) ausgibt, wenn für die Fehlersuche die Ebene 1 festgelegt ist, und das bei Ebene 0 überhaupt nichts macht.
1: #include <iostream.h>
2:
3: #ifndef DEBUG
4: #define ASSERT(x)
5: #elif DEBUG == 1
6: #define ASSERT(x) \
7: if (! (x))\
8: { \
9: cout << "Fehler!! Assert " << #x << " gescheitert\n"; \
10: }
11: #elif DEBUG == 2
12: #define ASSERT(x) \
13: if (! (x) ) \
14: { \
15: cout << "Fehler!! Assert " << #x << " gescheitert\n"; \
16: cout << " in Zeile " << __LINE __ << "\n; \
17: cout << " in Datei " << __LINE __ << "\n; \
18: }
19: #endif
DPrint
, das auf die Definition von DEBUG
testet. Wenn DEBUG
definiert ist, soll das Makro den als Parameter übergebenen Wert anzeigen.
#ifndef DEBUG
#define DPRINT(string)
#else
#define DPRINT(STRING) cout << #STRING ;
#endif
+
) zu verwenden. Hinweis: Arbeiten Sie mit Bit-Operatoren.
Wenn man sich die Addition zweier Bits anschaut, erkennt man, daß die Antwort aus zwei Bits besteht: dem Ergebnisbit und dem Übertragsbit (Carry-Bit). Wenn man also 1 und 1 binär addiert, ergibt dies 0 und einen Übertrag von 1. Addiert man 101 und 001 sieht das beispielsweise wie folgt aus:
101 // 5
001 //1
110 //6
Wenn man zwei »gesetzte« Bits binär addiert, ist das Ergebnis 0 und der Übertrag ist 1. Wenn man zwei »nicht-gesetzte« Bits binär addiert, ist sowohl das Ergebnis als auch der Übertrag gleich 0. Wenn man ein gsetztes und ein nicht-gesetztes Bit binär addiert, ist das Ergebnis 1 und der Übertrag ist 0:
lhs rhs | Carry Ergebnis
------------+------------------
0 0 | 0 0
0 1 | 0 1
1 0 | 0 1
1 1 | 1 0
Schauen wir uns die Logik des Carry-Bits genauer an. Wenn beide oder eines der zu addierenden Bits (lhs und rhs) gleich 0 ist, ist auch das Carry-Bit gleich 0. Nur wenn beide Bits auf 1 gesetzt sind, wird auch das Carry-Bit 1. Dies entspricht genau der Arbeitsweise des UND-Operators (&).
In gleicher Weise wird das Ergebnisbit durch eine XOR-Operation berechnet: Wenn ein Bit (aber nicht beide) auf 1 gesetzt ist, ist das Ergebnis 1, ansonsten 0.
Ergibt sich ein Übertrag, wird dieser zum nächsten signifikanten (links gelegenen) Bit hinzuaddiert. Wir müssen also entweder über die einzelnen Bits iterieren oder die Berechnung rekursiv vornehmen.
#include <iostream.h>
unsigned int add( unsigned int lhs, unsigned int rhs )
{
unsigned int result, carry;
while ( 1 )
{
result = lhs ^ rhs;
carry = lhs & rhs;
if ( carry == 0 )
break;
lhs = carry << 1;
rhs = result;
};
return result;
}
int main()
{
unsigned long a, b;
for (;;)
{
cout << "Geben Sie zwei Zahlen ein. (0 zum Beenden): ";
cin >> a >> b;
if (!a && !b)
break;
cout <<a << " + " << b << " = " << add(a,b) << endl;
}
return 0;
}
Alternativ könnte man die Addition auch durch Rekursion implementieren.
#include <iostream.h>
unsigned int add( unsigned int lhs, unsigned int rhs )
{
unsigned int carry = lhs & rhs;
unsigned int result = lhs ^ rhs;
if ( carry )
return add( result, carry << 1 );
else
return result;
}
int main()
{
unsigned long a, b;
for (;;)
{
cout << " Geben Sie zwei Zahlen ein. (0 zum Beenden): ";
cin >> a >> b;
if (!a && !b)
break;
cout <<a << " + " << b << " = " << add(a,b) << endl;
}
return 0;
}
© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH