webentwicklung-frage-antwort-db.com.de

Warum Doppelzeiger verwenden? oder Warum Zeiger auf Zeiger verwenden?

Wann sollte ein Doppelzeiger in C verwendet werden? Kann jemand mit einem Beispiel erklären?

Was ich weiß ist, dass ein Doppelzeiger ein Zeiger auf einen Zeiger ist. Warum brauche ich einen Zeiger auf einen Zeiger?

203
manju

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

Wenn Sie eine Liste von Wörtern (einen Satz) wünschen, können Sie char **sentence verwenden.

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

Wenn Sie eine Liste von Monologen (eine Biografie) wünschen, können Sie char ****biography verwenden.

Wenn Sie eine Liste von Biografien (eine Biobibliothek) wünschen, können Sie char *****biolibrary verwenden.

Wenn Sie eine Liste von Biobibliotheken (a ?? lol) wünschen, können Sie char ******lol verwenden.

... ...

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:

gesamtwörter in meinem Lol: 243
414
pmg

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 Sie **, wenn Sie die Speicherzuweisung oder -zuweisung auch außerhalb eines Funktionsaufrufs beibehalten (ODER beibehalten) möchten. (So übergeben Sie eine solche Funktion mit dem Doppelzeiger arg.)

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

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

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

Hier ist eine einfache Antwort !!!!

  • nehmen wir an, Sie haben einen Zeiger, dessen Wert eine Adresse ist.
  • aber jetzt möchten Sie diese Adresse ändern.
  • sie könnten dies tun, indem Sie pointer1 = pointer2 ausführen, und pointer1 hätte jetzt die Adresse von pointer2.
  • ABER! Wenn Sie möchten, dass eine Funktion das für Sie erledigt, und Sie möchten, dass das Ergebnis nach dem Ausführen der Funktion bestehen bleibt, müssen Sie zusätzliche Arbeit erledigen, Sie benötigen einen neuen pointer3, der nur auf pointer1 zeigt, und übergeben Sie pointer3 an die Funktion.

  • hier ist ein lustiges Beispiel (schauen Sie sich zuerst die Ausgabe 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);
}
  • und hier ist die Ausgabe:
 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!!!. 
67

Wenn Sie zusätzlich zu Asha's / Antwort einen einzelnen Zeiger auf das Beispiel unten verwenden (z. B. zuweis1 ()), verlieren Sie die Referenz auf den innerhalb 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;
   alloc1(p);
   //printf("%d ",*p);//value is undefined
   alloc2(&p);
   printf("%d ",*p);//will print 10
   free(p);
   return 0;
}

Der Grund dafür ist, dass in alloc1 der Zeiger per Wert übergeben wird. Wenn es also dem Ergebnis des malloc-Aufrufs in alloc1 zugewiesen wird, bezieht sich die Änderung nicht auf Code in einem anderen Bereich.

39
Silviu

1. Grundkonzept -

Wenn Sie wie folgt erklären: -

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

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 aufgrund von unterschiedlichen Deklarationen von 1 abweicht).
(** ch) wird auf den genauen Wert des Zeichens dereferenziert.

Durch Hinzufügen weiterer Zeiger wird die Dimension eines Datentyps erweitert, von Zeichen zu String, zu String-Array usw. Sie können sie mit einer 1d-, 2d- und 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 weitere Anwendung von Doppelzeigern -
(dies würde auch den Referenzdurchlauf umfassen)

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 wird AA sein. Dies funktioniert nicht, da Sie die Funktion mit "Übergeben von Wert" versehen haben.

Der korrekte 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, um eine Zeichenfolge anstelle eines Zeichens zu aktualisieren.
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.

22
Bhavuk Mathur

