2ième Classe (Mercredi, 13 Octobre) C++ Intro CSI2572
2 C A C++ Les commentaires Il y a deux types de commentaires en C++ : les commentaires mono ligne et les blocs de commentaire. Les commentaires mono ligne commencent par les symboles // et permettent de placer en commentaire la fin d'une ligne comme le montre le fragment de code suivant : int i; // fin de la ligne en commentaire
2 C A C++ II Les types références En C, il n`y avait qu'un seul mode de passage des paramètres : le passage par valeur (même le passage par adresse correspond à passer une adresse par valeur ). Supposons qu'une fonction ait besoin de modifier la valeur de l'un de ses paramètres. Comme seul le passage par valeur est supporté, il ne reste plus qu'à passer un pointeur sur l'objet à modifier. En C++, on peut aussi passer par référence. Problèmes: Et si on passe un argument par valeur qui occupe beaucoup despace? La copie doit être mise sur le stack… Ce qui est inutile si on ne désire pas la modifier.
Les types références (syntaxe) Une référence à un paramètre de fonction en C++ est un alias pour son argument correspondant. Pour indiquer quun paramètre de fonction doit être passé par réference, il suffit de faire suivre le type du paramètre de la fonction par le symbole & : char* foo(int &a, char b); Cest la même chose pour la déclaration dun alias à une variable (une variable de type référence): int a; int & ar = a; Les variables de type référence doivent être initialisées lors de leur déclarations. Attention: La syntaxe est néanmoins trompeuse car le signe de référence "&" est le même que celui de la prise d'adresse.
/* En C d'abord */ void swapEntiers(int *a, int *b) { int c = *a; *a = *b; *b = c; } int main(int argc, char *argv[]) { int i=5; int j=6; swapEntiers(&i, &j); return 0; } // Et maintenant en C++ void swapEntiers(int &a, int &b) { int c=a; a = b; b = c; } int main(int, char **) { int i=5; int j=6; swapEntiers(i,j); return 0; }
ATTENTION: #include int main(int, char **) { int i=5; int j=10; int &r=i; cout << i << endl; // Affiche 5 cout << r << endl; // Affiche 5 r++; cout << r << endl; // Affiche 6 cout << i << endl; // Affiche 6 r=j; r++; cout << r << endl; // Affiche 11 cout << j << endl; // Affiche 10 cout << i << endl; // Affiche 11 ! return 0; } ATTENTION: Les références qui ont été crées en variables ne peuvent plus être réassignées.
Surcharge des fonctions La surcharge est un mécanisme qui permet de donner différentes signatures d'arguments à une même fonction. Vous pouvez nommer de la même façon des fonctions de prototypes différents. L'intérêt est de pouvoir nommer de la même manière des fonctions réalisant la même opération à partir de paramètres différents. 2 C A C++ III
Exemple: Vous travailliez sur un système de gestion d'interface utilisateur à base de fenêtres. Chaque fenêtre est peut être identifiée de 2 manières différentes: 1.Un identificateur numérique 2.le nom : une chaîne de caractères Vous voulez écrire 2 fonctions qui permettent de récupérer un pointeur sur une fenêtre en utilisant soit l'identificateur numérique, soit le nom. Avec un système sans surcharge, il vous faudrait utiliser 2 noms de fonctions différents, par exemple : Window *GetWindowIdent(unsigned long identificateurNumerique); Window *GetWindowName(const char *nom);
Surcharger vous permet d'utilisez le même nom : Window *GetWindow(unsigned long identificateurNumerique); Window *GetWindow(const char *nom);
Le compilateur s'y retrouve parce que le nom « interne » de la fonction contient la liste des paramètres. À la compilation, en consultant la liste des paramètres effectifs, le compilateur établit quelle est la forme de la fonction à appeler. Il est impossible d'avoir des fonctions qui ne diffèrent que par leur type de retour. En effet, le compilateur ne peut pas les distinguer si on omet de récupérer la valeur retournée.
2 C A C++ IV Les paramètres avec valeurs par défaut C++ vous permet de spécifier une valeur par défaut au arguments de vos fonctions. De 7 manière, quand vous utilisez la fonction, vous n'avez à spécifier ces arguments que lorsque leur valeur diffère du défaut. Les valeurs par défaut ne peuvent être spécifiés que lors de la déclaration de la fonction et ne doivent pas être rappelés lors de définition de la fonction. Exemple: int Show (Window *p, unsigned int modeOuverture = 1);
Le nombre des paramètres avec des valeurs par défaut n'est pas limité. En fait, tous les paramètres peuvent prendre une valeur par défaut. Toutefois, seuls les derniers paramètres de la fonction peuvent avoir une valeur par défaut. Par exemple, le code suivant est illégal : void f1(int i, double angle=0.0, double longueur); De même, avec : void f2(double angle=0.0, double longueur=50.0); L'appel suivant est illégal : f2(,35.0); Vous ne pouvez omettre que les valeurs des derniers paramètres.
2 C A C++ V Les nouvelles primitives d'entrées / sorties Les fonctionnalités d'entrées / sorties en C++ sont orientées objet. Elles reposent sur la notion de flux (en anglais: streams). Leur utilisation se fait principalement au travers des opérateurs de redirection en sortie " >". Ainsi, l'écriture à l'écran d'une expression se traduit par le code suivant : cout << expression; Cela sous entend la surcharge de l'opérateur << pour le type de donnée correspondant à expression. Ce travail a été réalisé pour la plupart des types atomiques du C++. Il vous appartiendra de le faire pour vos propres classes.
Objet FluxPériphérique "C"Description coutstdoutFlux associé à la sortie standard (écran) cinstdinFlux associé à l'entrée standard (clavier) cerrstderrFlux associé à la sortie des erreurs (écran) Les flux standards Il y a 3 objets de type flux:
Les opérateurs << sont prévus pour être chaînés. Vous pouvez donc les cumuler sur la même ligne de code, par exemple: int i; int j; cout << i << " " << j << endl; La constante symbolique endl vous permet de changer de lignes indépendamment de l'architecture dans laquelle le programe évolue.
Comme avec les printf, Il est possible de spécifier des formats d'affichage à la manière des fameux "%6.2f". Pour cela, il faut utiliser des manipulateurs de flux. L'exemple suivant créé l'équivalent d'un printf("6.2f",r); #include using namespace std; int main(int, char **) { double r= ; int i=5; cout << setw(6) << setprecision(2) << r << endl; cout << i << " " << i++ << endl; cout << i << endl; return 0; } Les appels sont réalisés de la droite vers la gauche. Les expressions que vous affichez sont présentées dans l'ordre que vous avez demandé mais elles ont été évaluées dans l'ordre inverse.
En plus des "anciens" malloc et free du C, C++ possède un nouveau jeu d'opérateurs d'allocation/désallocation de mémoire : new et delete. Ils ont été créés principalement pour la gestion dynamique des objets, mais on peut les utiliser également pour des variables simples. int * p = (int*)malloc(sizeof(int)); free(p); p = 0; int * p = (int*)calloc(10,sizeof(int)); free(p); p = 0; int * p = new int; delete p; p = 0; int * p = new int[10]; delete[] p; p = 0; Allocation de mémoire en C++ 2 C A C++ VI
double ** alloc_matrix(int n, int m) { double ** M = new double*[n]; for(int i=0; i< n; ++i) M[i] = new double[m]; return M; } void free_matrix(double ** M, int n) { for(int i=0; i< n; ++i) delete[] M[i]; delete[] M; }
La programmation orientée objet cherche à modéliser informatiquement des éléments du monde réel en entités informatiques appelées objets. Les objets sont des données informatiques regroupant les principales caractéristiques des éléments du monde réel (taille, couleur,...). La difficulté du processus de modélisation est dans la création d'une représentation abstraite d'entités ayant une existence matérielle (chien, voiture, ampoule,...) ou bien virtuelle (sécurité sociale, temps,...). 2 C A C++ VII
Dans le monde réél, deux T-shirts peuvent être identique et distincts. La classe, c'est la structure d'un objet, c'est-à-dire la déclaration de l'ensemble des entités qui composeront un objet. Une classe peut être considérée comme un moule à partir duquel on peut créer des objets. Un objet est donc "issu" d'une classe. C'est une instanciation d'une classe, c'est la raison pour laquelle on pourra parler indifféremment d'objet ou d'instance.
Une classe est composée de deux parties: Les attributs (parfois appelés données membres): il s'agit des données représentant l'état de l'objet Les méthodes (parfois appelées fonctions membres): il s'agit des opérations applicables aux objets Si on définit la classe voiture, les objets Toyota_Civic, Mustang2003 seront des instanciations de cette classe. Il pourra éventuellement exister plusieurs objets Toyota_Civic, différenciés par leur numéro de série. Deux instanciations de classes pourront même avoir tous leurs attributs égaux sans pour autant être le même objet.
Dans ce modèle, un véhicule est représenté par une chaîne de caractères (sa marque) et trois entiers : la puissance fiscale, la vitesse maximale et la vitesse courante. chaque objet véhicule aura sa propre copie de ses données : on parle alors d'attribut d'instance. L'opération d'instanciation qui permet de créer un objet à partir d'une classe consiste précisément à fournir des valeurs particulières pour chacun des attributs d'instance.
La déclaration de la classe commence par le mot clef class et est encadrée par une paire d'accolades. L'accolade finale est suivie d'un point virgule. Les membres déclarés après le mot clef public forment l'interface de la classe. Ceux qui suivent le mot private sont invisibles de l'utilisateur ; > L'ordre de déclaration des méthodes et des attributs est laissé au libre arbitre du programmeur. > La déclaration des attributs est semblable à la déclaration d'une variable alors que celle d'une méthode ressemble à s'y méprendre au prototype d'une fonction ! Néanmoins, il ne faut pas oublier que chaque méthode possède un argument caché : l'objet sur lequel elle est invoquée.
Lors de l'implémentation des méthodes, il est nécessaire de préfixer le nom de la méthode implémentée du nom de la classe suivi de ' :: '. Par exemple, l'implémentation de la méthode deplacerVers de la classe Point se fait en spécifiant: Point::deplacerVers H Le constructeur est une méthode particulière qui porte le même nom que la classe et dont le but est d'initialiser les attributs lors de la création d'un objet. H Les méthodes x et y sont déclarées constantes à l'aide du mot clef const. Cela signifie que leur code n'affecte en aucune manière la valeur des attributs de l'objet cible.
De la même manière, qu'une classe, un objet est caractérisé par: Attributs: Il s'agit des données caractérisant l'objet. Ce sont des variables stockant des informations d'état de l'objet Méthodes (appelées parfois fonctions membres): Les méthodes d'un objet caractérisent son comportement, c'est-à- dire l'ensemble des actions (appelées opérations) que l'objet est à même de réaliser. Ces opérations permettent de faire réagir l'objet aux sollicitations extérieures (ou d'agir sur les autres objets). De plus, les opérations sont étroitement liées aux attributs, car leurs actions peuvent dépendre des valeurs des attributs, ou bien les modifier De plus, un objet a aussi une Identitée
Encapsulation, polymorphism et héritage Encapsulation: Le rassemblement des données et du code les utilisant dans une entité unique (objet). La séparation nette entre la partie publique d'un objet (ou interface) seule connue de l'utilisateur de la partie privée ou implémentation qui reste masquée. Polymorphism: Une méthode peut adopter plusieurs formes différentes. Héritage Possibilité de définir des familles de classes traduisant le principe de généralisation / spécialisation. « La classe dérivée est une version spécialisée de sa classe de base »
L'encapsulation consiste à masquer l'accès à certains attributs et méthodes d'une classe. Pourquoi masquer? C Cacher les détails d'implémentation des objets à l'utilisateur permet de modifier, par exemple la structure de données interne d'une classe (remplacer un tableau par une liste chaînée) sans pour autant entraîner de modifications dans le code de lutilisateur, linterface nétant pas atteinte. C Abstraction de données : la structure d'un objet n'est pas visible de l'extérieur, son interface est constituée de messages invocables par un utilisateur. La réception d'un message déclenche l'exécution de la méthode correspondant à ce message. C Abstraction procédurale : Du point de vue de l'extérieur (cest-à-dire en fait du client de lobjet), l'invocation d'un message est une opération atomique. L'utilisateur n'a aucun élément d'information sur la mécanique interne mise en œuvre. Par exemple, il ne sait pas si le traitement requis a demandé lintervention de plusieurs méthodes ou même la création dobjets temporaires etc. Encapsulation I
Encapsulation II En C++, on choisit le paramètres d'encapsulation à l'aide des mots clés : private : les membres privés ne sont accessibles que par les fonctions membres de la classe. protected : les membres protégés sont comme les membres privés. Mais ils sont aussi accessibles par les fonctions membres des classes dérivées. public : les membres publics sont accessibles par tous. La partie publique est appelée interface. Les mots réservés private, protected et public peuvent figurer plusieurs fois dans la déclaration de la classe. Le droit d'accès ne change pas tant qu'un nouveau droit n'est pas spécifié.
class Avion { public : // fonctions membres publiques void init(char [], char *, float); void affiche(); private : // membres privées char immatriculation[6], *type; float poids; // fonction membre privée void erreur(char *message); }; // n'oubliez pas ce ; après l'accolade
Les fonctions membres sont définies dans un module séparé ou plus loin dans le code source. Syntaxe de la définition hors de la classe d'une méthode : type Classe::nom_méthode( paramètres_formels ) { // corps de la fonction }
class Avion { public : // fonctions membres publiques void init(char [], char *, float); void affiche(); private : // membres privées char immatriculation[6], *type; float poids; // fonction membre privée void erreur(char *message); }; // n'oubliez pas ce ; après l'accolade void Avion::init(char m[], char *t, float p) { if ( strlen(m) != 5 ) { erreur("Immatriculation invalide"); strcpy( immatriculation, "?????"); } else strcpy(immatriculation, m); type = new char [strlen(t)+1]; strcpy(type, t); poids = p; } void Avion::affiche() { cout << immatriculation << " " << type; cout << " " << poids << endl; }
Comme pour struct, le nom de la classe représente un nouveau type de donnée. On peut donc définir des variables de ce nouveau type; (créer des objets ou des instances) Avion av1; // une instance simple (statique) Avion *av2; // un pointeur (non initialisé) Avion compagnie[10]; // un tableau d'instances av2 = new Avion; // création (dynamique) d'une instance
Après avoir créé une instance (de façon statique ou dynamique) on peut accéder aux attributs et méthodes de la classe. Cet accès se fait comme pour les structures à l'aide de l'opérateur. ou ->. av1.init("FGBCD", "TB20", 1.47); av2->init("FGDEF", "ATR 42", 80.0); compagnie[0].init("FEFGH","A320", 150.0); av1.affiche(); av2->affiche(); compagnie[0].affiche(); av1.poids = 0; // erreur, poids est un membre privé
Certaines méthodes d'une classe ne doivent (ou ne peuvent) pas modifier les valeurs des données membres de la classe, ni retourner une référence non constante ou un pointeur non constant d'une donnée membre : on dit que ce sont des fonctions membres constantes. Ce type de déclaration renforce les contrôles effectués par le compilateur et permet donc une programmation plus sûre sans coût d'exécution. Il est donc très souhaitable d'en déclarer aussi souvent que possible dans les classes. class Nombre { public : void setnbre(int n) { nbre = n; } // méthodes constantes int getnbre() const { return nbre; } void affiche() const; private : int nbre; }; inline void Nombre::affiche() const { cout << "Nombre = " << nbre << endl; }
class vector { private: double x,y; /* coordinate */ public: vector(); /* default constructor: */ vector(double, double); /* constructor: */ double get_x() const; double get_y() const; }; class line_segment { private: vector a,b; /* end points */ public: line_segment(vector, vector); /* constructor: a,b */ vector get_a() const; vector get_b() const; void set_a(vector); void set_b(vector); vector middle() const; };