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);}
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:
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);return0;}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!!!.
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.
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.
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
typedefstruct 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 pointerfor(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:
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.
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.
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:
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 pointersint 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.
#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>typedefunsignedchar** handle_type;//some data_structure that the library functions would work withtypedefstruct{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);return0;}
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.
„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.
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.
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.
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.
Celem jest zmiana tego, na co wskazuje studentA, za pomocą funkcji.
#include<stdio.h>#include<stdlib.h>typedefstructPerson{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);return0;}/**
* OUTPUT:
* 1. studentA = brian (not changed)
* 2. studentA = brian (not changed)
* 3. studentA = erich (changed!)
*/
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.”)
„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źnikamain::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 fsię nie zmieni x. Ale jeśli xjest wskaźnikiem, powinieneś Załóżmy, że fmoże zmodyfikować obiekt wskazany przez x).
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-pointersdouble** A = calloc(num_rows,sizeof(double*));// return NULL if the memory couldn't allocatedif(A == NULL)return NULL;// For each double-pointer (row) allocate memory for num_cols floatsfor(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 memoryif(A[i]== NULL){for(int j =0; j < i; j++){
free(A[j]);}
free(A);return NULL;}}return A;}
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-pointersdouble** matrix = calloc(num_rows,sizeof(double*));// return NULL if the memory couldn't allocatedif(matrix == NULL)return NULL;// For each double-pointer (row) allocate memory for num_cols// doublesfor(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 memoryif(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);return0;}
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.
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).
double*
.Odpowiedzi:
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
Wynik:
źródło
arr[a][b][c]
nie jest***arr
. Wskaźnik wskaźników korzysta z referencji referencji, podczas gdyarr[a][b][c]
jest przechowywany jako zwykła tablica w głównej kolejności rzędów.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:
źródło
void allocate(int *p)
a ty nazwiesz to takallocate(p)
?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ć.
Oto wynik: ( najpierw przeczytaj )
źródło
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.
Powodem tego jest to, że
alloc1
wskaźnik jest przekazywany przez wartość. Tak więc, gdy zostanie ponownie przypisany do wynikumalloc
wywołania wewnątrzalloc1
, zmiana nie dotyczy kodu w innym zakresie.źródło
free(p)
to za mało, trzebaif(p) free(*p)
też*p
ocenia się doint
trzymania wartość 10, przekazując toint
, aby zwolnić () `jest to zły pomysł.alloc1()
wprowadza przeciek pamięci. Zwracana z funkcji wartość wskaźnika, która ma zostać przekazana za darmo, zostaje utracona.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
Teraz chcesz zaimplementować
remove_if
funkcję, która akceptuje kryterium usuwaniarm
jako jeden z argumentów i przegląda połączoną listę: jeśli pozycja spełnia kryterium (coś podobnegorm(entry)==true
), jej węzeł zostanie usunięty z listy. Na koniecremove_if
zwraca nagłówek (który może różnić się od oryginalnego nagłówka) połączonej listy.Możesz pisać
jako twoja
for
pętla. Komunikat jest taki, że bez podwójnych wskaźników musisz zachowaćprev
zmienną, aby ponownie zorganizować wskaźniki i obsłużyć dwa różne przypadki.Ale dzięki podwójnym wskaźnikom możesz pisać
Nie potrzebujesz
prev
teraz, ponieważ możesz bezpośrednio zmodyfikować to, coprev->next
wskazywało .Aby wszystko było bardziej zrozumiałe, postępujmy trochę według kodu. Podczas usuwania:
entry == *head
: będzie*head (==*curr) = *head->next
-head
teraz wskazuje na wskaźnik nowego węzła nagłówka. Robisz to poprzez bezpośrednią zmianęhead
zawartości na nowy wskaźnik.entry != *head
: podobnie*curr
jest to , coprev->next
wskazywało, a teraz wskazujeentry->next
.Bez względu na przypadek, możesz ponownie uporządkować wskaźniki w ujednolicony sposób z podwójnymi wskaźnikami.
źródło
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 ..
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:
Wyjście będzie AA. To nie działa, ponieważ masz funkcję „Pass By By Value”.
Prawidłowym sposobem na to byłoby -
Teraz rozszerz to wymaganie dotyczące aktualizacji ciągu zamiast znaku.
W tym celu musisz otrzymać parametr w funkcji jako podwójny wskaźnik.
W tym przykładzie metoda oczekuje podwójnego wskaźnika jako parametru do aktualizacji wartości ciągu.
ź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.char
wskaźników. Wskaźnik do tablicychar*
powinien być napisany na przykład w następujący sposób:char(*(*p)[42])
definiujep
jako wskaźnik do tablicy 42 wskaźników dochar
.*str = ...
str
dereferencyjne niezainicjowane wywołanie niezdefiniowanego zachowania.malloc(sizeof(char) * 10);
nie przydziela miejsca na 10 wskaźników,char
alechar
tylko na 10.for(i=0;i<10;i++) { str = ...
nie korzysta z indeksui
.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:
Mam nadzieję że to pomoże,
Jason
źródło
unsigned char
jest szczególnie używany, ponieważ przechowujemy wskaźnik do danych binarnych, które będą reprezentowane jako nieprzetworzone bajty. Używanievoid
będzie wymagać obsady w pewnym momencie i na ogół nie jest tak czytelne, jak cel tego, co się dzieje.Prosty przykład, który prawdopodobnie widziałeś wiele razy wcześniej
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łowychar *argv[]
ichar **argv
są 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.
źródło
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.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.
źródło
Na przykład możesz chcieć upewnić się, że po zwolnieniu pamięci czegoś ustawisz wskaźnik na zero.
Gdy wywołujesz tę funkcję, wywołujesz ją za pomocą adresu wskaźnika
Teraz
myMemory
jest ustawiony na NULL i każda próba jego ponownego użycia będzie oczywiście błędna.źródło
if(*memory)
ifree(*memory);
Na przykład, jeśli chcesz losowy dostęp do nieciągłych danych.
- w C
Przechowujesz wskaźnik,
p
któ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) * n
bajtów.źródło
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 ...
Następnie ułóż tablicę posortowanych wskaźników do obiektów.
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.
źródło
Dlaczego podwójne wskaźniki?
Celem jest zmiana tego, na co wskazuje studentA, za pomocą funkcji.
źródło
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
b
równy ciągowia
.Co dzieje się na linii
Function_1(&a, b);
?„Wartość”
&main::a
(adresu) jest kopiowana do parametrustd::string* Function_1::a
. DlategoFunction_1::a
jest wskaźnikiem do (tj. Adresu pamięci) łańcuchamain::a
.„Wartość”
main::b
(adres w pamięci) jest kopiowana do parametrustd::string* Function_1::b
. Dlatego w pamięci są teraz 2 adresy, oba wskaźniki zerowe. W wierszub = a;
zmienna lokalnaFunction_1::b
jest następnie zmieniana na równąFunction_1::a
(=&main::a
), ale zmiennamain::b
pozostaje niezmieniona. Po wywołaniuFunction_1
,main::b
jest jeszcze wskaźnik zerowy.Co dzieje się na linii
Function_2(&a, &b);
?Traktowanie
a
zmiennej jest takie samo: w funkcjiFunction_2::a
znajduje się adres ciągumain::a
.Ale zmienna
b
jest teraz przekazywana jako wskaźnik do wskaźnika. „Wartość”&main::b
( adres wskaźnikamain::b
) jest kopiowana dostd::string** Function_2::b
. Dlatego w ramach Function_2 wyrejestrowanie tego jako*Function_2::b
spowoduje dostęp i modyfikacjęmain::b
. Tak więc linia*b = a;
faktycznie ustawiamain::b
(adres) równyFunction_2::a
(= adresmain::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łujeszf(x)
, jeślix
jest to obiekt, powinieneś być w stanie założyć, żef
się nie zmienix
. Ale jeślix
jest wskaźnikiem, powinieneś Załóżmy, żef
może zmodyfikować obiekt wskazany przezx
).źródło
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ą:
Oto ilustracja:
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:
źródło
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.
źródło
Porównaj wartość modyfikującą zmiennej z wartością modyfikującą wskaźnika :
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):
NOWOŚĆ (lepiej):
źródło