Chapitre 1: LES POINTEURS
L'importance des pointeurs en C
La plupart des langages de programmation offrent la possibilité d'accéder aux données dans la
mémoire de l'ordinateur à l'aide de pointeurs, c.-à-d. à l'aide de variables auxquelles on peut attribuer
les adresses d'autres variables.
En C, les pointeurs jouent un rôle primordial dans la définition de fonctions: Comme le passage des
paramètres en C se fait toujours par la valeur, les pointeurs sont le seul moyen de changer le contenu
de variables déclarées dans d'autres fonctions. Ainsi le traitement de tableaux et de chaînes de
caractères dans des fonctions serait impossible sans l'utilisation de pointeurs.
En outre, les pointeurs nous permettent d'écrire des programmes plus compacts et plus efficients et
fournissent souvent la seule solution raisonnable à un problème. Ainsi, la majorité des applications
écrites en C profitent extensivement des pointeurs.
Le revers de la médaille est très bien formulé par Kernighan & Ritchie dans leur livre 'Programming in
C':
" ... Les pointeurs étaient mis dans le même sac que l'instruction goto comme une excellente
technique de formuler des programmes incompréhensibles. Ceci est certainement vrai si les pointeurs
sont employés négligemment, et on peut facilement créer des pointeurs qui pointent 'n'importe où'.
Avec une certaine discipline, les pointeurs peuvent aussi être utilisés pour programmer de façon claire
et simple. C'est précisément cet aspect que nous voulons faire ressortir dans la suite. ..."
Cette constatation a ensuite motivé les créateurs du standard ANSI-C à prescrire des règles explicites
pour la manipulation des pointeurs.
1.1. Adressage de variables
1.2. Les pointeurs
1.3. Pointeurs et tableaux
1.4. Tableaux de pointeurs
1.5. Allocation dynamique de mémoire
1.1. Adressage de variables
Avant de parler de pointeurs, il est indiqué de brièvement passer en revue les deux modes
d'adressage principaux, qui vont d'ailleurs nous accompagner tout au long des chapitres suivants.
1.1.1. Adressage direct
1.1.2. Adressage indirect
1.1.1. Adressage direct
Dans la programmation, nous utilisons des variables pour stocker des informations. La valeur d'une
variable se trouve à un endroit spécifique dans la mémoire interne de l'ordinateur. Le nom de la
variable nous permet alors d'accéder directement à cette valeur.
ASD2 - TI
1
Adressage direct: Accès au contenu d'une variable par le nom de la variable.
Exemple
1.1.2. Adressage indirect
Si nous ne voulons ou ne pouvons pas utiliser le nom d'une variable A, nous pouvons copier l'adresse
de cette variable dans une variable spéciale P, appelée pointeur. Ensuite, nous pouvons retrouver
l'information de la variable A en passant par le pointeur P.
Adressage indirect: Accès au contenu d'une variable, en passant par un pointeur qui contient
l'adresse de la variable.
Exemple
Soit A une variable contenant la valeur 10 et P un pointeur qui contient l'adresse de A. En mémoire, A
et P peuvent se présenter comme suit:
1.2. Les pointeurs
Définition: Pointeur
Un pointeur est une variable spéciale qui peut contenir l'adresse d'une autre variable.
En C, chaque pointeur est limité à un type de données. Il peut contenir l'adresse d'une variable simple
de ce type ou l'adresse d'une composante d'un tableau de ce type.
Si un pointeur P contient l'adresse d'une variable A, on dit que 'P pointe sur A'.
Remarque
Les pointeurs et les noms de variables ont le même rôle: Ils donnent accès à un emplacement dans la
mémoire interne de l'ordinateur. Il faut quand même bien faire la différence:
- Un pointeur est une variable qui peut 'pointer' sur différentes adresses.
- Le nom d'une variable reste toujours lié à la même adresse.
ASD2 - TI
2
1.2.1. Les opérateurs de base
1.2.2. Les opérations élémentaires sur pointeurs
1.2.1. Les opérateurs de base
Lors du travail avec des pointeurs, nous avons besoin
- d'un opérateur 'adresse de': &
pour obtenir l'adresse d'une variable.
- d'un opérateur 'contenu de': *
pour accéder au contenu d'une adresse.
- d'une syntaxe de déclaration
pour pouvoir déclarer un pointeur.
L'opérateur 'adresse de' : &
&<NomVariable>
fournit l'adresse de la variable <NomVariable>
L'opérateur & nous est déjà familier par la fonction scanf, qui a besoin de l'adresse de ses arguments
pour pouvoir leur attribuer de nouvelles valeurs.
Exemple
int N;
printf("Entrez un nombre entier : ");
scanf("%d", &N);
Attention !
L'opérateur & peut seulement être appliqué à des objets qui se trouvent dans la mémoire interne, c.-à-
d. à des variables et des tableaux. Il ne peut pas être appliqué à des constantes ou des expressions.
Représentation schématique
Soit P un pointeur non initialisé
et A une variable (du même type) contenant la valeur 10 :
Alors l'instruction
P = &A;
affecte l'adresse de la variable A à la variable P. Dans notre représentation schématique, nous
pouvons illustrer le fait que 'P pointe sur A' par une flèche:
ASD2 - TI
3
L'opérateur 'contenu de' : *
*<NomPointeur>
désigne le contenu de l'adresse référencée par le pointeur
<NomPointeur>
Exemple
Soit A une variable contenant la valeur 10, B une variable contenant la valeur 50 et P un pointeur non
initialisé:
Après les instructions,
P = &A;
B = *P;
*P = 20;
- P pointe sur A,
- le contenu de A (référencé par *P) est affecté à B, et
- le contenu de A (référencé par *P) est mis à 20.
Déclaration d'un pointeur
<Type> *<NomPointeur>
déclare un pointeur <NomPointeur> qui peut recevoir des adresses de
variables du type <Type>
ASD2 - TI
4
Une déclaration comme
int *PNUM;
peut être interprétée comme suit:
"*PNUM est du type int"
ou
"PNUM est un pointeur sur int"
ou
"PNUM peut contenir l'adresse d'une variable du type int"
Exemple
Le programme complet effectuant les transformations de l'exemple ci-dessus peut se présenter
comme suit:
|void main()
ou bien |void main()
|{
|{
| / déclarations /
|/ déclarations /
| short A = 10;
| short A, B, *P;
| short B = 50;
| / traitement /
| short *P;
| A = 10;
| / traitement /
| B = 50;
| P = &A;
| B = *P;
| P = &A;
| B = *P;
| *P = 20;
| *P = 20;
|}
|}
Remarque
Lors de la déclaration d'un pointeur en C, ce pointeur est lié explicitement à un type de données.
Ainsi, la variable PNUM déclarée comme pointeur sur int ne peut pas recevoir l'adresse d'une variable
d'un autre type que int.
Nous allons voir que la limitation d'un pointeur à un type de variables n'élimine pas seulement un
grand nombre de sources d'erreurs très désagréables, mais permet une série d'opérations très
pratiques sur les pointeurs (voir 1.3.2.).
ASD2 - TI
5
1.2.2. Les opérations élémentaires sur pointeurs
Exercice 1.1
En travaillant avec des pointeurs, nous devons observer les règles suivantes:
Priorité de * et &
- Les opérateurs * et & ont la même priorité que les autres opérateurs unaires (la négation !,
l'incrémentation ++, la décrémentation --). Dans une même expression, les opérateurs unaires *, &,
!, ++, -- sont évalués de droite à gauche.
- Si un pointeur P pointe sur une variable X, alors *P peut être utilisé partout où on peut écrire X.
Publicité
Exemple
Après l'instruction
P = &X;
les expressions suivantes, sont équivalentes:
Y = *P+1
Y = X+1
P = P+10
X = X+10
*P += 2
X += 2
++*P
++X
(*P)++
X++
Dans le dernier cas, les parenthèses sont nécessaires:
Comme les opérateurs unaires * et ++ sont évalués de droite à gauche, sans les parenthèses
le pointeur P serait incrémenté, non pas l'objet sur lequel P pointe.
On peut uniquement affecter des adresses à un pointeur.
Le pointeur NUL
Seule exception: La valeur numérique 0 (zéro) est utilisée pour indiquer qu'un pointeur ne pointe 'nulle
part'.
int *P;
P = 0;
ASD2 - TI
6
Finalement, les pointeurs sont aussi des variables et peuvent être utilisés comme telles. Soit P1 et P2
deux pointeurs sur int, alors l'affectation
P1 = P2;
copie le contenu de P2 vers P1. P1 pointe alors sur le même objet que P2.
Résumons:
Après les instructions:
int A;
int *P;
P = &A;
A désigne le contenu de A
&A désigne l'adresse de A
P désigne l'adresse de A
*P désigne le contenu de A
En outre:
&P désigne l'adresse du pointeur P
*A est illégal (puisque A n'est pas un pointeur)
Exercice 1.1
void main()
{
int A = 1;
int B = 2;
int C = 3;
int P1, P2;
P1=&A;
P2=&C;
P1=(P2)++;
P1=P2;
ASD2 - TI
7
P2=&B;
P1-=P2;
++*P2;
P1=*P2;
A=++P2*P1;
P1=&A;
P2=P1/=*P2;
}
Copiez le tableau suivant et complétez-le pour chaque instruction du programme ci-dessus.
A
1
1
B
2
2
C
3
3
P1
P2
/
&A
/
/
Init.
P1=&A
P2=&C
P1=(P2)++
P1=P2
P2=&B
P1-=P2
++*P2
P1=*P2
A=++P2*P1
P1=&A
P2=P1/=*P2
1.3. Pointeurs et tableaux
En C, il existe une relation très étroite entre tableaux et pointeurs. Ainsi, chaque opération avec des
indices de tableaux peut aussi être exprimée à l'aide de pointeurs. En général, les versions formulées
avec des pointeurs sont plus compactes et plus efficientes, surtout à l'intérieur de fonctions. Mais, du
moins pour des débutants, le 'formalisme pointeur' est un peu inhabituel.
1.3.1. Adressage des composantes d'un tableau
1.3.2. Arithmétique des pointeurs
1.3.3. Pointeurs et chaînes de caractères
ASD2 - TI
8
1.3.4. Pointeurs et tableaux à deux dimensions
1.3.1. Adressage des composantes d'un tableau
Exercice 1.2
Le nom d'un
&tableau[0] et tableau sont une seule et même adresse.
tableau représente
l'adresse de son premier élément. En d'autre
termes
:
En simplifiant, nous pouvons retenir que le nom d'un tableau est un pointeur constant sur le premier
élément du tableau.
Exemple
En déclarant un tableau A de type int et un pointeur P sur int,
int A[10];
int *P;
l'instruction:
P = A; est équivalente à P =& A[0];
Si P pointe sur une composante quelconque d'un tableau, alors P+1 pointe sur la composante
suivante. Plus généralement,
P+i pointe sur la i-ième composante derrière P et
P-i pointe sur la i-ième composante devant P.
Ainsi, après l'instruction,
P = A;
le pointeur P pointe sur A[0], et
*(P+1)
désigne le contenu de A[1]
*(P+2) désigne le contenu de A[2]
...
...
ASD2 - TI
9
*(P+i) désigne le contenu de A[i]
Remarque
Au premier coup d'oeil, il est bien surprenant que P+i n'adresse pas le i-ième octet derrière P, mais la
i-ième composante derrière P ...
Ceci s'explique par
'défensive' des créateurs du langage C:
la stratégie de programmation
Si on travaille avec des pointeurs, les erreurs les plus
perfides sont causées par des pointeurs malplacés et des
adresses mal calculées. En C, le compilateur peut
calculer automatiquement l'adresse de l'élément P+i en
ajoutant à P la grandeur d'une composante multipliée par
i. Ceci est possible, parce que:
- chaque pointeur est limité à un seul type de données, et
- le compilateur connaît le nombre d'octets des différents types.
Exemple
Soit A un tableau contenant des éléments du type float et P un pointeur sur float:
float A[20], X;
float *P;
Après les instructions,
P = A;
X = *(P+9);
X contient la valeur du 10-ième élément de A, (c.-à-d. celle de A[9]). Une donnée du type float ayant
besoin de 4 octets, le compilateur obtient l'adresse P+9 en ajoutant 9 * 4 = 36 octets à l'adresse dans
P.
Rassemblons les constatations ci dessus :
Comme A représente l'adresse de A[0],
*(A+1) désigne le contenu de A[1]
*(A+2) désigne le contenu de A[2]
...
*(A+i) désigne le contenu de A[i]
Publicité
Attention !
Il existe toujours une différence essentielle entre un pointeur et le nom d'un tableau:
ASD2 - TI
10
- Un pointeur est une variable,
donc des opérations comme P = A ou P++ sont permises.
- Le nom d'un tableau est une constante,
donc des opérations comme A = P ou A++ sont
impossibles.
Ceci nous permet de jeter un petit coup d'oeil derrière les
rideaux:
Lors de la première phase de la compilation, toutes les
expressions de la forme A[i] sont traduites en *(A+i). En
multipliant l'indice i par la grandeur d'une composante, on
obtient un indice en octets:
<indice en octets> = <indice élément> *< grandeur élément>
Cet indice est ajouté à l'adresse du premier élément du tableau pour obtenir l'adresse de la
composante i du tableau. Pour le calcul d'une adresse donnée par une adresse plus un indice en
octets, on utilise un mode d'adressage spécial connu sous le nom 'adressage indexé':
<adresse indexée> = <adresse> + <indice en octets>
Presque tous les processeurs disposent de plusieurs registres spéciaux (registres index) à l'aide
desquels on peut effectuer l'adressage indexé de façon très efficace.
Résumons Soit un tableau A d'un type quelconque et i un indice pour les composantes de A, alors
A
désigne l'adresse de A[0]
A+i
désigne l'adresse de A[i]
*(A+i) désigne le contenu de A[i]
Si P = A, alors
P
pointe sur l'élément A[0]
P+i
pointe sur l'élément A[i]
*(P+i) désigne le contenu de A[i]
Formalisme tableau et formalisme pointeur
A l'aide de ce bagage, il nous est facile de 'traduire' un programme écrit à l'aide du 'formalisme
tableau' dans un programme employant le 'formalisme pointeur'.
Exemple
Les deux programmes suivants copient les éléments positifs d'un tableau T dans un deuxième tableau
POS.
ASD2 - TI
11
Formalisme tableau
void main()
{
int T[10] = {-3, 4, 0, -7, 3, 8, 0, -1, 4, -9};
int POS[10];
int I,J; / indices courants dans T et POS /
for (J=0,I=0 ; I<10 ; I++)
if (T[I]>0)
{
POS[J] = T[I];
J++;
}
}
Nous pouvons remplacer systématiquement la notation tableau[I] par *(tableau + I), ce
qui conduit à ce programme:
Formalisme pointeur
void main()
{
int T[10] = {-3, 4, 0, -7, 3, 8, 0, -1, 4, -9};
int POS[10];
int I,J; / indices courants dans T et POS /
for (J=0,I=0 ; I<10 ; I++)
if (*(T+I)>0)
{
(POS+J) = (T+I);
J++;
}
}
Sources d'erreurs
Un bon nombre d'erreurs lors de l'utilisation de C provient de la confusion entre soit contenu et
adresse, soit pointeur et variable. Revoyons donc les trois types de déclarations que nous
connaissons jusqu'ici et résumons les possibilités d'accès aux données qui se présentent.
Les variables et leur utilisation int A;
déclare une variable simple du type int
A désigne le contenu de A
&A désigne l'adresse de A
int B[];
déclare un tableau d'éléments du type int
B
désigne l'adresse de la première composante de B.
(Cette adresse est toujours constante)
ASD2 - TI
12
B[i] désigne le contenu de la composante i du tableau
&B[i] désigne l'adresse de la composante i du tableau
en utilisant le formalisme pointeur:
B+i
désigne l'adresse de la composante i du tableau
*(B+i) désigne le contenu de la composante i du tableau
int *P;
déclare un pointeur sur des éléments du type int.
P peut pointer sur des variables simples du type int ou
sur les composantes d'un tableau du type int.
P
désigne l'adresse contenue dans P
(Cette adresse est variable)
*P
désigne le contenu de l'adresse dans P
Si P pointe dans un tableau, alors
P
désigne l'adresse de la première composante
P+i
désigne l'adresse de la i-ième composante derrière P
*(P+i) désigne le contenu de la i-ième composante derrière P
Exercice 1.2
Ecrire un programme qui lit deux tableaux A et B et leurs dimensions N et M au clavier et qui ajoute
les éléments de B à la fin de A. Utiliser le formalisme pointeur à chaque fois que cela est possible.
1.3.2. Arithmétique des pointeurs
Exercice 1.3
Exercice 1.4
Exercice 1.5
Exercice 1.6
ASD2 - TI
13
Comme les pointeurs jouent un rôle si important, le langage C soutient une série d'opérations
arithmétiques sur les pointeurs que l'on ne rencontre en général que dans les langages machines. Le
confort de ces opérations en C est basé sur le principe suivant:
Toutes les opérations avec les pointeurs tiennent compte automatiquement du type et de la
grandeur des objets pointés.
- Affectation par un pointeur sur le même type
Soient P1 et P2 deux pointeurs sur le même type de données, alors l'instruction
P1 = P2;
fait pointer P1 sur le même objet que P2
- Addition et soustraction d'un nombre entier
Si P pointe sur l'élément A[i] d'un tableau, alors
P+n pointe sur A[i+n]
P-n pointe sur A[i-n]
- Incrémentation et décrémentation d'un pointeur
Si P pointe sur l'élément A[i] d'un tableau, alors après l'instruction
P++; P pointe sur A[i+1]
P+=n; P pointe sur A[i+n]
P--; P pointe sur A[i-1]
P-=n; P pointe sur A[i-n]
Domaine des opérations
L'addition, la soustraction, l'incrémentation et la décrémentation sur les pointeurs sont seulement
définies à l'intérieur d'un tableau. Si l'adresse formée par le pointeur et l'indice sort du domaine du
tableau, alors le résultat n'est pas défini.
Seule exception: Il est permis de 'pointer' sur le premier octet derrière un tableau (à condition que cet
octet se trouve dans le même segment de mémoire que le tableau). Cette règle, introduite avec le
standard ANSI-C, légalise la définition de boucles qui incrémentent le pointeur avant l'évaluation de la
condition d'arrêt.
Exemples
int A[10];
ASD2 - TI
14
int *P;
P = A+9; / dernier élément -> légal /
P = A+10; / dernier élément + 1 -> légal /
P = A+11; / dernier élément + 2 -> illégal /
P = A-1; / premier élément - 1 -> illégal /
- Soustraction de deux pointeurs
Soient P1 et P2 deux pointeurs qui pointent dans le même tableau:
P1-P2
fournit le nombre de composantes comprises entre P1 et P2.
Le résultat de la soustraction P1-P2 est
- négatif, si P1 précède P2
- zéro, si P1 = P2
- positif, si P2 precède P1
- indéfini, si P1 et P2 ne pointent pas dans le même tableau
Plus généralement, la soustraction de deux pointeurs qui pointent dans le même tableau est
Publicité
équivalente à la soustraction des indices correspondants.
- Comparaison de deux pointeurs
On peut comparer deux pointeurs par <, >, <=, >=, ==, !=.
La comparaison de deux pointeurs qui pointent dans le même tableau est équivalente à la
comparaison des indices correspondants. (Si les pointeurs ne pointent pas dans le même tableau,
alors le résultat est donné par leurs positions relatives dans la mémoire).
Exercice 1.3
Pourquoi les créateurs du standard ANSI-C ont-ils décidé de légaliser les pointeurs sur le premier
élément derrière un tableau? Donner un exemple.
ASD2 - TI
15
Exercice 1.4
Soit P un pointeur qui 'pointe' sur un tableau A:
int A[] = {12, 23, 34, 45, 56, 67, 78, 89, 90};
int *P;
P = A;
Quelles valeurs ou adresses fournissent ces expressions:
a)
b)
c)
d)
e)
f)
g)
h)
*P+2
*(P+2)
&P+1
&A[4]-3
A+3
&A[7]-P
P+(*P-10)
(P+(P+8)-A[7])
Exercice 1.5
Ecrire un programme qui lit un entier X et un tableau A du type int au clavier et élimine toutes les
occurrences de X dans A en tassant les éléments restants. Le programme utilisera les pointeurs P1 et
P2 pour parcourir le tableau.
Exercice 1.6
Ecrire un programme qui range les éléments d'un tableau A du type int dans l'ordre inverse. Le
programme utilisera des pointeurs P1 et P2 et une variable numérique AIDE pour la permutation des
éléments.
1.3.3. Pointeurs et chaînes de caractères
Exercice 1.7
Exercice 1.8
Exercice 1.9
Exercice 1.10
Exercice 1.11
Exercice 1.12
Exercice 1.13
Exercice 1.14
ASD2 - TI
16
De la même façon qu'un pointeur sur int peut contenir l'adresse d'un nombre isolé ou d'une
composante d'un tableau, un pointeur sur char peut pointer sur un caractère isolé ou sur les éléments
d'un tableau de caractères. Un pointeur sur char peut en plus contenir l'adresse d'une chaîne de
caractères constante et il peut même être initialisé avec une telle adresse.
A la fin de ce chapitre, nous allons anticiper avec un exemple et montrer que les pointeurs sont les
éléments indispensables mais effectifs des fonctions en C.
- Pointeurs sur char et chaînes de caractères constantes
Affectation
a) On peut attribuer l'adresse d'une chaîne de caractères constante à un pointeur sur char:
Exemple
char *C;
C = "Ceci est une chaîne de caractères constante";
Nous pouvons lire cette chaîne constante (p.ex: pour l'afficher), mais il n'est pas recommandé de la
modifier, parce que le résultat d'un programme qui essaie de modifier une chaîne de caractères
constante n'est pas prévisible en ANSI-C.
Initialisation
b) Un pointeur sur char peut être initialisé lors de la déclaration si on lui affecte l'adresse d'une chaîne
de caractères constante:
char *B = "Bonjour !";
Attention !
Il existe une différence importante entre les deux déclarations:
char A[] = "Bonjour !"; / un tableau /
char B = "Bonjour !"; / un pointeur */
A est un tableau qui a exactement la grandeur pour contenir la chaîne de caractères et la terminaison
'\0'. Les caractères de la chaîne peuvent être changés, mais le nom A va toujours pointer sur la même
adresse en mémoire.
B est un pointeur qui est initialisé de façon à ce qu'il pointe sur une chaîne de caractères constante
stockée quelque part en mémoire. Le pointeur peut être modifié et pointer sur autre chose. La chaîne
constante peut être lue, copiée ou affichée, mais pas modifiée.
ASD2 - TI
17
Modification
c) Si nous affectons une nouvelle valeur à un pointeur sur une chaîne de caractères constante, nous
risquons de perdre la chaîne constante. D'autre part, un pointeur sur char a l'avantage de pouvoir
pointer sur des chaînes de n'importe quelle longueur:
Exemple
char *A = "Petite chaîne";
char *B = "Deuxième chaîne un peu plus longue";
A = B;
Maintenant A et B pointent sur la même chaîne; la "Petite chaîne" est perdue:
Les affectations discutées ci-dessus ne peuvent pas être effectuées avec des tableaux de caractères:
Attention !
Exemple
char A[45] = "Petite chaîne";
char B[45] = "Deuxième chaîne un peu plus longue";
char C[30];
A = B; / IMPOSSIBLE -> ERREUR !!! /
C = "Bonjour !"; / IMPOSSIBLE -> ERREUR !!! /
Dans cet exemple, nous essayons de copier l'adresse de B dans A, respectivement l'adresse de la
chaîne constante dans C. Ces opérations sont impossibles et illégales parce que l'adresse
représentée par le nom d'un tableau reste toujours constante.
Pour changer le contenu d'un tableau, nous devons changer les composantes du tableau l'une après
l'autre (p.ex. dans une boucle) ou déléguer cette charge à une fonction de <stdio> ou <string>.
Conclusions:
ASD2 - TI
18
Utilisons des tableaux de caractères pour déclarer les chaînes de caractères que nous
voulons modifier.
Utilisons des pointeurs sur char pour manipuler des chaînes de caractères constantes (dont
le contenu ne change pas).
Utilisons de préférence des pointeurs pour effectuer les manipulations à l'intérieur des
tableaux de caractères. (voir aussi les remarques ci-dessous).
Perspectives et motivation
- Avantages des pointeurs sur char
Comme la fin des chaînes de caractères est marquée par
un symbole spécial, nous n'avons pas besoin de
connaître la longueur des chaînes de caractères; nous
pouvons même laisser de côté les indices d'aide et
parcourir les chaînes à l'aide de pointeurs.
Cette façon de procéder est indispensable pour traiter les chaînes de caractères dans des fonctions.
Nous pouvons, ici, ouvrir une petite parenthèse pour illustrer les avantages des pointeurs dans la
définition de fonctions traitant des chaînes de caractères:
Pour fournir un tableau comme paramètre à une fonction, il faut passer l'adresse du tableau à la
fonction. Or, les paramètres des fonctions sont des variables locales, que nous pouvons utiliser
comme variables d'aide. Bref, une fonction obtenant une chaîne de caractères comme paramètre,
dispose d'une copie locale de l'adresse de la chaîne. Cette copie peut remplacer les indices ou les
variables d'aide du formalisme tableau.
Discussion d'un exemple
Reprenons l'exemple de la fonction strcpy, qui copie la chaîne CH2 vers CH1. Les deux chaînes sont
les arguments de la fonction et elles sont déclarées comme pointeurs sur char. La première version
de strcpy est écrite entièrement à l'aide du formalisme tableau:
void strcpy(char CH1, char CH2)
{
int I;
I=0;
while ((CH1[I]=CH2[I]) != '\0')
I++;
}
Dans une première approche, nous pourrions remplacer simplement la notation tableau[I] par
*(tableau + I), ce qui conduirait au programme:
void strcpy(char CH1, char CH2)
{
int I;
I=0;
while (((CH1+I)=(CH2+I)) != '\0')
I++;
}
ASD2 - TI
19
Cette transformation ne nous avance guère, nous avons tout au plus gagné quelques millièmes de
secondes lors de la compilation. Un 'véritable' avantage se laisse gagner en calculant directement
avec les pointeurs CH1 et CH2 :
void strcpy(char CH1, char CH2)
{
while ((CH1=CH2) != '\0')
{
CH1++;
CH2++;
}
}
Publicité
Comme nous l'avons déjà constaté dans l'introduction de ce manuel, un vrai professionnel en C
escaladerait les 'simplifications' jusqu'à obtenir:
void strcpy(char CH1, char CH2)
{
while (CH1++ = CH2++)
;
}
Assez 'optimisé' - fermons la parenthèse et familiarisons-nous avec les notations et les manipulations
du 'formalisme pointeur' ...
Exercice 1.7
Ecrire un programme qui lit deux tableaux d'entiers A et B et leurs dimensions N et M au clavier et qui
ajoute les éléments de B à la fin de A. Utiliser deux pointeurs PA et PB pour le transfer et afficher le
tableau résultant A.
Exercice 1.8
Ecrire de deux façons différentes, un programme qui vérifie sans utiliser une fonction de <string>, si
une chaîne CH introduite au clavier est un palindrome:
a) en utilisant uniquement le formalisme tableau
b) en utilisant des pointeurs au lieu des indices numériques
Rappel: Un palindrome est un mot qui reste le même qu'on le lise de gauche à droite ou de droite à
gauche:
Exemples: PIERRE ==> n'est pas un palindrome
ASD2 - TI
20
OTTO ==> est un palindrome
23432 ==> est un palindrome
Exercice 1.9
Ecrire un programme qui lit une chaîne de caractères CH et détermine la longueur de la chaîne à
l'aide d'un pointeur P. Le programme n'utilisera pas de variables numériques.
Exercice 1.10
Ecrire un programme qui lit une chaîne de caractères CH et détermine le nombre de mots contenus
dans la chaîne. Utiliser un pointeur P, une variable logique, la fonction isspace et une variable
numérique N qui contiendra le nombre des mots.
Exercice 1.11
Ecrire un programme qui lit une chaîne de caractères CH au clavier et qui compte les occurrences des
lettres de l'alphabet en ne distinguant pas les majuscules et les minuscules. Utiliser un tableau ABC
de dimension 26 pour mémoriser le résultat et un pointeur PCH pour parcourir la chaîne CH et un
pointeur PABC pour parcourir ABC. Afficher seulement le nombre des lettres qui apparaissent au mois
une fois dans le texte.
Exemple:
Entrez un ligne de texte (max. 100 caractères) :
Jeanne
La chaîne "Jeanne" contient :
1 fois la lettre 'A'
2 fois la lettre 'E'
1 fois la lettre 'J'
3 fois la lettre 'N'
Exercice 1.12
Ecrire un programme qui lit un caractère C et une chaîne de caractères CH au clavier. Ensuite toutes
les occurrences de C dans CH seront éliminées. Le reste des caractères dans CH sera tassé à l'aide
d'un pointeur et de la fonction strcpy.
ASD2 - TI
21
Exercice 1.13
Ecrire un programme qui lit deux chaînes de caractères CH1 et CH2 au clavier et élimine toutes les
lettres de CH1 qui apparaissent aussi dans CH2. Utiliser deux pointeurs P1 et P2, une variable
logique TROUVE et la fonction strcpy.
Exemples: Bonjour Bravo ==> njou
Bonjour bravo ==> Bnjou
abacab aa
==> bcab
Exercice 1.14
Ecrire un programme qui lit deux chaînes de caractères CH1 et CH2 au clavier et supprime la
première occurrence de CH2 dans CH1. Utiliser uniquement des pointeurs, une variable logique
TROUVE et la fonction strcpy.
Exemples: Alphonse phon ==> Alse
totalement t
==> otalement
abacab
aa ==> abacab
1.3.4. Pointeurs et tableaux à deux dimensions
Exercice 1.15
Exercice 1.16
Exercice 1.17
L'arithmétique des pointeurs se laisse élargir avec toutes ses conséquences sur les tableaux à deux
dimensions. Voyons cela sur un exemple:
Exemple
Le tableau M à deux dimensions est défini comme suit:
int M[4][10] = {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
{10,11,12,13,14,15,16,17,18,19},
{20,21,22,23,24,25,26,27,28,29},
{30,31,32,33,34,35,36,37,38,39}};
ASD2 - TI
22
Le nom du tableau M représente l'adresse du premier élément du tableau et pointe (oh, surprise...) sur
le tableau M[0] qui a la valeur:
{0,1,2,3,4,5,6,7,8,9}.
L'expression (M+1) est l'adresse du deuxième élément du tableau et pointe sur M[1] qui a la valeur:
{10,11,12,13,14,15,16,17,18,19}.
Explication
Au sens strict du terme, un tableau à deux dimensions est un tableau unidimensionnel dont chaque
composante est un tableau unidimensionnel. Ainsi, le premier élément de la matrice M est le vecteur
{0,1,2,3,4,5,6,7,8,9}, le deuxième élément est
{10,11,12,13,14,15,16,17,18,19} et ainsi de suite.
L'arithmétique des pointeurs qui respecte automatiquement les dimensions des éléments conclut
logiquement que:
M+I désigne l'adresse du tableau M[I]
Problème
Comment pouvons-nous accéder à l'aide de pointeurs aux éléments de chaque composante du
tableau, c.à-d.: aux éléments M[0][0], M[0][1], ... , M[3][9] ?
Discussion
Une solution consiste à convertir la valeur de M (qui est un pointeur sur un tableau du type int) en un
pointeur sur int. On pourrait se contenter de procéder ainsi:
int M[4][10] = {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
{10,11,12,13,14,15,16,17,18,19},
{20,21,22,23,24,25,26,27,28,29},
{30,31,32,33,34,35,36,37,38,39}};
int *P;
P = M; / conversion automatique /
Cette dernière affectation entraîne une conversion automatique de l'adresse &M[0] dans l'adresse
&M[0][0]. (Remarquez bien que l'adresse transmise reste la même, seule la nature du pointeur a
changé).
Cette solution n'est pas satisfaisante à cent pour-cent: Généralement, on gagne en lisibilité en
explicitant la conversion mise en oeuvre par l'opérateur de conversion forcée ("cast"), qui évite en plus
des messages d'avertissement de la part du compilateur.
Solution
Voici finalement la version que nous utiliserons:
int M[4][10] = {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
{10,11,12,13,14,15,16,17,18,19},
{20,21,22,23,24,25,26,27,28,29},
{30,31,32,33,34,35,36,37,38,39}};
int *P;
ASD2 - TI
23
P = (int )M; / conversion forcée */
Dû à la mémorisation ligne par ligne des tableaux à deux dimensions, il nous est maintenant possible
traiter M à l'aide du pointeur P comme un tableau unidimensionnel de dimension 4*10.
Exemple
Les instructions suivantes calculent la somme de tous les éléments du tableau M:
int M[4][10] = {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
{10,11,12,13,14,15,16,17,18,19},
{20,21,22,23,24,25,26,27,28,29},
{30,31,32,33,34,35,36,37,38,39}};
int *P;
int I, SOM;
P = (int*)M;
SOM = 0;
for (I=0; I<40; I++)
SOM += *(P+I);
Attention !
Lors de l'interprétation d'un tableau à deux dimensions comme tableau unidimensionnel il faut
calculer avec le nombre de colonnes indiqué dans la déclaration du tableau.
Exemple
Pour la matrice A, nous réservons de la mémoire pour 3 lignes et 4 colonnes, mais nous utilisons
seulement 2 lignes et 2 colonnes:
int A[3][4];
A[0][0]=1;
A[0][1]=2;
A[1][0]=10;
A[1][1]=20;
Dans la mémoire, ces composantes sont stockées comme suit :
L'adresse de l'élément A[I][J] se calcule alors par:
A + I*4 + J
Conclusion
Pour pouvoir travailler à l'aide de pointeurs dans un tableau à deux dimensions, nous avons besoin de
quatre données:
ASD2 - TI
24
a) l'adresse du premier élément du tableau converti dans le type simple des éléments du tableau
b) la longueur d'une ligne réservée en mémoire
(- voir déclaration - ici: 4 colonnes)
c) le nombre d'éléments effectivement utilisés dans une ligne
(- p.ex: lu au clavier - ici: 2 colonnes)
Publicité