Übungsbesprechung von Kapitel1

siehe loesungen1.html

Programmieren mit c++, Kapitel 2

Formatierte Ausgabe

Die Anweisung
if(j*i<10) cout << " ";
in der Übung3 wurde gebraucht damit die Zahlen in der Tabelle untereinander zu stehen kamen. Dies kann man aber mit der Funktion form(), die in stream.h definiert ist, eleganter lösen.
(Im aktuellen C-Standard gibt es form() leider nicht mehr, printf() ist aber noch aktuell)
Die Benutzung sieht so aus:
cout << form(Formatstring,Parameterliste);
oder
printf(Formatstring,Parameterliste);
Der Formatstring hat folgende Form:
"...%Zahl1.Zahl2X..."
Das Prozentzeichen kennzeichnet den Beginn einer Formatierung, und ein Zeichen X bildet den Abschluss. Zeichen die ausserhalb solcher Konstrukte liegen werden unverändert dargestellt. Zahl1 gibt die minimale Anzahl Ausgabestellen an, Zahl2 gibt für Fliesszahlen die Anzahl Nachkommastellen an. Ein Minuszeichen vor Zahl1 bewirkt eine linksbündige Darstellung. Mit einer 0 vor Zahl1 wird mit Nullen statt mit Leerzeichen aufgefüllt.
Für X können folgende Zeichen stehen:
  Für ganzzahlige Argumente (char, short, int oder long):
	d	Das Argument wird Dezimal dargestellt
	x	Das Argument wird Hexadezimal dargestellt (Basis 16)
	o	Das Argument wird Oktal dargestellt (Basis 8)
	u	Das Argument wird Dezimal ohne Vorzeichen dargestellt
	c	Das Argument wird als einzelnes Zeichen betrachtet
	Für long Zahlen muss jeweils noch ein l (kleines L) vor dem
	Abschlusszeichen stehen, also zum Beispiel %ld.
  Für Fliesszahlen (double oder float):
	f	Normale Fliesszahl (z.B. 123.45)
	e	Exponentialdarstellung (z.B. 1.2345e2)
	g	Es wird automatisch zwischen f und e gewählt
	Für double Zahlen kann jeweils noch ein l (kleines L) vor dem
	Abschlusszeichen stehen, also zum Beispiel %lf.
  Für Strings (char *):
	s	Zahl2 bestimmt hier die maximale Ausgabelänge

Die entsprechende Anweisung in unserm Beispiel könnte dann so aussehen:
	cout << form(" %3d",j*i);

In C gibt es eine Funktion printf() die gleich wie form() funktioniert, aber keinen Rückgabewert liefert, sondern direkt auf die Standardausgabe ausgibt. (in stdio.h definiert). Dort gibt es auch eine entsprechende Eingabefunktionen scanf(), wobei immer Zeiger auf Variablen übergeben werden müssen. Zudem gibts noch die Funktionen sprintf() und sscanf(): Hier wird vor dem Formatstring noch ein Zielstring (sprintf) oder Quellstring (sscanf) angegeben.
All diese Funktionen kann man auch in C++ benutzen.
(Ausführliche Beschreibung z.B. in 2)

Warnung: Die Anzahl und Typen der Argumente müssen mit den Formatanweisungen übereinstimmen. Da die Auswertung erst in der Funktion erfolgt, hat der Compiler keine Möglichkeit dies zu überprüfen. Fehler können somit zu unerwarteten Resultaten führen oder einen Absturz verursachen.

Kommando Argumente

Auf den meisten Computersystemen können Programme einfach durch Angabe des Namens gestartet werden. Auf der VAX braucht man dazu folgende Definition:
$ Name:==$disk$user:[Benutzername.ckurs]Name.exe
(Auf allen andern Betriebssystemen ist keine Definition nötig)
Hinter dem Programmnamen lassen sich noch Argumente mit angeben. Die Argumente muss das Programm natürlich lesen können. Das Betriebssystem übergibt der Funktion main() deshalb zwei Argumente:
 main(int argc, char *argv[])
