Warum doppelte Indirektion verwenden? oder Warum Zeiger auf Zeiger verwenden?

Lesezeit: 14 Minuten

Benutzeravatar von manju
Manju

Wann sollte eine doppelte Indirektion in C verwendet werden? Kann das jemand anhand eines Beispiels erklären?

Was ich weiß, ist, dass eine doppelte Indirektion ein Zeiger auf einen Zeiger ist. Warum brauche ich einen Zeiger auf einen Zeiger?

  • Vorsichtig sein; der Ausdruck “Doppelzeiger” bezieht sich auch auf den Typ double*.

    – Keith Thompson

    19. Oktober 2016 um 2:02 Uhr

  • Beachten Sie: Die Antwort auf diese Frage ist für C und C++ unterschiedlich – fügen Sie dieser sehr alten Frage kein c+-Tag hinzu.

    – BЈовић

    1. August 2017 um 8:32 Uhr

  • @BЈовић Obwohl es eine alte Frage und ein alter Kommentar ist, was ist der Unterschied in der Verwendung von Doppelzeigern zwischen C und C++? Nachdem ich Ihren Kommentar gesehen habe, dass sie unterschiedlich sind, habe ich versucht, die Antwort selbst zu geben, aber ich sehe immer noch wenig Unterschied in der Verwendung von Doppelzeigern in C und C++.

    – Sangjun Lee

    14. September 2020 um 4:53 Uhr

  • kann für ein gezacktes Array von Zeichen verwendet werden, dh eine Liste von Listen, bei denen jede Liste eine andere Länge hat

    – Wahrheitsanpasser

    20. Januar um 2:40 Uhr

Benutzeravatar von pmg
pmg

Wenn Sie eine Liste von Zeichen (ein Wort) haben möchten, können Sie verwenden char *word

Wenn Sie eine Liste von Wörtern (einen Satz) möchten, können Sie verwenden char **sentence

Wenn Sie eine Liste von Sätzen (einen Monolog) wünschen, können Sie verwenden char ***monologue

Wenn Sie eine Liste der Monologe (eine Biografie) wünschen, können Sie verwenden char ****biography

Wenn Sie eine Liste von Biografien (eine Bio-Bibliothek) wünschen, können Sie verwenden char *****biolibrary

Wenn Sie eine Liste von Bio-Bibliotheken (ein ??lol) möchten, können Sie verwenden char ******lol

… …

Ja, ich weiß, dass dies möglicherweise nicht die besten Datenstrukturen sind


Anwendungsbeispiel mit einem sehr sehr sehr langweilig lol

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int wordsinsentence(char **x) {
    int w = 0;
    while (*x) {
        w += 1;
        x++;
    }
    return w;
}

int wordsinmono(char ***x) {
    int w = 0;
    while (*x) {
        w += wordsinsentence(*x);
        x++;
    }
    return w;
}

int wordsinbio(char ****x) {
    int w = 0;
    while (*x) {
        w += wordsinmono(*x);
        x++;
    }
    return w;
}

int wordsinlib(char *****x) {
    int w = 0;
    while (*x) {
        w += wordsinbio(*x);
        x++;
    }
    return w;
}

int wordsinlol(char ******x) {
    int w = 0;
    while (*x) {
        w += wordsinlib(*x);
        x++;
    }
    return w;
}

int main(void) {
    char *word;
    char **sentence;
    char ***monologue;
    char ****biography;
    char *****biolibrary;
    char ******lol;

    //fill data structure
    word = malloc(4 * sizeof *word); // assume it worked
    strcpy(word, "foo");

    sentence = malloc(4 * sizeof *sentence); // assume it worked
    sentence[0] = word;
    sentence[1] = word;
    sentence[2] = word;
    sentence[3] = NULL;

    monologue = malloc(4 * sizeof *monologue); // assume it worked
    monologue[0] = sentence;
    monologue[1] = sentence;
    monologue[2] = sentence;
    monologue[3] = NULL;

    biography = malloc(4 * sizeof *biography); // assume it worked
    biography[0] = monologue;
    biography[1] = monologue;
    biography[2] = monologue;
    biography[3] = NULL;

    biolibrary = malloc(4 * sizeof *biolibrary); // assume it worked
    biolibrary[0] = biography;
    biolibrary[1] = biography;
    biolibrary[2] = biography;
    biolibrary[3] = NULL;

    lol = malloc(4 * sizeof *lol); // assume it worked
    lol[0] = biolibrary;
    lol[1] = biolibrary;
    lol[2] = biolibrary;
    lol[3] = NULL;

    printf("total words in my lol: %d\n", wordsinlol(lol));

    free(lol);
    free(biolibrary);
    free(biography);
    free(monologue);
    free(sentence);
    free(word);
}

