Je sais que c'est une question vraiment basique, mais je viens de commencer avec une programmation C ++ de base après avoir codé quelques projets avec des langages de haut niveau.
Fondamentalement, j'ai trois questions:
Pourquoi utiliser des pointeurs sur des variables normales?
Quand et où dois-je utiliser des pointeurs?
Comment utilisez-vous des pointeurs avec des tableaux?
Nous utilisons des pointeurs, car il est plus facile de donner une adresse à quelqu'un que de donner une copie de votre maison à tout le monde.
Rishi Dua
14
@RishiDua C'est la meilleure explication d'un pointeur que j'ai jamais rencontré. Merci pour cela, cela a amélioré ma compréhension :)
Josh
2
Les pointeurs peuvent également être utilisés lorsque vous souhaitez renvoyer plusieurs valeurs / objets.
Gaurav
Réponses:
172
Pourquoi utiliser des pointeurs sur des variables normales?
La réponse courte est: non. ;-) Les pointeurs doivent être utilisés là où vous ne pouvez pas utiliser autre chose. C'est soit par manque de fonctionnalités appropriées, soit par manque de types de données, soit par pure performance. Plus ci-dessous ...
Quand et où dois-je utiliser des pointeurs?
La réponse courte ici est: où vous ne pouvez pas utiliser autre chose. En C, vous n'avez aucune prise en charge pour les types de données complexes tels qu'une chaîne. Il n'y a également aucun moyen de passer une variable "par référence" à une fonction. C'est là que vous devez utiliser des pointeurs. Vous pouvez également les faire pointer vers pratiquement n'importe quoi, des listes liées, des membres de structures, etc. Mais n'entrons pas dans les détails ici.
Comment utilisez-vous des pointeurs avec des tableaux?
Avec peu d'effort et beaucoup de confusion. ;-) Si nous parlons de types de données simples tels que int et char, il y a peu de différence entre un tableau et un pointeur. Ces déclarations sont très similaires (mais pas les mêmes - par exemple, sizeofrenverront des valeurs différentes):
char* a ="Hello";char a[]="Hello";
Vous pouvez atteindre n'importe quel élément du tableau comme celui-ci
printf("Second char is: %c", a[1]);
Index 1 puisque le tableau commence par l'élément 0. :-)
Ou vous pouvez également le faire
printf("Second char is: %c",*(a+1));
L'opérateur de pointeur (le *) est nécessaire car nous disons à printf que nous voulons imprimer un caractère. Sans le *, la représentation des caractères de l'adresse mémoire elle-même serait imprimée. Maintenant, nous utilisons plutôt le personnage lui-même. Si nous avions utilisé% s au lieu de% c, nous aurions demandé à printf d'imprimer le contenu de l'adresse mémoire pointée par 'a' plus un (dans cet exemple ci-dessus), et nous n'aurions pas eu à mettre le * devant:
printf("Second char is: %s",(a+1));/* WRONG */
Mais cela n'aurait pas simplement imprimé le deuxième caractère, mais à la place tous les caractères dans les adresses de mémoire suivantes, jusqu'à ce qu'un caractère nul (\ 0) soit trouvé. Et c'est là que les choses commencent à devenir dangereuses. Que faire si vous essayez accidentellement d'imprimer une variable de type entier au lieu d'un pointeur char avec le formateur% s?
char* a ="Hello";int b =120;
printf("Second char is: %s", b);
Cela imprimerait tout ce qui se trouve sur l'adresse mémoire 120 et continuerait d'imprimer jusqu'à ce qu'un caractère nul soit trouvé. Il est faux et illégal d'exécuter cette instruction printf, mais cela fonctionnerait probablement de toute façon, car un pointeur est en fait du type int dans de nombreux environnements. Imaginez les problèmes que vous pourriez causer si vous utilisiez sprintf () à la place et affectiez de cette manière un "tableau de caractères" trop long à une autre variable, qui ne disposait que d'un certain espace limité. Vous finiriez très probablement par écrire sur autre chose dans la mémoire et faire planter votre programme (si vous avez de la chance).
Oh, et si vous n'affectez pas de valeur de chaîne au tableau / pointeur char lorsque vous le déclarez, vous DEVEZ lui allouer suffisamment de mémoire avant de lui donner une valeur. Utilisation de malloc, calloc ou similaire. Ceci puisque vous n'avez déclaré qu'un seul élément de votre tableau / une seule adresse mémoire à pointer. Voici donc quelques exemples:
char* x;/* Allocate 6 bytes of memory for me and point x to the first of them. */
x =(char*) malloc(6);
x[0]='H';
x[1]='e';
x[2]='l';
x[3]='l';
x[4]='o';
x[5]='\0';
printf("String \"%s\" at address: %d\n", x, x);/* Delete the allocation (reservation) of the memory. *//* The char pointer x is still pointing to this address in memory though! */
free(x);/* Same as malloc but here the allocated space is filled with null characters!*/
x =(char*) calloc(6,sizeof(x));
x[0]='H';
x[1]='e';
x[2]='l';
x[3]='l';
x[4]='o';
x[5]='\0';
printf("String \"%s\" at address: %d\n", x, x);/* And delete the allocation again... */
free(x);/* We can set the size at declaration time as well */char xx[6];
xx[0]='H';
xx[1]='e';
xx[2]='l';
xx[3]='l';
xx[4]='o';
xx[5]='\0';
printf("String \"%s\" at address: %d\n", xx, xx);
Notez que vous pouvez toujours utiliser la variable x après avoir effectué un free () de la mémoire allouée, mais vous ne savez pas ce qui s'y trouve. Notez également que les deux printf () peuvent vous donner des adresses différentes, car il n'y a aucune garantie que la deuxième allocation de mémoire est effectuée dans le même espace que la première.
Votre exemple avec 120 est faux. Il utilise% c et non% s donc il n'y a pas de bogue; il imprime simplement la lettre minuscule x. De plus, votre affirmation ultérieure qu'un pointeur est de type int est un conseil erroné et très mauvais à donner à un programmeur C inexpérimenté en pointeurs.
R .. GitHub STOP HELPING ICE
13
-1 Vous avez bien commencé mais le premier exemple est faux. Non, ce n'est pas pareil. Dans le premier cas aest un pointeur et dans le second cas aest un tableau. L'ai-je déjà mentionné? Ce n'est pas la même chose! Vérifiez par vous-même: comparez la taille de (a), essayez d'attribuer une nouvelle adresse à un tableau. Ça ne marchera pas.
sellibitze
42
char* a = "Hello";et char a[] = "Hello";ne sont pas identiques, ils sont très différents. L'un déclare un pointeur, l'autre un tableau. Essayez un sizeofet vous voyez la différence.
Patrick Schlüter
12
Msgstr "Il n'est pas faux ou illégal d 'exécuter cette instruction printf". C'est tout à fait faux. Cette instruction printf a un comportement indéfini. Sur de nombreuses implémentations, cela entraînera une violation d'accès. Les pointeurs ne sont pas réellement de type int, ils sont en fait des pointeurs (et rien d'autre).
Mankarse
19
-1, Vous vous êtes trompé tellement de faits ici et je vais simplement dire que vous ne méritez pas le nombre de votes positifs ou le statut de réponse accepté.
asveikau
50
L'une des raisons d'utiliser des pointeurs est qu'une variable ou un objet peut être modifié dans une fonction appelée.
En C ++, il est préférable d'utiliser des références que des pointeurs. Bien que les références soient essentiellement des pointeurs, C ++ cache dans une certaine mesure le fait et donne l'impression que vous passez par la valeur. Cela permet de changer facilement la façon dont la fonction appelante reçoit la valeur sans avoir à modifier la sémantique de sa transmission.
Considérez les exemples suivants:
Utilisation de références:
publicvoid doSomething(){int i =10;
doSomethingElse(i);// passes i by references since doSomethingElse() receives it// by reference, but the syntax makes it appear as if i is passed// by value}publicvoid doSomethingElse(int& i)// receives i as a reference{
cout << i << endl;}
Probablement une bonne idée de mentionner que les références sont plus sûres, dans la mesure où vous ne pouvez pas passer une référence nulle.
SpoonMeiser
26
Oui, c'est probablement le plus grand avantage d'utiliser des références. Merci de l'avoir signalé. Pas de jeu de mots :)
trshiv
2
Vous pouvez sûrement passer une référence nulle. Ce n'est pas aussi simple que de passer un pointeur nul.
2013
1
d'accord avec @ n0rd. Si vous pensez que vous ne pouvez pas passer une référence nulle, vous vous trompez. Il est plus facile de passer une référence pendant que la référence nulle, mais en fin de compte, vous pouvez le faire assez facilement. Les références ne sont pas une solution miracle protégeant un ingénieur de se tirer une balle dans le pied. Voyez-le en direct .
WhozCraig
2
@ n0rd: Faire cela est un comportement explicitement indéfini.
Daniel Kamil Kozar
42
Les pointeurs vous permettent de faire référence au même espace en mémoire à partir de plusieurs emplacements. Cela signifie que vous pouvez mettre à jour la mémoire dans un emplacement et que le changement peut être vu depuis un autre emplacement dans votre programme. Vous économiserez également de l'espace en pouvant partager des composants dans vos structures de données.
Vous devez utiliser des pointeurs à tout endroit où vous devez obtenir et transmettre l'adresse à un endroit spécifique de la mémoire. Vous pouvez également utiliser des pointeurs pour parcourir les tableaux:
Un tableau est un bloc de mémoire contigu qui a été alloué avec un type spécifique. Le nom du tableau contient la valeur du point de départ du tableau. Lorsque vous ajoutez 1, cela vous amène au deuxième endroit. Cela vous permet d'écrire des boucles qui incrémentent un pointeur qui glisse vers le bas du tableau sans avoir de compteur explicite à utiliser pour accéder au tableau.
Voici un exemple en C:
char hello[]="hello";char*p = hello;while(*p){*p +=1;// increase the character by one
p +=1;// move to the next spot}
printf(hello);
impressions
ifmmp
car il prend la valeur de chaque caractère et l'incrémente d'une unité.
because it takes the value for each character and increments it by one. Est en représentation ascii ou comment?
Eduardo S.
28
Les pointeurs sont un moyen d'obtenir une référence indirecte à une autre variable. Au lieu de conserver la valeur d'une variable, ils vous indiquent son adresse . Cela est particulièrement utile lorsque vous traitez des tableaux, car en utilisant un pointeur sur le premier élément d'un tableau (son adresse), vous pouvez trouver rapidement l'élément suivant en incrémentant le pointeur (à l'emplacement d'adresse suivant).
La meilleure explication des pointeurs et de l'arithmétique des pointeurs que j'ai lue se trouve dans le langage de programmation C de K&R . Un bon livre pour commencer à apprendre le C ++ est C ++ Primer .
Merci! enfin, une explication pratique des avantages de l'utilisation de la pile! un pointeur à positionner dans un tableau améliore-t-il également les performances en accédant aux valeurs @ et par rapport au pointeur?
C'est probablement la meilleure explication que j'ai lue. les gens ont un moyen de "compliquer les choses" :)
Detilium
23
Permettez-moi d'essayer de répondre à cela aussi.
Les pointeurs sont similaires aux références. En d'autres termes, ce ne sont pas des copies, mais plutôt un moyen de se référer à la valeur d'origine.
Avant toute chose, un endroit où vous devrez généralement beaucoup utiliser des pointeurs est lorsque vous avez affaire à du matériel embarqué . Vous devez peut-être basculer l'état d'une broche d'E / S numérique. Vous traitez peut-être une interruption et devez stocker une valeur à un emplacement spécifique. Vous obtenez l'image. Cependant, si vous ne traitez pas directement avec du matériel et que vous vous demandez simplement quels types utiliser, lisez la suite.
Pourquoi utiliser des pointeurs par opposition aux variables normales? La réponse devient plus claire lorsque vous traitez avec des types complexes, comme les classes, les structures et les tableaux. Si vous deviez utiliser une variable normale, vous pourriez finir par faire une copie (les compilateurs sont assez intelligents pour empêcher cela dans certaines situations et C ++ 11 aide aussi, mais nous resterons à l'écart de cette discussion pour l'instant).
Maintenant, que se passe-t-il si vous souhaitez modifier la valeur d'origine? Vous pouvez utiliser quelque chose comme ceci:
MyType a;//let's ignore what MyType actually is right now.
a = modify(a);
Cela fonctionnera très bien et si vous ne savez pas exactement pourquoi vous utilisez des pointeurs, vous ne devriez pas les utiliser. Méfiez-vous de la raison "ils sont probablement plus rapides". Exécutez vos propres tests et s'ils sont réellement plus rapides, utilisez-les.
Cependant, supposons que vous résolviez un problème où vous devez allouer de la mémoire. Lorsque vous allouez de la mémoire, vous devez la désallouer. L'allocation de mémoire peut réussir ou non. C'est là que les pointeurs sont utiles - ils vous permettent de tester l'existence de l'objet que vous avez alloué et ils vous permettent d'accéder à l'objet pour lequel la mémoire a été allouée en dé-référençant le pointeur.
MyType*p = NULL;//empty pointerif(p){//we never reach here, because the pointer points to nothing}//now, let's allocate some memory
p =newMyType[50000];if(p)//if the memory was allocated, this test will pass{//we can do something with our allocated arrayfor(size_t i=0; i!=50000; i++){MyType&v =*(p+i);//get a reference to the ith object//do something with it//...}delete[] p;//we're done. de-allocate the memory}
C'est la raison pour laquelle vous utiliseriez des pointeurs - les références supposent que l'élément auquel vous faites référence existe déjà . Un pointeur ne fonctionne pas.
L'autre raison pour laquelle vous utiliseriez des pointeurs (ou au moins finissez par les gérer) est qu'ils sont un type de données qui existait avant les références. Par conséquent, si vous finissez par utiliser des bibliothèques pour faire les choses que vous savez être meilleures, vous constaterez que beaucoup de ces bibliothèques utilisent des pointeurs partout, simplement en raison de la durée de leur existence (beaucoup d'entre eux ont été écrits avant C ++).
Si vous n'avez utilisé aucune bibliothèque, vous pouvez concevoir votre code de manière à vous tenir à l'écart des pointeurs, mais étant donné que les pointeurs sont l'un des types de base du langage, plus vous vous familiarisez rapidement avec leur utilisation, plus portable vos compétences C ++ seraient.
Du point de vue de la maintenabilité, je dois également mentionner que lorsque vous utilisez des pointeurs, vous devez soit tester leur validité et gérer le cas quand ils ne sont pas valides, soit supposer qu'ils sont valides et accepter le fait que votre programme va planter ou pire QUAND cette hypothèse est brisée. Autrement dit, votre choix avec des pointeurs est soit d'introduire une complexité de code, soit de faire plus d'efforts de maintenance en cas de rupture et que vous essayez de retrouver un bogue qui appartient à toute une classe d'erreurs que les pointeurs introduisent, comme la corruption de mémoire.
Donc, si vous contrôlez tout votre code, éloignez-vous des pointeurs et utilisez plutôt des références, en les conservant constantes quand vous le pouvez. Cela vous obligera à réfléchir à la durée de vie de vos objets et finira par garder votre code plus facile à comprendre.
N'oubliez pas cette différence: une référence est essentiellement un pointeur valide. Un pointeur n'est pas toujours valide.
Alors, je dis qu'il est impossible de créer une référence invalide? Non, c'est tout à fait possible, car C ++ vous permet de faire presque n'importe quoi. C'est juste plus difficile à faire involontairement et vous serez étonné de voir combien de bugs sont involontaires :)
Vous pouvez écrire de belles classes wrapper pour les E / S mappées en mémoire avec lesquelles vous pouvez essentiellement éviter l'utilisation de pointeurs.
Fondamentalement, l'architecture CPU standard est une architecture Von Neumann, et il est extrêmement utile de pouvoir se référer à l'emplacement d'un élément de données en mémoire et de faire de l'arithmétique avec lui sur une telle machine. Si vous connaissez une variante du langage d'assemblage, vous verrez rapidement à quel point cela est crucial au bas niveau.
C ++ rend les pointeurs un peu déroutants, car il les gère parfois pour vous et cache leur effet sous la forme de «références». Si vous utilisez le C droit, le besoin de pointeurs est beaucoup plus évident: il n'y a pas d'autre moyen de faire un appel par référence, c'est la meilleure façon de stocker une chaîne, c'est la meilleure façon d'itérer dans un tableau, etc.
Une utilisation des pointeurs (je ne mentionnerai pas les choses déjà couvertes dans les publications d'autres personnes) est d'accéder à la mémoire que vous n'avez pas allouée. Ce n'est pas très utile pour la programmation PC, mais il est utilisé dans la programmation intégrée pour accéder aux périphériques matériels mappés en mémoire.
À l'époque du DOS, vous pouviez accéder directement à la mémoire vidéo de la carte vidéo en déclarant un pointeur sur:
Pas une raison d'utiliser un pointeur - une structure de type span - qui contient également la longueur - n'est pas beaucoup plus appropriée. De nos jours c'est le cas gsl::span, et ce le sera bientôt std::span.
einpoklum
10
En grande partie, les pointeurs sont des tableaux (en C / C ++) - ce sont des adresses en mémoire, et sont accessibles comme un tableau si vous le souhaitez (dans les cas "normaux").
Puisqu'elles sont l'adresse d'un article, elles sont petites: elles n'occupent que l'espace d'une adresse. Puisqu'ils sont petits, les envoyer à une fonction est bon marché. Et puis ils permettent à cette fonction de travailler sur l'élément réel plutôt que sur une copie.
Si vous souhaitez effectuer une allocation de stockage dynamique (comme pour une liste liée), vous devez utiliser des pointeurs, car ils sont le seul moyen d'extraire de la mémoire du tas.
Je pense qu'il est trompeur de dire que les pointeurs sont des tableaux. En réalité, les noms de tableau sont des pointeurs const vers le premier élément du tableau. Tout simplement parce que vous pouvez accéder à un point arbitraire comme si son tableau ne signifie pas qu'il l'est ... vous pourriez obtenir une violation d'accès :)
rmeador
2
Les pointeurs ne sont pas des tableaux. Lire l' article 6 de la FAQ comp.lang.c .
Keith Thompson
Les pointeurs ne sont vraiment pas des tableaux. En outre, il n'y a pas non plus beaucoup d'utilisation dans les tableaux bruts - certainement pas après C ++ 11 et std::array.
einpoklum
@einpoklum - les pointeurs sont suffisamment proches des tableaux pour être équivalents dans les opérations de référence et les itérations à travers les tableaux :) .... aussi - C ++ 11 était à 3 ans de la sortie lorsque cette réponse a été écrite en 2008
warren
@warren: Les pointeurs ne sont pas des tableaux ni proches des tableaux, ils se désintègrent simplement en tableaux. Il est pédagogiquement inapproprié de dire aux gens qu'ils sont similaires. En outre, vous pouvez certainement avoir des références à des tableaux, qui ne sont pas du même type que des pointeurs et qui conservent les informations de taille; voir ici
einpoklum
9
Les pointeurs sont importants dans de nombreuses structures de données dont la conception nécessite la capacité de lier ou de chaîner efficacement un "nœud" à un autre. Vous ne "choisiriez" pas un pointeur plutôt qu'un type de données normal comme float, ils ont simplement des objectifs différents.
Les pointeurs sont utiles lorsque vous avez besoin de hautes performances et / ou d'une empreinte mémoire compacte.
L'adresse du premier élément de votre tableau peut être affectée à un pointeur. Cela vous permet ensuite d'accéder directement aux octets alloués sous-jacents. L'intérêt d'un tableau est de vous éviter d'avoir à le faire.
Une façon d'utiliser des pointeurs sur des variables consiste à éliminer la mémoire en double requise. Par exemple, si vous avez un grand objet complexe, vous pouvez utiliser un pointeur pour pointer vers cette variable pour chaque référence que vous créez. Avec une variable, vous devez dupliquer la mémoire pour chaque copie.
Vraiment, quand on y pense, cela a du sens. Lorsque vous utilisez le polymorphisme de sous-type, en fin de compte, vous ne savez pas à l'avance quelle implémentation de la classe ou de la sous-classe de la méthode sera invoquée car vous ne savez pas quelle est la classe réelle.
Cette idée d'avoir une variable qui contient un objet d'une classe inconnue est incompatible avec le mode par défaut (non pointeur) de C ++ de stockage d'objets sur la pile, où la quantité d'espace allouée correspond directement à la classe. Remarque: si une classe a 5 champs d'instance contre 3, plus d'espace devra être alloué.
Notez que si vous utilisez '&' pour passer des arguments par référence, l'indirection (c'est-à-dire les pointeurs) est toujours impliquée dans les coulisses. Le «&» est juste du sucre syntaxique qui (1) vous évite d'avoir à utiliser la syntaxe des pointeurs et (2) permet au compilateur d'être plus strict (comme interdire les pointeurs nuls).
Non, vous n'êtes pas obligé d'utiliser des pointeurs - vous pouvez utiliser des références. Et lorsque vous écrivez "les pointeurs sont impliqués dans les coulisses" - cela n'a aucun sens. gotoles instructions sont également utilisées en arrière-plan - dans les instructions de la machine cible. Nous ne prétendons toujours pas les utiliser.
einpoklum
@ einpoklum-reinstateMonica Si vous avez un ensemble d'objets sur lesquels vous souhaitez agir, en affectant tour à tour chaque élément à une variable temporaire et en appelant une méthode polymorphe sur cette variable, alors oui, vous avez BESOIN d'un pointeur car vous ne pouvez pas lier une référence.
Sildoreth
Si vous avez une référence de classe de base à une classe dérivée et appelez une méthode virtuelle sur cette référence, le remplacement de la classe dérivée sera appelé. Ai-je tort?
einpoklum
@ einpoklum-reinstateMonica C'est exact. Mais vous ne pouvez pas changer quel objet est référencé. Donc, si vous parcourez une liste / ensemble / tableau de ces objets, une variable de référence ne fonctionnera pas.
Sildoreth
5
Parce que copier de gros objets partout dans le monde fait perdre du temps et de la mémoire.
Et comment les pointeurs aident-ils à cela? Je pense qu'une personne venant de Java ou de .Net ne connaît pas la différence entre la pile et le tas, donc cette réponse est assez inutile ..
Mats Fredriksson
Vous pouvez passer par référence. Cela empêchera la copie.
Martin York,
1
@MatsFredriksson - Au lieu de transmettre (copier) une grande structure de données et de recopier le résultat, il vous suffit de pointer où il se trouve dans la RAM, puis de le modifier directement.
John U
Ne copiez donc pas de gros objets. Personne n'a dit que vous aviez besoin de pointeurs pour cela.
einpoklum
5
Voici ma réponse, et je ne promettrai pas d'être un expert, mais j'ai trouvé des pointeurs excellents dans l'une de mes bibliothèques que j'essaie d'écrire. Dans cette bibliothèque (c'est une API graphique avec OpenGL :-)), vous pouvez créer un triangle avec des objets vertex passés en eux. La méthode de dessin prend ces objets triangulaires, et bien .. les dessine en fonction des objets vertex que j'ai créés. Eh bien, ça va.
Mais, si je change une coordonnée de sommet? Le déplacer ou quelque chose avec moveX () dans la classe vertex? Eh bien, ok, maintenant je dois mettre à jour le triangle, ajouter plus de méthodes et de performances est gaspillé parce que je dois mettre à jour le triangle chaque fois qu'un sommet se déplace. Ce n'est toujours pas un gros problème, mais ce n'est pas terrible.
Maintenant, que se passe-t-il si j'ai un maillage avec des tonnes de sommets et des tonnes de triangles, et que le maillage tourne, se déplace, etc. Je devrai mettre à jour tous les triangles qui utilisent ces sommets, et probablement tous les triangles de la scène car je ne saurais pas lesquels utilisent quels sommets. C'est très intensif en informatique, et si j'ai plusieurs mailles sur un paysage, oh mon dieu! Je suis en difficulté, car je mets à jour chaque triangle, presque chaque image, car ces sommets changent tout le temps!
Avec les pointeurs, vous n'avez pas à mettre à jour les triangles.
Si j'avais trois * objets Vertex par classe de triangle, non seulement j'économise de la place car un triangle zillion n'a pas trois objets vertex qui sont eux-mêmes grands, mais aussi ces pointeurs pointeront toujours vers les sommets auxquels ils sont destinés, peu importe la fréquence à laquelle les sommets changent. Étant donné que les pointeurs pointent toujours vers le même sommet, les triangles ne changent pas et le processus de mise à jour est plus facile à gérer. Si je vous ai dérouté, je n'en doute pas, je ne prétends pas être un expert, jetant juste mes deux cents dans la discussion.
Le besoin de pointeurs en langage C est décrit ici
L'idée de base est que de nombreuses limitations du langage (comme l'utilisation de tableaux, de chaînes et la modification de plusieurs variables dans les fonctions) pourraient être supprimées en manipulant avec les emplacements de mémoire des données. Pour surmonter ces limitations, des pointeurs ont été introduits en C.
De plus, il est également constaté qu'en utilisant des pointeurs, vous pouvez exécuter votre code plus rapidement et économiser de la mémoire dans les cas où vous transmettez des types de données volumineux (comme une structure avec de nombreux champs) à une fonction. Faire une copie de ces types de données avant de passer prendrait du temps et consommerait de la mémoire. C'est une autre raison pour laquelle les programmeurs préfèrent les pointeurs pour les types de données volumineuses.
PS: Veuillez vous référer au lien fourni pour une explication détaillée avec un exemple de code.
La question concerne C ++, cette réponse concerne C.
einpoklum
non, la question est étiquetée c ET c ++. Peut-être que la balise c n'est pas pertinente, mais elle est là depuis le début.
Jean-François Fabre
3
En java et C # toutes les références d'objet sont des pointeurs, le problème avec c ++ est que vous avez plus de contrôle sur l'endroit où vous pointez le pointeur. Souvenez-vous Avec une grande puissance vient une grande responsabilité.
En ce qui concerne votre deuxième question, vous n'avez généralement pas besoin d'utiliser de pointeurs lors de la programmation, mais il y a une exception à cela et c'est lorsque vous créez une API publique.
Le problème avec les constructions C ++ que les gens utilisent généralement pour remplacer les pointeurs sont très dépendants du jeu d'outils que vous utilisez, ce qui est bien lorsque vous avez tout le contrôle dont vous avez besoin sur le code source, cependant si vous compilez une bibliothèque statique avec Visual Studio 2008 par exemple et essayez de l'utiliser dans un studio visuel 2010, vous obtiendrez une tonne d'erreurs de l'éditeur de liens car le nouveau projet est lié à une version plus récente de STL qui n'est pas rétrocompatible. Les choses deviennent encore plus désagréables si vous compilez une DLL et donnez une bibliothèque d'importation que les gens utilisent dans un ensemble d'outils différent, car dans ce cas, votre programme se bloquera tôt ou tard sans raison apparente.
Ainsi, dans le but de déplacer de grands ensembles de données d'une bibliothèque à une autre, vous pouvez envisager de donner un pointeur vers un tableau à la fonction qui est censée copier les données si vous ne voulez pas forcer les autres à utiliser les mêmes outils que vous utilisez . La bonne partie à ce sujet est qu'il n'a même pas besoin d'être un tableau de style C, vous pouvez utiliser un vecteur std :: et donner le pointeur en donnant l'adresse du premier élément & vector [0] par exemple, et utiliser le vecteur std :: pour gérer le tableau en interne.
Une autre bonne raison d'utiliser à nouveau les pointeurs en C ++ concerne les bibliothèques, pensez à avoir une DLL qui ne peut pas être chargée lorsque votre programme s'exécute, donc si vous utilisez une bibliothèque d'importation, la dépendance n'est pas satisfaite et le programme se bloque. C'est le cas par exemple lorsque vous donnez une API publique dans une DLL à côté de votre application et que vous souhaitez y accéder à partir d'autres applications. Dans ce cas, pour utiliser l'API, vous devez charger la DLL à partir de son emplacement (généralement dans une clé de registre), puis vous devez utiliser un pointeur de fonction pour pouvoir appeler des fonctions à l'intérieur de la DLL. Parfois, les personnes qui créent l'API sont assez gentilles pour vous donner un fichier .h qui contient des fonctions d'assistance pour automatiser ce processus et vous donner tous les pointeurs de fonction dont vous avez besoin,
Dans certains cas, les pointeurs de fonction sont requis pour utiliser des fonctions qui se trouvent dans une bibliothèque partagée (.DLL ou .so). Cela comprend l'exécution de choses dans plusieurs langues, où une interface DLL est souvent fournie.
Faire des compilateurs
Faire des calculatrices scientifiques, où vous avez un tableau ou une carte vectorielle ou chaîne de pointeurs de fonction?
Essayer de modifier directement la mémoire vidéo - créer votre propre package graphique
Faire une API!
Structures de données - pointeurs de liaison de nœuds pour les arbres spéciaux que vous créez
Il y a beaucoup de raisons pour les pointeurs. La gestion du nom C en particulier est importante dans les DLL si vous souhaitez conserver la compatibilité entre les langues.
Réponses:
La réponse courte est: non. ;-) Les pointeurs doivent être utilisés là où vous ne pouvez pas utiliser autre chose. C'est soit par manque de fonctionnalités appropriées, soit par manque de types de données, soit par pure performance. Plus ci-dessous ...
La réponse courte ici est: où vous ne pouvez pas utiliser autre chose. En C, vous n'avez aucune prise en charge pour les types de données complexes tels qu'une chaîne. Il n'y a également aucun moyen de passer une variable "par référence" à une fonction. C'est là que vous devez utiliser des pointeurs. Vous pouvez également les faire pointer vers pratiquement n'importe quoi, des listes liées, des membres de structures, etc. Mais n'entrons pas dans les détails ici.
Avec peu d'effort et beaucoup de confusion. ;-) Si nous parlons de types de données simples tels que int et char, il y a peu de différence entre un tableau et un pointeur. Ces déclarations sont très similaires (mais pas les mêmes - par exemple,
sizeof
renverront des valeurs différentes):Vous pouvez atteindre n'importe quel élément du tableau comme celui-ci
Index 1 puisque le tableau commence par l'élément 0. :-)
Ou vous pouvez également le faire
L'opérateur de pointeur (le *) est nécessaire car nous disons à printf que nous voulons imprimer un caractère. Sans le *, la représentation des caractères de l'adresse mémoire elle-même serait imprimée. Maintenant, nous utilisons plutôt le personnage lui-même. Si nous avions utilisé% s au lieu de% c, nous aurions demandé à printf d'imprimer le contenu de l'adresse mémoire pointée par 'a' plus un (dans cet exemple ci-dessus), et nous n'aurions pas eu à mettre le * devant:
Mais cela n'aurait pas simplement imprimé le deuxième caractère, mais à la place tous les caractères dans les adresses de mémoire suivantes, jusqu'à ce qu'un caractère nul (\ 0) soit trouvé. Et c'est là que les choses commencent à devenir dangereuses. Que faire si vous essayez accidentellement d'imprimer une variable de type entier au lieu d'un pointeur char avec le formateur% s?
Cela imprimerait tout ce qui se trouve sur l'adresse mémoire 120 et continuerait d'imprimer jusqu'à ce qu'un caractère nul soit trouvé. Il est faux et illégal d'exécuter cette instruction printf, mais cela fonctionnerait probablement de toute façon, car un pointeur est en fait du type int dans de nombreux environnements. Imaginez les problèmes que vous pourriez causer si vous utilisiez sprintf () à la place et affectiez de cette manière un "tableau de caractères" trop long à une autre variable, qui ne disposait que d'un certain espace limité. Vous finiriez très probablement par écrire sur autre chose dans la mémoire et faire planter votre programme (si vous avez de la chance).
Oh, et si vous n'affectez pas de valeur de chaîne au tableau / pointeur char lorsque vous le déclarez, vous DEVEZ lui allouer suffisamment de mémoire avant de lui donner une valeur. Utilisation de malloc, calloc ou similaire. Ceci puisque vous n'avez déclaré qu'un seul élément de votre tableau / une seule adresse mémoire à pointer. Voici donc quelques exemples:
Notez que vous pouvez toujours utiliser la variable x après avoir effectué un free () de la mémoire allouée, mais vous ne savez pas ce qui s'y trouve. Notez également que les deux printf () peuvent vous donner des adresses différentes, car il n'y a aucune garantie que la deuxième allocation de mémoire est effectuée dans le même espace que la première.
la source
a
est un pointeur et dans le second casa
est un tableau. L'ai-je déjà mentionné? Ce n'est pas la même chose! Vérifiez par vous-même: comparez la taille de (a), essayez d'attribuer une nouvelle adresse à un tableau. Ça ne marchera pas.char* a = "Hello";
etchar a[] = "Hello";
ne sont pas identiques, ils sont très différents. L'un déclare un pointeur, l'autre un tableau. Essayez unsizeof
et vous voyez la différence.L'une des raisons d'utiliser des pointeurs est qu'une variable ou un objet peut être modifié dans une fonction appelée.
En C ++, il est préférable d'utiliser des références que des pointeurs. Bien que les références soient essentiellement des pointeurs, C ++ cache dans une certaine mesure le fait et donne l'impression que vous passez par la valeur. Cela permet de changer facilement la façon dont la fonction appelante reçoit la valeur sans avoir à modifier la sémantique de sa transmission.
Considérez les exemples suivants:
Utilisation de références:
Utilisation de pointeurs:
la source
Voici un exemple en C:
impressions
car il prend la valeur de chaque caractère et l'incrémente d'une unité.
la source
because it takes the value for each character and increments it by one
. Est en représentation ascii ou comment?Les pointeurs sont un moyen d'obtenir une référence indirecte à une autre variable. Au lieu de conserver la valeur d'une variable, ils vous indiquent son adresse . Cela est particulièrement utile lorsque vous traitez des tableaux, car en utilisant un pointeur sur le premier élément d'un tableau (son adresse), vous pouvez trouver rapidement l'élément suivant en incrémentant le pointeur (à l'emplacement d'adresse suivant).
La meilleure explication des pointeurs et de l'arithmétique des pointeurs que j'ai lue se trouve dans le langage de programmation C de K&R . Un bon livre pour commencer à apprendre le C ++ est C ++ Primer .
la source
Permettez-moi d'essayer de répondre à cela aussi.
Les pointeurs sont similaires aux références. En d'autres termes, ce ne sont pas des copies, mais plutôt un moyen de se référer à la valeur d'origine.
Avant toute chose, un endroit où vous devrez généralement beaucoup utiliser des pointeurs est lorsque vous avez affaire à du matériel embarqué . Vous devez peut-être basculer l'état d'une broche d'E / S numérique. Vous traitez peut-être une interruption et devez stocker une valeur à un emplacement spécifique. Vous obtenez l'image. Cependant, si vous ne traitez pas directement avec du matériel et que vous vous demandez simplement quels types utiliser, lisez la suite.
Pourquoi utiliser des pointeurs par opposition aux variables normales? La réponse devient plus claire lorsque vous traitez avec des types complexes, comme les classes, les structures et les tableaux. Si vous deviez utiliser une variable normale, vous pourriez finir par faire une copie (les compilateurs sont assez intelligents pour empêcher cela dans certaines situations et C ++ 11 aide aussi, mais nous resterons à l'écart de cette discussion pour l'instant).
Maintenant, que se passe-t-il si vous souhaitez modifier la valeur d'origine? Vous pouvez utiliser quelque chose comme ceci:
Cela fonctionnera très bien et si vous ne savez pas exactement pourquoi vous utilisez des pointeurs, vous ne devriez pas les utiliser. Méfiez-vous de la raison "ils sont probablement plus rapides". Exécutez vos propres tests et s'ils sont réellement plus rapides, utilisez-les.
Cependant, supposons que vous résolviez un problème où vous devez allouer de la mémoire. Lorsque vous allouez de la mémoire, vous devez la désallouer. L'allocation de mémoire peut réussir ou non. C'est là que les pointeurs sont utiles - ils vous permettent de tester l'existence de l'objet que vous avez alloué et ils vous permettent d'accéder à l'objet pour lequel la mémoire a été allouée en dé-référençant le pointeur.
C'est la raison pour laquelle vous utiliseriez des pointeurs - les références supposent que l'élément auquel vous faites référence existe déjà . Un pointeur ne fonctionne pas.
L'autre raison pour laquelle vous utiliseriez des pointeurs (ou au moins finissez par les gérer) est qu'ils sont un type de données qui existait avant les références. Par conséquent, si vous finissez par utiliser des bibliothèques pour faire les choses que vous savez être meilleures, vous constaterez que beaucoup de ces bibliothèques utilisent des pointeurs partout, simplement en raison de la durée de leur existence (beaucoup d'entre eux ont été écrits avant C ++).
Si vous n'avez utilisé aucune bibliothèque, vous pouvez concevoir votre code de manière à vous tenir à l'écart des pointeurs, mais étant donné que les pointeurs sont l'un des types de base du langage, plus vous vous familiarisez rapidement avec leur utilisation, plus portable vos compétences C ++ seraient.
Du point de vue de la maintenabilité, je dois également mentionner que lorsque vous utilisez des pointeurs, vous devez soit tester leur validité et gérer le cas quand ils ne sont pas valides, soit supposer qu'ils sont valides et accepter le fait que votre programme va planter ou pire QUAND cette hypothèse est brisée. Autrement dit, votre choix avec des pointeurs est soit d'introduire une complexité de code, soit de faire plus d'efforts de maintenance en cas de rupture et que vous essayez de retrouver un bogue qui appartient à toute une classe d'erreurs que les pointeurs introduisent, comme la corruption de mémoire.
Donc, si vous contrôlez tout votre code, éloignez-vous des pointeurs et utilisez plutôt des références, en les conservant constantes quand vous le pouvez. Cela vous obligera à réfléchir à la durée de vie de vos objets et finira par garder votre code plus facile à comprendre.
N'oubliez pas cette différence: une référence est essentiellement un pointeur valide. Un pointeur n'est pas toujours valide.
Alors, je dis qu'il est impossible de créer une référence invalide? Non, c'est tout à fait possible, car C ++ vous permet de faire presque n'importe quoi. C'est juste plus difficile à faire involontairement et vous serez étonné de voir combien de bugs sont involontaires :)
la source
Voici un point de vue légèrement différent, mais perspicace, sur la raison pour laquelle de nombreuses fonctionnalités de C ont un sens: http://steve.yegge.googlepages.com/tour-de-babel#C
Fondamentalement, l'architecture CPU standard est une architecture Von Neumann, et il est extrêmement utile de pouvoir se référer à l'emplacement d'un élément de données en mémoire et de faire de l'arithmétique avec lui sur une telle machine. Si vous connaissez une variante du langage d'assemblage, vous verrez rapidement à quel point cela est crucial au bas niveau.
C ++ rend les pointeurs un peu déroutants, car il les gère parfois pour vous et cache leur effet sous la forme de «références». Si vous utilisez le C droit, le besoin de pointeurs est beaucoup plus évident: il n'y a pas d'autre moyen de faire un appel par référence, c'est la meilleure façon de stocker une chaîne, c'est la meilleure façon d'itérer dans un tableau, etc.
la source
Une utilisation des pointeurs (je ne mentionnerai pas les choses déjà couvertes dans les publications d'autres personnes) est d'accéder à la mémoire que vous n'avez pas allouée. Ce n'est pas très utile pour la programmation PC, mais il est utilisé dans la programmation intégrée pour accéder aux périphériques matériels mappés en mémoire.
À l'époque du DOS, vous pouviez accéder directement à la mémoire vidéo de la carte vidéo en déclarant un pointeur sur:
De nombreux appareils intégrés utilisent encore cette technique.
la source
gsl::span
, et ce le sera bientôtstd::span
.En grande partie, les pointeurs sont des tableaux (en C / C ++) - ce sont des adresses en mémoire, et sont accessibles comme un tableau si vous le souhaitez (dans les cas "normaux").
Puisqu'elles sont l'adresse d'un article, elles sont petites: elles n'occupent que l'espace d'une adresse. Puisqu'ils sont petits, les envoyer à une fonction est bon marché. Et puis ils permettent à cette fonction de travailler sur l'élément réel plutôt que sur une copie.
Si vous souhaitez effectuer une allocation de stockage dynamique (comme pour une liste liée), vous devez utiliser des pointeurs, car ils sont le seul moyen d'extraire de la mémoire du tas.
la source
std::array
.Les pointeurs sont importants dans de nombreuses structures de données dont la conception nécessite la capacité de lier ou de chaîner efficacement un "nœud" à un autre. Vous ne "choisiriez" pas un pointeur plutôt qu'un type de données normal comme float, ils ont simplement des objectifs différents.
Les pointeurs sont utiles lorsque vous avez besoin de hautes performances et / ou d'une empreinte mémoire compacte.
L'adresse du premier élément de votre tableau peut être affectée à un pointeur. Cela vous permet ensuite d'accéder directement aux octets alloués sous-jacents. L'intérêt d'un tableau est de vous éviter d'avoir à le faire.
la source
Une façon d'utiliser des pointeurs sur des variables consiste à éliminer la mémoire en double requise. Par exemple, si vous avez un grand objet complexe, vous pouvez utiliser un pointeur pour pointer vers cette variable pour chaque référence que vous créez. Avec une variable, vous devez dupliquer la mémoire pour chaque copie.
la source
En C ++, si vous souhaitez utiliser le polymorphisme de sous-type , vous devez utiliser des pointeurs. Voir cet article: Polymorphisme C ++ sans pointeurs .
Vraiment, quand on y pense, cela a du sens. Lorsque vous utilisez le polymorphisme de sous-type, en fin de compte, vous ne savez pas à l'avance quelle implémentation de la classe ou de la sous-classe de la méthode sera invoquée car vous ne savez pas quelle est la classe réelle.
Cette idée d'avoir une variable qui contient un objet d'une classe inconnue est incompatible avec le mode par défaut (non pointeur) de C ++ de stockage d'objets sur la pile, où la quantité d'espace allouée correspond directement à la classe. Remarque: si une classe a 5 champs d'instance contre 3, plus d'espace devra être alloué.
Notez que si vous utilisez '&' pour passer des arguments par référence, l'indirection (c'est-à-dire les pointeurs) est toujours impliquée dans les coulisses. Le «&» est juste du sucre syntaxique qui (1) vous évite d'avoir à utiliser la syntaxe des pointeurs et (2) permet au compilateur d'être plus strict (comme interdire les pointeurs nuls).
la source
goto
les instructions sont également utilisées en arrière-plan - dans les instructions de la machine cible. Nous ne prétendons toujours pas les utiliser.Parce que copier de gros objets partout dans le monde fait perdre du temps et de la mémoire.
la source
Voici ma réponse, et je ne promettrai pas d'être un expert, mais j'ai trouvé des pointeurs excellents dans l'une de mes bibliothèques que j'essaie d'écrire. Dans cette bibliothèque (c'est une API graphique avec OpenGL :-)), vous pouvez créer un triangle avec des objets vertex passés en eux. La méthode de dessin prend ces objets triangulaires, et bien .. les dessine en fonction des objets vertex que j'ai créés. Eh bien, ça va.
Mais, si je change une coordonnée de sommet? Le déplacer ou quelque chose avec moveX () dans la classe vertex? Eh bien, ok, maintenant je dois mettre à jour le triangle, ajouter plus de méthodes et de performances est gaspillé parce que je dois mettre à jour le triangle chaque fois qu'un sommet se déplace. Ce n'est toujours pas un gros problème, mais ce n'est pas terrible.
Maintenant, que se passe-t-il si j'ai un maillage avec des tonnes de sommets et des tonnes de triangles, et que le maillage tourne, se déplace, etc. Je devrai mettre à jour tous les triangles qui utilisent ces sommets, et probablement tous les triangles de la scène car je ne saurais pas lesquels utilisent quels sommets. C'est très intensif en informatique, et si j'ai plusieurs mailles sur un paysage, oh mon dieu! Je suis en difficulté, car je mets à jour chaque triangle, presque chaque image, car ces sommets changent tout le temps!
Avec les pointeurs, vous n'avez pas à mettre à jour les triangles.
Si j'avais trois * objets Vertex par classe de triangle, non seulement j'économise de la place car un triangle zillion n'a pas trois objets vertex qui sont eux-mêmes grands, mais aussi ces pointeurs pointeront toujours vers les sommets auxquels ils sont destinés, peu importe la fréquence à laquelle les sommets changent. Étant donné que les pointeurs pointent toujours vers le même sommet, les triangles ne changent pas et le processus de mise à jour est plus facile à gérer. Si je vous ai dérouté, je n'en doute pas, je ne prétends pas être un expert, jetant juste mes deux cents dans la discussion.
la source
Le besoin de pointeurs en langage C est décrit ici
L'idée de base est que de nombreuses limitations du langage (comme l'utilisation de tableaux, de chaînes et la modification de plusieurs variables dans les fonctions) pourraient être supprimées en manipulant avec les emplacements de mémoire des données. Pour surmonter ces limitations, des pointeurs ont été introduits en C.
De plus, il est également constaté qu'en utilisant des pointeurs, vous pouvez exécuter votre code plus rapidement et économiser de la mémoire dans les cas où vous transmettez des types de données volumineux (comme une structure avec de nombreux champs) à une fonction. Faire une copie de ces types de données avant de passer prendrait du temps et consommerait de la mémoire. C'est une autre raison pour laquelle les programmeurs préfèrent les pointeurs pour les types de données volumineuses.
PS: Veuillez vous référer au lien fourni pour une explication détaillée avec un exemple de code.
la source
En java et C # toutes les références d'objet sont des pointeurs, le problème avec c ++ est que vous avez plus de contrôle sur l'endroit où vous pointez le pointeur. Souvenez-vous Avec une grande puissance vient une grande responsabilité.
la source
En ce qui concerne votre deuxième question, vous n'avez généralement pas besoin d'utiliser de pointeurs lors de la programmation, mais il y a une exception à cela et c'est lorsque vous créez une API publique.
Le problème avec les constructions C ++ que les gens utilisent généralement pour remplacer les pointeurs sont très dépendants du jeu d'outils que vous utilisez, ce qui est bien lorsque vous avez tout le contrôle dont vous avez besoin sur le code source, cependant si vous compilez une bibliothèque statique avec Visual Studio 2008 par exemple et essayez de l'utiliser dans un studio visuel 2010, vous obtiendrez une tonne d'erreurs de l'éditeur de liens car le nouveau projet est lié à une version plus récente de STL qui n'est pas rétrocompatible. Les choses deviennent encore plus désagréables si vous compilez une DLL et donnez une bibliothèque d'importation que les gens utilisent dans un ensemble d'outils différent, car dans ce cas, votre programme se bloquera tôt ou tard sans raison apparente.
Ainsi, dans le but de déplacer de grands ensembles de données d'une bibliothèque à une autre, vous pouvez envisager de donner un pointeur vers un tableau à la fonction qui est censée copier les données si vous ne voulez pas forcer les autres à utiliser les mêmes outils que vous utilisez . La bonne partie à ce sujet est qu'il n'a même pas besoin d'être un tableau de style C, vous pouvez utiliser un vecteur std :: et donner le pointeur en donnant l'adresse du premier élément & vector [0] par exemple, et utiliser le vecteur std :: pour gérer le tableau en interne.
Une autre bonne raison d'utiliser à nouveau les pointeurs en C ++ concerne les bibliothèques, pensez à avoir une DLL qui ne peut pas être chargée lorsque votre programme s'exécute, donc si vous utilisez une bibliothèque d'importation, la dépendance n'est pas satisfaite et le programme se bloque. C'est le cas par exemple lorsque vous donnez une API publique dans une DLL à côté de votre application et que vous souhaitez y accéder à partir d'autres applications. Dans ce cas, pour utiliser l'API, vous devez charger la DLL à partir de son emplacement (généralement dans une clé de registre), puis vous devez utiliser un pointeur de fonction pour pouvoir appeler des fonctions à l'intérieur de la DLL. Parfois, les personnes qui créent l'API sont assez gentilles pour vous donner un fichier .h qui contient des fonctions d'assistance pour automatiser ce processus et vous donner tous les pointeurs de fonction dont vous avez besoin,
la source
Il y a beaucoup de raisons pour les pointeurs. La gestion du nom C en particulier est importante dans les DLL si vous souhaitez conserver la compatibilité entre les langues.
la source