Zeiger und Arrays
In den ersten Beitrag in dieser Reihe, sprachen wir ein wenig über Zeiger . In der zweiten, sprechen wir von Referenzen . Heute erkunden wir die intime Beziehung (ui!) zwischen Zeigern und Vektoren (oder Matrizen).
Hinweis Arrays
Ein Array oder eine Matrix oder eine Anordnung, es ist eine mathematische Abstraktion verwendet, um eine Reihe von homogenen Daten repräsentieren, dh die gleichen Typs (int, float, etc.). Diese Abstraktion ist in tabellarischer Form mit Zeilen und Spalten organisiert. Jedes Element im Array verfügt über eine einzigartige Koordinaten (Zeile und Spalte), so dass ein gegebenes Element E (i, j) das einzige Element in der "Zeile i", "Spalte j" darstellt.
Die Array-Deklaration lautet wie folgt:
/ / One-dimensionale Matrizen oder Vektoren 10 ] ; // 10 elementos, do 0 ao 9 Erstausbildung int [10] / / 10 Elemente, 0 bis 9 23 ] ; // 23 elementos, do 0 ao 22 cvet char [23] / / 23 Elemente, von 0 bis 22 / / Zwei-dimensionale Matrizen 2 ] [ 3 ] ; // 2 linhas (0 a 1) e 3 colunas (0 a 2) IMAT int [2] [3] / / 2 Zeilen (0-1) und drei Spalten (0-2) 10 ] [ 2 ] ; // 10 linhas (0 a 9) e 2 colunas (0 a 1) DMAT double [10] [2] / / 10 Zeilen (0-9) und zwei Spalten (0-1)
Jedes Element des Arrays ist unabhängig von den anderen und kann als die folgende Syntax zugegriffen werden:
10 ] ; int imat [ 3 ] [ 4 ] ; // Alterando o quarto elemento do vetor ivet. // Lembre-se que começa-se a contar a partir do elemento zero ivet [ 3 ] = 13 ; // Lendo o segundo elemento do vetor ivet int num = ivet [ 1 ] ; // Alterando o elemento da primeira linha, segunda coluna de imat imat [ 0 ] [ 1 ] = 42 ; // Lendo o elemento na terceira linha, quarta coluna de imat int foo = imat [ 2 ] [ 3 ] ; Erstausbildung int [10]; IMAT int [3] [4] / / Ändern der vierte Element der beruflichen Erstausbildung Vektor / / Remember, dass man das Zählen von Element beginnt Null Erstausbildung [3] = 13 / /. Lesen das zweite Element des Arrays int a = Erstausbildung Erstausbildung [1] / / Ändern des Elements der ersten Zeile, zweite Spalte IMAT IMAT [0] [1] = 42 / / Lesen des Elements in der dritten Zeile, vierte Spalte IMAT IMAT int foo = [2] [3];
In diesem Beitrag werden wir nicht zu diskutieren Hintergrund Matrizen, werden wir nur untersuchen die Beziehung zwischen Arrays und Zeiger in eine ziemlich intuitiv.
Die Größen der Matrizen
Da ein Array ist eine Abstraktion, die mehrere Werte des gleichen Typs aufnehmen kann, die Größe, was es? Wie viel Platz im Speicher belegt?
Betrachten Sie den folgenden Code:
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 10 ] ; Erstausbildung int [10]; 13 ] ; cvet char [13]; 20 ] ; dvet double [20]; 3 ] [ 4 ] ; CMAT char [3] [4]; 5 ] [ 4 ] ; IMAT int [5] [4]; "sizeof(int) = " << sizeof ( int ) << endl ; cout <<"sizeof (int) =" <<sizeof (int) <<endl; "sizeof(char) = " << sizeof ( char ) << endl ; cout <<"sizeof (char) =" <<sizeof (char) <<endl; "sizeof(double) = " << sizeof ( double ) << endl ; cout <<"sizeof (double) =" <<sizeof (double) <<endl; "sizeof(ivet) = " << sizeof ( ivet ) << endl ; cout <<"sizeof (Erstausbildung) =" <<sizeof (Erstausbildung) <<endl; "sizeof(cvet) = " << sizeof ( cvet ) << endl ; cout <<"sizeof (cvet) =" <<sizeof (cvet) <<endl; "sizeof(dvet) = " << sizeof ( dvet ) << endl ; cout <<"sizeof (dvet) =" <<sizeof (dvet) <<endl; "sizeof(cmat) = " << sizeof ( cmat ) << endl ; cout <<"sizeof (CMAT) =" <<sizeof (CMAT) <<endl; "sizeof(imat) = " << sizeof ( imat ) << endl ; cout <<"sizeof (IMAT) =" <<sizeof (IMAT) <<endl; |
Das Ergebnis ist recht vernünftig. Der Raum mit einer Matrix besetzt ist gleich der Anzahl der Elemente von der Größe der Element-Typ multipliziert (Zeilen x Spalten x sizeof (type)). Nun, wenn jedes Element unabhängig ist, ist davon auszugehen ist, dass jeder einen separaten Platz im Speicher belegt, sonst überschreibt die andere Element. So ist, dass jedes Element seinen eigenen Speicher-Adresse hat?
Arrays und Speicher-Adressen
Der Einfachheit halber werden wir dann analysieren die möglichen Adressen ein Array von Zeichen, deren Größe nur ein 1 Byte angegeben:
15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 5 ; const int max = 5; max ] = { 'A' , 'B' , 'C' , 'D' , 'E' } ; cvet char [max] = {'A', 'B', 'C', 'D', 'E'}; / / Anzeige der Inhalte, Daten-Werte und Adressen. "Índice \t Valor \t Endereço do elemento \n " ) ; printf ("index \ t Wert \ t Element address \ n"); int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) { "%d \t %c \t %p \n " , i, cvet [ i ] , & cvet [ i ] ) ; printf ("% d \ t% c \ t% p \ n", i, cvet [i], & Weiterbildung [i]); } / / Anzeige der Adresse des Arrays "Endereço da matriz: %p \n " , & cvet ) ; printf ("Adresse des Arrays:% p \ n", & Weiterbildung); / / Anzeige der Adresse des Arrays wieder "Endereço da matriz: %p \n " , cvet ) ; printf ("Adresse des Arrays:% p \ n", Weiterbildung); |
Die Adressen der Elemente sind aufeinander aufbauend, dh jedes Element ist neben dem ehemaligen gespeichert. Zusätzlich gibt es noch zwei weitere interessante Fakten:
- Die Adresse des array (& Weiterbildung), in Zeile 25 gezeigt, ist das gleiche wie das erste Element des Arrays;
- Cvet Die Variable selbst als Zeiger interpretiert werden kann, wie in Zeile 28 gezeigt;
In C + + ist eine gemeinsame Array einen zusammenhängenden Block von Speicher, dessen Name interpretiert werden kann (cast) als Zeiger, der auf sein erstes Element. Zusätzlich ist es ein gültiger Zeiger auf ein Array, solange der Zeiger auf das Ziel der gleichen Art wie die Art der Array-Elemente ist der Punkt. Während atrubuição eines Arrays auf einen Zeiger, erzeugt der Compiler eine implizite Typumwandlung. Das Ziel Zeiger als einen Zeiger auf den Speicherbereich von der Array besetzt interpretiert werden.
Eine der Folgen nicht so offensichtlich ist, dass implizit in der Besetzung, ist die Information verloren, dass das Gebiet eines Speicher-Arrays wurde. So werden die Informationen über die Größe des Arrays verloren. Aus der Sicht des Zeigers, ist es an den Anfang eines beliebigen Block des Speichers, der eine Nummer zu willkürlichen zeigt. Ausfahrt und fahren für ein Array zu einem Zeiger bedeutet, sich von einer restriktiveren und mehr abstrakte High-Level-Abstraktion für eine weniger restriktive und unteren Ebene.
Auf der anderen Seite versuchen, einen Zeiger auf ein Array erzeugt einen Compiler-Fehler für inkompatible Typen zuordnen. Ein Array ist eine Speicher-Block n (Byte) als Zeiger hat nur eine bestimmte, eine Adresse. Der Compiler hat keine Möglichkeit, vorher zu wissen, ob ein Zeiger auf einer Fläche von 1, 2 oder 200 Byte Punkte.
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 300 ; const int max = 300; max ] ; cvet char [max]; pc = 0 ; char * pc = 0; " \n Antes da atribuição \n " ) ; printf ("\ n Bevor die Zuweisung \ n"); "cvet = %p \n " , cvet ) ; printf ("cvet =% p \ n", Weiterbildung); "pc = %p \n " , pc ) ; printf ("pc =% p \ n", pc); "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ; printf ("sizeof (cvet) =% lu \ n", sizeof (cvet)); "sizeof(pc) = %lu \n " , sizeof ( pc ) ) ; printf ("sizeof (cp) =% lu \ n", sizeof (cp)); pc = cvet; " \n Depois da atribuição \n " ) ; printf ("\ n Nach der Abtretung \ n"); "cvet = %p \n " , cvet ) ; printf ("cvet =% p \ n", Weiterbildung); "pc = %p \n " , pc ) ; printf ("pc =% p \ n", pc); "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ; printf ("sizeof (cvet) =% lu \ n", sizeof (cvet)); "sizeof(pc) = %lu \n " , sizeof ( pc ) ) ; printf ("sizeof (cp) =% lu \ n", sizeof (cp)); |
Beachten Sie, dass vor der Zuweisung (Zeile 25) den Zeiger pc. Null ist, so war es initialisiert. Da die Größen zeigen, dass das Array nur 300 Byte und der Zeiger 8 (meine Maschine ist ein amd 64) hat. Nach der Zuweisung beide das "point" auf den gleichen Speicherbereich, aber die Größen ändern sich nicht. Es war eine implizite Umwandlung von char [300], um char *, und in diesem Spiel der Zeiger pc kann nicht wissen, die Größe des Speicherbereichs, auf die er verweist. Aber das Array weiterhin cvet genau zu wissen, was er ist, ohne existenzielle Krise.
Zeigerarithmetik - Vulgar, so what?
Warum, wenn ich weiß, dass die Daten in einer Matrix nebeneinander angeordnet sind, kann ich einen Zeiger, der auf die nächste Adresse Sprung und den Zugriff auf das nächste Element. Es heißt Zeigerarithmetik.
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 | ivet [ max ] = { 1 , 2 , 3 , 4 , 5 , 6 } ; int * pi = ivet ; for ( int i = 0 ; i < max ; i ++ ) { printf ( "%p = %d \n " , pi, * pi ++ ) ; } printf ( " \n " ) ; // Em duas Jetzt mit ganzen beruflichen Erstausbildung int [max] = {1, 2, 3, 4, 5, 6}; int * pi = beruflichen Erstausbildung; for (int i = 0; i <max; i + +) {printf ("% p =% d \ n ", pi, pi * + +);} printf (" \ n ") / / In zwei |
In Zeile 18 den Zeiger pc ist nun das Array cvet Punkt, und damit auf sein erstes Element, das Zeichen 'B'. In Zeile 21 der Inhalt des PC, der die Adresse, wo es das Zeichen 'B' gespeichert wurde, wird also dann de-referenziert erhöht. Im ersten Durchgang der Wert von i ist Null, so ist der Wert für die Dereferenzierung 'B'. In den folgenden Schritten wird die nächste Adresse mit anderen Charakteren in der ursprünglichen Array gespeichert de-referenziert. Es ist mehr oder weniger, was der Compiler intern macht, wenn man die Syntax cvet [i] verwenden. Die Abstraktion von Array bietet Ihnen eine freundliche Art des Umgangs mit zusammenhängende Bereiche des Speichers, * (pc + i).
Aber wenn das Array Abstraktion ist einfacher zu bedienen, dass Pointer-Arithmetik?
Eine Antwort darauf ist die Leitung 26. Sie tut das gleiche, was die Linie 21, aber ein wenig schneller. In der Syntax der Linie 21, oder ähnlich, die Array-Syntax kann der Zugriff auf bestimmte Daten in sehr grobe Befehle zusammengefasst werden:
- Nehmen Sie die Basisadresse des Arrays;
- Hinzufügen, um den Wert des Index-Adresse;
- De-reference diese neue Adresse;
Bereits Zeigerarithmetik sieht wie folgt aus:
- De-reference diese Adresse;
Der Befehl erhöhen (oder hat mit Adressen getan) nicht sagen, weil es ein Teil der Schleife ist, obwohl i + +; ist schneller als a = b + c,. Nun stell dir vor dieser kleinen Gewinn von 66% angewandt, um einen Datenbereich von 1 MB. Es wird über 2 Millionen Befehle weniger werden!
Die Technik der Verwendung eines Zeigers auf einen beliebigen Bereich des Speichers zu manipulieren ist in der Regel in Low-Level-Programmierung (am nächsten an der Maschine), Manipulation von gebrauchten Puffer und Streicher, unter anderem schmutzigen Tricks. In den Eingeweiden von Computern, Operationen, die große Bereiche des Arbeitsspeichers fegen, sind oft mit Pointer-Arithmetik durchgeführt. Auf dieser Ebene Darwin herrscht und nur die vorbereitete wird überleben. Von hier aus beginnt zu geben die Sprache eine Macht, die nur die reinen Herzens verstehen können.
Eine wichtige Beobachtung ist, dass zwischen den Zeilen 31 und 38 ist mit der ganzen Erfahrung wiederholt. Beachten Sie, dass die Zahlen 4 Bytes, die automatisch erhöht werden 4 von 4 Byte, nicht 1 zu 1 aus, dh die Schrittweite automatisch auf sizeof (type) berechnet. Inkrementieren eines Zeigers bedeutet, in den nächsten Bereich des Speichers ähnlich wie die eigentlichen Daten, nicht nur die nächste Adresse zugreifen. Da die Größe eines char ist ein Byte, wenn incremetamos einen Zeiger auf char, bewegen sich nur ein Byte. Wenn Sie einen Zeiger inkrementieren zu verdoppeln, werden wir uns 8 Bytes, und so weiter.
Eine weitere Beobachtung ist, dass ein zweidimensionales Array kann "linearisiert" werden, wie in den Zeilen 40 bis 52 dargestellt. Dies ist nützlich, wenn zutreffend, zur besseren Nutzung der Prozessor-Cache, zum Beispiel zu machen.
void * pansexuell von Zeigern
Früher habe ich gesagt, dass war nur möglich, um ein Array zu einem Zeiger, der auf den gleichen Typ wie die Array-Daten ging zuweisen. Ich habe gelogen geradezu! Der Grund dafür ist, dass für jemanden, der hat die Post vor diesem Thema, es sicherer ist, zu glauben, dass es nicht
!
Es gibt zwei Ausnahmen von der Regel. Die erste ist, wenn es eine explizite Typumwandlung und dem Ziel-Zeiger "denkt" das ist die richtige Art zeigt. Ein Beispiel ist in Zeile 44 der vorherigen Code.
Der zweite ist der Fall von Zeigern auf void. Ein void-Zeiger ist ein Zeiger, keine Anforderungen an die Art der Daten, die in den Speicherbereich, auf die es sich macht. Es ist ein Zeiger auf einen allgemeinen Bereich des Gedächtnisses, etwas sehr niedrigem Niveau.
Zur Verwendung eines Daten, auf die ein Zeiger auf void, vor de-referenzieren, müssen Sie eine explizite Umwandlung in eine gültige Art, da es ein int *, int, und verwiesen auf eine char * ist, um de-referenziert char, erraten, was es zu einem void *-referenziert wird?
15 16 17 18 19 20 21 22 23 24 25 26 27 | 6 ; const int max = 6; max ] = { 'B' , 'L' , 'A' , 'B' , 'O' , 'S' } ; cvet char [max] = {'B', 'L', 'A', 'B', 'O', 'S'}; pv = cvet ; void * pv = cvet; / / Compile-Fehler. / / * Pv / / Das Tal sizeof (void)? int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) { "%c" , * ( ( ( char * ) pv ) + i ) ) ; printf ("% c", * (((char *) pv) + i)); } " \n " ) ; printf ("\ n"); |
Zeiger auf void verwendet werden, wenn man zu einer allgemeinen Bereich des Speichers ohne Kontrolle / Wissen über die Art der Daten, dass dieser Bereich enthält, oder Funktionen, die keine Annahmen über die Typen der Parameter kann Punkt braucht, wie die API lib pthreads (link beliebig).
Ende
Je mehr wir in die Themen der Zeiger zu vertiefen, sind wir näher an der Maschine. Ein Großteil der Macht der C-und C + + kommt von dort, und ein Großteil der Schuld zu. Die Komplexität erhöht und die Risiken. Für viele darin liegt der Spaß!
Links
- arrays.zip (alle Quellen der Post);
- Zeiger auf cplusplus.com
- Pointers in blabos.org
- Referenzen in blabos.org
- The Guilty
Kommentare
- Leandro Melo http://0xc0de.wordpress.com
- Francisco-bruno-luisa

