28
Chapitre II – Débuter avec C++ Historique du C et du C++ [Deitel et Deitel, Comment Programmez en C++. Eyrolles, 2 ième édition, 2000, Section 1.7, pp. 9-10] " Le C++ a été développé à partir du C qui fut lui-même élaboré à partir de deux langages de programmation antérieurs, le BCPL et le B. Le BCPL fut développé en 1967 par Martin Richards comme langage d’écriture de logiciels de systèmes d’exploitation et de compilateurs. Ken Thompson modela plusieurs particularités de son langage B à partir de leurs équivalents en BCPL. En 1970, il utilisa le langage B pour créer des versions primitives du système d’exploitation UNIX aux laboratoires Bell sur un ordinateur DEC PDP-7. Le BCPL et le B étaient des langages « non typés »; chaque élément de données occupait un « mot » en mémoire et le fardeau de traiter un élément comme un nombre entier ou comme un nombre réel, par exemple, incombait au programmeur. Le langage C a été développé à partir du B par Dennis Ritchie aux laboratoires Bell et a été exécuté pour la première fois en 1972 sur un ordinateur DEC PDP- 11. Le C utilise de nombreux concepts importants des langages BCPL et B tout en incorporant le typage des données et d’autres fonctionnalités. Le C s’est d’abord répandu comme langage de développement du système d’exploitation UNIX et, aujourd’hui, la majorité des systèmes d’exploitation sont écrits en C et (ou) en C++. Au cours des deux dernières décennies, le C est en effet devenu disponible pour la plupart des ordinateurs, car il est indépendant du matériel. En utilisant un design soigné, il est possible d’écrire des programmes C portables sur la plupart des machines. À la fin des années 1970, le C avait évolué en ce qu’on désigne maintenant par les vocables de « C conventionnel », « C classique » ou « C de Kernighan et Ritchie ». En 1978, la publication chez Prentice-Hall du livre The C Programming Language de Kernighan and Ritchie attira l’attention générale sur ce langage. L’emploi très répandu du C sur différents types d’ordinateurs (parfois appelés plates-formes matérielles) amena hélas de nombreuses variantes, similaires mais souvent incompatibles. Cette situation était un problème sérieux pour les développeurs chargés d’écrire des programmes portables capables de fonctionner sur différentes plates-formes, d’où la nécessité évidente d’une version standard du langage C. En 1983, l’American National Standards Committee on Computers and Information Processing (X3) créait le comité technique X3J11 afin de « fournir une définition du langage claire et indépendante de la machine ». Le standard élaboré fut approuvé en 1989. L’ANSI travailla ensuite de pair avec l’International Standards Organization (ISO) pour normaliser le langage C à - 1 -

Dbuter avec C++ - ift.ulaval.cadupuis/Programmation orientee objets/Chap. II... · Le C++, une extension du C, a été développé au début des années 1980 par Bjarne Stroustrup

  • Upload
    ngominh

  • View
    214

  • Download
    0

Embed Size (px)

Citation preview

Chapitre II – Débuter avec C++

Historique du C et du C++

[Deitel et Deitel, Comment Programmez en C++. Eyrolles, 2ième édition, 2000, Section 1.7, pp. 9-10]

" Le C++ a été développé à partir du C qui fut lui-même élaboré à partir de deux langages de programmation antérieurs, le BCPL et le B. Le BCPL fut développé en 1967 par Martin Richards comme langage d’écriture de logiciels de systèmes d’exploitation et de compilateurs. Ken Thompson modela plusieurs particularités de son langage B à partir de leurs équivalents en BCPL. En 1970, il utilisa le langage B pour créer des versions primitives du système d’exploitation UNIX aux laboratoires Bell sur un ordinateur DEC PDP-7. Le BCPL et le B étaient des langages « non typés »; chaque élément de données occupait un « mot » en mémoire et le fardeau de traiter un élément comme un nombre entier ou comme un nombre réel, par exemple, incombait au programmeur. Le langage C a été développé à partir du B par Dennis Ritchie aux laboratoires Bell et a été exécuté pour la première fois en 1972 sur un ordinateur DEC PDP-11. Le C utilise de nombreux concepts importants des langages BCPL et B tout en incorporant le typage des données et d’autres fonctionnalités. Le C s’est d’abord répandu comme langage de développement du système d’exploitation UNIX et, aujourd’hui, la majorité des systèmes d’exploitation sont écrits en C et (ou) en C++. Au cours des deux dernières décennies, le C est en effet devenu disponible pour la plupart des ordinateurs, car il est indépendant du matériel. En utilisant un design soigné, il est possible d’écrire des programmes C portables sur la plupart des machines. À la fin des années 1970, le C avait évolué en ce qu’on désigne maintenant par les vocables de « C conventionnel », « C classique » ou « C de Kernighan et Ritchie ». En 1978, la publication chez Prentice-Hall du livre The C Programming Language de Kernighan and Ritchie attira l’attention générale sur ce langage. L’emploi très répandu du C sur différents types d’ordinateurs (parfois appelés plates-formes matérielles) amena hélas de nombreuses variantes, similaires mais souvent incompatibles. Cette situation était un problème sérieux pour les développeurs chargés d’écrire des programmes portables capables de fonctionner sur différentes plates-formes, d’où la nécessité évidente d’une version standard du langage C. En 1983, l’American National Standards Committee on Computers and Information Processing (X3) créait le comité technique X3J11 afin de « fournir une définition du langage claire et indépendante de la machine ». Le standard élaboré fut approuvé en 1989. L’ANSI travailla ensuite de pair avec l’International Standards Organization (ISO) pour normaliser le langage C à

