Je sais que la question est vraiment fondamentale, mais je viens tout juste de commencer à programmer en C++, après avoir codé quelques projets avec des langages de haut niveau.
Fondamentalement, j'ai trois questions:
La réponse courte est: non. ;-) Les pointeurs doivent être utilisés là où vous ne pouvez rien utiliser d’autre. Cela est dû soit au manque de fonctionnalités appropriées, à l'absence de types de données, soit à des performances optimales. Plus ci-dessous ...
La réponse courte ici est: Où vous ne pouvez utiliser rien d'autre. En C, vous ne supportez pas les types de données complexes tels qu'une chaîne. Il n’existe également aucun moyen de transmettre une variable "par référence" à une fonction. C'est là que vous devez utiliser des pointeurs. Vous pouvez aussi les faire pointer pratiquement tout, listes chaînées, membres de structures, etc. Mais n'entrons pas dans cela 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 identiques - par exemple, sizeof
renverra des valeurs différentes):
char* a = "Hello";
char a[] = "Hello";
Vous pouvez atteindre n'importe quel élément du tableau comme ceci
printf("Second char is: %c", a[1]);
Index 1 puisque le tableau commence par l'élément 0. :-)
Ou vous pouvez également faire ceci
printf("Second char is: %c", *(a+1));
L'opérateur de pointeur (*) est nécessaire car nous disons à printf que nous voulons imprimer un caractère. Sans le *, la représentation en caractères de l'adresse de la mémoire elle-même serait imprimée. Maintenant, nous utilisons le personnage lui-même à la place. Si nous avions utilisé% s au lieu de% c, nous aurions demandé à printf d'imprimer le contenu de l'adresse mémoire indiquée par 'a' plus un (dans cet exemple ci-dessus), et nous n'aurions pas dû mettre le * devant:
printf("Second char is: %s", (a+1)); /* WRONG */
Mais cela n'aurait pas simplement imprimé le deuxième caractère, mais tous les caractères des adresses mémoire suivantes, jusqu'à ce qu'un caractère nul (\ 0) ait été 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 de caractère 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 à 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, puisqu'un pointeur est du type int dans de nombreux environnements. Imaginez les problèmes que vous pourriez rencontrer si vous utilisiez plutôt sprintf () et affectez ainsi "tableau de caractères" trop long à une autre variable, qui ne dispose que d'un certain espace. Vous risqueriez probablement d’écrire par-dessus un autre élément de la mémoire et de provoquer le blocage de votre programme (si vous avez de la chance).
Oh, et si vous n'attribuez pas de valeur de chaîne au tableau de caractères/au pointeur lorsque vous la déclarez, vous DEVEZ lui allouer une quantité de mémoire suffisante avant de lui attribuer une valeur. Utilisation de malloc, calloc ou similaire. Ceci puisque vous n’avez déclaré qu’un élément dans votre tableau/une seule adresse de mémoire à pointer. Alors voici 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 qu'il y a dedans. Notez également que les deux printf () peuvent vous donner des adresses différentes, car rien ne garantit que la deuxième allocation de mémoire est effectuée dans le même espace que le premier.
Une des raisons d'utiliser des pointeurs est la possibilité de modifier une variable ou un objet 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 en partie le fait et donne l’impression que vous passez par valeur. Cela facilite la modification de la manière dont la fonction appelante reçoit la valeur sans avoir à modifier la sémantique de sa transmission.
Considérez les exemples suivants:
Utiliser des références:
public void 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
}
public void doSomethingElse(int& i) // receives i as a reference
{
cout << i << endl;
}
Utiliser des pointeurs:
public void doSomething()
{
int i = 10;
doSomethingElse(&i);
}
public void doSomethingElse(int* i)
{
cout << *i << endl;
}
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);
empreintes
ifmmp
parce qu'il prend la valeur pour chaque caractère et l'incrémente d'un.
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 . Ceci est particulièrement utile lorsque vous utilisez des tableaux, car en utilisant un pointeur sur le premier élément d’un tableau (son adresse), vous pouvez rapidement trouver l’élément suivant en incrémentant le pointeur (à l’emplacement de l’adresse suivante).
La meilleure explication des pointeurs et de l'arithmétique des pointeurs que j'ai lue est celle de K & R Le langage de programmation C . Un bon livre pour commencer à apprendre le C++ est C++ Primer .
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 faire référence à la valeur d'origine.
Avant toute chose, un endroit où vous devrez généralement utiliser des pointeurs c'est souvent lorsque vous faites affaire avec du matériel embarqué. Peut-être devez-vous basculer l'état d'une broche numérique IO. Peut-être que vous traitez une interruption et avez besoin de 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 lorsqu'il s'agit de types complexes, tels que des classes, des structures et des tableaux. Si vous utilisiez une variable normale, vous pourriez en faire une copie (les compilateurs sont assez intelligents pour empêcher cela dans certaines situations et C++ 11 aussi, mais nous allons rester à l’écart de cette discussion pour le moment).
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 nécessitant l’allocation de mémoire. Lorsque vous allouez de la mémoire, vous devez la désallouer. L'allocation de mémoire peut être ou ne pas réussir. C’est là que les pointeurs sont utiles - ils vous permettent de vérifier l’existence de l’objet vous avez alloué et 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 pointer
if(p)
{
//we never reach here, because the pointer points to nothing
}
//now, let's allocate some memory
p = new MyType[50000];
if(p) //if the memory was allocated, this test will pass
{
//we can do something with our allocated array
for(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 que vous référencez existe déjà. Un pointeur ne le fait pas.
L'autre raison pour laquelle vous utiliseriez des pointeurs (ou du moins que vous finissiez par devoir les gérer) est parce 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 pour lesquelles vous savez qu’elles sont meilleures, vous constaterez que beaucoup de ces bibliothèques utilisent des pointeurs un peu partout, simplement à cause de leur ancienneté (beaucoup d'entre eux ont été écrits avant C++).
Si vous n’utilisez aucune bibliothèque, vous pouvez concevoir votre code de manière à éviter les pointeurs. Toutefois, étant donné que les pointeurs sont l’un des types de base du langage, plus vous vous familiariserez vite avec ces derniers, plus portable vos compétences en C++ seraient.
Du point de vue de la maintenabilité, je devrais également mentionner que lorsque vous utilisez des pointeurs, vous devez soit vérifier leur validité et gérer le cas où ils ne le sont pas, soit simplement supposer qu'ils sont valides et accepter le fait que votre programme va planter ou pire quand cette hypothèse est brisée. En d'autres termes, votre choix avec les pointeurs est d'introduire soit la complexité du code, soit plus d'effort de maintenance en cas de problème et que vous essayez de localiser un bogue qui appartient à toute une classe d'erreurs introduites par les pointeurs, 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 gardant const lorsque vous le pouvez. Cela vous obligera à réfléchir sur la durée de vie de vos objets et à rendre votre code plus facile à comprendre.
Rappelez-vous simplement cette différence: ne référence est essentiellement un pointeur valide. Un pointeur n'est pas toujours valide.
Alors, est-ce que je dis qu'il est impossible de créer une référence invalide? Non, c'est tout à fait possible, car le C++ vous permet de faire presque n'importe quoi. C'est plus difficile à faire par inadvertance et vous serez étonné de voir combien de bugs sont involontaires :)
Voici un point de vue légèrement différent, mais perspicace, sur la raison pour laquelle de nombreuses fonctionnalités de C ont du sens: http://steve.yegge.googlepages.com/tour-de-babel#C
Fondamentalement, l'architecture de processeur standard est une architecture de 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 procéder à un calcul arithmétique sur un tel ordinateur. Si vous connaissez une variante du langage d'assemblage, vous verrez rapidement à quel point c'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 straight C, le besoin de pointeurs est beaucoup plus évident: il n'y a pas d'autre moyen de faire appel par référence, c'est le meilleur moyen de stocker une chaîne, c'est le meilleur moyen de parcourir un tableau, etc.
Une utilisation des pointeurs (je ne mentionnerai pas de 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 sur PC, mais il est utilisé dans la programmation intégrée pour accéder à des périphériques matériels mappés en mémoire.
Autrefois, sous DOS, vous pouviez accéder directement à la mémoire vidéo de la carte vidéo en déclarant un pointeur sur:
unsigned char *pVideoMemory = (unsigned char *)0xA0000000;
De nombreux périphériques intégrés utilisent encore cette technique.
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 des cas "normaux").
Comme ils sont l'adresse d'un article, ils sont petits: ils ne prennent que l'espace d'une adresse. Comme ils sont petits, les envoyer à une fonction est bon marché. Et puis, ils permettent à cette fonction de fonctionner sur l'élément réel plutôt que sur une copie.
Si vous souhaitez effectuer une allocation de stockage dynamique (par exemple, pour une liste liée), vous devez utiliser des pointeurs, car ils constituent le seul moyen de récupérer de la mémoire du tas.
Les pointeurs jouent un rôle important 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 voudriez pas "choisir" un pointeur sur un type de données normal comme float, ils ont simplement des buts différents.
Les pointeurs sont utiles lorsque vous avez besoin de performances élevées et/ou d’empreinte mémoire compacte.
L'adresse du premier élément de votre tableau peut être assignée à un pointeur. Cela vous permet ensuite d'accéder directement aux octets alloués sous-jacents. Tout l'intérêt d'un tableau est d'éviter que vous ayez à le faire.
Une façon d'utiliser les pointeurs sur les variables consiste à éliminer la mémoire requise en double. Par exemple, si vous avez un objet complexe volumineux, vous pouvez utiliser un pointeur pour pointer sur cette variable pour chaque référence que vous faites. Avec une variable, vous devez dupliquer la mémoire pour chaque copie.
Parce que copier de gros objets dans tous les lieux fait perdre du temps et de la mémoire.
En C++, si vous souhaitez utiliser le sous-type polymorphisme , vous avez utiliser des pointeurs. Voir ce post: Polymorphisme C++ sans pointeurs .
Vraiment, quand on y pense, cela a du sens. En fin de compte, lorsque vous utilisez le polymorphisme de sous-type, vous ne savez pas à l'avance quelle implémentation de la méthode ou de la classe ou de la sous-classe sera invoquée, car vous ne savez pas quelle est la classe réelle.
Cette idée d'avoir une variable contenant un objet d'une classe inconnue est incompatible avec le mode par défaut (sans pointeur) de C++ de stockage des 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, il faudra allouer plus d'espace.
Voici ma réponse, et je ne vais pas promettre d'être un expert, mais j'ai trouvé des pointeurs géniaux 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 contenant des objets de sommet. La méthode draw utilise ces objets triangle, et bien .. les dessine en fonction des objets de sommet que j'ai créés. Eh bien, c'est bon.
Mais, si je change une coordonnée de sommet? Déplacez-le ou quelque chose avec moveX () dans la classe de vertex? Bon, ok, maintenant je dois mettre à jour le triangle, ajouter plus de méthodes et la performance est gaspillée parce que je dois mettre à jour le triangle chaque fois qu'un sommet se déplace. Ce n'est toujours pas grave, 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 si le maillage est en rotation, et en mouvement, etc. Je devrai mettre à jour chaque triangle qui utilise ces sommets, et probablement chaque triangle de la scène, car je ne saurais pas lesquels utilisent quels sommets. C'est énormément consommateur en informatique, et si j'ai plusieurs maillages sur un paysage, oh mon dieu! Je suis en difficulté, car je mets à jour presque chaque image dans chaque triangle, car ces sommets changent tout le temps!
Avec les pointeurs, vous n'avez pas à mettre à jour les triangles.
Si j'avais trois * objets sommet par classe de triangle, non seulement je gagne de la place car un zillion de triangles n'a pas trois objets sommet qui sont grands, mais ces pointeurs pointeront toujours sur les sommets auxquels ils sont destinés, peu importe la raison. combien de fois les sommets changent. Comme les pointeurs pointent toujours sur le même sommet, les triangles ne changent pas et le processus de mise à jour est plus facile à gérer. Si je vous confondais, je n'en douterais pas, je ne prétends pas être un expert, je mets simplement mes deux sous 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 (telles que l'utilisation de tableaux, de chaînes de caractères et la modification de plusieurs variables dans des fonctions) pourraient être supprimées en manipulant les emplacements mémoire des données. Pour surmonter ces limitations, des pointeurs ont été introduits dans C.
De plus, nous voyons également 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 volumineuses (comme une structure comportant 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.
Dans 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. Rappelez-vous Avec un grand pouvoir vient une grande responsabilité.
En ce qui concerne votre deuxième question, vous n'avez généralement pas besoin d'utiliser des pointeurs lors de la programmation. Cependant, il existe une exception à cela: vous créez une API publique.
Le problème des constructions C++ que les utilisateurs utilisent généralement pour remplacer les pointeurs est très dépendant du jeu d’outils que vous utilisez, ce qui est bien lorsque vous avez tout le contrôle nécessaire sur le code source. Toutefois, si vous compilez une bibliothèque statique avec Visual Studio 2008, par exemple. Si vous essayez de l'utiliser dans Visual Studio 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 compatible avec les versions antérieures. Les choses deviennent encore plus fastidieuses si vous compilez une DLL et donnez une bibliothèque d'importation que les utilisateurs utilisent dans un jeu d'outils différent, car dans ce cas, votre programme se plantera 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 sur un tableau vers la fonction censée copier les données si vous ne voulez pas forcer les autres utilisateurs à utiliser les mêmes outils que vous. . La bonne partie de ceci est qu’il n’est même pas nécessaire que ce soit un tableau de style C, vous pouvez utiliser un std :: vector et donner le pointeur en donnant l’adresse du premier élément & vector [0] par exemple, et utiliser le std :: vector pour gérer le tableau en interne.
Une autre bonne raison d'utiliser des pointeurs en C++ concerne à nouveau les bibliothèques: pensez à ne pas pouvoir charger une dll lors de l'exécution de votre programme. Ainsi, 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 dans la DLL. Parfois, les personnes qui fabriquent l’API sont assez sympas pour vous fournir un fichier .h contenant des fonctions auxiliaires pour automatiser ce processus et vous fournir tous les pointeurs de fonction dont vous avez besoin. Sinon, vous pouvez utiliser LoadLibrary et GetProcAddress sur windows et dlopen et dlsym sur unix pour les obtenir (étant donné que vous connaissez la signature entière de la fonction).
Il y a beaucoup de raisons pour les pointeurs. Avoir un nom C en particulier est important dans les DLL si vous souhaitez conserver la compatibilité entre les langues.