C++ : Leçon 9 Tableaux



Documents pareils
I. Introduction aux fonctions : les fonctions standards

Chapitre 2. Classes et objets

Pour signifier qu'une classe fille hérite d'une classe mère, on utilise le mot clé extends class fille extends mère

INITIATION AU LANGAGE C SUR PIC DE MICROSHIP

TP n 2 Concepts de la programmation Objets Master 1 mention IL, semestre 2 Le type Abstrait Pile

Chapitre 1 : Introduction aux bases de données

Chap III : Les tableaux

Généralités sur le Langage Java et éléments syntaxiques.

Les chaînes de caractères

INF2015 Développement de logiciels dans un environnement Agile. Examen intra 20 février :30 à 20:30

Les structures. Chapitre 3

2 Grad Info Soir Langage C++ Juin Projet BANQUE

Recherche dans un tableau

1. Qu'est-ce que SQL? La maintenance des bases de données Les manipulations des bases de données... 5

Cours d initiation à la programmation en C++ Johann Cuenin

Série TD 3. Exercice 4.1. Exercice 4.2 Cet algorithme est destiné à prédire l'avenir, et il doit être infaillible! Exercice 4.3. Exercice 4.

Créer une base de données

Centre CPGE TSI - Safi 2010/2011. Algorithmique et programmation :

UEO11 COURS/TD 1. nombres entiers et réels codés en mémoire centrale. Caractères alphabétiques et caractères spéciaux.

Date : Tangram en carré page

Traitement de texte : Quelques rappels de quelques notions de base

Premiers Pas en Programmation Objet : les Classes et les Objets

LE PROBLEME DU PLUS COURT CHEMIN

CHAPITRE VIII : Les circuits avec résistances ohmiques

Institut Supérieure Aux Etudes Technologiques De Nabeul. Département Informatique

IN Cours 1. 1 Informatique, calculateurs. 2 Un premier programme en C

Travaux pratiques. Compression en codage de Huffman Organisation d un projet de programmation

Initiation à la programmation en Python

CRÉER UNE BASE DE DONNÉES AVEC OPEN OFFICE BASE

L'instruction if permet d'exécuter des instructions différentes selon qu'une condition est vraie ou fausse. Sa forme de base est la suivante:

1. Introduction Création d'une macro autonome Exécuter la macro pas à pas Modifier une macro... 5

Seance 2: En respectant la méthode de programmation par contrat, implémentez les autres fonctions de jeu.

Rappel. Analyse de Données Structurées - Cours 12. Un langage avec des déclaration locales. Exemple d'un programme

Introduction à la programmation orientée objet, illustrée par le langage C++ Patrick Cégielski

Didacticiel de mise à jour Web

Cours d introduction à l informatique. Partie 2 : Comment écrire un algorithme? Qu est-ce qu une variable? Expressions et instructions

Programmation C++ (débutant)/instructions for, while et do...while

INTRODUCTION A JAVA. Fichier en langage machine Exécutable

Exercices de dénombrement

LES TYPES DE DONNÉES DU LANGAGE PASCAL

modélisation solide et dessin technique

Conventions d écriture et outils de mise au point

Le chiffre est le signe, le nombre est la valeur.

Le stockage local de données en HTML5

Débuter avec OOo Base

Représentation des Nombres

Systemes d'exploitation des ordinateurs

Bases de programmation. Cours 5. Structurer les données

IMPORTATION, CRÉATION, MANIPULATION, EXPORTATION DE DONNÉES STATISTIQUES

DA MOTA Anthony - Comparaison de technologies : PhoneGap VS Cordova

Créer le schéma relationnel d une base de données ACCESS

SOMMAIRE. Travailler avec les requêtes... 3

1. Introduction Création d'une requête...2

Algorithmique et Programmation, IMA

Chapitre 5 : Les procédures stockées PL/SQL

C++ COURS N 2 : CLASSES, DONNÉES ET FONCTIONS MEMBRES Classes et objets en C++ Membres d'une classe Spécification d'une classe Codage du comportement

GUIDE DE L UTILISATEUR

TRAVAUX DIRIGES D'INFORMATIQUE INITIATION A LA MANIPULATION DE WINDOWS 98

FOIRE AUX QUESTIONS PAIEMENT PAR INTERNET. Nom de fichier : Monetico_Paiement_Foire_aux_Questions_v1.7 Numéro de version : 1.7 Date :

l'ordinateur les bases

Le Langage De Description De Données(LDD)

PARAGON SYSTEM BACKUP 2010

Onglet sécurité de Windows XP Pro et XP Home

Import de comptes (xls)

Algorithme. Table des matières

Compte-rendu de projet de Système de gestion de base de données

Chapitre 1 : La gestion dynamique de la mémoire

Publipostage avec Calc

O b s e r v a t o i r e E V A P M. Taxonomie R. Gras - développée

UE C avancé cours 1: introduction et révisions

1/ Présentation de SQL Server :

RÉSOLUTION DE SYSTÈMES À DEUX INCONNUES

Info0101 Intro. à l'algorithmique et à la programmation. Cours 3. Le langage Java

Utiliser Access ou Excel pour gérer vos données

RECOMMANDATION 27 EFFICACITE DE LA COMMUNICATION, ENTRE LES CANAUX DE DISTRIBUTION ET LES ASSUREURS, ET RECIPROQUEMENT.

Projet : PcAnywhere et Le contrôle à distance.

Utilisation d objets : String et ArrayList

Introduction à MATLAB R

IV- Comment fonctionne un ordinateur?

Le langage C. Séance n 4

Guide d'initiation aux. certificats SSL. Faire le bon choix parmi les options qui s'offrent à vous en matière de sécurité en ligne. Document technique

6. Les différents types de démonstrations

Auguria_PCM Product & Combination Manager

Manuel d utilisation 26 juin Tâche à effectuer : écrire un algorithme 2

Projet de traitement d'image - SI 381 reconstitution 3D d'intérieur à partir de photographies

TP 1. Prise en main du langage Python

Une introduction au nouveau guide de la SCHL sur les réserves de remplacement

Navigation dans Windows

Chapitre 2 Devine mon nombre!

Introduction. I Étude rapide du réseau - Apprentissage. II Application à la reconnaissance des notes.

UE Programmation Impérative Licence 2ème Année

Conception de circuits numériques et architecture des ordinateurs

1. Structure d un programme C. 2. Commentaire: /*..texte */ On utilise aussi le commentaire du C++ qui est valable pour C: 3.

Manuel de l'utilisateur d'intego VirusBarrier Express et VirusBarrier Plus