- 1 -

l’échelle mondiale; le document collectif du standard fut publié en 1990 et porte l’identification ANSI/ISO 9899 : 1990. On peut commander des copies de ce document auprès de l’ANSI. La deuxième édition du Kernighan et Ritchie, publiée en 1988, est conforme à cette version appelée ANSI C. Le C++, une extension du C, a été développé au début des années 1980 par Bjarne Stroustrup aux laboratoires Bell. Le C++ procure une quantité de fonctionnalités qui rehaussent le langage C et, de surcroît, offre des possibilités pour la programmation orientée objets."

Fichiers utilisés en C++

.h Fichier d’en-tête (header). Ces fichiers textes renferment uniquement des

déclarations. On peut inclure un fichier d’en-tête défini par le programmeur en utilisant la

directive de précompilation #include.

.cpp Fichier source. Ces fichiers textes contiennent le code des programmes.

.obj Fichier résultant de la compilation.

.lib Fichier librairie. Une librairie regroupe plusieurs fichiers « .obj ».

.exe Fichier exécutable généré par l’édition des liens.

Différences entre compilateurs Plusieurs compilateurs ne respectent pas le standard C++ en un ou plusieurs points. Voici quelques incompatibilités classiques. Les fichiers d’entête système de certains vieux compilateurs possèdent une extension .h, par exemple : #include <iostream.h> Ajouter simplement un .h ne fonctionne toutefois pas pour tous les fichiers inclus. Par exemple, en C++ standard, vous pouvez inclure la directive #include <string> ; la

- 2 -

directive #include <string.h> n’inclut pas les chaînes C++, mais les chaînes de style C, totalement différentes et moins utiles. Un autre fichier d’entête courant contient des fonctions mathématiques. En C++ standard, vous employez la directive #include <cmath>. Avec des compilateurs plus anciens, vous employez à la place #include <math.h>. Les anciens compilateurs ne prennent pas en charge les espaces de noms. Dans ce cas, omettez la directive using namespace std;

Différences entre le C et le C++

Les programmes C sont des programmes C++ à quelques exceptions près.

⇒ On peut continuer à utiliser nos fonctions C dans les nouveaux programmes C++.

⇒ Le passage au langage orienté objet s’est fait en douceur. Différences : - C : une variable locale doit être déclarée en tête de bloc. C++ : cette règle ne s’applique plus.

- C++ : la déclaration d’une structure correspond obligatoirement à la définition d’un nouveau type de donnée.

⇒ l’opérateur typedef n’est plus nécessaire.

- C++ : Un prototype de fonction doit contenir la définition des arguments de la fonction.

C : Certains compilateurs C ne l’exigent pas. - C : Un pointeur de type void * peut être converti implicitement lors

d’une affectation en un pointeur d’un autre type. C++ : Ce n’est pas le cas; on peut faire appel à l’opérateur de « cast ». - C++ : Une fonction sans valeur de retour se définit et se déclare à l’aide

du mot clé void. C : Le mot clé void est facultatif.

- 3 -

Mots-clés C et C++ auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while Mots-clés exclusifs au C++ asm bool catch class const_cast delete dynamic_cast explicit false friend inline mutable namespace new operator private protected public reinterpret_cast static_cast template this throw true try typeid typename using virtual wchar_t

Généralités sur le C++

Aucune contrainte particulière au niveau des espaces et des sauts de ligne.

⇒ « aérer » votre programme. La différence est faite entre les majuscules et les minuscules. Toutes les instructions doivent se terminer par un « ; ». Le fichier d’en-tête iostream.h est nécessaire à l’utilisation des fonctions

d’affichage à l’écran (le flux de sortie standard cout) et de saisie au clavier (le flux d’entrée standard cin).

#include <iostream.h> int main()

Ex. :

{ int i; cin >> i; cout << "Premier essai :" << i; return 0;

}

- 4 -

Fonctions

La bibliothèque standard du C++ offre une riche collection de fonctions pour effectuer les calculs mathématiques courants, les manipulations de chaînes de caractères, les entrées et les sorties, la vérification des erreurs, etc. Ces fonctions font partie intégrante de l’environnement de programmation du C++. Leur emploi accroît la portabilité des programmes; de plus, il est difficile d’améliorer leur performance. La conception des programmes en C++ consistera typiquement à combiner les nouvelles fonctions écrites par le programmeur avec des fonctions déjà disponibles dans la bibliothèque standard. Les instructions définissant chaque fonction ne sont écrites qu’une seule fois et sont cachées (masquées) ailleurs. Ce « masquage » des détails d’implantation favorise une bonne conception des logiciels. Les fonctions offrent plusieurs avantages :

