Faites attention; l'expression "double pointeur" fait également référence au type double*.
Keith Thompson
1
Prenez note: la réponse à cette question est différente pour C et C ++ - n'ajoutez pas de balise c + à cette très vieille question.
BЈовић
Réponses:
479
Si vous voulez avoir une liste de caractères (un mot), vous pouvez utiliser char *word
Si vous voulez une liste de mots (une phrase), vous pouvez utiliser char **sentence
Si vous voulez une liste de phrases (un monologue), vous pouvez utiliser char ***monologue
Si vous voulez une liste de monologues (une biographie), vous pouvez utiliser char ****biography
Si vous voulez une liste de biographies (une bio-bibliothèque), vous pouvez utiliser char *****biolibrary
Si vous voulez une liste de bio-bibliothèques (a ?? lol), vous pouvez utiliser char ******lol
... ...
oui, je sais que ce ne sont peut-être pas les meilleures structures de données
Exemple d'utilisation avec un lol très très très ennuyeux
#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);}
Je voulais juste souligner que un arr[a][b][c]n'est pas un ***arr. Le pointeur des pointeurs utilise des références de références, tandis que arr[a][b][c]est stocké comme un tableau habituel dans l'ordre principal des lignes.
MCCCS
170
L'une des raisons est que vous souhaitez modifier la valeur du pointeur transmis à une fonction comme argument de fonction, pour ce faire, vous avez besoin d'un pointeur vers un pointeur.
En termes simples, utilisez **lorsque vous souhaitez conserver (OU conserver la modification) l'allocation ou l'affectation de mémoire même en dehors d'un appel de fonction.(Donc, passez une telle fonction avec l'argument du double pointeur.)
Ce n'est peut-être pas un très bon exemple, mais vous montrera l'utilisation de base:
qu'est-ce qui serait différent si allocate était void allocate(int *p)et vous l'appeliez comme allocate(p)?
ア レ ッ ク ス
@AlexanderSupertramp Oui. Le code sera défectueux. Veuillez voir la réponse de Silviu.
Abhishek
@Asha quelle est la différence entre allocate (p) et allocate (& p)?
user2979872
1
@Asha - Ne pouvons-nous pas simplement retourner le pointeur? Si nous devons le garder vide, quel est un cas d'utilisation pratique de ce scénario?
Shabirmean
91
Disons que vous avez un pointeur. Sa valeur est une adresse.
mais maintenant vous voulez changer cette adresse.
vous pourriez. ce faisant pointer1 = pointer2, vous donnez à pointer1 l'adresse de pointer2.
mais! si vous effectuez cette opération dans une fonction et que vous souhaitez que le résultat persiste une fois la fonction terminée, vous devez effectuer un travail supplémentaire. vous avez besoin d'un nouveau pointeur3 pour pointer vers pointeur1. passez pointer3 à la fonction.
Voici un exemple. regardez d'abord la sortie ci-dessous, pour comprendre.
#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);}
Voici la sortie: ( lisez ceci en premier )
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!!!.
C'est une excellente réponse et m'a vraiment aidé à visualiser le but et l'utilité d'un double pointeur.
Justin
1
@Justin avez-vous vérifié ma réponse au-dessus de celle-ci? c'est plus propre :)
Brian Joseph Spinos
10
Excellente réponse, il suffit d'expliquer que <code> void cant_change (int * x, int * z) </code> échoue parce que ses paramètres ne sont que de nouveaux pointeurs de portée locale qui sont initialisés de la même manière que les pointeurs a et f (donc ils ne le sont pas comme a et f).
Pedro Reis
1
Facile? Vraiment? ;)
alk
1
cette réponse explique vraiment l'une des utilisations les plus courantes du pointeur vers des pointeurs, merci!
tonyjosi
50
Ajout à la réponse d' Asha , si vous utilisez un seul pointeur vers l'exemple ci-dessous (par exemple alloc1 ()), vous perdrez la référence à la mémoire allouée à l'intérieur de la fonction.
La raison pour laquelle cela se produit comme ceci est que alloc1le pointeur est transmis par valeur. Ainsi, lorsqu'il est réaffecté au résultat de l' mallocappel à l'intérieur de alloc1, la modification ne concerne pas le code dans une portée différente.
@ShijingLv: No. *pévalue à une intdétention la valeur de 10, passer ceci intà free () `est une mauvaise idée.
alk
L'allocation effectuée dans alloc1()introduit une fuite de mémoire. La valeur du pointeur à transmettre gratuitement est perdue en revenant de la fonction.
alk
1
Pas (!) Besoin de lancer le résultat de malloc en C.
alk
23
J'ai vu un très bon exemple aujourd'hui, à partir de ce billet de blog , comme je le résume ci-dessous.
Imaginez que vous ayez une structure pour les nœuds dans une liste chaînée, qui est probablement
Maintenant, vous voulez implémenter une remove_iffonction, qui accepte un critère de suppression rmcomme l'un des arguments et traverse la liste liée: si une entrée satisfait le critère (quelque chose comme rm(entry)==true), son nœud sera supprimé de la liste. Au final, remove_ifrenvoie la tête (qui peut être différente de la tête d'origine) de la liste chaînée.
Vous pouvez écrire
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;}
comme forboucle. Le message est que, sans pointeurs doubles, vous devez conserver une prevvariable pour réorganiser les pointeurs et gérer les deux cas différents.
Mais avec des pointeurs doubles, vous pouvez réellement écrire
// 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;}
Vous n'avez pas besoin d'un prevmaintenant car vous pouvez directement modifier ce qui prev->nextpointait .
Pour rendre les choses plus claires, suivons un peu le code. Lors de la suppression:
if entry == *head: ce sera *head (==*curr) = *head->next- headpointe maintenant vers le pointeur du nouveau noeud de titre. Pour ce faire, vous modifiez directement headle contenu de vers un nouveau pointeur.
si entry != *head: de la même manière, *currc'est ce qui prev->nextpointait et pointe maintenant entry->next.
Dans tous les cas, vous pouvez réorganiser les pointeurs de manière unifiée avec des pointeurs doubles.
1. char * ch - (appelé pointeur de caractère)
- ch contient l'adresse d'un seul caractère.
- (* ch) déréférera à la valeur du caractère.
2. char ** ch -
'ch' contient l'adresse d'un tableau de pointeurs de caractères. (comme dans 1)
'* ch' contient l'adresse d'un seul caractère. (Notez qu'il est différent de 1, en raison de la différence de déclaration).
(** ch) déréférencera la valeur exacte du caractère.
L'ajout de pointeurs étend la dimension d'un type de données, du caractère à la chaîne, au tableau de chaînes, etc. Vous pouvez le relier à une matrice 1d, 2d, 3d ..
Ainsi, l'utilisation du pointeur dépend de la façon dont vous le déclarez.
Voici un code simple ..
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. Une autre application des pointeurs doubles -
(cela couvrirait également le passage par référence)
Supposons que vous souhaitiez mettre à jour un caractère à partir d'une fonction. Si vous essayez ce qui suit: -
Étendez maintenant cette exigence pour mettre à jour une chaîne au lieu d'un caractère.
Pour cela, vous devez recevoir le paramètre dans la fonction sous la forme d'un double pointeur.
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);}
Dans cet exemple, la méthode attend un double pointeur comme paramètre pour mettre à jour la valeur d'une chaîne.
#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; } Mais vous pouvez aussi le faire sans utiliser de pointeur double.
kumar
" char ** ch - 'ch' contient l'adresse d'un tableau de pointeurs de caractères. " Non, il contient l'adresse du 1er élément d'un tableau de charpointeurs. Un pointeur vers un tableau de char*serait par exemple comme ceci: char(*(*p)[42])définit pcomme pointeur vers un tableau de 42 pointeurs vers char.
alk
Le dernier extrait est complètement cassé. Pour commencer: voici un *str = ...strappel non défini initialisé non référencé.
alk
Cela malloc(sizeof(char) * 10);n'alloue pas de place pour 10 pointeurs charmais pour 10 charseulement ..
alk
Cette boucle for(i=0;i<10;i++) { str = ... manque d'utiliser l'index i.
alk
17
Les pointeurs vers les pointeurs sont également utiles en tant que «poignées» vers la mémoire où vous voulez passer une «poignée» entre les fonctions vers la mémoire re-localisable. Cela signifie essentiellement que la fonction peut modifier la mémoire pointée par le pointeur à l'intérieur de la variable de poignée, et chaque fonction ou objet qui utilise la poignée pointera correctement vers la mémoire nouvellement déplacée (ou allouée). Les bibliothèques aiment faire cela avec des types de données "opaques", c'est-à-dire des types de données où vous n'avez pas à vous soucier de ce qu'ils font avec la mémoire pointée, vous passez simplement la "poignée" entre les fonctions de la bibliothèque pour effectuer certaines opérations sur cette mémoire ...
Par exemple:
#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;}
Quelle est la raison pour laquelle le type de poignée est un caractère non signé **? Est-ce que void ** fonctionnerait aussi bien?
Connor Clark
5
unsigned charest spécifiquement utilisé car nous stockons un pointeur sur des données binaires qui seront représentées sous forme d'octets bruts. L'utilisation voidnécessitera un cast à un moment donné et n'est généralement pas aussi lisible que l'intention de ce qui est fait.
Jason
7
Exemple simple que vous avez probablement déjà vu à plusieurs reprises
int main(int argc,char**argv)
Dans le deuxième paramètre, vous l'avez: pointeur vers pointeur vers char.
Notez que la notation du pointeur ( char* c) et la notation du tableau ( char c[]) sont interchangeables dans les arguments de fonction. Vous pouvez donc aussi écrire char *argv[]. En d'autres termes char *argv[]etchar **argv sont interchangeables.
Ce que cela représente est en fait un tableau de séquences de caractères (les arguments de ligne de commande qui sont donnés à un programme au démarrage).
Voir également cette réponse pour plus de détails sur la signature de fonction ci-dessus.
"la notation du pointeur ( char* c) et la notation du tableau ( char c[]) sont interchangeables" (et ont la même signification exacte) dans les arguments de fonction . Ce sont cependant des arguments de fonction différents.
pmg
6
Les chaînes sont un excellent exemple d'utilisation des pointeurs doubles. La chaîne elle-même est un pointeur, donc chaque fois que vous devez pointer vers une chaîne, vous aurez besoin d'un double pointeur.
Bon point, perte de signal entre le cerveau et le clavier. Je l'ai édité pour donner un peu plus de sens.
Jeff Foster
Pourquoi ne pouvons-nous pas faire ce qui suit ... void safeFree (void * memory) {if (memory) {free (memory); memory = NULL; }}
Peter_pk
@Peter_pk Assigner de la mémoire à null ne serait d'aucune utilité car vous avez passé un pointeur par valeur, pas par référence (d'où l'exemple d'un pointeur sur un pointeur).
Jeff Foster
2
Par exemple, si vous souhaitez un accès aléatoire à des données non contiguës.
p ->[p0, p1, p2,...]
p0 -> data1
p1 -> data2
- en C
T ** p =(T **) malloc(sizeof(T*)* n);
p[0]=(T*) malloc(sizeof(T));
p[1]=(T*) malloc(sizeof(T));
Vous stockez un pointeur pqui pointe vers un tableau de pointeurs. Chaque pointeur pointe vers une donnée.
Si sizeof(T)est grand, il peut ne pas être possible d'allouer un bloc contigu (c'est-à-dire en utilisant malloc) d' sizeof(T) * noctets.
Pas (!) Besoin de lancer le résultat de malloc en C.
alk
2
Une chose pour laquelle je les utilise constamment, c'est quand j'ai un tableau d'objets et que je dois effectuer des recherches (recherche binaire) sur eux par différents champs.
Je garde le tableau d'origine ...
int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);
Créez ensuite un tableau de pointeurs triés vers les objets.
Vous pouvez créer autant de tableaux de pointeurs triés que vous le souhaitez, puis utiliser une recherche binaire sur le tableau de pointeurs triés pour accéder à l'objet dont vous avez besoin à partir des données dont vous disposez. Le tableau d'objets d'origine peut rester non trié, mais chaque tableau de pointeurs sera trié par son champ spécifié.
L'objectif est de changer ce vers quoi pointe studentA, à l'aide d'une fonction.
#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!)
*/
Pas (!) Besoin de lancer le résultat de malloc en C.
alk
2
Voici un exemple C ++ très simple qui montre que si vous souhaitez utiliser une fonction pour définir un pointeur pour pointer vers un objet, vous avez besoin d'un pointeur vers un pointeur . Autrement, le pointeur continuera de revenir à null .
(Une réponse C ++, mais je pense que c'est la même chose en C.)
(Aussi, pour référence: Google ("pass by value c ++") = "Par défaut, les arguments en C ++ sont passés par valeur. Lorsqu'un argument est passé par valeur, la valeur de l'argument est copiée dans le paramètre de la fonction.")
Nous voulons donc définir le pointeur bégal à la chaîne a.
La "valeur" de &main::a(une adresse) est copiée dans le paramètre std::string* Function_1::a. C'est donc Function_1::aun pointeur vers (c'est-à-dire l'adresse mémoire de) la chaîne main::a.
La "valeur" de main::b(une adresse en mémoire) est copiée dans le paramètre std::string* Function_1::b. Par conséquent, il y a maintenant 2 de ces adresses en mémoire, les deux pointeurs nuls. À la ligne b = a;, la variable locale Function_1::best alors modifiée en égal Function_1::a(= &main::a), mais la variable main::best inchangée. Après l'appel à Function_1, main::best toujours un pointeur nul.
Que se passe-t-il à la ligne Function_2(&a, &b);?
Le traitement de la avariable est le même: au sein de la fonction, se Function_2::atrouve l'adresse de la chaîne main::a.
Mais la variable best maintenant passée en tant que pointeur vers un pointeur. La "valeur" de &main::b(l' adresse du pointeurmain::b ) est copiée dans std::string** Function_2::b. Par conséquent, au sein de Function_2, déréférencer cela comme *Function_2::bva accéder et modifier main::b. Donc, la ligne *b = a;définit réellement main::b(une adresse) égale à Function_2::a(= adresse de main::a), ce que nous voulons.
Si vous voulez utiliser une fonction pour modifier une chose, que ce soit un objet ou une adresse (pointeur), vous devez passer un pointeur sur cette chose. La chose que vous transmettez réellement ne peut pas être modifiée (dans la portée d'appel) car une copie locale est effectuée.
(Une exception est si le paramètre est une référence, comme std::string& a. Mais généralement, ce sont const. Généralement, si vous appelez f(x), if xest un objet, vous devriez pouvoir supposer qu'il fne sera pas modifié x. Mais s'il xs'agit d'un pointeur, alors vous devriez supposons que cela fpuisse modifier l'objet pointé par x.)
Le code C ++ pour répondre à une question C n'est pas la meilleure idée.
alk
1
Un peu tard pour la fête, mais j'espère que cela aidera quelqu'un.
Dans les tableaux C, allouez toujours de la mémoire sur la pile, donc une fonction ne peut pas retourner un tableau (non statique) car la mémoire allouée sur la pile est libérée automatiquement lorsque l'exécution atteint la fin du bloc en cours. C'est vraiment ennuyeux lorsque vous souhaitez traiter des tableaux bidimensionnels (c'est-à-dire des matrices) et implémenter quelques fonctions qui peuvent modifier et renvoyer des matrices. Pour ce faire, vous pouvez utiliser un pointeur vers pointeur pour implémenter une matrice avec une mémoire allouée dynamiquement:
/* 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;}
Le pointeur double sur pointeur double A pointe vers le premier élément A [0] d'un bloc de mémoire dont les éléments sont eux-mêmes des pointeurs doubles. Vous pouvez imaginer ces doubles pointeurs comme les lignes de la matrice. C'est la raison pour laquelle chaque double pointeur alloue de la mémoire aux éléments num_cols de type double. De plus, A [i] pointe vers la i-ème ligne, c'est-à-dire que A [i] pointe vers A [i] [0] et ce n'est que le premier double élément du bloc de mémoire pour la i-ème ligne. Enfin, vous pouvez accéder facilement à l'élément de la i-ème ligne et de la j-ème colonne avec A [i] [j].
Voici un exemple complet qui illustre l'utilisation:
#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;}
J'ai utilisé des pointeurs doubles aujourd'hui alors que je programmais quelque chose pour le travail, donc je peux expliquer pourquoi nous avons dû les utiliser (c'est la première fois que je dois utiliser des pointeurs doubles). Nous avons dû gérer le codage en temps réel des trames contenues dans les tampons qui sont membres de certaines structures. Dans l'encodeur, nous avons dû utiliser un pointeur vers l'une de ces structures. Le problème était que notre pointeur était modifié pour pointer vers d'autres structures à partir d'un autre thread. Afin d'utiliser la structure actuelle dans l'encodeur, j'ai dû utiliser un double pointeur, afin de pointer vers le pointeur qui était en cours de modification dans un autre thread. Il n'était pas évident au début, du moins pour nous, que nous devions adopter cette approche. Beaucoup d'adresses ont été imprimées au cours du processus :)).
Vous DEVEZ utiliser des pointeurs doubles lorsque vous travaillez sur des pointeurs modifiés à d'autres endroits de votre application. Vous pouvez également trouver des pointeurs doubles indispensables lorsque vous traitez du matériel qui vous est renvoyé et qui vous est adressé.
Comparez la valeur de modification de la variable avec la valeur de modification du pointeur :
#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;}
Cela m'a aidé à éviter de renvoyer la valeur du pointeur lorsque le pointeur a été modifié par la fonction appelée (utilisée dans la liste liée individuellement).
double*
.Réponses:
Si vous voulez avoir une liste de caractères (un mot), vous pouvez utiliser
char *word
Si vous voulez une liste de mots (une phrase), vous pouvez utiliser
char **sentence
Si vous voulez une liste de phrases (un monologue), vous pouvez utiliser
char ***monologue
Si vous voulez une liste de monologues (une biographie), vous pouvez utiliser
char ****biography
Si vous voulez une liste de biographies (une bio-bibliothèque), vous pouvez utiliser
char *****biolibrary
Si vous voulez une liste de bio-bibliothèques (a ?? lol), vous pouvez utiliser
char ******lol
... ...
oui, je sais que ce ne sont peut-être pas les meilleures structures de données
Exemple d'utilisation avec un lol très très très ennuyeux
Production:
la source
arr[a][b][c]
n'est pas un***arr
. Le pointeur des pointeurs utilise des références de références, tandis quearr[a][b][c]
est stocké comme un tableau habituel dans l'ordre principal des lignes.L'une des raisons est que vous souhaitez modifier la valeur du pointeur transmis à une fonction comme argument de fonction, pour ce faire, vous avez besoin d'un pointeur vers un pointeur.
En termes simples, utilisez
**
lorsque vous souhaitez conserver (OU conserver la modification) l'allocation ou l'affectation de mémoire même en dehors d'un appel de fonction.(Donc, passez une telle fonction avec l'argument du double pointeur.)Ce n'est peut-être pas un très bon exemple, mais vous montrera l'utilisation de base:
la source
void allocate(int *p)
et vous l'appeliez commeallocate(p)
?pointer1 = pointer2
, vous donnez à pointer1 l'adresse de pointer2.mais! si vous effectuez cette opération dans une fonction et que vous souhaitez que le résultat persiste une fois la fonction terminée, vous devez effectuer un travail supplémentaire. vous avez besoin d'un nouveau pointeur3 pour pointer vers pointeur1. passez pointer3 à la fonction.
Voici un exemple. regardez d'abord la sortie ci-dessous, pour comprendre.
Voici la sortie: ( lisez ceci en premier )
la source
Ajout à la réponse d' Asha , si vous utilisez un seul pointeur vers l'exemple ci-dessous (par exemple alloc1 ()), vous perdrez la référence à la mémoire allouée à l'intérieur de la fonction.
La raison pour laquelle cela se produit comme ceci est que
alloc1
le pointeur est transmis par valeur. Ainsi, lorsqu'il est réaffecté au résultat de l'malloc
appel à l'intérieur dealloc1
, la modification ne concerne pas le code dans une portée différente.la source
free(p)
ne suffit pas, vous devezif(p) free(*p)
aussi*p
évalue à uneint
détention la valeur de 10, passer ceciint
à free () `est une mauvaise idée.alloc1()
introduit une fuite de mémoire. La valeur du pointeur à transmettre gratuitement est perdue en revenant de la fonction.J'ai vu un très bon exemple aujourd'hui, à partir de ce billet de blog , comme je le résume ci-dessous.
Imaginez que vous ayez une structure pour les nœuds dans une liste chaînée, qui est probablement
Maintenant, vous voulez implémenter une
remove_if
fonction, qui accepte un critère de suppressionrm
comme l'un des arguments et traverse la liste liée: si une entrée satisfait le critère (quelque chose commerm(entry)==true
), son nœud sera supprimé de la liste. Au final,remove_if
renvoie la tête (qui peut être différente de la tête d'origine) de la liste chaînée.Vous pouvez écrire
comme
for
boucle. Le message est que, sans pointeurs doubles, vous devez conserver uneprev
variable pour réorganiser les pointeurs et gérer les deux cas différents.Mais avec des pointeurs doubles, vous pouvez réellement écrire
Vous n'avez pas besoin d'un
prev
maintenant car vous pouvez directement modifier ce quiprev->next
pointait .Pour rendre les choses plus claires, suivons un peu le code. Lors de la suppression:
entry == *head
: ce sera*head (==*curr) = *head->next
-head
pointe maintenant vers le pointeur du nouveau noeud de titre. Pour ce faire, vous modifiez directementhead
le contenu de vers un nouveau pointeur.entry != *head
: de la même manière,*curr
c'est ce quiprev->next
pointait et pointe maintenantentry->next
.Dans tous les cas, vous pouvez réorganiser les pointeurs de manière unifiée avec des pointeurs doubles.
la source
1. Concept de base -
Lorsque vous déclarez ce qui suit: -
1. char * ch - (appelé pointeur de caractère)
- ch contient l'adresse d'un seul caractère.
- (* ch) déréférera à la valeur du caractère.
2. char ** ch -
'ch' contient l'adresse d'un tableau de pointeurs de caractères. (comme dans 1)
'* ch' contient l'adresse d'un seul caractère. (Notez qu'il est différent de 1, en raison de la différence de déclaration).
(** ch) déréférencera la valeur exacte du caractère.
L'ajout de pointeurs étend la dimension d'un type de données, du caractère à la chaîne, au tableau de chaînes, etc. Vous pouvez le relier à une matrice 1d, 2d, 3d ..
Ainsi, l'utilisation du pointeur dépend de la façon dont vous le déclarez.
Voici un code simple ..
2. Une autre application des pointeurs doubles -
(cela couvrirait également le passage par référence)
Supposons que vous souhaitiez mettre à jour un caractère à partir d'une fonction. Si vous essayez ce qui suit: -
La sortie sera AA. Cela ne fonctionne pas, car vous avez "passé par valeur" à la fonction.
La bonne façon de procéder serait -
Étendez maintenant cette exigence pour mettre à jour une chaîne au lieu d'un caractère.
Pour cela, vous devez recevoir le paramètre dans la fonction sous la forme d'un double pointeur.
Dans cet exemple, la méthode attend un double pointeur comme paramètre pour mettre à jour la valeur d'une chaîne.
la source
#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; }
Mais vous pouvez aussi le faire sans utiliser de pointeur double.char
pointeurs. Un pointeur vers un tableau dechar*
serait par exemple comme ceci:char(*(*p)[42])
définitp
comme pointeur vers un tableau de 42 pointeurs verschar
.*str = ...
str
appel non défini initialisé non référencé.malloc(sizeof(char) * 10);
n'alloue pas de place pour 10 pointeurschar
mais pour 10char
seulement ..for(i=0;i<10;i++) { str = ...
manque d'utiliser l'indexi
.Les pointeurs vers les pointeurs sont également utiles en tant que «poignées» vers la mémoire où vous voulez passer une «poignée» entre les fonctions vers la mémoire re-localisable. Cela signifie essentiellement que la fonction peut modifier la mémoire pointée par le pointeur à l'intérieur de la variable de poignée, et chaque fonction ou objet qui utilise la poignée pointera correctement vers la mémoire nouvellement déplacée (ou allouée). Les bibliothèques aiment faire cela avec des types de données "opaques", c'est-à-dire des types de données où vous n'avez pas à vous soucier de ce qu'ils font avec la mémoire pointée, vous passez simplement la "poignée" entre les fonctions de la bibliothèque pour effectuer certaines opérations sur cette mémoire ...
Par exemple:
J'espère que cela t'aides,
Jason
la source
unsigned char
est spécifiquement utilisé car nous stockons un pointeur sur des données binaires qui seront représentées sous forme d'octets bruts. L'utilisationvoid
nécessitera un cast à un moment donné et n'est généralement pas aussi lisible que l'intention de ce qui est fait.Exemple simple que vous avez probablement déjà vu à plusieurs reprises
Dans le deuxième paramètre, vous l'avez: pointeur vers pointeur vers char.
Notez que la notation du pointeur (
char* c
) et la notation du tableau (char c[]
) sont interchangeables dans les arguments de fonction. Vous pouvez donc aussi écrirechar *argv[]
. En d'autres termeschar *argv[]
etchar **argv
sont interchangeables.Ce que cela représente est en fait un tableau de séquences de caractères (les arguments de ligne de commande qui sont donnés à un programme au démarrage).
Voir également cette réponse pour plus de détails sur la signature de fonction ci-dessus.
la source
char* c
) et la notation du tableau (char c[]
) sont interchangeables" (et ont la même signification exacte) dans les arguments de fonction . Ce sont cependant des arguments de fonction différents.Les chaînes sont un excellent exemple d'utilisation des pointeurs doubles. La chaîne elle-même est un pointeur, donc chaque fois que vous devez pointer vers une chaîne, vous aurez besoin d'un double pointeur.
la source
Par exemple, vous pouvez vous assurer que lorsque vous libérez la mémoire de quelque chose, vous définissez le pointeur sur null par la suite.
Lorsque vous appelez cette fonction, vous l'appelez avec l'adresse d'un pointeur
Now
myMemory
est défini sur NULL et toute tentative de réutilisation sera manifestement fausse.la source
if(*memory)
etfree(*memory);
Par exemple, si vous souhaitez un accès aléatoire à des données non contiguës.
- en C
Vous stockez un pointeur
p
qui pointe vers un tableau de pointeurs. Chaque pointeur pointe vers une donnée.Si
sizeof(T)
est grand, il peut ne pas être possible d'allouer un bloc contigu (c'est-à-dire en utilisant malloc) d'sizeof(T) * n
octets.la source
Une chose pour laquelle je les utilise constamment, c'est quand j'ai un tableau d'objets et que je dois effectuer des recherches (recherche binaire) sur eux par différents champs.
Je garde le tableau d'origine ...
Créez ensuite un tableau de pointeurs triés vers les objets.
Vous pouvez créer autant de tableaux de pointeurs triés que vous le souhaitez, puis utiliser une recherche binaire sur le tableau de pointeurs triés pour accéder à l'objet dont vous avez besoin à partir des données dont vous disposez. Le tableau d'objets d'origine peut rester non trié, mais chaque tableau de pointeurs sera trié par son champ spécifié.
la source
Pourquoi des pointeurs doubles?
L'objectif est de changer ce vers quoi pointe studentA, à l'aide d'une fonction.
la source
Voici un exemple C ++ très simple qui montre que si vous souhaitez utiliser une fonction pour définir un pointeur pour pointer vers un objet, vous avez besoin d'un pointeur vers un pointeur . Autrement, le pointeur continuera de revenir à null .
(Une réponse C ++, mais je pense que c'est la même chose en C.)
(Aussi, pour référence: Google ("pass by value c ++") = "Par défaut, les arguments en C ++ sont passés par valeur. Lorsqu'un argument est passé par valeur, la valeur de l'argument est copiée dans le paramètre de la fonction.")
Nous voulons donc définir le pointeur
b
égal à la chaînea
.Que se passe-t-il à la ligne
Function_1(&a, b);
?La "valeur" de
&main::a
(une adresse) est copiée dans le paramètrestd::string* Function_1::a
. C'est doncFunction_1::a
un pointeur vers (c'est-à-dire l'adresse mémoire de) la chaînemain::a
.La "valeur" de
main::b
(une adresse en mémoire) est copiée dans le paramètrestd::string* Function_1::b
. Par conséquent, il y a maintenant 2 de ces adresses en mémoire, les deux pointeurs nuls. À la ligneb = a;
, la variable localeFunction_1::b
est alors modifiée en égalFunction_1::a
(=&main::a
), mais la variablemain::b
est inchangée. Après l'appel àFunction_1
,main::b
est toujours un pointeur nul.Que se passe-t-il à la ligne
Function_2(&a, &b);
?Le traitement de la
a
variable est le même: au sein de la fonction, seFunction_2::a
trouve l'adresse de la chaînemain::a
.Mais la variable
b
est maintenant passée en tant que pointeur vers un pointeur. La "valeur" de&main::b
(l' adresse du pointeurmain::b
) est copiée dansstd::string** Function_2::b
. Par conséquent, au sein de Function_2, déréférencer cela comme*Function_2::b
va accéder et modifiermain::b
. Donc, la ligne*b = a;
définit réellementmain::b
(une adresse) égale àFunction_2::a
(= adresse demain::a
), ce que nous voulons.Si vous voulez utiliser une fonction pour modifier une chose, que ce soit un objet ou une adresse (pointeur), vous devez passer un pointeur sur cette chose. La chose que vous transmettez réellement ne peut pas être modifiée (dans la portée d'appel) car une copie locale est effectuée.
(Une exception est si le paramètre est une référence, comme
std::string& a
. Mais généralement, ce sontconst
. Généralement, si vous appelezf(x)
, ifx
est un objet, vous devriez pouvoir supposer qu'ilf
ne sera pas modifiéx
. Mais s'ilx
s'agit d'un pointeur, alors vous devriez supposons que celaf
puisse modifier l'objet pointé parx
.)la source
Un peu tard pour la fête, mais j'espère que cela aidera quelqu'un.
Dans les tableaux C, allouez toujours de la mémoire sur la pile, donc une fonction ne peut pas retourner un tableau (non statique) car la mémoire allouée sur la pile est libérée automatiquement lorsque l'exécution atteint la fin du bloc en cours. C'est vraiment ennuyeux lorsque vous souhaitez traiter des tableaux bidimensionnels (c'est-à-dire des matrices) et implémenter quelques fonctions qui peuvent modifier et renvoyer des matrices. Pour ce faire, vous pouvez utiliser un pointeur vers pointeur pour implémenter une matrice avec une mémoire allouée dynamiquement:
Voici une illustration:
Le pointeur double sur pointeur double A pointe vers le premier élément A [0] d'un bloc de mémoire dont les éléments sont eux-mêmes des pointeurs doubles. Vous pouvez imaginer ces doubles pointeurs comme les lignes de la matrice. C'est la raison pour laquelle chaque double pointeur alloue de la mémoire aux éléments num_cols de type double. De plus, A [i] pointe vers la i-ème ligne, c'est-à-dire que A [i] pointe vers A [i] [0] et ce n'est que le premier double élément du bloc de mémoire pour la i-ème ligne. Enfin, vous pouvez accéder facilement à l'élément de la i-ème ligne et de la j-ème colonne avec A [i] [j].
Voici un exemple complet qui illustre l'utilisation:
la source
J'ai utilisé des pointeurs doubles aujourd'hui alors que je programmais quelque chose pour le travail, donc je peux expliquer pourquoi nous avons dû les utiliser (c'est la première fois que je dois utiliser des pointeurs doubles). Nous avons dû gérer le codage en temps réel des trames contenues dans les tampons qui sont membres de certaines structures. Dans l'encodeur, nous avons dû utiliser un pointeur vers l'une de ces structures. Le problème était que notre pointeur était modifié pour pointer vers d'autres structures à partir d'un autre thread. Afin d'utiliser la structure actuelle dans l'encodeur, j'ai dû utiliser un double pointeur, afin de pointer vers le pointeur qui était en cours de modification dans un autre thread. Il n'était pas évident au début, du moins pour nous, que nous devions adopter cette approche. Beaucoup d'adresses ont été imprimées au cours du processus :)).
Vous DEVEZ utiliser des pointeurs doubles lorsque vous travaillez sur des pointeurs modifiés à d'autres endroits de votre application. Vous pouvez également trouver des pointeurs doubles indispensables lorsque vous traitez du matériel qui vous est renvoyé et qui vous est adressé.
la source
Comparez la valeur de modification de la variable avec la valeur de modification du pointeur :
Cela m'a aidé à éviter de renvoyer la valeur du pointeur lorsque le pointeur a été modifié par la fonction appelée (utilisée dans la liste liée individuellement).
VIEUX (mauvais):
NOUVEAU (mieux):
la source