Dossier projet isn 2015 par Victor Gregoire

SOS Info: Traitement de textes. 1. Structurer un document. 2. Enregistrer un document

PROBLEMES D'ORDONNANCEMENT AVEC RESSOURCES

Partie 7 : Gestion de la mémoire

CONDITIONS PARTICULIERES NUMEROS SVA

Transcription:

Centre Informatique pour les Lettres et les Sciences Humaines C++ : Leçon 9 Tableaux - Vrais tableaux... Accès aux éléments d'un tableau... Classes, tableaux et pointeurs... Initialisation des tableaux... Déterminer la taille d'un tableau... Tableaux de caractères : les chaînes du langage C... 5 La fragilité des vrais tableaux... 6 - Faux tableaux...7 L'arithmétique des pointeurs... 7 Déréférencer un pointeur à l'aide de crochets... 8 Utilisation d'un pointeur pour accéder à un vrai tableau... 8 Création de faux tableaux par allocation dynamique... 9 - Les tableaux comme paramètres de fonctions...0 La fonction reçoit un "pointeur sur ", et non un vrai tableau... La fonction ne peut pas déterminer la taille du faux tableau... Autres conséquences de la transmission d'une adresse... Notations malsaines... - Bon, c'est gentil tout ça, mais ça fait déjà pages. Qu'est-ce que je dois vraiment en retenir?... 5 - J'ai rien compris, est-ce que quelqu'un d'autre pourrait m'expliquer ça un peu plus clairement?... 6 - Pré-requis de la Leçon 0... Document du 8/0/0 - Retrouvez la version la plus récente sur http://www.up.univ-mrs.fr/wcpp/lecons.htm

C++ - Leçon 9 Tableaux / Le programme que nous avons réalisé au cours du TD 08 a mis en évidence une limite de notre capacité actuelle à stocker des données en mémoire. En effet, que ces données soient directement représentées dans des variables ou qu'elles soient stockées dans des zones de mémoire auxquelles on accède en déréférençant des pointeurs, le problème reste le même : à chaque donnée doit correspondre une variable (soit celle qui va contenir la donnée, soit le pointeur qui va contenir l'adresse de la zone de mémoire contenant la donnée). Lorsque le nombre de données devient très grand, il n'est plus envisageable de créer une à une toutes les variables qui seraient nécessaires. Dans le TD 08, nous avons utilisé une méthode qui permet de manipuler un grand nombre de données à l'aide d'un petit nombre de variables : les données sont stockées dans un fichier, et une seule d'entre elle est effectivement représentée en mémoire à un moment donné. Cette approche présente certains avantages, et en particulier celui de placer très haut la limite du nombre de données manipulables. En effet, la capacité de stockage disponible pour un programme ainsi conçu est déterminée par l'espace disque accessible, et non par la taille de la mémoire. Il ne faut cependant pas surestimer cet avantage, car les machines actuelles disposent souvent d'une mémoire considérable, et les systèmes d'exploitation modernes prolongent encore cette capacité en faisant automatiquement appel aux disques lorsque cela devient nécessaire. Les inconvénients de la méthode employée dans le TD 08 apparaissent en général avant ses avantages : lorsque les traitements à effectuer sont plus complexes qu'un calcul de moyenne, et notamment lorsque ces traitements font intervenir simultanément un grand nombre de données, la nécessité d'accéder à un fichier ralentit l'exécution du programme dans des proportions qui peuvent devenir inacceptables. La Leçon 9 va donc être consacrée à la présentation d'un dispositif permettant de stocker en mémoire un grand nombre de données, sans avoir à créer un nombre équivalent de variables. - Vrais tableaux Lorsqu'il s'agit de stocker en mémoire une collection de données du même type, le langage C++ permet de ne créer qu'une seule variable, de type "tableau de ". La syntaxe utilisée pour créer un tableau exige simplement que le nom de la variable soit suivi du nombre de valeurs qui doivent pouvoir être stockées dans le tableau, placé entre crochets. La ligne int untableau[]; définit donc une variable de type "tableau d'int", nommée untableau et susceptible de stocker simultanément valeurs entières. Si cet exemple fait intervenir le type int, il reste bien entendu que celui-ci ne dispose d'aucun privilège particulier, et que tous les types de données (y compris les types définis, tels que les types énumérés ou les classes) peuvent, de la même façon, donner naissance à des tableaux. Accès aux éléments d'un tableau Les tableaux partagent avec les classes la particularité de posséder des "sous-variables". Alors que les "sous-variables" d'une classe s'appellent des "variables membre", celles d'un tableau s'appellent des "éléments". Si les variables membre peuvent être de types différents, les éléments d'un tableau sont nécessairement tous du même type. Une autre différence importante est que les variables membre possèdent des noms différents, que le programmeur doit spécifier lorsqu'il définit la classe, alors que les éléments d'un tableau sont simplement numérotés à partir de 0, sans que le programmeur ait à intervenir. Ces numéros sont plus communément appelés des "index". La syntaxe qui permet d'accéder aux sous variables est également différente, puisqu'on désigne une variable membre en connectant son nom à celui de l'instance concernée à l'aide de l'opérateur de sélection ".", alors qu'on désigne un élément d'un tableau en plaçant, après le nom du tableau, une paire de crochets encadrant l'index de l'élément. Si untableau est défini comme ci-dessus, on affectera, par exemple, la valeur nulle à l'élément d'indice en écrivant : untableau[] = 0; Remarquez que, si les crochets utilisés pour encadrer un index (lorsqu'on accède à un élément d'un tableau) et ceux utilisés pour encadrer la taille (lorsqu'on définit un tableau) sont typographiquement identiques (les symboles utilisés sont, dans un cas comme dans l'autre, [ et ]), ils correspondent évidemment à des opérations fort différentes. J-L Péris - 8/0/0