- l’approche « diviser pour mieux régner » améliore la maniabilité du développement du programme

- la réutilisation du logiciel : l’utilisation de fonctions existantes comme blocs de construction pour créer de nouveaux programmes.

- éviter la répétition du code dans un programme par simple appel à la fonction.

Chaque fonction devrait se limiter à effectuer une tâche unique et bien définie. Le nom de la fonction devrait exprimer cette tâche avec clarté. Considérons maintenant la définition d’une fonction (syntaxe) : type_de_valeur_renvoyée Nom_de_la_fonction

( type_de_x argument_x, …… type_de_z argument_z) { déclarations et instructions formant le corps de la fonction (bloc) } type_de_ valeur_renvoyée correspond à :

- un type standard de C++ - un type de donnée créé par le programmeur - une structure - une classe - void (si la fonction ne renvoie rien)

Note : (i) C++ n’impose pas de récupérer la valeur retournée par une fonction.

(ii) Si la fonction ne possède pas de paramètre, C++ n’indique rien entre les parenthèses ou le mot clé void. Une liste de paramètres de fonction vide en

- 5 -

C++ a une signification tout à fait différente d’une telle liste en C. En C, elle signifie que la vérification des arguments est désactivée; autrement dit, l’appel de fonction peut passer les arguments qu’il veut. En C++, elle signifie que la fonction ne prend aucun argument. Les programmes en C utilisant cette caractéristique pourraient donc signaler des erreurs de syntaxe lors de leur compilation en C++.

Exemple :

#include <iostream.h> // Définition de la fonction int cube(int m) { return m * m * m; } int main() { for (int i = 1; i <= 10; i++) cout << cube(i) << “ “; cout << endl; return 0;

}

Définition d’un prototype de fonction

L’une des caractéristiques les plus importantes du C++ est le prototype de fonction. Cela indique au compilateur le nom de la fonction, le type de données renvoyé par la fonction, le nombre de paramètres que la fonction s’attend à recevoir, les types des paramètres et l’ordre dans lequel ces paramètres devraient se suivre. Le compilateur utilise les prototypes de fonctions pour valider les appels de fonction. Ils sont requis en C++; vous pouvez utiliser les directives de précompilateur #include pour obtenir des prototypes de fonctions de la bibliothèque standard ou des prototypes de fonctions personnelles à partir de fichiers d’en-tête. Comme nous l’avons vu précédemment, un prototype de fonction n’est pas requis si la définition de la fonction apparaît avant la première utilisation de cette fonction; le cas échéant, la définition de la fonction sert également de prototype de fonction. Exemple :

#include <iostream.h> int cube(int n); // Prototype de la fonction

- 6 -

int main() { for (int i = 1; i <= 10; i++) cout << cube(i) << “ “; cout << endl; return 0; } // Définition de la fonction int cube(int m) { return m * m * m; }

En plus de fournir au compilateur les caractéristiques d’une fonction, cela donne aussi l’occasion de donner la spécification d’une fonction. Exemple :

float Racine_carree(float x); /* Permet de calculer la racine carrée d’un nombre réel. Pré - x > 0 Post - Retourne la racine carrée de la valeur du paramètre x. */

Note : Même si le compilateur l’ignore, il est préférable d’utiliser des noms de

paramètres dans les prototypes de fonctions à des fins de documentation.

Fichiers d’en-tête

Contiennent notamment les prototypes des fonctions standards que vous

appelez. Permettent de séparer la spécification d’un type de donnée ou d’une classe de

son implantation.

Ils ne contiennent pas le corps des fonctions, uniquement des déclarations.

Renferment :

- d’autres fichiers d’en-tête, - les prototypes des fonctions - la déclaration de variables externes

- 7 -

- des types de données - les déclarations de structures, - les déclarations de classes.

Précompilateur C++

L’une des directives de précompilation est : #include <nom_du_fichier.h> permet d’inclure un fichier d’en-tête i.e. remplace cette ligne par le contenu du fichier. Le fichier est recherché dans les répertoires par défaut. Ex. : #include <iostream.h>

indique au précompilateur d’inclure le contenu du fichier d’en-tête du flux d’entrée /sortie iostream.h dans le programme. Ce fichier doit être inclus dans tout programme qui dirige des données de sortie vers l’écran ou qui reçoit des données d’entrée à partir du clavier au moyen d’un flux d’entrée/sortie de style C++.

Pour inclure nos propres fichiers d’en-tête, la syntaxe est : Ex. : #include "Polygone_2D.h"

Le fichier d’en-tête doit être dans le même répertoire que le fichier source qui a utilisé la commande include.

