Mehrdimensionale C-Arrays (und Zeiger)



  • Hallo liebe Community. Ich lerne grade mit dem Buch "Der C++ Programmierer" und bin jetzt an Kapitel 5 hängen geblieben. Es geht erstmal um zweidimensionale Arrays und um Funktionen, die diese ausgeben. Letzteres lasse ich erstmal aus, da ich den Knackpunkt davor noch nicht geschnallt habe.
    Gegeben sei ein Array:

    int feld1[2][3] = { 1, 3, 5, 7, 9, 11 };
    

    Nach meiner Interpretation bedeutet das erstmal, dass ich hier 2 mal 3 int-Werte habe (also 6). Wenn ich zurückblicke, dann hab ich ja erfahren, dass ein Array nichts anderes als ein Zeiger auf ein Bereich im Speicher ist. Bei der Initialisierung zeigt dieser auf den Anfang des Bereichs. Also ist

    feld[x] = 0;
    

    äquivalent zu

    *(feld+x) = 0;
    

    Wenn ich jetzt auf mein 2D-Array zurückkomme, dann müsste ich ja dasselbe haben. Das heißt, ich habe zwei Zeiger auf je 3 int-Werten. Die beiden Zeiger sind in einem Array zusammengefasst, sodass ich sagen kann, dass ich nun ein Zeiger auf Zeiger auf int-Werten habe. Soweit richtig?

    Was ich jetzt nicht vertehe: Wieso kann ich einen Zeiger auf dieses Array nur so initialisieren

    int (*feld1p)[3] = feld1;
    

    Versuche ich die Klammern zu streichen, meckert meine IDE rum von wegen "ich brauche eine Initialisierung mit {}". Oder brauch ich die Klammern, weil ich sonst ein Zeigerarray mit 3 Elementen anforder?



  • RaggyGandalf schrieb:

    Wenn ich jetzt auf mein 2D-Array zurückkomme, dann müsste ich ja dasselbe haben. Das heißt, ich habe zwei Zeiger auf je 3 int-Werten. Die beiden Zeiger sind in einem Array zusammengefasst, sodass ich sagen kann, dass ich nun ein Zeiger auf Zeiger auf int-Werten habe. Soweit richtig?

    Nein!
    Auch das 2D-Array ist nur ein einfacher Zeiger auf den Speicherbereich.
    Die optisch richtige initialisierung wäre

    int feld1[2][3] = { {1, 3, 5}, {7, 9, 11} };
    

    Du kannst statt mit feld1[y][x] auch mit *(feld+y*3+x) darauf zugreifen.

    Du kannst auf einen Doppelzeiger auch mit der Arrayschreibweise zugreifen.
    Trotzedem sind das unterschiedliche Dinge.

    Das int (*feld1p)[3] bedeutet: declare feld1p as pointer to array 3 of int
    Also einen Zeiger auf ein int-Array aus 3 Elementen
    Das int *feld1p[3] bedeutet: declare feld1p as array 3 of pointer to int
    Also ein Array mit drei Zeiger auf int.

    KAsnnst du bei http://cdecl.org/ nachschauen.


  • Mod

    RaggyGandalf schrieb:

    Wenn ich zurückblicke, dann hab ich ja erfahren, dass ein Array nichts anderes als ein Zeiger auf ein Bereich im Speicher ist.

    Nein! Ein Array ist dieser Speicherbereich. Je nach Kontext kann es aber automatisch in einen Zeiger auf sein erstes Element umgewandelt werden. Es ist aber kein Zeiger!

    Wenn ich jetzt auf mein 2D-Array zurückkomme, dann müsste ich ja dasselbe haben. Das heißt, ich habe zwei Zeiger auf je 3 int-Werten. Die beiden Zeiger sind in einem Array zusammengefasst, sodass ich sagen kann, dass ich nun ein Zeiger auf Zeiger auf int-Werten habe. Soweit richtig?

    Nein, das 2x3-Array kann sich verhalten wie ein Zeiger auf sein erstes Element, also wie ein Zeiger auf ein int[3]-Array. Und das ist ganz etwas anderes als ein Zeiger auf einen int-Zeiger. Dies ist das wohl häufigste Missverständnis bei mehrdimensionalen Arrays.

    Was ich jetzt nicht vertehe: Wieso kann ich einen Zeiger auf dieses Array nur so initialisieren

    int (*feld1p)[3] = feld1;
    

    Versuche ich die Klammern zu streichen, meckert meine IDE rum von wegen "ich brauche eine Initialisierung mit {}". Oder brauch ich die Klammern, weil ich sonst ein Zeigerarray mit 3 Elementen anforder?

    Den Grund findest du oben. Dein feld1p muss vom Typ "Zeiger auf int[3]" sein. Bei int *feld1p[3] wäre feld1p ein Array von int-Zeigern mit 3 Elementen.

    P.S.: Dir ist schon klar, dass du dies in C++ praktisch niemals brauchen wirst, oder? Selbst in C sind mehrdimensionale Arrays eher selten, da sie so unhandlich sind. Interessant zu wissen ist all dies aber schon, da stimme ich zu, da man viel über das Typensystem lernen kann.



  • Okay, danke euch beiden. Ich glaub mir wird es jetzt etwas klarer 🙂

    int feld1[2][3] -> Ich habe ein Array mit 2 Zeigern auf ein Array[3] (also auf je 3 Elemente)
    int *feld1p[3] -> Ich deklariere ein Array aus Zeigern auf int
    int (*feld1p)[3] -> Ich deklariere einen Zeiger auf ein Array aus 3 int

    Die Klammer dient also dazu, dass ich die Deklaration von einem Zeigerarray verhindere. Damit hab ich einen Zeiger auf ein Array mit der angegebenen Anzahl von Elementen.

    Ich glaube ich check das heute eh nicht mehr. Ich lese mir das morgen nocheinmal durch 😃

    SeppJ schrieb:

    P.S.: Dir ist schon klar, dass du dies in C++ praktisch niemals brauchen wirst, oder? Selbst in C sind mehrdimensionale Arrays eher selten, da sie so unhandlich sind.

    Ja, das weiß ich. Ich denk' mir immer: Nimm' lieber viel Wissen mit. Je mehr ich weiß, desto leichter werden mir einige Dinge fallen. Nicht nur beim Programmieren.



  • int feld1[2][3] Ist ein Array mit 2*3 also 6 Elementen die direkt hintereinander im Speicher liegen.
    Das Objekt belegt 6 *sizeof(int) Speicherelemente.
    Da sind keine Zeiger im Spiel.
    Du bekommst zwar eine Adresse wenn du nur feld1 oder auch feld[1] schreibst.

    Was du da beschreibst ist (*feld1[2])[3] declare feld1 as array 2 of pointer to array 3 of int Hier hast du 2 Zeiger, aber noch keinen Speicher auf den diese zeigen. Die belegen schon 2*sozeof(int*) Speicherelemente.
    Dazu kommen dann noch die int-arrays mit 2*3*sizeof(int).



  • Ja ich hab mich heute morgen nochmal rangesetzt und habs jetzt anscheinend geschnallt. Es wurde nocheinmal im Buch drauf eingegangen, wie man 2D Arrays über eine Funktion ausgibt. Nachdem ich da durchgeblickt habe, war es relativ einfach. Die Übung war es dann das gleiche zu tun, nur diesmal mit einem 3D-Array. Ich hab mich sofort drangemacht und war innerhalb von 20min fertig. Und wow, es funktioniert sogar 🙂

    #include <iostream>
    #include <conio.h>
    
    template<typename T>
    void ausgabe2D(T tabelle, size_t n) {
         const size_t MAX = sizeof(tabelle[0]) / sizeof(tabelle[0][0]);
         for(size_t i = 0; i < n; ++i) {
                    for(size_t j = 0; j < MAX; ++j) {
                               std::cout << tabelle[i][j];
                    }
                    std::cout << std::endl;
         }
    }
    
    template<typename T>
    void ausgabe3D(T tabelle, size_t n) {
         const size_t MAX_Y = sizeof(tabelle[0]) / sizeof(tabelle[0][0]);
         const size_t MAX_Z = sizeof(tabelle[0][0]) / sizeof(tabelle[0][0][0]);
         for(size_t i = 0; i < n; ++i) {
                    for(size_t j = 0; j < MAX_Y; ++j) {
                               for(size_t k = 0; k < MAX_Z; ++k) {
                                          std::cout << tabelle[i][j][k];
                               }
                               std::cout << " ";
                    }
                    std::cout << std::endl;
         }
    }
    
    int main() {
        // 2D
        int arr2D[4][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
        int (*pArr2D)[2] = arr2D;
        ausgabe2D(pArr2D, 4);
    
        // 3D
        int arr3D[2][3][2] = { { { 1, 2 }, { 3, 4 }, { 5, 6 } }, { { 7, 8 }, { 9, 10 }, { 11, 12 } } };
        int (*pArr3D)[3][2] = arr3D;
        ausgabe3D(pArr3D, 2);
    
        _getch();
    }
    

    Also vielen Dank nochmal euch beiden :xmas1:

    PS: Gemerkt. Arrays sind Speicherbereiche und keine Zeiger auf deren Beginn. Durch den Aufruf des Namens vom Array erhalte ich einen Zeiger auf die derzeitige Position (unverändert am Anfang) des Speicherbereichs. Mit Zeigerarithmetik kann ich mich durch das Array arbeiten.


Anmelden zum Antworten