Das erste Argument enthält die Anzahl Parameter, die beim Aufruf angegeben wurden, wobei der Name des Programms mitgezählt wird. Das zweite Argument ist ein Feld von Zeigern auf char, oder anders gesagt ein Stringfeld. Der Zugriff auf einen String erfolgt dabei mit argv[i]. (wobei i zwischen 0 und argc liegen muss).

Übung 4: Analysiere das Beispiel uebung3b.cc
Übung 5: Schreibe ein Programm argecho.cc das seine Argumente ausgibt.

Zeiger und Referenzen

Wir wollen fogendes Beispiel betrachten:
int quadriere(int x)
{
 x = x*x;  return x;
}
main()
{
 int x=7,y;
 y=quadriere(x);
 cout << x << y;
}
In C++ (und auch in C) werden Funktionsargumente direkt übergeben, und nicht etwa die Adressen wie zum Beispiel in Fortran oder Pascal. Die Adresse eines Objekts wird auch als Zeiger bezeichnet. Wenn also von einem Zeiger auf int die Rede ist, so ist damit die Adresse gemeint an der ein Objekt vom Typ int gespeichert ist. Wenn wir also im obigen Beispiel mit der Funktion quadriere die Variable x des Hauptprogramms verändern wollen, müssen wir einen Zeiger auf x übergeben. Dazu gibt es zwei Möglichkeiten:

1. Wir definieren die Funktion quadriere so:

int quadriere(int& x)
Damit erreichen wir dass der Compiler beim Aufruf einen Zeiger auf x übergibt. Dies wird auch als "passing by referenz" bezeichnet. Das Zeichen & in der obigen Deklaration heisst "Referenz auf". x ist also vom Typ "Referenz auf int".

2. Wir benutzen einen Zeiger:

int quadriere(int* x)
Um dann auf das Objekt selbst zuzugreifen schreibt man *x. In unserm Beispiel also:
 *x = *x * *x;  return *x;

Der erzeugte Code ist in beiden Fällen gleich. Das Beispiel ref.cc benutzt beide Varianten. Um den erzeugten Maschinencode (ein Assemblerprogramm) daraus zu speichern kann man auf der VAX die /machine Option benutzen:

$ GCC/PLUS/MACHINE REF.CC
Unter Linux (auch andere Unixe mit GCC, sowie auf Amiga) benutzt man:
 gcc -S ref.cc
Es wird dann ein Assemblerprogramm ref.s erzeugt. Wir können dieses dann mit unserm Editor betrachten, und uns davon überzeugen dass in beiden Fällen der gleiche Code generiert wurde.

Wir haben schon mehrmals Strings verwendet ohne genau zu erklären wie sie funktionieren. Ein String in C++ ist eine Feld von Objekten des Typs char, wobei das letzte den Wert 0 hat. Eine Variable die einen 3 Zeichen langen String aufnehmen kann könnte man also so definieren:

	char string[4]={'a','b','c',0};
Einfacher ist es aber einen Zeiger zu benutzen:
	char *string="abc";
Wenn in einem Ausdruck ein in doppelten Anführungszeichen eingeschlossener Text vorkommt, so wird ein String erzeugt und der Zeiger auf das erste Zeichen benutzt. In einfache Anführungszeichen eingeschlossene Zeichen (z.B.'a') werden wie ganze Zahlen behandelt. (ASCII-Code des Zeichens). Um ein nicht druckbares Zeichen zu verwenden setzt man den Backslash \ gefolgt von einer 3-stelligen Oktalzahl. Beispiel: '\007'.
Für einige Spezialzeichen gibt es auch kürzere Schreibweisen:
	'\n'	Zeilentrenner, auf der VAX gleich wie '\012' (LF=LineFeed)
		auf der IBM werden zum Teil zwei Zeichen erzeugt (CR LF)
	'\t'	Tabulator ('\011')
	'\r'	Wagenrücklauf ('\015'  CR=CarriageReturn)
	'\0'	Nullzeichen ('\000')
	'\\'	Backslash
	'\''	Anführungszeichen