C++ - Leçon 9 Tableaux / Comme à son habitude, le langage C++ numérote les éléments à partir de zéro. Ceci signifie qu'après la déclaration int tableau[]; les éléments qui existent sont tableau[0], tableau[], tableau[] et tableau[]. En conséquence : Un tableau ne possède pas d'élément dont l'index soit égal au nombre d'éléments du tableau! Il est tout à fait primordial de ne jamais oublier ce détail car, pour des raisons qui deviendront claires un peu plus loin dans cette Leçon : Lorsque vous accédez à un élément d'un tableau, c'est à vous de vous assurer que l'index utilisé correspond effectivement à un élément du tableau. Classes, tableaux et pointeurs Le parallélisme entre classes et tableaux peut être illustré par les deux ensembles d'instructions suivants, qui créent tous deux une variable composée de deux sous-variables de type double, et placent dans ces sous variables les valeurs. et.7. Avec une classe Avec un tableau Définition du type class demo { public: double membrezero; //inutile double membreun; }; Définition de la variable demo uneinstance; double untableau[]; Accès aux sous-variables uneinstance.membrezero =.; uneinstance.membreun =.7; untableau[0] =.; untableau[] =.7; Classes et tableaux diffèrent cependant énormément quant aux possibilités qu'ils offrent. Les classes sont capables d'avoir des variables membre de types différents, et peuvent comporter des fonctions membre. Elles peuvent aussi être définies les unes par rapport aux autres, ce qui donne naissance à de nombreuses possibilités sur lesquelles nous reviendrons dans la suite du cours. Bien qu'ils soient considérablement plus simples que les classes, les tableaux présentent des caractéristiques puissantes. Essayez, par exemple, de transposer le fragment de code suivant pour qu'il utilise une classe au lieu d'utiliser un tableau : int tab[56]; int index; for(index = 0 ; index < 56 ; index = index + ) tab[index] = index; La propriété des tableaux qui est exploitée dans ce fragment de code est le fait que l'on n'accède pas aux éléments à l'aide de noms, mais à l'aide d'un index appliqué à une variable unique (le tableau lui-même). La ligne est exécutée un très grand nombre de fois et, lors de chacune de ces exécutions, elle accède à un élément différent du tableau, chose qui serait impossible si chacun de ceux-ci était une variable dotée d'un nom spécifique (comme le sont, par exemple, les variables membre d'une classe). Nous avons déjà rencontré un phénomène semblable : déréférencer un pointeur permet aussi à une même ligne de code d'accéder à une donnée différente chaque fois qu'elle est exécutée (il suffit que la valeur du pointeur ait changé entre temps). Il y a là bien plus qu'une similitude, et cette parenté profonde entre tableaux et pointeurs va constituer un des thèmes majeurs de cette Leçon. Initialisation des tableaux Etant donné qu'un tableau contient plusieurs valeurs, son initialisation nécessite une liste de données. Cette liste doit simplement figurer entre accolades, chaque valeur étant séparée de la suivante par une virgule. L'instruction suivante initialise un tableau d'entiers : int tab[] = {,,, 0}; J-L Péris - 8/0/0

C++ - Leçon 9 Tableaux / Si le nombre de valeurs figurant dans la liste est inférieur au nombre d'éléments du tableau, seuls les premiers éléments du tableau sont initialisés. Si le nombre de valeurs figurant dans la liste est supérieur au nombre d'éléments du tableau, la compilation du programme échouera. L'initialisation des tableaux est donc très simple et très pratique. Notez toutefois que le signe égal que nous utilisons ici N'EST PAS l'opérateur 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 élément d'un tableau, mais il est impossible d'affecter, en une seule opération, une valeur à chacun des éléments, ce qui pourrait, légitimement, s'appeler "affecter une valeur (de type tableau) à un tableau". int tab[] = {6,, }; //ceci est une INITIALISATION int tab[]; tab = tab; //ERREUR : on ne peut pas AFFECTER une valeur à un tableau tab = {6,, }; //même remarque La compilation d'un programme comportant ce genre de tentatives provoque un message d'erreur signalant (plus ou moins clairement) que tab n'est pas une lvalue, c'est à dire ne peut pas légalement apparaître à gauche d'un opérateur d'affectation. Lorsque tous les éléments d'un tableau sont initialisés, le compilateur est capable d'utiliser la liste de valeurs pour déterminer la taille du tableau, ce qui évite au programmeur de se tromper en essayant de le faire lui-même. Cet effet est obtenu simplement en ne fournissant aucune taille entre les crochets qui signalent la définition du tableau : int tab[] = {5, -, 8, }; //équivaut à int tab[] = {5, -, 8, }; Cette très grande facilité d'initialisation des tableaux compense en partie le désagrément occasionné par l'impossibilité d'utiliser l'opérateur d'affectation : lorsque les valeurs devant être utilisées sont connues du programmeur, il s'agit le plus souvent de constantes et il est donc possible d'utiliser l'initialisation. Lorsque, par contre, ces valeurs doivent être déterminées durant l'exécution du programme, elles sont généralement obtenues l'une après l'autre (au cours de l'exécution d'une boucle) et il est alors très facile d'affecter chacune d'entre elles à l'élément du tableau auquel elle correspond. Déterminer la taille d'un tableau L'opérateur sizeof() permet, lorsqu'il est appliqué à un tableau, de déterminer la place occupée par celui-ci en mémoire. Si le tableau untableau a été défini, on peut donc écrire : int tailleuntableau = sizeof(untableau); L'espace occupé en mémoire par un tableau dépend de deux facteurs : le nombre d'éléments du tableau, et la taille de chacun de ces éléments. Comme tous les éléments d'un tableau sont nécessairement du même type, ils sont tous de la même taille. On peut donc facilement calculer le nombre d'éléments d'un tableau, en divisant la taille totale de celui-ci par la taille de son premier élément : int nbelements = sizeof(untableau) / sizeof(untableau[0]); Le premier élément du tableau possède un avantage sur tous les autres : il existe toujours (la création d'un tableau ne comportant aucun élément est interdite), et il est donc toujours possible d'utiliser sizeof() pour déterminer sa taille. Ce calcul est souvent utile, en particulier lorsque la taille du tableau est déterminée automatiquement par le compilateur à partir de la liste de valeurs servant à l'initialiser. Il est préférable, dans ce cas, d'éviter d'utiliser une constante égale à ce nombre d'éléments, car "Size of " signifie tout simplement "taille de ", en anglais. La création d'un tableau mobilise, bien entendu, un bloc de mémoire d'une taille égale au nombre de données pouvant être accueillies dans ce tableau, multiplié par la taille d'une valeur du type concerné. Si ces deux nombres augmentent, leur produit peut rapidement prendre des proportions impressionnantes. Avant d'envisager la création de tableaux comportant des milliers de valeurs de type complexe (des instances d'une classe comportant de nombreuses variables membre, par exemple, surtout si certaines de ces variables membre sont elles-même des tableaux ), il est donc préférable d'évaluer la quantité de mémoire requise, de façon à vérifier que le programme pourra fonctionner dans des conditions satisfaisantes. J-L Péris - 8/0/0

C++ - Leçon 9 Tableaux 5/ toute modification de la liste nécessiterait alors d'ajuster la valeur de cette constante, ce qui est une source d'erreurs dont il vaut mieux se passer. Les quelques secondes qu'il faut pour écrire int tab[] = {5, 6, 8, }; int index; for (index = 0 ; index < sizeof(tab)/sizeof(tab[0]) ; index = index + ) traitement(tab[index]); plutôt que int tab[] = {5, 6, 8, }; int index; for (index = 0 ; index < ; index = index + ) //TRES DANGEREUX! traitement(tab[index]); vous épargneront des heures de débugage, ou, pire encore, des résultats faux. Tableaux de caractères : les chaînes du langage C Les tableaux de char présentent une particularité : ils tiennent lieu de type "chaîne" dans le langage C, qui est dépourvu de type spécifiquement prévu pour la manipulation de textes. Le langage C++ ne possède pas non plus de "type spécifiquement prévu pour la manipulation de textes", mais le mécanisme des classes permet de remédier très facilement à cet état de fait, et d'obtenir un type dont l'usage est bien plus facile et sûr que celui des tableaux de char. La Standard Templates Library comporte d'ailleurs une telle classe, simplement nommée string. Les langages C et C++ ne sont toutefois pas assez disjoints pour qu'un programmeur C++ puisse envisager d'ignorer totalement la façon dont C manipule les textes. Nous avons vu (Leçon ) que le type char, bien qu'étant un type entier, se prête bien à la représentation des symboles alphanumériques : il suffit d'associer conventionnellement une valeur différente à chacun de ces symboles et de prendre soin, lorsqu'on affiche le contenu d'une variable de ce type, de ne pas utiliser les conventions habituelles, mais de dessiner le symbole qui a été associé à la valeur à afficher. Dans ce contexte, une valeur de quarante-huit ne se présente pas sous la forme "8", mais sous la forme "0". Utiliser des tableaux de char pour représenter des chaînes de caractères ne pose guère qu'un seul problème : comment peut-on déterminer où se trouve la fin de la chaîne? Il est possible qu'une réponse à cette question vous semble aller de soi : la fin de la chaîne correspond à la fin du tableau. Deux raisons font que cette façon de voir les choses ne peut absolument pas être adoptée : - La taille des tableaux est fixée lors de leur définition, et elle ne peut pas réellement être modifiée par la suite. La plupart des programmes ont pourtant besoin de manipuler des chaînes de caractères dont le contenu (et donc, éventuellement, la longueur) est variable. - Comme nous allons le voir dans la suite de cette Leçon, il existe de nombreux contextes où la taille d'un tableau ne peut pas être déterminée, et constituerait donc un bien piètre indicateur de fin de chaîne. Il existe deux types de réponses au problème de la spécification de la fin d'une série telle qu'une chaîne de caractères : soit on indique explicitement le nombre d'éléments, soit on utilise une valeur particulière pour symboliser la fin. Ces deux méthodes créent des contraintes différentes : dans le premier cas, il faut d'une part effectuer des calculs pour tenir à jour ce compte et, d'autre part, réserver quelques octets de mémoire pour le stocker 5, alors que dans le second cas, le choix d'une valeur particulière pour matérialiser la fin de la série fait que cette valeur ne peut plus figurer dans la série. La première méthode est, notamment, utilisée 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 chaîne de caractères, la fin de cette chaîne est matérialisée par un char de valeur nulle. Sans faire réellement partie du langage C++, la STL n'en est pas moins, comme son nom l'indique, considérée comme "standard". Elle est donc, normalement, disponible dans tous les environnements de développement C++. La convention généralement en vigueur est appelée le "code ASCII". 5 Cette nécessité de représenter en mémoire le nombre d'éléments de la série risque d'introduire une limite absolue à la longueur admissible. Si, par exemple, on ne réserve qu'un octet pour stocker la longueur, on ne peut pas utiliser de séries ayant plus de 55 éléments. J-L Péris - 8/0/0

C++ - Leçon 9 Tableaux 6/ La valeur 0 ne correspond, dans le code ASCII, à aucun symbole représentable graphiquement. Son utilisation comme marqueur de fin de chaînes ne prive donc celles-ci d'aucun caractère utile (la valeur du caractère '0' est, nous l'avons vu, 8 et non 0). Lorsqu'on utilise une chaîne littérale pour initialiser un tableau, le compilateur prend automatiquement soin de placer le caractère nul final. Il reste cependant nécessaire d'être conscient de l'existence de ce caractère, car il occupe une place dans le tableau. Ainsi, si l'on utilise la détermination automatique de la taille du tableau, la définition suivante char chaine[] = "coucou"; est équivalente à char chaine[7] = {'c','o','u','c',o','u', 0}; et non à char chaine[6] = {'c','o','u','c',o','u'};//ce tableau n'est pas une chaîne! Etant donné que les tableaux ne se prêtent pas aux opérations d'affectation, modifier le contenu de ce type de chaînes de caractères n'est pas aussi facile que les initialiser 6. Ecrire char chaine[5]; chaine = "n'importe quoi"; //ERREUR : left operand must be l-value! ne peut conduire qu'à une erreur de compilation. Comme il serait extrêmement fastidieux d'avoir à modifier les éléments du tableau un par un lorsqu'une chaîne de caractère change de valeur, les programmes qui utilisent ce type de données font généralement appel à la partie de la librairie standard du langage C qui comporte des fonctions de manipulation de chaînes. L'utilisation de ces fonctions nécessite de faire figurer, en tête du fichier, une ligne #include "string.h" de façon à inclure les déclarations nécessaires. La fonction qui assure la copie du contenu d'une chaîne vers une autre se nomme strcpy(), et il convient de lui indiquer quelles sont les chaînes concernées. Le code erroné présenté précédemment peut ainsi être rectifié : char chaine[5]; strcpy(chaine, "tout va bien"); //strcpy() sert d'opérateur d'affectation Une autre fonction de la librairie standard dont l'usage est très fréquent est strlen(), qui renvoie le nombre de caractères effectifs (c'est à dire sans compter le 0 final) de la chaîne qu'on lui transmet comme argument. Le fragment de code suivant détermine le nombre d'apparitions du caractère 'x' dans la chaîne unechaine : 5 6 int longueur = strlen(unechaine); int position; int nbx = 0; for(position = 0 ; position < longueur ; position = position + ) if (unechaine[position] == 'x') nbx = nbx + ; En tant que programmeur C++, vous disposez d'outils permettant une manipulation bien plus élégante des données textuelles, et vous devriez limiter votre utilisation des tableaux de char aux cas où la nécessité d'assurer la compatibilité avec des portions de code écrites en C les rend indispensables. Rappelons que, même si vous ne disposez pas d'une librairie spécifique à votre environnement comportant une classe adaptée à ce type de données (comme par exemple la classe CString des MFC, dans le cas de Windows), vous avez très vraisemblablement accès à la Standard Template Library, qui comporte une classe string. La fragilité des vrais tableaux Il reste une propriété des tableaux dont il est important d'être conscient : les seuls cas où le langage les traite effectivement comme des tableaux est l'application des opérateurs "adresse 6 Notons au passage que, si elle offre un confort incontestable, l'initialisation d'un tableau de caractères à l'aide d'une chaîne littérale constitue un abus syntaxique manifeste, puisqu'une valeur unique (la chaîne) est décomposée pour attribuer des valeurs différentes aux différents éléments du tableau. Cette opération, tout à fait exceptionnelle, est effectuée par le compilateur (ce qui permet de l'utiliser pour effectuer des initialisations) mais n'est pas réalisable lors de l'exécution du programme (ce qui interdit son usage dans le cadre d'une affectation). J-L Péris - 8/0/0

