Kapitel 2 Matrizen in C++
Kapitel 2 Matrizen in C++
Kapitel 2 Matrizen in C++
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
<strong>Kapitel</strong> 2<br />
<strong>Matrizen</strong> <strong>in</strong> <strong>C++</strong><br />
In der Computersprache C ist die Standardmethode zur Behandlung von <strong>Matrizen</strong> durch<br />
1 const <strong>in</strong>t n=10;<br />
2<br />
3 double a[n][n];<br />
gegeben. Allerd<strong>in</strong>gs gibt es bei dieser Methode e<strong>in</strong>e Reihe von Schwierigkeiten, <strong>in</strong>sbesondere<br />
wenn wir das Array a an e<strong>in</strong>e Funktion übergeben.<br />
E<strong>in</strong>e deutlich zufriedenstellendere Lösung kann mit Hilfe des Klassenkonzeptes erzielt<br />
werden. In den folgenden Abschnitten wollen wir e<strong>in</strong>e Matrixklasse programmieren. Diese<br />
Klasse wird Ausgangspunkt für die meisten Programme dieses Kurses darstellen.<br />
2.1 Die Matrixklasse matrix<br />
Beg<strong>in</strong>nen wir damit, die wichtigsten Elemente dieser Matrixklasse anzuführen. Der E<strong>in</strong>fachheit<br />
halber wollen wir nur reelle <strong>Matrizen</strong> (Variablentyp double) der Größe n × n<br />
(Ordnung n) betrachten.<br />
Zuerst soll es möglich se<strong>in</strong>:<br />
• e<strong>in</strong>e Matrix e<strong>in</strong>er bestimmten Größe zu erstellen (d.h., genügend Speicherplatz für<br />
die n × n Matrixelemente zu reservieren);<br />
• auf die Matrixelemente zuzugreifen (d.h., sie zu lesen oder zu verändern).<br />
E<strong>in</strong> typisches Programm könnte dann von der Form<br />
7
8 KAPITEL 2. MATRIZEN IN <strong>C++</strong><br />
1 matrix a(3); // 3 x 3 Matrix<br />
2<br />
3 a(1,2)=1;<br />
4 a [0][1]=2;<br />
se<strong>in</strong>. In der ersten Zeile wird e<strong>in</strong>e 3×3 Matrix deklariert, wobei an dieser Stelle genügend<br />
Speicherplatz reserviert werden soll. Zeile 3 zeigt e<strong>in</strong>en Zugriff auf das Matrixelement a12.<br />
Weiters wäre es wünschenswert, dass wir auch <strong>in</strong> “klassischer” C-Form a[ i ][ j ] auf das<br />
Matrixelement aij zugreifen können.<br />
Wir beg<strong>in</strong>nen nun mit der Implementierung der Matrixklasse. Zuerst öffnen wir zwei Dateien<br />
matrix.h und matrix.cc. matrix.h ist die header-Datei, <strong>in</strong> der die Klassendef<strong>in</strong>ition<br />
(sowie kürzere Klassenfunktionen) stehen sollen. In matrix.cc werden die längeren<br />
Klassenfunktionen ausprogrammiert.<br />
Im Folgenden soll angenommen werden, dass Sie die Grundkonzepte von Klassen <strong>in</strong> <strong>C++</strong><br />
kennen und wissen, wie Sie mehrere Dateien kompilieren und b<strong>in</strong>den können (z.B. unter<br />
Verwendung von Makefile).<br />
2.1.1 Grundelemente der Matrixklasse<br />
Beg<strong>in</strong>nen wir mit der Klasse matrix.h. Das Gerüst ist von der Form<br />
1 #ifndef matrix h<br />
2 #def<strong>in</strong>e matrix h<br />
3<br />
4 class matrix {<br />
5 public:<br />
6 matrix(<strong>in</strong>t n=0);<br />
7 ˜matrix();<br />
8<br />
9 double& operator() (<strong>in</strong>t i, <strong>in</strong>t j);<br />
10 double ∗operator[] (<strong>in</strong>t i);<br />
11<br />
12 <strong>in</strong>t size () const {return n; }<br />
13<br />
14 private:<br />
15 <strong>in</strong>t n;<br />
16 double ∗a;<br />
17 };<br />
18<br />
19 #endif<br />
Die Bedeutung der Zeilen ist wie folgt:
2.1. DIE MATRIXKLASSE matrix 9<br />
Zeilen 1, 2, 19. Die übliche Struktur von header files, die verh<strong>in</strong>dert, das e<strong>in</strong>e header-<br />
Datei öfters als e<strong>in</strong>mal e<strong>in</strong>gelesen wird.<br />
Zeile 6. Konstruktor für die Matrix der Ordnung n.<br />
Zeile 7. Destruktor.<br />
Zeile 9. Zugriff auf Matrixelemente über a(i , j).<br />
Zeile 10. Zugriff auf Matrixelemente über a[ i ][ j ].<br />
Zeile 12. Funktion, die die Ordnung n der Matrix liefert.<br />
Zeile 15. Ordnung n der Matrix (d.h., n × n-Matrix).<br />
Zeile 16. Zeiger auf Matrixelemente.<br />
2.1.2 Speicherung der Matrixelemente<br />
Betrachten wir die Matrix<br />
A =<br />
⎛<br />
⎜<br />
⎝<br />
a00 a01 a02 a03<br />
a10 a11 a12 a13<br />
a20 a21 a22 a23<br />
a30 a31 a32 a33<br />
wobei wir von der C-Notation Gebrauch gemacht haben, dass Indizes mit 0 beg<strong>in</strong>nen.<br />
Wie können wir diese 4 × 4 Matrix abspeichern?<br />
E<strong>in</strong>e Möglichkeit besteht dar<strong>in</strong>, die Matrixelemente h<strong>in</strong>tere<strong>in</strong>ander <strong>in</strong> e<strong>in</strong>em Vektor der<br />
Länge n × n = 16 zu speichern<br />
{a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33}.<br />
Konstruktor und Destruktor.<br />
Im Konstruktor der Matrixklasse müssen wir somit e<strong>in</strong>en Speicherplatz der Länge n × n<br />
reservieren, der dann vom Destruktor wieder freigegeben wird<br />
6 matrix(<strong>in</strong>t n=0) : n( n ) { if (n) a=new double[n∗n]; }<br />
7 ˜matrix() { if (n) delete[] a; }<br />
operator().<br />
Wie greifen wir auf das Matrixelement aij zu? Offensichtlich über a[ i∗n+j].<br />
Wir können somit die Klassenfunktion operator() implementieren. Weil sie so kurz ist,<br />
können wir sie gleich nach matrix.h schreiben. Zuerst fügen wir vor der Klassendef<strong>in</strong>ition<br />
die Zeile<br />
⎞<br />
⎟<br />
⎠
10 KAPITEL 2. MATRIZEN IN <strong>C++</strong><br />
#def<strong>in</strong>e <strong>in</strong>dex(i,j) (( i)∗n+(j))<br />
e<strong>in</strong>. Und weiters<br />
9 double& operator() (<strong>in</strong>t i, <strong>in</strong>t j) { return a[<strong>in</strong>dex(i,j )]; }<br />
operator[].<br />
Schließlich wollen wir noch den normalen Indexoperator implementieren. Wie gehen wir<br />
vor?<br />
Wenn wir wollen, dass wir mit Hilfe von a[ i ][ j ] auf das Matrixelement aij e<strong>in</strong>er Matrix<br />
A zugreifen können, so muss operator[i] e<strong>in</strong>en Zeiger liefern. Betrachten wir nochmals<br />
die Aufteilung des Speicherplatzes<br />
a a+n a+2∗n a+3∗n<br />
↓ ↓ ↓ ↓<br />
{ a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33 }<br />
Wir erkennen, dass der Zeiger a+i∗n auf den Beg<strong>in</strong>n der i-te Zeile der Matrix A zeigt.<br />
Durch (a+i∗n)[j] können wir also auf aij zugreifen.<br />
Somit können wir die Klassenfunktion<br />
10 double& operator[] (<strong>in</strong>t i) { return a+i∗n; }<br />
vervollständigen.<br />
Aufgabe 2.1— Vervollständigen Sie die Matrixklasse. Schreiben Sie e<strong>in</strong> Programm,<br />
das e<strong>in</strong>e 3 × 3-Matrix deklariert und sie mit den Matrixelementen<br />
besetzt.<br />
⎛<br />
A = ⎝<br />
1 2 0<br />
2 4 3<br />
7 2 1<br />
Aufgabe 2.2— Betrachten Sie die folgende Funktion:<br />
1 void f(const matrix& a)<br />
2 {<br />
3 cout
2.2. ALGORITHMEN 11<br />
2.2 Algorithmen<br />
In diesem Abschnitt wollen wir e<strong>in</strong>ige nützliche Funktionen der <strong>C++</strong>-Standardbibliothek<br />
besprechen, die <strong>in</strong> den header-Dateien<br />
#<strong>in</strong>clude <br />
#<strong>in</strong>clude <br />
def<strong>in</strong>iert s<strong>in</strong>d.<br />
Ausgansgpunkt s<strong>in</strong>d die sogenannten Iteratoren. Das s<strong>in</strong>d beliebige Objekte, für die gelten<br />
soll, dass sie:<br />
• über den Zuordnungsoperator operator= auf e<strong>in</strong>en bestimmten Anfangswert gesetzt<br />
werden können;<br />
• über die Vergleichsoperatoren operator== und operator!= mit e<strong>in</strong>em anderen Iterator<br />
verglichen werden können;<br />
• über operator++ <strong>in</strong>krementiert werden können;<br />
• über operator∗ e<strong>in</strong>en Wert liefern.<br />
Offensichtlich s<strong>in</strong>d Zeiger Iteratoren, da alle Anforderungen<br />
1 double a[10],∗p;<br />
2<br />
3 for (p=a; p!=a+n; p++) ∗p=0;<br />
erfüllt s<strong>in</strong>d (es gibt allerd<strong>in</strong>gs noch e<strong>in</strong>e Reihe weiterer Iteratoren, die Zugriff auf verschiedenst<br />
Objekte —z.B. Conta<strong>in</strong>er— erlauben).<br />
Was können wir mit diesen Iteratoren machen? All die kle<strong>in</strong>en Aufgaben, die wir im<br />
Programmieralltag stets benötigen. In dem obigen Beispiel hätten wir auch<br />
1 fill (a,a+n,0.);<br />
schreiben können unter Benutzung der Funktion<br />
1 copy(InputIter first , InputIter last , const double& value);<br />
Hierbei wird der Speicherplatz <strong>in</strong> dem Bereich first bis last mit dem Wert value belegt.<br />
Zwei D<strong>in</strong>ge s<strong>in</strong>d zu beachten, die allgeme<strong>in</strong> für alle Funktionen von numeric und algorithm<br />
gelten:<br />
• es erfolgt ke<strong>in</strong>e Überprüfung, ob der Speicherplatz, auf den das Programm zugreift,<br />
zuvor reserviert wurde;
12 KAPITEL 2. MATRIZEN IN <strong>C++</strong><br />
• der letzte Iterator last zeigt auf das Element, das unmittelbar nach dem letzten<br />
Element steht, auf das zugegriffen werden soll (<strong>in</strong> dem obigen Beispiel a+n, d.h.<br />
das Element a[n]).<br />
Die am häufigsten verwendeten Funktionen s<strong>in</strong>d<br />
1 // aus numeric<br />
2 T accumulate(InputIter first , InputIter last , T <strong>in</strong>it );<br />
3 T <strong>in</strong>ner product(InputIter first1 , InputIter last1 , InputIter first2 , T <strong>in</strong>it );<br />
4<br />
5 // aus algorithm<br />
6 count(InputIter first , InputIter last , const T& value);<br />
7 copy(InputIter first , InputIter last , OutputIter result );<br />
8 bool equal(InputIter first1 , InputIter last1 , InputIter first2 );<br />
9 fill (OutputIter first , OutputIter last , const T& value);<br />
10 InputIter max element(InputIter first , InputIter last );<br />
11 InputIter max element(InputIter first , InputIter last );<br />
12 replace(ForwardIter first , ForwardIter last , const T& old value, const T& new value);<br />
13 reverse(ForwardIter first , ForwardIter last );<br />
14 sort(ForwardIter first , ForwardIter last );<br />
Hierbei bezeichnet T den Variablentyp (z.B. double), InputIter ist e<strong>in</strong> Iterator der nur<br />
gelesen wird, OutputIter e<strong>in</strong>er der verändert wird, und ForwardIter e<strong>in</strong>er der gelesen und<br />
verändert wird.<br />
Die Wirkung der Funktionen ist:<br />
accumulate: summiert den Inhalt von InputIter zu <strong>in</strong>it ;<br />
<strong>in</strong>ner product: bildet das <strong>in</strong>nere Produkt ab von zwei Vektoren a und b;<br />
count: zählt, wie oft value vorkommt;<br />
copy: kopiert e<strong>in</strong>en Bereich <strong>in</strong> e<strong>in</strong>en anderen;<br />
equal: überprüft zwei Bereiche auf Gleichheit;<br />
fill: kopiert den Wert value <strong>in</strong> alle Elemente e<strong>in</strong>es Bereiches;<br />
max element: liefert den Iterator auf das größte Element;<br />
m<strong>in</strong> element: liefert den Iterator auf das kle<strong>in</strong>ste Element;<br />
replace: ersetzt <strong>in</strong> e<strong>in</strong>em Bereich old value durch new value;<br />
reverse: kehrt den Inhalt e<strong>in</strong>es Bereiches um;<br />
sort: sortiert den Inhalt e<strong>in</strong>es Bereiches <strong>in</strong> aufsteigender Reihenfolge.<br />
Wann immer möglich, wollen wir <strong>in</strong> den folgenden Programmen von diesen Funktionen<br />
Gebrauch machen.
2.3. DIE VOLLSTÄNDIGE MATRIXKLASSE 13<br />
Aufgabe 2.3— Betrachten Sie das Programm<br />
1 #<strong>in</strong>clude <br />
2 #<strong>in</strong>clude <br />
3<br />
4 void ma<strong>in</strong>()<br />
5 {<br />
6 <strong>in</strong>t a [6]={0,1,2,0,3,2};<br />
7 <strong>in</strong>t b [6];<br />
8 }<br />
Benutzen Sie die zuvor e<strong>in</strong>geführten Funktionen und<br />
– kopieren Sie den Inhalt von a nach b;<br />
– überprüfen Sie die beiden Arrays auf Gleichheit;<br />
– drehen Sie die Reihenfolge von b um;<br />
– bestimmen Sie die Summe 5 i=0 ai;<br />
– bestimmen Sie das größte Element von b;<br />
– sortieren Sie a;<br />
– setzen Sie alle Elemente von b auf 0.<br />
2.3 Die vollständige Matrixklasse<br />
Damit wir die Funktionen von numeric und algorithm auch für die Matrixklasse verwenden<br />
können, führen wir zwei weitere Klassenfunktionen<br />
1 double ∗beg<strong>in</strong>() const { return a; }<br />
2 double ∗end() const { return a+n∗n; }<br />
e<strong>in</strong>, die auf den Beg<strong>in</strong>n bzw. das Ende des Matrixspeicherplatzes zeigen.<br />
Mit Hilfe dieser zusätzlichen Funktionen, können wir dann z.B. alle Matrixelement auf<br />
e<strong>in</strong>en bestimmten Wert setzen<br />
1 matrix a(5);<br />
2<br />
3 copy(a.beg<strong>in</strong>(),a.end (),0);<br />
Aufgabe 2.4— Schreiben Sie e<strong>in</strong> Programm, das den Inhalt e<strong>in</strong>er Matrix A<br />
<strong>in</strong> e<strong>in</strong>e zweite Matrix B kopiert.
14 KAPITEL 2. MATRIZEN IN <strong>C++</strong><br />
Für die häufigsten Matrixverknüpfungen —wie Zuordnung A = B, Addition A + B,<br />
Subtraktion A−B oder Multiplikation A∗B (entsprechend den Regeln der Matrixmultiplikation)—<br />
ist es vernünftig, die entsprechenden Klassenoperatoren zu implementieren.<br />
E<strong>in</strong>e solche Implementierung ist <strong>in</strong> den Dateien<br />
http://physik.uni-graz.at/~uxh/l<strong>in</strong>eare-algebra/matrix.h<br />
http://physik.uni-graz.at/~uxh/l<strong>in</strong>eare-algebra/matrix.cc<br />
zu f<strong>in</strong>den. Die vollständige Klasse be<strong>in</strong>haltet folgende Funktionen:<br />
1 class matrix {<br />
2<br />
3 public:<br />
4 matrix(<strong>in</strong>t n);<br />
5 matrix(const matrix& m);<br />
6 matrix(const char ∗filename);<br />
7 ˜matrix();<br />
8<br />
9 double ∗opertor[] (<strong>in</strong>t i );<br />
10 const double ∗operator[] (<strong>in</strong>t i) const;<br />
11 double& operator() (<strong>in</strong>t i, <strong>in</strong>t j);<br />
12 double operator() (<strong>in</strong>t i, <strong>in</strong>t j ) const;<br />
13<br />
14 const matrix& operator= (const matrix& m);<br />
15<br />
16 const matrix& operator+= (const matrix& m);<br />
17 const matrix& operator−= (const matrix& m);<br />
18 const matrix& operator∗= (const matrix& m);<br />
19<br />
20 matrix operator+ (const matrix& m) const;<br />
21 matrix operator− (const matrix& m) const;<br />
22 matrix operator∗ (const matrix& m) const;<br />
23<br />
24 double ∗beg<strong>in</strong>() const;<br />
25 double ∗end() const;<br />
26<br />
27 <strong>in</strong>t size ();<br />
28 void write();<br />
29 };<br />
Die Wirkung der Funktionen ist wie folgt:<br />
Zeile 4. Standardkonstruktor.<br />
Zeile 5. Konstruktor, wobei die Matrix dieselbe Ordnung n und Matrixelemente wie m<br />
erhält.
2.3. DIE VOLLSTÄNDIGE MATRIXKLASSE 15<br />
Zeile 6. Konstruktor, bei dem die Ordnung n und Matrixelemente aus e<strong>in</strong>er Datei<br />
filename e<strong>in</strong>gelesen werden. Der erste E<strong>in</strong>trag <strong>in</strong> der Datei soll die Ordnung n<br />
se<strong>in</strong>, gefolgt von den n × n Matrixelementen. Beispielsweise erhält mit der Datei<br />
matrix.dat<br />
1 3<br />
2 2 3 4<br />
3 1 4 0<br />
4 8 7 1<br />
die Matrix a <strong>in</strong> dem Programm<br />
1 matrix a(”matrix.dat”);<br />
die Form<br />
⎛<br />
⎝<br />
2 3 4<br />
1 4 0<br />
8 7 1<br />
⎞<br />
⎠.<br />
Zeilen 9–12. Zugriff auf Matrixelement über a(i , j) und a[ i ][ j ].<br />
Zeile 14. Zuordnungsoperator, der es erlaubt, Ordnung n und Inhalt e<strong>in</strong>er Matrix m <strong>in</strong><br />
die aktuelle Matrix zu kopieren.<br />
Zeilen 16–22. Addition, Subtraktion und Multiplikation (entsprechend cij = <br />
k aikbkj)<br />
von <strong>Matrizen</strong>; z.B.<br />
1 matrix a(3), b(3), c (3);<br />
2<br />
3 a+=b; c=a+b;<br />
4 a−=b; c=a−b;<br />
5 a∗=b; c=a∗b;<br />
Bei all diesen Operationen wird überprüft, ob die <strong>Matrizen</strong> dieselbe Ordnung n<br />
haben.<br />
Zeilen 24, 25. Funktionen, die den Beg<strong>in</strong>n beg<strong>in</strong>() bzw. das Ende end() des Speicherplatzes<br />
liefern, an dem die Matrixelemente abgespeichert s<strong>in</strong>d.<br />
Zeile 27. Ordnung n der Matrix.<br />
Zeile 28. E<strong>in</strong>fache Ausgaberout<strong>in</strong>e für Matrix.<br />
Aufgabe 2.5— Erstellen Sie zwei Dateien matrix.dat und diag.dat mit<br />
Inhalt<br />
⎛<br />
⎝<br />
2 3 4<br />
1 4 0<br />
8 7 1<br />
⎞<br />
⎠ und<br />
⎛<br />
⎝<br />
1 0 0<br />
0 1 0<br />
0 0 1<br />
⎞<br />
⎠:<br />
– lesen Sie die <strong>Matrizen</strong> m und e <strong>in</strong> e<strong>in</strong>em Programm e<strong>in</strong>, und geben Sie<br />
ihren Inhalt am Bildschirm aus;<br />
– multiplizieren Sie die beiden <strong>Matrizen</strong>; zeigen Sie, dass gilt m·e = e·m;<br />
– addieren Sie m und e.
16 KAPITEL 2. MATRIZEN IN <strong>C++</strong><br />
2.4 <strong>Matrizen</strong> <strong>in</strong> Fortran90<br />
Es gibt e<strong>in</strong>e Reihe anderer Programmiersprachen, <strong>in</strong> denen die wichtigsten Matrixoperationen<br />
bereits implemetiert s<strong>in</strong>d. Besonders e<strong>in</strong>fach ist das Rechnen mit <strong>Matrizen</strong> <strong>in</strong><br />
fortran90, auf das wir hier kurz e<strong>in</strong>gehen wollen.<br />
Beispielsweise werden <strong>in</strong> dem Programm<br />
1 program ma<strong>in</strong><br />
2<br />
3 real, dimension(0:2,0:2) :: a, b, c<br />
4<br />
5 a=0<br />
6 b=1<br />
7<br />
8 a(0,0)=1<br />
9<br />
10 c=matmul(a,b)<br />
11<br />
12 pr<strong>in</strong>t∗,c<br />
13<br />
14 end program<br />
<strong>in</strong> der Zeile 3 die <strong>Matrizen</strong> a,b,c der Größe 3 × 3 def<strong>in</strong>iert (Indizes von 0 bis 2), deren<br />
Matrixelemente <strong>in</strong> Zeilen 5 und 6 auf e<strong>in</strong>e Konstante gesetzt werden; <strong>in</strong> Zeile 8 wird<br />
das Element a00 gesetzt; <strong>in</strong> Zeile 10 erfolgt e<strong>in</strong>e Matrixmultiplikation von a und b; und<br />
schließlich wird die Matrix c <strong>in</strong> Zeile 12 ausgegeben.<br />
Wir sehen, dass all die Funktionen (und noch e<strong>in</strong>ige mehr), die wir <strong>in</strong> diesem <strong>Kapitel</strong><br />
programmiert haben, <strong>in</strong> fortran90 bereits vorhanden s<strong>in</strong>d. Dies ist auch e<strong>in</strong>er der<br />
Gründe, weshalb sich die Programmiersprache fortran <strong>in</strong> der Physik großer Beliebtheit<br />
erfreut. Dennoch wird sich zeigen, dass wir mit der selbst erstellten Matrixklasse matrix<br />
alle Probleme vollständig befriedigend lösen können.