Quand utiliser une double indirection en C? Quelqu'un peut-il expliquer avec un exemple?
Ce que je sais, c'est qu'une double indirection est un pointeur vers un pointeur. Pourquoi aurais-je besoin d'un pointeur vers un pointeur?
Quand utiliser une double indirection en C? Quelqu'un peut-il expliquer avec un exemple?
Ce que je sais, c'est qu'une double indirection est un pointeur vers un pointeur. Pourquoi aurais-je besoin d'un pointeur vers un pointeur?
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
#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);
}
Production:
total des mots dans mon lol: 243
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.
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:
void allocate(int** p)
{
*p = (int*)malloc(sizeof(int));
}
int main()
{
int* p = NULL;
allocate(&p);
*p = 42;
free(p);
}
void allocate(int *p)
et vous l'appeliez comme allocate(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.
#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);
}
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!!!.
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.
void alloc2(int** p) {
*p = (int*)malloc(sizeof(int));
**p = 10;
}
void alloc1(int* p) {
p = (int*)malloc(sizeof(int));
*p = 10;
}
int main(){
int *p = NULL;
alloc1(p);
//printf("%d ",*p);//undefined
alloc2(&p);
printf("%d ",*p);//will print 10
free(p);
return 0;
}
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 de alloc1
, la modification ne concerne pas le code dans une portée différente.
free(p)
ne suffit pas, vous devez if(p) free(*p)
aussi
*p
évalue à une int
détention la valeur de 10, passer ceci int
à 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
typedef struct node
{
struct node * next;
....
} node;
Maintenant, vous voulez implémenter une remove_if
fonction, qui accepte un critère de suppression rm
comme 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_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
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 for
boucle. Le message est que, sans pointeurs doubles, vous devez conserver une prev
variable 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 pointer
for (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 prev
maintenant car vous pouvez directement modifier ce qui prev->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 directement head
le contenu de vers un nouveau pointeur.entry != *head
: de la même manière, *curr
c'est ce qui prev->next
pointait 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. 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 ..
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: -
void func(char ch)
{
ch = 'B';
}
int main()
{
char ptr;
ptr = 'A';
printf("%c", ptr);
func(ptr);
printf("%c\n", ptr);
}
La sortie sera AA. Cela ne fonctionne pas, car vous avez "passé par valeur" à la fonction.
La bonne façon de procéder serait -
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);
}
É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 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);
}
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.
char
pointeurs. Un pointeur vers un tableau de char*
serait par exemple comme ceci: char(*(*p)[42])
définit p
comme pointeur vers un tableau de 42 pointeurs vers char
.
*str = ...
str
appel non défini initialisé non référencé.
malloc(sizeof(char) * 10);
n'alloue pas de place pour 10 pointeurs char
mais pour 10 char
seulement ..
for(i=0;i<10;i++) { str = ...
manque d'utiliser l'index i
.
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>
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;
}
J'espère que cela t'aides,
Jason
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'utilisation void
nécessitera un cast à un moment donné et n'est généralement pas aussi lisible que l'intention de ce qui est fait.
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.
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.
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.
void safeFree(void** memory) {
if (*memory) {
free(*memory);
*memory = NULL;
}
}
Lorsque vous appelez cette fonction, vous l'appelez avec l'adresse d'un pointeur
void* myMemory = someCrazyFunctionThatAllocatesMemory();
safeFree(&myMemory);
Now myMemory
est défini sur NULL et toute tentative de réutilisation sera manifestement fausse.
if(*memory)
etfree(*memory);
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 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.
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.
int compare_object_by_name( const void *v1, const void *v2 ) {
OBJECT *o1 = *(OBJECT **)v1;
OBJECT *o2 = *(OBJECT **)v2;
return (strcmp(o1->name, o2->name);
}
OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects);
int i = 0;
for( ; i<num_objects; i++)
object_ptrs_by_name[i] = original_array+i;
qsort(object_ptrs_by_name, num_objects, sizeof(OBJECT *), compare_object_by_name);
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é.
Pourquoi des pointeurs doubles?
L'objectif est de changer ce vers quoi pointe studentA, à l'aide d'une fonction.
#include <stdio.h>
#include <stdlib.h>
typedef struct Person{
char * name;
} Person;
/**
* we need a ponter to a pointer, example: &studentA
*/
void change(Person ** x, Person * y){
*x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct.
}
void dontChange(Person * x, Person * y){
x = y;
}
int main()
{
Person * studentA = (Person *)malloc(sizeof(Person));
studentA->name = "brian";
Person * studentB = (Person *)malloc(sizeof(Person));
studentB->name = "erich";
/**
* we could have done the job as simple as this!
* but we need more work if we want to use a function to do the job!
*/
// studentA = studentB;
printf("1. studentA = %s (not changed)\n", studentA->name);
dontChange(studentA, studentB);
printf("2. studentA = %s (not changed)\n", studentA->name);
change(&studentA, studentB);
printf("3. studentA = %s (changed!)\n", studentA->name);
return 0;
}
/**
* OUTPUT:
* 1. studentA = brian (not changed)
* 2. studentA = brian (not changed)
* 3. studentA = erich (changed!)
*/
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
.
#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
Que se passe-t-il à la ligne Function_1(&a, b);
?
La "valeur" de &main::a
(une adresse) est copiée dans le paramètre std::string* Function_1::a
. C'est donc Function_1::a
un 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::b
est alors modifiée en égal Function_1::a
(= &main::a
), mais la variable main::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, se Function_2::a
trouve l'adresse de la chaîne main::a
.
Mais la variable b
est maintenant passée en tant que pointeur vers un pointeur. La "valeur" de &main::b
(l' adresse du pointeur main::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::b
va 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 x
est un objet, vous devriez pouvoir supposer qu'il f
ne sera pas modifié x
. Mais s'il x
s'agit d'un pointeur, alors vous devriez supposons que cela f
puisse modifier l'objet pointé par x
.)
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-pointers
double** A = calloc(num_rows, sizeof(double*));
// return NULL if the memory couldn't allocated
if(A == NULL) return NULL;
// For each double-pointer (row) allocate memory for num_cols floats
for(int i = 0; i < num_rows; i++){
A[i] = calloc(num_cols, sizeof(double));
// return NULL if the memory couldn't allocated
// and free the already allocated memory
if(A[i] == NULL){
for(int j = 0; j < i; j++){
free(A[j]);
}
free(A);
return NULL;
}
}
return A;
}
Voici une illustration:
double** double* double
------------- ---------------------------------------------------------
A ------> | A[0] | ----> | A[0][0] | A[0][1] | A[0][2] | ........ | A[0][cols-1] |
| --------- | ---------------------------------------------------------
| A[1] | ----> | A[1][0] | A[1][1] | A[1][2] | ........ | A[1][cols-1] |
| --------- | ---------------------------------------------------------
| . | .
| . | .
| . | .
| --------- | ---------------------------------------------------------
| A[i] | ----> | A[i][0] | A[i][1] | A[i][2] | ........ | A[i][cols-1] |
| --------- | ---------------------------------------------------------
| . | .
| . | .
| . | .
| --------- | ---------------------------------------------------------
| A[rows-1] | ----> | A[rows-1][0] | A[rows-1][1] | ... | A[rows-1][cols-1] |
------------- ---------------------------------------------------------
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-pointers
double** matrix = calloc(num_rows, sizeof(double*));
// return NULL if the memory couldn't allocated
if(matrix == NULL) return NULL;
// For each double-pointer (row) allocate memory for num_cols
// doubles
for(int i = 0; i < num_rows; i++){
matrix[i] = calloc(num_cols, sizeof(double));
// return NULL if the memory couldn't allocated
// and free the already allocated memory
if(matrix[i] == NULL){
for(int j = 0; j < i; j++){
free(matrix[j]);
}
free(matrix);
return NULL;
}
}
return matrix;
}
/* Fills the matrix with random double-numbers between -1 and 1 */
void randn_fill_matrix(double** matrix, int rows, int cols){
for (int i = 0; i < rows; ++i){
for (int j = 0; j < cols; ++j){
matrix[i][j] = (double) rand()/RAND_MAX*2.0-1.0;
}
}
}
/* Frees the memory allocated by the matrix */
void free_matrix(double** matrix, int rows, int cols){
for(int i = 0; i < rows; i++){
free(matrix[i]);
}
free(matrix);
}
/* Outputs the matrix to the console */
void print_matrix(double** matrix, int rows, int cols){
for(int i = 0; i < rows; i++){
for(int j = 0; j < cols; j++){
printf(" %- f ", matrix[i][j]);
}
printf("\n");
}
}
int main(){
srand(time(NULL));
int m = 3, n = 3;
double** A = init_matrix(m, n);
randn_fill_matrix(A, m, n);
print_matrix(A, m, n);
free_matrix(A, m, n);
return 0;
}
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).
VIEUX (mauvais):
int *func(int *P)
{
...
return P;
}
int main(void)
{
int *pointer;
pointer = func(pointer);
...
}
NOUVEAU (mieux):
void func(int **pointer)
{
...
}
int main(void)
{
int *pointer;
func(&pointer);
...
}
double*
.