C++ - Leçon 9 Tableaux 7/ de" et sizeof(). Dans tous les autres cas, Pour permettre l'évaluation d'une expression dans laquelle figure le nom d'un vrai tableau, le compilateur remplace ce nom par l'adresse du premier élément du tableau en question. Il n'est donc, en pratique, pas possible d'utiliser réellement les vrais tableaux sans comprendre comment on utilise les pointeurs pour les imiter. - Faux tableaux La clé de la compréhension des rapports entre tableaux et pointeurs réside dans la prise de conscience de la vraie nature des crochets que nous avons utilisés pour encadrer les index dans les exemples précédents. La vérité est que ces crochets ne sont qu'une façon de déréférencer un pointeur, et que leur usage n'est nullement lié à l'existence d'un tableau. En toute rigueur syntaxique, un index entre crochets n'a de sens que s'il est utilisé pour déréférencer un pointeur. Cette notation n'est donc pas simplement "utilisable même en l'absence de vrai tableau", elle est bel et bien "non applicable aux vrais tableaux". C'est seulement grâce à la règle d'évaluation des expressions énoncée ci-dessus que tout se passe comme si nous utilisions les crochets pour accéder aux éléments d'un vrai tableau. Le déréférencement d'un pointeur à l'aide d'un index entre crochets peut être défini d'une façon simple et brutale : Si ptr est un pointeur, et n un nombre entier, alors l'expression ptr[n] est strictement équivalente à *(ptr + n) Cette définition présente toutefois un défaut : sa dernière ligne fait appel à l'addition d'un pointeur et d'une valeur entière, une opération nouvelle pour nous. L'arithmétique des pointeurs Que se passe-t-il lorsqu'on ajoute une valeur entière à un pointeur? La valeur contenue dans un pointeur est une adresse, c'est à dire, fondamentalement, un nombre entier. L'idée d'ajouter une quantité entière à cette valeur n'est donc pas complètement folle, mais elle pose quand même un problème : quel va être le sens de la valeur ainsi obtenue? On comprend bien que le résultat va être une adresse, mais peut-on être certain qu'il s'agit d'une valeur valide pour un pointeur, c'est à dire d'une adresse à laquelle débute la représentation d'une donnée dont le type correspond à celui du pointeur? La réponse à cette question est malheureusement négative. Rien ne permet au compilateur de s'assurer qu'une adresse obtenue par calcul est effectivement celle d'une donnée du type attendu. Il existe en revanche des cas où le compilateur peut être absolument certain que le résultat n'est pas une valeur valide. Imaginons, par exemple, un pointeur sur float contenant une adresse à laquelle débute la représentation d'une valeur de type float. Etant donné qu'une telle représentation occupe plusieurs octets, il est certain que l'adresse suivante n'est pas une valeur admissible pour notre pointeur! La première adresse qui peut éventuellement correspondre au début de la représentation d'une autre valeur de type float est celle qui suit la fin de la représentation de la valeur, et non celle qui suit son début. Toute modification de la valeur contenue dans un pointeur qui serait d'une amplitude inférieure à la taille du type pointé ne pourrait que générer un pointeur invalide. Si le type float occupe quatre octets et que la valeur initiale du pointeur est n, on peut représenter la situation ainsi : Adresse n n + n + n + n+, c'est à dire n + sizeof(float) Commentaire Premier octet de la représentation d'une valeur de type float Suite de la représentation de la valeur de type float Dernier octet de la représentation de la valeur de type float Première adresse où l'on peut espérer voir débuter la représentation d'une autre valeur de type float J-L Péris - 8/0/0