Ich habe heute ein sehr gutes Beispiel gesehen, aus diesem Blogbeitrag , 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_if-Funktion implementieren, die ein Entfernungskriterium rm als eines der Argumente akzeptiert und die verknüpfte Liste durchquert: Wenn ein Eintrag das Kriterium erfüllt (z. B. rm(entry)==true), wird sein Knoten aus der Liste entfernt. Am Ende gibt remove_if den Kopf der verknüpften Liste zurück (der vom ursprünglichen Kopf abweichen kann).

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 for-Schleife. Die Nachricht lautet ohne Doppelzeiger. Sie müssen eine Variable prev verwalten, um die Zeiger neu zu ordnen und die zwei 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 benötigen jetzt keine prev, da Sie direkt ändern können, was prev->next auf zeigt.

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

  1. wenn entry == *head: es wird *head (==*curr) = *head->next - head nun auf den Zeiger des neuen Überschriftenknotens zeigen. Sie tun dies, indem Sie den Inhalt von head direkt in einen neuen Zeiger ändern.
  2. wenn entry != *head: Ähnlich ist *curr das, worauf prev->next hingewiesen hat, und zeigt jetzt auf entry->next.

In jedem Fall können Sie die Zeiger mit Doppelzeigern einheitlich neu organisieren.

19
ziyuang

Zeiger auf Zeiger sind auch als "Handles" für den Speicher nützlich, an denen Sie einen "Handle" zwischen Funktionen übergeben möchten, um den Speicher wieder zu lokalisieren. Das bedeutet im Wesentlichen, dass die Funktion den Speicher ändern kann, auf den der Zeiger in der Handle-Variablen zeigt, und dass jede Funktion oder jedes Objekt, das den Handle verwendet, ordnungsgemäß auf den neu verlagerten (oder zugewiesenen) Speicher verweist. Bibliotheken mögen dies mit "undurchsichtigen" Datentypen, dh Datentypen, wenn Sie sich keine Gedanken darüber machen müssen, was sie mit dem Speicher tun, auf den verwiesen wird. Sie übergeben einfach den "Griff" zwischen den beiden Funktionen der Bibliothek, um einige Vorgänge in diesem Speicher auszuführen ... Die Bibliotheksfunktionen können den Speicher unter der Haube zuordnen und die Zuweisung aufheben, ohne dass Sie sich explizit um den Prozess der Speicherverwaltung kümmern müssen oder wohin das Handle 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

15
Jason

Zeichenfolgen sind ein hervorragendes 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.

6
drysdam

Ein einfaches Beispiel, das Sie wahrscheinlich schon oft gesehen haben

int main(int argc, char **argv)

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

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

Was oben dargestellt ist, ist tatsächlich ein Array von Zeichenfolgen (die Befehlszeilenargumente, die einem Programm beim Start gegeben werden).

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

5
plats1

Beispielsweise möchten Sie möglicherweise sicherstellen, dass Sie den Zeiger auf null setzen, wenn Sie den Speicher 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);

Nun ist myMemory auf NULL gesetzt und jeder Versuch der Wiederverwendung wird offensichtlich falsch sein.

4
Jeff Foster

Das Folgende ist ein sehr einfaches C++ - Beispiel, das zeigt, dass, wenn Sie eine Funktion verwenden möchten, um einen Zeiger auf ein Objekt zu setzen, Sie benötigen einen Zeiger auf einen Zeiger. Andernfalls der Zeiger wird weiterhin auf null zurückgesetzt) ​​==.

(Eine C++ Antwort, aber ich glaube, es ist das gleiche in C.)

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

Also wollen wir den Zeiger b gleich dem String a setzen.

#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 in der Zeile Function_1(&a, b);?

  • Der "Wert" von &main::a (Eine Adresse) wird in den Parameter std::string* Function_1::a Kopiert. Daher ist Function_1::a Ein Zeiger (d. H. Die Speicheradresse von) der Zeichenfolge main::a.

  • Der "Wert" von main::b (Eine Adresse im Speicher) wird in den Parameter std::string* Function_1::b Kopiert. Daher befinden sich jetzt 2 dieser Adressen im Speicher, beide Nullzeiger. In der Zeile b = a; Wird die lokale Variable Function_1::b Auf den Wert Function_1::a (= &main::a) Geändert, die Variable main::b Jedoch unverändert. Nach dem Aufruf von Function_1 Ist main::b Immer noch ein Nullzeiger.

