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


272

Wann sollte in C eine doppelte Indirektion verwendet werden? Kann jemand mit einem Beispiel erklären?

Was ich weiß ist, dass eine doppelte Indirektion ein Zeiger auf einen Zeiger ist. Warum sollte ich einen Zeiger auf einen Zeiger benötigen?


49
Achtung; Der Ausdruck "Doppelzeiger" bezieht sich auch auf den Typ double*.
Keith Thompson

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Јовић

Antworten:


479

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) möchten, können Sie verwenden char ***monologue

Wenn Sie eine Liste von Monologen (eine Biografie) möchten, können Sie verwenden char ****biography

Wenn Sie eine Liste von Biografien (eine Biobibliothek) möchten, können Sie diese verwenden char *****biolibrary

Wenn Sie eine Liste von Biobibliotheken (a ?? 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 langweiligen 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:

Gesamtzahl der Wörter in meinem lol: 243

6
Ich wollte nur darauf hinweisen, dass a arr[a][b][c]kein a ist ***arr. Zeiger von Zeigern verwenden Referenzen von Referenzen, während arr[a][b][c]sie als übliches Array in Zeilenreihenfolge gespeichert werden.
MCCCS

170

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.

Mit einfachen Worten: Verwenden **Sie diese Option, wenn Sie die Speicherzuordnung oder -zuweisung auch außerhalb eines Funktionsaufrufs beibehalten (ODER Änderungen beibehalten) möchten. (Übergeben Sie also eine solche Funktion mit dem Doppelzeiger arg.)

Dies ist möglicherweise kein sehr gutes Beispiel, zeigt Ihnen jedoch die grundlegende Verwendung:

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

int main()
{
  int* p = NULL;
  allocate(&p);
  *p = 42;
  free(p);
}

14
Was wäre anders, wenn Allokation wäre void allocate(int *p)und Sie es so nannten allocate(p)?
ス レ ッ ク ス

@ AlexanderSupertramp Ja. Der Code wird fehlerhaft sein. Bitte lesen Sie Silvius Antwort.
Abhishek

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

1
@Asha - Können wir nicht einfach den Zeiger zurückgeben? Wenn wir es für nichtig erklären müssen, was ist dann ein praktischer Anwendungsfall für dieses Szenario?
Shabirmean

91
  • Angenommen, Sie haben einen Zeiger. Sein Wert ist eine Adresse.
  • Aber jetzt möchten Sie diese Adresse ändern.
  • du könntest. Auf diese Weise pointer1 = pointer2geben Sie Zeiger1 die Adresse von Zeiger2.
  • 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 benötigen einen neuen Zeiger3, um auf Zeiger1 zu zeigen. Übergeben Sie den Zeiger3 an die Funktion.

  • 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: (zuerst lesen )

 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!!!. 

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

1
@ Justin hast du meine Antwort über dieser überprüft? es ist sauberer :)
Brian Joseph Spinos

10
Gute Antwort, es fehlt nur zu erklären, dass <code> void cant_change (int * x, int * z) </ code> fehlschlägt, weil seine Parameter nur neue Zeiger mit lokalem Gültigkeitsbereich sind, die ebenfalls mit a- und f-Zeigern initialisiert werden (also nicht) das gleiche wie a und f).
Pedro Reis

1
Einfach? "Ja wirklich?" ;)
alk

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

48

Wenn Sie zusätzlich zu Ashas Antwort einen einzelnen Zeiger auf das Beispiel unten verwenden (z. B. alloc1 ()), verlieren Sie den Verweis auf den in der Funktion zugewiesenen Speicher.

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 im alloc1Zeiger ein Wert übergeben wird. Wenn es also dem Ergebnis des mallocAufrufs innerhalb von zugewiesen wird, alloc1bezieht sich die Änderung nicht auf Code in einem anderen Bereich.


1
Was passiert, wenn p ein statischer Ganzzahlzeiger ist? Segmentierungsfehler erhalten.
Kapilddit

free(p)ist nicht genug, müssen Sie if(p) free(*p)auch
Shijing Lv