C++ - Leçon 9 Tableaux 8/ En conséquence, lorsqu'on ajoute à un pointeur, ce n'est pas que le langage C++ ajoute à la valeur que ce pointeur contient, mais le nombre d'octets occupé en mémoire par la représentation d'une donnée du type correspondant à celui du pointeur. De même, lorsqu'on ajoute une quantité n à un pointeur, la valeur qu'il contient augmente en fait de n fois la taille du type pointé. Cette façon de procéder aux additions impliquant des pointeurs peut sembler étrange, mais elle se traduit par une conséquence extrêmement bénéfique : tout fonctionne comme si tous les types de données occupaient un seul octet en mémoire, et on peut utiliser les pointeurs sans se préoccuper de la taille des objets qu'ils désignent 7. L'addition d'un nombre entier est certainement la plus importante des opérations arithmétiques pouvant être appliquées aux pointeurs. Il est également possible de calculer la différence entre deux pointeurs du même type : le résultat obtenu est une valeur entière qui correspond aux nombres d'éléments du type concerné qui pourraient être stockés entre les deux adresses. Déréférencer un pointeur à l'aide de crochets Que se passe-t-il lorsque l'on applique un index entre crochets à un pointeur? Comme l'indique la définition "simple et brutale" énoncée plus haut, l'effet obtenu est un peu le même que celui de l'opérateur * : l'expression ainsi constituée n'a pas pour valeur le contenu du pointeur (c'est à dire une adresse), mais le contenu de la zone de mémoire désignée par cette adresse, interprété comme étant une donnée du type correspondant à celui du pointeur. Il existe toutefois une différence, puisque dans le cas des crochets, la zone de mémoire n'est pas désignée directement par l'adresse contenue dans le pointeur. Il faut en effet ajouter l'index au pointeur pour obtenir l'adresse à laquelle est située la valeur que prendra l'expression. L'étoile et les crochets étant deux notations correspondant fondamentalement à la même opération, une expression comportant l'une de ces notations peut toujours être réécrite en utilisant l'autre. Etant donné qu'ajouter une valeur nulle à une adresse ne modifie évidemment pas cette dernière, on peut en particulier dire que : Il est parfaitement indifférent d'écrire *machin ou machin[0]. Ces notations ne sont évidemment acceptables que si machin est un tableau ou un pointeur. Si ces deux notations sont interchangeables, il semble toutefois que, pour de nombreux programmeurs, elles suggèrent des situations différentes : lorsqu'un pointeur est déréférencé à l'aide de la notation indexée, c'est généralement pour accéder à l'une des valeurs d'une série en comportant plusieurs, de type identique. Le code suivant, quoique parfaitement correct d'un point de vue syntaxique, est tout à fait atypique : double unevar = 0; double * unptr = & unevar; unptr[0] =.8; //ok, mais suggère à tort l'existence d'une série de valeurs La connotation inverse est bien moins prononcée, et le fait qu'un pointeur soit déréférencé à l'aide de l'étoile ne doit pas être interprété comme un indice suggérant qu'il n'existe pas une série de données auxquelles le programme va accéder en faisant varier la valeur du pointeur. Puisque les crochets ne sont qu'une nouvelle façon de déréférencer un pointeur, il n'est pas étonnant qu'ils n'offrent pas un degré de protection supérieur à celui proposé par l'opérateur étoile : c'est vous seul qui êtes responsable non seulement de la validité du pointeur, mais aussi du fait que lui ajouter la valeur de l'index utilisé ne conduit pas à accéder à une zone de mémoire située en dehors du tableau (en clair : si le pointeur utilisé contient l'adresse du premier élément, l'index doit rester strictement inférieur au nombre d'éléments). Utilisation d'un pointeur pour accéder à un vrai tableau Après la définition char messageun[] = "Salut, ça va?"; nous savons que, en vertu de la règle énoncée page 7, une instruction telle que 7 Ce qui est fort heureux car, rappelons le, la taille des différents types de données n'est pas la même sur tous les systèmes. S'il fallait tenir compte de cette taille dans le texte source, il deviendrait très difficile d'écrire des programmes pouvant être compilés et fonctionner de façon correcte sur divers ordinateurs. J-L Péris - 8/0/0

