Les pointeurs | pointeur.cpp |
Approchez, approchez, mesdames et messieurs. Venez plus près, voilà. Aujourd'hui, je suis venu pour vous parler des pointeurs. Mais qu'est-ce que c'est quoi donc que c'est?
Le pointeur est au C ce que la baguette magique est au prestidigitateur. Ou si vous préférez, programmer en C sans utiliser les pointeurs, c'est comme conduire une Formule 1 en restant en première... avouez que c'est dommage!. Aujourd'hui, nous allons passer à la vitesse supérieure, et introduire dans votre petit bagage algorithmique ce merveilleux outil qu'est le pointeur.
Bon, j'crois que là, Carl il a pété un plomb!
Retour dans la mémoire
Replongeons-nous un instant dans la mémoire vive, ou RAM, de notre ordinateur. Vous savez déjà qu'elle est une sorte de longue série de cases, ou octets (8 bits), et que ces cases sont numérotées. Pour l'instant, tout va bien. Vous savez ensuite que les variables que nous créons sont placées en mémoire. On peut alors se demander où exactement une variable est stockée. Prenons un programme qui commence par créer des variables. Comment ces variables sont-elles disposées en mémoire par le compilateur? Sont-elles placées les unes à la suite des autres, ou sont-elles éparpillées dans toute la mémoire? En regrardant les adresses en mémoire de ces variables, on pourrait savoir comment elles ont été rangées.
Mais comment savoir l'adresse d'une variable en mémoire? On peut en avoir la taille avec sizeof. Et bien de la même façon, on peut en avoir l'adresse avec l'opérateur & placé en préfixe. Voyez plutôt :
int a, b, c;
cout << "Taille d'un int: " << sizeof(int) << endl; |
Ce petit exemple crée trois int en mémoire, affiche la taille d'un int, et affiche les adresses respectives de nos 3 variables. Voici ce que donne l'exécution de ce programme sur mon ordinateur (il est peu probable que vous obteniez exactement les mêmes chiffres chez vous) :
Taille d'un int: 2
Adresse de a: 0x7c272216 Adresse de b: 0x7c272214 Adresse de c: 0x7c272212 |
Première ligne : un int occupe 2 octets en mémoire (encore une fois, votre programme affichera peut-être autre chose, probablement 4). Rien de nouveau sous le soleil.
Ensuite, les adresses : que veulent dire ces trois trucs indigestes que l'ordinateur nous a recraché à la figure, avec des chiffres et des lettres de partout??? En réalité, il s'agit de nombres écrits en base 16, c'est-à-dire en hexadécimal, ou hexa comme je le dirais par la suite. Si vous ne savez pas de quoi je parle, allez voir l'annexe Le langage binaire, qui vous expliquera tout ça.
En C/C++, un nombre hexa commence toujours par ce fameux 0x. Comme cela, pas de confusion possible avec d'autres types de nombres (décimal le plus souvent, octal pour les petits farceurs). Le reste indique la valeur du nombre, soit 7c272216. Vous savez, si vous avez été voir l'annexe, qu'un "chiffre" hexa est codé sur 4 bits. Donc, ce nombre occupe 8*4 = 32 bits en mémoire, soit 4 octets.
![]() |
|
Mais pourquoi? Ce sont des int, alors ils font 2 octets, non?
Là je vous arrête sur ce point très important : l'adresse d'une variable n'a aucun rapport avec sa valeur ou son type. L'adresse d'une variable est celle que veut bien lui donner le compilateur. Donc, que l'on ait des int ou des double, ou des char, ou des CamionDePompier (une petite struct, pourquoi pas? :-), les adresses seront toujours codés sur 32 bits (ici, mais là encore, ça peut varier d'un ordinateur à un autre).
Avec 32 bits, en théorie, on peut compteur jusqu'à 4 milliards, ce qui signifirait qu'on peut adresser 4 milliards d'octets, soient environs 4 Giga-octets (qui peut me dire qu'il a tout ça sur son système aujourd'hui en 2000? Allez, défi! Et si nous sommes déjà en 2020, vous êtes priés de ne pas rigoler).
Pour revenir sur des rails connus, on peut dire qu'une adresse en mémoire est un type d'entier codé sur 32 bits (encore une fois, c'est une question d'architecture).
C'est pas beau de pointer du doigt !
Bon, mais alors c'est bien beau de savoir que toute variable a une adresse, mais à quoi ça sert (ça doit être la question qui énerve le plus les chercheurs en mathématiques pures... ils détestent les applications pratiques :-).
Pensez-vous donc! A plein de choses, pardi! Sinon, je n'en ferais pas l'apologie... pas bête, hein? Bon, imaginez que vous êtes dans la cour de récréation, et qu'un grand blaireau vienne subitement vous remodeler le faciès. Pas content du tout, vous allez voir le pion (qui prend son café en dragant une collègue), et vous lui expliquez que quelqu'un vous a marave la gueu... vous a tapé dessus. Il vous demande de qui il s'agit, et là, pas moyen de sortir son nom : vous ne le connaissez pas. Mais oh! soudain, à ce moment là, une idée de génie vous traverse l'esprit, et voilà qu'une chose étrange se passe : vous levez votre bras à l'horizontale, votre main se ferme mais un doigt (votre index, restons courtois) reste tendu... et il pointe vers le gros méchant blaireau qui vous a refait le nez.
Wunderschön, vous venez de trouver un moyen de désigner quelqu'un, sans pour autant connaître son nom. Eh bien dans un premier temps, c'est à ça que vont nous servir les pointeurs.
Sérieux, les gars, je sais pas ce qu'il a pris aujourd'hui, Carl, mais faut qu'il arrête!
Un pointeur contient l'adresse d'une variable du type déclaré (car les pointeurs aussi sont typés). Pour un peu d'ailleurs, pointeur est un type à part entière. Et donc rien ne l'empêche de pointer sur n'importe quelle variable du type prédéfini. Voici comment cela se passe :
int a, b, c;
int* p; p = &a; p = &b; p = &c; |
Fichtre! Dès la seconde ligne, ça devient bizarre. En effet, vous avez sans doute tous vu la petite subtilité : int* p
Revenons un peu en arrière : pour nous, "int a" signifie : a est une variable de type int. Et bien de la même façon, nous traduirons "int* p" par : p est un pointeur sur une variable de type int. La petite astérisque signifie donc "pointeur". Retenons d'abord :
![]() |
|
En réalité, vous trouverez toutes sortes d'écritures pour déclarer un pointeur :
int* p;
int *q; int * r; |
Toutes sont correctes. Personnellement, je préfère la première, car je pense que pointeur est un type, pas une sorte d'attribut. Je le colle donc avec le type de la valeur pointée. La première ligne exprime mieux le fait que p est un pointeur vers un int.
--- Attention cependant ---
int* p, q;
|
ne déclare pas deux pointeurs, mais un pointeur vers un int, et un int. C'est le seul piège. De toute façon, déclarer plusieurs variables sur la même ligne rend les programmes un peu plus difficiles à lire, et le problème ne se pose pas si vous déclarez une variable à chaque ligne (méthode recommandée par les programmeurs de champions! ... désolé).
Pourquoi faut-il fournir un type de pointeur? Une adresse en mémoire est une adresse en mémoire, quelque soit le type de variable qui s'y trouve non? Nous reviendrons sur cette question lorsque nous parlerons de calculs avec les pointeurs. Sachez d'ici-là qu'il y a un moyen de préciser qu'un pointeur ne pointe sur rien de précis... je vous tiens cruellement en halaine avant de vous dire comment qu'on fait ça.
L'indirection
Encore une fois, tout cela est fort décoratif, avec des petites étoiles partout, mais ça ne sert toujours pas à grand chose... patience jeunes Padowans! La force des pointeurs sera bientôt avec vous.
Maintenant que nous savons comment prendre l'adresse d'une variable, et stocker celle-ci dans un pointeur, voyons ce que nous pouvons faire de la valeur de cette variable, car c'est là que ça devient intéressant...
Continuons de travailler un peu sur l'exemple précédent :
int a = 5, b = 2, c = 17;
int* p; p = &a; p = &b; p = &c; |
Voilà donc le mystère résolu! En utilisant l'opérateur d'indirection * devant le nom du pointeur, vous vous trouvez avec la valeur contenue à cet endroit de la mémoire, et pas avec l'adresse de cet endroit. Magique non? D'ailleurs, que vous afficherait ce programme si vous n'aviez pas mis cet opérateur * devant le p? Je vous le donne en mille : trois adresses en mémoire aussi antipathiques qu'inutiles. Car je vous le rappelle : p contient une valeur qui est une adresse. D'ailleurs, c'est là qu'intervient le typage d'un pointeur : il faut savoir, lors de l'indirection, combien d'octets il faut prendre en compte pour recréer la valeur de la variable, ce qui n'est faisable que si on connaît le type de cette variable (et donc sa taille en octets).
![]() |
|
Il existe une valeur spéciale pour les pointeurs : c'est la valeur 0 (ou NULL comme vous le verrez souvent dans les programmes). En effet, la case numéro 0 de la mémoire n'est pas accessible : on considère donc que l'adresse 0 n'existe pas, ce qui veut dire qu'un pointeur NULL ne contient pas d'adresse, c'est-à-dire qu'il ne pointe sur aucune case en mémoire. Cela peut-être une bonne valeur à tester.
Attention aux pointeurs fous! Comme je vous l'ai expliqué, chaque programme a son espace en mémoire dans lequel il est libre de faire ce qu'il veut. Mais hors de son territoire, il n'a plus le droit d'écrire dans la mémoire, sous peine de se voir terminé illico par le système. Un pointeur fou est un pointeur qui contient une adresse complètement aléatoire, comme cela peut être le cas lors de sa création. Vous ne pouvez donc pas savoir si vous avez le droit d'écrire à cet endroit-là de la mémoire. Et si vous tentez le coup, et que vous n'avez pas le droit d'écrire là, alors vous risquez au mieux de planter le programme. Moralité, il vaut mieux toujours initialiser un pointeur à NULL : c'est la seule adresse qui ne sera jamais allouée.
![]() |
|
Passage de l'adresse d'une variable dans une fonction
Mes frères, la vérité doit éclater au grand jour : vous pouvez également modifier une valeur sur laquelle pointe un pointeur. Exactement comme vous la lisez, vous pouvez également la modifier (comme avec une variable normale) :
int a = 5;
int* p = a; cout << "Un coup, a vaut " << *p; *p = 10; cout << " et un autre coup, a vaut " << a << endl; |
Vous vous doutez bien de l'affichage produit :
Un coup, a vaut 5 et un autre coup, a vaut 10
|
Le premier cout nous rappelle que *p est un synonyme de a (dans ce cas-ci). Et à la ligne suivante, nous constatons qu'écrire *p = 10 revient exactement a écrire a = 10.
On constate donc qu'on peut, avec un pointeur, changer la valeur d'une variable.
Revenons alors un peu en arrière : nous savons pour l'instant que lorsque nous passons une variable comme paramètre à une fonction, la valeur de celle-ci est recopiée dans une autre variable : celle utilisé à l'intérieur de la fonction. Ceci est toujours vrai, mais avec les pointeurs, on peut "contourner" la difficulté. Imaginons par exemple que nous voulions échanger simplement les valeurs de deux variables, à l'aide d'une fonction Swap(int a, int b). Testons ceci :
void Swap(int a, int b)
{ int temp = a; } main() int x = 10, y = 15; } |
Dans le principe, ce programme est bon : dans la fonction swap(), on déclare une variable temporaire, puis on fait une rotation des valeurs de a et de b (sans la variable temporaire, il serait vachement plus difficile de permuter les valeurs de deux variables!). Le problème, c'est que a et b sont des copies locales des variables x et y, et donc changer les unes n'affectent en rien les autres. C'est là que les pointeurs viennent à notre secours :
void Swap(int* a, int* b)
{ int temp = *a; } main() int x = 10, y = 15; } |
Voili-voilou, avec ce programme, nous obtenons l'effet escompté : d'abord x vaut 10 et y vaut 15, et après l'appel à Swap(), x vaut 15 et y vaut 10.
En effet, la fonction Swap() attend maintenant deux pointeurs vers des int en paramètres. On donne donc les adresses respectives de x et de y, et ces adresses sont copiées dans a et dans b. A ce moment-là, en modifiant les valeurs qui se trouvent aux adresses contenues dans a et b, on modifie effectivement les variables dont on a donné l'adresse, c'est-à-dire x et y. C'est pourtant simple de ne pas se tromper...
On peut donc tout à fait passer des adresses en paramètres. Attention de ne pas vous tromper : ne passez pas directement x et y, ou le compilateur vous dirait que vous vous êtes trompé! Un pointeur n'est pas un int, je vous le rappelle.
Bien sûr, on ne passe pas forcément une variable par adresse pour la modifier à l'intérieur d'une fonction. Imaginez que vous avez fait une struct qui contienne beaucoup de variables membres. Faire une copie de cette grosse struct à chaque appel de fonction peut être très prenant. Dans ce cas-là, passez tout s'implement son adresse et le tour est joué (l'appel est bien plus rapide, et également plus sûr, comme nous le verrons plus tard). En règle générale, il vaut même mieux passer les struct (ou autres collections semblables) par adresse. Un peu plus loin dans ce cours, nous verrons comment utiliser les pointeurs sur une struct.
![]() |
|
La relation entre tableaux et pointeurs
La relation en C/C++ entre tableaux et pointeurs est très marquée. Bien sûr, comme toute variable qui se respecte, vous pouvez obtenir l'adresse d'un élément d'un tableau, de la manière la plus naturelle qui soit :
double tabl[10];
cout << "adresse de tabl[0] = " << &tabl[0] << endl; |
Jusque là, rien de surprenant, puisque nous savons comment obtenir l'adresse d'une variable, et que les membres d'un tableau sont plus des variables comme des autres.
Par contre, ce que vous ne savez pas encore (à part ceux qui le savent déjà, bien sûr :-), c'est la nature de la variable tabl. Il n'existe pas en C++ de type tableau. Et pourtant, nous déclarons une variable tabl... Est-ce un double? Essayons, pour voir :
double tabl[10];
tabl = 0.4; |
Ceux qui tenteront le coup se retrouverons avec un très convivial Illegal use of floating point à l'écran lors de la compilation. Ceci signifie donc que ce n'est pas un double. Il ne reste qu'une solution : c'est un pointeur!!! En effet, vous vous souvenez qu'un tableau est une suite de variables (toutes du même type) qui sont placées les unes après les autres en mémoire. Et bien tabl pointe tout simplement sur le début de cette zone de mémoire, si bien qu'en réalité, on a
tabl == &tabl[0]
Autrement dit, tabl est un pointeur vers un double, qui se trouve être le premier élément d'un tableau de doubles.
Revenons alors un peu sur la notation entre crochets. Que signifie réellement tabl[5] ? Pour nous, cela veut dire le sixième élément du tableau désigné par tabl. Et c'est tout ce qu'il nous faut, nous aurions tort de nous compliquer la vie à penser plus compliqué. Mais le compilateur, lui, interprète cela comme autre chose. Il connaît la taille en mémoire de nos objets (ici, des double). En supposant que chaque objet "mesure" n octets, tabl[5] est situé à 5*n octets du début de la zone mémoire occupée par le tableau.
![]() |
|
Hum... avant de continuer, voyons quelques précisions concernant...
L'arithmétique des pointeurs
Ciel, encore des calculs!!! Ce ne finira jamais!
On peut faire des calculs avec les pointeurs, mais les règles sont un peu différentes. Un pointeur contient une adresse, qui exprime un décalage, en octets, depuis l'adresse 0. Regardez le petit programme suivant :
int a = 10, *p = &a;
cout << "a est un int, de taille " << sizeof(a) << " octets\n"; |
Vous obtiendrez un affichage similaire à :
a est un int de taille 2 octets
adresse de a: 0x63cf225a adresse suivante: 0x63cf225c |
Bien sûr, vous n'aurez certainement pas les mêmes valeurs. Par contre, voici ce que nous constatons tous : si nous faisons la différence entre l'adresse suivante et l'adresse de a, nous obtenons un décalage du nombre d'octets occupé par un int (a dans notre cas) - Rappelez-vous qu'une adresse est affichée en hexadécimal.
Pourtant, vous remarquez que nous n'avons incrémenté le pointeur p que de 1 (++p)... en réalité, lorsqu'on travaille avec les pointeurs, l'unité ne vaut plus 1, mais la taille de l'objet vers lequel on pointe (2 octets dans cet exemple-ci). Donc ++p (tout comme p = p + 1) incrémente de 1*sizeof(int) = 2 l'adresse contenue dans p.
Pour en revenir sur notre notation de tabl[5], voici un moyen tordu de l'interpréter (maintenant que nous savons calculer avec des pointeurs) :
int tabl[10];
tabl[5] = 23; cout << "tabl[5] vaut " << tabl[5] << " ou encore " << *(tabl + 5) << endl; |
La première façons d'afficher la valeur de tabl[5] est la façon classique. La seconde est une façon détournée : *(tabl + 5) signifie la valeur contenue à l'adresse contenue dans tabl plus un décalage de 5 fois le nombre d'octets occupés par un int. Allez, facile! La valeur contenue entre crochets représente donc un décalage en mémoire. Si vous indiquez entre les crochets un décalage dépassant la taille limite du tableau, le compilateur n'y voit que du feu : il va aller mettre un pointeur là où vous lui indiquez, et va faire à cet endroit tout ce que vous lui demandez de faire... sous réserve que vous ayez accès à cette zone de la mémoire.
Bon. Je vois que vous restez sceptiques sur la relation entre tableaux et pointeurs. Voici un petit programme qui va vous montrer que je ne délire pas. Nous créons deux tableaux de même taille, et nous fixons la dernière valeur du premier tableau à 0, pous indiquer la fin du tableau. Ensuite, nous copions toutes les valeurs du premier tableau dans le second, à l'aide d'une syntaxe ultra-compacte, très caractéristique du C/C++. Regardez plutôt :
#include <iostream.h>
void AfficheTableau(int* t) while(*t++) cout << *t << " "; } main() int tableau1[10] = {5, 3, 9, 11, 4, 132, 45, 2, 89, 0}; AfficheTableau(tableau2); while(*t2++ = *t1++); // Sans les mains! AfficheTableau(tableau2); } |
Vous pouvez le tester. L'affichage est relativement prévisible :
-1 -1 -1 -1 -1 -1 -1 -1 -1
5 3 9 11 4 132 45 2 89 |
Nous avons donc bien copié le contenu de tableau1 dans tableau2. Mais regardez bien : la boucle, la copie de valeur ainsi que l'incrémentation des pointeurs ne se fait qu'en une seule instruction : while(*t2++ = *t1++); Ca, c'est du compact!
Vous remarquerez également que les crochets n'apparaissent que là ils sont réellement nécessaires : lors de la déclaration des deux tableaux. Ensuite, tout le travail se fait avec des pointeurs. Ceci devrait déjà vous convaincre un peu plus que pointeurs et tableaux sont intimement liés.
Observez églamement la manière dont le tableau est passé à la fonction AfficheTableau() : on passe l'adresse du premier élément, contenue dans tableau2 (sans crochet!). Attention, n'écrivez pas AfficheTableau(&tableau), car vous passeriez l'adresse d'un pointeur, ce qui produirait un affichage imprévisible (heureusement qu'on ne fait que lire des valeurs en mémoire!).
Pointeur sur une structure
Maintenant que nous avons examiné l'utilisation de pointeurs pour manipuler les tableaux, voyons ce qu'il en est des structures. Car accéder à une variable membre d'une struct dont on a l'adresse ne se fait pas de la même manière que l'accès habituel. Cependant, peu de choses changent.
#include <iostream.h>
struct ABC int a, b, c; }; void ChangerValeurs(ABC* p, int valeur) p->a = valeur - 1; } main() ABC maStruct; } |
L'affichage est évident.
Observez la nouvelle syntaxe : dans main(), nous avons notre ABC appelé maStruct, et nous accédons à ces membres avec l'opérateur ".", jusqu'ici, tout va bien.
Par contre, dans ChangerValeurs(), nous utilisons à la place de "." l'opérateur "->". Ceci est du au fait qu'un pointeur ne contient pas de variables membres : il s'agit ni plus ni moins que d'une adresse. Par contre, ce pointeur pointe vers une structure qui elle, possède trois variables membres a, b et c de type int. L'opérateur d'indirection "->" permet donc au compilateur de savoir qu'il faut aller chercher, à l'adresse précisée, des données membres. Pour l'instant, nous nous en tiendrons ici, car on peut affreusement compliquer les choses lorsqu'on s'enfonce vers une programmation plus complexe, mais à notre niveau, il n'en est pas encore question. Ceci nous suffit amplement pour le moment : retenez qu'il faut remplacer "." par "->" lorsqu'on manipule un pointeur vers une structure. C'est ce qu'on appelle le déréférencement.
![]() |
|
Pointeur sur un pointeur (Eh ! 'faut pas pousser mémé dans les orties !)
Allons bon, encore une touche d'exotisme. Et ils font aussi la danse du ventre, non? Dommage!
Et d'abord, pourquoi pas (faire un pointeur vers un pointeur, pas danser la danse du ventre!)? Après tout, nous considérons un pointeur comme une varaible comme les autres, à part qu'elle sait faire plus de choses, non? Alors il serait envisageable de faire un pointeur vers un pointeur. Vous verrez par la suite, d'ailleurs, que cela peut servir (normal, sinon je ne vous en parlerais pas!). Alors voici comment faire un pointeur sur un pointeur :
int a = 14;
int* p = &a; int** pp = &p; cout << "a vaut : (" << a << "," << *p << "," << **pp << ")\n"; |
Vous avez vu la belle indirection, avec deux étoiles et tout et tout??? Voilà, c'était vraiment pas plus sorcier que ça. Désolé, l'affichage est tellement barbare que je ne le mets pas ici. Tout ce qu'il faut constater, c'est que sur chaque ligne, on voit plusieurs fois la même valeur (ce qui était largement prévisible).
Pointer sur tout et n'importe quoi
Simple curiosité : si un pointeur contient une adresse en mémoire, comment se fait-il qu'on soit obligé de respecter un certain type pour le contenu de cette mémoire. Ne peut-on pas dire : je veux pointer à cet endroit-là de la mémoire sans nécessairement savoir quel type de donnée s'y trouve? En fait, il existe un moyen de pointer sur un peu tout et n'importe quoi. Vous vous rappelez du type void? C'est grâce à lui qu'on va préciser que l'on ne donne pas de type particulier à un pointeur. Mais dès lors que vous déclarez un pointeur void (void* p;) vous ne pouvez pas faire d'indirection comme d'habitude : il faut effectuer une conversion pour pouvoir récupérer la valeur contenue à l'adresse en question :
int a = 14;
void* p = &a; cout << *((int*)p); |
Sans cette conversion, vous ne pourriez pas obtenir la valeur souhaitée, car void n'est pas un type de donnée : ici, il signifie pointeur vers un type quelconque. Donc, si la variable stockée à cet endroit n'est pas un int, vous vous retrouvez avec une valeur qui n'a pas de réalité.
![]() |
|
Bien sûr, tout ceci s'applique aussi bien aux types fondamentaux qu'aux structures et autres mutants arachnoïdes que nous propose le C++. Attention, si vous utilisez un pointeur void sur une struct, vous pouvez (moyennant une petite converions du pointeur) accéder aux membres de la structure, mais vous pourriez aussi faire cela avec n'importe quel type :
struct Exemple { int a, b; } e;
int i; void* p; e.a = 45; p = &e; p = &i; |
L'affichage peut surprendre :
e.a = 45
e.b = 32; i.a = 12 ???? i.b = 45 !!!! |
Revoyons le programme : d'abord, nous créons une structure Exemple, et nous en déclarons l'instance e. Cette structure comporte deux membres : a et b. Ensuite, nous déclarons un int i et un pointeur void p.
On commence par donner à p l'adresse de e, et on affiche (grâce à une conversion du pointeur afin d'accéder aux membres) les valeurs de e.a et e.b. Tout va bien.
Par contre, lorsqu'on donne à p l'adresse de i, rien ne va plus : on fait exactement la même chose qu'avec e, c'est-à-dire qu'on accède aux membres a et b de i... qui n'existent pas!!! Et pourtant, le compilateur ne nous dit rien. On obtient alors le résultat un peu étrange ci-dessus. Comme quoi, les pointeurs peuvent vraiment nous faire jouer n'importe comment avec la mémoire! Attention donc avec les pointeurs void.
Mise en application
Pour mettre en application ce que nous avons vu aujourd'hui, nous allons reprendre l'un de nos programmes précédents, celui du cours sur les tableaux, où il s'agissait de trier un tableau d'entiers. Nous allons réécrire ce programme pour utiliser les pointeurs, qui nous permettrons entre autres d'alléger le code de la fonction de tri.
1
2 4 7 8 10 13 14 16 17 18 19 20 21 23 25 28 29 |
#include <iostream.h>
void Swap(int* val1, int* val2) int temp = *val1; } void PrintTable(int* table, int size) for(int i = 0; i < size; i++) } void Sort(int* table, int size) for(int start = 0; start < size-1; start++) for(int k = start + 1; k < size; k++) if(table[k] < table[start]) Swap(&table[start], &table[k]); } int main() int table[] = {4, 6, 3, 7, 9, 2, 1, 10, 5, 8}; PrintTable(table, size); return 0; } |
Ce programme peut vous sembler bien plus long a priori, mais croyez-moi, les pointeurs sont un avantage : nous pouvons maintenant utiliser des fonction pour subdiviser le travail en tâches simples, et on pourrait sans effort trier 10 tableaux sans efforts (ce qui n'était pas garanti la dernière fois!).
Comme d'habitude, notre algorithme de tri est une merveille d'efficacité et de rapidité, et je vous laisse le soin d'en trouver des meilleurs. Remarquez qu'il est assez simple! Nous utilisons la fonction Swap(int*, int*) comme nous l'avons écrite plus haut dans le cours, une une fonction PrintTable(int*, int) pour afficher un tableau (je vous rappelle que lors d'un passage à une fonction, on ne peut plus connaître automatiquement la longueur du tableau, on est donc obligé de passer la taille du tableau comme second paramètre.
Comme tous nos ancètres l'ont fait avant nous pendant des millénaires, nous allons nous aussi commencer par revoir les points importants de ce cours :
![]() |
|
Voyons maintenant ce que nous pourrions développer avec les pointeurs. Cependant, les pointeurs vont devenir quotidiens et vous apprendrez très vite à vous en servir de toute façon.
![]() |
|
Voilà pour aujourd'hui. Dans les cours à venir, nous parlerons beaucoup de pointeurs, alors assurez-vous d'avoir bien tout compris avant de continuer.
Concernant le problème des 8 Reines, mis en exercice dans le cours sur les tableaux, je remercie Freddy qui m'a très vite envoyé une bonne solution. J'attends les autres!!!
Voir aussi: Le langage binaire - les tableaux