Note : (1) Il ne faut pas inclure le même fichier plusieurs fois.

(2) Ces techniques permettent de définir une architecture de projets qui correspond souvent à la complexité des développements.

- 8 -

Velo Bateau_a_vapeur Automobile

Programme d’application

Vehicule

Un exécutable, construit à partir de plusieurs fichiers sources, doit respecter les règles suivantes :

- un et un seul des fichiers doit contenir la fonction main() - chaque fichier du projet « .cpp » doit être compilé séparément, dans

l’ordre du projet (règle de prédéclaration respectée).

- 9 -

Diriger le flux de données vers l’objet du flux de sortie standard cout normalement associé à l’écran

Ex. : cout << "Bienvenue au C++!\n"; où - << désigne l’opérateur d’insertion de flux,

- la valeur à droite de l’opérateur est insérée dans le flux de sortie lors de l’exécution du programme,

- normalement, les caractères de l’opérande droit s’affichent exactement comme ils apparaissent entre les guillemets, sauf s’il s’agit de caractères qui ne sont pas affichables. La barre oblique inverse (\) désigne un caractère de changement de code et indique de sortir un caractère spécial.

Séquence de Description changement de code \n Nouvelle ligne. Positionne le curseur au début de la

ligne suivante. \t Tabulation horizontale. Déplace le curseur à la

tabulation suivante. \r Retour de chariot. Positionne le curseur au début de la

ligne courante; ne passe pas à la ligne suivante. \a Alerte. Active la sonnerie du système. \\ Barre oblique inverse. Utilisée pour afficher un

caractère de barre oblique inverse. \ " Guillemets. Sert à afficher un caractère de guillemets.

Diriger le flux de données vers l’objet du flux d’entrée standard cin normalement associé au clavier

Exemple : int entier1, entier2; cout << "Entrez le premier entier\n"; cin >> entier1; cout << "Entrez le second entier\n"; cin >> entier2; cout << "La somme est " entier1 + entier2 << endl; endl est un manipulateur de flux; il produit une nouvelle ligne, puis vide le tampon de sortie. endl force donc toute sortie accumulée à s’afficher immédiatement. Dans l’exemple précédent, on illustre aussi l’emploi de multiples opérateurs << dans une seule instruction. La concaténation de flux est possible; il n’est pas nécessaire d’utiliser des instructions de sortie multiples pour produire plusieurs parties de données.

- 10 -

Variables et opérateurs

Les déclarations de variables peuvent être placées à peu près n’importe où dans une fonction mais doivent apparaître avant que la variable ne soit utilisée dans le programme.

float pi = 3.14159; int jour = 21, mois = 9, annee; Nommage des variables

- N’importe quelle série de caractères formée de lettres, de chiffres ou de caractères de soulignement ( _ ) mais ne commençant pas par un chiffre.

- Pas de norme, adopter une convention et s’y tenir. - Les mots réservés du C++ ne peuvent pas être utilisés comme noms de

variables. - Le C++ interprète différemment les majuscules et les minuscules. - Utiliser des noms de variables évocateurs pour rendre votre programme

plus facile à lire. - Éviter d’utiliser des identificateurs commençant par des caractères de

soulignement car le compilateur C++ utilise parfois des noms de ce genre pour ses propres tâches internes.

Type de variable Famille Taille Intervalle de valeurs char Caractère 8 bits -128 à 127 unsigned char Octet 8 bits 0 à 255 short int Entier 16 bits -32 768 à 32 767 unsigned short int Entier 16 bits 0 à 65 535 int Entier 16 bits -32 768 à 32 767 unsigned int Entier 16 bits 0 à 65 535 long int Entier 32 bits -2 147 483 648 à 2 147 483 647 unsigned long int Entier 32 bits 0 à 4 294 967 295 float Réel 32 bits 3.4 e -38 à 3.4e+38 double Réel 64 bits 1.7e-308 à 1.7e+308 long double Réel 80 bits 3.4e-4932 à 1.1e+4932 bool Booléen 8 bits false ou true Ex. : bool Etat; Etat = false; ….. cout << "Vaut 0 ou 1" << Etat;

// Le type bool est souvent utilisé : bool Pile_pleine();

- 11 -

Opérateur sizeof : Permet d’obtenir l’espace occupé par une variable ou un type de donnée (octet). Ex. : short i = 2; cout << sizeof(i); cout << sizeof(long);

Portée des identificateurs et mémorisation

La portée d’un identificateur est l’endroit où l’on peut référencer l’identificateur dans un programme. Certains identificateurs peuvent être référencés dans la totalité d’un programme; d’autres ne peuvent l’être que dans certaines parties d’un programme. Un autre attribut associé à un identificateur est la période durant laquelle l’identificateur existe en mémoire.

Variables locales

Ces variables sont créées lors de l’entrée du bloc dans lequel elles sont déclarées et détruites lors de la sortie du bloc. Ces variables sont utilisables dans la portée où elles sont définies i.e. à l’intérieur du bloc ou de la fonction où elles sont déclarées; elles sont détruites lorsqu’on sort de sa portée.