C++ - Leçon 9 Tableaux 9/ char lettre = messageun[]; ne peut être exécutée que parce que le compilateur remplace en fait le nom messageun par l'adresse du premier élément. Dans un premier temps, notre code devient donc : char * ptr = & messageun[0]; char lettre = ptr[]; Cette substitution permet de déréférencer le pointeur à l'aide des crochets et, finalement, le code effectivement exécuté ressemble donc plutôt à : char * ptr = & messageun[0]; //le pointeur reçoit l'adresse du er élément ptr = ptr + ; //on ajoute l'index au pointeur char lettre = *ptr; //on accède à la zone correspondant à cette adresse Bien entendu, ces substitutions sont réalisées de façon invisible, lors de la compilation du programme. Le compilateur ne modifie évidemment pas le code source pour y ajouter la déclaration d'une variable ptr dont le programmeur n'a que faire! Si notre code source comporte effectivement un pointeur contenant l'adresse du premier élément du tableau, ce pointeur fournit une alternative au nom du tableau lorsqu'on veut accéder aux éléments de celui-ci. En procédant ainsi, nous ne faisons qu'anticiper sur l'interprétation que fera de toute façon le compilateur : char *monpointeur = & messageun[0]; char lettre = monpointeur[]; //équivaut à char lettre = messageun[]; Bien entendu, dans la mesure où notre tableau possède un nom, on ne voit pas très bien l'intérêt qu'il y aurait à créer une autre variable, de type pointeur, pour accéder aux éléments. Il arrive cependant que nous soyons confrontés à des tableaux dépourvus de nom : un exemple tout à fait banal est celui des chaînes littérales. Nous avons déjà rencontré à plusieurs reprises des expressions du genre "une chaîne de caractères", mais nous avons jusqu'à présent évité de soulever la question de leur type. Il s'agit en fait d'un "tableau (anonyme) de caractères", auquel, selon le contexte, le compilateur pourra substituer l'adresse du premier de ces caractères. Une chaîne littérale peut donc fournir une adresse acceptable comme valeur pour un "pointeur sur char", comme l'illustre l'exemple suivant : char * messagedeux = "Salut, ça va?"; La nuance entre cette approche et celle consistant à écrire char messageun[] = "Salut, ça va?"; est assez subtile puisque, bien que les variables ainsi créées ne soient pas du même type (l'une est un tableau contenant des caractères alors que l'autre n'est qu'un pointeur contenant l'adresse d'une zone de mémoire anonyme qui, elle, contient les caractères), l'usage qu'on peut en faire est, à de rares cas particuliers près, rigoureusement identique. Une différence évidente entre messageun et messagedeux est la valeur que produirait sizeof() s'il leur était appliqué. Il est par ailleurs possible que certains compilateurs interdisent la modification de messagedeux, alors que le contenu de messageun peut varier. Création de faux tableaux par allocation dynamique Déréférencer un pointeur à l'aide de la notation indexée n'a guère d'intérêt que s'il existe une série de valeurs auxquelles on va pouvoir accéder en faisant varier la valeur de l'index. Si, comme nous venons de le voir, cette condition peut être réalisée en stockant dans le pointeur l'adresse du premier élément d'un (vrai) tableau, cette technique reste d'un intérêt limité, et il est généralement nécessaire de pouvoir créer des séries de données indépendamment de l'existence de vrais tableaux. L'opérateur new [] permet de réserver une zone mémoire d'une taille adéquate à la constitution d'une telle série. Il suffit pour cela de préciser, entre les crochets, le nombre d'éléments que doit comporter la série : double * ptr; ptr = new double[]; L'usage d'une allocation dynamique entraîne bien entendu les conséquences habituelles : il faut tout d'abord s'assurer que new [] a été en mesure de réserver la mémoire requise, et il ne J-L Péris - 8/0/0

