EPFL
Faculté Informatique & Communications
Cours d'informatique

Mini-Référence :
Types avancés en C++

Strings Tableaux de taille fixe Tableaux dynamiques typedef Structures Pointeurs Énumérations

Le type (classe) string (chaîne de caractères)

Bases

Pour utiliser la classe string, il faut faire figurer au début du programme la ligne suivante :
#include<string>

La classe string est prévue pour stocker des chaînes de caractères. Elle s'utilise le plus souvent comme un type standard. Les valeurs littérales des chaînes de caractères sont entourées par des guillemets.

Exemples :

string c;                                      // déclaration de la variable c de type string
c = "Ceci est une chaîne de caractères";       // affectation

string d("Ceci en est une deuxième");          // déclaration+initialisation

string e;
cin >> e;                                      // l'utilisateur entre un mot, stocké dans e;

cout << c << endl << d << endl << e << endl;   // Affichage des chaînes

Fonctions spécifiques

La classe string possède des fonctions spécifiques (ou "méthodes") : size(), substr, find, rfind, ...

Voici quelques exemples :

#include <string>
#include <iostream>

void main()
{
    string machaine1 = "Bonjour";
    string machaine2 = " à tous";
    string tachaine;

    tachaine = machaine1[2];  // affecte le caractère d'index 2 de machaine1
                              // à tachaine : après, tachaine vaut "n"

    tachaine = machaine1 + machaine2;  // affecte "Bonjour à tous" à tachaine


    cout << machaine1.size()  // affiche la longueur de machaine1 : 7
         << endl;

    cout << machaine2.substr(3,4)  // affiche la sous-chaîne qui
                                   // commence au caractère d'indice 3 de machaine2
                                   //  (cad "t") et de longueur 4
                                   // -> affiche "tous"
         << endl;

    cout << tachaine.find( "ou" ) // affiche la position de la première chaîne "ou"
                                  // -> affiche 4
         << endl;

    cout << tachaine.rfind( "ou" ) // affiche la position de la première chaîne "ou"
                                   // à partir de la droite (right -> *r*find)
                                   // -> affiche 11;
         << endl;
}

Références complémentaires


Tableaux de taille fixe

Un tableau est un ensemble d'objets de même type, conservés de manière contiguë en mémoire, et désignables par un nom unique (le nom du tableau). Ces objets sont accessibles individuellement par la donnée d'un indice appartenant à l'intervalle [0..taille-1]), où taille est le nombre d'objets dans le tableau.

Le type des objets contenus dans le tableau est appelé type de base du tableau, et il peut être quelconque ; il peut notamment s'agir d'un autre tableau.

La déclaration d'un tableau se fait au moyen de la syntaxe suivante :

type identificateur[taille];
où :

Il est possible d'initialiser une variable ou constante tableau lors de sa déclaration en spécifiant une partie ou tous les littéraux constituant les valeurs initiales.

Dans ce cas, la spécification de la taille du tableau est optionnelle; si elle n'est pas présente, la taille sera déterminée par le nombre de littéraux spécifiés.

type identificateur[N] = { val1, ..., valN };

L'accès individuel aux éléments se fait en postfixant l'identificateur de tableau par un index spécifié entre crochets (parenthèses carrées) :

identificateur[index];
Attention ! Il n'y a pas de test de débordement !!

C'est à dire qu'une erreur comme

int tab[4];
int i(3);
...
i=i+2;
... tab[i] ... // tab[5] !!
n'est pas détectée, ni par le compilateur ni lors de l'execution.

Exemple récapitulatif :

#include <iostream>

/*
 * But   : réaliser l'affichage à l'écran des paramètres
 *         d'un objet géométrique.
 *
 * Entrée: titre = nom de la figure
 *         params = paramètres de la figure : tableau d'entiers
 *         nombre = nombre de 'paramètres' dans le tableau
 */

