begin process at 2008 08 21 05:27:28
1 229 217 membres
48 nouveaux aujourd'hui
14 260 membres club

Vous ne trouvez pas de réponse à votre problème ? Alors posez la question dans le forum.
Souvenez-vous qu'il n'y a jamais de question bête, mais rester dans l'ignorance parce que l'on n'ose pas poser une question, ça c'est une erreur !

INTRODUCTION AU C++


Information sur le tutorial

Catégorie :Divers Date de création : 13/06/2007 12:04:36 Vu : 11 097 fois

Note :
Aucune note

Commentaire sur cette source (1)
Ajouter un commentaire et/ou une note

Description

cery good

Tutorial

le C++

Introduction pour qui connaît le C

Cette page n'est pas destinée au débutant. Je suppose que le lecteur connait bien le C standard (ANSI). Sinon, vous pouvez regarder mon poly C++ prévu pour le débutant. Vous y trouverez également des informations supplémentaires, en particulier les TP. Plusieurs autres sites existent sur C++ (voir haut de la page). Vous pouvez également voir mon cours C s'adressant à tous, du débutant à l'avancé.

1) INTRODUCTION

Le C++ est LA solution pour passer progressivement aux L.O.O. (langages orientés objet), sans avoir à réécrire l'existant, ainsi qu'en gardant la possibilité d'optimiser certaines parties du programme en restant "proche de la machine".

a) qu'est-ce qu'un objet ?

une STRUCTure regroupant des données (on dit aussi attributs ou champs) et les fonctions (méthodes) pour les manipuler. Par exemple, l'objet vecteur est composé de trois réels; et de diverses opérations : produit scalaire, vectoriel,...

b) pourquoi un L.O.O. ?

Le passage de l'assembleur aux langages structurés a permis d'obtenir des programmes maintenables : on peut les comprendre, les modifier, les améliorer : on a une structure de programme claire. Par contre si l'on décide de modifier de manière importante la structure des données (par exemple remplacer le tableau des données par une liste chaînée), il fallait réécrire tout le programme. Les objets structurent les données : en changeant la structure d'un objet, il suffit de modifier ses "méthodes" pour que la transformation s'applique à tout le programme. La programmation est plus simple, les méthodes (fonctions en C) sont classées par types de données plutôt que séquentiellement. De plus elles sont organisées hiérarchiquement (arborescence = bidimensionnel plutôt que séquentiel = linéaire).

c) avantages - inconvénients ?

  • Programmes plus faciles à écrire et à maintenir. Modification aisée, y compris des types de données. Modularité accrue (un objet bien défini reservira dans de nombreux programmes).
  • Programme résultant (exécutable) quelquefois un peu moins efficace (plus gros et moins rapide, mais aujourd'hui le prix de la vitesse et de la mémoire est inférieur au coût d'une optimisation de programme, on n'écrit plus grand chose en assembleur).

d) Pourquoi C++ ?

C++ est sûrement un mauvais L.O.O. (du point de vue du puriste), par contre il permet de garder tous les avantages du C : portable, possibilité d'utiliser différents niveaux d'optimisation au sein d'un même programme (objets - langage structuré classique - assembleur). Il permet de passer en douceur aux objets, mais surtout de garder et réutiliser toutes les bibliothèques existantes. Bien que plus strict que C, il acceptera à peu près tout, donc sera avare en messages d'erreur de compilation. C'est le programmeur qui doit se forcer à programmer "objets", s'il ne le fait pas le compilateur ne le prévient même pas. Comme vous le verrez ici, le passage aux objets (si l'on connaît déjà C) est très simple.

SMALLTALK est quand à lui 100% objet, mais vous forcerait à réécrire tous vos programmes (et surtout repenser leur organisation). JAVA est un peu entre les deux, plus simple mais aussi plus limité dans ses possibilités que C++ (notion de pointeurs inutile, pas de surcharge des opérateurs, pas d'héritage multiple...).

2) PREMIERES SPECIFICITES C++

a) commentaires