Was passiert in der Zeile Function_2(&a, &b);?

  • Die Behandlung der Variablen a ist dieselbe: In der Funktion ist Function_2::a Die Adresse des Strings main::a.

  • Aber die Variable b wird jetzt als Zeiger an einen Zeiger übergeben. Der "Wert" von &main::b (Die Adresse des Zeigersmain::b) Wird in std::string** Function_2::b Kopiert. Wenn Sie daher in Function_2 auf *Function_2::b Dereferenzieren, wird auf main::b Zugegriffen und dieses geändert. 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 Die tatsächliche Weitergabe kann (im aufrufenden Bereich) nicht geändert werden, da eine lokale Kopie erstellt wird.

(Eine Ausnahme ist, wenn der Parameter eine Referenz ist, z. B. std::string& a. In der Regel sind dies jedoch const. Wenn Sie f(x) aufrufen, wenn x ist ein Objekt, von dem Sie annehmen sollten, dass fx nicht ändert . Wenn jedoch x ein Zeiger ist, sollten Sie davon ausgehen, dass f das Objekt, auf das von verwiesen wird, ändern kann x.)

2
jt117

Zum Beispiel, wenn Sie willkürlichen 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) groß ist, kann es nicht möglich sein, einen zusammenhängenden Block (dh malloc) von sizeof(T) * n-Bytes zuzuweisen.

2
log0

Eine Sache, für die ich sie ständig verwende, ist, wenn ich ein Array von Objekten habe und nach verschiedenen Feldern nachschlagen (binäre Suche).
Ich behalte das ursprüngliche Array ...

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

Erstellen Sie dann ein Array von sortierten Zeigern 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 Zeiger-Arrays erstellen, wie Sie benötigen, und dann eine binäre Suche für das sortierte Zeiger-Array verwenden, um auf das gewünschte Objekt anhand der vorhandenen Daten zuzugreifen. Das ursprüngliche Array von Objekten kann unsortiert bleiben, aber jedes Zeigerarray wird nach seinem angegebenen Feld sortiert.

2
DavidMFrey

Als eine der Anwendungen des Doppelzeigers wird die Zeichenfolge so aktualisiert, dass die vorgenommenen Änderungen zurückgespiegelt werden.

#include <iostream>
#include <cstring>  // for using strcpy
using namespace std;

void change(char **temp)
{
   strcpy(temp[0],"new");
   strcpy(temp[1],"value");
}

int main()
{
   char **str;
   str = (char **)malloc(sizeof(char *)*3);
   str[0]=(char *)malloc(10);
   str[1]=(char *)malloc(10);
   strcpy(str[0],"old");
   strcpy(str[1],"name");
   char **temp = str;  // always use the temporary variable
   while(*temp!=NULL)
{
    cout<<*temp<<endl;
    temp++;
}
temp = str;   // making it point it to the original head because we have changed the address in while loop above
change(str);
while(*temp!=NULL)
{
   cout<<*temp<<endl;
   temp++;
}

free(temp);
free(str[0]);
free(str[1]);
free(str);
2
Abhishek D K

Ich habe heute Doppelzeiger verwendet, als ich etwas für die Arbeit programmiert habe, daher kann ich beantworten, warum wir sie verwenden mussten (es war das erste Mal, dass ich Doppelzeiger verwenden musste). Wir mussten uns mit der Echtzeit-Codierung von Frames beschäftigen, 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 so geändert wurde, dass er auf andere Strukturen aus einem anderen Thread verweist. 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. Zunächst war es zumindest für uns nicht offensichtlich, dass wir diesen Ansatz ergreifen mussten. Viele Adressen wurden dabei gedruckt :)).

Sie sollten Doppelzeiger verwenden, wenn Sie an Zeigern arbeiten, die an anderen Stellen Ihrer Anwendung geändert werden. Es kann auch sein, dass Doppelzeiger ein Muss sind, wenn Sie sich mit Hardware beschäftigen, die Sie zurücksendet und an Sie adressiert.

