Perché usare la doppia indiretta? o Perché usare i puntatori ai puntatori?


272

Quando si dovrebbe usare una doppia indiretta in C? Qualcuno può spiegare con un esempio?

Quello che so è che una doppia indiretta è un puntatore a un puntatore. Perché dovrei avere bisogno di un puntatore a un puntatore?


49
Stai attento; la frase "doppio puntatore" si riferisce anche al tipo double*.
Keith Thompson,

1
Prendi nota: la risposta a questa domanda è diversa per C e C ++ - non aggiungere tag c + a questa domanda molto vecchia.
BЈовић

Risposte:


479

Se vuoi avere un elenco di caratteri (una parola), puoi usare char *word

Se vuoi un elenco di parole (una frase), puoi usare char **sentence

Se vuoi un elenco di frasi (un monologo), puoi usare char ***monologue

Se vuoi un elenco di monologhi (una biografia), puoi usarlo char ****biography

Se si desidera un elenco di biografie (una bio-biblioteca), è possibile utilizzare char *****biolibrary

Se vuoi un elenco di bio-librerie (a ?? lol), puoi usare char ******lol

...

sì, so che queste potrebbero non essere le migliori strutture dati


Esempio di utilizzo con un lol molto molto molto noioso

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

Produzione:

parole totali nel mio lol: 243

7
Volevo solo sottolineare che a arr[a][b][c]non è a ***arr. Il puntatore dei puntatori utilizza riferimenti di riferimenti, mentre arr[a][b][c]è archiviato come un normale array nell'ordine principale delle righe.
MCCCS,

170

Uno dei motivi è che si desidera modificare il valore del puntatore passato a una funzione come argomento della funzione, per fare ciò è necessario un puntatore a un puntatore.

In parole semplici, utilizzare **quando si desidera conservare (o mantenere la modifica in) Allocazione o assegnazione di memoria anche al di fuori di una chiamata di funzione. (Quindi, passa tale funzione con doppio puntatore arg.)

Questo potrebbe non essere un ottimo esempio, ma ti mostrerà l'uso di base:

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

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

14
cosa sarebbe diverso se allocasse fosse void allocate(int *p)e lo chiamassi come allocate(p)?
ア レ ッ ク ス

@AlexanderSupertramp Sì. Il codice sarà segfault. Vedi la risposta di Silviu.
Abhishek,

@Asha qual è la differenza tra allocate (p) e allocate (& p)?
user2979872,

1
@Asha - Non possiamo semplicemente restituire il puntatore? Se dobbiamo tenerlo nullo, qual è un caso d'uso pratico di questo scenario?
Shabirmean,

91
  • Diciamo che hai un puntatore. Il suo valore è un indirizzo.
  • ma ora vuoi cambiare quell'indirizzo.
  • potresti. facendo ciò pointer1 = pointer2, date a pointer1 l'indirizzo di pointer2.
  • ma! se lo fai all'interno di una funzione e vuoi che il risultato persista dopo che la funzione è stata eseguita, devi fare del lavoro extra. hai bisogno di un nuovo pointer3 solo per puntare a pointer1. passa pointer3 alla funzione.

  • ecco un esempio. guarda prima l'output qui sotto, per capire.

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

Ecco l'output: ( leggi prima questo )

 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
Questa è un'ottima risposta e mi ha davvero aiutato a visualizzare lo scopo e l'utilità di un doppio puntatore.
Giustino,

1
@Justin hai controllato la mia risposta sopra questa? è più pulito :)
Brian Joseph Spinos,

10
Ottima risposta, manca solo di spiegare che <code> void cant_change (int * x, int * z) </code> fallisce perché i suoi 'parametri sono solo nuovi puntatori con ambito locale inizializzati allo stesso modo puntatori a ef (quindi non lo sono lo stesso di a e f).
Pedro Reis,

1
Semplice? Veramente? ;)
alk

1
questa risposta spiega davvero uno degli usi più comuni del puntatore ai puntatori, grazie!
tonyjosi,

49

Aggiungendo alla risposta di Asha , se si utilizza un singolo puntatore al seguente esempio (ad esempio alloc1 ()) si perderà il riferimento alla memoria allocata all'interno della funzione.

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

Il motivo per cui si verifica in questo modo è che nel alloc1puntatore viene passato per valore. Pertanto, quando viene riassegnato al risultato della mallocchiamata all'interno di alloc1, la modifica non riguarda il codice in un ambito diverso.


1
Cosa succede se p è un puntatore intero statico? Errore di segmentazione.
kapilddit,

free(p)non è abbastanza, devi farlo if(p) free(*p)anche tu
Shijing Lv