C++ - Leçon 9 Tableaux 0/ faut pas oublier de libérer cette mémoire lorsqu'on n'en a plus besoin. La mémoire allouée par new [] doit être libérée par delete[] 5 6 7 8 9 0 5 6 7 Ces conditions étant remplies, le pointeur peut-être utilisé pratiquement comme s'il s'agissait d'un vrai tableau : double * ptr; ptr = new double[]; if(ptr!= NULL) { int i; for (i=0 ; i < ; i = i + ) ptr[i] = * i; //ne dirait-on pas qu'il s'agit d'un tableau? utilise(ptr); //appel d'une fonction qui fait quelque chose d'intéressant delete[] ptr; } L'opérateur delete[] doit systématiquement être utilisé lorsque la mémoire a été allouée par new[]. A la différence de new[], delete[] n'a besoin d'aucune spécification de taille, et son couple de crochets reste donc toujours vide. Le fait que la valeur du pointeur soit obtenue par allocation dynamique n'a pas pour seules conséquences la nécessité de tester sa validité avant de le déréférencer et de libérer explicitement la mémoire qui lui a été attribuée. Un des avantages déterminants de ces faux tableaux par rapport aux vrais est que leur taille peut être choisie lors de l'exécution du programme. Imaginons qu'il existe une fonction nommée demandetaille() qui instaure un dialogue avec l'utilisateur et renvoie le nombre de données que celui-ci désire traiter. On peut alors écrire : int taille = demandetaille(); double * ptr = new double[taille]; if(ptr!= NULL) { utilise(ptr); //appel d'une fonction qui fait quelque chose d'intéressant delete[] ptr; } 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'opérateur sizeof() est incapable de déterminer la taille d'un faux tableau. En effet, la variable que nous manipulons n'est qu'un pointeur, pas un tableau. Lui appliquer l'opérateur sizeof()permet de vérifier que le système utilisé représente les adresses sur bits, soit quatre octets, mais ne dit absolument rien sur le nombre de données présentes dans le faux tableau. D'autre part, Il est impossible d'initialiser un faux tableau. Il faut donc recourir à des opérations d'affectation pour attribuer leur première valeur aux éléments d'un faux tableau. Remarquez que, de toute façon, si vous ignorez la taille qu'aura effectivement le faux tableau, vous seriez bien en peine de fournir une liste correcte de valeurs initiales - Les tableaux comme paramètres de fonctions Nous avons déjà souligné (page 7) que "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 élément du tableau en question". Une des conséquences de cette règle est que, de fait, Il est définitivement impossible qu'une fonction reçoive comme paramètre un vrai tableau. J-L Péris - 8/0/0