void afficherParametres(const string& titre,
                        const int params[],
                        const unsigned int nombre)
{
    cout << "Paramètres de la figure '" << titre
         << "' :" << endl;

    for(unsigned int p(0); p < nombre; p++)
        cout << '[' << p << "] = " << params[p] << endl;

    cout << endl; // saut de ligne supplémentaire
}

main()
{
    const int taille_rectangle(2);
    int rectangle1[taille_rectangle];    // déclaration de variable
    int parallelepipede[3] = {2, 3, 1};  // décl.-init. de variable
    const int rectangle2[taille_rectangle] = {5, 3}; // déclaration de constante

    rectangle1[0] = 1;
    rectangle1[1] = 3;

    afficherParametres("rectangle numéro 1", rectangle1, taille_rectangle);
    afficherParametres("rectangle numéro 2", rectangle2, taille_rectangle);

    // NIVEAU AVANCÉ : spécification de la taille par le biais de sizeof.
    // Au niveau "normal", préférez utiliser des constantes.
    afficherParametres("parallelepipede 1", parallelepipede,
                       sizeof(parallelepipede)/sizeof(parallelepipede[0]));

}

Résultat :

Paramètres de la figure 'rectangle numéro 1' :
[0] = 1
[1] = 3

Paramètres de la figure 'rectangle numéro 2' :
[0] = 5
[1] = 3

Paramètres de la figure 'parallelepipede 1' :
[0] = 2
[1] = 3
[2] = 1

Tableaux dynamiques (collections ou conteneurs)

Attention ! Pour utiliser le type vector, permettant de mettre en oeuvre les tableaux dynamiques, les programmes doivent importer la librairie de définition de ce type, au moyen de la directive :
#include <vector>

Déclaration

La syntaxe pour déclarer un tableau dynamique nommé identificateur, dont les éléments sont de type type est :
vector < type > identificateur;

Si l'on connaît à priori la dimension (dim_vec) du tableau dynamique, celui-ci peut être déclaré ainsi :

vector < type > identificateur(dim_vect);

Si l'on connaît la taille (initiale) du tableau dynamique lors de sa déclaration, on peut alors initialiser ses éléments, et leur affecter à tous la même valeur valeur, en utilisant la syntaxe suivante :

vector <
type > identificateur(dim_vect, valeur);
Attention ! la valeur d'initialisation valeur doit évidemment être du type type.

Accès aux éléments

Comme dans le cas des tableaux de taille fixe, on peut accéder individuellement aux éléments du tableau dynamique en utilisant les indices (à partir de 0).

La syntaxe pour accéder à la valeur d'un élément d'un tableau dynamique est :

identificateur[indice];

Affectation d'un élément

La syntaxe pour affecter une valeur à un élément d'un tableau dynamique est :
identificateur[indice] = valeur;
mais on peut aussi utiliser la fonction spécifique pushd expliquée ci-dessous

Fonctions spécifiques

Parmi les fonctions spécifiques aux tableaux dynamiques, celles-ci vous seront sans doute utiles :
int size() renvoie la taille du tableau dynamique
bool empty() renvoie true si le tableau dynamique est vide (de taille nulle), false sinon
void clear() vide le tableau dynamique de ses éléments
void pop_back() supprime le dernier élément du tableau dynamique
void push_back(type-base) ajoute l'élément <valeur> à la fin du tableau dynamique
base-type& front() renvoie une référence au premier élément du tableau
base-type& back() renvoie une référence au dernier élément du tableau