Les commentaires /* ... */ restent possibles, on y a ajouté les commentaires commençant par // et se finissant à la fin de la ligne.

b) entrées-sorties (flux)

à condition d'inclure <iostream.h> (et donc pas <stdio.h>), on peut utiliser cout (pour afficher à l'écran) et cin (pour lire sur le clavier). Exemple :

float P; int Nb;cout << "prix unitaire ? ";cin >> P;cout << "Nombre ? ";cin >> Nb;cout.precision(2); //manipulateur (fonction membre)de cout : tous les                   //flottants QUI SUIVENT seront affichés avec                   // 2 chiffres après la virgulecout << "prix total : " << P*Nb << "F \n";

L'avantage de ces fonctions est qu'elles peuvent être plus facilement surchargées que printf et scanf (par exemple étendues aux tableaux). Les flux fstream possèdent les mêmes fonctionnalités pour les fichiers (je ne détaille pas).

c) mot clef const

    const type var=valeur;

contrairement au #define, ceci permet une analyse syntaxique (on garde les #define pour les "réécritures" et compilations conditionnelles). Exemple d'erreur supprimée :

   #define max 10; //le ; est en trop   x=max+1; //les compilateurs diront "signe + interdit ici"

d) passage d'arguments par adresse

Pour qu'un argument d'une fonction soit passé par adresse plutôt que par valeur, il suffit d'ajouter le signe & dans l'entête de la fonction (qui doit être prototypée avant toute utilisation). Ceci évite l'écriture "pointeur" tant pour les arguments réels que formels :

void echange(int &a, int &b) {int z=a;a=b;b=z;}int X,Y;echange(X,Y);

e) arguments par défaut

On peut déclarer des valeurs par défaut aux arguments d'une fonction (uniquement dans le prototype, pas dans l'entête). Les arguments réels peuvent être omis en commençant par le dernier (impossible d'omettre les premiers si l'on veut préciser un suivant).

void maFonction(int argument1=18,argument2=3);maFonction(1,2); //c'est moi qui fixe toutmaFonction(10);  //le deuxiéme argument vaudra 3maFonction();    //les deux sont fixés par défaut

f) résolution de portée

Si vous disposez de deux (ou plus) entités (donnée ou méthode) de même nom, en C standard seule la plus locale est accessible. "ou::nom" permet en C++ de préciser de quel nom on parle (en général "ou" correspond à une classe, "::nom" pour accéder à une variable globale).

On peut aussi désormais définir des espaces de noms :

namespace truc { ...... } 

Tout ce qui est défini dans truc (donc entre les accolades) est différent de ce qui est défini autre part. A l'extérieur de truc, on peut néanmoins accéder à ses composantes par truc::NomDeLaComposante. On peut aussi utiliser la déclaration :

using truc;
qui permet d'accéder à toutes les composantes de truc (dans la portée de la déclaration, évidement). Par exemple, tous les objets et méthodes standards du C++ sont dans l'espace de nom "std", en toute rigueur il faudrait toujours dire std::cout<..... ou au moins using std; en déclaration globale (sauf si on a prévenu le compilateur).

3) VOCABULAIRE

a) la classe

Une classe définit un regroupement de données et de méthodes. C'est donc une extension des STRUCT du C :

class MaClasse {déclaration données et méthodes } MonInstance;

Ne pas oublier le ; final même quand on ne déclare pas d'instance ici (en général il vaut mieux séparer les déclarations de classes qui devraient être globales, et les instances qui devraient plutôt être locales). En fait les mots clef struct et union permettent également la déclaration de méthodes en plus de données, simplement elles sont par défaut publiques (accessibles aux autres classes) alors que pour une classe elles sont par défaut privées.