En C++, contrairement à C, ce n’est plus nécessaire de définir les variables locales en tête de bloc. Ex. : #include <iostream.h>

int cube(int n); // Prototype de la fonction void main() { for (int i = 1; i <= 10; i++) { int j; cout << cube(i) << “ “; j = i; }; cout << i << endl; // cout << j << endl; n’est pas autorisée. } int cube(int m) { return m * m * m; }

- 12 -

Note : Le mot-clé auto déclare explicitement des variables locales qui n’existent que dans le corps de la fonction dans laquelle la définition apparaît. Les variables locales ont, par défaut, cette propriété; c’est pourquoi, le mot clé auto est rarement utilisé.

Arguments de fonction :

- cas particulier de variables locales où la portée est toujours le corps de la fonction.

- Seule différence : elles sont initialisées de manière différente à chaque appel de la fonction.

Variable registre :

- indique au compilateur que cette variable locale ou paramètre de fonction doit être de préférence stockée dans un registre haute vitesse de la machine, s’il en existe un, disponible.

- Permet d’améliorer la performance en éliminant la surcharge causée par le

stockage répétitif des variables de la mémoire vers les registres et le chargement correspondant des résultats en mémoire.

Ex . : register int compteur = 1;

Variable statique :

- Les variables locales déclarées avec le mot-clé static ne sont encore connues qu’à l’intérieur de la fonction dans laquelle elles sont définies, mais, elles conservent leurs valeurs lors de la sortie de la fonction.

- Elle n’est pas détruite lorsque le programme quitte la fonction.

- Elle conserve sa valeur entre les différents appels de cette fonction.

- Si vous ne les initialisez pas, elles prendront la valeur 0.

- Peut être utilisé pour connaître le nombre de fois où vous appelez une

fonction (compteur de fonction).

Ex. : static int compteur = 1;

Variables globales

- définies en dehors des fonctions

- 13 -

- portée : le fichier source où elles sont déclarées.

- Les variables globales conservent leurs valeurs pendant toute l’exécution du programme.

- Seules les fonctions qui sont définies après cette variable pourront

l’utiliser. - La mise à jour des variables globales doit se faire avec des méthodes bien

spécifiées.

Ex. : #include <iostream.h> int total = 0; // Variable globale

int cube(int n); // Prototype de la fonction void main() {

for (int i = 1; i <= 10; i++) { total = total + cube(i); }; cout << total << endl;

}

int cube(int m) {

return m * m * m; }

Variables et fonctions externes : - Par défaut, les variables globales et les noms de fonctions ont l’attribut extern. - Manipulables par toutes les fonctions définies dans le même fichier source à la suite

de leurs déclarations ou définitions dans le fichier. - Puisqu’un projet peut contenir plusieurs sources, le mot clé extern est utilisé quand

une même variable est partagée par plusieurs fichiers d’un même projet. - À éviter car le fichier source qui renferme la définition de la variable devrait

contenir toutes les fonctions qui manipulent cette variable. - Préférable d’opter pour un bon découpage, les fichiers d’en-tête et la commande

#include.

- 14 -

Ex. : float x; fichier gestion.cpp

extern float x; fichier Application_gestion.cpp

Conversion de types

Consiste à modifier le type de données stockées dans une variable.

- Conversion implicite

La conversion est traitée automatiquement par le compilateur. Ex. : char c = 65; int i = 'A';

- Conversion explicite

La conversion est forcée en utilisant l’opérateur de cast i.e. il s’agit d’indiquer entre parenthèses le type pour lequel vous souhaitez transformer une variable. Ex. : float f1 = 10.4; float f2 = (long int) f1; Le C++ offer l’opérateur de forçage de type unaire static_cast<type de donnée> (opérande) pour effectuer une conversion explicite. Ex. : float moyenne; int total, compteur; …….. moyenne = static_cast<float>(total) / compteur; Dans cet exemple, le résultat du calcul de total / compteur donne un entier puisque les 2 variables sont entières et la partie fractionnaire est perdue. L’opérateur de forçage crée une copie temporaire à virgule flottante de son opérande entre parenthèses, total. La valeur stockée dans total est toujours un entier. Le calcul est maintenant constitué d’une valeur à virgule flottante divisée par un entier. Puisque le compilateur C++ ne peut qu’évaluer des expressions dans lesquelles les types de données des opérandes sont identiques, le compilateur effectue une conversion implicite; dans l’exemple, une conversion implicite à float de compteur est effectuée.

- 15 -

Définition des constantes symboliques

(1) Utiliser le préprocesseur #define PI = 3.14159; PI peut être utilisé partout dans le programme; PI ne possède pas de type. ***** à éviter **** (2) le mot clé const

const float PI = 3.14159; données typées

(3) utilisation des énumérations

