Sie sind auf Seite 1von 12

Centre Informatique pour les Lettres

et les Sciences Humaines


Apprendre C++ avec Qt : Leon 16
Tableaux





1 - Vrais tableaux..................................................................................................................... 2
Accs aux lments d'un tableau.....................................................................................2
Initialisation des tableaux ...............................................................................................2
Dterminer la taille d'un tableau .....................................................................................3
Tableaux de caractres : les chanes du langage C...........................................................3
La fragilit des vrais tableaux ..........................................................................................5
2 - Faux tableaux..................................................................................................................... 5
L'arithmtique des pointeurs ...........................................................................................5
Drfrencer un pointeur l'aide de l'oprateur [ ] ..........................................................6
Utilisation d'un pointeur pour accder un vrai tableau.................................................6
Cration de faux tableaux par allocation dynamique .......................................................7
3 - Transmettre un tableau une fonction ............................................................................... 8
La fonction reoit une adresse, et non un vrai tableau.....................................................8
La fonction ne peut pas dterminer la taille de la srie de donnes ..................................8
Autres consquences de la transmission d'une adresse ...................................................9
Notations archaques et malsaines ..................................................................................9
4 - Tableaux vs. conteneurs.................................................................................................... 10
Choisir entre un tableau et une QMap ............................................................................10
Choisir entre un tableau et une QSt r i ng.......................................................................12
5 - Bon, c'est gentil tout a, mais a fait dj 11 pages. Qu'est-ce que je dois vraiment en
retenir ?............................................................................................................................ 12

Document du 31/01/06 - Retrouvez la version la plus rcente sur http://www.up.univ-mrs.fr/wcpp
C++ - Leon 16 Tableaux 2/12
Jusqu' prsent, lorsque nous avons eu besoin de stocker des donnes nombreuses ou sur
lesquelles nous avions l'intention d'oprer l'aide d'une boucle, nous avons eu recours des
classes proposes par la librairie Qt : QVal ueLi st , QMap, ou QSt r i ng (lorsque les donnes
concernes sont des caractres). Bien que le recours de tels conteneurs soit, dans la plupart
des cas, la bonne solution, il n'est pas inutile de connatre la seule mthode qu'offre le langage
lui-mme pour stocker des collections de donnes
1 - Vrais tableaux
Lorsqu'il s'agit de stocker en mmoire une collection de donnes du mme type, le langage C++
permet de ne crer qu'une seule variable, de type "tableau de...". La syntaxe utilise pour crer
un tableau exige simplement que le nom de la variable soit suivi du nombre de valeurs qui
doivent pouvoir tre stockes dans le tableau, plac entre crochets. La ligne

i nt unTabl eau[ 512] ; / / cr at i on d' un t abl eau de 512 i nt

dfinit donc une variable de type "tableau d'i nt ", nomme unTabl eau et susceptible de stocker
simultanment 512 valeurs entires. Si cet exemple fait intervenir le type i nt , il reste bien
entendu que celui-ci ne dispose d'aucun privilge particulier, et que tous les types (y compris
les types numrs et les classes) peuvent, de la mme faon, donner naissance des tableaux.
Accs aux lments d'un tableau
On accde habituellement aux lments d'un tableau l'aide de l'oprateur [ ] , auquel on
passe l'index de l'lment auquel on souhaite accder. Si unTabl eau est dfini comme ci-
dessus, on affectera, par exemple, la valeur nulle l'lment d'indice 2 en crivant :

unTabl eau[ 2] = 0; / / accs un l ment du t abl eau