La classe n'est que la description d'un type d'objets, comme on dirait "une voiture c'est 4 roues (diamètre, taille, pression...), un moteur (puissance, cylindrée..), ça peut avancer et freiner...".

b) l'instance

L'instanciation est la création d'un objet d'une classe. Une instance est donc à peu près ce qu'on appelait avant une variable. "Ma voiture" et "La voiture de mon voisin" sont des instances de la classe "voiture". L'adresse de l'instance actuelle est appelée "this" (sans avoir à la déclarer).

c) l'héritage

Les classes sont structurées de manière arborescente. Si l'on crée une classe d'objets A (dite classe de base), on peut créer une classe B qui "dérive" de A : elle en hérite toutes les composantes (données et méthodes). On peut, à partir de C++ version 2, utiliser l'héritage multiple (une classe hérite de plusieurs classes de base), alors que ce n'était pas possible avant (attention, ce cas là est suffisamment complexe pour qu'il soit interdit en Java) .

d) la surcharge

On peut décrire plusieurs méthodes de même nom, à condition que chacune s'applique à des types de données différents. Par exemple on peut définir int puissance(int,int) et float puissance(float,float), les deux fonctions ayant une implémentation différente suivant le type de données, c'est le compilateur qui choisira en fonction des types des arguments. On peut même surcharger les opérateurs classiques du C (redéfinir + pour les vecteurs par exemple). On ne peut pas surcharger deux fonctions ayant exactement les mêmes types d'arguments mais retournant un type différent (produits scalaire et vectoriel par exemple, il faudra distinguer les deux par leur signe opératoire, ^ et * par exemple)

e) le constructeur

Pour chaque classe, il existe une méthode nécessaire (mais non obligatoire, si on ne la définit pas le compilateur en crée une par défaut) : le constructeur. Son nom est toujours le même que celui de la classe. Il est appelé implicitement à chaque nouvelle création d'instance ou explicitement par la fonction new (correspond au malloc, mais c'est le compilateur qui détermine la taille nécessaire). Le constructeur est une fonction qui ne retourne rien (même pas void). Le destructeur est appelé implicitement à la destruction d'un objet ou explicitement par delete. Remarque : le constructeur peut affecter une valeur à un membre constant (mais qui ne pourra pas changer jusqu'à sa destruction).

4)APPLICATIONS

a)simple

class Point   {    int X;intY; //les données    int GetX(void) {return X;} // déclaration "interne" ou "inline"    int GetY(void); // déclaration externe    Point (int NewX=0, int NewY=0) {X=NewX;Y=NewY;} //déclaration        //interne du constructeur, avec initialisation par défaut  };int Point::GetY(void)   {return Y;} //déclaration "externe", il faut préciser               //à quelle classe elle se rapporte, ici POINT.               //dans la réalité j'aurai plutot utilisé une déclaration interne

si je déclare :

   Point P(5,10); //appel automatique du constructeur à la déclaration   int coordX;

je peux par exemple appeler la fonction (attention, pas n'importe où, voir paragraphe suivant) :

   coordX=P.GetX();

b) accès aux membres d'une classe

les membres d'une classe peuvent être

  • privés (private), c'est à dire accessible uniquement aux autres membres de cette classe;
  • protégés (protected), c'est à dire accessibles au membres de la classe et ses classes dérivées (par héritage);
  • publics (public), c'est à dire accessibles "classiquement" : même portée qu'une déclaration classique C.

Exemple :

class Point   {    int X;intY;    //privé par défaut  public :         // tout ce qui suit est public    int GetX(void) {return X;}       // ceci permet d'accéder aux                //infos sans savoir comment elles ont été stockées    int GetY(void) {return Y;}    Point (int NewX, int NewY) {X=NewX;Y=NewY;}  };

On peut utiliser les 3 accès, autant de fois que l'on veut et dans n'importe que ordre. L'accès qui s'applique est le dernier spécifié (ou celui par défaut, private pour class et public pour struct).

Si l'on veut vraiment programmer "objets", il faudrait déclarer privés tous les attributs d'un objet (ou protégés), et ne mettre publiques que les méthodes pemettant de les manipuler. Pour l'objet "point" ci dessus, son utilisation doit être indépendante de l'organisation interne des données, deux variables X et Y ou un tableau par exemple.

c) héritage

class Point   {  protected: //accessible uniquement par héritage    int X;intY;   public : // accessible partout    int GetX(void) {return X;}    int GetY(void) {return Y;}    Point (int NewX=0, intNewY=0) {X=NewX;Y=NewY;}  };class Pixel : public Point //dérive de point,   {  protected:    int couleur;  public :    Pixel (int nx,int ny,int coul=0);    void allume(void);    void allume(int couleur); //surcharge : on peut allumer avec une autre couleur    void eteind(void);  };

Les accès dérivés sont le plus restrictif entre celui défini dans la classe de base et celui précisé lors de la dérivation (ici dérivation publique, les accès restent inchangés sauf pour les privés qui sont inaccessibles).

Pixel::Pixel(int nx,int ny,int coul):Point(nx,ny) // je précise la           //liste (séparée par des virgules) des constructeurs           //(sinon val par défaut), je n'ai plus qu'à construire les            //ajouts par rapport à la classe de base  {couleur=coul;}void Pixel::allume(void)   {g_pixel(X,Y,couleur);} //g_pixel : une fonction qui allume un pixel à l'écranvoid Pixel::allume(int coul)   {g_pixel(X,Y,couleur=coul);}void Pixel::eteind(void)   {allume(0);}

On pourrait maintenant définir une classe segment contenant un pixel et un point (la couleur n'a besoin d'être stockée qu'une fois). On redéfinirait des méthodes de même nom : Segment::allume...

d) new, delete

Une déclaration simple d'une instance est statique (définie lors de la programmation, pas de l'exécution). L'instanciation dynamique par contre se fait par l'opérateur new (qui va permettre d'oublier les malloc).

  Pixel *ptrPixel = new Pixel(100,100,1); // construction explicite  ptrPixel->allume(); //utilisation  delete ptrPixel; //destruction, le destructeur par défaut est souvent suffisant

Si l'on veut définir explicitement le destructeur d'une classe (pour fermer un fichier par exemple), on utilise le nom de la classe précédé de ~ :

Point::~Point() {...}

e) surcharge d'un opérateur

utilisons le signe + pour additionner deux Points (par adresse pour éviter de recopier en local):

Point operator+ (Point &P1, Point &P2)  { Point res(P1.GetX()+P2.GetX(),P1.GetY()+P2.GetY(),P1);    return res;}

On peut aussi surcharger << (pour cout) :

ostream& operator << (ostream& flux, Point& P)  { flux << "[" << P.GetX() << "," << P.GetY() << "]";     return flux;  }

Ces deux surcharges sont globales. Mais on peut également les définir comme fonctions membres :

class Point {  .....  Point operator + (Point &P);  Point operator = (Point &P);};Point::Point::operator + (Point & P)  {Point r;r.X=this->X+P.X;r.Y=this->Y+P.Y;return r;}void main(void) {  Point A,B,C;  A=B; //appelle A.operator=(B)  B+C; //appelle A.operator+(B)  A=B+C; //marchera aussi, mais A=B=C je n'en suis pas sur  }

f) classes virtuelles

Soient : une classe A, deux classes B et C dérivant de A, une classe D dérivant de B et C. Nous aurons dans D deux instances de A (qui peuvent être différentes). Mais si une seule instance de A suffisait, il suffit de les déclarer "virtuelles" :

class A {...};class B : virtual public A {...};class C : virtual public A {...};class D : public B, public C {...};

Le constructeur de D appellera une seule fois celui de A

g) polymorphisme

Si plusieurs classes (point, ligne, segment) possèdent des méthodes de même signature (écriture similaire du prototype), on peut éviter de réécrire des fonctions dont le contenu serait identique mais d'appliquant à des objets différents (déplacer=éteindre+ajouter+allumer pout tous mes objets). On peut pour cela utiliser les fonctions virtuelles (dynamiques : le choix de la fonction a utiliser est déterminée à l'exécution) ou les fonctions templates (statiques : le choix des fonctions est fait à la compilation). Voyez l'exemple complet.

5) EXEMPLE COMPLET (commenté)

#include <iostream.h>#include "graphiq0.cpp"  // petite biblio graphique. contient g_init,                         // g_fin, g_pixel, g_ligne (voir plus loin)class Point  {  protected: //accessible uniquement par héritage    int X;int Y;  public : // accessible partout    int GetX(void) {return X;}  //hors héritage on ne peut que lire,                                //pas écrire    int GetY(void) {return Y;}    Point (int NewX=0, int NewY=0) {X=NewX;Y=NewY;}  };//On peut surcharger << (pour cout) : marche pour le point et ses héritiers :ostream& operator << (ostream& flux, Point& P)  { flux << "[" << P.GetX() << "," << P.GetY() << "]";    return flux;  }class Pixel : public Point //dérive de point  {  protected:    int couleur;  public :    Pixel (int nx=0,int ny=0,int coul=0);    virtual void allume(void);    virtual void allume(int couleur); //surcharge : on peut allumer avec une autre couleur    void eteind(void);     // héritable dynamiquement    void ajoute(int plusx=1,int plusy=1);    void deplace(int plusx=1,int plusy=1);// héritable dynamiquement    int GetCouleur(void);  };Pixel::Pixel(int nx,int ny,int coul):Point(nx,ny) //je passe ainsi les                      // arguments au constructeur de Point (sinon il prend                      // celui par défaut, cad sans arguments  {couleur=coul;}void Pixel::allume(void)  {g_pixel(X,Y,couleur);}void Pixel::allume(int coul)  {g_pixel(X,Y,couleur=coul);}void Pixel::eteind(void)  {allume(0);}   //allume est virtuelle, toute classe dérivée      //possédant allume possèdera automatiquement éteintvoid Pixel::ajoute(int plusx,int plusy)  {X+=plusx;Y+=plusy;}void Pixel::deplace(int plusx,int plusy)  { int OldCol=couleur;eteind();couleur=OldCol;    ajoute(plusx,plusy);allume();}int Pixel::GetCouleur(void)  {return couleur;}class Segment : public Pixel  {  protected :    int LX;int LY;  public :    Segment (int x0=0,int y0=0,int lx=0,int ly=0,int coul=1);    void allume(void);    void allume(int couleur);     //le reste est hérité  };Segment::Segment(int x0,int y0,int lx,int ly,int coul):Pixel(x0,y0,coul)  {LX=lx;LY=ly;}void Segment::allume(void)  {g_ligne(X,Y,X+LX,Y+LY,couleur);}void Segment::allume(int coul)  {g_ligne(X,Y,X+LX,Y+LY,couleur=coul);}class Rectangle : public Pixel  {  protected :    int LX;int LY;  public :    Rectangle (int x0=0,int y0=0,int lx=0,int ly=0,int coul=1);    void allume(void);    void allume(int couleur);   };Rectangle::Rectangle(int x0,int y0,int lx,int ly,int coul):Pixel(x0,y0,coul)  {LX=lx;LY=ly;}void Rectangle::allume(void)  {   g_ligne(X,Y,X+LX,Y,couleur);   g_ligne(X+LX,Y,X+LX,Y+LY,couleur);   g_ligne(X+LX,Y+LY,X,Y+LY,couleur);   g_ligne(X,Y+LY,X,Y,couleur);  }void Rectangle::allume(int coul)  {couleur=coul;this->allume();}//utilisons le signe + pour additionner deux objets quels qu'ils soient:template <class T> //T est un type de classe "variable"T operator+ (T &P1,Point &P2)  {T res(P1.GetX()+P2.GetX(),P1.GetY()+P2.GetY(),P1);return res;}// ce qui ne marche pas pour les types autres que point : le constructeur// prend les autres arguments par défaut (longueur, couleur)void main(void) {  g_init();  Pixel *ptrPixel = new Pixel(100,100,1);  ptrPixel->allume(); //utilisation  cout << *ptrPixel << ':' << ptrPixel->GetCouleur()<< '\n';  delete ptrPixel; //destruction, le destructeur par défaut est souvent suffisant  Segment s(50,50,100,0,10);  s.allume();  Rectangle r(150,150,100,100,4);  r.allume();  cin.get();  //équivalent du getch  Point decal(-25,25);  cout << (s+decal) << endl;  (r+decal).allume();  s.deplace(0,100);  r.deplace(0,100);  cout << r << endl; //endl envoie un \n  cin.get();  g_fin(); }


----------- La bibliothèque graphique pour Tubo C (DOS) - Graphiq0.cpp -----------

/* fichier inclus pour INTRO.CPP, version Turbo C 3.0 P.Trau 22/2/97 *//* bibliothèque graphique minimale . Ce fichier contient les fonctions   qu'il faudra réécrire si l'on change de compilateur. Il faut savoir :   - passer en mode graphique : g_init (sauf si vous y êtes déjà)   - quitter le mode graphique : g_fin   - allumer un point : g_pixel   - à la rigueur tracer une ligne : g_ligne (ou le laisser tel quel,     il n'utilise que g_pixel)*/#include <graphics.h>#define abs(X) ((X>0)?(X):(-(X)))void g_init(void) {  int gdriver = DETECT, gmode, errorcode;  initgraph(&gdriver, &gmode, "");  errorcode = graphresult();  if (errorcode != grOk)#ifdef __cplusplus      cout << "g_erreur: " << grapherrormsg(errorcode) <<"\n";#else      printf("g_erreur: %s\n", grapherrormsg(errorcode));#endif  setcolor(getmaxcolor()); }void g_fin(void) {closegraph();}void g_pixel(int x,int y,int color) {putpixel(x,y,color);}void g_ligne(int xd,int yd,int xf,int yf,int color) {  int somme,pasx,pasy,deltax,deltay;  deltax=abs(xf-xd);deltay=abs(yf-yd);  pasx=(xd<xf)?1:-1;  pasy=(yd<yf)?1:-1;  g_pixel(xd,yd,color);  if (deltax>deltay) /* ils sont déjà positifs */   {    somme=deltax/2;    while(xd!=xf)     {      xd+=pasx;      somme+=deltay;      if(somme>=deltax) {somme-=deltax;yd+=pasy;}      g_pixel(xd,yd,color);     }   }  else   {    somme=deltay/2;    while(yd!=yf)     {      yd+=pasy;      somme+=deltax;      if(somme>=deltay) {somme-=deltay;xd+=pasx;}      g_pixel(xd,yd,color);     }   } }


  • signaler à un administrateur
    Commentaire de NitRic le 07/10/2007 21:06:37

    DOS n'existe plus depuis Windows 2000 ...
    <iostream.h>/etc. sont obsolètes, y'a plus de « .h » aujourd'hui
    faudrait peut-être penser à changer de compilateur parce que Turbo C c'est comme ...
    un tantinet vieux ...

Ajouter un commentaire

Pub



Appels d'offres

Recherche developpeur ...
Budget : 700€
extraction dinformatio...
Budget : 300€
campagne Adwords
Budget : 5 000€

CalendriCode

Août 2008
LMMJVSD
    123
45678910
11121314151617
18192021222324
25262728293031

Boutique

Boutique de goodies CodeS-SourceS