Dlaczego warto stosować podwójną pośrednią? lub Po co używać wskaźników do wskaźników?

272

Kiedy należy zastosować podwójną pośrednią w C? Czy ktoś może wyjaśnić przykładem?

Wiem, że podwójna pośrednia to wskaźnik do wskaźnika. Dlaczego miałbym potrzebować wskaźnika do wskaźnika?

manju
źródło
49
Bądź ostrożny; wyrażenie „podwójny wskaźnik” odnosi się również do typu double*.
Keith Thompson,
Zanotuj: odpowiedź na to pytanie jest inna dla C i C ++ - nie dodawaj znacznika c + do tego bardzo starego pytania.
BЈовић

Odpowiedzi:

479

Jeśli chcesz mieć listę znaków (słowa), możesz użyć char *word

Jeśli chcesz listę słów (zdania), możesz użyć char **sentence

Jeśli chcesz listę zdań (monolog), możesz użyć char ***monologue

Jeśli chcesz listę monologów (biografia), możesz użyć char ****biography

Jeśli chcesz listę biografii (biobiblioteki), możesz użyć char *****biolibrary

Jeśli chcesz listę biobibliotek (a? Lol), możesz użyć char ******lol

... ...

tak, wiem, że to mogą nie być najlepsze struktury danych


Przykład użycia z bardzo, bardzo nudnym 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);
}

Wynik:

suma słów w moim lol: 243
pmg
źródło
6
Chciałem tylko zaznaczyć, że a arr[a][b][c]nie jest ***arr. Wskaźnik wskaźników korzysta z referencji referencji, podczas gdy arr[a][b][c]jest przechowywany jako zwykła tablica w głównej kolejności rzędów.
MCCCS,
170

Jednym z powodów jest to, że chcesz zmienić wartość wskaźnika przekazywanego do funkcji jako argument funkcji, aby to zrobić, potrzebujesz wskaźnika do wskaźnika.

Krótko mówiąc, użyj, **jeśli chcesz zachować (LUB zachować zmianę w) przydziale pamięci lub przydziale nawet poza wywołaniem funkcji. (Tak, podaj taką funkcję z podwójnym wskaźnikiem arg.)

To może nie być bardzo dobry przykład, ale pokaże podstawowe zastosowanie:

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

int main()
{
  int* p = NULL;
  allocate(&p);
  *p = 42;
  free(p);
}
Asha
źródło
14
co by było inaczej, gdyby alokacja była, void allocate(int *p)a ty nazwiesz to tak allocate(p)?
ア レ ッ ク ス
@AlexanderSupertramp Tak. Kod zostanie uszkodzony. Zobacz odpowiedź Silviu.
Abhishek
@Asha, jaka jest różnica między przydzielaniem (p) a przydzielaniem (& p)?
user2979872,
1
@Asha - Czy nie możemy po prostu zwrócić wskaźnika? Jeśli musimy zachować tę pustkę, jaki jest praktyczny przypadek zastosowania tego scenariusza?
Shabirmean
91
  • Powiedzmy, że masz wskaźnik. Jego wartością jest adres.
  • ale teraz chcesz zmienić ten adres.
  • mógłbyś. robiąc pointer1 = pointer2, dajesz pointer1 adres pointer2.
  • ale! jeśli robisz to w obrębie funkcji i chcesz, aby wynik był zachowany po wykonaniu funkcji, musisz wykonać dodatkową pracę. potrzebujesz nowego wskaźnika po3, aby wskazać wskaźnik1. przekazać wskaźnik do funkcji.

  • Oto przykład. najpierw spójrz na wynik poniżej, aby zrozumieć.

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

Oto wynik: ( najpierw przeczytaj )

 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!!!. 
Brian Joseph Spinos
źródło
4
To świetna odpowiedź i naprawdę pomogła mi wizualizować cel i użyteczność podwójnego wskaźnika.
Justin
1
@Justin sprawdziłeś moją odpowiedź powyżej tego? jego sprzątaczka :)
Brian Joseph Spinos 11.04.17
10
Świetna odpowiedź, po prostu brakuje wyjaśnienia, że ​​<code> void cant_change (int * x, int * z) </code> zawodzi, ponieważ jego parametry to tylko nowe wskaźniki o zasięgu lokalnym, które są inicjowane podobnie jak wskaźniki ai f (więc nie są to samo co a i f).
Pedro Reis,
1
Prosty? Naprawdę? ;)
alk
1
ta odpowiedź naprawdę wyjaśnia jedno z najczęstszych zastosowań wskaźnika do wskaźników, dzięki!
tonyjosi
48