Ausgabe:

total words in my lol: 243

  • Wollte nur darauf hinweisen, dass a arr[a][b][c] ist kein ***arr. Zeiger von Zeigern verwenden Referenzen von Referenzen, während arr[a][b][c] wird als übliches Array in Zeilenhauptordnung gespeichert.

    – MCCCS

    23. Juni 2018 um 9:50 Uhr

  • @pmg ich kann char *ptr= “mein Satz” verwenden; und ptr übergeben, um zu funktionieren, warum ** dann?

    – nauman

    26. März 2021 um 20:15 Uhr


  • @ user143252 — Was wäre, wenn du hast "my sentence", "his sentence", "her sentence"und "their sentence"? Du kannst tun char *ptr1 = "my sentence"; etc… aber es ist bequemer, ein Array von 5 (4 + NULL) Zeigern zu erstellen: char *ptrs[5] = {"my sentence", "his sentence", ..., NULL}. Wenn Sie dieses Array an eine Funktion übergeben (foo(ptrs)) wird das Array automatisch in Typ konvertiert char**!

    – pmg

    26. März 2021 um 20:29 Uhr


Benutzeravatar von Asha
Ascha

Ein Grund ist, dass Sie den Wert des Zeigers ändern möchten, der als Funktionsargument an eine Funktion übergeben wird. Dazu benötigen Sie einen Zeiger auf einen Zeiger.

In einfachen Worten, Verwenden ** wenn Sie die Speicherzuweisung oder -zuweisung auch außerhalb eines Funktionsaufrufs beibehalten (ODER Änderungen beibehalten) möchten. (Übergeben Sie also eine solche Funktion mit einem doppelten Zeiger arg.)

Dies ist vielleicht kein sehr gutes Beispiel, zeigt Ihnen aber die grundlegende Verwendung:

#include <stdio.h>
#include <stdlib.h>

void allocate(int **p)
{
    *p = (int *)malloc(sizeof(int));
}

int main()
{
    int *p = NULL;
    allocate(&p);
    *p = 42;
    printf("%d\n", *p);
    free(p);
}

  • was wäre anders, wenn allocate wäre void allocate(int *p) und du hast es so genannt allocate(p)?

    – Incerteza

    7. September 2014 um 20:03 Uhr

  • @AlexanderSupertramp Ja. Der Code wird segfault. Bitte sehen Sie sich Silvius Antwort an.

    – Abhishek

    30. September 2014 um 5:37 Uhr

  • @Asha was ist der Unterschied zwischen allocate(p) und allocate(&p)?

    – Benutzer2979872

    10. Oktober 2017 um 15:23 Uhr


  • @Asha – Können wir den Zeiger nicht einfach zurückgeben? Wenn wir es für ungültig halten müssen, was ist dann ein praktischer Anwendungsfall für dieses Szenario?

    – Shabirmean

    9. Oktober 2018 um 23:27 Uhr

  • @ user2979872 allocate (p): p wird als Wert übergeben, und daher werden Funktionsänderungen nicht in der Hauptmethode widergespiegelt. allocate(&p): p wird als Referenz übergeben, und daher werden die Änderungen in p in der Hauptmethode widergespiegelt. Allerdings gibt es einen Haken. Wenn allocate(p) verwendet wird und wir den Wert in der Adresse ändern, auf die b zeigt, dann werden die Änderungen in main() für den Wert widergespiegelt, da die Änderung direkt am Speicherort stattfand. Nur um es noch einmal zu wiederholen, die Wertänderung in p wird immer noch nicht widergespiegelt.

    – Ankit Arora

    9. April 2021 um 10:40 Uhr

Benutzeravatar von Brian Joseph Spinos
Brian Joseph Spinos

  • Nehmen wir an, Sie haben einen Zeiger. Sein Wert ist eine Adresse.
  • aber jetzt möchten Sie diese Adresse ändern.
  • Sie könnten. durch tun pointer1 = pointer2gibst du pointer1 die Adresse von pointer2.
  • aber! Wenn Sie dies innerhalb einer Funktion tun und möchten, dass das Ergebnis nach Abschluss der Funktion erhalten bleibt, müssen Sie zusätzliche Arbeit leisten. Sie brauchen einen neuen Zeiger3, nur um auf Zeiger1 zu zeigen. Zeiger3 an die Funktion übergeben.

  • Hier ist ein Beispiel. Schauen Sie sich zuerst die Ausgabe unten an, um zu verstehen.

