1. Introduction au C++



sommaire chapitre 1

1.5. Améliorations du C++ par rapport au C

1.5.1. Le qualificateur const

Le langage C++ introduit le qualificateur const qui indique qu'une donnée ne sera pas modifiée. Situé au niveau d'un sous-programme const indique qu'aucune donnée ne sera modifiée à l'intérieur du sous-programme. le qualificateur const peut également servir à :

  1. // instead of #define N 5
  2. const int N = 5;
  3.  
  4. int R;
  5.  
  6. class A {
  7.     int a;
  8.  
  9.   public:
  10.     // int is already const: const int f(...)
  11.     const int f(const int n) const {
  12.       N = 1; // forbidden !!
  13.       n = 3; // forbidden (2nd const)          
  14.       a = 2; // forbidden (3rd const)
  15.  
  16.       R = 5; // allowed
  17.     }
  18. };
  19.  
  20.  
  21.  
  22.  
  23.  

En ce qui concerne les pointeurs constants, on peut les déclarer de plusieurs manières :

  1. #include <iostream>
  2. #include <string.h>
  3. using namespace std;
  4.  
  5. const char *ptr1 = "modifiable";
  6. const char * const ptr2 = "non modifiable";
  7. char ptr3[] = "a string";
  8.  
  9. int main() {
  10.     ptr1 = "coucou";
  11.     cout << "ptr1 = " << ptr1 << endl; 
  12.    
  13.     // 1. not possible !!!!
  14.     // error: assignment of read-only variable ‘ptr2’
  15.     ptr2 = "toto";
  16.     // 2. error: invalid conversion from ‘const char*’ to ‘char*’ [-fpermissive]
  17.     strcpy(ptr2, "toto");
  18.    
  19.     strcpy(ptr3, "toto");
  20.     cout << "ptr3 = " << ptr3 << endl;
  21.    
  22.     // 3. will produce overflow and Segmentation fault
  23.     strcpy(ptr3, "totototototototototototo");
  24.     cout << "ptr3 = " << ptr3 << endl;
  25.        
  26.     return 0;
  27. }
  28.  

1.5.2. le qualificateur enum

Le mot clé enum permet de déclarer des énumérations ou un nouveau type (entier) associé à une liste de valeurs.

C++11 introduit les enum class que l'on qualifie d'énumération à portée (scoped) pour lesquelles il est nécessaire de spécifier le nom qui définit l'énumération :

  1. #include <iostream>
  2. #include <cstdlib>
  3. using namespace std;
  4.  
  5. // unscoped enumeration
  6. // zero = 0, un = 1, ...
  7. enum { zero, un, deux, trois };
  8.  
  9. // unscoped enumeration
  10. // mardi = 2, ....
  11. enum Jour { lundi=1, mardi, mercredi, jeudi, vendredi, samedi, dimanche };
  12.  
  13. // Scoped enumeration
  14. enum class Mois { Janvier = 1, Fevrier, Mars, Avril, Mai,
  15.     Juin, Juillet, Aout,
  16.     Septembre, Octobre, Novembre, Decembre };
  17.  
  18.  
  19. int main() {
  20.  
  21.     // un_jour = 2, no scope
  22.     Jour un_jour = mardi;
  23.     // can convert easily to integer if unscoped
  24.     int m = un_jour;
  25.    
  26.     // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  27.     // because unscoped enum 'mardi' does not reference
  28.     // the constant of Jour
  29.     int mardi = -123;
  30.     // you have to write this:
  31.     mardi = Jour::mardi;
  32.    
  33.  
  34.     cout << "un_jour = " << un_jour << endl;
  35.     cout << "mardi = " << mardi << endl;
  36.  
  37.     // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  38.     // still possible to write this !
  39.     int Janvier = -56;
  40.    
  41.     // need to cast a scoped constant
  42.     cout << "Fevrier = " << static_cast<int>(Mois::Fevrier) << endl;
  43.    
  44.     return EXIT_SUCCESS;
  45. }
  46.  
un_jour = 2
mardi = 2
Fevrier = 2

Par défaut, les énumérations à portée sont de type int mais on peut modifier leur comportement :

enum Class Status: std::uint32_t;
enum Class Jour: std::uint8_t;

1.5.3. Espace de nom (namespace)

Pour des projets de grande envergure, des conflits de noms apparaissent pour certains noms de classes ou de variables. C'est par exemple le cas si on tente d'utiliser différentes librairies de calcul ensemble comme BLAS, GSL et MKL. Si on utilise le produit de matrices, le sous-programme cblas_dgemm existe sous BLAS et MKL. De même, les constantes comme CblasNoTrans est redéfnie dans chaque librairie.

Le C++ offre un moyen de remédier à ce problème en limitant la portée des symboles à une zone appelée espace de nommage ou namespace, appliqué à l'exemple précédent on aurait :

namespace cblas {
  enum CBLAS_TRANSPOSE {CblasNoTrans=111, CblasTrans=112, CblasConjTrans=113};
  void cblas_dgemm(...);
}

namespace mkl {
  typedef enum {CblasNoTrans=111, CblasTrans=112, CblasConjTrans=113} CBLAS_TRANSPOSE;
  void cblas_dgemm(...);
}

namespace gsl {
  typedef  enum CBLAS_TRANSPOSE   CBLAS_TRANSPOSE_t;
  int  gsl_blas_dgemm(...);
}

Pour accéder à une déclaration de l'espace de nommage on peut utiliser l'opérateur de résolution de portée :: ou alors utiliser l'instruction :

#include "mkl.h"
#include "cblas.h"
#include "gsl_cblas.h"

cblas::cblas_dgemm(cblas::CblasRowMajor, cblas::CblasNoTrans, ...);
mkl::cblas_dgemm(mkl::CblasRowMajor, mkl::CblasNoTrans, ...)
gsl::gsl_blas_dgemm(gsl::CblasRowMajor, gsl::CblasNoTrans, ...);

En particulier std est l'espace de nommage réservé à la STL (Standard Template Library).

Attention à ne pas utiliser un nom qui correspond à une fonction ou mot-clé du C. (ex: system)

Dans la partie header (.h) définir le namespace, puis dans la partie implementation (.cpp) l'utiliser :

  1. #ifndef NAMESPACE_H
  2. #define NAMESPACE_H
  3.  
  4. #include <iostream>
  5. using namespace std;
  6.  
  7. namespace my_namespace {
  8.  
  9.     class A {
  10.     protected:
  11.         static int count;
  12.         int data;
  13.        
  14.     public:
  15.         A();
  16.         A(int d);
  17.         static int get_count();
  18.        
  19.         friend ostream& operator<<(ostream& out, A& obj);
  20.     };
  21.    
  22. }
  23.  
  24. #endif
  25.  
  26.  
  1. #include "c1_namespace.h"
  2.  
  3. using namespace my_namespace;
  4.  
  5. int A::count = 0;
  6.  
  7. A::A() {
  8.     data = 0;
  9.     ++count;
  10. }
  11.  
  12. A::A(int d) {
  13.     data = d;
  14.     ++count;
  15. }
  16.  
  17. int A::get_count() {
  18.     return count;
  19. }
  20.  
  21. // !!!!!!!!!!!!!!!!!
  22. namespace my_namespace {
  23.  
  24.     ostream& operator<<(ostream& out, A& obj) {
  25.         out << "A.data = " << obj.data;
  26.         return out;
  27.     }
  28.  
  29. }
  30.  
  31. int main() {   
  32.     A a(3);
  33.    
  34.     cout << a << endl;
  35.  
  36.     cout << "count = " << A::get_count() << endl;
  37.     return 0;
  38. }
  39.  

1.5.4. Entrées / sorties

La STL introduit des flux (stream) sur lesquels on peut réaliser des entrées/sorties. Par défaut on trouve les flux suivants :

On manipule ces flux comme en Java et non plus comme en C mais en utilisant des opérateurs spécifiques. Le symbole endl (enf of line) représente le caractère '\n'

// include the librairy for input/output
#include <iostream>
using namespace std;

int n;
cin >> n; // lecture d'un entier
cout << "la valeur de n est " << n << endl;

1.5.4.a  flux de sortie

Des manipulateurs de flux sont définis qui permettent de formater l'affichage des données :

1.5.4.b  flux d'entrée

Utilisation de la fonction istream& getline(istream& is, string& str, char delim) pour lire des chaines de caractères.

1.5.5. Typage

Afin de remédier aux déficiences du langage C concernant le typage, de nouveaux opérateurs sont introduits :

// tranform integer into double
int i=1;
double d = static_cast<double>(i);

int f(int& n) {
  return n+1;
}

int g(const int& n) {
  return f(const_cast<int>>(n));
}

1.5.6. Paramètres par défaut

La possibilité est offerte en C++ de donner une valeur par défaut uniquement aux paramètres les plus à droite dans la définition d'un sous-programme :

void fonction(int n, int m = 1) {
  cout << n << " " << m << endl;
}

f(1,5); // n = 1, m = 5
f(1); // equivalent to f(1,1) because m was not specified