Pytanie Dlaczego warto korzystać z podwójnej wskazówki? lub Dlaczego warto używać wskaźników do wskaźników?


Kiedy należy użyć podwójnego wskaźnika w C? Czy ktoś może wyjaśnić na przykładzie?

Wiem, że podwójny wskaźnik jest wskaźnikiem do wskaźnika. Dlaczego potrzebowałbym wskaźnika do wskaźnika?


179
2018-04-07 12:08


pochodzenie


Bądź ostrożny; wyrażenie "podwójna wskazówka" odnosi się również do typu double*. - Keith Thompson
Zanotuj: odpowiedź na to pytanie jest inna w C i C ++ - nie dodawaj znacznika c + do tego bardzo starego pytania. - BЈовић


Odpowiedzi:


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

Jeśli chcesz listę słów (zdanie), 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 (bio-biblioteki), możesz użyć char *****biolibrary

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

... ...

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


359
2018-04-07 12:23



lol............ - amanuel2
Być może najbardziej przyjazne mózgowi, przyziemne, realistyczne i pełne humoru wyjaśnienie wskaźnika wszechczasów w historii wskaźników.
Szkoda, że ​​cały Stack Overflow nie był tak czytelny - Definity
Jeden z moich podobnych (próbowałem być humorem) został usunięty, mówiąc: "StackOverflow nie lubi zabawy". : / - Dilip Raj Baral
wow ... jasne wyjaśnienie. - ajayramesh


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

W prostych słowach, Posługiwać się ** kiedy chcesz zachować (LUB zachować zmiany) Przydzielenie Pamięci lub Przypisanie nawet poza wywołaniem funkcji. (Tak, Przekaż taką funkcję z podwójnym wskaźnikiem arg.)

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

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

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

142
2018-04-07 12:11



co by było inaczej, gdyby przydzielił void allocate(int *p) i nazwaliście to allocate(p)? - アレックス
@AlexanderSupertramp Tak. Kod ulegnie segregacji. Zobacz odpowiedź Silviu. - Abhishek
@Asha jaka jest różnica między przydzielić (p) i przydzielić (& p)? - user2979872


Oto prosta odpowiedź !!!!

  • powiedzmy, że masz wskaźnik, że jego wartość jest adresem.
  • ale teraz chcesz zmienić ten adres.
  • można, wykonując wskaźnik1 = wskaźnik2, a wskaźnik1 będzie miał teraz adres wskaźnika2.
  • ALE! jeśli chcesz, aby funkcja zrobiła to za Ciebie, a chcesz, aby wynik pozostał po wykonaniu tej funkcji, musisz wykonać dodatkową pracę, potrzebujesz nowego wskaźnika3 tylko po to, aby wskazać wskaźnik1 i przekazać wskaźnik do funkcji.

  • tutaj jest zabawny przykład (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);
}
  • i tutaj jest wynik:
 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!!!. 

58
2018-06-08 20:00



To jest świetna odpowiedź i naprawdę pomogła mi unaocznić cel i użyteczność podwójnego wskaźnika. - Justin
@Justin czy sprawdziłeś moją odpowiedź powyżej tego? jego czystsze :) - Brian Joseph Spinos
Świetna odpowiedź, po prostu brakuje wyjaśnienia, że ​​<code> void cant_change (int * x, int * z) </ code> nie działa, ponieważ jego parametry są tylko nowymi wskaźnikami o lokalnym zasięgu, które są inicjowane podobnie do wskaźników a i f (więc nie są taki sam jak a i f). - Pedro Reis


Dodanie do Asha Odpowiedź, jeśli użyjesz pojedynczego wskaźnika do przykładu poniżej (na przykład alloc1 ()) stracisz 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;
   alloc1(p);
   //printf("%d ",*p);//value is undefined
   alloc2(&p);
   printf("%d ",*p);//will print 10
   free(p);
   return 0;
}