C++ - Leçon 9 Tableaux / La fonction reçoit un "pointeur sur ", et non un vrai tableau Imaginons que nous disposions d'un vrai tableau d'int que nous souhaitons communiquer à une fonction nommée sommetab() qui serait chargée de faire l'addition de toutes les valeurs qu'il contient. Nous écrivons donc quelque chose comme : int montab[] = {0, 5,,,, 8}; int total = sommetab(montab); Lorsque le programme est sur le point d'appeler la fonction somme(), il lui faut évaluer l'expression figurant entre les parenthèses, de façon à déterminer la valeur qui doit être utilisée pour "initialiser" le paramètre de la fonction. Comme cette expression est le nom d'un vrai tableau, la règle énoncée page 7 entre en vigueur, et la valeur que reçoit la fonction est donc l'adresse du premier élément du tableau, c'est à dire l'adresse d'un int. La fonction sommetab() doit donc disposer d'un paramètre de type "pointeur sur int". La fonction ne peut pas déterminer la taille du faux tableau 5 6 7 8 Ce pointeur peut tout à fait être utilisé pour accéder aux valeurs contenues dans le tableau. Il suffit pour cela de le déréférencer (soit au moyen de la notation indexée, soit au moyen de l'étoile). Un léger problème se pose toutefois : ce pointeur n'est pas un vrai tableau, et la fonction est donc incapable de déterminer combien il y a d'éléments. Il nous faut donc trouver un moyen de communiquer la taille du tableau à la fonction, et plusieurs solutions sont envisageables : réserver le premier élément du tableau à cet usage, utiliser une valeur "impossible" pour signaler la fin des données 8, ou utiliser un second paramètre pour indiquer la taille du tableau. Le fait que l'opérateur sizeof() soit incapable de déterminer la taille d'un faux tableau n'est pas un détail secondaire. Comme nous le voyons ici, combiné avec le fait que les fonctions ne peuvent recevoir que de faux tableaux, il donne naissance à des contraintes dont la levée est une des raisons d'être du langage C++. Dans l'absolu, la meilleure réponse au problème soulevé par notre exemple est "Ne stockez pas vos données dans un tableau". Etant donnés la nature du langage et le contexte historique (il reste encore beaucoup de librairies et de programmeurs C) il ne semble pourtant pas réaliste de chercher à maîtriser les techniques plus évoluées avant d'avoir bien compris les mécanismes élémentaires qui les sous-tendent. Si nous adoptons la dernière des solutions envisagées ci-dessus, notre fonction somme() peut être définie ainsi : int sommetab(char * tab, int nbelements) { int index; int somme = 0; for (index = 0 ; index < nbelements ; index = index + ) somme = somme + tab[index]; return somme; } et elle pourra être invoquée à l'aide de l'instruction int total = sommetab(montab, sizeof(montab)/sizeof(montab[0]); Remarquez que la simple mention du nom du tableau suffit à assurer la transmission d'une adresse (en vertu de la règle d'évaluation des expressions comportant des noms de tableaux). L'appel de la fonction sommetab() ne nécessite donc aucun usage de l'opérateur "adresse de". La réalité du processus peut être mieux explicitée en écrivant int total = sommetab(&(montab[0]), sizeof(montab)/sizeof(montab[0]); ce qui met bien en évidence la transmission de l'adresse du premier élément du tableau. La plupart des programmeurs estiment cependant que, loin de les simplifier, cette façon de s'exprimer complique encore les choses. 8 Si, par exemple, les données ne peuvent logiquement être que positives, on peut utiliser une valeur négative pour indiquer la fin de la série. J-L Péris - 8/0/0

C++ - Leçon 9 Tableaux / Autres conséquences de la transmission d'une adresse 5 6 7 8 9 Le fait que la fonction reçoit l'adresse du tableau la rend capable de modifier le contenu du tableau. Selon le rôle joué par la fonction, il s'agit là soit d'un avantage, soit d'une source d'erreurs que nous préférerions éviter. D'autre part, étant donné que la valeur qui lui est transmise est de toutes façons une adresse, une même fonction peut indifféremment traiter des vrais et des faux tableaux. Notre fonction sommetab() pourrait tout à fait être utilisée ainsi : int taille = 0; int * fauxtab = new int[taille]; if (fauxtab!= NULL) {//affecte des valeurs aux éléments (l'initialisation est impossible ) int index; for (index=0 ; index < taille ; index = index + ) fauxtab[index] = index; sommetab(fauxtab,taille); //calcule la somme } Un faux tableau étant, par définition, un pointeur, il ne pose évidemment aucun problème à une fonction qui attend une adresse pour initialiser son paramètre! Cette très agréable indifférence des fonctions à l'authenticité des tableaux qui leurs sont fournis est malheureusement limitée au cas des tableaux unidimensionnels, comme vous le constaterez si vous étudiez l'annexe : Tableaux multidimensionnels. Notations malsaines Pour des raisons historiques, le paramètre d'une fonction qui reçoit un "pointeur sur " peut également être déclaré à l'aide d'une notation utilisant les crochets. Ainsi, notre fonction sommetab[] pourrait non seulement être déclarée int sommetab(int *tab, int nb); mais aussi int sommetab(int tab[], int nb); ou même int sommetab(int tab[0], int nb); //ma version préférée //un bluff éhonté //un délire total Ces deux dernières notations présentent le grave défaut de laisser supposer que la fonction reçoit comme paramètre un (vrai) tableau, ce qui est totalement inexact. Le fait d'utiliser l'une ou l'autre de ces déclarations ne change rien à la nature du paramètre transmis, qui reste inexorablement un simple pointeur, comme le prouvent les trois constatations suivantes. ) Aucune des trois formes de déclaration possibles ne rend le compilateur capable de rejeter une séquence du type : int * ptr; int total = sommetab(ptr, ); //le pointeur ptr n'est même pas valide! Le compilateur n'exige donc jamais que le premier paramètre transmis à sommetab() soit un tableau de 0 entiers. Il n'est même pas nécessaire qu'il s'agisse réellement d'un tableau : un simple "pointeur sur int" convient, même s'il est dépourvu de contenu valide! Dans ces conditions, prétendre que ce paramètre est autre chose qu'un pointeur apparaît clairement comme une promesse qui ne sera pas tenue. ) Quelle que soit la déclaration adoptée, si la fonction applique l'opérateur sizeof() à la valeur qu'elle reçoit comme premier argument, elle pourra en déduire brillamment la taille d'un pointeur, mais elle n'aura jamais accès à la taille du tableau de cette façon. D'ailleurs, si la troisième de ces déclarations signifiait réellement ce qu'elle prétend, pourquoi serait-il nécessaire de passer un second paramètre? ) Ces trois versions de la déclaration sont en fait parfaitement équivalentes du point de vue du compilateur. Ceci est illustré de façon spectaculaire par le fait la fonction peut être déclarée trois fois dans le même programme, en utilisant les trois versions de la déclaration, et ce sans provoquer la J-L Péris - 8/0/0

C++ - Leçon 9 Tableaux / moindre protestation du type "redeclaration is not identical" de la part du compilateur 9. Déclarer la fonction (dans un fichier.h) en utilisant l'une de ces formes n'oblige d'ailleurs nullement à utiliser la même forme en tête de définition (dans le.cpp). Vous pouvez penser ce que vous voulez du fait que le nom d'un tableau est "dégradé" en l'adresse de son premier élément lorsque les expressions sont évaluées, mais vous n'y changerez rien en utilisant des notations qui masquent ce fait incontournable. Vous risquez tout au plus de l'oublier, ce qui vous empêchera d'en tirer parti lorsque c'est un avantage, et en aggravera les conséquences lorsque c'est un inconvénient. - Bon, c'est gentil tout ça, mais ça fait déjà pages. Qu'est-ce que je dois vraiment en retenir? ) Il existe deux sortes de tableaux, les vrais et les faux. ) Les faux tableaux ne sont que des pointeurs rendus valides par le fait qu'ils contiennent l'adresse d'un bloc de mémoire réservé à l'aide de new[]. ) Lorsqu'ils cessent d'être utiles, les faux tableaux doivent être détruits avec delete[]. ) Les crochets ([ et ]) sont un moyen de déréférencer un pointeur : on ajoute au pointeur la valeur de l'index figurant entre les crochets, puis on accède à l'adresse ainsi obtenue. 5) La seule chose que le langage sache faire avec un vrai tableau, c'est donner sa taille. 6) Le langage masque son incapacité à travailler réellement sur les vrais tableaux en utilisant secrètement l'adresse de leur premier élément. Ceci lui permet de faire semblant d'accéder aux éléments d'un vrai tableau à l'aide des crochets. 7) Cette imposture s'écroule (notamment) dès qu'un tableau est passé comme argument à une fonction : celle-ci doit se contenter du simple pointeur qu'elle reçoit. 8) Les vrais amis n'essaient pas de vous faire prendre de faux tableaux pour des vrais. 5 - J'ai rien compris, est-ce que quelqu'un d'autre pourrait m'expliquer ça un peu plus clairement? Si les opérateurs new[] et delete[] sont spécifiques à C++, tous les autres points abordés dans cette Leçon relèvent du C le plus standard. La meilleure référence en la matière reste, à mon avis, l'ouvrage de Harbison et Steele déjà cité. Si vous avez survécu à la Leçon 7, ce n'est sans doute pas new[] et delete[] qui risquent de vous impressionner, puisque leur usage est tout à fait analogue à celui de new et delete. Le manuel d'ivor Horton traite des tableaux dans le chapitre, qui comporte également l'introduction des notions de pointeur, d'allocation dynamique et de référence. Comme le traitement des tableaux aborde aussi bien les vrais que les faux tableaux et expose de plus l'usage de ces deux types de tableaux dans leur version multidimensionnelle 0, il faut avouer que les 9 pages du chapitre paraissent un peu brèves (d'autant qu'elles ne sont pas exemptes d'erreurs, telles que la confusion entre "heap" et "free store" ou le silence pudique sur le type des variables recueillant, page 69, les adresses fournies par new[]). Le passage de tableaux comme arguments à des fonctions est traité tout aussi rapidement (pages 87 à 90), et la traduction en "français" n'ajoute rien à la clarté de la chose. 6 - Pré-requis de la Leçon 0 La Leçon 0 traite d'un sujet voisin de celui de la Leçon 9, puisqu'elle aborde une autre façon d'organiser les données, qui peut parfois constituer une alternative aux tableaux. Plutôt que de constituer un pré-requis l'une pour l'autre, ces deux Leçons devraient s'éclairer mutuellement : voir comment on peut NE PAS créer un tableau est aussi un bon moyen de comprendre ce qu'est un tableau. 9 Rappel : un objet ne peut être défini qu'une fois, mais il peut faire l'objet de déclarations multiples, à condition que celles-ci soient toutes équivalentes (cf. Leçon 5, page 5). 0 Pour les tableaux multidimensionnels, vous pouvez également consulter l'annexe. J-L Péris - 8/0/0