Remarquez que, si les crochets utiliss pour encadrer la taille (lorsqu'on dfinit un tableau) et
ceux utiliss pour encadrer un index (lorsqu'on accde un lment d'un tableau) sont
typographiquement identiques, leur parent s'arrte l : il s'agit d'une syntaxe de dclaration
dans le premier cas et d'un oprateur dans le second.

Comme son habitude, le langage C++ numrote les lments partir de zro. Ceci signifie
qu'aprs la dclaration

i nt t ab[ 4] ;

les lments qui existent sont t ab[ 0] , t ab[ 1] , t ab[ 2] et t ab[ 3] . Plus gnralement,

Un tableau ne possde pas d'lment dont l'index soit gal au nombre d'lments du tableau.

Il est tout fait primordial de ne jamais oublier ce dtail car, pour des raisons qui deviendront
videntes au cours de cette Leon :

Lorsque vous accdez un lment d'un tableau, c'est vous de vous assurer que l'index
utilis correspond effectivement un lment du tableau.
Initialisation des tableaux
Etant donn qu'un tableau contient plusieurs valeurs, son initialisation ncessite une liste de
donnes. Cette liste doit simplement figurer entre accolades, chaque valeur tant spare de la
suivante par une virgule. L'instruction suivante initialise un tableau d'entiers :

i nt t ab[ 4] = {3, 2, 1, 0};

Si la liste ne comporte pas assez de valeurs pour remplir le tableau, seuls les premiers lments
seront initialiss. Si la liste comporte trop de valeurs, la compilation du programme chouera.

L'initialisation des tableaux est donc trs simple et trs pratique. Notez toutefois que le signe
gal que nous utilisons ici N'EST PAS l'oprateur d'affectation. La nuance est d'importance car

On ne peut pas affecter une valeur un tableau.

On peut, nous l'avons vu, affecter une valeur un lment d'un tableau, mais il est impossible
d'affecter, en une seule opration, une valeur chacun des lments, ce qui pourrait,
lgitimement, s'appeler "affecter une valeur (de type tableau) un tableau".

J-L Pris - 31/01/06
C++ - Leon 16 Tableaux 3/12
i nt t ab1[ 3] = {36, 42, 12}; / / ceci est une I NI TI ALI SATI ON
i nt t ab2[ 3] ;
t ab2 = t ab1; / / ERREUR : on ne peut pas AFFECTER une val eur un t abl eau
t ab2 = {36, 42, 12}; / / mme r emar que

Ce genre d'erreur provoque un message du compilateur signalant que t ab2 n'est pas une
l val ue, c'est dire ne peut pas lgalement apparatre gauche d'un oprateur d'affectation.

Lorsque tous les lments sont initialiss, le compilateur peut dterminer la taille du tableau
en comptant les valeurs, ce qui vite au programmeur de se tromper en essayant de le faire lui-
mme. Cet effet est obtenu simplement en omettant la taille lors de la cration du tableau :

i nt t ab[ ] = {15, - 3, 18, 4}; / / qui vaut i nt t ab[ 4] = {15, - 3, 18, 4};

Cette facilit d'initialisation des tableaux compense en partie le dsagrment occasionn par
l'impossibilit d'utiliser l'oprateur d'affectation : lorsque les valeurs devant tre utilises sont
connues du programmeur, il s'agit le plus souvent de constantes et il est donc possible d'utiliser
l'initialisation. Lorsque ces valeurs doivent tre calcules par le programme, elles sont
gnralement obtenues l'une aprs l'autre (au cours de l'excution d'une boucle) et il est alors
trs facile d'affecter chacune d'entre elles l'lment du tableau auquel elle correspond.
Dterminer la taille d'un tableau
L'oprateur si zeof ( ) permet, lorsqu'il est appliqu un tableau, de dterminer la place
occupe par celui-ci en mmoire
1
. Si le tableau unTabl eau a t dfini, on peut donc crire :

i nt t ai l l eDuTabl eau = si zeof ( unTabl eau) ;

L'espace occup en mmoire par un tableau dpend de deux facteurs : le nombre d'lments
du tableau, et la taille de chacun de ces lments. Comme tous les lments d'un tableau sont
obligatoirement du mme type, ils sont tous de la mme taille. On peut donc calculer le
nombre d'lments d'un tableau en divisant sa taille totale par celle de son premier lment :

i nt nbEl ement s = si zeof ( unTabl eau) / si zeof ( unTabl eau[ 0] ) ;

Le premier lment possde un avantage sur les autres : il existe forcment (la cration d'un
tableau de 0 lment est interdite), et il est donc toujours possible de dterminer sa taille.

Ce calcul est souvent utile, en particulier lorsque la taille du tableau est value par le
compilateur. Il faut alors viter que cette taille figure explicitement dans la suite du
programme, car toute modification de la longueur de la liste de valeurs deviendrait dlicate :

i nt t ab[ ] = {15, 36, 18, 12}; / / t ai l l e aut omat i que 1
i nt n; 2
f or ( n = 0 ; n < 4 ; ++n) / / mai s on f ai t comme si el l e t ai t i mmuabl e : 3
t r ai t ement ( t ab[ n] ) ; / / TRES DANGEREUX ! 4

Les quelques secondes supplmentaires qu'il faut pour crire

i nt t ab[ ] = {15, 36, 18, 12}; 1
i nt n; 2
f or ( n = 0 ; n < si zeof ( t ab) / si zeof ( t ab[ 0] ) ; ++n) 3
t r ai t ement ( t ab[ n] ) ; 4

vous pargneront des heures de dbugage, ou, pire encore, des rsultats faux.
Tableaux de caractres : les chanes du langage C
Les tableaux de char prsentent une particularit : ils tiennent lieu de type "chane" dans le
langage C, qui est dpourvu de type spcifiquement prvu pour la manipulation de textes.

Le langage C++ est lui aussi dpourvu d'un tel type, mais il se prte aisment la dfinition de
classes telles que la classe QSt r i ng, qui permettent de manipuler du texte dans des conditions
de confort et de scurit bien meilleures que celles offertes par les tableaux de char . Les
langages C et C++ ne sont toutefois pas assez disjoints pour qu'un programmeur C++ puisse
envisager d'ignorer totalement la faon dont C manipule les textes.

Nous avons vu (Leon 2) que le type char , bien qu'tant un type entier, se prte volontiers la
reprsentation des symboles alphanumriques : il suffit d'associer arbitrairement une valeur

1
"Size of..." signifie "taille de...", en anglais.
J-L Pris - 31/01/06
C++ - Leon 16 Tableaux 4/12
diffrente chacun de ces symboles et de prendre soin, lors de l'affichage du contenu d'une
variable de ce type, de dessiner le symbole qui a t associ sa valeur plutt que les chiffres
qui la reprsentent. Avec les conventions habituellement adoptes (le code ASCII), une valeur
de quarante-huit ne se prsente ainsi pas sous la forme "48", mais sous la forme "0".

Utiliser des tableaux de char pour reprsenter des chanes de caractres ne pose gure qu'un
seul problme : comment peut-on dterminer o se trouve la fin de la chane ?

Il est possible qu'une rponse cette question vous semble aller de soi : la fin de la chane
correspond la fin du tableau. Deux facteurs disqualifient cette faon de voir les choses :
- La taille des tableaux est fixe lors de leur dfinition, et elle ne peut pas rellement tre
modifie par la suite. La plupart des programmes ont pourtant besoin de manipuler des
chanes de caractres dont le contenu (et donc, ventuellement, la longueur) est variable.
- Comme nous allons le voir bientt, il existe de nombreux contextes o la taille d'un tableau ne
peut pas tre dtermine, et constituerait donc un bien pitre indicateur de fin de chane.

Il existe deux mthodes pour spcifier la longueur d'une srie de caractres : soit on indique
explicitement le nombre d'lments, soit on utilise une valeur particulire pour marquer la fin.

Chacune de ces mthodes a ses inconvnients. La premire exige des calculs pour tenir jour la
longueur et quelques octets pour la stocker. Le nombre d'octets ddis ce stockage limite pour
la taille des chanes (avec un seul octet, par exemple, les chanes ne pourront pas comporter
plus de 255 caractres). Dans la seconde mthode, le choix d'une valeur particulire pour
matrialiser la fin de la srie fait que cette valeur ne peut plus figurer dans la srie.

La premire mthode est, notamment, utilise par le langage Pascal et par certains BASIC.
Pour ce qui est du langage C, il adopte la seconde :

Lorsqu'un tableau de char est utilis pour stocker une chane de caractres, la fin de cette
chane est matrialise par un char de valeur nulle.

Dans le code ASCII, la valeur nulle ne correspond aucun symbole reprsentable
graphiquement. L'usage de cette valeur pour marquer leur fin ne prive donc les chanes d'aucun
caractre utile (la valeur du caractre '0' est, nous l'avons vu, quarante-huit et non zro).

Lorsqu'on utilise une chane littrale pour initialiser un tableau, le compilateur prend
automatiquement soin de placer le caractre nul final. Il reste cependant ncessaire d'tre
conscient de l'existence de ce caractre, car il occupe une place dans le tableau. Ainsi, si l'on
utilise la dtermination automatique de la taille du tableau, la dfinition suivante

char chai ne[ ] = " coucou" ;
est quivalente
char chai ne[ 7] = {' c' , ' o' , ' u' , ' c' , o' , ' u' , 0};
et non
char chai ne[ 6] = {' c' , ' o' , ' u' , ' c' , o' , ' u' }; / / ce t abl eau n' est pas une cha ne !

Etant donn que les tableaux ne se prtent pas aux oprations d'affectation, modifier le
contenu de ce type de chanes de caractres n'est pas aussi facile que les initialiser. Ecrire

char chai ne[ 15] ; 1
chai ne = " n' i mpor t e quoi " ; / / ERREUR : l ef t oper and must be l - val ue ! 2

ne peut conduire qu' une erreur de compilation. Comme il serait trs fastidieux de modifier
les caractres un par un lorsqu'une chane change de valeur, on prfre gnralement faire
appel aux fonctions de manipulation de chanes de la librairie standard du langage C.
L'utilisation de ces fonctions ncessite la prsence d'une directive

#i ncl ude " st r i ng. h"

La fonction qui assure la copie du contenu d'une chane vers une autre se nomme st r cpy( ) ,
et il convient de lui indiquer quelles sont les chanes concernes. Le code erron prsent
prcdemment peut ainsi tre rectifi :

char chai ne[ 15] ; 1
st r cpy( chai ne, " t out va bi en" ) ; / / st r cpy( ) ser t d' opr at eur d' af f ect at i on 2

La longueur d'une chane confie st r cpy( ) ne doit jamais excder la taille du tableau de
char dans lequel cette fonction va la recopier.

J-L Pris - 31/01/06
C++ - Leon 16 Tableaux 5/12
Une autre fonction de la librairie standard dont l'usage est trs frquent est st r l en( ) , qui
renvoie le nombre de caractres effectifs (c'est dire sans compter le 0 final) de la chane qu'on
lui transmet comme argument. Le fragment de code suivant donne donc la variable
nbCar act er es la valeur 12 :

i nt nbCar act er es = st r l en( chai ne) ; / / chai ne cont i ent " t out va bi en" 3
La fragilit des vrais tableaux
Il reste une proprit des tableaux dont il faut imprativement tre conscient : une fois qu'ils
ont t crs, le langage ne les traite effectivement comme des tableaux que lors de
l'application des oprateurs "adresse de" et si zeof ( ) . Dans tous les autres cas,

Pour valuer une expression dans laquelle figure le nom d'un vrai tableau, le compilateur
remplace ce nom par l'adresse du premier lment du tableau en question.

Il n'est donc, en pratique, pas possible de comprendre rellement les vrais tableaux sans savoir
comment on utilise les pointeurs pour les imiter.
2 - Faux tableaux
La cl de la comprhension des rapports entre tableaux et pointeurs rside dans la nature de
l'oprateur [ ] standard. Il s'agit en ralit d'un oprateur drfrencement, et son usage n'est
nullement li l'existence d'un tableau. Le fonctionnement de l'oprateur [ ] appliqu un
pointeur est dfini d'une faon simple :

Si pt r est un pointeur, et n un nombre entier, alors l'expression
pt r [ n]
est strictement quivalente
*( pt r + n)

Cette dfinition soulve toutefois une difficult : sa dernire ligne fait appel l'addition d'un
pointeur et d'une valeur entire, une opration nouvelle pour nous.
L'arithmtique des pointeurs
La valeur contenue dans un pointeur est une adresse, c'est dire un nombre entier. L'ide
d'ajouter une quantit entire cette valeur n'est donc pas compltement folle, mais elle pose
quand mme un problme : quel va tre le sens de la valeur ainsi obtenue ? Le rsultat va tre
une adresse, mais peut-on tre certain qu'elle est valide, c'est dire qu'elle est celle de la
reprsentation d'une donne dont le type est celui annonc par le type du pointeur ? La
rponse cette question est malheureusement ngative :

Rien ne permet au compilateur de s'assurer qu'une adresse obtenue par calcul est
effectivement celle d'une donne du type attendu.

Il existe en revanche des cas o le compilateur peut tre absolument certain que le rsultat
n'est pas une valeur valide pour un pointeur. Imaginons, par exemple, un pointeur sur f l oat
valide, cest dire contenant ladresse d'un f l oat . Etant donn qu'un f l oat occupe plusieurs
octets, il est certain que l'adresse suivante n'est pas une valeur valide pour notre pointeur. La
premire adresse qui peut ventuellement correspondre au dbut de la reprsentation d'un
second f l oat est celle qui suit la fin de la reprsentation du premier, et non celle qui suit son
dbut. Si un pointeur est valide, toute augmentation de sa valeur d'une quantit infrieure la
taille du type point ne peut gnrer quun pointeur invalide. Si le type f l oat occupe quatre
octets et que la valeur initiale du pointeur est n, on peut reprsenter la situation ainsi :

Adresse Commentaire
n Premier octet de la reprsentation d'une valeur de type f l oat
n + 1
n + 2
Suite de la reprsentation de la valeur de type f l oat
n + 3 Dernier octet de la reprsentation de la valeur de type f l oat
n + 4, c'est dire
n + si zeof ( f l oat )
Premire adresse o l'on peut esprer voir dbuter la
reprsentation d'une autre valeur de type f l oat

J-L Pris - 31/01/06
C++ - Leon 16 Tableaux 6/12
Lorsqu'on ajoute 1 un pointeur, ladresse que ce pointeur contient naugmente donc pas de 1,
mais du nombre d'octets occups par une donne du type correspondant celui du pointeur.

En dautres termes, lorsque vous ajoutez 1 un pointeur, le compilateur lui attribue la premire
valeur dont il nest pas certain quelle rende le pointeur invalide

De mme, lorsqu'on ajoute une quantit x un pointeur, ladresse qu'il contient augmente en
fait de x fois la taille du type point. Cette faon de procder peut sembler trange, mais elle est
trs pratique : tout fonctionne comme si tous les types de donnes occupaient un seul octet, et
on peut utiliser les pointeurs sans se proccuper de la taille des objets qu'ils dsignent.

L'addition d'un nombre entier est certainement la plus importante des oprations arithmtiques
pouvant tre appliques aux pointeurs. Il est galement possible de calculer la diffrence entre
deux pointeurs du mme type : le rsultat obtenu est une valeur entire qui correspond au
nombre d'lments du type concern qui pourraient tre stocks entre les deux adresses.
Drfrencer un pointeur l'aide de l'oprateur [ ]
Loprateur [ ] exigeant un drfrencement, rappelons comment est value une expression
qui applique cette opration une adresse : lexpression a pour valeur celle reprsente
ladresse en question. Dans le cas de loprateur [ ] , cette adresse est la somme de la valeur du
pointeur auquel il est appliqu et de lindex mentionn entre les crochets.

L'toile et les crochets correspondent fondamentalement la mme opration, et une
expression comportant l'une de ces notations peut toujours tre rcrite en utilisant l'autre.
Ajouter une valeur nulle une adresse ne la modifie videmment et on peut donc dire que :

Il est parfaitement indiffrent d'crire *machi n ou machi n[ 0] .

Ces notations ne sont, l'une et l'autre, acceptables que si lvaluation de lexpression machi n
donne une adresse, ce qui est le cas si machi n est le nom dun tableau ou dun pointeur.

Si ces deux notations sont interchangeables, elles suggrent nanmoins des situations
diffrentes : lorsqu'un pointeur est drfrenc l'aide des crochets, c'est gnralement pour
accder l'une des valeurs d'une srie en comportant plusieurs, de type identique. Le code
suivant, quoique parfaitement correct d'un point de vue syntaxique, est tout fait atypique :

doubl e uneVar = 0; 1
doubl e * unPt r = & uneVar ; 2
unPt r [ 0] = 12. 8; / / ok, mai s suggr e t or t l ' exi st ence d' une sr i e de val eur s 3

La connotation inverse est bien moins prononce, et le fait qu'un pointeur soit drfrenc
l'aide de l'toile ne doit pas tre interprt comme un indice suggrant qu'il n'existe pas une srie
de donnes auxquelles le programme va accder en faisant varier la valeur du pointeur.

Les crochets n'tant qu'une faon de drfrencer un pointeur, il n'est pas tonnant qu'ils
n'offrent pas un degr de protection suprieur celui propos par l'oprateur toile : vous tes
seul responsable de la validit de l'adresse obtenue en ajoutant l'index la valeur du pointeur.
Utilisation d'un pointeur pour accder un vrai tableau
Les deux rgles nonces page 5 se combinent pour donner l'impression que l'oprateur [ ]
permet d'accder aux lments d'un vrai tableau : le nom du tableau est remplac par l'adresse
du premier lment de celui-ci, l'index est ajout cette adresse et le rsultat dsigne donc l'un
des lments du tableau. Notez au passage que tout ceci ne fonctionne que parce que

Les diffrents lments d'un tableau sont reprsents la suite les uns des autres en mmoire

Si les lments d'un tableau taient disperss un peu partout dans la mmoire, ajouter l'index
l'adresse du premier ne permettrait pas d'accder au index+1
me
lment.

Un pointeur contenant l'adresse du premier lment du tableau fournit donc une alternative
au nom du tableau lorsqu'on veut accder aux lments de celui-ci :

doubl e nombr es[ 5] ; 1
doubl e * pt r = & nombr es[ 0] ; 2
pt r [ 3] = 2. 5; / / qui vaut nombr e[ 3] = 2. 5; 3
*( pt r + 4) = 1. 7; / / qui vaut nombr e[ 4] = 1. 7; 4

J-L Pris - 31/01/06
C++ - Leon 16 Tableaux 7/12
En procdant ainsi, nous ne faisons qu'expliciter l'interprtation que fera de toute faon le
compilateur. Bien entendu, dans la mesure o notre tableau possde un nom, on ne voit pas
trs bien l'intrt qu'il y aurait crer une autre variable, de type pointeur, pour accder aux
lments. Il arrive cependant que nous soyons confronts des tableaux dpourvus de nom :
un exemple tout fait banal est celui des chanes littrales. Nous avons dj rencontr
plusieurs reprises des expressions du genre "une chane de caractres", mais nous avons
jusqu' prsent vit de soulever la question de leur type. Il s'agit en fait d'un "tableau
(anonyme) de caractres", auquel, selon le contexte, le compilateur pourra substituer l'adresse
du premier de ces caractres. Une chane littrale peut donc fournir une adresse acceptable
comme valeur pour un "pointeur sur char", comme l'illustre l'exemple suivant :

char * messageUn = " Sal ut , a va ?" ;

La nuance entre cette approche et celle consistant crire

char messageDeux[ ] = " Sal ut , a va ?" ;

est assez subtile puisque, bien que les variables ainsi cres ne soient pas du mme type (l'une
est un tableau contenant des caractres alors que l'autre n'est qu'un pointeur contenant
l'adresse d'une zone de mmoire anonyme qui, elle, contient les caractres), l'usage qu'on peut
en faire est, de rares cas particuliers prs, rigoureusement identique.

Une diffrence vidente entre messageUn et messageDeux est la valeur que produirait si zeof ( )
s'il leur tait appliqu. Il est par ailleurs possible que certains compilateurs interdisent la
modification de messageUn, alors que le contenu de messageDeux peut toujours varier.
Cration de faux tableaux par allocation dynamique
Drfrencer un pointeur l'aide de la notation indexe n'a gure d'intrt que s'il existe une
srie de valeurs auxquelles on va pouvoir accder en faisant varier la valeur de l'index. Si,
comme nous venons de le voir, cette condition peut tre ralise en stockant dans le pointeur
l'adresse du premier lment d'un (vrai) tableau, cette technique reste d'un intrt limit, et il
est souvent ncessaire de pouvoir crer des sries de donnes indpendamment de l'existence
de vrais tableaux. L'oprateur new [ ] permet de rserver une zone mmoire d'une taille
adquate la constitution d'une telle srie. Il suffit pour cela de prciser, entre les crochets, le
nombre d'lments que doit comporter la srie :

doubl e * pt r ; 1
pt r = new doubl e[ 4] ; 2

Attention aux fautes de frappe :
pt r = new doubl e( 4) ;
ne rserve pas une zone permettant de stocker 4 valeurs dcimales, mais une zone permettant
d'en stocker une seule, cette zone tant initialise avec la valeur 4.

L'usage d'une allocation dynamique entrane bien entendu les consquences habituelles : il
faut tout d'abord s'assurer que new [ ] a t en mesure de rserver la mmoire requise, et il ne
faut pas oublier de librer cette mmoire lorsqu'on n'en a plus besoin.

La mmoire alloue par new [ ] doit tre libre par del et e[ ]

Ces conditions tant remplies, le pointeur peut-tre utilis pratiquement comme s'il s'agissait
d'un vrai tableau :

doubl e * pt r = new doubl e[ 4] ; 1
i f ( pt r ! = NULL) 2
{ 3
i nt i ; 4
f or ( i =0 ; i < 4 ; ++i ) 5
pt r [ i ] = 3 * i ; / / ne di r ai t - on pas qu' i l s' agi t d' un t abl eau ? 6
ut i l i se( pt r ) ; / / appel d' une f onct i on qui f ai t quel que chose d' i nt r essant 7
del et e[ ] pt r ; 8
} 9

L'oprateur del et e[ ] doit systmatiquement tre utilis lorsque la mmoire a t alloue par
new[ ] . A la diffrence de new[ ] , del et e[ ] n'a besoin d'aucune spcification de taille, et son
couple de crochets reste donc toujours vide.

J-L Pris - 31/01/06
C++ - Leon 16 Tableaux 8/12
Le principal avantage des faux tableaux est que leur taille peut tre choisie au cours de
l'excution du programme. Imaginons qu'il existe une fonction nomme demandeTai l l e( ) qui
instaure un dialogue avec l'utilisateur et renvoie le nombre de donnes que celui-ci dsire
traiter. On peut alors crire :

i nt t ai l l e = demandeTai l l e( ) ; 1
doubl e * pt r = new doubl e[ t ai l l e] ; / / cr e un f aux t abl eau " sur mesur e" 2
i f ( pt r ! = NULL) 3
t r ai t e( pt r ) ; / / appel d' une f onct i on qui f ai t quel que chose d' i nt r essant 4
del et e[ ] pt r ; 5

Une telle souplesse est inaccessible aux vrais tableaux, dont la taille doit tre connue au
moment de la compilation du programme. Il reste malheureusement encore un prix payer
pour cette souplesse, sous la forme de deux restrictions d'usage :

L'oprateur si zeof ( ) est incapable de dterminer la taille d'un faux tableau.

En effet, la variable que nous manipulons n'est pas un tableau mais un simple pointeur. Lui
appliquer l'oprateur si zeof ( ) permet de vrifier que le systme utilis reprsente les adresses
sur 32 bits, soit quatre octets, mais ne dit absolument rien sur le nombre de donnes que le
faux tableau peut contenir. D'autre part,

Il est impossible d'initialiser un faux tableau.

Il faut donc recourir des oprations d'affectation pour attribuer leur premire valeur aux
lments d'un faux tableau.

Remarquez que, si vous ignorez la taille qu'aura effectivement le faux tableau, il serait fort
trange que vous soyez en mesure de fournir une liste correcte de valeurs initiales...
3 - Transmettre un tableau une fonction
Comme nous le savons, "Pour permettre l'valuation d'une expression dans laquelle figure le nom
d'un vrai tableau, le compilateur remplace ce nom par un pointeur ayant pour valeur l'adresse du
premier lment du tableau en question". Une des consquences de cette rgle est que, de fait,

Il est dfinitivement impossible qu'une fonction reoive comme paramtre un vrai tableau.
La fonction reoit une adresse, et non un vrai tableau
Imaginons que nous disposions d'un vrai tableau d'i nt que nous souhaitons communiquer
une fonction nomme sommeTab( ) qui serait charge de faire l'addition de toutes les valeurs
qu'il contient. Nous crivons donc quelque chose comme :

i nt monTab[ ] = {10, 15, 3, 12, 42, 18}; 1
i nt t ot al = sommeTab( monTab) ; 2

Lorsque le programme est sur le point d'appeler la fonction somme( ) , il lui faut valuer
l'expression figurant entre les parenthses, de faon dterminer la valeur qui doit tre
transmise la fonction. Cette expression tant le nom d'un vrai tableau, la rgle rappele ci-
dessus entre en vigueur, et la valeur que reoit la fonction est donc l'adresse du premier
lment du tableau, c'est dire l'adresse d'un i nt . Par consquent, la fonction sommeTab( )
doit disposer d'un paramtre de type "pointeur sur i nt ".
La fonction ne peut pas dterminer la taille de la srie de donnes
Ce pointeur peut tout fait tre utilis pour accder aux valeurs contenues dans le tableau. Il
suffit pour cela de le drfrencer (avec l'oprateur [ ] ou l'oprateur * ).

L'adresse du premier lment ne permet en revanche pas de dterminer combien il y a
d'lments, et il nous faut donc trouver un autre moyen pour indiquer la fonction quelle est
la taille du tableau. Plusieurs solutions sont envisageables : utiliser un second paramtre pour
indiquer la taille du tableau, rserver le premier lment du tableau cet usage, ou utiliser
une valeur "impossible" pour signaler la fin des donnes (si, par exemple, les donnes ne
peuvent tre que positives, on peut utiliser une valeur ngative pour indiquer la fin de la srie).

J-L Pris - 31/01/06
C++ - Leon 16 Tableaux 9/12
Si nous adoptons la premire solution, notre fonction somme( ) peut tre dfinie ainsi :

i nt sommeTab( i nt * t ab, i nt nbEl ement s) 1
{ 2
i nt i ndex; 3
i nt somme = 0; 4
f or ( i ndex = 0 ; i ndex < nbEl ement s ; ++i ndex) 5
somme += t ab[ i ndex] ; 6
r et ur n somme; 7
} 8

et elle pourra tre invoque comme ceci :

i nt t ot al = sommeTab( monTab, si zeof ( monTab) / si zeof ( monTab[ 0] ) ;

Remarquez que la simple mention du nom du tableau suffit assurer la transmission d'une
adresse (en vertu de la rgle d'valuation des expressions comportant des noms de tableaux).
L'appel de la fonction sommeTab( ) ne ncessite donc aucun usage de l'oprateur &.
Autres consquences de la transmission d'une adresse
Le fait que la fonction reoit l'adresse du tableau (et non une copie de celui-ci) la rend, par
dfaut, capable de modifier le contenu du tableau. Si elle n'a pas le faire, il sera donc
prfrable de la doter d'un paramtre de type "pointeur sur constant".

D'autre part, tant donn que la valeur qui lui est transmise est de toute faon une adresse,
une mme fonction peut indiffremment traiter des vrais et des faux tableaux. Notre fonction
sommeTab( ) pourrait tout fait tre utilise ainsi :

const i nt TAI LLE_TABLEAU = 10; 1
i nt * f auxTab = new i nt [ TAI LLE_TABLEAU] ; 2
i f ( f auxTab ! = NULL) 3
{ 4
/ / af f ect e des val eur s aux l ment s ( l ' i ni t i al i sat i on est i mpossi bl e)
i nt i ndex; 5
f or ( i ndex=0 ; i ndex < TAI LLE_TABLEAU; ++i ndex) 6
f auxTab[ i ndex] = i ndex; 7
/ / cal cul e l a somme
sommeTab( f auxTab, TAI LLE_TABLEAU) ; 8
} 9

Un faux tableau tant, par dfinition, un pointeur, il ne pose videmment aucun problme une
fonction qui attend une adresse pour initialiser son paramtre !

Cette trs agrable indiffrence des fonctions l'authenticit des tableaux qui leurs sont
fournis est malheureusement limite au cas des tableaux unidimensionnels, comme vous le
constaterez si vous tudiez l'Annexe 7 : Tableaux multidimensionnels.
Notations archaques et malsaines
Pour des raisons historiques, le paramtre d'une fonction qui reoit un "pointeur sur..." peut
galement tre dclar l'aide d'une notation utilisant les crochets. Ainsi, notre fonction
sommeTab[ ] pourrait non seulement tre dclare

i nt sommeTab( i nt *t ab, i nt nb) ; / / ma ver si on pr f r e

mais aussi

i nt sommeTab( i nt t ab[ ] , i nt nb) ; / / un bl uf f hont

ou mme

i nt sommeTab( i nt t ab[ 10] , i nt nb) ; / / un dl i r e t ot al

Ces deux dernires notations prsentent le grave dfaut de laisser supposer que la fonction
reoit comme paramtre un (vrai) tableau, ce qui est totalement inexact. Le fait d'utiliser l'une
ou l'autre de ces dclarations ne change rien la nature du paramtre transmis, qui reste
inexorablement un simple pointeur, comme le prouvent les trois constatations suivantes.

J-L Pris - 31/01/06
C++ - Leon 16 Tableaux 10/12
1) Aucune des trois formes de dclaration possibles ne rend le compilateur capable de rejeter
une squence du type :

i nt * pt r ;
i nt t ot al = sommeTab( pt r , 12) ; / / l e poi nt eur pt r n' est mme pas val i de !

Le compilateur n'exige donc jamais que le premier paramtre transmis sommeTab( ) soit un
tableau de 10 entiers. Il n'est mme pas ncessaire qu'il s'agisse rellement d'un tableau : un
simple "pointeur sur i nt " convient, mme s'il est dpourvu de contenu valide ! Dans ces
conditions, prtendre que ce paramtre est autre chose qu'un pointeur apparat clairement
comme une promesse qui ne sera pas tenue.

2) Quelle que soit la dclaration adopte, si la fonction applique l'oprateur si zeof ( ) la
valeur qu'elle reoit comme premier argument, elle pourra en dduire brillamment la taille
d'un pointeur, mais elle n'aura jamais accs la taille du tableau de cette faon.

D'ailleurs, si la troisime de ces dclarations signifiait rellement ce qu'elle prtend, pourquoi
serait-il ncessaire de passer un second paramtre ?

3) Ces trois versions de la dclaration sont en fait parfaitement quivalentes du point de vue du
compilateur.

Ceci est illustr de faon spectaculaire par le fait que sommeTab( ) ne peut pas tre surcharge
en utilisant les diverses formes de dclaration possibles pour son premier paramtre. Dclarer la
fonction (dans un fichier .h) en utilisant l'une de ces formes n'oblige d'ailleurs nullement
utiliser la mme forme en tte de dfinition (dans le .cpp).

Vous pouvez penser ce que vous voulez du fait que le nom d'un tableau est "dgrad" en
l'adresse du premier lment de celui-ci lorsque les expressions sont values, mais vous n'y
changerez rien en utilisant des notations qui masquent la ralit. Vous risquez tout au plus
d'oublier cette rgle, ce qui vous empchera d'en tirer parti lorsqu'elle prsente un avantage, et
en aggravera les consquences lorsqu'elle prsente un inconvnient.
4 - Tableaux vs. conteneurs
Du point de vue de l'usage qu'on en fait, un tableau d'unCer t ai nNombr e de choses ressemble
trangement une QMap <i nt , choses>o l'on fait en sorte que les cls utilises soient les
valeurs de 0 unCer t ai nNombr e- 1. Un tableau de char utilis pour stocker du texte
ressemble, pour sa part, une QSt r i ng. Concrtement, l'alternative se prsente donc
frquemment : doit-on alors prfrer les tableaux ou les conteneurs ?
Choisir entre un tableau et une QMap
Pour un programmeur qui dispose d'une classe telle que QMap, les tableaux ne prsentent
gure d'attraits :

- Les vrais tableaux exigent que leur taille soit fixe lors de l'criture du programme, et elle est
gnralement limite par le systme (et non par la quantit de mmoire disponible).

- L'utilisation de faux tableaux exige une gestion de l'allocation dynamique (new[ ] et del et e[ ] )
qui peut tre source d'erreurs.

- La taille des tableaux est fixe (mme un faux tableau sera incapable grandir si les donnes
stocker s'avrent plus nombreuses que prvues).

Une QMap, par contraste, n'impose son utilisateur aucune contrainte de taille ou de gestion
explicite de l'allocation dynamique. La leve de ces contraintes est, en fait, la raison d'tre
fondamentale des classes conteneur.

- Les index permettant d'accder aux lments d'un tableau sont obligatoirement des entiers,
alors qu'il est possible de crer une QMap utilisant des cls d'un autre type.

Vue sous cet angle, une QMap ressemble plus un dictionnaire qu' un tableau.

- Les lments d'un tableau qui restent inutiliss occupent autant de place que les autres.

- S'il existe des lments inutiliss, un QMap: : I t er at or permet de ne parcourir que ceux qui
nous intressent.

J-L Pris - 31/01/06
C++ - Leon 16 Tableaux 11/12
Imaginons que nous devions afficher le nombre d'occurrences de chacune des valeurs observes
dans une srie de nombres entiers positifs strictement infrieurs 5000. Si ces nombres
peuvent tre obtenus un un en appelant une fonction val eur Sui vant e( ) (qui signale
l'puisement des donnes en renvoyant 1), comment allons nous procder au dnombrement ?

Si nous utilisons un tableau, nous allons crire quelque chose comme

i nt n;
/ / cr at i on des compt eur s
doubl e compt eur s[ 5000] ; / / un compt eur pour chacune des val eur s possi bl es
f or ( n=0 ; n < si zeof ( compt eur s) / si zeof ( compt eur s[ 0] ) ; ++n)
compt eur s[ n] = 0;
/ / dnombr ement
n = val eur Sui vant e( ) ;
whi l e ( n >= 0)
{
i f ( n < si zeof ( compt eur s) / si zeof ( compt eur s[ 0] )
++compt eur s[ n] ; / / i ncr ment e l e compt eur cor r espondant l a val eur obser ve
n = val eur Sui vant e( ) ;
}
/ / af f i chage
f or ( n=0 ; n < si zeof ( compt eur s) / si zeof ( compt eur s[ 0] ) ; ++n)
i f ( compt eur s[ n] ! = 0)
af f i che( n, compt eur s[ n] ) ;

Si nous utilisons une QMap, le code quivalent est

i nt n;
QMap<i nt , i nt > compt eur s;
/ / dnombr ement
n = val eur Sui vant e( ) ;
whi l e ( n >= 0)
{
++compt eur s[ n] ; / / i ncr ment e l e compt eur cor r espondant l a val eur obser ve
n = val eur Sui vant e( ) ;
}
/ / af f i chage
QMap<i nt , i nt >: : I t er at or i t ;
f or ( i t =compt eur s. begi n( ) ; i t ! = compt eur s. end( ) ; ++i t )
af f i che( i t . key( ) , i t . dat a( ) ) ;

La diffrence essentielle entre ces deux fragments de code est que, dans le second, aucun
compteur n'est cr pour dnombrer les valeurs qui n'apparaissent jamais dans les donnes.

Remarquez aussi que, si les donnes contiennent des valeurs non conformes (ngatives ou
suprieures 4999), le programme aura du mal les grer s'il utilise un tableau (l'exemple ci-
dessus les ignore simplement) alors que le problme ne se pose pas avec une QMap (le
programme fonctionne mme si on ne sait absolument rien des valeurs qu'on risque de
rencontrer).

Il faut nanmoins remarquer que :

- Certaines donnes se prtent mal une structuration plus sophistique qu'un tableau : une
image, par exemple, ne sera sans doute jamais reprsente par une QMap de pixels.

Tout simplement parce qu'aucune carte graphique ne sait ce qu'est une QMap, alors que la
"structuration" (si l'on peut dire) en tableau est universellement reconnue.

- Les vrais tableaux peuvent tre initialiss.

C'est un dtail bien moins mineur qu'on pourrait croire. Il m'arrive de crer des vrais tableaux
uniquement dans ce but :

QMap <i nt , QSt r i ng> l esMoi s;
{/ / pseudo i ni t i al i sat i on de l a QMap
i nt n;
QSt r i ng moi s[ ] = {" J anvi er " , " Fvr i er " , " Mar s" , " Avr i l " , " Mai " , " J ui n" ,
" J ui l l et " , " Aot " , " Sept embr e" , " Oct obr e" , " Novembr e" , " Dcembr e" };
f or ( n=0 ; n < si zeof ( moi s) / si zeof ( moi s[ 0] ) ; ++n)
l esMoi s[ n] = moi s[ n] ;
}/ / di spar i t i on du vr ai t abl eau devenu i nut i l e

A part dans quelques cas bien particuliers, vous n'avez donc pas vritablement de raison de
prfrer un tableau une QMap.
J-L Pris - 31/01/06
C++ - Leon 16 Tableaux 12/12
Choisir entre un tableau et une QSt r i ng
La plupart des remarques faites propos du cas gnral restent vraies dans le cas o les
donnes reprsenter sont les caractres d'un texte. Il faut toutefois ajouter que :

- Si l'ide d'lments inoccups est peu pertinente lorsqu'on reprsente du texte, les
contraintes lies l'immuabilit de la taille des tableaux sont trs gnantes dans ce cas.

- Les QSt r i ng peuvent tre initialises.

- Les QSt r i ng contiennent des QChar et non des char , ce qui leur permet d'utiliser Unicode
plutt que le code ASCII et les rend capable de reprsenter correctement un texte, quelle
qu'en soit la langue.

- Il est trs facile d'obtenir une QSt r i ng partir d'un tableau de char , et rciproquement.

Vous n'avez donc a priori aucune raison d'crire du code utilisant des tableaux de char pour
stocker du texte.

Ce qui ne veut pas dire que vous ne rencontrerez jamais de code pratiquant ainsi.
5 - Bon, c'est gentil tout a, mais a fait dj 11 pages. Qu'est-ce
que je dois vraiment en retenir ?
1) Si vous n'avez pas une raison rellement imprieuse justifiant l'usage d'un tableau, utilisez
plutt un conteneur.

2) Il existe deux sortes de tableaux, les vrais et les faux.

3) Les faux tableaux ne sont que des pointeurs rendus valides par le fait qu'ils contiennent
l'adresse d'un bloc de mmoire rserv l'aide de new[ ] .

4) Lorsqu'ils cessent d'tre utiles, les faux tableaux doivent tre dtruits avec del et e[ ] .

5) L'oprateur [ ] drfrence le pointeur auquel il est appliqu : on ajoute la valeur du
pointeur celle de l'index figurant entre les crochets, puis on accde l'adresse ainsi
obtenue.

6) Les seules choses que le langage sache faire avec un vrai tableau, c'est l'initialiser et donner
sa taille.

7) Le langage masque son incapacit travailler rellement sur les vrais tableaux en utilisant
secrtement l'adresse de leur premier lment. Ceci lui permet de faire semblant d'accder
aux lments d'un vrai tableau l'aide de l'oprateur [ ] .

8) Cette imposture s'croule (notamment) ds qu'un tableau est pass comme argument une
fonction : celle-ci doit se contenter du simple pointeur qu'elle reoit.

9) Les vrais amis n'essaient pas de vous faire prendre de faux tableaux pour des vrais.



J-L Pris - 31/01/06

Das könnte Ihnen auch gefallen