Blatt zu Kurs 5

Werbung
Computergrundkenntnisse und Programmieren, WS 09/10, Übung 5: Pointer - Teil 2, Files
Christof Gattringer ([email protected])
In der letzten Stunde haben wir das Konzept von Zeigern (= Pointern), sowie den Adressoperator & und den Dereferenzierungsoperator * kennen gelernt. Hier ein Beispiel zu deren Verwendung:
Ein Programm mit Zeigern:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
using namespace std;
int main() {
int meinalter = 5, deinalter = 10;
int* Palter = 0;
cout << endl;
cout << " meinalter : " << meinalter << endl;
cout << " &meinalter : " << &meinalter << endl << endl;
cout << " deinalter : " << deinalter << endl;
cout << " &deinalter : " << &deinalter << endl << endl;
Palter = &meinalter;
cout << " Palter : " << Palter << endl;
cout << " *Palter : " << *Palter << endl << endl;
Palter = &deinalter;
cout << " Palter : " << Palter << endl;
cout << " *Palter : " << *Palter << endl << endl;
cout << " &Palter : " << &Palter << endl << endl;
return 0;
}
Das Programm liefert die Ausgabe (kann bei ihnen andere Adressen liefern):
meinalter : 5
&meinalter : 0x7fffffa50024
deinalter : 10
&deinalter : 0x7fffffa50020
Palter : 0x7fffffa50024
*Palter : 5
1
Palter : 0x7fffffa50020
*Palter : 10
&Palter : 0x7fffffa50018
Ein Zeiger kann also auf unterschiedliche Speicherbereiche des gleichen Typs zeigen und erlaubt
so oft einfache Lösungen die sonst als “Felder von Feldindizes” implementiert werden müssen. Die
letzte Anweisung zeigt die Tatsache, dass der Zeiger selbst eine Variable ist und auch eine Adresse
im Speicher hat.
Felder und Zeiger:
Wir haben bereits Felder (Arrays) kennen gelernt. Eine ihrer Besonderheiten war, dass Sie bei der
Übergabe in ein Unterprogramm automatisch als Referenz übergeben werden. Der tiefere Grund
dafür ist, dass der Name des Feldes selbst ein Zeiger auf den ersten Eintrag ist. Diesen Zusammenhang illustriert das folgende Programm:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;
int main() {
const int leng = 5;
int vec[leng];
int * P = 0;
for (int i = 0; i < leng; i++ ) {
vec[i] = 10*leng + i;
}
P = & vec[0];
cout << endl << " Adressen am Anfang: " << endl << endl;
cout << " P
: " << P << endl;
cout << " vec : " << vec << endl << endl << endl;
cout << " Adressen der Eintraege: " << endl << endl;
for (int i = 0; i < leng; i++ ) {
cout <<
cout <<
cout <<
P = P +
" P
" &vec[i]
" vec + i
1;
: " << P << endl;
: " << &vec[i] << endl;
: " << vec + i << endl << endl;
}
P = vec;
2
32
33
34
35
36
37
38
39
40
41
42
43
cout << endl << endl << " Werte der Eintraege: " << endl << endl;
for (int i = 0; i < leng; i++ ) {
cout <<
cout <<
cout <<
P = P +
" *P
: " << *P << endl;
" vec[i]
: " << vec[i] << endl;
" *(vec + i) : " << *(vec + i) << endl << endl;
1;
}
return 0;
}
Das Programm zeigt auch, dass man mit Zeigern rechnen, und auf diese Weise die Werte des Feldes
ansteuern kann. Dieses Verfahren heißt Zeigerarithmetik.
Übergabe von Arrays variabler Größe mit Pointern:
Wenn ein Unterprogramm mit Vektoren oder Matrizen (allgemein mit Arrays) arbeitet so ist es
oft erwünscht, dass das Unterprogramm mit Arrays unterschiedlicher Größe aufgerufen werden
kann. Ein Beispiel ist eine Unterprogramm das Matrizen transponiert und das man nicht für jede
Matrixgröße extra schreiben möchte. Daher ist die Übergabe von Arrays mit variabler Größe ein
immer wieder auftretendes Standardproblem.
Diese Problem lässt sich nun ebenfalls durch Pointer lösen. Statt des Arrays (ein oder mehrdimensional) wird der Pointer auf das erste Element übergeben. Der Pointer der im Unterprogramm
entgegengenommen wird zeigt dann auf den Speicherplatz des ersten Elements ohne sich darum
kümmern zu müssen wie groß das Array ist und welche Dimension es hat.
Der Nachteil ist, dass wenn man auf einzelne Einträge des Arrays zugreifen möchte, man deren
Adressen erst ausrechnen muss (ein Beispiel für die bereits erwähnte Pointerarithmetik). Dazu werden die Größen des übergebenen Arrays als zusätzliche integer Argumente übergeben. Es ist wichtig
bei der Adressberechnung zu berücksichtigen welche Einträge wo stehen (vergleiche Übungsbeispiel
04.1). Das nachfolgende Beispiel demonstriert die Vorgangsweise anhand der bereits angesprochenen Matrixtransposition.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;
void zeigmatrix( int* mm, int dim1, int dim2 );
void transpose( int* mm, int* mmT, int dim1, int dim2 );
// --------------------------------------------------------------------int main() {
int aa[3][5] = { {0,1,2,3,4}, {10,11,12,13,14}, {20,21,22,23,24}} ;
int tt[5][3];
3
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
cout << endl << " Die Matrix A :" << endl << endl;
zeigmatrix( &aa[0][0] , 3 , 5 );
transpose( &aa[0][0] , &tt[0][0] , 3, 5 );
cout << " A transponiert :" << endl << endl;
zeigmatrix( &tt[0][0] , 5 , 3 );
return 0;
}
// --------------------------------------------------------------------void zeigmatrix( int* mm, int dim1, int dim2 ) {
for (int i = 0; i < dim1; i++) {
for (int j = 0; j < dim2; j++) {
cout.width(4);
cout << mm[i*dim2+j] << "
}
cout << endl;
}
";
cout << endl << endl;
}
// --------------------------------------------------------------------void transpose( int* mm, int* mmT, int dim1, int dim2 ) {
for (int i = 0; i < dim1; i++) {
for (int j = 0; j < dim2; j++) {
mmT[j*dim1+i] = mm[i*dim2+j];
}}
}
Schreiben und Lesen auf Files:
Oft ist es notwendig Daten auf Files zu schreiben oder diese von dort einzulesen. Dazu wird die Headerdatei fstream mit der Anweisung #include <fstream> am Anfang eingebunden. Die Files die
man lesen, bzw. beschreiben möchte werden einem Objekt ifstream beziehungsweise ofstream
zugeordnet. Auf die Files kann man dann genauso zugreifen, wie man mit cin >> Daten vom Key4
board einliest, beziehungsweise, wie man mit cout << auf den Bildschirm ausschreibt. Das folgende
Beispiel illustriert die Verwendung von Files (der File “Meininput.data” muss bereits existieren):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ifstream reinfile("Meininput.data");
ofstream rausfile("Meinoutput.data");
int zahl;
cout << endl;
for ( int k = 0; k < 8; k++ ) {
reinfile >> zahl;
cout << zahl << endl;
rausfile << zahl << endl;
}
cout << endl;
return 0;
}
Wird der Name des zu öffnenden Files erst im Programm eingelesen, so muss der String in einen
C-String konvertiert werden. Der File wird dann mit dem Kommando
ifstream meinfile(meinname.c str());
geöffnet. Sobald wir das Konzept der Klassen kennen gelernt haben werden wir in der Lage sein
sehr viel weitreichendere Möglichkeiten zum Arbeiten mit Files zu entwickeln.
5
Aufgabe 05.1:
Schreiben Sie das Programm aus Aufgabe 01.3 (Berechnung von Potenzen) so um, dass so viel
als möglich mit Zeigern gerabeitet wird. D.h. statt Variablen verwenden sie Zeiger auf Variablen
und den Dereferenzierungsoperator. (Kein sehr sinnvolles Beispiel - dient nur zur Übung von Zeigern.)
Aufgabe 05.2:
Schreiben Sie ein Programm das einen Vector v mit gleichverteilten Zufallszahlen r ∈ [0, 1) füllt.
Diese Zufallszahlen erhalten Sie mit der Anweisung
vec[i] = rand()/(RAND_MAX + 1.);
Das Programm soll die Einträge des Vektors dann auf einen File schreiben. Als Kontrollgrößen soll
es noch die ersten fünf Momente Mn , n = 1, 2, 3, 4, 5, definiert als
Mn =
−1
1 NX
vn ,
N i=0 i
ausrechnen und auf dem Bildschirm ausgeben.
Ein zweites Programm soll den Vektor von Zahlen aus dem File wieder einlesen und ebenfalls
die ersten fünf Momente berechnen und ausgeben. Der Vergeich der Zahlen zeigt ob Sie die Daten
richtig ein- und ausgelesen haben.
Damit das Beispiel nicht allzu öd ist eine kleine Denksportaufgabe dazu. Sie werden sehen, dass
für ausreichend viele Zufallszahlen die Momente gegen einfache Werte streben. Das erste Moment
ist der Mittelwert, und für im Intervall [0, 1) gleichverteilte Zahlen ist der trivialerweise 1/2. Wie
sieht es für die höheren Momente aus? Können sie die “einfachen” Resultate erraten? Versuchen
Sie ein Argument anzugeben warum genau diese Werte herauskommen.
Aufgabe 05.3:
Mit der heutigen Übung schließen wir den “Grundwortschatz” von C++ ab. Zeigen Sie was sie
bereits gelernt haben und schreiben Sie ein Programm für ein Problem Ihrer Wahl. Das Programm
soll möglichst viele der bereits bekannten Strukturen wie Unterprogramme, Schleifen, Files etc.
verwenden.
Falls Sie gar keine Idee haben hier ein Vorschlag für den Notfall: Ihr Programm erledigt die
statistische Auswertung des letzten Laborversuches. Es liest (in einem Unterprogramm natürlich)
die Messdaten von einem File ein, und macht dann in einem anderen Unterprogramm die Analyse,
berechnet also zum Beispiel den Mittelwert und den statistischen Fehler 1 .
Wichtig: Da diesmal die Aufgabe, insbesondere 05.3, etwas umfangreicher ist, ist der Abgabetermin für die Beispiele zu Blatt 5 erst der Mittwoch 25.11.2009. Nächste Stunde (Blatt 6) wird es
keine Aufgabe geben.
1
Besser wäre natürlich eine Programm das einen Galaxienhaufen simuliert : )
6
Zugehörige Unterlagen
Herunterladen