1. Introduction au C++
1.6. Classe, données membres, méthodes
1.6.1. Vocabulaire
La classe permet l'encapsulation des données qui consiste à cacher les données d'une classe aux autres classes.
Les données doivent alors être accédées par les méthodes (fonctions membres) de la classe.
| Langage C |
Langage C++ |
| structure |
classe / objet |
| attribut / champ |
donnée membre |
| sous-programme |
méthode / fonction membre |
Vocabulaire lié aux classes
1.6.2. Déclaration d'une classe de base (classe mère)
Lors de la déclaration de la classe, on place les données membres, les méthodes, ...
dans différentes sections qui imposent des restrictions d'accés :
- public : les données membres et méthodes sont accessibles en dehors de la classe
- protected : les données membres et méthodes sont accessibles par les classes qui héritent de cette classe
- private : les données membres et méthodes sont accessibles uniquement par la classe qui les déclare
Afficher le code
class Nom {
public:
// données membres, méthodes, types
// visibles ou accessibles en dehors de la classe
...
protected:
// données membres, méthodes, types
// visibles ou accessibles par héritage
...
private:
// données membres, méthodes, types
// accessibles par la classe uniquement
...
};
On peut déclarer la classe :
- dans un seul fichier entête .h par exemple
Afficher le code ens/inra/c1_class_person.cpp
#include <iostream>
#include <string>
using namespace std;
/**
* Definition of a Person
* identified by its name and age
*/
class Person {
// ==============================================
// data members
// ==============================================
protected:
// name of the person (default value is empty string)
string name;
// age of the person (default value is 0)
int age;
// ==============================================
// methods
// ==============================================
public:
/**
* default constructor
*/
Person() {
name = "";
age = 0;
}
/**
* constructor with 2 arguments
* @param n name of the person
* @param a age of the person
*/
Person(string n, int a) {
name = n;
age = a;
}
/**
* getter for name
* @return name of the person
*/
string get_name() {
return name;
}
/**
* getter for age
* @return age of the person
*/
int get_age() {
return age;
}
/**
* setter for name
* @param n name of the person
*/
void set_name(string n) {
name = n;
}
/**
* setter for age
* @param a age of the person
*/
void set_age(int a) {
age = a;
}
/**
* print class contents
* @param output stream reference
* @return output stream reference
*/
ostream& print(ostream& out) {
out << name << ", " << age;
return out;
}
friend ostream& operator<<(ostream& out, Person& p) {
return p.print(out);
}
};
ou dans deux fichiers :
- un fichier entête .h qui représente l'interface
- un fichier .cpp qui représente implantation
Afficher le code ens/inra/c1_class_person_interface.h
#ifndef PERSON_H
#define PERSON_H
#include <iostream>
#include <string>
using namespace std;
/**
* Definition of a Person
* identified by its name and age
*/
class Person {
// ==============================================
// data members
// ==============================================
protected:
// name of the person (default value is empty string)
string name;
// age of the person (default value is 0)
int age;
// ==============================================
// methods
// ==============================================
public:
/**
* default constructor
*/
Person();
/**
* constructor with 2 arguments
* @param n name of the person
* @param a age of the person
*/
Person(string n, int a);
/**
* getter for name
* @return name of the person
*/
string get_name();
/**
* getter for age
* @return age of the person
*/
int get_age();
/**
* setter for name
* @param n name of the person
*/
void set_name(string n);
/**
* setter for age
* @param a age of the person
*/
void set_age(int a);
/**
* print class contents
* @param output stream reference
* @return output stream reference
*/
ostream& print(ostream& out);
friend ostream& operator<<(ostream& out, Person& p) {
return p.print(out);
}
};
#endif
Afficher le code ens/inra/c1_class_person_implementation.cpp
#include "person.h"
// default constructor
Person::Person() {
name = "";
age = 0;
}
// constructor with 2 arguments
Person::Person(string n, int a) {
name = n;
age = a;
}
// getter for name
string Person::get_name() {
return name;
}
// getter for age
int Person::get_age() {
return age;
}
// setter for name
void Person::set_name(string n) {
name = n;
}
// setter for age
void Person::set_age(int a) {
age = a;
}
// display class
ostream& Person::print(ostream& out) {
out << name << ", " << age;
return out;
}
1.6.3. Typage : typeid, typeinfo
L'opérateur typeid du C++ permet de connaître le type d'un objet :
Afficher le code ens/inra/c1_class_typeid.cpp
#include <iostream>
#include <string>
#include <typeinfo>
#include <cstdio>
using namespace std;
class Base {
};
class Derived : public Base {
};
int main() {
int integer = 1;
float real32 = 3.14;
int *ptr_integer = &integer;
cout << "type of integer = " << typeid(integer).name() << endl;
cout << "type of real32 = " << typeid(real32).name() << endl;
cout << "type of ptr_integer = " << typeid(ptr_integer).name() << endl;
const std::type_info& r1 = typeid(cout << integer);
cout << endl;
cout << "'cout<< integer' has type : " << r1.name() << endl;
const std::type_info& r2 = typeid( printf("%f\n", real32) );
cout << "'printf(\"%f\\n\", real32)' has type : " << r2.name() << endl;
Base b;
Derived d;
cout << "type of b = " << typeid(b).name() << endl;
cout << "type of d = " << typeid(d).name() << endl;
Base *p = (Base *) &b;
cout << "type of p = " << typeid(p).name() << endl;
Base *q = (Base *) &d;
cout << "type of q = " << typeid(q).name() << endl;
cout << "type of *q = " << typeid(*q).name() << endl;
if (typeid(p) == typeid(q)) {
cout << "p and q are the same" << endl;
}
if (typeid(*p) == typeid(*q)) {
cout << "*p and *q are the same" << endl;
} else {
cout << "*p and *q are different" << endl;
}
return 0;
}
type of integer = i
type of real32 = f
type of ptr_integer = Pi
1
'cout<< integer' has type : So
'printf("%f\n", real32)' has type : i
type of b = 4Base
type of d = 7Derived
type of p = P4Base
type of q = P4Base
type of *q = 4Base
p and q are the same
*p and *q are the same
1.6.4. Les Constructeurs
Les constructeurs sont appelés lors de la déclaration d'une variable
du type de la classe :
Afficher le code ens/inra/c1_class_cons.cpp
#include <string>
#include <iostream>
using namespace std;
class Person {
protected:
string name;
int age;
public:
/**
* default constructor with no argument
*/
Person() {
name = "";
age = 0;
}
/**
* constructor with name
* @param n name of the person
*/
Person(string n) {
name = n;
age = 0;
}
/**
* constructor with name and age
* @param n name of the person
* @param a age of the person
*/
Person(string n, int a) {
name = n;
age = a;
}
/**
* print class contents
* @param output stream reference
* @return output stream reference
*/
ostream& print(ostream& out) {
out << name << ", " << age;
return out;
}
friend ostream& operator<<(ostream& out, Person& p) {
return p.print(out);
}
};
// ====================================
// main function
// ====================================
int main() {
// call of Person()
// Note: don't use Person p1(); remove parenthesis
Person p1;
// call of Person(string n)
Person p2("toto");
// call of Person(string n, int a)
Person p3("toto", 10);
// define array of Persons
Person *tab;
// create an array of Persons, call default constructor
tab = new Person[10];
// call constructor Person(string n, int a) -std=c++11
tab = new Person[10] { Person("toto", 10) };
for (int i=0; i<10; ++i) {
cout << tab[i] << endl;
}
return 0;
}
tableaux d'objets
Lors de l'allocation d'un tableau d'objets, chacun des objets est initialisé avec le constructeur approprié.
S'il n'en existe aucun, une erreur est générée (C++11 avec liste d'initialisation).
tab = new Person[10] { Person("toto", 10) };
Mot clé explicit
Il est recommandé dorénavant d'utiliser le mot clé explicit devant les constructeurs qui comportent un argument afin d'éviter les conversions implicites et éventuellement donner des effets de bord indésirables.
exemple avec nombre complexe
Afficher le code ens/inra/c1_class_explicit_1.cpp
#include <iostream>
using namespace std;
/**
* class to implement complex number
*/
class Complex {
double real, imag;
public:
/**
* default constructor with no argument
*/
Complex() : real(0), imag(0) {
cout << "call default constructor" << endl;
}
/**
* copy constructor
*/
Complex(const Complex& c) {
real = c.real;
imag = c.imag;
}
/**
* constructor with 2 arguments but one is assigned !
*/
Complex( double r, double i = 0.0) {
cout << "call constructor with two arguments: real=" << r
<< ", imag=" << i << endl;
real = r; imag = i;
}
/**
* overloading of addition operator
*/
Complex operator+(const Complex& c) {
double tmp_real = real + c.real;
double tmp_imag = imag + c.imag;
return Complex(tmp_real, tmp_imag);
}
bool operator==(Complex rhs) {
return (real == rhs.real && imag == rhs.imag) ? true : false;
}
friend ostream& operator<<(ostream& out, Complex& c) {
out << "(" << c.real << "," << c.imag << ")";
}
};
int main() {
Complex one(1);
Complex sum;
cout << one << endl;
// implicit conversion of 3 into Complex(3, 0)
sum = one + 3;
cout << sum << endl;
// implicit conversion of 4 into Complex(4, 0)
if (sum == 4.0) {
cout << "result is valid" << endl;
} else {
cout << "error !!!" << endl;
}
return 0;
}
call constructor with two arguments: real=1, imag=0
call default constructor
(1,0)
call constructor with two arguments: real=3, imag=0
call constructor with two arguments: real=4, imag=0
(4,0)
call constructor with two arguments: real=4, imag=0
result is valid
Afficher le code ens/inra/c1_class_explicit_2.cpp
#include <iostream>
using namespace std;
/**
* class to implement complex number
*/
class Complex {
double real, imag;
public:
/**
* default constructor with no argument
*/
Complex() : real(0), imag(0) {
cout << "call default constructor" << endl;
}
/**
* copy constructor
*/
Complex(const Complex& c) {
real = c.real;
imag = c.imag;
}
/**
* constructor with 2 arguments but one is assigned !
* declared explicit !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/
explicit Complex( double r, double i = 0.0) {
cout << "call constructor with two arguments: real=" << r
<< ", imag=" << i << endl;
real = r; imag = i;
}
/**
* overloading of addition operator
*/
Complex operator+(const Complex& c) {
double tmp_real = real + c.real;
double tmp_imag = imag + c.imag;
return Complex(tmp_real, tmp_imag);
}
bool operator==(Complex rhs) {
return (real == rhs.real && imag == rhs.imag) ? true : false;
}
friend ostream& operator<<(ostream& out, Complex& c) {
out << "(" << c.real << "," << c.imag << ")";
}
};
int main() {
Complex one(1);
Complex sum;
cout << one << endl;
// *** explicit *** conversion of 3 into Complex(3, 0)
sum = one + Complex(3);
cout << sum << endl;
// *** explicit *** conversion of 4 into Complex(4, 0)
if (sum == Complex(4.0)) {
cout << "result is valid" << endl;
} else {
cout << "error !!!" << endl;
}
return 0;
}
exemple avec chaine de caractères
Voici un autre exemple pour lequel on obtient un résultat erroné : une chaine de 120 caractères car le code ASCII de 'x' vaut 120. :
Afficher le code ens/inra/c1_class_cons_explicit.cpp
#include <iostream>
#include <cstring>
using namespace std;
class MyString {
private:
int size;
char *str;
public:
// Default constructor
/* explicit */ MyString(int n) {
int i;
size = n;
str = new char [size+1];
for (i=0; i<size; ++i) str[i] = '.';
str[i] = '\0';
}
// A method to compare two Complex numbers
MyString(char *p) {
size = strlen(p);
str = p;
}
friend ostream& operator<<(ostream& out, MyString& s) {
out << "(" << s.size << ") " << s.str << endl;
return out;
}
};
int main() {
MyString s = 'x';
cout << s << endl;
return 0;
}
(120) ...............................................................................
.........................................
Le même exemple avec conversion explcite :
Afficher le code ens/inra/c1_class_explicit_2.cpp
#include <iostream>
using namespace std;
/**
* class to implement complex number
*/
class Complex {
double real, imag;
public:
/**
* default constructor with no argument
*/
Complex() : real(0), imag(0) {
cout << "call default constructor" << endl;
}
/**
* copy constructor
*/
Complex(const Complex& c) {
real = c.real;
imag = c.imag;
}
/**
* constructor with 2 arguments but one is assigned !
* declared explicit !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/
explicit Complex( double r, double i = 0.0) {
cout << "call constructor with two arguments: real=" << r
<< ", imag=" << i << endl;
real = r; imag = i;
}
/**
* overloading of addition operator
*/
Complex operator+(const Complex& c) {
double tmp_real = real + c.real;
double tmp_imag = imag + c.imag;
return Complex(tmp_real, tmp_imag);
}
bool operator==(Complex rhs) {
return (real == rhs.real && imag == rhs.imag) ? true : false;
}
friend ostream& operator<<(ostream& out, Complex& c) {
out << "(" << c.real << "," << c.imag << ")";
}
};
int main() {
Complex one(1);
Complex sum;
cout << one << endl;
// *** explicit *** conversion of 3 into Complex(3, 0)
sum = one + Complex(3);
cout << sum << endl;
// *** explicit *** conversion of 4 into Complex(4, 0)
if (sum == Complex(4.0)) {
cout << "result is valid" << endl;
} else {
cout << "error !!!" << endl;
}
return 0;
}
1.6.5. Le Destructeur
Il n'existe qu'un seul destructeur qui est chargé de libérer les ressources allouées
ou utilisées par le/les constructeur(s).
le destructeur ne possède aucun argument.
le destructeur est dorénavant déclaré virtuel en cas d'héritage afin
de permettre l'appel de la série des destructeurs.
afin de permettre l'utilisation de dynamic_cast il faut qu'il y
est une méthode virtuelle.
si aucun destructeur n'est déclaré le compilateur en crée un par défaut (C++11)
Afficher le code ens/inra/c1_class_destr.cpp
/**
* Definition of a Vector of integers
*/
class Vector {
// ==============================================
// data members
// ==============================================
protected:
// maximum number of elements
int max_elements;
// storage for the elements allocated dynamically
int *elements;
// ==============================================
// methods
// ==============================================
public:
/**
* constructor with initial size in number of elements
* @param me maximum number of elements
*/
Vector(int me) {
max_elements = me;
elements = new int [ max_elements ];
}
/**
* destructor
*/
virtual ~Vector() {
delete [] elements;
}
};
1.6.6. Constructeur par recopie
Le constructeur par recopie (copy constructor) est utilisé lorsqu'un constructeur prend
en paramètre un élément de la même classe que lui : la nouvelle instance de la classe sera
crée à partir de l'instance existante, il s'agit généralement d'une copie, d'où l'appellation
de copy constructor
Vector v1(100);
// create copy of v1
Vector v2(v1);
Le constructeur par recopie doit être déclaré de type : T(const T& )
Note: il existe un constructeur par recopie par défaut qui recopie les valeurs des données
membres de v1 dans v2, ce qui peut mener à faire crasher le programme.

Afficher le code ens/inra/c1_class_cons_copy.cpp
/**
* Definition of a Vector of integers
*/
class Vector {
// ==============================================
// data members
// ==============================================
protected:
// maximum number of elements
int max_elements;
// storage for the elements allocated dynamically
int *elements;
// ==============================================
// methods
// ==============================================
public:
/**
* constructor with initial size in number of elements
* @param me maximum number of elements
*/
Vector(int me) {
max_elements = me;
elements = new int [ max_elements ];
}
/**
* destructor
*/
virtual ~Vector() {
delete [] elements;
}
/**
* copy constructor
*/
Vector(const Vector& v) {
max_elements = v.max_elements;
elements = new int [ max_elements ];
memcpy(elements, v.elements, max_elements * sizeof(int));
}
};
1.6.7. Opérateur d'affectation
L'opérateur d'affectation doit être redéfini pour certaines classes, notamment si la
classe alloue des données. Il est utilisé dans le cas suivant :
Vector v1(100);
Vector v2(250);
v2 = v1;
D'un point de vue sémantique, il réalise le traitement du destructeur suivi du traitement
du constructeur par recopie.
Note: il existe un opérateur d'affectation par défaut qui recopie les valeurs des données
membres de v1 dans v2.
Il doit être déclaré de type : T& operator=(const T& )
Afficher le code ens/inra/c1_class_assignment_op.cpp
/**
* Definition of a Vector of integers
*/
class Vector {
// ==============================================
// data members
// ==============================================
protected:
// maximum number of elements
int max_elements;
// storage for the elements allocated dynamically
int *elements;
// ==============================================
// methods
// ==============================================
public:
/**
* constructor with initial size in number of elements
* @param me maximum number of elements
*/
Vector(int me) {
max_elements = me;
elements = new int [ max_elements ];
}
/**
* destructor
*/
virtual ~Vector() {
delete [] elements;
}
/**
* copy constructor
*/
Vector(const Vector& v) {
max_elements = v.max_elements;
elements = new int [ max_elements ];
memcpy(elements, v.elements, max_elements * sizeof(int));
}
/**
* assignment operator
*/
Vector& operator=(const Vector& v) {
// verifiy that we are not assigning the Vector to itself !!
if (&v != this) {
// destroy data previously allocated
delete [] elements;
// recreate a copy
max_elements = v.max_elements;
elements = new int [ max_elements ];
memcpy(elements, v.elements, max_elements * sizeof(int));
}
return *this;
}
};
1.6.8. Getters et setters
Les getters sont les méthodes qui permettent de récupérer la valeur
des données membres.
Les setters sont les méthodes qui permettent d'attribuer une valeur
aux données membres.
Afficher le code ens/inra/c1_class_getters_setters.cpp
#include <iostream>
#include <string>
using namespace std;
class Person {
protected:
string name;
int age;
public:
Person() {
name = "";
age = 0;
}
Person(string n, int a) {
name = n;
age = a;
}
// ********
// getters
// ********
string get_name() {
return name;
}
int get_age() {
return age;
}
// ********
// setters
// ********
void set_name(string n) {
name = n;
}
void set_age(int a) {
age = a;
}
};
1.6.9. Affichage
On redéfinit l'opérateur << qui appelle une méthode
print, cette dernière peut être déclarée virtuelle (cf méthode
virtuelle) si on crée une hiérarchie de classe à partir de la classe de base.
Afficher le code ens/inra/c1_class_person.cpp
#include <iostream>
#include <string>
using namespace std;
/**
* Definition of a Person
* identified by its name and age
*/
class Person {
// ==============================================
// data members
// ==============================================
protected:
// name of the person (default value is empty string)
string name;
// age of the person (default value is 0)
int age;
// ==============================================
// methods
// ==============================================
public:
/**
* default constructor
*/
Person() {
name = "";
age = 0;
}
/**
* constructor with 2 arguments
* @param n name of the person
* @param a age of the person
*/
Person(string n, int a) {
name = n;
age = a;
}
/**
* getter for name
* @return name of the person
*/
string get_name() {
return name;
}
/**
* getter for age
* @return age of the person
*/
int get_age() {
return age;
}
/**
* setter for name
* @param n name of the person
*/
void set_name(string n) {
name = n;
}
/**
* setter for age
* @param a age of the person
*/
void set_age(int a) {
age = a;
}
/**
* print class contents
* @param output stream reference
* @return output stream reference
*/
ostream& print(ostream& out) {
out << name << ", " << age;
return out;
}
friend ostream& operator<<(ostream& out, Person& p) {
return p.print(out);
}
};