@ShijingLv: Nein. Wird *pmit einem intWert von 10 bewertet. Dies intan free () zu übergeben, ist eine schlechte Idee.
Alk

Die in vorgenommene Zuordnung alloc1()führt zu einem Speicherverlust. Der frei zu übergebende Zeigerwert geht durch die Rückkehr von der Funktion verloren.
Alk

Keine (!) Notwendigkeit, das Ergebnis von malloc in C.
alk

23

Ich habe heute ein sehr gutes Beispiel aus diesem Blog-Beitrag gesehen , wie 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 möchten Sie eine remove_ifFunktion implementieren , die ein Entfernungskriterium rmals eines der Argumente akzeptiert und die verknüpfte Liste durchläuft: Wenn ein Eintrag das Kriterium erfüllt (so etwas wie rm(entry)==true), wird sein Knoten aus der Liste entfernt. Am Ende wird remove_ifder Kopf (der sich möglicherweise vom ursprünglichen Kopf unterscheidet) der verknüpften Liste zurückgegeben.

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;
}

als deine forSchleife. Die Nachricht lautet, dass Sie ohne Doppelzeiger eine prevVariable verwalten müssen, um die Zeiger neu zu organisieren und die beiden verschiedenen Fälle zu behandeln.

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;
}

Sie brauchen kein prevJetzt, da Sie direkt ändern können, worauf prev->nextverwiesen wird .

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

  1. if entry == *head: it be *head (==*curr) = *head->next- zeigt headjetzt auf den Zeiger des neuen Steuerkursknotens. Sie tun dies, indem Sie den headInhalt direkt in einen neuen Zeiger ändern .
  2. if entry != *head: ähnlich *currist es, worauf prev->nexthingewiesen wurde und jetzt zeigt entry->next.

Unabhängig davon, in welchem ​​Fall Sie die Zeiger mit Doppelzeigern einheitlich neu organisieren können.


22

1. Grundkonzept -

Wenn Sie wie folgt deklarieren: -

1. char * ch - (als Zeichenzeiger bezeichnet)
- ch enthält die Adresse eines einzelnen Zeichens.
- (* ch) wird auf den Wert des Zeichens dereferenziert.

2. char ** 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 dereferenziert.