#include <stdio.h>

int main()
{

    int c = 1;
    int d = 2;
    int e = 3;
    int * a = &c;
    int * b = &d;
    int * f = &e;
    int ** pp = &a;  // pointer to pointer 'a'

    printf("\n a's value: %x \n", a);
    printf("\n b's value: %x \n", b);
    printf("\n f's value: %x \n", f);
    printf("\n can we change a?, lets see \n");
    printf("\n a = b \n");
    a = b;
    printf("\n a's value is now: %x, same as 'b'... it seems we can, but can we do it in a function? lets see... \n", a);
    printf("\n cant_change(a, f); \n");
    cant_change(a, f);
    printf("\n a's value is now: %x, Doh! same as 'b'...  that function tricked us. \n", a);

    printf("\n NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' \n");
     printf("\n change(pp, f); \n");
    change(pp, f);
    printf("\n a's value is now: %x, YEAH! same as 'f'...  that function ROCKS!!!. \n", a);
    return 0;
}

void cant_change(int * x, int * z){
    x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", x);
}

void change(int ** x, int * z){
    *x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", *x);
}

Hier ist die Ausgabe: (Lesen Sie dies zuerst)

 a's value: bf94c204

 b's value: bf94c208 

 f's value: bf94c20c 

 can we change a?, lets see 

 a = b 

 a's value is now: bf94c208, same as 'b'... it seems we can, but can we do it in a function? lets see... 

 cant_change(a, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c208, Doh! same as 'b'...  that function tricked us. 

 NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' 

 change(pp, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c20c, YEAH! same as 'f'...  that function ROCKS!!!. 

  • Dies ist eine großartige Antwort und hat mir wirklich geholfen, den Zweck und die Nützlichkeit eines Doppelzeigers zu visualisieren.

    – Justin

    11. April 2017 um 15:11 Uhr

  • @Justin hast du dir meine Antwort darüber angesehen? es ist sauberer 🙂

    – Brian Joseph Spinos

    11. April 2017 um 23:51 Uhr

  • Tolle Antwort, es fehlt nur zu erklären, dass void cant_change(int * x, int * z) fehlschlägt, weil seine Parameter nur neue lokale Zeiger sind, die ebenso wie a- und f-Zeiger initialisiert werden (also nicht das gleiche wie a und f).

    – Pedro Reis

    17. Mai 2017 um 16:59 Uhr


  • Einfach? Wirklich? 😉

    – alk

    16. April 2019 um 16:46 Uhr

  • Diese Antwort erklärt wirklich eine der häufigsten Verwendungen von Zeigern auf Zeiger, danke!

    – tonyjosi

    27. Januar 2020 um 5:51 Uhr

Benutzeravatar von Silviu
Silviu

Ergänzend zu Ashas Antwort, wenn Sie einen einzelnen Zeiger auf das Beispiel unten verwenden (z. B. alloc1() ), verlieren Sie den Verweis auf den innerhalb der Funktion zugewiesenen Speicher.

#include <stdio.h>
#include <stdlib.h>

void alloc2(int** p) {
    *p = (int*)malloc(sizeof(int));
    **p = 10;
}

void alloc1(int* p) {
    p = (int*)malloc(sizeof(int));
    *p = 10;
}

int main(){
    int *p = NULL;
    alloc1(p);
    //printf("%d ",*p);//undefined
    alloc2(&p);
    printf("%d ",*p);//will print 10
    free(p);
    return 0;
}

Der Grund dafür ist, dass in alloc1 der Zeiger wird als Wert übergeben. Wenn es also dem Ergebnis von neu zugewiesen wird malloc Anruf innerhalb von alloc1bezieht sich die Änderung nicht auf Code in einem anderen Bereich.

Benutzeravatar von Ziyuan
Ziyuan

Ich habe heute ein sehr gutes Beispiel gesehen, aus diesen Blogbeitragwie ich unten zusammenfasse.

Stellen Sie sich vor, Sie haben eine Struktur für Knoten in einer verknüpften Liste, was wahrscheinlich der Fall ist

typedef struct node
{
    struct node * next;
    ....
} node;

Jetzt wollen Sie a implementieren remove_if Funktion, die ein Entfernungskriterium akzeptiert rm als eines der Argumente und durchläuft die verknüpfte Liste: Wenn ein Eintrag das Kriterium erfüllt (etwas wie rm(entry)==true), wird sein Knoten aus der Liste entfernt. Schlussendlich, remove_if gibt den Kopf (der sich vom ursprünglichen Kopf unterscheiden kann) der verknüpften Liste zurück.

Du darfst schreiben

for (node * prev = NULL, * curr = head; curr != NULL; )
{
    node * const next = curr->next;
    if (rm(curr))
    {
        if (prev)  // the node to be removed is not the head
            prev->next = next;
        else       // remove the head
            head = next;
        free(curr);
    }
    else
        prev = curr;
    curr = next;
}

wie dein for Schleife. Die Botschaft ist, ohne doppelte Zeiger müssen Sie a pflegen prev Variable, um die Zeiger neu zu organisierenund behandeln Sie die beiden unterschiedlichen Fälle.

Aber mit Doppelzeigern können Sie tatsächlich schreiben

// now head is a double pointer
for (node** curr = head; *curr; )
{
    node * entry = *curr;
    if (rm(entry))
    {
        *curr = entry->next;
        free(entry);
    }
    else
        curr = &entry->next;
}

Du brauchst kein prev jetzt weil Sie können direkt was ändern prev->next wies auf.

Um die Dinge klarer zu machen, folgen wir dem Code ein wenig. Während der Entfernung:

  1. wenn entry == *head: es wird sein *head (==*curr) = *head->nexthead zeigt nun auf den Zeiger des neuen Heading-Knotens. Sie tun dies, indem Sie direkt ändern head‘s Inhalt in einen neuen Zeiger.
  2. wenn entry != *head: ähnlich, *curr ist, was prev->next zeigte auf und zeigt jetzt auf entry->next.

Egal in welchem ​​Fall, Sie können die Zeiger mit Doppelzeigern einheitlich neu organisieren.

1. Grundkonzept –

Wenn Sie Folgendes erklären: –

1. char *ch – (Zeichenzeiger genannt)

-ch enthält die Adresse eines einzelnen Zeichens.
– (*ch) dereferenziert den Wert des Zeichens.

2. Zeichen **ch –

‘ch’ enthält die Adresse eines Arrays von Zeichenzeigern. (wie in 1)
‘*ch’ enthält die Adresse eines einzelnen Zeichens. (Beachten Sie, dass es sich aufgrund der unterschiedlichen Deklaration von 1 unterscheidet).
(**ch) wird auf den genauen Wert des Zeichens dereferenzieren.

Durch das Hinzufügen weiterer Zeiger wird die Dimension eines Datentyps erweitert, von einem Zeichen zu einer Zeichenfolge, zu einem Array von Zeichenfolgen usw. Sie können es mit einer 1d-, 2d-, 3d-Matrix in Beziehung setzen.

Die Verwendung des Zeigers hängt also davon ab, wie Sie ihn deklarieren.

Hier ist ein einfacher Code..

int main()
{
    char **p;
    p = (char **)malloc(100);
    p[0] = (char *)"Apple";      // or write *p, points to location of 'A'
    p[1] = (char *)"Banana";     // or write *(p+1), points to location of 'B'

    cout << *p << endl;          //Prints the first pointer location until it finds '\0'
    cout << **p << endl;         //Prints the exact character which is being pointed
    *p++;                        //Increments for the next string
    cout << *p;
}

2. Eine andere Anwendung von Doppelzeigern –

(dies würde auch Pass-by-Reference abdecken)

Angenommen, Sie möchten ein Zeichen aus einer Funktion aktualisieren. Wenn Sie Folgendes versuchen: –

void func(char ch)
{
    ch="B";
}

int main()
{
    char ptr;
    ptr="A";
    printf("%c", ptr);

    func(ptr);
    printf("%c\n", ptr);
}

Die Ausgabe ist AA. Dies funktioniert nicht, da Sie an die Funktion “Passed By Value” haben.

Der richtige Weg wäre –

void func( char *ptr)        //Passed by Reference
{
    *ptr="B";
}

int main()
{
    char *ptr;
    ptr = (char *)malloc(sizeof(char) * 1);
    *ptr="A";
    printf("%c\n", *ptr);

    func(ptr);
    printf("%c\n", *ptr);
}

Erweitern Sie nun diese Anforderung zum Aktualisieren einer Zeichenfolge anstelle eines Zeichens.
Dazu müssen Sie den Parameter in der Funktion als Doppelzeiger erhalten.

void func(char **str)
{
    strcpy(str, "Second");
}

int main()
{
    char **str;
    // printf("%d\n", sizeof(char));
    *str = (char **)malloc(sizeof(char) * 10);          //Can hold 10 character pointers
    int i = 0;
    for(i=0;i<10;i++)
    {
        str = (char *)malloc(sizeof(char) * 1);         //Each pointer can point to a memory of 1 character.
    }

    strcpy(str, "First");
    printf("%s\n", str);
    func(str);
    printf("%s\n", str);
}

In diesem Beispiel erwartet die Methode einen doppelten Zeiger als Parameter, um den Wert einer Zeichenfolge zu aktualisieren.

Benutzeravatar von Jason
Jason

Zeiger auf Zeiger sind auch praktisch als “Handles” für Speicher, wenn Sie einen “Handle” zwischen Funktionen an verschiebbaren Speicher herumreichen möchten. Das bedeutet im Grunde, dass die Funktion den Speicher ändern kann, auf den der Zeiger in der Handle-Variable zeigt, und jede Funktion oder jedes Objekt, das das Handle verwendet, ordnungsgemäß auf den neu verschobenen (oder zugewiesenen) Speicher zeigt. Bibliotheken tun dies gerne mit “undurchsichtigen” Datentypen, dh Datentypen, bei denen Sie sich keine Gedanken darüber machen müssen, was sie mit dem Speicher tun, auf den gezeigt wird, Sie geben einfach den “Griff” zwischen ihnen herum Funktionen der Bibliothek, um einige Operationen an diesem Speicher auszuführen … die Bibliotheksfunktionen können den Speicher unter der Haube zuweisen und freigeben, ohne dass Sie sich explizit um den Prozess der Speicherverwaltung kümmern müssen oder wohin der Griff zeigt.

Zum Beispiel:

#include <stdlib.h>

typedef unsigned char** handle_type;

//some data_structure that the library functions would work with
typedef struct 
{
    int data_a;
    int data_b;
    int data_c;
} LIB_OBJECT;

handle_type lib_create_handle()
{
    //initialize the handle with some memory that points to and array of 10 LIB_OBJECTs
    handle_type handle = malloc(sizeof(handle_type));
    *handle = malloc(sizeof(LIB_OBJECT) * 10);

    return handle;
}

void lib_func_a(handle_type handle) { /*does something with array of LIB_OBJECTs*/ }

void lib_func_b(handle_type handle)
{
    //does something that takes input LIB_OBJECTs and makes more of them, so has to
    //reallocate memory for the new objects that will be created

    //first re-allocate the memory somewhere else with more slots, but don't destroy the
    //currently allocated slots
    *handle = realloc(*handle, sizeof(LIB_OBJECT) * 20);

    //...do some operation on the new memory and return
}

void lib_func_c(handle_type handle) { /*does something else to array of LIB_OBJECTs*/ }

void lib_free_handle(handle_type handle) 
{
    free(*handle);
    free(handle); 
}


int main()
{
    //create a "handle" to some memory that the library functions can use
    handle_type my_handle = lib_create_handle();

    //do something with that memory
    lib_func_a(my_handle);

    //do something else with the handle that will make it point somewhere else
    //but that's invisible to us from the standpoint of the calling the function and
    //working with the handle
    lib_func_b(my_handle); 

    //do something with new memory chunk, but you don't have to think about the fact
    //that the memory has moved under the hood ... it's still pointed to by the "handle"
    lib_func_c(my_handle);

    //deallocate the handle
    lib_free_handle(my_handle);

    return 0;
}

Hoffe das hilft,

Jason

  • Was ist der Grund dafür, dass der Handle-Typ unsigned char** ist? Würde void** genauso gut funktionieren?

    – Connor Clark

    31. Mai 2016 um 5:44 Uhr

  • unsigned char wird speziell verwendet, weil wir einen Zeiger auf Binärdaten speichern, die als Rohbytes dargestellt werden. Verwenden void wird irgendwann eine Besetzung erfordern und ist im Allgemeinen nicht so lesbar wie die Absicht dessen, was getan wird.

    – Jason

    7. Juni 2016 um 14:15 Uhr

1427320cookie-checkWarum doppelte Indirektion verwenden? oder Warum Zeiger auf Zeiger verwenden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy