Quand utiliser des pointeurs dans une langue nécessite-t-il que quelqu'un en utilise plus d'un, par exemple un pointeur triple? Quand est-il judicieux d'utiliser un pointeur triple au lieu d'utiliser simplement un pointeur normal?
Par exemple:
char * * *ptr;
au lieu de
char *ptr;
chaque étoile doit être lue comme "ce qui est pointé par un pointeur", donc
char *foo;
est "char qui pointé par un pointeur foo". toutefois
char *** foo;
est "char qui pointé par un pointeur qui est pointé à un pointeur qui est pointé à un pointeur foo". Ainsi, foo est un pointeur. A cette adresse se trouve un deuxième pointeur. À l'adresse indiquée par celle-ci se trouve un troisième pointeur. Déréférencer le troisième pointeur résulte en un caractère. Si c'est tout ce qu'il y a à faire, il est difficile de faire valoir ses arguments.
Il est encore possible de faire un travail utile, cependant. Imaginez que nous écrivions un substitut à bash ou à un autre programme de contrôle de processus. Nous voulons gérer les invocations de nos processus de manière orientée objet ...
struct invocation {
char* command; // command to invoke the subprocess
char* path; // path to executable
char** env; // environment variables passed to the subprocess
...
}
Mais nous voulons faire quelque chose d'extraordinaire. Nous voulons avoir un moyen de parcourir tous les différents ensembles de variables d’environnement telles qu’elles sont perçues par chaque sous-processus. Pour ce faire, nous rassemblons chaque ensemble de membres env
des instances d’invocation dans un tableau env_list
et le transmettons à la fonction qui traite ce sujet:
void browse_env(size_t envc, char*** env_list);
Si vous travaillez avec des "objets" en C, vous avez probablement ceci:
struct customer {
char *name;
char *address;
int id;
} typedef Customer;
Si vous voulez créer un objet, vous feriez quelque chose comme ceci:
Customer *customer = malloc(sizeof Customer);
// Initialise state.
Nous utilisons un pointeur sur un struct
ici parce que les arguments struct
sont passés par valeur et nous devons travailler avec one object. (En outre: Objective-C, un langage wrapper orienté objet pour C, utilise en interne mais visiblement des pointeurs sur struct
s.)
Si j'ai besoin de stocker plusieurs objets, j'utilise un tableau:
Customer **customers = malloc(sizeof(Customer *) * 10);
int customerCount = 0;
Puisqu'une variable de tableau dans C points vers le premier élément, j'utilise un pointeur… à nouveau. Maintenant, j'ai un double pointeur.
Mais maintenant, imaginez que j’ai une fonction qui filtre le tableau et en retourne un nouveau. Mais imaginons que ce ne soit pas possible via le mécanisme de retour car il doit renvoyer un code d'erreur: ma fonction accède à une base de données. Je dois le faire à travers un argument de référence. Ceci est la signature de ma fonction:
int filterRegisteredCustomers(Customer **unfilteredCustomers, Customer ***filteredCustomers, int unfilteredCount, int *filteredCount);
La fonction prend un tableau de clients et renvoie une référence à un tableau de clients (qui sont des pointeurs vers un struct
). Il prend également le nombre de clients et renvoie le nombre de clients filtrés (encore une fois, argument par référence).
Je peux l'appeler de cette façon:
Customer **result, int n = 0;
int errorCode = filterRegisteredCustomers(customers, &result, customerCount, &n);
Je pourrais continuer à imaginer plus de situations… Celle-ci est sans la typedef
:
int fetchCustomerMatrix(struct customer ****outMatrix, int *rows, int *columns);
Évidemment, je serais un développeur horrible et/ou sadique de laisser les choses ainsi. Donc, en utilisant:
typedef Customer *CustomerArray;
typedef CustomerArray *CustomerMatrix;
Je peux juste faire ça:
int fetchCustomerMatrix(CustomerMatrix *outMatrix, int *rows, int *columns);
Si votre application est utilisée dans un hôtel où vous utilisez une matrice par niveau, vous aurez probablement besoin d'un tableau pour une matrice:
int fetchHotel(struct customer *****hotel, int *rows, int *columns, int *levels);
Ou simplement ceci:
typedef CustomerMatrix *Hotel;
int fetchHotel(Hotel *hotel, int *rows, int *columns, int *levels);
Ne me lancez même pas sur une série d'hôtels:
int fetchHotels(struct customer ******hotels, int *rows, int *columns, int *levels, int *hotels);
… Disposés dans une matrice (une sorte de grande société hôtelière?):
int fetchHotelMatrix(struct customer *******hotelMatrix, int *rows, int *columns, int *levels, int *hotelRows, int *hotelColumns);
Ce que j'essaie de dire, c'est que vous pouvez imaginer des applications folles pour plusieurs indirections. Assurez-vous simplement d'utiliser typedef
si les multi-pointeurs sont une bonne idée et que vous décidez de les utiliser.
(Ce message compte-t-il comme une application pour un SevenStarDeveloper ?)
La baguette de ImageMagicks a une fonction qui est déclarée comme
WandExport char* * * * * * DrawGetVectorGraphics ( const DrawingWand *)
Un pointeur est simplement une variable contenant une adresse mémoire.
Vous utilisez donc un pointeur sur un pointeur lorsque vous souhaitez conserver l'adresse d'une variable de pointeur.
Si vous voulez renvoyer un pointeur et que vous utilisez déjà la variable de retour, vous passerez l'adresse d'un pointeur. La fonction supprime ensuite ce pointeur afin de pouvoir définir la valeur du pointeur. C'est à dire. le paramètre de cette fonction serait un pointeur sur un pointeur.
Plusieurs niveaux d'indirection sont également utilisés pour les tableaux multidimensionnels. Si vous voulez renvoyer un tableau à 2 dimensions, vous utiliseriez un pointeur triple. Lorsque vous les utilisez pour des tableaux multi-dimensionnels, veillez à ce que le casting soit correctement effectué lors de chaque niveau d'indirection.
Voici un exemple de renvoi d'une valeur de pointeur via un paramètre:
//Not a very useful example, but shows what I mean...
bool getOffsetBy3Pointer(const char *pInput, char **pOutput)
{
*pOutput = pInput + 3;
return true;
}
Et vous appelez cette fonction comme suit:
const char *p = "hi you";
char *pYou;
bool bSuccess = getOffsetBy3Pointer(p, &pYou);
assert(!stricmp(pYou, "you"));
Les tableaux alloués dynamiquement à N dimensions, où N> 3, nécessitent trois niveaux d'indirection ou plus en C.
Char *** foo peut être interprété comme un pointeur sur un tableau de chaînes en deux dimensions.
Une utilisation standard de doubles pointeurs, par exemple: myStruct ** ptrptr, est un pointeur sur un pointeur. Par exemple, en tant que paramètre de fonction, cela vous permet de modifier la structure réelle vers laquelle l'appelant pointe, au lieu de pouvoir uniquement modifier les valeurs au sein de cette structure.
Vous utilisez un niveau supplémentaire d'indirection - ou de pointage - lorsque cela est nécessaire, pas parce que ce serait amusant. Vous voyez rarement des pointeurs triples; Je ne pense pas avoir jamais vu un pointeur quadruple (et mon esprit serait ahurissant si je le faisais).
Les tables d'état peuvent être représentées par un tableau 2D d'un type de données approprié (des pointeurs vers une structure, par exemple). Quand j'ai écrit du code presque générique pour faire des tables d'état, je me souviens d'avoir une fonction qui prenait un triple pointeur - qui représentait un tableau 2D de pointeurs sur des structures. Aie!
int main( int argc, char** argv );
Les fonctions qui encapsulent la création de ressources utilisent souvent des doubles pointeurs. C'est-à-dire que vous transmettez l'adresse d'un pointeur à une ressource. La fonction peut ensuite créer la ressource en question et définir le pointeur pour y pointer. Ceci n'est possible que s'il a l'adresse du pointeur en question, il doit donc s'agir d'un double pointeur.
Lorsque vous utilisez des structures de données imbriquées dynamiquement allouées (ou liées à un pointeur). Ces choses sont toutes liées par des pointeurs.
Les pointeurs vers les pointeurs sont rarement utilisés en C++. Ils ont principalement deux utilisations.
La première utilisation est de passer un tableau. char**
, par exemple, est un pointeur sur pointeur sur caractère, qui est souvent utilisé pour transmettre un tableau de chaînes. Les pointeurs sur les tableaux ne fonctionnent pas pour de bonnes raisons, mais c'est un sujet différent (voir le comp.lang.c FAQ si vous voulez en savoir plus). Dans de rares cas, vous pouvez voir un troisième *
utilisé pour un tableau de tableaux, mais il est généralement plus efficace de tout stocker dans un tableau contigu et de l'indexer manuellement (par exemple, array[x+y*width]
plutôt que array[x][y]
). En C++, cependant, c'est beaucoup moins courant à cause des classes de conteneur.
La deuxième utilisation est de passer par référence. Un paramètre int*
permet à la fonction de modifier le nombre entier pointé par la fonction appelante et est généralement utilisé pour fournir plusieurs valeurs de retour. Ce modèle de passage de paramètres par référence afin de permettre des retours multiples est toujours présent en C++, mais, à l'instar d'autres utilisations du passage par référence, il est généralement remplacé par l'introduction de références réelles. L'autre raison de passer par référence - éviter de copier des constructions complexes - est également possible avec la référence C++.
C++ a un troisième facteur qui réduit l'utilisation de plusieurs pointeurs: il a string
. Une référence à string peut prendre le type char**
en C, de sorte que la fonction puisse modifier l'adresse de la variable de chaîne transmise, mais en C++, nous voyons généralement string&
à la place.
La double indirection simplifie de nombreux algorithmes d’équilibrage d’arbres, dans lesquels on souhaite généralement pouvoir "dissocier" efficacement un sous-arbre de son parent. Par exemple, une implémentation d'arborescence AVL peut utiliser:
void rotateLeft(struct tree **tree) {
struct tree *t = *tree,
*r = t->right,
*rl = r->left;
*tree = r;
r->left = t;
t->right = rl;
}
Sans le "double pointeur", nous devrions faire quelque chose de plus compliqué, comme garder explicitement la trace du parent d'un nœud et déterminer s'il s'agit d'une branche gauche ou droite.
En particulier dans les dialectes C à un seul thread qui n'utilisent pas de manière agressive l'analyse de repliement de type, il peut parfois être utile d'écrire des gestionnaires de mémoire pouvant accueillir des objets déplaçables. Au lieu de donner des pointeurs directs aux applications sur des blocs de mémoire, l'application reçoit des pointeurs dans une table de descripteurs de descripteurs, chacun contenant un pointeur sur un bloc de mémoire réel ainsi qu'un mot indiquant sa taille. Si on a besoin d'allouer de l'espace pour un struct woozle
, on pourrait dire:
struct woozle **my_woozle = newHandle(sizeof struct woozle);
et ensuite accès (un peu maladroitement dans la syntaxe C - la syntaxe est plus propre dans Pascal): (* my_woozle) -> someField = 23; il est important que les applications non gardent les pointeurs directs sur la cible d'une poignée pour les appels aux fonctions qui allouent de la mémoire, mais s'il n'existe qu'un seul pointeur sur chaque bloc identifié par une poignée. le gestionnaire de mémoire sera en mesure de déplacer les choses au cas où la fragmentation deviendrait un problème.
L’approche ne fonctionne pas aussi bien dans les dialectes du C qui Agissent de manière agressive en recherchant un aliasing basé sur un type, car le pointeur renvoyé par NewHandle
ne permet pas D'identifier un pointeur de type struct woozle*
mais pointeur de type void*
, et même sur les plates-formes où ces types de pointeurs auraient la même représentation que la norme ne nécessite pas que les implémentations interprètent un transtypage de pointeur comme une indication qu'il devrait s’attendre à ce qu’un aliasing puisse se produire.
Si vous devez modifier un pointeur dans une fonction, vous devez lui transmettre une référence.
Il est logique d’utiliser un pointeur sur un pointeur chaque fois que le pointeur pointe réellement vers un pointeur (cette chaîne est illimitée, ce qui permet de créer des "pointeurs triples", etc.).
La raison de la création de ce code est parce que vous voulez que le compilateur/interprète soit capable de vérifier correctement les types que vous utilisez (empêchez les bugs mystères).
Vous n'avez pas à utiliser de tels types - vous pouvez toujours simplement utiliser un simple "void *" et une conversion de type chaque fois que vous avez besoin de déréférencer le pointeur et d'accéder aux données vers lesquelles le pointeur se dirige. Mais c’est généralement une mauvaise pratique et une source d’erreurs - il existe certainement des cas où utiliser void * est en fait une bonne chose et rend le code beaucoup plus élégant. Pensez-y plus comme votre dernier recours.
=> C’est surtout pour aider le compilateur à s’assurer que les choses sont utilisées comme elles sont censées être utilisées.
Pour être honnête, j'ai rarement vu un triple pointeur.
J'ai jeté un coup d'œil sur la recherche de code dans Google, et il y a quelques-unsexemples , mais pas très éclairant. (voir les liens à la fin - SO ne les aime pas)
Comme d'autres l'ont mentionné, vous verrez de temps à autre des doubles indicateurs. Les simples pointeurs simples sont utiles car ils pointent sur une ressource allouée. Les doubles pointeurs sont utiles car vous pouvez les transmettre à une fonction et la faire remplir par le pointeur "normal" pour vous.
Il semble que vous ayez peut-être besoin d'explications sur ce que sont les pointeurs et leur fonctionnement. Vous devez d'abord comprendre cela, si ce n'est déjà fait.