Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
CHAPITRE VII
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Un tableau est un ensemble d’éléments de même type désignés par un identificateur unique ; chaque élément est repéré par un indice précisant sa position au sein de l’ensemble
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
1) Tableaux à un indice
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
1) Tableaux à un indice
#include <stdio.h>
main()
{
int i, som, nbm ;
float moy ;
int t[20] ;
for (i=0 ; i<20 ; i++)
{
printf ("donnez la note numéro %d : ", i+1) ;
scanf ("%d", &t[i]) ;
}
for (i=0, som=0 ; i<20 ; i++) som += t[i] ;
moy = som / 20 ;
printf ("\n\n moyenne de la classe : %f\n", moy) ;
for (i=0, nbm=0 ; i<20 ; i++ )
if (t[i] > moy) nbm++ ;
printf ("%d élèves ont plus de cette moyenne", nbm) ;
}
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
1) Tableaux à un indice
int t[20]
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
1) Tableaux à un indice
t[2] = 5 //à gauche d’un opérateur d’affectation « OK »
t[3]++ --t[ i ] //opérande d’un opérateur d’incrémentation « OK »
t1 = t2 /pas d’affectations globales de tableaux « NO »
t[n-3]
t[3*p-2*k+j%l]
t[c1+3]
t[c2-c1]
Avec n, p, k et j (de type int)
c1 et c2 (de type char)
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
1) Tableaux à un indice
#define N 50
.....
int t[N] ;
float h[2*N-1] ;
Les expressions N et 2*N-1 ne sont plus calculables par le compilateur (elle sera cependant acceptée en C++)
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
2) Tableaux à plusieurs indices
int t[5][3] //réserve un tableau de 15 (5 x 3) éléments
t[3][2] t[ i ][ j ] t[ i-3][ i+j ]
obtenu en faisant varier le dernier indice en premier
lorsque l’un des indices « déborde »
t[0][5] désigne en fait l’élément t[1][2]
Par contre, t[5][0] désigne un emplacement
situé juste au-delà du tableau
t[0][0]
t[0][1]
t[0][2]
t[1][0]
t[1][1]
t[1][2]
....
t[4][0]
t[4][1]
t[4][2]
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
int tab[5] = { 10, 20, 5, 0, 3 } ; //place les valeurs 10, 20, 5, 0 et 3 dans chacun des //cinq éléments du tableau tab
int tab[5] = { 10, 20 } ; //mentionner que les premières valeurs
int tab[5] = { 10, 20, 5 } ;
int tab[ ] = { 10, 20, 5, 0, 3 } ; //omettre la dimension du tableau
3) Initialisation des tableaux
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
int tab [3] [4] = { { 1, 2, 3, 4 } ,
{ 5, 6, 7, 8 },
{ 9,10,11,12 } }
int tab [3] [4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 } ;
int tab [3] [4] = { { 1, 2 } , { 3, 4, 5 } } ;
int tab [3] [4] = { 1, 2 , 3, 4, 5 } ;
3) Initialisation des tableaux
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Un pointeur, est une variable destinée à contenir une adresse d’un autre objet (variable, fonction...)
int * ad ; //réserve une variable ad comme étant un pointeur sur des entiers
int n ;
n = 20 ;
ad = &n ; //affecte à la variable ad la valeur de l’adresse de la variable n
*ad = 30 ; //affecter à la lvalue *ad la valeur 30 (même résultat avec n = 30 ;)
4) Notion de pointeur – Les opérateurs * et &
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
int * ad1, * ad2, * ad ; //Les variables ad1, ad2 et ad sont des pointeurs sur des entiers
int n = 10, p = 20 ;
ad1 = &n ; //place dans ad1 l’adresse de n
ad2 = &p ; //place dans ad2 l’adresse de p
* ad1 = * ad2 + 2 ; // exécute (n = p + 2) ;
* ad1 += 3 est équivalent à (n = n + 3)
( * ad1 ) ++ est équivalent à (n++)
(&ad)++ ou (&p)++ //seront rejetées à la compilation
4) Notion de pointeur – Les opérateurs * et &
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
int * ad ;
ad + 1 ; //représente l’adresse de l’entier suivant
Cette notion sera très utile dans le traitement des tableaux ou des chaînes
La différence entre ad+1 et ad est ici de sizeof(int) octets
double * ad ; //cette différence serait de sizeof(double) octets dans ce cas
ad++ //incrémente l’adresse contenue dans ad
4) Notion de pointeur – Les opérateurs * et &
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Exemple de permutation
5) Transmission par adresse avec un pointeur
#include <stdio.h>
main()
{
void echange (int * ad1, int * ad2) ;
int a = 10, b = 20 ;
printf ("avant appel %d %d\n", a, b) ;
echange (&a, &b) ;
printf ("après appel %d %d", a, b) ;
}
void echange (int * ad1, int * ad2)
{
int x ;
x = * ad1 ;
* ad1 = * ad2 ;
* ad2 = x ;
}
avant appel 10 20
après appel 20 10
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Les arguments effectifs de l’appel de echange sont les adresses des variables n et p (et non plus leurs valeurs).
Donc on transmet à la fonction echange les valeurs des expressions &n et &p.
5) Transmission par adresse avec un pointeur
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
L’identificateur d’un tableau, lorsqu’il est employé seul (sans indices à sa suite),
est considéré comme un pointeur (constant) sur le début du tableau
Soit la déclaration suivante :
int t [10]
La notation t est équivalente à & t [0].
L’identificateur t est considéré comme étant de type pointeur sur le type correspondant aux éléments du tableau (int *).
Exemples de notations équivalentes :
t +1 & t [1]
t + i & t [ i ]
t [ i ] * ( t + i )
6) Un nom de tableau est un pointeur constant
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Voici plusieurs façons de placer la valeur 1 dans chacun des 10 éléments d’un tableau t utilisant ces notations
6) Un nom de tableau est un pointeur constant
int i ;
for (i=0 ; i<10 ; i++)
* (t+i) = 1 ;
int i ;
int * p :
for (p=t, i=0 ; i<10 ; i++, p++)
* p = 1 ;
Dans le second cas, on a recopier la valeur représentée par t dans un pointeur nommé p. En effet, le symbole t représente une adresse constante (t est une constante de type pointeur sur des entiers).
Ainsi, t++ serait invalide, au même titre que 3++
Un nom de tableau est un pointeur constant ; ce n’est pas une lvalue.
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
L’identificateur d’un tableau, employé seul, représente son adresse de début. Mais, il ne s’agit plus d’un pointeur sur des éléments du tableau.
Soit la déclaration :
int t [3] [4] ;
t désigne un tableau de 3 éléments,
chacun de ces éléments étant lui-même un tableau de 4 entiers.
t représente bien l’adresse de début de notre tableau t, il n’est plus de type int * (comme c’était le cas pour un tableau à un indice) mais d’un type « pointeur sur des blocs de 4 entiers »
t +1 correspond à l’adresse de t, augmentée de 4 entiers
t [0] & t [0][ ]
t [1] & t [1][ ]
6) Un nom de tableau est un pointeur constant
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
7) Opérateurs réalisables sur des pointeurs
int * p ;
for (p=t ; p<t+10 ; p++)
*p = 1 ;
La différence de deux pointeurs de même type fournit le nombre d’éléments du type en question situés entre les deux adresses correspondantes.
Cas déjà rencontré des cas d’affectation de la valeur d’un pointeur à un pointeur de même type.
Il est conseillé d’employer la constante NULL prédéfinie dans stdio.h, et également dans stddef.h (qui sera remplacée par la constante entière 0 lors du traitement par le préprocesseur).
int * n ;
double * x ;
n = NULL ; // équivalent de n = 0 ;
x = NULL ; // équivalent de x = 0 ;
if (n == NULL) ... // équivalent de if (n == 0) ...
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Il n’existe aucune conversion implicite d’un type pointeur dans un autre.
En revanche, il est toujours possible de faire appel à l’opérateur de cast.
En générale, (à éviter), compte tenu des risques qu’elle comporte.
Certaines machines imposent aux adresses des objets ce que l’on appelle des « contraintes d’alignement ».
Exemple, un objet de 2 octets sera toujours placé à une adresse paire, tandis qu’un caractère (objet d’un seul octet) pourra être placé à n’importe quelle adresse.
Dans ce cas, la conversion d’un char * en un int * peut conduire soit à l’adresse effective du caractère lorsque celle-ci est paire, soit à une adresse voisine lorsque celle-ci est impaire.
7) Opérateurs réalisables sur des pointeurs
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Le typage des pointeurs peut s’avérer gênant dans certaines circonstances telles que celles où une fonction doit manipuler les adresses d’objets de type non connu (ou, plutôt, susceptible de varier d’un appel à un autre).
Désigne un pointeur sur un objet de type quelconque (on parle souvent de « pointeur générique ») (pointeur sans type).
Ne peut pas intervenir dans des opérations arithmétiques
Parce qu’on ne connaît pas la taille des objets pointés
7) Opérateurs réalisables sur des pointeurs
void *
void * p ;
void * q ;
int i ;
p + i ; // Illégal
p - q ; // Illégal
p++ ; // Illégal
*p = 10 ; // Illégal
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Les pointeurs génériques sont théoriquement compatibles avec tous les autres
Exemple : fonction qui « met à zéro » un emplacement de la mémoire dont on lui fournit l’adresse et la taille (en octets)
7) Opérateurs réalisables sur des pointeurs
void * p
int * i
char * c;
p = i ; // Légal
i = p ; // Illégal pour tous les types sauf ’char’
c = p ; // Légal
void raz (void * adr, int n)
{
int i ;
for (i=0 ; i<n ; i++, adr++) *adr = 0 ; // Illégal
}
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Exemple : fonction qui « met à zéro » un emplacement de la mémoire dont on lui fournit l’adresse et la taille (en octets)
7) Opérateurs réalisables sur des pointeurs
void raz (void * adr, int n)
{
int i ;
char * ad = adr ;
for (i=0 ; i<n ; i++, ad++) *ad = 0 ;
}
main()
{
void raz (void *, int) ; /* prototype réduit */
int t[10] ; /* tableau à mettre à zéro */
double z ; /* double à mettre à zéro */
raz (t, 10*sizeof(int)) ;
raz (&z, sizeof (z)) ;
}
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Si on place le nom d’un tableau en argument effectif de l’appel d’une fonction, on transmet l’adresse du tableau à la fonction
Ce qui lui permet d’effectuer toutes les manipulations voulues sur ses éléments, qu’il s’agisse d’utiliser leur valeur ou de la modifier.
Exemples :
Fonction qui met la valeur 1 dans tous les éléments d’un tableau de 10 éléments, l’adresse de ce tableau étant transmise en argument.
8) Tableaux transmis en argument
void fct (int t[10])
{
int i ;
for (i=0 ; i<10 ; i++) t[i]=1 ;
}
main()
{
int t1[10], t2[10] ;
fct(t1) ;
fct(t2) ;
}
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
L’en-tête de fct peut être écrit de l’une des manières suivantes
La définition de la fonction, utilise indifféremment le formalisme tableau ou le formalisme pointeur
8) Tableaux transmis en argument
void fct (int t[10])
void fct (int * t)
void fct (int t[]) //La réservation mémoire du tableau est réalisée par //la fonction appelante
for (i=0 ; i<10 ; i++) t[i] = 1 ;
for (i=0 ; i<10 ; i++, t++) *t = 1 ; //ici t est juste une copie de
for (i=0 ; i<10 ; i++) *(t+i) = 1 ; // l’adresse du tableau
int * p ;
for (p=t ; p<t+10 ; p++) *p = 1 ;
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Il suffit de transmettre la taille en argument.
Exemple, une fonction qui calcule la somme des éléments d’un tableau d’entiers de taille quelconque
8) Tableaux transmis en argument
int som (int t[],int nb)
{
int s = 0, i ;
for (i=0 ; i<nb ; i++)
s += t[i] ;
return (s) ;
}
main()
{
int t1[30], t2[15], t3[10] ;
int s1, s2, s3 ;
.....
s1 = som(t1, 30) ;
s2 = som(t2, 15) + som(t3, 10) ;
.....
}
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Exemple d’une fonction qui place la valeur 1 dans chacun des éléments d’un tableau de dimensions 10 x 15 :
Pour l’en-tête de la fonction, le compilateur ne se contente plus de l’adresse de début ; il doit également connaître la seconde dimension du tableau
8) Tableaux transmis en argument
void raun (int t[10][15])
{
int i, j ;
for (i=0 ; i<10 ; i++)
for (j=0 ; j<15 ; j++)
t[i][j] = 1 ;
}
rau (int t[][15]) //ok
rau (int t[][]) //Pas ok
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Soit une fonction qui place la valeur 0 dans chacun des éléments de la diagonale d’un tableau carré de taille quelconque.
Une façon de résoudre ce problème consiste à adresser les éléments voulus par des pointeurs en effectuant le calcul d’adresse approprié
Exemple d’appel de la fonction
8) Tableaux transmis en argument
void diag (int * p, int n)
{
int i ;
for (i=0 ; i<n ; i++)
{
* p = 0 ;
p += n+1 ;
}
}
int t[30][30] ;
diag (t, 30)
| A | B | C | P1 | P2 |
Init. | 1 | 2 | 3 | / | / |
P1=&A | 1 | 2 | 3 | &A | / |
P2=&C | | | | | |
*P1= (*P2)++ | | | | | |
P1=P2 | | | | | |
P2=&B | | | | | |
*P1-=*P2 | | | | | |
++*P2 | | | | | |
*P1*=*P2 | | | | | |
A=++*P2**P1 | | | | | |
P1=&A | | | | | |
*P2=*P1/=*P2 | | | | | |
main()
{
int A = 1;
int B = 2;
int C = 3;
int *P1, *P2;
P1=&A;
P2=&C;
*P1=(*P2)++;
P1=P2;
P2=&B;
*P1-=*P2;
++*P2;
*P1*=*P2;
A=++*P2**P1;
P1=&A;
*P2=*P1/=*P2;
return 0;
}
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) *P+2
b) *(P+2)
c) &P+1
d) &A[4]-3
e) A+3
f) &A[7]-P
g) P+(*P-10)
h) *(P+*(P+8)-A[7])
Quels résultats fournira ce programme :
#include <stdio.h>
main()
{
int t[4] = {10, 20, 30, 40} ;
int * ad [4] ;
int i ;
for (i=0 ; i<4 ; i++) ad[i] = t+i ; /* 1 */
for (i=0 ; i<4 ; i++) printf ("%d ", * ad[i]) ; /* 2 */
printf ("\n") ;
printf ("%d %d \n", * (ad[1] + 1), * ad[1] + 1) ; /* 3 */
}
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Le nom d’une fonction (employé seul) est traduit par le compilateur en l’adresse de cette fonction (Analogue aux noms de tableaux)
Soit la déclaration suivante:
(* adf) est une fonction à deux arguments (de type double et int) fournissant un
résultat de type int
adf est un pointeur sur une fonction à deux arguments (double et int) fournissant
un résultat de type int
9) Pointeurs sur des fonctions
int (* adf) (double, int) ;
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Si fct1 et fct2 sont des fonctions ayant les prototypes suivants :
Placent, dans adf, l’adresse de la fonction correspondante (fct1 ou fct2).
Il devient possible de programmer un « appel de fonction variable » (c’est-à-dire
que la fonction appelée peut varier au fil de l’exécution du programme)
9) Pointeurs sur des fonctions
int fct1 (double, int) ;
int fct2 (double, int) ;
adf = fct1 ; //ok
adf = fct2 ; //ok
(* adf) (5.35, 4) ;
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
Soit une fonction permettant de calculer l’intégrale d’une fonction quelconque suivant une méthode numérique donnée.
Cette fonction integ posséderait alors un en-tête de ce genre :
Le premier argument muet correspond à l’adresse de la fonction dont on cherche à calculer l’intégrale.
f est donc un pointeur sur une fonction recevant un argument de type float et fournissant un résultat de type float.
Au sein de la définition de la fonction integ, il sera possible d’appeler la fonction dont on aura ainsi reçu l’adresse
de la façon suivante :
9) Pointeurs sur des fonctions
float integ ( float(*f)(float), ..... )
(*f)(x)
main()
{
float fct1(float);
.....
res1 = integ (fct1, .....) ;
}
Langage C
I. II. III. IV. V. VI. VII. VIII. IX.
Les tableaux et les pointeurs
4) Écrire une fonction calculant la somme de deux matrices dont les éléments sont de type (int).
Exercice