Le mot clé enum permet de définir une suite de constantes numérotées automatiquement par le compilateur (la 1e constante prend la valeur 0, la 2e constante prend la valeur 1, etc.) ex. : enum Direction{NORD, SUD, EST, OUEST};

0 1 2 3 On peut imposer des valeurs :

Enum Direction{ NORD = 9, SUD = 12, EST = 5, OUEST = 2}; Ces constantes sont considérées comme des entiers (int) par le compilateur.

Structures de contrôle Le C++ n’a que sept types différents de structures de contrôle : la séquence, 3 types de sélection (if, if-else, switch) et 3 types de répétition (while, do / while, for), combinées de seulement deux façons : par empilement ou par imbrication des structures de contrôle. *** À réviser si nécessaire ***

- 16 -

Tableaux

Rappel :

- permet de définir un ensemble de variables de même type - le nombre de composantes doit être spécifié par une constante et jamais

par une variable

- on ne peut pas modifier leur taille après les avoir créés

- ce sont des tableaux statiques par opposition à des tableaux dynamiques où l’allocation mémoire est réalisée à l’exécution du programme et une variable peut être utilisée pour indiquer le nombre d’éléments.

Ex. :

const int Max_elements = 10; float tache[Max_elements]; int b[100], x[27];

- les indices des tableaux commencent à 0. Le i ième élément du tableau est

d’indice i – 1. - Les crochets utilisés pour l’indice d’un tableau sont en réalité un opérateur

du C++.

- L’initialisation des éléments d’un tableau peut se faire de différentes façons :

Ex. : int n[10] = {0}; // les éléments restants autres que n[0] sont // initialisés à 0 par défaut. int m[] = { 1, 2, 3, 4, 5}; // Crée un tableau à 5 éléments. int o[3] = {-1, 0, 1}; // Crée un tableau à 3 éléments.

- On peut appliquer la spécification static à une déclaration locale de tableau pour éviter que ce tableau ne soit créé et initialisé à chaque appel de la fonction. Cette mesure évite aussi la destruction du tableau à chaque sortie de la fonction et améliore la performance du programme. Le tableau existera donc pour la durée complète du programme mais n’est visible que dans le corps de cette fonction.

- 17 -

Les tableaux déclarés static sont initialisés lors du chargement du programme. Si le programmeur n’initialise pas explicitement un tableau de type static, le compilateur l’initialisera à zéro. Toutefois, les éléments du tableau local de type static d’une fonction ne sont pas initialisés à chaque appel de la fonction; ils conservent leurs anciennes valeurs. Ex. : void Exemple_de_fonction(void) { static int tableau[10]; . . . }

Passage d’un tableau à une fonction :