Durch Hinzufügen weiterer Zeiger wird die Dimension eines Datentyps von Zeichen zu Zeichenfolge, zu Array von Zeichenfolgen usw. erweitert. Sie können ihn auf eine 1d-, 2d-, 3d-Matrix beziehen.

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 weitere Anwendung von Doppelzeigern -
(dies würde auch die Referenzübergabe 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 "Passed By Value" an die Funktion übergeben haben.

Der richtige Weg, dies zu tun, 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 Doppelzeiger als Parameter, um den Wert einer Zeichenfolge zu aktualisieren.


#include <stdio.h> int main() { char *ptr = 0; ptr = malloc(255); // allocate some memory strcpy( ptr, "Stack Overflow Rocks..!!"); printf("%s\n", ptr); printf("%d\n",strlen(ptr)); free(ptr); return 0; } Sie können dies aber auch ohne Doppelzeiger tun.
Kumar

" char ** ch - 'ch' enthält die Adresse eines Arrays von Zeichenzeigern. " Nein, es enthält die Adresse des ersten Elements eines Arrays von charZeigern. Ein Zeiger auf ein Array von char*würde beispielsweise wie folgt eingegeben: char(*(*p)[42])Definiert pals Zeiger auf ein Array von 42 Zeiger auf char.
Alk

Das letzte Snippet ist komplett kaputt. Für den Anfang: Hier *str = ... strwird dereferenziert, nicht initialisiert, und undefiniertes Verhalten aufgerufen.
Alk

Dies malloc(sizeof(char) * 10);reserviert keinen Platz für 10 Zeiger auf, charsondern nur für 10 char..
alk

In dieser Schleife wird for(i=0;i<10;i++) { str = ... der Index nicht verwendet i.
Alk

17

Zeiger auf Zeiger sind auch nützlich als "Handles" für den Speicher, bei denen Sie ein "Handle" zwischen Funktionen an den neu lokalisierbaren Speicher weitergeben möchten. Dies bedeutet im Grunde, dass die Funktion den Speicher ändern kann, auf den der Zeiger in der Handle-Variablen zeigt, und jede Funktion oder jedes Objekt, das das Handle verwendet, ordnungsgemäß auf den neu verschobenen (oder zugewiesenen) Speicher verweist. 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 das "Handle" zwischen den Datentypen weiter Funktionen der Bibliothek, um einige Operationen an diesem Speicher auszuführen ...

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 ein Zeichen ohne Vorzeichen ist **? Würde void ** genauso gut funktionieren?
Connor Clark

5
unsigned charwird speziell verwendet, weil wir einen Zeiger auf Binärdaten speichern, die als Rohbytes dargestellt werden. Die Verwendung voiderfordert irgendwann eine Besetzung und ist im Allgemeinen nicht so lesbar wie die Absicht, was getan wird.
Jason

7

Ein einfaches Beispiel, das Sie wahrscheinlich schon oft gesehen haben

int main(int argc, char **argv)

Im zweiten Parameter haben Sie es: Zeiger auf Zeiger auf Zeichen.

Beachten Sie, dass die Zeigernotation ( char* c) und die Arraynotation ( char c[]) in Funktionsargumenten austauschbar sind. Du könntest also auch schreiben char *argv[]. Mit anderen Worten char *argv[]und char **argvsind austauschbar.

Was das Obige darstellt, ist in der Tat ein Array von Zeichenfolgen (die Befehlszeilenargumente, die einem Programm beim Start gegeben werden).

Siehe auch diese Antwort für weitere Details zur obigen Funktionssignatur.


2
"Zeigernotation ( char* c) und Array-Notation ( char c[]) sind austauschbar" (und haben dieselbe exakte Bedeutung) in Funktionsargumenten . Sie unterscheiden sich jedoch außerhalb von Funktionsargumenten.
PMG

6

Strings sind ein gutes Beispiel für die Verwendung von Doppelzeigern. Die Zeichenfolge selbst ist ein Zeiger. Wenn Sie also auf eine Zeichenfolge zeigen müssen, benötigen Sie einen Doppelzeiger.


5

Beispielsweise möchten Sie möglicherweise sicherstellen, dass Sie den Zeiger anschließend auf null setzen, wenn Sie den Speicher von etwas freigeben.

void safeFree(void** memory) {
    if (*memory) {
        free(*memory);
        *memory = NULL;
    }
}

Wenn Sie diese Funktion aufrufen, rufen Sie sie mit der Adresse eines Zeigers auf

void* myMemory = someCrazyFunctionThatAllocatesMemory();
safeFree(&myMemory);

Jetzt myMemoryist NULL eingestellt und jeder Versuch, es wiederzuverwenden, ist offensichtlich falsch.


1
es sollte sein if(*memory)undfree(*memory);
Asha

1
Guter Punkt, Signalverlust zwischen Gehirn und Tastatur. Ich habe es bearbeitet, um ein bisschen mehr Sinn zu machen.
Jeff Foster

Warum können wir nicht Folgendes tun ... void safeFree (void * memory) {if (memory) {free (memory); Speicher = NULL; }}
Peter_pk

@Peter_pk Das Zuweisen von Speicher zu Null würde nicht helfen, da Sie einen Zeiger nach Wert und nicht nach Referenz übergeben haben (daher das Beispiel eines Zeigers auf einen Zeiger).
Jeff Foster

2

Zum Beispiel, wenn Sie zufälligen Zugriff auf nicht zusammenhängende Daten wünschen.

p -> [p0, p1, p2, ...]  
p0 -> data1
p1 -> data2

- in C.

T ** p = (T **) malloc(sizeof(T*) * n);
p[0] = (T*) malloc(sizeof(T));
p[1] = (T*) malloc(sizeof(T));

Sie speichern einen Zeiger p, der auf ein Array von Zeigern zeigt. Jeder Zeiger zeigt auf ein Datenelement.

Wenn sizeof(T)es groß ist, ist es möglicherweise nicht möglich, einen zusammenhängenden Block (dh mit malloc) von sizeof(T) * nBytes zuzuweisen .


1
Keine (!) Notwendigkeit, das Ergebnis von Malloc in C.
Alk

2

Eine Sache, für die ich sie ständig benutze, ist, wenn ich eine Reihe von Objekten habe und sie anhand verschiedener Felder nachschlagen (binäre Suche) muss.
Ich behalte das ursprüngliche Array ...

int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);

Erstellen Sie dann ein Array sortierter Zeiger auf die Objekte.

int compare_object_by_name( const void *v1, const void *v2 ) {
  OBJECT *o1 = *(OBJECT **)v1;
  OBJECT *o2 = *(OBJECT **)v2;
  return (strcmp(o1->name, o2->name);
}

OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects);
  int i = 0;
  for( ; i<num_objects; i++)
    object_ptrs_by_name[i] = original_array+i;
  qsort(object_ptrs_by_name, num_objects, sizeof(OBJECT *), compare_object_by_name);

Sie können so viele sortierte Zeigerarrays erstellen, wie Sie benötigen, und dann eine binäre Suche im sortierten Zeigerarray verwenden, um auf das Objekt zuzugreifen, das Sie mit den vorhandenen Daten benötigen. Das ursprüngliche Array von Objekten kann unsortiert bleiben, aber jedes Zeigerarray wird nach dem angegebenen Feld sortiert.


2

Warum Doppelzeiger?

Ziel ist es, mithilfe einer Funktion zu ändern, worauf studentA zeigt.

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


typedef struct Person{
    char * name;
} Person; 

/**
 * we need a ponter to a pointer, example: &studentA
 */
void change(Person ** x, Person * y){
    *x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct.
}

void dontChange(Person * x, Person * y){
    x = y;
}

int main()
{

    Person * studentA = (Person *)malloc(sizeof(Person));
    studentA->name = "brian";

    Person * studentB = (Person *)malloc(sizeof(Person));
    studentB->name = "erich";

    /**
     * we could have done the job as simple as this!
     * but we need more work if we want to use a function to do the job!
     */
    // studentA = studentB;

    printf("1. studentA = %s (not changed)\n", studentA->name);

    dontChange(studentA, studentB);
    printf("2. studentA = %s (not changed)\n", studentA->name);

    change(&studentA, studentB);
    printf("3. studentA = %s (changed!)\n", studentA->name);

    return 0;
}

/**
 * OUTPUT:
 * 1. studentA = brian (not changed)
 * 2. studentA = brian (not changed)
 * 3. studentA = erich (changed!)
 */

1
Keine (!) Notwendigkeit, das Ergebnis von malloc in C.
alk

2

Das folgende einfache C ++ - Beispiel zeigt, dass Sie einen Zeiger auf einen Zeiger benötigen , wenn Sie eine Funktion zum Setzen eines Zeigers auf ein Objekt verwenden möchten . Andernfalls wird der Zeiger immer wieder auf Null zurückgesetzt .

(Eine C ++ - Antwort, aber ich glaube, dass es in C dasselbe ist.)

(Als Referenz: Google ("Übergeben des Werts c ++") = "Standardmäßig werden Argumente in C ++ als Wert übergeben. Wenn ein Argument als Wert übergeben wird, wird der Wert des Arguments in den Parameter der Funktion kopiert.")

Wir wollen also den Zeiger bgleich dem String setzen a.

#include <iostream>
#include <string>

void Function_1(std::string* a, std::string* b) {
  b = a;
  std::cout << (b == nullptr);  // False
}

void Function_2(std::string* a, std::string** b) {
  *b = a;
  std::cout << (b == nullptr);  // False
}

int main() {
  std::string a("Hello!");
  std::string* b(nullptr);
  std::cout << (b == nullptr);  // True

  Function_1(&a, b);
  std::cout << (b == nullptr);  // True

  Function_2(&a, &b);
  std::cout << (b == nullptr);  // False
}

// Output: 10100

Was passiert an der Leitung Function_1(&a, b);?

  • Der "Wert" von &main::a(einer Adresse) wird in den Parameter kopiert std::string* Function_1::a. Daher Function_1::aist ein Zeiger auf (dh die Speicheradresse von) der Zeichenfolge main::a.

  • Der "Wert" von main::b(eine Adresse im Speicher) wird in den Parameter kopiert std::string* Function_1::b. Daher befinden sich jetzt 2 dieser Adressen im Speicher, beide Nullzeiger. In der Zeile b = a;wird die lokale Variable Function_1::bdann in gleich Function_1::a(= &main::a) geändert , die Variable main::bbleibt jedoch unverändert. Nach dem Aufruf Function_1, main::bist immer noch ein Null - Zeiger.

Was passiert an der Leitung Function_2(&a, &b);?

  • Die Behandlung der aVariablen ist dieselbe: Innerhalb der Funktion Function_2::abefindet sich die Adresse der Zeichenfolge main::a.

  • Die Variable bwird jetzt jedoch als Zeiger auf einen Zeiger übergeben. Der "Wert" von &main::b(die Adresse des Zeigers main::b ) wird kopiert std::string** Function_2::b. Daher wird in Function_2 eine Dereferenzierung vorgenommen, um darauf *Function_2::bzuzugreifen und Änderungen vorzunehmen main::b. Die Zeile *b = a;setzt also tatsächlich main::b(eine Adresse) gleich Function_2::a(= Adresse von main::a), was wir wollen.

Wenn Sie eine Funktion zum Ändern eines Objekts verwenden möchten, sei es ein Objekt oder eine Adresse (Zeiger), müssen Sie einen Zeiger auf dieses Objekt übergeben. Das Objekt, das Sie tatsächlich übergeben, kann nicht geändert werden (im aufrufenden Bereich), da eine lokale Kopie erstellt wird.

(Eine Ausnahme ist, wenn der Parameter eine Referenz ist, wie z std::string& a. B.. In der Regel sind dies jedoch const. Wenn Sie ein Objekt aufrufen f(x), xsollten Sie im Allgemeinen davon ausgehen können, dass f es sich nicht ändert x. Wenn xes sich jedoch um einen Zeiger handelt, sollten Sie dies tun Angenommen, dies f könnte das Objekt ändern, auf das von gezeigt wird x.)


C ++ - Code zur Beantwortung einer C-Frage ist nicht die beste Idee.
Alk

1

Ein bisschen zu spät zur Party, aber hoffentlich hilft das jemandem.

In C-Arrays wird immer Speicher auf dem Stapel zugewiesen, daher kann eine Funktion kein (nicht statisches) Array zurückgeben, da der auf dem Stapel zugewiesene Speicher automatisch freigegeben wird, wenn die Ausführung das Ende des aktuellen Blocks erreicht. Das ist wirklich ärgerlich, wenn Sie sich mit zweidimensionalen Arrays (dh Matrizen) befassen und einige Funktionen implementieren möchten, die Matrizen ändern und zurückgeben können. Um dies zu erreichen, können Sie einen Zeiger zu Zeiger verwenden, um eine Matrix mit dynamisch zugewiesenem Speicher zu implementieren:

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    double** A = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(A == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols floats
    for(int i = 0; i < num_rows; i++){
        A[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(A[i] == NULL){
            for(int j = 0; j < i; j++){
                free(A[j]);
            }
            free(A);
            return NULL;
        }
    }
    return A;
} 

Hier ist eine Illustration:

double**       double*           double
             -------------       ---------------------------------------------------------
   A ------> |   A[0]    | ----> | A[0][0] | A[0][1] | A[0][2] | ........ | A[0][cols-1] |
             | --------- |       ---------------------------------------------------------
             |   A[1]    | ----> | A[1][0] | A[1][1] | A[1][2] | ........ | A[1][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             |   A[i]    | ----> | A[i][0] | A[i][1] | A[i][2] | ........ | A[i][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             | A[rows-1] | ----> | A[rows-1][0] | A[rows-1][1] | ... | A[rows-1][cols-1] |
             -------------       ---------------------------------------------------------

Der Doppelzeiger-zu-Doppelzeiger A zeigt auf das erste Element A [0] eines Speicherblocks, dessen Elemente selbst Doppelzeiger sind. Sie können sich diese Doppelzeiger als Zeilen der Matrix vorstellen. Aus diesem Grund reserviert jeder Doppelzeiger Speicher für num_cols-Elemente vom Typ double. Außerdem zeigt A [i] auf die i-te Zeile, dh A [i] zeigt auf A [i] [0] und das ist nur das erste Doppelelement des Speicherblocks für die i-te Zeile. Schließlich können Sie mit A [i] [j] einfach auf das Element in der i-ten Zeile und j-ten Spalte zugreifen.

Hier ist ein vollständiges Beispiel, das die Verwendung demonstriert:

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

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows double-pointers
    double** matrix = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(matrix == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols
    // doubles
    for(int i = 0; i < num_rows; i++){
        matrix[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(matrix[i] == NULL){
            for(int j = 0; j < i; j++){
                free(matrix[j]);
            }
            free(matrix);
            return NULL;
        }
    }
    return matrix;
}

/* Fills the matrix with random double-numbers between -1 and 1 */
void randn_fill_matrix(double** matrix, int rows, int cols){
    for (int i = 0; i < rows; ++i){
        for (int j = 0; j < cols; ++j){
            matrix[i][j] = (double) rand()/RAND_MAX*2.0-1.0;
        }
    }
}


/* Frees the memory allocated by the matrix */
void free_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        free(matrix[i]);
    }
    free(matrix);
}

/* Outputs the matrix to the console */
void print_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        for(int j = 0; j < cols; j++){
            printf(" %- f ", matrix[i][j]);
        }
        printf("\n");
    }
}


int main(){
    srand(time(NULL));
    int m = 3, n = 3;
    double** A = init_matrix(m, n);
    randn_fill_matrix(A, m, n);
    print_matrix(A, m, n);
    free_matrix(A, m, n);
    return 0;
}

0

Ich habe heute Doppelzeiger verwendet, als ich etwas für die Arbeit programmiert habe, damit ich antworten kann, warum wir sie verwenden mussten (es ist das erste Mal, dass ich tatsächlich Doppelzeiger verwenden musste). Wir mussten uns mit der Echtzeitcodierung von Frames befassen, die in Puffern enthalten sind, die Mitglieder einiger Strukturen sind. Im Encoder mussten wir einen Zeiger auf eine dieser Strukturen verwenden. Das Problem war, dass unser Zeiger geändert wurde, um auf andere Strukturen eines anderen Threads zu verweisen. Um die aktuelle Struktur im Encoder zu verwenden, musste ich einen Doppelzeiger verwenden, um auf den Zeiger zu zeigen, der in einem anderen Thread geändert wurde. Zumindest für uns war zunächst nicht klar, dass wir diesen Ansatz wählen mussten. Dabei wurden viele Adressen gedruckt :)).