Definition neuer Typen

Mit dem Schlüsselwort typedef können wir einen neuen Typ definieren:
typedef Typ Neuertyp;
Die Syntax ist gleich wie bei der Deklaration einer Variablen, statt aber eine Variable zu deklarieren wird eben ein Typ definiert.
Beispiele:
	typedef short WORD;
	typedef unsigned long ULONG;
	typedef char * STRING;
Die Verwendung eines so definierten Typs ist gleich wie bei einem Grundtyp.
Beispiele:
	WORD a,b,c;
	ULONG positive_zahl;
	STRING name="test.dat";
Bisher haben wir nur einfache Typen verwendet, es gibt aber auch zusammengesetzte. Ein Beispiel:
struct adresse
{
 STRING name,strasse,ort;
 int nummer,plz;
 long telefon;
};
Der Zugriff auf ein Element einer Struktur erfolgt mit dem Punkt. Die Verwendung sieht dann etwa so aus:
	adresse uni;
	uni.name="Universität";
	uni.ort="Zürich";
	uni.plz=8057;
	uni.telefon=6354478;
Um bei Funktionsaufrufen nicht die ganze Struktur kopieren zu müssen, verwendet man bei der Parameterübergabe oft Zeiger. (Vor allem in C, wo es noch keine Referenz gab). Wenn z ein Zeiger auf eine Struktur ist, kann man statt mit (*z).element auch mit z->element auf einen Struktureintrag zugreifen. Beispiel:
void adresse_drucken(adresse *zadr)
{
 cout << zadr->name << '\n'
      << zadr->plz << ' ' << zadr->ort << '\n'
      << "Tel." << zadr->telefon << '\n';
}
Innerhalb einer Struktur dürfen auch weitere Strukturen vorkommen, jedoch nicht die Struktur die gerade definiert wird (und somit eben noch nicht fertig definiert ist). Man kann aber einen Zeiger darauf verwenden. Beispiel:
struct adresse
{
 adresse *next;
 STRING name,strasse,ort;
 int nummer,plz;
 long telefon;
};

Klassen

In Strukturen dürfen auch Funktionen vorkommen. Oft ist es dann auch sinnvoll einige Elemente der Struktur nur für diese internen Funktionen zu verwenden. Dazu verwendet man statt struct das Schlüsselwort class. Allgemeiner Aufbau einer Klasse:
class Klassenname
{
 // Lokale Objekte
public:
 // Oeffentliche Objekte
};

Beispiel:
class primzahl
{
 int p;
public:
 int lesen() {return p;}
 int setzen(int zahl) {p=zahl;}
 int next();
};
Die Funktion next ist hier erst deklariert aber noch nicht definiert. Man kann sie danach folgendermassen definieren:
int primzahl::next()
{
 p++;
 while(!istprim(p)) p++;
 return p;
}

Die Zeichen :: weisen den Compiler darauf hin dass die Funktion next für die
Klasse primzahl definiert wird.
Auf Klassenfunktionen kann man ebenfalls (wie auch auf Strukturvariablen)
mit dem Punktoperator zugreifen:
	primzahl x;
	x.setzen(17);
	cout << "2 Primzahlen:" << x.lesen(); cout << x.next();

Übung 6: Erweitere die Klasse primzahl um die Funktion int vorherige()

Übung 7: Schreibe ein Programm das alle Primzahlen zwischen 1000 und 900 sucht, wobei zuerst die grösseren ausgedruckt werden. (Natürlich mit Verwendung der Klasse primzahl)


Last update: 30-März-2007