Voici un exemple : void modifierTableau(int Tab[], int taille) ; int main() { int tableau[5] = {0} ; . . . modifierTableau(tableau, 5) ; . . . } . . . Il peut arriver qu’une fonction ne doivent pas être autorisée à modifier les éléments d’un tableau dans vos programmes. Le C++ offre le qualificatif de type const pour y arriver. En effet, lorsque le qualificatif const précède le paramètre d’un tableau, les éléments de ce dernier deviennent constants dans le corps de la fonction courante. Ex. : void essai(const int tab[]) ; int main() { int a[] = {10, 20, 30}; essai(a) ; . . . } void essai(const int b[]) { b[0] = 2 ; // erreur . . . }

- 18 -

Chaînes de caractères Le type chaîne de caractères n’existe pas en C++ à moins que ….. Pour créer une chaîne, on doit définir un tableau de caractères. Ex. : char Nom[20+1]; // 20 éléments + 1 caractère de fin de chaîne

La longueur que vous indiquez à la création d’une chaîne est le nombre maximum de caractères de la chaîne.

On n’est jamais obligé d’utiliser toute la capacité de la chaîne. Le cas échéant, la chaîne n’est pas complétée par des espaces.

Il existe un caractère \0 (code ASCII 0) pour indiquer la fin de la chaîne. Ce caractère est essentiel pour éviter les débordements. Il intervient dans les cas suivants :

- il faut prévoir l’espace occupé par ce caractère lors de la création de la chaîne.

- en initialisant une chaîne, caractère par caractère, vous devez placer le

caractère \0.

- si l’on souhaite tronquer ou vider une chaîne.

Initialisation d’une chaîne

(A) caractère par caractère Nom[0] = 'O'; Nom[1] = 'U'; Nom[2] = 'I'; Nom[3] = '\0'; // ou Nom[3] = 0; cout << Nom; // C’est le nom du tableau qui est spécifié. Note : Chaque fois que vous avez besoin de passer une chaîne de caractères à une

fonction, vous devez passer le nom du tableau.

- 19 -

(B) utilisation des fonctions de manipulation de chaînes

les prototypes des fonctions de manipulation de chaînes (40 environ) se trouvent dans le fichier d’en-tête string.h

#include <iostream.h> #include <string.h> int main() { char Nom[20+1]; char Prenom[20+1]; strcpy(Nom, "Boivin"); // Initialisation de Nom strcpy(Prenom, "Virginie"); // Initialisation de Prenom strcat(Nom, " - Leduc"); // Concaténation d’une chaîne à Nom cout << Prenom << " " << Nom; return 0; }

Vider une chaîne

- initialiser le 1er élément du tableau à \0. - strcpy(Nom, ""); // Copie d’une chaîne vide.

Tronquer une chaîne

Nom[7] = "\0"; cout << Nom; // Donne à l’affichage Boivin Nom[7] = "-"; cout << Nom; // Donne à l’affichage Boivin – Leduc // L’usage de \0 n’écrase pas les caractères suivants.

Comparer 2 chaînes Ex. : strcmp(Prenom, " - "); Retourne la valeur 0 si les 2 chaînes sont identiques.

- 20 -

Structures (enregistrement)

Une structure est un type de donnée permettant de regrouper plusieurs variables qui peuvent être de types différents. struct Client { char Nom[20+1]; char Prenom[20+1]; int Matricule; float Remuneration_totale; }; …….. Client X; Client * pX; …….. X.Matricule = 7834; // Accès au champ Matricule via l’opérateur point ( . ) pX = &X; cout << pX -> Matricule; // Accès au champ Matricule via un pointeur vers l’objet

// grâce à l’opérateur flèche ( -> ). // L’expression pX -> Matricule équivaut à (*pX).Matricule.

Toutefois, une structure ne peut contenir aucune instance d’elle-même. Par exemple, un membre de type Client ne peut jamais être déclaré au sein de la définition de la structure Client. Par contre, un pointeur vers une autre structure Client peut y être inclus. Une fonction peut récupérer ou renvoyer une variable de type structure. Ex. : void Affiche(const Client & X) { …. } Cet exemple permet le passage par référence des structures Client vers la fonction d’affichage éliminant la surcharge associée au passage par valeurs qui aurait nécessité des copies des structures pour les passer à la fonction d’affichage. De plus, l’utilisation de const empêche la modification de la structure Client par la fonction d’affichage. En C, les structures ne peuvent être affichées de façon globale; on doit plutôt mettre en forme et afficher leurs membres l’un après l’autre. On peut écrire une fonction affichant tous les membres d’une structure selon un format approprié quelconque. La surcharge de l’opérateur << permet un affichage facile de variables d’un type structure.

- 21 -

Pointeurs

Incontournable en C++. Variable qui contient l’adresse d’une autre

variable. &a désigne l’adresse de la variable a. déclaration de pointeurs : type * nom_du_pointeur Ex. : char * p; Client * Q; Q = &X; Types primaires ou créés par l’utilisateur (typedef, enum, struct, class) Un pointeur doit obligatoirement être typé. char c; …… p = &c; ……. *p = "a"; // L’emploi de l’opérateur * de cette façon est ce // qu’on appelle la déréférenciation d’un pointeur. Constante d’initialisation des pointeurs : NULL ou 0. Avantage des pointeurs : permet l’accès à des variables hors de portée. Utilisation des pointeurs avec les fonctions Permet d’accéder à une variable déclarée dans une autre fonction, i.e.

quelle que soit sa portée. Ex. : #include <iostream.h> void Carre(float * Nombre) { float X = *Nombre; *Nombre = X * (*Nombre); // *Nombre=(*Nombre) * (*Nombre);

} void main() { float Y = 10.9; Carre(&Y); cout << Y; }

- 22 -