Powodem tego jest to, że w alloc1 wskaźnik jest przekazywany według wartości. Tak więc, kiedy jest ponownie przypisany do wyniku malloc zadzwoń do wewnątrz alloc1zmiana nie dotyczy kodu w innym zakresie.


32
2017-09-23 20:33



Co się dzieje, gdy p jest statycznym wskaźnikiem całkowitoliczbowym? Zgłaszanie awarii segmentacji. - kapilddit


1. Podstawowa koncepcja -

Kiedy deklarujesz, co następuje:

1. char * ch - (zwany wskaźnikiem znaków)
- ch zawiera adres pojedynczego znaku.
- (* ch) spowoduje dereferencję do wartości postaci.

2. char ** ch -
"ch" zawiera adres tablicy znaków. (jak w 1)
"* ch" zawiera adres pojedynczego znaku. (Należy zauważyć, że różni się od 1, ze względu na różnicę w deklaracji).
(** ch) spowoduje dereferencję do dokładnej wartości postaci.

Dodanie kolejnych wskaźników rozszerzy wymiar typu danych, od znaku do łańcucha znaków, do tablicy łańcuchów, itd. Możesz powiązać go z macierzą 1d, 2d, 3d.

Tak więc użycie wskaźnika zależy od tego, jak je 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 -
(obejmowałoby to również przekazanie 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ę "Przekazywana przez wartość" do tej funkcji.

Prawidłowy sposób to zrobić -

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 ten wymóg o aktualizowanie łańcucha 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 aktualizującego wartość ciągu.


19
2018-06-18 09:47



#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


Widziałem dzisiaj bardzo dobry przykład, od ten wpis na blogu, jak podsumowałem poniżej.

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

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

Teraz chcesz zaimplementować remove_if funkcja, która akceptuje kryterium usunięcia rm jako jeden z argumentów i przemierza połączoną listę: jeśli wpis spełnia kryterium (coś w stylu rm(entry)==true), jego węzeł zostanie usunięty z listy. Na końcu, remove_if zwraca głowę (która może być inna niż pierwotna głowa) 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;
}

jak twoje for pętla. Wiadomość brzmi: bez podwójnych wskaźników, musisz utrzymywać prev zmienna w celu reorganizacji wskaźnikówi poradzić sobie z dwoma różnymi przypadkami.

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 prev teraz, ponieważ możesz bezpośrednio modyfikować co prev->next wskazał.

Aby wszystko było wyraźniejsze, spójrzmy trochę na kod. Podczas usuwania:

  1. gdyby entry == *head: To będzie *head (==*curr) = *head->next - head wskazuje teraz na wskaźnik nowego węzła nagłówka. Robisz to przez bezpośrednią zmianę headjest zadowolony z nowego wskaźnika.
  2. gdyby entry != *head: podobnie, *curr jest co prev->next wskazał, a teraz wskazuje entry->next.

Bez względu na to, w którym przypadku, możesz ponownie zorganizować wskaźniki w jednolity sposób za pomocą podwójnych wskaźników.


16
2017-08-03 23:29





Wskaźniki do wskaźników są również użyteczne jako "uchwyty" do pamięci, w których chcesz przekazać "uchwyt" między funkcjami do ponownego zlokalizowania pamięci. 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 tego uchwytu, wskaże poprawnie nowo przeniesioną (lub przydzieloną) pamięć. Biblioteki lubią to robić z "nieprzezroczystymi" typami danych, czyli typami danych, nie musisz się martwić o to, co robią z wskazaną pamięcią, po prostu przekazujesz "uchwyt" pomiędzy funkcje biblioteki do wykonywania niektórych operacji w tej pamięci ... Funkcje biblioteczne mogą przydzielać i usuwać alokację pamięci pod maską bez konieczności jawnego martwienia się o proces zarządzania pamięcią lub o wskazanie uchwytu.

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


11
2018-04-07 14:52



Jaki jest powód, dla którego typ uchwytu jest niepodpisany char **? Czy pustka ** działałaby równie dobrze? - Connor Clark
unsigned char jest specjalnie używany, ponieważ przechowujemy wskaźnik do danych binarnych, które będą reprezentowane jako surowe bajty. Za pomocą void w pewnym momencie będzie wymagać odlewu i generalnie nie jest tak czytelny, jak cel tego, co się robi. - Jason