@ShijingLv: No. *pvaluta un intvalore di 10, passando inta free () `è una cattiva idea.
alk

L'allocazione effettuata in alloc1()introduce una perdita di memoria. Il valore del puntatore da passare libero viene perso tornando dalla funzione.
alk

Non è necessario (!) Lanciare il risultato di malloc in C.
alk,

23

Ho visto un ottimo esempio oggi, da questo post sul blog , come riassumo di seguito.

Immagina di avere una struttura per i nodi in un elenco collegato, che probabilmente lo è

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

Ora vuoi implementare una remove_iffunzione, che accetta un criterio di rimozione rmcome uno degli argomenti e attraversa l'elenco collegato: se una voce soddisfa il criterio (qualcosa di simile rm(entry)==true), il suo nodo verrà rimosso dall'elenco. Alla fine, remove_ifrestituisce la testa (che può essere diversa dalla testa originale) dell'elenco collegato.

Puoi scrivere

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

come il tuo forloop. Il messaggio è, senza doppi puntatori, è necessario mantenere una prevvariabile per riorganizzare i puntatori e gestire i due diversi casi.

Ma con doppi puntatori, puoi effettivamente scrivere

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

Non hai bisogno di un prevora perché puoi modificare direttamente ciò che ha prev->nextindicato .

Per chiarire le cose, seguiamo un po 'il codice. Durante la rimozione:

  1. if entry == *head: sarà *head (==*curr) = *head->next- headora punta al puntatore del nuovo nodo di intestazione. Puoi farlo cambiando direttamente headil contenuto in un nuovo puntatore.
  2. if entry != *head: allo stesso modo, *currè ciò che ha prev->nextindicato e ora indica entry->next.

In ogni caso, puoi riorganizzare i puntatori in modo unificato con doppi puntatori.


22

1. Concetto di base -

Quando dichiari quanto segue: -

1. char * ch - (chiamato pointer carattere)
- ch contiene l'indirizzo di un singolo carattere.
- (* ch) farà riferimento al valore del personaggio.

2. char ** ch -
'ch' contiene l'indirizzo di una matrice di puntatori di caratteri. (come in 1)
'* ch' contiene l'indirizzo di un singolo carattere. (Nota che è diverso da 1, a causa della differenza nella dichiarazione).
(** ch) farà riferimento al valore esatto del personaggio.

L'aggiunta di più puntatori espande la dimensione di un tipo di dati, da carattere a stringa, a matrice di stringhe e così via ... È possibile collegarlo a una matrice 1d, 2d, 3d ..

Quindi, l'uso del puntatore dipende da come lo dichiarate.

Ecco un semplice codice ..

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. Un'altra applicazione di puntatori doppi -
(questo coprirebbe anche il passaggio per riferimento)

Supponiamo di voler aggiornare un personaggio da una funzione. Se provi quanto segue: -

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

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

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

L'output sarà AA. Questo non funziona, poiché hai "Passed By Value" alla funzione.

Il modo corretto per farlo sarebbe:

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

Ora estendi questo requisito per aggiornare una stringa invece del carattere.
Per questo, è necessario ricevere il parametro nella funzione come doppio puntatore.

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 questo esempio, il metodo prevede un doppio puntatore come parametro per aggiornare il valore di una stringa.


#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; } Ma puoi farlo anche senza usare il doppio puntatore.
Kumar,

" char ** ch - 'ch' contiene l'indirizzo di una matrice di puntatori di caratteri. " No, contiene l'indirizzo del primo elemento di una matrice di charpuntatori. Un puntatore a un array di char*verrebbe digitato ad esempio in questo modo: char(*(*p)[42])definisce pcome puntatore a un array di 42 puntatori a char.
alk

L'ultimo frammento è completamente rotto. Per i principianti: ecco il *str = ... strdereferenziato non inizializzato che invoca un comportamento indefinito.
alk

Questo malloc(sizeof(char) * 10);non alloca spazio per 10 puntatori charma charsolo per 10 ..
alk

Questo ciclo non for(i=0;i<10;i++) { str = ... utilizza l'indice i.
alk

17

I puntatori ai puntatori sono utili anche come "handle" nella memoria in cui si desidera passare un "handle" tra le funzioni per riposizionare la memoria. Ciò significa fondamentalmente che la funzione può cambiare la memoria a cui punta il puntatore all'interno della variabile handle e che ogni funzione o oggetto che sta utilizzando l'handle punterà correttamente alla memoria appena riposizionata (o allocata). Alle biblioteche piace fare questo con tipi di dati "opachi", cioè tipi di dati dove non devi preoccuparti di quello che stanno facendo con la memoria puntata, fai semplicemente il giro tra il "handle" tra il funzioni della libreria per eseguire alcune operazioni su quella memoria ...

Per esempio:

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

Spero che questo ti aiuti,

Jason


Qual è la ragione per cui il tipo di handle è unsigned char **? Vuol dire che funzionerebbe altrettanto bene?
Connor Clark,

5
unsigned charviene utilizzato specificamente perché stiamo memorizzando un puntatore a dati binari che verranno rappresentati come byte non elaborati. L'uso voidrichiederà un cast ad un certo punto, e in genere non è così leggibile come l'intento di ciò che viene fatto.
Jason

7

Semplice esempio che probabilmente hai visto molte volte prima

int main(int argc, char **argv)

Nel secondo parametro ce l'hai: puntatore a puntatore a char.

Notare che la notazione del puntatore ( char* c) e la notazione dell'array ( char c[]) sono intercambiabili negli argomenti della funzione. Quindi potresti anche scrivere char *argv[]. In altre parole char *argv[]e char **argvsono intercambiabili.

Ciò che rappresenta quanto sopra è in effetti un array di sequenze di caratteri (gli argomenti della riga di comando che vengono forniti a un programma all'avvio).

Vedi anche questa risposta per maggiori dettagli sulla firma della funzione sopra.


2
"pointer notation ( char* c) e array notation ( char c[]) sono intercambiabili" (e hanno lo stesso significato esatto) negli argomenti della funzione . Sono diversi tuttavia argomenti di funzioni esterne.
pm

6

Le stringhe sono un ottimo esempio di utilizzo dei doppi puntatori. La stringa stessa è un puntatore, quindi ogni volta che devi puntare a una stringa, avrai bisogno di un doppio puntatore.


5

Ad esempio, potresti voler assicurarti che quando liberi la memoria di qualcosa, in seguito imposti il ​​puntatore su null.

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

Quando si chiama questa funzione, la si chiama con l'indirizzo di un puntatore

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

Ora myMemoryè impostato su NULL e qualsiasi tentativo di riutilizzarlo sarà ovviamente molto sbagliato.


1
dovrebbe essere if(*memory)efree(*memory);
Asha,

1
Buon punto, perdita di segnale tra cervello e tastiera. L'ho modificato per dare un po 'più senso.
Jeff Foster

Perché non possiamo fare quanto segue ... void safeFree (void * memory) {if (memory) {free (memory); memoria = NULL; }}
Peter_pk,

@Peter_pk Assegnare la memoria a null non sarebbe di aiuto perché hai passato un puntatore per valore, non per riferimento (da qui l'esempio di un puntatore a un puntatore).
Jeff Foster,

2

Ad esempio, se si desidera un accesso casuale a dati non contigui.

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

Si memorizza un puntatore pche punta a una matrice di puntatori. Ogni puntatore punta a un dato.

Se sizeof(T)è grande, potrebbe non essere possibile allocare un blocco contiguo (cioè usando malloc) di sizeof(T) * nbyte.


1
Non è necessario (!) Lanciare il risultato di malloc in C.
alk,

2

Una cosa per cui li uso costantemente è quando ho una matrice di oggetti e ho bisogno di eseguire ricerche (ricerca binaria) su di essi in diversi campi.
Conservo l'array originale ...

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

Quindi crea una matrice di puntatori ordinati per gli oggetti.

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

Puoi creare tutti gli array di puntatori ordinati di cui hai bisogno, quindi utilizzare una ricerca binaria sull'array di puntatori ordinati per accedere all'oggetto che ti serve dai dati che hai. L'array originale di oggetti può rimanere indifferenziato, ma ogni array di puntatori verrà ordinato in base al campo specificato.


2

Perché puntatori doppi?

L'obiettivo è cambiare ciò a cui punta studentA, usando una funzione.

#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
Non è necessario (!) Lanciare il risultato di malloc in C.
alk,

2

Di seguito è riportato un esempio C ++ molto semplice che mostra che se si desidera utilizzare una funzione per impostare un puntatore in modo che punti a un oggetto, è necessario un puntatore a un puntatore . Altrimenti, il puntatore continuerà a tornare a null .

(Una risposta in C ++, ma credo che sia la stessa in C.)

(Inoltre, per riferimento: Google ("passa per valore c ++") = "Per impostazione predefinita, gli argomenti in C ++ vengono passati per valore. Quando un argomento viene passato per valore, il valore dell'argomento viene copiato nel parametro della funzione.")

Quindi vogliamo impostare il puntatore buguale alla stringa 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

Cosa succede alla linea Function_1(&a, b);?

  • Il "valore" di &main::a(un indirizzo) viene copiato nel parametro std::string* Function_1::a. Pertanto Function_1::aè un puntatore a (cioè l'indirizzo di memoria della) stringa main::a.

  • Il "valore" di main::b(un indirizzo in memoria) viene copiato nel parametro std::string* Function_1::b. Pertanto ora ci sono 2 di questi indirizzi in memoria, entrambi i puntatori null. Alla riga b = a;, la variabile locale Function_1::bviene quindi cambiata in uguale Function_1::a(= &main::a), ma la variabile main::brimane invariata. Dopo la chiamata a Function_1, main::bè ancora un puntatore null.

Cosa succede alla linea Function_2(&a, &b);?

  • Il trattamento della avariabile è lo stesso: all'interno della funzione, Function_2::aè l'indirizzo della stringa main::a.

  • Ma la variabile bviene ora passata come puntatore a un puntatore. Il "valore" di &main::b(l' indirizzo del puntatore main::b ) viene copiato std::string** Function_2::b. Pertanto all'interno di Function_2, dereferenziandolo come *Function_2::baccederà e modificherà main::b. Quindi la linea *b = a;sta effettivamente impostando main::b(un indirizzo) uguale a Function_2::a(= indirizzo di main::a) che è quello che vogliamo.

Se vuoi usare una funzione per modificare una cosa, sia essa un oggetto o un indirizzo (puntatore), devi passare un puntatore a quella cosa. La cosa che si passa effettivamente non può essere modificata (nell'ambito della chiamata) perché viene creata una copia locale.

(Un'eccezione è se il parametro è un riferimento, come ad esempio std::string& a. Ma di solito questi sono const. In generale, se si chiama f(x), se xè un oggetto che si dovrebbe essere in grado di assumere che f non si modifica x. Ma se xè un puntatore, allora si dovrebbe supponiamo che f potrebbe modificare l'oggetto a cui punta x).


Il codice C ++ per rispondere a una domanda C non è la migliore idea.
alk

1

Un po 'tardi alla festa, ma spero che questo possa aiutare qualcuno.

Nelle matrici C allocare sempre memoria nello stack, quindi una funzione non può restituire un array (non statico) a causa del fatto che la memoria allocata nello stack viene liberata automaticamente quando l'esecuzione raggiunge la fine del blocco corrente. È davvero fastidioso quando si desidera gestire matrici bidimensionali (ovvero matrici) e implementare alcune funzioni che possono alterare e restituire matrici. A tale scopo, è possibile utilizzare un puntatore a puntatore per implementare una matrice con memoria allocata dinamicamente:

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

Ecco un'illustrazione:

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] |
             -------------       ---------------------------------------------------------

Il doppio puntatore al doppio puntatore A punta al primo elemento A [0] di un blocco di memoria i cui elementi sono puntatori doppi stessi. Puoi immaginare questi doppi puntatori come le righe della matrice. Questo è il motivo per cui ogni doppio puntatore alloca la memoria per gli elementi num_cols di tipo double. Inoltre A [i] punta alla riga i-es, cioè A [i] punta ad A [i] [0] e questo è solo il primo doppio elemento del blocco di memoria per la riga i. Infine, puoi accedere facilmente all'elemento nella riga i-esima e nella colonna j-esima con A [i] [j].

Ecco un esempio completo che dimostra l'utilizzo:

#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

Oggi ho usato doppi puntatori mentre stavo programmando qualcosa per lavoro, quindi posso rispondere perché abbiamo dovuto usarli (è la prima volta che devo effettivamente usare doppi puntatori). Abbiamo avuto a che fare con la codifica in tempo reale dei frame contenuti nei buffer che sono membri di alcune strutture. Nell'encoder abbiamo dovuto usare un puntatore a una di quelle strutture. Il problema era che il nostro puntatore veniva modificato per puntare ad altre strutture da un altro thread. Per utilizzare la struttura corrente nell'encoder, ho dovuto usare un doppio puntatore, al fine di puntare al puntatore che veniva modificato in un altro thread. All'inizio non era ovvio, almeno per noi, che dovevamo adottare questo approccio. Nel processo sono stati stampati molti indirizzi :)).

DOVREBBE utilizzare doppi puntatori quando si lavora su puntatori che vengono modificati in altri punti dell'applicazione. Potresti anche trovare doppi puntatori come un must quando hai a che fare con hardware che ritorna e ti indirizza.


0

Confronta il valore di modifica della variabile con il valore di modifica del puntatore :

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

Questo mi ha aiutato a evitare di restituire il valore del puntatore quando il puntatore è stato modificato dalla funzione chiamata (utilizzata nella lista singolarmente collegata).

VECCHIO (cattivo):

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

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

NOVITÀ (migliore):

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

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.