Zeiger und Arrays
In den ersten Beitrag dieser Serie, wir sprachen ein wenig über Zeiger . In der zweiten, sprechen wir über Referenzen . Heute diskutieren wir die innige Beziehung (ui!) Zwischen Zeigern und Arrays (oder Arrays).
Hinweis Arrays
Ein Array oder Matrix oder eine Anordnung, ist eine mathematische Abstraktion verwendet, um eine homogene Gruppe von Daten, dh die gleichen Typs (int, float, etc.) darzustellen. Diese Abstraktion ist in Tabellenform organisiert, mit Zeilen und Spalten. Jedes Element in der Matrix hat einzigartige Koordinaten (Zeile und Spalte), so dass ein gegebenes Element E (i, j) stellt die einzige Element in der "Linie i ',' Spalte j".
Die Syntax zur Deklaration Arrays ist wie folgt:
/ / Eindimensionale Arrays oder Vektoren 10 ] ; // 10 elementos, do 0 ao 9 Ivet int [10] / / 10 Elemente, 0-9 23 ] ; // 23 elementos, do 0 ao 22 Cvet char [23] / / 23 Elemente, 0-22 / / Zweidimensionalen Arrays 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 Array-Element wird von den anderen unabhängig und kann als folgender Syntax aufgerufen werden:
10 ] ; Ivet int [10]; 3 ] [ 4 ] ; IMAT int [3] [4]; / / Ändern der vierte Element des Vektors Ivet. / / Beachten Sie, dass Sie beginnen Element Null zu zählen, von der ] = 13 ; Ivet [3] = 13; / / Lesen der zweite Element des Vektors Ivet ivet [ 1 ] ; int num = Ivet [1]; / / Ändern der Element der ersten Zeile, zweite Spalte IMAT ] [ 1 ] = 42 ; IMAT [0] [1] = 42; / / Lesen des Elements in der dritten Zeile, vierte Spalte IMAT imat [ 2 ] [ 3 ] ; int foo = IMAT [2] [3];
In diesem Beitrag werden wir nicht diskutieren Hintergrund Matrix, untersuchen wir nur die Beziehung zwischen Arrays und Zeiger in einem ziemlich intuitiv.
Größen von Arrays
Da ein Array ist eine Abstraktion, die mehrere Werte des gleichen Typs enthält, wie groß ist es? Wie viel Platz es einnimmt in Erinnerung?
Betrachten Sie den folgenden Code:
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 10 ] ; Ivet 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 (Ivet) =" <<sizeof (Ivet) <<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 durchaus sinnvoll. Der Platz, den eine Matrix gleich der Anzahl der Elemente durch die Größe der Typ des Elements (Zeilen x Spalten x multipliziert sizeof (type)). Allerdings, wenn jedes Element unabhängig ist, wird davon ausgegangen, dass jeder über einen separaten Ort im Speicher belegt, sonst wird man ein anderes Element zu überschreiben. Also, 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 bei einem ein Byte:
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 und Adressen Werte. "Índice \t Valor \t Endereço do elemento \n " ) ; printf ("Index \ t Value \ 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], & Cvet [i]); ) / / Anzeige der Adresse des Arrays "Endereço da matriz: %p \n " , & cvet ) ; printf ("Adresse des Arrays:% p \ n", & Cvet); / / Anzeige der Adresse des Arrays wieder "Endereço da matriz: %p \n " , cvet ) ; printf ("Adresse des Arrays:% p \ n", Cvet); |
Die Adressen der Elemente werden sequentiell, dh, jedes Element neben dem ehemaligen gespeichert. Zusätzlich gibt es zwei Fakten noch interessanter:
- Die Adresse des array (& Cvet), in Zeile 25 dargestellt, ist das gleiche wie das erste Element des Arrays;
- Der sehr variable Cvet kann als Zeiger interpretiert werden, wie in Zeile 28 gezeigt;
In C + + ist eine gemeinsame Array einen zusammenhängenden Block von Speicher, dessen Name kann interpretiert werden (cast) als Zeiger, der auf sein erstes Element. Außerdem ist es bis zu einem Punkt Zeiger auf ein Array, wo der Zeiger auf den Bestimmungsort ist vom gleichen Typ wie der Typ der Array-Elemente zu machen. Während atrubuição eines Arrays auf einen Zeiger, macht der Compiler eine implizite Typumwandlungen. Die Ziel-Zeiger als Zeiger auf den Speicherbereich belegt das Array interpretiert werden.
Eine der Folgen nicht so offensichtlich ist, dass bei der impliziten Stimmen ist die Information verloren, dass das Gebiet ein Speicher-Array wurde. So wird die Information über die Größe des Arrays verloren. Aus der Sicht des Zeigers, ist es an den Anfang eines beliebigen Block des Speichers, eine Größe zu willkürlichen zeigt. Ausfahrt und fahren in einem Array zu einem Zeiger, bedeutet, sich von einer Abstraktion restriktiver und mehr High-Level-Abstraktion zu einer weniger restriktiven und unteren Ebene.
Auf der anderen Seite versuchen, einen Zeiger auf ein Array zuweisen erzeugt einen Compiler-Fehler für unvereinbar Typen. Ein Array ist ein Speicherbereich von n-Daten (in Bytes), da ein Zeiger hat nur eine bestimmte, eine Adresse. Der Compiler kann nicht im Vorhinein wissen, ob ein Zeiger auf einer Fläche von 1, 2 oder 200 Bytes.
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 Zuordnung \ n"); "cvet = %p \n " , cvet ) ; printf ("Cvet =% p \ n", Cvet); "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 (PC) =% lu \ n", sizeof (PC)); PC = Cvet; " \n Depois da atribuição \n " ) ; printf ("\ n Nach der Abtretung \ n"); "cvet = %p \n " , cvet ) ; printf ("Cvet =% p \ n", Cvet); "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 (PC) =% lu \ n", sizeof (PC)); |
Beachten Sie, dass vor der Zuweisung (Zeile 25) den Zeiger PC. ist null, da es so gebootet wurde. Bereits Größen zeigen, dass die 300 Byte-Array und der Zeiger nur 8 (meine Maschine hat, ist ein AMD 64). Nach der Abtretung beide das "Punkt" auf den gleichen Bereich des Speichers, aber die Größen ändern sich nicht. Es war eine implizite Umwandlung in char [300] zu char * und der Zeiger in diesem Spiel PC kann nicht wissen, die Größe des Speicherbereichs auf den er zeigt. Aber das Array Cvet noch genau zu wissen, was er ist, ohne existenzielle Krise.
Zeigerarithmetik - vulgo, so what?
Warum, wenn ich weiß, dass die Daten in einem Array sind nebeneinander angeordnet, verwende ich einen Zeiger, der an die nächste Adresse springen und den Zugriff auf das nächste Element. Der Name dieses ist die Arithmetik Zeigern.
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 | 6 ; const int max = 6; max ] = { 'B' , 'L' , 'A' , 'B' , 'O' , 'S' } ; Cvet char [max] = ('B', 'L', 'A', 'B', 'O', 'S'); pc = cvet ; char * pc = Cvet; int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) ( "%c" , * ( pc + i ) ) ; printf ("% c", * (PC + i)); ) " \n " ) ; printf ("\ n"); int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) ( "%c" , * pc ++ ) ; printf ("% c", * PC + +); ) " \n " ) ; printf ("\ n"); / / Jetzt mit Zahlen max ] = { 1 , 2 , 3 , 4 , 5 , 6 } ; Ivet int [max] = (1, 2, 3, 4, 5, 6); pi = ivet ; int * pi = Ivet; int i = 0 ; i < max ; i ++ ) { for (int i = 0; i <max; i + +) ( "%p = %d \n " , pi, * pi ++ ) ; printf ("% p =% d \ n", pi * pi + +); ) " \n " ) ; printf ("\ n"); / / In zwei Dimensionen 2 ] [ 3 ] = { { 'B' , 'L' , 'A' } , { 'B' , 'O' , 'S' } } ; CMAT char [2] [3] = (('B', 'L', 'A'), ('B', 'O', 'S')); ppc ; char * PPC; char * ) cmat ; ppc = (char *) CMAT; int i = 0 ; i < 2 ; i ++ ) { for (int i = 0; i <2; i + +) ( int j = 0 ; j < 3 ; j ++ ) { for (int j = 0, j <3 j + +) ( "%c" , * ( ppc + 3 * i + j ) ) ; printf ("% c", * (PPC + 3 * i + j)); ) " \n " ) ; printf ("\ n"); ) " \n " ) ; printf ("\ n"); |
In Zeile 18 den Zeiger PC ist nun Punkt auf dem Array Cvet und damit auf sein erstes Element, das Zeichen 'B'. In Zeile 21 den Inhalt der PC, der die Adresse, wo es das Zeichen 'gespeichert wurde, ist B', dh dann dereferenziert erhöht. Im ersten Durchlauf den Wert von i gleich Null ist, so ist es Dereferenzierung wird der Wert für 'B'. In den folgenden Schritten, die folgende Adresse ist für andere de-Zeichen in der ursprünglichen Array gespeichert verwiesen. Es ist mehr oder weniger das, was der Compiler intern funktioniert, wenn Sie die Syntax Cvet [i] zu verwenden. Die Abstraktion der Array gibt Ihnen einen freundlichen Umgang mit angrenzenden Gebieten des Speichers, (* PC + i).
Aber wenn die Abstraktion des Array einfacher zu bedienen als Pointer-Arithmetik verwendet wird?
Eine Antwort ist, Zeile 26. Sie tut das gleiche wie in Zeile 21, aber ein wenig schneller. In der Syntax der Linie 21, oder ähnlich, die Array-Syntax, den Zugang zu einem gegebenen Daten können grob zusammengefasst werden auch in den Kommandos:
- Nehmen Sie die Basis-Adresse des Arrays;
- Hinzufügen, um den Wert des Index-Adresse;
- De-Referenzierung der neuen Adresse;
Bereits mit Zeigerarithmetik sieht wie folgt aus:
- De-Referenzierung dieser Adresse;
Der Befehl Inkrement (oder Konto-Adressen mit gemacht) wird nicht sagen, weil es Schleife ist Teil der, obwohl i + +; ist schneller als a = b + c;. Nun stellen Sie sich diesen kleinen Gewinn von 66% angewendet, um eine Daten Fläche von 1 MB. Es wird über 2 Millionen weniger Befehle werden!
Die Technik der über einen Zeiger auf einen beliebigen Speicher umgehen Bereich ist in der Regel), die in niedrigen Niveau Programmierung (näher an der Maschine, die Manipulation von Puffern und Streicher, unter anderem schmutzigen Tricks. In den Eingeweiden von Computern, Operationen, fegen große Bereiche der Erinnerung, oft mit Zeigerarithmetik durchgeführt. Auf dieser Ebene Darwin herrscht und nur der überleben wird vorbereitet. Von hier aus beginnt die Sprache zu einer Macht, die nur die reinen Herzens kann verstehen geben.
Ein wichtiger Hinweis ist, dass zwischen den Linien 31 und 38 ist die Erfahrung mit ganzen Zahlen wiederholt. Beachten Sie, dass die Zahlen 4 Bytes, die automatisch erhöht werden 4 in 4 Bytes, nicht 1 auf 1, dh der Zuwachs wird automatisch berechnet, sizeof (type). Inkrementieren eines Zeigers bedeutet zum nächsten Bereich des Speichers ähnlich tatsächlichen Daten, nicht nur die nächste Adresse zuzugreifen. Da die Größe eines char ist ein Byte, wenn incremetamos ein Zeiger auf char, bewegen sich nur ein Byte. Wenn Sie einen Zeiger auf Doppel-Inkrement, wir bewegen 8 Byte, und so weiter.
Eine weitere Beobachtung ist, dass ein zweidimensionales Array kann "linearisierte" wie in den Zeilen 40 bis 52 dargestellt. Dies ist nützlich, falls zutreffend, zur besseren Nutzung der Prozessor-Cache zu machen, zum Beispiel.
void *, die pansexuell von Zeigern
Früher habe ich gesagt, dass war nur möglich, um ein Array zu einem Zeiger, der für die gleiche Art wurde zuordnen, dass die Array-Daten. Ich unverhohlen lügen! Der Grund dafür ist, dass für jemanden, der den Posten zurückgetreten, bevor dieses Thema, ist es sicherer, zu glauben, er kann nicht
!
Es gibt zwei Ausnahmen von der Regel. Die erste ist, wenn es eine explizite Typumwandlungen und Zeiger target "denkt", dass ist die richtige Art zeigt. Ein Beispiel dafür ist in Zeile 44 der vorherigen Code.
Der zweite ist der Fall der Zeiger auf void. Ein Zeiger auf void ist ein Zeiger, dass keine Anforderungen an die Art der Daten, die im Bereich der Speicher ist, macht auf den er zeigt. Er ist ein Zeiger auf einer generischen Bereich des Speichers, etwas sehr niedrigen Niveau.
Zur Verwendung eines Daten, auf die ein Zeiger auf vor de-reference nichtig, müssen wir eine explizite Umwandlung eine gültige Art zu machen, denn wenn ein int * ist ein de-int und char * verwiesen wird de-bezogen auf char, erraten, was es ist de-a void * referenziert?
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 / / Wie viel ist 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, wie die Nummer können muss API lib pthreads (link willkürlich).
Schließen
Je mehr wir forschen in den Themen auf Zeigern, sind wir näher an die Maschine. Ein großer Teil der Macht von C und C + + daraus, und eine Menge Probleme zu. Die Komplexität nimmt zu und die Risiken zu. 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
- Die Guilty