1
Axenie Ionut

Warum Doppelzeiger?

Ziel ist es, mit Hilfe einer Funktion zu ändern, worauf studentA verweist.

#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

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

In C-Arrays wird immer Speicher auf dem Stack zugewiesen. Daher kann eine Funktion kein (nicht statisches) Array zurückgeben, da der auf dem Stack 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 (d. H. Matrizen) befassen und einige Funktionen implementieren möchten, die Matrizen ändern und zurückgeben können. Um dies zu erreichen, können Sie eine Matrix mit dynamisch zugewiesenem Speicher mithilfe eines Zeigers auf Zeiger implementieren:

/* Initializes a matrix */
float** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    float** A = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(A == NULL) return NULL;
    // For each float-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:

 float**        float*            float
             -------------       ---------------------------------------------------------
   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 Float-Pointer-to-Float-Pointer A zeigt auf das erste Element A [0] eines Speicherblocks, dessen Elemente selbst Float-Pointer sind. Sie können sich diese Float-Zeiger als die Zeilen der Matrix vorstellen. Das ist der Grund, warum jeder Float-Zeiger Speicher für num_cols Float-Elemente reserviert. Außerdem zeigt A [i] auf die i-te Zeile, d. H. A [i] zeigt auf A [i] [0], und das ist nur das erste Float-Element 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 */
float** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    float** matrix = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(matrix == NULL) return NULL;
    // For each float-pointer (row) allocate memory for num_cols
    // floats
    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 float-numbers between -1 and 1 */
void randn_fill_matrix(float** matrix, int rows, int cols){
    for (int i = 0; i < rows; ++i){
        for (int j = 0; j < cols; ++j){
            matrix[i][j] = (float) Rand()/Rand_MAX*2.0f-1.0f;
        }
    }
}


/* Frees the memory allocated by the matrix */
void free_matrix(float** 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(float** 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;
    float** A = init_matrix(m, n);
    randn_fill_matrix(A, m, n);
    print_matrix(A, m, n);
    free_matrix(A, m, n);
    return 0;
}
1
joni

Vergleiche den modifizierenden Wert der Variablen mit dem modifizierenden Wert 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 geändert wurde (wird in einfach verknüpften Listen verwendet).

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);
  ...
}    
0
Sany

das folgende Beispiel, das ich gebe, wird einen Einblick oder eine Intuition darüber geben, wie Doppelzeiger funktionieren, ich werde die Schritte durchgehen

1) try to understand the following statements
   char **str ;

   a) str is of type char ** whose value is an address of another pointer.
   b) *str is of type char * whose value is an address of variable or (it is a string itself).
   c) **str is of type char ,gives the value stored, in this case a character.

das Folgende ist der Code, auf den Sie sich auf die obigen Punkte (a, b, c) beziehen können

str = (char **)malloc(sizeof(char *) *2); // here i am assigning the mem for two char *
       str[0]=(char *)"abcdefghij"; // assignin the value
       str[1]=(char *)"xyzlmnopqr"; 

um nun den Wert zu drucken, dh Strings im Array, schauen Sie sich nur den Punkt b, .__ an. Bei String sind sowohl der Wert als auch die Adresse gleich, daher müssen Sie ihn nicht erneut dereferenzieren.

cout<<*str<<endl;   // abcdefghij;

um nun die nächste Zeichenfolge zu drucken, kommen Sie aus einer Dereferenzierung, d. h. (*) von * str nach str, und erhöhen Sie sie dann wie unten gezeigt

str++;

jetzt den String ausdrucken

cout<<*str<<endl;        //xyzlmnopqr

jetzt nur Zeichen in einer Zeichenfolge drucken, siehe Punkt c)

cout<<**str<<endl;  // prints the first character i.e "a"