Notez que les méthodes clear, pop_back et push_back modifient la taille (i.e. le nombre d'éléments) du tableau dynamique.

Exemples :

vector<int> tab(3, 0);     // déclaration et initialisation à 0 d'un
                           // tableau dynamique d'entiers de dimension 3


tab[2] = -45;     // affectation de la valeur -45 à l'élément
                  // de position 2 du tableau dynamique tab

cout << tab[1] << endl;    // affichage de l'élément
                           // de position 1 du tableau tab -> 0

cout << tab.size() << endl; // affichage de la taille
                            // du tableau tab -> 3

tab.push_back(20);    // ajout à la fin du tableau d'un élément valant 20

cout << tab.size() << " "; // affichage taille -> 4

tab.pop_back();            // suppression du dernier élément

cout << tab.back() << endl;// affichage du dernier élément -> -45

tab.clear();               // vidage du tableau

cout << tab.size() << endl;// affichage taille tableau -> 0

Références complémentaires


Alias de type : typedef

Le spécificateur (mot clef) «typedef» permet de définir un synonyme (alias) pour un type. Il y a deux raisons principales militant en faveur d'une utilisation intensive de cette clause: tout d'abord, typedef permet de faciliter l'écriture des types complexes, en décomposant les étapes de la définition, et en rendant réutilisable ces définitions (pas besoin de réécrire des déclarations complexes). La seconde raison (non sans rapport avec la première) est le regroupement des définitions, lié à une plus forte explicitation d'informations (voir ci-après).

Syntaxe :   typedef type alias;

où:

Après la déclaration d'un type synonyme, l'alias peut être utilisé exactement comme un type prédéfini du langage.

En fait, l'utilisation de ce spécificateur est motivée par les mêmes raisons que celles qui valident l'utilisation de fonctions pour mettre en oeuvre du code réutilisable. Il permet d'ajouter de l'information conceptuelle (i.e. expliciter des informations implicites), en établissant une distinction lors d'utilisation multiple d'un même type de base.

Exemples :

typedef vector< vector <double> > Matrice;

typedef int distance;
typedef distance surface;
typedef distance volume;

L'information explicitée dans ce dernier exemple est clairement visible. L'avantage d'une telle spécification, par rapport à la donnée systématique des types de base (int dans ce cas), est évidente : si, pour des raisons quelconques, il devient nécessaire de modifier la représentation utilisée pour l'une des grandeurs (par exemple, les distances doivent être représentées par des réels), il suffira de simplement modifier la définition de l'alias distance; sans définition unique, au moyen de typedef, de la notion de distance, il faudrait parcourir tous le (les) programme(s), en traquant tous les entiers représentant potentiellement une distance.

Une seconde raison (et pas des moins importantes) pour une telle spécification est d'augmenter le degré de lisibilité des programmes. Même si le compilateur accepte, par exemple, l'affectation d'une variable de type volume par une expression de type distance, puisqu'il s'agit en fait du même type, les risques de telles confusions pour le programmeur humain sont malgrés tout moindres.


Structures

Les structures sont simplement des regroupements de plusieurs variables appelées champs de la structure. Les champs peuvent être de tous types (élémentaires ou complexes) et pas forcément du tous du même (type).

Les structures présentent donc l'intérêt de pourvoir regrouper des entités pour leur manipulation unique.

La syntaxe de déclaration d'une structure est :

struct nom {
    type1 champ1;
    ...
    typeN champN;
};

nom est le nom que vous souhaitez donner à la structure, type1 est le type du premier champ et champ1 son nom, etc...
L'ordre des champs n'a pas d'importance.

Exemples :

struct Complexe {
   double x;
   double y;
};

struct Personne {
  string nom;
  unsigned short int age;
  char sexe;
  double taille;
  unsigned short int poids;
};

Une fois définie, une struct s'utilise comme n'importe quel autre type.

Exemples :

Complexe z;
Personne untel;

On peut également initialiser une structure lors de sa déclaration. Cela se fait avec la syntaxe suivante :

    nom variable = { val1, ... valN };

nom est le nom de la structure, variable le nom de la variable déclarée et val1 une valeur du type correspondant au premier champ défini dans la structure (type1 dans la définition précédente).

Exemples :

Complexe z = { 0.0, 1.0 };
Personne untel = { "Pierre", 19, 'M', 1.82, 75 };

Pour accéder à un champ particulier d'une structure, on utilise la syntaxe :

variable.champ

variable le nom de la variable (de type "structure") et champ le champ considéré.

Exemples (continuation des précédents) :

z.x = -3;
... z.y * z.y + z.x * z.x ...

untel.age++;

Pointeurs

Un pointeur est une variable contenant l'adresse d'un autre objet informatique. C'est donc une indirection.

Il n'y a rien de particulier par rapport aux pointeurs si ce n'est qu'ils représentent un niveau d'abstration supplémentaire. C'est peut être là que réside la difficulté de le compréhension.

La déclaration d'un pointeur se fait en ajoutant une étoile (*) après le type que l'on veut pointer.

Par exemple, pour déclarer une variable (nommée ptr) pointant sur un entier on écrira :

int* ptr;

Le type pointé peut être n'importe quel type valide : élémentaire ou complexe (ce peut même être un pointeur !).

Pour accéder au contenu pointé par un pointeur, il faut écrire :

*ptr

Pour faire pointer un pointeur sur une autre variable on utilisera l'opérateur d'adresse & (qui renvoit l'adresse de la variable à laquelle il s'applique). Par exemple :

int i(4);
int j(5);
int* ptr(&i); // ptr pointe sur i

cout << *ptr << endl; // affiche 4

ptr = &j; // ptr pointe maintenant sur j


j++;  // j vaut maintenant 6
cout << *ptr << endl; // affiche 6

*ptr = *prt + 2; // augmente la valeur pointée (donc celle de j)
cout << j << endl; // affiche 8

Les pointeurs peuvent aussi pointer sur des fonctions. Exemple :

int f(int,double); // prototype d'une fonction (prennant un int et
                   // un double et retournant un entier)

int (*ptr)(int,double); // pointeur sur une telle fonction

ptr = f;  // ici ptr pointe sur f, c'est la MÊME chose que
ptr = &f; // avec les fonction on n'a pas besoin du &, mais on peut
          // le mettre si on préfère.

On peut également allouer directement la mémoire que l'on veut pointer, sans que ce soit un objet déjà définit.
Dans ce cas, il faut également penser à rendre la mémoire allouée lorsqu'on en a plus besoin.

Pour allouer de la mémoire on utiliser new et pour la rendre delete.

Exemple :

   int *ptr(0); // ptr ne pointe sur rien pour l'instant

   ptr = new int; // ptr pointe maintenant sur une zone mémoire
                  // prète à recevoir un entier (int)
                  // (mais pas encore initialisée)

  *ptr = 5; // remplit la zone mémoire pointée par prt avec la valeur 5

   // plus haut on aurait aussi pu écrire :
   // ptr = new int(5);
   // pour faire une initialisation en même temps que l'allocation mémoire

   ...

   delete ptr;  // on a plus besoin de prt : libère la zone mémoire
                // qu'on lui avait alloué.


Énumérations

Une énumération est un type de donnée qui contient une liste de mot-clefs définis par l'utilisateur et les associe à des entiers. Par exemple,

   enum couleur { rouge, vert, bleu };

associe à «rouge» la valeur 0, à «vert» la valeur 1, et à «bleu» la valeur 2. On utilise une énumération de la façon suivante :

   enum couleur { rouge, vert, bleu }; // ceci est bien une déclaration de type

   void foobar(couleur maCouleur) // là, on prend en argument un objet de type "couleur".
   {
      switch (maCouleur)
      {
         case rouge:
            // faire quelque chose pour rouge
            break;
         case vert:
            // faire quelque chose pour vert
            break;
         case bleu:
            // faire quelque chose pour bleu
            break;
      }
   }

Il est par ailleurs possible d'affecter soi-même une valeur à un élément de la liste d'options :

enum couleur { rouge, vert=4, bleu };

associe à «rouge» la valeur 0, à «vert» la valeur 4, et à «bleu» la valeur 5.


Dernière mise à jour : $Date: 2009-09-11 17:10:52 +0200 (Fri, 11 Sep 2009) $   ($Revision: 25 $)