Einführung in C++ (Teil 3)
Eine Struktur ist eine Ansammlung mehrerer Variablen verschiedener Typen unter einem Namen, um die Daten besser organisieren zu können. Eine Struktur wird wie eine ganz normale Variable definiert und kann danach beliebig oft im Programm verwendet werden. Auf die Struktur kann komponentenweise oder komplett zugegriffen werde. Der komponentenweise Zugriff erfolgt mit Hilfe des Punktoperators. Dabei wird der Name der Struktur, gefolgt von einem Punkt und dem Namen der gewünschten Komponente angegeben.
Beispiel 1:
#include <iostream.h>
#include <string.h>
struct Student
{
char Vorname[20];
char Nachname[20];
int Matrikelnummer;
char Studienfach[20];
char Abschluss[20];
int Fachsemester;
};
void main()
{
Student Jan = {"Jan","Mueller", 984178, "Physik",
"Diplom", 5};
cout << "Name: " << Jan.Vorname <<
" " << Jan.Nachname << endl;
cout << "Matrikelnummer: " << Jan.Matrikelnummer
<< endl
<< "Studienfach:
" << Jan.Studienfach << endl
<< "Angestr.
Abschluss: " << Jan.Abschluss << endl
<< "Fachsemester:
" << Jan.Fachsemester << endl;
}
Output:
Name: Jan Mueller
Matrikelnummer: 984178
Studienfach: Physik
Angestr. Abschluss: Diplom
Fachsemester: 5
Bisher war der Speicherplatzbedarf immer schon fest vorgegeben.
Der Operator new bietet die Möglichkeit Speicherplatz in
der richtigen Menge bereitzustellen.
Der Zugriff auf den Speicher erfolgt ausschließlich über
Zeiger:
int *p;
// Zeiger auf int
p = new int;
// int-Objekt wird erzeugt
*p = 5;
// int-Objekt bekommt den Wert 5 zugewiesen
n x m - Matrizzen:
Schritt 1: Mache die Zahl der Spalten variabel
Ausgangspunkt: O.b.d.A 4 x 3 - Matrix, also float Matrix[4]
[3]
-> 4 x m - Matrix: float *Matrix[4] ;
// Array von 4 Zeigervariablen
Matrix[i] = new float[m] ; // Jede Spalte
erhält m Einträge
wobei i = 0 .. 3
Schritt 2: Mache zusätzlich noch die Zahl der Zeilen variabel
-> n x m - Matrix: float **Matrix;
// Matrix ist ein Zeiger auf Zeiger auf float
Matrix = new float* [n];
// Jede Zeile erhält n Einträge
Matrix[i] = new float[m] ; // Jede Spalte
erhält m Einträge
float **Matrix ist gleichbedeutend mit (float*) *Matrix. Matrix ist also ein Zeiger auf Zeiger die auf Elemente vom Typ float zeigen.
Beispiel 2:
#include <iostream.h>
int i = 5;
void main()
{
int i,j,dim1,dim2;
cout << "Gib die Anzahl der Zeilen und Spalten
der Matrix an: ";
cin >> dim1 >> dim2;
int **matrix;
matrix = new int* [dim1];
for (i = 0; i < dim1; i++)
matrix[i] = new int[dim2];
for (i = 0; i < dim1; i++)
{
for (j = 0; j < dim2; j++)
{
cout << "Geben sie das Matrixelement
a" << i+1 << j+1 <<" ein: ";
cin >> matrix [i] [j];
}
}
cout << "Die eingegebene Matrix lautet: "
<< endl;
for (int i = 0; i < dim1; i++)
{
for (int j = 0; j < dim2; j++)
cout << matrix [i]
[j] << " ";
cout << endl;
}
}
Output:
Gib die Anzahl der Zeilen und Spalten der Matrix an:
2
3
Geben sie das Matrixelement a11 ein: 4
Geben sie das Matrixelement a12 ein: 5
Geben sie das Matrixelement a13 ein: 1
Geben sie das Matrixelement a21 ein: 8
Geben sie das Matrixelement a22 ein: 2
Geben sie das Matrixelement a23 ein: 3
Die eingegebene Matrix lautet:
4 5 1
8 2 3
Man kann den Quellcode von umfangreichen Programmen auf mehrere Dateien aufteilen. Um den Mechanismus der Aufteilung zu verstehen, muß zunächst einmal erläutert werden, wie ein C++ Programm aus den Quelldateien erzeugt wird. Das Umsetzen der Quelltexte in ein ausführbares Programm erfolgt in drei Schritten: Präporzessor -> Compiler -> Linker.
Der Präprozessor sucht nach speziellen Anweisungen, die mit einem
Doppelkreuz (#) gekennzeichnet sind. Die wichtigste Funktion des Präprozessors
ist das Einbinden von Dateien durch den Befehl #include.
Dabei werden andere Quelltexte in den Quelltext des Programms eingebunden.
Der Compiler setzt den vom Präprozessor überarbeiteten Quelltext
in den Objektcode um. Der Objektcode ist ein maschinennaher Code, der aber
noch nicht ausgeführt werden kann, da er noch unaufgelöste Funktionsaufrufe
enthält. Er wird in Dateien mit der Endung .o gespeichert.
Der Linker setzt die vom Compiler generierten Objektdateien zu einem
vollständigen, aufführbaren Programm zusammen. Er verbindet die
Funktionsaufrufe mit den zugehörigen Funktionen.
Beispiel 3:
//Programm: Modul1.cpp
#include <iostream.h>
void vertausche(int *a, int *b);
void main()
{
int x, y;
x = 5;
y = 7;
cout << "x = " << x << " und
" << "y = " << y << endl;
vertausche (&x, &y);
cout << "x = " << x << " und
" << "y = " << y << endl;
}
//Programm: Modul2.cpp
#include <iostream.h>
void vertausche(int *a, int *b)
{
int h;
h = *a;
*a = *b;
*b = h;
}
Separate Compilation:
> c++ -c Modul1.cpp
> c++ -c Modul2.cpp
Der Schalter -c unterdrückt den Aufruf des Linkers. Die Quelldateien werden nur übersetzt, es entstehen die Objektdateien Modul1.o und Modul2.o.
Das Linken erfolgt über den Befehl
> c++ -o vertausche Modul1.o Modul2.o
Der Aufruf des Linkers bildet aus den Objektdateien das ausführbare Programm unter dem Namen vertausche.
Man kann das Compilieren und Linken auch in einem Aufruf veranlassen:
> c++ -o vertausche Modul1.cpp Modul2.cpp
Allgemeine Regeln:
Beispiel 4:
// Programm file1.cpp
#include <iostream.h>
int a = 3, b = 5, c = 1;
//Definition externer Variablen
int f();
//Deklaration Funktionsprototyp
void main()
{
cout << a << " " << b << " " <<
c << endl;
cout << f() << endl;
cout << a << " " << b << " " <<
c << endl;
}
// Programm file2.cpp
int f()
{
extern int a;
//Verweis auf globales (externes) a
int b;
//b ist lokal
a = b = 2;
return (a + b);
}
> c++ -c file1.cpp
> c++ -c file2.cpp
> c++ -o test file1.o file2.o
> ./test
Output:
3 5 1
4
2 5 1
Vorteil der separaten Complilation: Will man in einem Modul etwas verändern, braucht man nicht das gesamte Programm erneut zu compilieren, sondern nur das Modul, in dem die Änderung vorgenommen wurde.
Beispiel 5:
//Veränderung von file2.cpp. Setze a = b = 1.
int f()
{
extern int a;
//Verweis auf globales (externes) a
int b;
//b ist lokal
a = b = 1;
return (a + b);
}
> c++ -c file2.cpp
> c++ -o vertausche file1.o file2.o
Output:
3 5 1
2
1 5 1
Die Ein- und Ausgabe muß nicht unbedingt über die Tastatur oder den Bildschirm erfolgen. Mit
> Programmname < Quelldateiname > Zieldateiname
werden als Eingabe in das Programm Programmname die Einträge der Datei Eingabe verwendet und die Ausgabe des Programms in die Datei Zieldatei umgelenkt.
Beispiel 6: Schreibe die Ausgabe des Programms vertausche1 in die Datei vertausche.dat.
// Programm: vertausche1
#include <iostream.h>
void vertausche(int *a, int *b)
{
int h;
h = *a;
*a = *b;
*b = h;
}
void main()
{
int x, y;
x = 5;
y = 7;
cout << "x = " << x << " und
" << "y = " << y << endl;
vertausche (&x, &y);
cout << "x = " << x << " und
" << "y = " << y << endl;
}
> c++ -o vertausche vertausche1.cpp
> ./vertausche > vertausche.dat
> cat vertausche.dat
x = 5 und y = 7
x = 7 und y = 5
Beispiel 7: Einlesen der Werte für x und y aus der Datei eingabe.dat
// Programm: vertausche2
#include <iostream.h>
void vertausche(int *a, int *b)
{
int h;
h = *a;
*a = *b;
*b = h;
}
void main()
{
int x, y;
cin >> x;
cin >> y;
cout << "x = " << x << " und " << "y
= " << y << endl;
vertausche (&x, &y);
cout << "x = " << x << " und " << "y
= " << y << endl;
}
> echo 4 5 > eingabe.dat
> c++ -o vertausche2 vertausche2.cpp
> ./vertausche1 < eingabe.dat
Output:
x = 4 und y = 5
x = 5 und y = 4
Will man die Werte für x und y aus der Datei eingabe.dat einlesen und das Ergebnis der Vertauschung in die Datei ausgabe.dat schreiben, so gibt man folgenden Befehl im Terminal ein:
> ./vertausche2 < eingabe.dat > ausgabe.dat
> cat ausgabe.dat
x = 4 und y = 5
x = 5 und y = 4
Eine Klasse ist ein abstrakter Datentyp. Sie enthält Daten mit den zugehörigen Funktionen.
Eine Klasse enthält also zusätzlich zu den Daten Funktionen, die beschreiben, wie man mit den Daten umzugehen hat. Die Daten und Funktionen sind Elemente eines Objekts.Abstrakter Datentyp = Daten + Funktionen
Die Klasse dient dazu, dem Compiler die Beschreibung von später
zu definierenden Objekten mitzuteilen.
Eine Variable oder Konstante dieses Datentyps nennt man eine Instanz
der Klasse.
Beispiel 8:
#include <iostream.h>
class vektor
{
public:
vektor spiegeln() const
{ vektor a;
a.x = -x;
a.y = -y;
return a; }
void print() const
{ cout << x << " " << y
<< endl; }
float x,y;
};
void main()
{
vektor u = {2,4}, v;
cout << "Vektor u: "; u.print();
u.x = 5;
v = u.spiegeln();
cout << "Vektor u: "; u.print ();
cout << "Vektor v: "; v.print ();
}
Output:
Vektor u: 2 4
Vektor u: 5 4
Vektor v: -5 -4
Der Datentyp Vektor enthält zwei Datenelemente x und y (Komponenten
des Vektors) und zwei Funktionen spiegeln (Spiegeln eines beliebigen Vektors
am Ursprung) und print (Ausgabe eines beliebigen Vektors auf den Bildschirm).
Das Schlüsselwort const, das z.B. im Ausdruck void
print() const verwendet wird, deutet an, daß die Funktion print
beispielsweise das Objekt u durch den Aufruf u.print () nicht
verändern wird.
Es ist sinnvoll, die Elementfunktionen in der Klassendefinition nur zu deklarieren, und sie an einer anderen Stelle zu definieren. Dort werden sie durch Voranstellen von Klassenname:: gekennzeichnet.
Beispiel 9:
#include <iostream.h>
class vektor
{
public:
vektor spiegeln() const;
void print() const;
float x,y;
};
vektor vektor::spiegeln() const
{
vektor a;
a.x = -x;
a.y = -y;
return a;
}
void vektor::print() const
{
cout << x << " " << y << endl;
}
void main()
{
vektor u = {2,4}, v;
cout << "Vektor u: "; u.print();
v = u.spiegeln();
cout << "Vektor v: "; v.print ();
}
Output:
Vektor u: 2 4
Vektor v: -2 -4
Das objektorientierte Programmieren basiert unter anderem auf dem Konzept
der Datenkapselung. Dadurch ist der Zugriff auf bestimmte Objekte eingeschränkt
und nur über wohldefinierte Schnittstellen erlaubt. Auf alle private
Elemente einer Klasse ist kein direkter Zugriff möglich.
Beispiel:
class vektor
{
public:
vektor spiegeln() const;
void print() const;
private:
float x,y;
};
Es ist kein direkter Zugriff auf die Elemente x und y möglich.
Die Zuweisung u.x = 5 führt zu folgender Fehlermeldung: member
`x' is a private member of class `vektor'.
private: nur in Elementfunktionen der Klasse selbst benutzbar.
public: allgemein benutzbar.
Um private Mitglieder einer Klasse initialisieren zu können braucht
man einen Konstruktor.
Ein Konstruktor ist eine besondere Methode, die den Namen
der Klasse trägt und keinen Rückgabewert besitzt (auch kein void).
Der Konstruktor reserviert den für das zu
instanzierende Objekt nötigen Speicherplatz.
Beispiel: vektor(float xx = 0, float yy = 0): x(xx),
y(yy){}
Durch den Aufruf des Konstruktors mittels u(2,4),
v(1,2), w(5) werden die Objekten u, v und w initialisiert. Durch
u(2,4)
erhält x den Wert 2 und y den Wert 4. x(xx) ist hier gleichbedeutend
mit x=xx=2 .
Da durch w(5) nur ein Parameter für
x übergeben wird, nimmt y den durch yy = 0 definierten defaultwert
an. w(5) ist gleichbedeutend mit x=xx=5 und y=yy=0.
Beispiel 10:
#include <iostream.h>
class vektor
{
public:
vektor(float xx = 0, float yy = 0): x(xx), y(yy){} //Konstruktor
vektor spiegeln() const;
void print() const;
private:
float x,y;
};
vektor vektor::spiegeln() const
{ vektor a;
a.x = -x;
a.y = -y;
return a;
}
void vektor::print() const
{
cout << x << " " << y <<
endl;
}
void main()
{
vektor u(2,4), v(1,2), w(5);
cout << "Vektor w: "; w.print ();
cout << "Vektor v: "; v.print ();
cout << "Vektor u: "; u.print ();
v = u.spiegeln();
cout << "Gespiegelter Vektor u: "; v.print
();
}
Output:
Vektor w: 5 0
Vektor v: 1 2
Vektor u: 2 4
Gespiegelter Vektor u: -2 -4
Um die Lesbarkeit von Programmen zu erhöhen und das Programmieren einfacher und übersichtlicher zu machen, definiert man sich eigene Operatoren. In C++ stehen eine Reihe von vordefinierten Operatoren zur Verfügung (+, -, *, /, += ...) wobei man folgendes beachten muß:
Auf diese Weise kann man die gewohnte Schreibweise für
die Addition/Subtraktion zweier Vektoren und die Spiegelung eines Vektors
am Ursprung benutzen (Beispiel ):
Addition zweier Vektoren: u + v = (u1,u2)
+ (v1,v2)=(u1+v1,u2+v2)
Subtraktion zweier Vektoren: u - v = (u1,u2)
- (v1,v2)=(u1-v1,u2-v2)
Spiegelung eines Vektors v am Ursprung: v=-(v1,v2)=(-v1,-v2)
Beispiel 11:
#include <iostream.h>
class vektor
{
public:
vektor(float xx = 0, float yy = 0): x(xx), y(yy){}
friend vektor operator+(const vektor &a, const vektor &b);
friend vektor operator-(const vektor &a, const vektor &b);
friend vektor operator-(const vektor &a);
void print() const;
private:
float x,y;
};
vektor operator+(const vektor &a, const vektor &b) //Addition
von Vektoren
{
return vektor(a.x + b.x, a.y + b.y);
}
vektor operator-(const vektor &a, const vektor &b) //Subtraktion
von Vektoren
{
return vektor(a.x - b.x, a.y - b.y);
}
vektor operator-(const vektor &a)
//Vektor spiegeln
{
return vektor(-a.x, -a.y);
}
void vektor::print() const
{
cout << x << " " << y << endl;
}
void main()
{
vektor u(2,4), v(1,2), Summe, Differenz, vs;
cout << "Vektor u: "; u.print ();
cout << "Vektor v: "; v.print ();
Summe = u + v;
cout << "Summenvektor: "; Summe.print();
Differenz = u - v;
cout << "Differenzvektor: "; Differenz.print();
vs = -v;
cout << "Gespiegelter Vektor: "; vs.print();
}
Output:
Vektor u: 2 4
Vektor v: 1 2
Summenvektor: 3 6
Differenzvektor: 1 2
Gespiegelter Vektor: -1 -2
Ein selbstdefinierter Operator ist eine Funktion in einer syntaktisch
ansprechenden Verkleidung.
Der Ausdruck u + v ist eine verkürzte Schreibweise
des Funktionsaufrufs operator+(u,v).
Bei der Deklaration der drei Operatorfunktionen innerhalb der Klasse
Vektor wurde das Schlüsselwort friend benutzt. Nur dadurch
erhielten die Operatorfunktionen Zugriff auf die privaten Klassenelemente
x und y.
Hinter Operatoren verbergen sich also letztlich Elementfunktionen. Der
Ausdurck
vektor operator+(const vektor &a, const vektor &b) ist somit
äquivalent zu
vektor addieren(const vektor &a, const vektor &b).
Man hätte das Programm auch folgendermaßen schreiben können:
Beispiel 12:
#include <iostream.h>
class vektor
{
public:
vektor(float xx = 0, float yy = 0): x(xx), y(yy){}
friend vektor addieren(const vektor &a, const vektor &b);
friend vektor subtrahieren(const vektor &a, const vektor
&b);
friend vektor spiegeln(const vektor &a);
vektor spiegeln();
void print() const;
private:
float x,y;
};
vektor addieren(const vektor &a, const vektor &b)
//Addition von Vektoren
{
return vektor(a.x + b.x, a.y + b.y);
}
vektor subtrahieren(const vektor &a, const vektor &b)
//Subtraktion von Vektoren
{
return vektor(a.x - b.x, a.y - b.y);
}
vektor spiegeln(const vektor &a)
//Vektor spiegeln
{
return vektor(-a.x, -a.y);
}
void vektor::print() const
{
cout << x << " " << y << endl;
}
void main()
{
vektor u(2,4), v(1,2), Summe, Differenz, vs;
cout << "Vektor u: "; u.print ();
cout << "Vektor v: "; v.print ();
Summe = addieren(u,v);
cout << "Summenvektor: "; Summe.print();
Differenz = subtrahieren(u,v);
cout << "Differenzvektor: "; Differenz.print();
vs = spiegeln(v);
cout << "Gespiegelter Vektor: "; vs.print();
}
Output:
Vektor u: 2 4
Vektor v: 1 2
Summenvektor: 3 6
Differenzvektor: 1 2
Gespiegelter Vektor: -1 -2