um nun das nächste Zeichen einer Zeichenfolge zu drucken, d. h. "b" .__, aus einem Dereferenzierungsoperator 1 zu kommen und ihn zu erhöhen, d. h. von ** str nach * str zu wechseln und * str ++ zu tun

*str++;

jetzt drucke das Zeichen

cout<<**str<<endl;  // prints the second character i.e "b"

da die beiden Arrays ("abcdefghij", "xylmnopqr") in einem fortlaufenden Speicherblock gespeichert werden, wenn die Adresse inkrementiert wird, werden alle Zeichen von zwei Zeichenfolgen gedruckt

0
Abhishek D K

Das folgende Beispiel wird hoffentlich einige Konzepte in Bezug auf Zeiger und Doppelzeiger, deren Unterschiede und Verwendung in gängigen Szenarien klären. 

    int* setptr(int *x)
    {
        printf("%u\n",&x);
        x=malloc(sizeof(int));
        *x=1;
        return x;
    }

In the above function setptr we can manipulate x either
1. by taking fn arg as int *x , doing  malloc and setting value of x and return x 
Or
    2. By taking arg as int ** and malloc and then set  **x value to some value.
Note: we cant set any general pointer directly without doing  malloc.Pointer indicates that it is a type of variable which can hold address of any data type.Now either we define a variable and give reference to it or we declare a pointer(int *x=NULL) and allocate some memory to it inside the called function where we pass x or a reference to it .. In either case we need to have address of a memory in the  pointer and in the case pointer initially points  to NULL or it is defined like int *x where it points  to any random address then we need to assign a valid memory address to pointer 

    1. either we need to allocate memory to it by malloc

    int *x=NULL means its address is 0.
    Now we need to either o following
    1.



    void main()
        {
            int *x;
            x=malloc
            *x=some_val;
        }
        Or
        void main()
        {
            int *x
            Fn(x);
        }

        void Fn(int **x)
        {
            *x=malloc;
            **x=5;
        }
        OR
        int * Fn(int *x)
        {
            x=malloc();
            *x=4;
            Return x;
        }


        2. Or we need to point it to a valid memory like a defined variable inside the function where pointer is defined.


        OR
        int main()
        {
            int a;
            int *x=&a;
            Fn(x);
            printf("%d",*x);
        }
        void Fn(int *x)
        {
            *x=2;
        }


     in both cases value pointed by x is changed inside fn

    But suppose if we do like


    int main()
    {
        int *x=NULL;
        printf("%u\n",sizeof(x));
        printf("%u\n",&x);
        x=setptr(x);
        //*x=2;
        printf("%d\n",*x);
        return 0;
    }

/* output
4
1
*/

#include<stdio.h>
void setptr(int *x)
{
    printf("inside setptr\n");
    printf("x=%u\n",x);
    printf("&x=%u\n",&x);
    //x=malloc(sizeof(int));
    *x=1;
    //return x;
}
int main()
{
    int *x=NULL;
    printf("x=%u\n",x);
    printf("&x=%u\n",&x);
    int a;
    x=&a;
    printf("x=%u\n",x);
    printf("&a=%u\n",&a);
    printf("&x=%u\n",&x);
    setptr(x);
    printf("inside main again\n");

    //*x=2;
    printf("x=%u\n",x);
    printf("&x=%u\n",&x);
    printf("*x=%d\n",*x);
    printf("a=%d\n",a);
    return 0;
}
0
Peter_pk

die Anwendung des Doppelzeigers, wie von Bhavuk Mathur gezeigt, scheint falsch zu sein .. Das folgende Beispiel ist das gültige

void func(char **str)
{
     strcpy(str[0],"second");
}

int main(){

    char **str;
    str = (char **)malloc(sizeof(char*)*1); // allocate 1 char* or string
    str[0] = (char *)malloc(sizeof(char)*10);      // allocate 10 character
    strcpy(str[0],"first");            // assign the string
    printf("%s\n",*str);
    func(str);            
    printf("%s\n",*str);           
    free(str[0]); 
    free(str);
}
0
Abhishek D K