Struny 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 trzeba wskazać ciąg, potrzebny jest podwójny wskaźnik.


4
2018-04-07 12:13





Poniżej znajduje się bardzo prosty przykład C ++, który pokazuje, że jeśli chcesz użyć funkcji do ustawienia wskaźnika do wskazania obiektu, potrzebujesz wskaźnika do wskaźnika. Inaczej, wskaźnik będzie powracał do wartości zerowej.

(Odpowiedź C ++, ale uważam, że jest taka sama w C.)

(Również w celach informacyjnych: Google ("przekazywanie według wartości c ++") = "Domyślnie argumenty w C ++ są przekazywane przez wartość. Gdy argument jest przekazywany przez wartość, wartość argumentu jest kopiowana do parametru funkcji.")

Dlatego chcemy ustawić wskaźnik b ró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 (adres) jest kopiowany do parametru std::string* Function_1::a. W związku z tym Function_1::a jest wskaźnikiem do (to jest adresu pamięci) ciągu main::a.

  • Wartość main::b (adres w pamięci) jest kopiowany do parametru std::string* Function_1::b. Dlatego są teraz 2 z tych adresów w pamięci, oba wskaźniki zerowe. Na linii b = a;, zmienna lokalna Function_1::b jest następnie zmieniany na równy Function_1::a (= &main::a), ale zmienna main::b pozostaje niezmieniony. Po telefonie do Function_1, main::b nadal jest wskaźnikiem zerowym.

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

  • Leczenie a zmienna jest taka sama: w ramach funkcji Function_2::a jest adresem ciągu main::a.

  • Ale zmienna b jest teraz przekazywana jako wskaźnik do wskaźnika. Wartość &main::b ( adres wskaźnika  main::b) jest kopiowany do std::string** Function_2::b. Dlatego w ramach Function_2, dereferencje to jako *Function_2::b będzie mieć dostęp i modyfikować main::b . A więc linia *b = a; faktycznie ustawia main::b (adres) równy Function_2::a (= adres main::a) tego właśnie chcemy.

Jeśli chcesz użyć funkcji do modyfikacji rzeczy, czy to obiektu, czy adresu (wskaźnika), musisz przekazać wskaźnik do tej rzeczy. To, co ty tak właściwie Przekazywanie nie może być modyfikowane (w zasięgu wywoływania), ponieważ tworzona jest kopia lokalna.

(Wyjątek stanowi sytuacja, gdy parametr jest odniesieniem, na przykład std::string& a. Ale zazwyczaj tak jest const. Ogólnie, jeśli zadzwonisz f(x), gdyby x jest obiektem, który powinieneś być w stanie założyć f  przyzwyczajenie modyfikować x. Ale jeśli x jest wskaźnikiem, powinieneś to założyć f  moc zmodyfikuj obiekt wskazywany przez x.)


4
2018-02-22 19:55





Prosty przykład, który prawdopodobnie widziałeś już wiele razy

int main(int argc, char **argv)

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

Zwróć uwagę, że notacja wskaźnika (char* c) i notację tablicową (char c[]) są wymienne w argumentach funkcji. Więc możesz też napisać char *argv[]. Innymi słowy char *argv[] i char **argv są wymienne.

To, co powyżej oznacza, jest w istocie zbiorem sekwencji znaków (argumentów wiersza poleceń, które są nadawane programowi podczas uruchamiania).

Zobacz też ta odpowiedź po więcej szczegółów na temat powyższego podpisu funkcji.


4
2017-07-08 20:22



"notacja wskaźnikowa (char* c) i notację tablicową (char c[]) są wymienne " (i mają to samo dokładne znaczenie) w argumentach funkcji. Różnią się jednak zewnętrznymi argumentami funkcji. - pmg
@pmg - edytowałem twoje dane. Dzięki. - plats1