Une fonction peut aussi renvoyer l’adresse d’une variable. float * Carre(float * Nombre) { // On ne doit pas retrouver l’adresse d’une variable locale. }

Arithmétique des pointeurs

Quand vous ajoutez la valeur 1 au contenu d’un pointeur, celui-ci ne se déplace pas nécessairement d’un octet en mémoire, mais plutôt du nombre d’octets équivalent à la taille de la variable pointée. Utilisée avec les tableaux.

Définition d’un pointeur vers un tableau

(i) déclarer un pointeur vers le type correspondant au type des composantes du tableau

(ii) initialiser ce pointeur avec l’adresse du 1er élément du tableau (iii) utiliser l’arithmétique des pointeurs pour accéder aux éléments du tableau.

int Mois[12]; int * pEntier; … pEntier = &Mois[0]; // ou encore, pEntier = Mois; …. *pEntier = 1; // initialiser la 1e composante *(pEntier+1) = 2; // initialiser la 2e composante *(pEntier+2) = 3; // initialiser la 3e composante etc.

Note : Le nom d’un tableau correspond à un pointeur constant (qui ne peut être modifié), i.e. l’adresse du 1e élément de ce tableau La notation pointeur peut être utilisée pour désigner les éléments du tableau ou utiliser la notation tableau avec un pointeur.

- 23 -

int Mois[12]; int * pEntier; pEntier = Mois; …. *Mois = 1; // ou encore, pEntier[0] = 1; *(Mois + 1) = 2; // ou encore, pEntier[1] = 2;

Passage d’un tableau à une fonction à l’aide de pointeurs

Il faut envoyer l’adresse de ce tableau et non le tableau. Ex. I : int Somme(int * pEntier, int Nb_elements) { int S = 0; for (int i = 0; i < Nb_elements; i++) S = S + *(pEntier + i); return S; } … cout << Somme(Mois, 12);

Ex. II : char Titre[10]; void Afficher(char * chaine) { cout << chaine << "\n";

} …. Afficher("POO"); …. Afficher(Titre);

Ex. III : void modifierTableau(int Tab[], int taille) ; int main() { int tableau[5] = {0} ; . . . modifierTableau(tableau, 5) ; . . . } . . .

- 24 -

Quatre méthodes permettent de passer un pointeur à une fonction : (A) un pointeur non constant vers des données non constantes

Cela n’inclut pas const. Ex. : void Conversion(char * c); int main() { char chaine[] = "conversion en majuscules"; Conversion(chaine); cout << chaine << endl; } void Conversion(char * c) { // On peut modifier c et la chaîne pointée par c. }

(B) un pointeur non constant vers des données constantes

Ex. : void Affichage(const char *); int main() { char chaine[] = "affichage des caracteres"; . . . Affichage(chaine); . . . } void Affichage(const char * sPtr) { for (; *sPtr != ‘\0’; sPtr++) cout << *sPtr; }

(C) un pointeur constant vers des données non constantes Cela pointe toujours vers le même emplacement mémoire et peut modifier les données contenues à cet emplacement. Ceci est le cas par défaut pour un nom de tableau qui est en fait un pointeur constant au début du tableau. On peut accéder à toutes les données du tableau et les modifier en utilisant le nom et les indices du tableau.

- 25 -

Ex. : int x, y; int * const ptr = &x; *ptr = 7; ptr = &y; // erreur (D) un pointeur constant vers des données constantes

Ex. : void essai(const int tab[]) ; int main() { int a[] = {10, 20, 30}; essai(a) ; . . . } void essai(const int b[]) { . . .

}

Pointeurs et structures

#include <iostream.h> #include <string.h> struct Client { char Nom[20+1]; char Prenom[20+1]; int code; }; void Afficher(Client * pC) { cout << pC -> Nom; cout << pC -> Prenom; cout << (*pC).code; } int main() { Client C; strcpy(C.Nom, "BEAUDOIN"); strcpy(C.Prenom, "LUC"); C.code = 1234; Afficher(&C); Return 0; }

- 26 -

Manipulation de pointeurs de fonctions

- Un pointeur peut être déclaré dans le but de contenir l’adresse d’une fonction. - Cela permet d’appeler la fonction exactement de la même manière qu’avec son nom. - Le type de fonction acceptée doit être indiqué. Ex. : int (*p) (int * pEntier, int N); // Déclaration du pointeur p de fonction. p = Somme; // Initialisation du pointeur de fonction p. cout << p(Mois, 12); // Appel de la fonction Somme. Note : (i) L’adresse d’une fonction correspond à son nom. (ii) Les pointeurs de fonction peuvent être utilisés par exemple dans le cas d’un outil

de gestion de menus.

Allocation dynamique de la mémoire

Les opérateurs new et delete fournissent un moyen plus commode d’effectuer les tâches d’allocation dynamique de la mémoire que les appels de fonctions malloc et free du langage C. Soit Personne * ptrPersonne; en utilisant la version ANSI du C, nous pouvons créer dynamiquement une variable de type Personne : ptrPersonne = malloc (sizeof (Personne) ); cela nécessite un appel à malloc et l’utilisation explicite de l’opérateur sizeof. Dans certains cas, on devait recourir au forçage de type sur le pointeur renvoyé par malloc (Personne *). Finalement, la fonction malloc ne fournit aucune méthode d’initialisation pour le bloc de mémoire allouée.

- 27 -

En C++, on a :

ptrPersonne = new Personne;

une variable de taille appropriée est automatiquement créée et l’opérateur new retourne un pointeur de type correspondant. Pour libérer l’espace occupé, vous devez utiliser l’opérateur delete : delete ptrPersonne; Le C++ permet de fournir un initialisateur pour une variable nouvellement créée comme, par exemple, float * ptrValeur = new float(3.14159); Pour créer un tableau d’entiers à 10 éléments, on a : int * ptrTableau = new int[10]; et pour le détruire, on a : delete [] ptrTableau; Note :

(i) Le mélange des allocations dynamiques de mémoire de style new et delete avec celles du genre malloc et free est une erreur de logique; l’espace créé par malloc ne peut être libéré par delete et les objets créés par new ne peuvent être supprimés par free.

(ii) L’espace créé pour un tableau doit être supprimé avec l’opérateur delete [] et l’espace créé pour un élément individuel doit être supprimé avec l’opérateur delete.

(iii) Il est préférable de n’utiliser que new et delete. ------------------------------------

- 28 -