Sie sollten doppelte Zeiger verwenden, wenn Sie an Zeigern arbeiten, die an anderen Stellen Ihrer Anwendung geändert wurden. Möglicherweise sind Doppelzeiger auch ein Muss, wenn Sie sich mit Hardware befassen, die zurückgibt und an Sie adressiert.


0

Vergleichen Sie den Änderungswert der Variablen mit dem Änderungswert des Zeigers :

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

void changeA(int (*a))
{
  (*a) = 10;
}

void changeP(int *(*P))
{
  (*P) = malloc(sizeof((*P)));
}

int main(void)
{
  int A = 0;

  printf("orig. A = %d\n", A);
  changeA(&A);
  printf("modi. A = %d\n", A);

  /*************************/

  int *P = NULL;

  printf("orig. P = %p\n", P);
  changeP(&P);
  printf("modi. P = %p\n", P);

  free(P);

  return EXIT_SUCCESS;
}

Dies hat mir geholfen, die Rückgabe des Zeigerwerts zu vermeiden, wenn der Zeiger durch die aufgerufene Funktion (die in einer einfach verknüpften Liste verwendet wird) geändert wurde.

ALT (schlecht):

int *func(int *P)
{
  ...
  return P;
}

int main(void)
{
  int *pointer;
  pointer = func(pointer);
  ...
}    

NEU (besser):

void func(int **pointer)
{
  ...
}

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.