Dodając do odpowiedzi Asha , jeśli użyjesz pojedynczego wskaźnika do poniższego przykładu (np. Replace1 ()), utracisz odniesienie do pamięci przydzielonej wewnątrz funkcji.

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

Powodem tego jest to, że alloc1wskaźnik jest przekazywany przez wartość. Tak więc, gdy zostanie ponownie przypisany do wyniku mallocwywołania wewnątrz alloc1, zmiana nie dotyczy kodu w innym zakresie.

Silviu
źródło
1
Co się stanie, jeśli p jest statycznym wskaźnikiem liczb całkowitych? Wystąpił błąd segmentacji.
kapilddit
free(p)to za mało, trzeba if(p) free(*p)też
Shijing Lv
@ShijingLv: Nie *pocenia się do inttrzymania wartość 10, przekazując to int, aby zwolnić () `jest to zły pomysł.
alk
Dokonany przydział alloc1()wprowadza przeciek pamięci. Zwracana z funkcji wartość wskaźnika, która ma zostać przekazana za darmo, zostaje utracona.
alk
Nie trzeba (!) Rzucać wyniku malloc w C.
alk
23

Widziałem dziś bardzo dobry przykład z tego postu na blogu , podsumowując poniżej.

Wyobraź sobie, że masz strukturę węzłów na połączonej liście, co prawdopodobnie jest

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

Teraz chcesz zaimplementować remove_iffunkcję, która akceptuje kryterium usuwania rmjako jeden z argumentów i przegląda połączoną listę: jeśli pozycja spełnia kryterium (coś podobnego rm(entry)==true), jej węzeł zostanie usunięty z listy. Na koniec remove_ifzwraca nagłówek (który może różnić się od oryginalnego nagłówka) połączonej listy.

Możesz pisać

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

jako twoja forpętla. Komunikat jest taki, że bez podwójnych wskaźników musisz zachować prevzmienną, aby ponownie zorganizować wskaźniki i obsłużyć dwa różne przypadki.

Ale dzięki podwójnym wskaźnikom możesz pisać

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

Nie potrzebujesz prevteraz, ponieważ możesz bezpośrednio zmodyfikować to, co prev->nextwskazywało .

Aby wszystko było bardziej zrozumiałe, postępujmy trochę według kodu. Podczas usuwania:

  1. if entry == *head: będzie *head (==*curr) = *head->next- headteraz wskazuje na wskaźnik nowego węzła nagłówka. Robisz to poprzez bezpośrednią zmianę headzawartości na nowy wskaźnik.
  2. jeśli entry != *head: podobnie *currjest to , co prev->nextwskazywało, a teraz wskazuje entry->next.

Bez względu na przypadek, możesz ponownie uporządkować wskaźniki w ujednolicony sposób z podwójnymi wskaźnikami.

ziyuang
źródło
22

1. Podstawowa koncepcja -

Gdy deklarujesz w następujący sposób:

1. char * ch - (zwany wskaźnikiem znaku)
- ch zawiera adres pojedynczego znaku.
- (* ch) spowoduje odwołanie do wartości znaku.

2. char ** ch -
„ch” zawiera adres tablicy wskaźników znaków. (jak w 1)
„* ch” zawiera adres pojedynczego znaku. (Należy pamiętać, że różni się od 1, ze względu na różnicę w deklaracji).
(** ch) spowoduje odwołanie do dokładnej wartości znaku.

Dodanie większej liczby wskaźników rozszerza wymiar typu danych, od znaku do ciągu, do tablicy ciągów itd. ... Możesz powiązać go z macierzą 1d, 2d, 3d.

Tak więc użycie wskaźnika zależy od tego, jak go zadeklarujesz.

Oto prosty kod ..

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. Kolejne zastosowanie podwójnych wskaźników -
(dotyczyłoby to również podania przez odniesienie)

Załóżmy, że chcesz zaktualizować znak z funkcji. Jeśli spróbujesz:

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

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

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

Wyjście będzie AA. To nie działa, ponieważ masz funkcję „Pass By By Value”.

Prawidłowym sposobem na to byłoby -

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

Teraz rozszerz to wymaganie dotyczące aktualizacji ciągu zamiast znaku.
W tym celu musisz otrzymać parametr w funkcji jako podwójny wskaźnik.

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

W tym przykładzie metoda oczekuje podwójnego wskaźnika jako parametru do aktualizacji wartości ciągu.

Bhavuk Mathur
źródło
#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; } Ale możesz to zrobić bez użycia podwójnego wskaźnika.
kumar
char ** ch - 'ch' zawiera adres tablicy wskaźników znaków. ” Nie, zawiera adres pierwszego elementu tablicy charwskaźników. Wskaźnik do tablicy char*powinien być napisany na przykład w następujący sposób: char(*(*p)[42])definiuje pjako wskaźnik do tablicy 42 wskaźników do char.
alk
Ostatni fragment jest całkowicie uszkodzony. Na początek: oto *str = ... strdereferencyjne niezainicjowane wywołanie niezdefiniowanego zachowania.
alk
To malloc(sizeof(char) * 10);nie przydziela miejsca na 10 wskaźników, charale chartylko na 10.
alk
Ta pętla for(i=0;i<10;i++) { str = ... nie korzysta z indeksu i.
alk
17

Wskaźniki do wskaźników są również przydatne jako „uchwyty” do pamięci, w których chcesz ominąć „uchwyt” między funkcjami, aby ponownie zlokalizować pamięć. Zasadniczo oznacza to, że funkcja może zmienić pamięć wskazywaną przez wskaźnik wewnątrz zmiennej uchwytu, a każda funkcja lub obiekt, który używa uchwytu, będzie poprawnie wskazywał na nowo przeniesioną (lub przydzieloną) pamięć. Biblioteki lubią to robić z „nieprzezroczystymi” typami danych, to znaczy typami danych, w których nie musisz się martwić tym, co robią z wskazaną pamięcią, po prostu omijasz „uchwyt” między funkcje biblioteki do wykonywania niektórych operacji na tej pamięci ...

Na przykład:

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

Mam nadzieję że to pomoże,

Jason

Jason
źródło
Jaki jest powód, dla którego typ uchwytu jest bez znaku char **? Czy void ** będzie działać równie dobrze?
Connor Clark
5
unsigned charjest szczególnie używany, ponieważ przechowujemy wskaźnik do danych binarnych, które będą reprezentowane jako nieprzetworzone bajty. Używanie voidbędzie wymagać obsady w pewnym momencie i na ogół nie jest tak czytelne, jak cel tego, co się dzieje.
Jason
7

Prosty przykład, który prawdopodobnie widziałeś wiele razy wcześniej

int main(int argc, char **argv)

W drugim parametrze masz go: wskaźnik do wskaźnika na char.

Zauważ, że notacja wskaźnika ( char* c) i notacja tablicowa ( char c[]) są wymiennymi argumentami funkcji. Więc możesz także pisać char *argv[]. Innymi słowy char *argv[]i char **argvsą wymienne.

Powyższe reprezentuje w rzeczywistości tablicę sekwencji znaków (argumenty wiersza poleceń podawane programowi podczas uruchamiania).

Zobacz także tę odpowiedź, aby uzyskać więcej informacji na temat powyższej sygnatury funkcji.

płytki 1
źródło
2
„notacja wskaźnikowa ( char* c) i notacja tablicowa ( char c[]) są wymienne” (i mają to samo dokładne znaczenie) w argumentach funkcji . Różnią się one jednak od argumentów funkcji.
pmg
6

Ciągi są doskonałym przykładem użycia podwójnych wskaźników. Sam ciąg jest wskaźnikiem, więc za każdym razem, gdy musisz wskazać ciąg, potrzebujesz podwójnego wskaźnika.

drysdam
źródło
5

Na przykład możesz chcieć upewnić się, że po zwolnieniu pamięci czegoś ustawisz wskaźnik na zero.

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

Gdy wywołujesz tę funkcję, wywołujesz ją za pomocą adresu wskaźnika

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

Teraz myMemoryjest ustawiony na NULL i każda próba jego ponownego użycia będzie oczywiście błędna.

Jeff Foster
źródło
1
powinno być if(*memory)ifree(*memory);
Asha
1
Dobra uwaga, utrata sygnału między mózgiem a klawiaturą. Zredagowałem go, aby miał trochę więcej sensu.
Jeff Foster,
Dlaczego nie możemy wykonać następujących czynności ... void safeFree (void * memory) {if (memory) {free (memory); pamięć = NULL; }}
Peter_pk
@Peter_pk Przypisanie pamięci do wartości null nie pomogłoby, ponieważ wskaźnik został przekazany przez wartość, a nie przez odwołanie (stąd przykład wskaźnika do wskaźnika).
Jeff Foster
2

Na przykład, jeśli chcesz losowy dostęp do nieciągłych danych.

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

- w C

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

Przechowujesz wskaźnik, pktóry wskazuje na tablicę wskaźników. Każdy wskaźnik wskazuje na kawałek danych.

Jeśli sizeof(T)jest duży, może nie być możliwe przydzielenie ciągłego bloku (tj. Użycie malloc) sizeof(T) * nbajtów.

log0
źródło
1
Nie trzeba (!) Rzucać wyniku malloc w C.
alk
2

Jedną z nich używam ich stale, gdy mam szereg obiektów i muszę wykonywać na nich wyszukiwania (wyszukiwanie binarne) według różnych pól.
Zachowuję oryginalny zestaw ...

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

Następnie ułóż tablicę posortowanych wskaźników do obiektów.

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

Możesz utworzyć dowolną liczbę posortowanych tablic wskaźników, a następnie użyć wyszukiwania binarnego w posortowanej tablicy wskaźników, aby uzyskać dostęp do potrzebnego obiektu na podstawie posiadanych danych. Oryginalna tablica obiektów może pozostać nieposortowana, ale każda tablica wskaźników zostanie posortowana według określonego pola.

DavidMFrey
źródło
2

Dlaczego podwójne wskaźniki?

Celem jest zmiana tego, na co wskazuje studentA, za pomocą funkcji.

#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!)
 */
Brian Joseph Spinos
źródło
1
Nie trzeba (!) Rzucać wyniku malloc w C.
alk
2

Poniżej znajduje się bardzo prosty przykład w C ++, który pokazuje, że jeśli chcesz użyć funkcji do ustawienia wskaźnika do obiektu, potrzebujesz wskaźnika do wskaźnika . W przeciwnym razie wskaźnik powróci do wartości zerowej .

(Odpowiedź w C ++, ale uważam, że tak samo jest w C.)

(Również w celach informacyjnych: Google („pass by value c ++”) = „Domyślnie argumenty w C ++ są przekazywane przez wartość. Gdy argument jest przekazywany przez wartość, wartość argumentu jest kopiowana do parametru funkcji.”)

Chcemy więc ustawić wskaźnik brówny ciągowi 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

Co dzieje się na linii Function_1(&a, b);?

  • „Wartość” &main::a(adresu) jest kopiowana do parametru std::string* Function_1::a. Dlatego Function_1::ajest wskaźnikiem do (tj. Adresu pamięci) łańcucha main::a.

  • „Wartość” main::b(adres w pamięci) jest kopiowana do parametru std::string* Function_1::b. Dlatego w pamięci są teraz 2 adresy, oba wskaźniki zerowe. W wierszu b = a;zmienna lokalna Function_1::bjest następnie zmieniana na równą Function_1::a(= &main::a), ale zmienna main::bpozostaje niezmieniona. Po wywołaniu Function_1, main::bjest jeszcze wskaźnik zerowy.

Co dzieje się na linii Function_2(&a, &b);?

  • Traktowanie azmiennej jest takie samo: w funkcji Function_2::aznajduje się adres ciągu main::a.

  • Ale zmienna bjest teraz przekazywana jako wskaźnik do wskaźnika. „Wartość” &main::b( adres wskaźnika main::b ) jest kopiowana do std::string** Function_2::b. Dlatego w ramach Function_2 wyrejestrowanie tego jako *Function_2::bspowoduje dostęp i modyfikację main::b. Tak więc linia *b = a;faktycznie ustawia main::b(adres) równy Function_2::a(= adres main::a), co jest tym, czego chcemy.

Jeśli chcesz użyć funkcji do modyfikacji rzeczy, czy to obiektu, czy adresu (wskaźnika), musisz przekazać wskaźnik do tej rzeczy. Rzeczy, którą faktycznie przekazujesz, nie można modyfikować (w zakresie wywoływania), ponieważ tworzona jest kopia lokalna.

(Wyjątkiem jest sytuacja, gdy parametr jest odwołaniem, np std::string& a. Ale zwykle są const. Ogólnie rzecz biorąc, jeśli wywołujesz f(x), jeśli xjest to obiekt, powinieneś być w stanie założyć, że f się nie zmieni x. Ale jeśli xjest wskaźnikiem, powinieneś Załóżmy, że f może zmodyfikować obiekt wskazany przez x).

jt117
źródło
Kod C ++ do odpowiedzi na pytanie C nie jest najlepszym pomysłem.
alk
1

Trochę późno na imprezę, ale mam nadzieję, że to komuś pomoże.

W tablicach C zawsze alokuje pamięć na stosie, dlatego funkcja nie może zwrócić tablicy (niestatycznej) ze względu na fakt, że pamięć przydzielona na stosie jest zwalniana automatycznie, gdy wykonanie osiągnie koniec bieżącego bloku. To naprawdę denerwujące, gdy chcesz poradzić sobie z tablicami dwuwymiarowymi (tj. Macierzami) i wdrożyć kilka funkcji, które mogą zmieniać i zwracać macierze. Aby to osiągnąć, możesz użyć wskaźnika od wskaźnika do implementacji macierzy z dynamicznie przydzielaną pamięcią:

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

Oto ilustracja:

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

Podwójny wskaźnik do podwójnego wskaźnika A wskazuje na pierwszy element A [0] bloku pamięci, którego elementami są same podwójne wskaźniki. Możesz sobie wyobrazić te podwójne wskaźniki jako rzędy macierzy. Dlatego każdy podwójny wskaźnik przydziela pamięć dla elementów num_cols typu double. Ponadto A [i] wskazuje na i-ty rząd, tj. A [i] wskazuje na A [i] [0], a to tylko pierwszy podwójny element bloku pamięci dla i-tego rzędu. Wreszcie możesz łatwo uzyskać dostęp do elementu w i-tym rzędzie i j-tej kolumnie za pomocą A [i] [j].

Oto kompletny przykład, który pokazuje użycie:

#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;
}
joni
źródło
0

Użyłem dziś podwójnych wskaźników podczas programowania czegoś do pracy, więc mogę odpowiedzieć, dlaczego musieliśmy ich używać (po raz pierwszy musiałem użyć podwójnych wskaźników). Mieliśmy do czynienia z kodowaniem w czasie rzeczywistym ramek zawartych w buforach, które są członkami niektórych struktur. W enkoderze musieliśmy użyć wskaźnika do jednej z tych struktur. Problem polegał na tym, że nasz wskaźnik był zmieniany, aby wskazywał na inne struktury z innego wątku. Aby użyć bieżącej struktury w koderze, musiałem użyć podwójnego wskaźnika, aby wskazać wskaźnik, który był modyfikowany w innym wątku. Na początku nie było oczywiste, przynajmniej dla nas, że musimy przyjąć takie podejście. Wiele adresów zostało wydrukowanych :)).

POWINIENEŚ używać podwójnych wskaźników podczas pracy ze wskaźnikami, które zostały zmienione w innych miejscach aplikacji. Możesz również znaleźć podwójne wskaźniki, które są niezbędne, gdy masz do czynienia ze sprzętem, który zwraca i adres do ciebie.

Axenie Ionut
źródło
0

Porównaj wartość modyfikującą zmiennej z wartością modyfikującą wskaźnika :

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

Pomogło mi to uniknąć zwracania wartości wskaźnika, gdy wskaźnik został zmodyfikowany przez wywoływaną funkcję (używaną na pojedynczo połączonej liście).

STARY (zły):

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

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

NOWOŚĆ (lepiej):

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

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
Sany
źródło