Programmieren in C++
Kapitel 1: Nichtobjektorientierte Erweiterungen zu C
Zwischen ANSI-C und C++ gibt es einige unterschiede, die nicht (zumindest nicht
offensichtlich) auf objektorientiertes
Programmieren zurückzuführen sind. Ich möchte hier auf einer Art
und Weise auf die wichtigsten Dinge
eingehen, welche nicht unbedingt Formal richtig ist, sondern vor allem
für Anfänger verständlich. Man
sehe mir also bitte die Behauptung nach, cin und cout wäre wenig
objektorientiert.
Ich sehe dieses Kapitel als Brücke zwischen C und C++, nicht mehr aber
auch nicht weniger. Einiges könnte
genauerbehandelt werden - wird es aber nicht!
1.1 Namensräume
Wir haben in C schon Header-Files kennengelernt. Diese enthalten (grob gesagt) Funktionen zu bestimmten bereichen,
wie zum Beispiel der Ein- und Ausgabe (stdio.h) oder mathematische Funktionen (z.B. math.h). Wir können solche
Header-Dateien selbst schreiben, und es kann auch vorkommen, dass wir eine Funktion in verschiedenen Header-Dateien
mehrmals schreiben (vielleicht um leicht unterschiedliche Arten der selben Funktion zu implementieren). Dann müssen
wir dem Compiler allerdings mitteilen, welche Version der Funktion er im zweifelsfall verwenden soll.
Ich erspare mir Details und bringe ein Beispiel, die für den ganzen Kurs reichen wird (hoffe ich!):
#include <iostream>
using namespace std;
|
gleichbedeutend ist (zumindest in unserem Fall):
Ich habe mich spontan entschieden, bei der herkömmlichen Methode zu bleiben!
1.2 Ein- und Ausgabe von Zeichen
Es gibt zwei neue Funktionen um Daten auszugeben und einzulesen. Sie tragen dem Modell rechnung,
dass Daten als Datenströme von der Tastatur kommen und auch als Ströme zum Bilschirm
gelangen (analog später auch auf Dateien anwendbar). Jetzt raten wir mal, woher die Datei iostream.h
ihren Namen hat.
Ich will die Funktionsweise der beiden Funktionen cin und cout anhand eines Beispiels
zeigen. Zusätzlich zu der hier gezeigten Verwendung beider Funktionen gibt es noch viele Möglichkeiten,
mit cin und cout zu arbeiten. Diese werde ich im Laufe des Kurses einfüren, wenn sie nötig sind!
|
#include <iostream.h>
void main( void )
{
int zahl = 5, eingabe;
cout << "Geben Sie bitte eine Zahl ein:
";
cin >> eingabe;
//
Das endl entspricht dem Steuerzeichen \n in ANSI C!
cout << zahl << " * " <<
eingabe << " = " << zahl*eingabe
<< endl;
}
|
Ein- und Ausgabe wird in C++ nichtmehr als Funktion im eigentlichen Sinne behandelt. Vielmehr
handelt es sich um Ein- und Ausgabeströme von und zu den Geräten. Der Stream-Operator
zeigt an, in welche Richtung der Transfer vonstatten geht. Ströme können auch geschachtelt
werden, wie die letzte Zeile zeigt. Um einen Zeilenumbruch zu erzeugen, verwendet man die endl-Marke.
Wichtigste Neuerung ist, dass cin und cout den Typ der übergebenen Variablen selbst erkennen.
Es werden keine Formatangaben mehr benötigt!
1.3 Datentypen
Die Datentypen in C++ entsprechen denen von ANSI-C, nur das C++ eine stärkere Typbindung
durchfürt! Zusätzlich enthält C++ den Datentyp bool, welcher Wahrheitswerte
(true oder false) enthält. Dabei entspricht false dem Wert 0, alle anderen Werte sind true!
1.4 Lokal definierte Variablen
In C habe ich am Rande erwähnt, dass Anweisungen zu Blöcken zusammengefasst werden können,
indem man mit den Blockklammern { und } arbeitet. In C++ ist es möglich, Variablen innerhalb dieser
Blockklammern zu definieren. Ihr Gültigkeitsbereich ist dann auf das Innere des Blockes begrenzt,
in dem sie definiert sind. Wir können so zum Beispiel Variablen direkt in einer For-Schleife definieren:
{
for
(
int
c = 1; c<= 10; c++ ) { cout <<
"Zahl: "<< c << endl; }
}
cout << c*10 << endl;
|
Die Variable c kann innerhalb der grünen Klammern problemlos verwendet werden, die
rote Zeile produziert allerdings einen Fehler, weil c an dieser Stelle nicht mehr definiert ist.
1.5 Referenzen
In C haben wir gelernt, dass man Parameter, deren Wert wir innerhalb einer Funktion verändern
wollen, als Zeiger übergeben müssen. In C++ kann man stattdessen Referenzen auf den Parameter
anlegen:
#include <iostream.h>
void drucke( int& parameter ) { cout <<
parameter << endl; parameter *= 2; }void main( void )
{
int parameter = 10;
int k = 1;
int& r = k;
k = 2;
drucke( r );
r = 4;
drucke( k );
cout << r << " " << k
<< endl;
drucke( k );
cout << i << endl;
}
|
Bevor ihr das Programm abtippt und ausprobiert, denkt mal darüber nach, welche Ausgabe
am Bildschirm erscheinen sollte.
Wichtig:Werden Variablen als Referenz deklariert (wie r im obigen Beispiel), dann muss
bei der Deklaration SOFORT angegeben werden, auch welche Variable verwiesen werden soll (im obigen
Beispiel k)!
1.6 Standartparameter (default-Werte für Parameter)
Man kann in C++ auch Standart-Werte fü Parameter angeben. Diese Parameter müssen dann
in der Parameterliste der Funktion am Ende stehen. Werden die Parameter beim Funktionsaufruf
weggelassen, dann verwendet die Funktion die Standartparameter.
Auch Referenzparameter können mit default-Werten vorbelegt werden. Wie das geht, zeigt alles
das folgende Beispiel:
#include <iostream.h>
//
Eigentlich sollte man auf globale Variablen verzichten
// hier sind sie zur Demonstration nötig!
int u = 4, v = 5;
void drucke( int a, int b, int &c, int& d = u, int e = 5, int&f = v )
{ cout << a << b << c << d
<< e << f << endl; }void main( void )
{
int w = 9, x
= 3, y = 1, z = 8;
drucke( u, v, w, x, y, z );
drucke( u, v, w );
drucke( u, v, w, x );
drucke( u, v, w, x, y );
}
|
1.7 Inline - Funktionen / Abkürzungen
An dieser Stelle gehe ich auf einen Typ von Funktionen ein, der uns VIEL(!) später helfen
wird, unsere Programme zu beschleunigen. Normalerweise liegen Funktionen irgendwo im Speicher.
Wird die Funktion aufgerufen, setzt der Prozessor seinen Programm-Zeiger (dieser zeigt auf die
jeweils nächste Zeile im laufenden Programm) auf diese Speicherstelle. Zuvor werden jedoch
verschiedene Prozessorinterne Werte auf den Stapelspeicher gesichert. Dieses sichern der sogenannten
Prozessumgebung ist jedoch sehr Zeitaufwendig, so dass es unter umständen schneller geht,
eine Funktion direkt in das Hauptprogramm einzubinden.
Um die Funktion jedoch trotzdem nur einmal schreiben zu müssen, definiert man sie als
Inline-Funktion. Diese sind genauso wie gewöhnliche Funktionen zu programmieren, werden
jedoch vom Compiler beim compilieren an die jeweiligen Positionen in die aufrufende Funktion
hineingeschrieben, somit entfällt das Springen und das Sichern der Umgebung.
Ich möchte mit dem folgenden Beispiel noch mehr zeigen: Es gibt in C die Mölichkeit,
if-Abfragen abzukürzen. Dazu schreibt man zunächst die Bedingung (z.B. a != b), dann ein ?,
dann die THEN- Anweisung, gefolgt von einem : und der ELSE- Anweisung. Ein Beispiel macht dies
deutlicher:
#include <iostream.h>
inline
int max( int a, int b)
{ return a > b ? a : b; }
void
main(
void )
{
int a= 5, b
= 6;
cout << "Maximum von a un b: "
<< max( a,b ) << endl;
}
|
Die Funktion max kann auch anders implementiert werden:
inline int max( int a, int b)
{
int ergebnis;
if( a > b) ergebnis = a; else ergebnis = b;
return ergebnis;
}
|
1.8 Überladene Funktionen
Es besteht die Möglichkeit, zwei Funktionen mit gleichem Namen zu implementieren.
Dies gelingt uns deshalb, weil in C++ nichtmehr nur der Name, sondern auch die
Übergabeparameter zur Idendifizierung einer Funktion herangezogen werden. So
lässt sich zum Beispiel eine Funktion Quadrat schreiben, die einmal
Ganzahlwerte und einmal Gleitkommawerte aufnimmt. Die Anzahl der Parameter von
überladenen Funktionen muss nicht gleich sein! Ich will wieder en kurzes
Beispiel bringen, welches den Sachverhalt ausreichend erläutert:
#include <iostream.h>
void
swap( double& m, double& n );
void swap( const char*& a, const char*& b );
void main( )
{
//
Hier handelt es sich um Zeiger auf
// Zeichenkonstanten der Zeiger kann
// verändert werden, der Inhalt nicht!
const char *s
= "eins";
const char *t = "zwei";
double x = 1.0, y = 2.0;
swap( x, y );
swap( s, t );
cout << x << " " << y
<< endl;
cout << s << " " << t
<< endl;
}
void swap( double& m, double& n )
{ double z = m; m = n; n = z; }
void swap( const char*& a, const char*& b )
{ const char *z = a; a = b; b = z; }
|
1.9 Speicherplatzreservierung
Die Änderungen auf diesem Gebiet sind so trivial, dass ich mir ein Beispiel spare. Wir
eretzen einfach malloc durch new und lassen die Parameter weg. New braucht keine Angabe
über die Größe des zu reservierenden Speichers mehr.
Statt den Speicher mit free wieder zu löschen geben wir ihn jetzt mit delete wieder
frei.
int *zeiger = new int;
delete zeiger; |
Das Beispiel legt einen Zeiger auf eine Integer-Variable an und reserviert gleich den
Speicherplatz dafür. New benötigt also lediglich den Typ, auf den der Zeiger
verweist. Gleich darauf wird der Speicher wieder freigegeben. Bitte verzichtet
darauf, den Sinn dieses Beispiels zu hinterfragen. Danke.
|