#include <iostream> int main () { std::cout << "Bienvenue au " "cours du GULL " "sur C++ !\n"; }
1972 | Dennis Ritchie invente C aux Bell Labs. |
---|---|
1979 | Bjarne Stroustrup invente C with Classes aux Bell Labs, inspiré par Simula67. |
1983 | C with Classes est renommé en C++. |
1990 | The Annotated C++ Reference Manual (ARM), souvent cité en référence, paraît. |
1991 | The C++ Programming Language Second Edition, avec lequel de nombreuses personnes (dont moi-même) ont appris C++, paraît. |
1997 | The C++ Programming Language Third Edition paraît. |
1998 | Le standard ISO C++ (ISO/IEC 14882:1998) est adopté. |
2003 | Le technical corrigendum 1 (TC1) au standard (ISO/IEC 14882:2003) est adopté. |
200x | Une nouvelle version de C++ (C++-0x) est prévue. |
C++ :
new
, delete
,
typeid
, dynamic_cast
et throw
ont besoin de support) ;
Contrôle | for do while break continue switch case default if else goto
return
| ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Types |
|
||||||||||||
Opérateurs | sizeof new delete
and and_eq bitand bitor compl not not_eq
or or_eq xor xor_eq
|
||||||||||||
Namespaces | namespace using
|
||||||||||||
Exceptions | try throw catch
|
||||||||||||
Classes | class this private protected public
virtual friend operator
explicit mutable
|
||||||||||||
Templates | template typename export
|
||||||||||||
Bas niveau | inline asm
|
autre langage | équivalent C++ |
---|---|
array |
int t [80]; |
function |
double sin (double); |
procedure |
void perror (const char *); |
import |
#include (commande du préprocesseur) |
null , NULL |
0 |
final |
C'est final par défaut, utiliser virtual autrement |
finally |
Classe::~Classe() { detruire(); } |
set |
std::set<int> ensemble; |
in |
ensemble.find(element) != ensemble.end() |
foreach |
std::for_each(coll.begin(), coll.end(), faire); |
interface , abstract |
class Garde { |
extends , implements |
class Chien : public Animal, public Garde { |
super |
typedef Base super; |
#include <iostream> int main () { std::cout << "Hello, new world!\n"; }
{ }
;()
pour les fonctions ;" "
et échappe avec \
.int
, char
, etc. ;main
;<<
;;
est obligatoire en fin d'instruction.std
;::
;<<
.#include <iostream.h> int main () { cout << "Hello, new world!\n"; return 0; }Du temps de l'ARM :
.h
;std
) n'existaient pas ;return 0
de main
n'était pas implicite.iostream.h
actuel :
#include <iostream> using namespace std;
bool b = true; char c = 'A'; wchar_t wc = L'A'; signed char sbyte = -128; unsigned char byte = 255; short s = 32767; unsigned short us = 22; int i = 32; unsigned u = 65535u; long l = 123456789L; unsigned long ul = 4294967295UL; float f = 1.23f; double g = 1324e-53; long double h = 1324e156L; void F ();
bool
et wchar_t
.int
et char
.void
signifie « rien », aucune valeur.int global; // définition d'un objet global de type int void f () // définition d'une fonction globale { int local; // objet local non initialisé int local2 = 2; // initialisé avec la syntaxe C int local3 (3); // initialisé avec la syntaxe C++ ObjetComplique obj("hello", 132, '0'); // syntaxe C++ avec // plusieurs paramètres int locale (); // déclaration de fonction ! local = 3; // instruction AutreObjet apres = local; // définition après instruction for (int i = 0; i < 10; ++i) { /* ... */ } // destruction automatique des objets locaux }
for
,
while
ou if
.#include <iostream> int i = 10; // définition de i void f (); // déclaration de f int main () { f(); f(); } void f () { // définition de f extern int i; // déclaration (définition ci-dessus) static int j = 0; // objet statique, survit au bloc int k = 0; // objet local const int c = 0; // objet local constant std::cout<<++i<<' '<<++j<<' '<<++k<<' '<<++c<<'\n'; std::cout<<++i<<' '<<++j<<' '<<++k<<' '<<++c<<".\n"; }
/tmp> ./a.out 11 1 1 0 12 2 2 0. 13 3 1 0 14 4 2 0.
.
, ->
, []
,
()
, ++
, --
, sizeof
,
~
, !
, -
, +
,
&
, *
, /
, %
,
<<
, >>
, <
,
<=
, >
, >=
, ==
,
!=
, ^
, |
, &&
,
||
, =
, *=
, /=
,
%=
, %=
, +=
, -=
,
<<=
, >>=
, &=
,
|=
, ^=
, ?:
, ,
::
, typeid
,
dynamic_cast
, static_cast
,
reinterpret_cast
, const_cast
, new
,
new[]
, delete
, delete[]
,
.*
, ->*
, throw
+
, -
, *
,
/
, %
, ^
, &
,
|
, ~
, !
, =
,
<
, >
, +=
, -=
,
*=
, /=
, %=
, ^=
,
&=
, |=
, <<
,
>>
, >>=
, <<=
,
==
, !=
, <=
, >=
,
&&
, ||
, ++
, --
,
->*
, ,
, ->
, []
,
()
, new
, new[]
, delete
,
delete[]
#include <iostream> int main () { int i = 1; // valeur entière int & r0; // une référence doit être initialisée int & r = i; // référence à une valeur entière int * p0; // pointeur non initialisé int * p = i; // erreur, types incompatibles int * p = &i; // pointeur sur une valeur entière std::cout<< i <<' '<< r <<' '<< p <<'\n' << i <<' '<< r <<' '<< *p <<'\n'; ++r; // incrémente i à travers r ++p; // incrémente p qui pointe à côté de i ! ++*p est OK. std::cout<< i <<' '<< r <<' '<< p <<'\n' << i <<' '<< r <<' '<< *p <<'\n'; }
/tmp> ./a.out 1 1 0xbffff914 1 1 1 2 2 0xbffff918 2 2 -1073743544
*
pointeur : opérateur unaire préfixé de déréférencement (pointage)&
variable : opérateur unaire préfixé d'adresseenum Couleur { BLANC, ROUGE = 0xF00, VERT = 0x0F0, BLEU }; Couleur c = VERT; // défini une variable de type Couleur struct Fraction { int numerateur; int diviseur; }; Fraction f = { 1, 2 }; // défini une variable de type Fraction int main () { f . numerateur = 2 ; // accès au membre Fraction * pf = & f ; // pointeur sur Fraction pf -> diviseur = 3 ; // équivalent à (*pf).diviseur // pointeur sur membre de Fraction de type int int Fraction :: * pm = & Fraction :: numerateur ; f .* pm = 4 ; pf ->* pm = 5 ; }
struct
comme en C..
permet d'accéder à un membre de structure.->
permet d'accéder à un membre de structure
pointée..*
et
->*
sont rarement utilisés.#include <iostream> void f1 (int i) { i = 1; } // par valeur void f2 (int & i) { i = 2; } // par référence void f3 (int * i) { *i = 3; } // par pointeur int main () { int i[] = { 0, 0, 0 }; f1 (i[0]); f2 (i[1]); f3 (&i[2]); std::cout<<"{ "<<i[0]<<", "<<i[1]<<", "<<i[2]<<" }\n"; f1 (0); f2 (0); // interdit f3 (0); // pointeur nul, plante f3() ! }
/tmp> ./a.out { 0, 2, 3 } Erreur de segmentation
new
et delete
#include <memory> #include <string> int * f () { int * pi1 = new int; // simple allocation de mémoire int * pi2 = new int (2); // initialise std::string * pi3 = new std::string("leak"); // construit std::auto_ptr<int> pi4 (new int(4)); delete pi1; // libère *pi1 return pi2; // *pi2 reste pointé par p dans main // *pi3 n'est plus pointé => mémoire perdue (fuite, leak) // *pi4 est détruit par le destructeur de pi4. } int main () { int * p = f (); delete p; }
new
survit jusqu'à destruction
par un delete
.malloc
et free
de C sont de plus bas niveau,
ils ne font qu'allouer des bytes.new
dépend de la durée de vie de l'objet,
pas de son type.auto_ptr
peuvent aider./tmp> cat >hello.cc #include <iostream> int main () { std::cout << "Hello, new world!\n"; } /tmp> g++ hello.cc /tmp> ls -l a.out -rwxrwxr-x 1 marc marc 13527 2006-01-12 23:52 a.out /tmp> ./a.out Hello, new world! /tmp>
hello.cc
: fichier C++ en entrée.cc
,
.cpp
, .cxx
, .C
, .c++
a.out
: exécutable en langage machine en sortie#include <iostream> int main () { std::cout << "Hello, new world!\n" }
/tmp> g++ hello.cc hello.cc: In function `int main()': hello.cc:6: erreur: expected `;' avant un élément lexical « } » /tmp>
int main () { std::cout << "Hello, new world!\n"; }
/tmp> g++ hello.cc hello.cc: In function `int main()': hello.cc:3: erreur: « cout » n'est pas un membre de « std » /tmp>
Appeler gcc au lieu de g++
/tmp> gcc hello.cc /tmp/ccsbrTRT.o(.text+0xd): In function `std::__verify_grouping(char const*, unsigned int, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)': : undefined reference to `std::basic_string<char, std::char_traits<char>, std::allocator<char> >::size() const' /tmp/ccsbrTRT.o(.text+0x60): In function `std::__verify_grouping(char const*, unsigned int, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)': : undefined reference to `std::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator[](unsigned int) const' /tmp/ccsbrTRT.o(.text+0x9f): In function `std::__verify_grouping(char const*, unsigned int, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)': : undefined reference to `std::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator[](unsigned int) const' /tmp/ccsbrTRT.o(.text+0xce): In function `std::__verify_grouping(char const*, unsigned int, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)': : undefined reference to `std::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator[](unsigned int) const' /tmp/ccsbrTRT.o(.text+0x127): In function `main': : undefined reference to `std::cout' /tmp/ccsbrTRT.o(.text+0x12c): In function `main': : undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)' /tmp/ccsbrTRT.o(.text+0x155): In function `__static_initialization_and_destruction_0(int, int)': : undefined reference to `std::ios_base::Init::Init()' /tmp/ccsbrTRT.o(.text+0x170): In function `__static_initialization_and_destruction_0(int, int)': : undefined reference to `std::ios_base::Init::~Init()' /tmp/ccsbrTRT.o(.eh_frame+0x11): undefined reference to `__gxx_personality_v0' collect2: ld a retourné 1 code d'état d'exécution /tmp>
N° | étape | outil | commande en ligne | résultat |
---|---|---|---|---|
1 | Préprocesseur | cpp | g++ -E hello.cc | stdout |
2 | Compilation | cc1plus | g++ -S hello.cc | hello.s |
3 | Assemblage | as | g++ -c hello.cc ou g++ -c hello.s | hello.o |
4 | Édition des liens | ld | g++ hello.cc ou g++ hello.o ou g++ hello.s | a.out |
-o
pour donner le nom du fichier produit-D
(define) pour définir des symboles du préprocesseur-I
(include) pour ajouter des répertoires où chercher les fichiers d'en-tête-l
(library) pour lier avec une bibliothèque-L
pour ajouter des répertoires où chercher les bibliothèques-Wall
(warn all) pour avoir plus d'avertissements-g
(debug) pour compiler un exécutable débugable (avec symboles)-O
, -O1
, -O2
,
-O3
, -Os
(optimize) pour optimiser-ansi -pedantic
pour être averti des déviations du standardman gcc
g++ -c hello.cc -DLINUX -I../mylibs/include -Wall -g -O -ansi
g++ hello.o -L../mylibs -lmy -o hello
/tmp> gcc -E hello.cc >hello.E /tmp> ls -l hello.E -rw-rw-r-- 1 marc marc 716014 2006-01-23 06:30 hello.E /tmp> less hello.E
*.h
et sort tout d'une traite (716014 octets)./tmp> g++ -S hello.cc /tmp> cat hello.s
/tmp> gcc -c hello.cc /tmp> nm hello.o --demangle 00000044 t global destructors keyed to main 0000005c t global constructors keyed to main U __gxx_personality_v0 00000074 T main 00000000 t __static_initialization_and_destruction_0(int, int) U std::ios_base::Init::Init() U std::ios_base::Init::~Init() U std::cout 00000000 b std::__ioinit U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
Fichiers inclus contenant les déclarations | iostream |
c.h |
---|---|---|
// fichier standard // distribué avec GCC |
#ifndef C_H #define C_H char b(void); char id(char); #endif |
|
fichiers source contenant les définitions (l'implémentation) | main.cc |
c.cc |
#include <iostream> #include "c.h" int main () { std::cout<<b()<<id('a')<<"r\n"; } |
#include "c.h" char b(void) { return 'b'; } char id(char c) { return c; } |
*.cc
inclut son *.h
et les *.h
déclarant les autres types, fonctions, etc., utilisés.*.h
aide à vérifier la cohérence
déclarations/implémentation.#ifndef
de garde contre la ré-inclusion de c.h
/tmp> g++ -c c.cc /tmp> g++ -c main.cc /tmp> ls -l *.o -rw-rw-r-- 1 marc marc 691 2006-01-13 00:32 c.o -rw-rw-r-- 1 marc marc 2688 2006-01-13 00:32 main.o /tmp> g++ main.o c.o -o foo /tmp> ls -l foo -rwxrwxr-x 1 marc marc 13970 2006-01-13 15:20 foo /tmp> ./foo bar /tmp>
*.cc
, ce qui produit
des fichiers objet *.o
.*.o
pour produire un exécutable.Makefile
ou un IDE.c.c | c.h | main.cc |
---|---|---|
#include "c.h" char b(void) { return 'b'; } char id(char c) { return c; } |
#ifndef C_H #define C_H #ifdef __cplusplus extern "C" { #endif char b(void); char id(char); #ifdef __cplusplus } #endif #endif |
#include <iostream> #include "c.h" int main() { std::cout << b() << id('a') << 'r' << std::endl; } |
/tmp> gcc -c c.c /tmp> g++ -c main.cc /tmp> ls -l *.o -rw-rw-r-- 1 marc marc 682 2006-01-13 00:32 c.o -rw-rw-r-- 1 marc marc 2680 2006-01-13 00:32 main.o /tmp> g++ main.o c.o -o foo /tmp> ./foo bar
/* c.c */ char b (void) { return 'b'; }
// cc.cc char b(void); // déclaration sans extern "C" int main () { b(); }
/tmp> gcc -c c.c /tmp> nm c.o 00000000 T b /tmp> g++ -c cc.cc /tmp> nm cc.o U __gxx_personality_v0 00000000 T main U _Z1bv /tmp> g++ c.o cc.o cc.o(.text+0x12): In function `main': : undefined reference to `b()' collect2: ld a retourné 1 code d'état d'exécution
b
et _Z1bv
.
extern "C"
.« Le but du concept de classe de C++ est de fournir au programmeur un outil pour créer de nouveaux types qui peuvent être utilisés aussi aisément que les types prédéfinis.» (Stroustrup)
Exemple de tels types : std::string
et
std::ostream
.
#include <string> #include <iostream> int main () { std::string h = "hello"; std::string w = "world"; h += ", "; // Appond une chaîne avec += w = h + w + '\n'; // Concaténation avec +, assignation avec = std::cout << w; // Affichage du tout avec << }
/tmp> ./a.out hello, world
date
,
std::complex
, big_num
, BCD
,
std::vector
, std::pair
, std::auto_ptr
.
String
(comme les
QString
de Qt).Fraction
(1/9)#include <iostream> #include "fraction.h" // contient la déclaration de Fraction int main () { Fraction f1 (6, 8); // = 6/8 = 3/4 Fraction f2 = 2; // = 2/1 Fraction f3; // non initialisé Fraction f4 = f1; // = f1 f3 = f1; f3 = Fraction(1, 4) + f3; f2 = f2 * f1 + 1; std::cout<<"f1 = "<<f1<<"\nf2 = "<<f2<<"\nf3 = "<<f3<<'\n'; }
/tmp> ./a.out f1 = 3/4 f2 = 5/2 f3 = 1
<<
et sans diviseur
lorsqu'il vaut 1#include <iostream> struct Fraction { int numerateur; int diviseur; }; int main () { Fraction f1 = {6, 8}; // initialisation Fraction f2 = {2}; // diviseur non initialisé ! Fraction f3; // non initialisation OK Fraction f4 = f1; // initialisation avec une fraction OK f3 = f1; // assignation d'une fraction OK std::cout<<f1.numerateur<<'/'<<f1.diviseur<<'\n'; }
/tmp> ./a.out 6/8
struct
de C#include <iostream> struct Fraction { int numerateur, diviseur; void reduire (); }; int main () { Fraction f1 = {6, 8}; // initialisation f1.reduire(); // transforme 6/8 en 3/4 std::cout<<f1.numerateur<<'/'<<f1.diviseur<<'\n'; }
/tmp> ./a.out 3/4
reduire
est une méthode de Fraction
.
comme pour les autres
membresreduire
modifie l'objet f1
pour lequel
elle est appelée.struct Fraction { int numerateur, diviseur; void reduire (); }; void Fraction::reduire () { if (diviseur < 0) { numerateur = -numerateur; diviseur = -diviseur; } int a = std::abs(numerateur), b = diviseur, c; while (b != 0) { c = a % b; a = b; b = c; } numerateur /= a; diviseur /= a; }
Fraction::
).this
// méthode de Fraction void Fraction::fun () { numerateur = 0; // équivalent this->numerateur = 0; // équivalent } // fonction agissant sur une Fraction void fun (Fraction * This) { This->numerateur = 0; } void f () { Fraction f; f.fun(); // appel de méthode fun(&f); // appel de fonction }
this
, un pointeur sur
l'objet concerné par l'appel de la méthode.this->
pour
accéder aux membres.struct Fraction { int numerateur, diviseur; Fraction(int, int); void reduire(); }; Fraction::Fraction(int n, int d) : numerateur (n), diviseur (d) { reduire(); } int main () { Fraction f1 (3, 4); // construction avec 2 entiers OK Fraction f2 (2); // construction avec 1 entier Fraction f2 = {2, 1};// initialisation littérale Fraction f3; // pas d'initialisation Fraction f4 = f1; // construction par copie OK f3 = f1; // assignation d'une fraction OK }Le constructeur :
: numerateur(n), diviseur(d)
)
reduire()
.struct Fraction { int numerateur, diviseur; Fraction(int n, int d): numerateur (n), diviseur (d) { reduire(); } Fraction(int n) : numerateur (n), diviseur (1) {} Fraction() {} void reduire(); }; int main () { Fraction f1 (3, 4); // Fraction(int, int) Fraction f2 = 2; // Fraction(int) Fraction f3; // Fraction() Fraction f4 = f1; // Fraction(const Fraction&) }
inline
.
Autre solution également dans le fichier d'en-tête :
inline Fraction::Fraction() {}
struct Fraction { int numerateur, diviseur; Fraction (int, int = 1); Fraction () {} void reduire (); }; // Implémentation Fraction::Fraction(int n, int d) : numerateur (n), diviseur (d) { reduire(); } int main () { Fraction f1 (3, 4); // Fraction(int, int); Fraction f2 (2); // Fraction(int, int = 1); Fraction f3; // Fraction(); Fraction f4 = f1; // Fraction(const Fraction&) }
Fraction(int = 0, int = 1)
est aussi possible, mais il
faut alors supprimer Fraction()
pour éviter l'ambiguïté.
#include "fraction.h" Fraction operator + (Fraction lhs, Fraction rhs) { return Fraction (lhs.numerateur * rhs.diviseur + rhs.numerateur * lhs.diviseur, lhs.diviseur * rhs.diviseur); } int main () { Fraction f1 (3, 4); f1 = operator + (f1, Fraction (1, 4)); f1 = f1 + Fraction (1, 4); f1 = operator + (f1, 3); f1 = f1 + 3; }
int operator+(int,int)
).operator+(f1,3)
.// Fraction(1,2) * Fraction(3,4) Fraction operator * (Fraction lhs, Fraction rhs) { return Fraction f(lhs.numerateur * rhs.numerateur, lhs.diviseur * rhs.diviseur); } // std::cout << Fraction(1,2) << '\n'; #include <ostream> std::ostream & operator << (std::ostream & s, Fraction f) { s << f.numerateur; if (f.diviseur != 1) s << '/' << f.diviseur; return s; } // quelques autres opérateurs utiles bool operator==(Fraction, Fraction); // opérateur binaire Fraction& Fraction::operator+=(Fraction); // op. binaire membre Fraction operator-(Fraction); // opérateur unaire Fraction operator++(Fraction&); // opérateur préfixé Fraction operator++(Fraction&, int); // opérateur postfixé Fraction::operator double() const; // opérateur de conversion
fraction.h
#ifndef FRACTION_H #define FRACTION_H #include <iosfwd> struct Fraction { int numerateur, diviseur; Fraction (); Fraction (int, int = 1); void reduire (); }; Fraction operator + (Fraction, Fraction); Fraction operator * (Fraction, Fraction); std::ostream & operator << (std::ostream &, Fraction); #endif
fraction.cc
#include "fraction.h" #include <cstdlib> #include <ostream> Fraction::Fraction() {} Fraction::Fraction(int n, int d) : numerateur (n), diviseur (d) { reduire(); } Fraction operator * (Fraction lhs, Fraction rhs) { return Fraction f(lhs.numerateur * rhs.numerateur, lhs.diviseur * rhs.diviseur); } Fraction operator + (Fraction lhs, Fraction rhs) { return Fraction f(lhs.numerateur * rhs.diviseur + rhs.numerateur * lhs.diviseur, lhs.diviseur * rhs.diviseur); } std::ostream & operator << (std::ostream & s, Fraction f) { s << f.numerateur; if (f.diviseur != 1) s << '/' << f.diviseur; return s; } void Fraction::reduire () { if (diviseur < 0) { numerateur = -numerateur; diviseur = -diviseur; } // PGCD numerateur et diviseur int a = std::abs(numerateur), b = diviseur, c; while (b != 0) { c = a % b; a = b; b = c; } numerateur /= a; diviseur /= a; }
class Fraction { // class au lieu de struct int numerateur_, diviseur_; // membres privés public: class DiviseurNul {}; Fraction (int n = 0, int d = 1) : numerateur_(n), diviseur_(d) { if (diviseur_ == 0) throw DiviseurNul(); reduire(); } int numerateur () const { return numerateur_; } int diviseur () const { return diviseur_; } void reduire (); }; void f() { Fraction f (0, 0); // exception Fraction::DiviseurNul f.numerateur; // erreur, numerateur est une méthode f.numerateur_; // erreur, numerateur_ est privé f.numerateur(); // OK, méthode publique }
class Fraction { //... int numerateur (); // peut modifier l'objet int numerateur () const; // ne modifie pas l'objet }; void func1(Fraction &); // peut modifier le paramètre void func2(const Fraction &); // ne modifie pas le paramètre void f () { const Fraction quart (1, 4); quart.numerateur (); // int numerateur () const }
const
d'un passage de paramètre
par référence ou pointeur.const
d'une méthode qui ne
modifie pas l'objet.const_cast
permet de passer outre (pour
utiliser des fonctions mal conçues) :
void lib_func(Fraction * f); // ne modifie en fait pas f void proxy_lib_func(const Fraction * f) { lib_func(const_cast<Fraction*>(f)); }
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> void probleme (const char *filename) { const int fd = open(filename, O_RDONLY); //... if (write (fd, buf, count) == -1) return; // sortie sans close(f) essai(); // fonction pouvant lancer une exception //... close(f); }
new
et delete
.class FileHandle { const int handle; public: FileHandle(const char* filename, int flags) : handle (open(filename, flags)) {} ~FileHandle() { close(handle); } operator int() const { return handle; } }; void solution (const char *filename) { const FileHandle fd (filename, O_RDONLY); //... if (write (fd, buf, count) == -1) return; //... // close automatique par FileHandle::~FileHandle }
operator int() const
permet d'utiliser un
objet FileHandle
comme un descripteur classique
(write(fd,
).void r (const FileHandle & handle) { // handle est une référence à l'objet de l'appelant /* ... */ } void v (FileHandle handle) { // handle est un nouvel objet construit par copie du paramètre /* ... */ // destruction de handle, appel automatique de close } void g () { FileHandle h (".file", O_RDONLY), h2 (".file2", O_RDONLY); h2 = h; // assignation r(h); // r reçoit une référence à h v(h); // v reçoit une copie de h // destruction de h et h2, ".file" fermé 3 fois, ".file2" 0 }
C(C&);
constructeur de copie, automatiquement utilisé
pour passer des paramètres, transmettre les exceptions, initialiser par
copie.C&operator=(C&);
opérateur d'assignation,
automatiquement utilisé pour l'assignation.class FileHandle { const int handle; FileHandle(const FileHandle&); // Pas de copie FileHandle& operator=(const FileHandle&); // Pas d'assignation public: FileHandle(const char* filename, int flags) : handle (open(filename, flags)) {} ~FileHandle() { close(handle); } operator int() const { return handle; } }; void f1 (FileHandle); void f2 (const FileHandle &); int main () { FileHandle h (".file", 0), h2 (".file2", 0); h2 = h; // impossible d'assigner f1 (h); // impossible de construire par copie f2 (h); // OK }
class FileHandle { bool owner; int handle; public: FileHandle (FileHandle& from) : owner(from.owner), handle(from.handle) { from.owner = false; } // possession transférée FileHandle& operator= (FileHandle& from) { if (&from != this) { // cas handle=handle; if (owner) close(handle); // possession abandonnée handle = from.handle; owner = from.owner; from.owner = false; // nouvelle possession transférée } return *this; } ~FileHandle () { if (owner) close(handle); } };
std::auto_ptr
transfère la possession.boost::scoped_ptr
a une possession intransférable.boost::shared_ptr
partage et compte la possession.std::string
duplique la chaîne, parfois en différé
(lazy evaluation et reference counting).Ce cours ne couvre que le support que C++ donne à la POO.
crier
différemment)wc
(word count)~ > wc </usr/share/common-licenses/GPL 340 2968 17992
#include "compteur.h" #include <iostream> int main(int argc, char * argv[]) { char c; Compteur*const compteur = MakeCompteur(argv[1]); // abstraction while (std::cin.get(c)) compteur->Compter(c); // polymorphisme std::cout << *compteur << '\n'; delete compteur; }
MakeCompteur
retourne un objet de type
CompteurDeLettres
, CompteurDeMots
,
CompteurDeLignes
ou CompteurDeTout
.
On se fiche de savoir lequel (abstraction).
void Compteur::Compter(char);
<<
pour ostream
et Compteur
(encapsulation).
std::ostream& operator<<(std::ostream&, Compteur&);
MakeCompteur
(fabrique de compteur)#include "compteur.h" Compteur * MakeCompteur(const char* arg) { if (arg && arg[0] == '-') { if (arg[1] == 'w') return new CompteurDeMots; if (arg[1] == 'l') return new CompteurDeLignes; if (arg[1] == 'm') return new CompteurDeLettres; } return new CompteurDeTout; }
Compteur*
permet de pointer sur
CompteurDeMots
, CompteurDeLignes
, etc. car :
Compteur
est une classe de base public de
CompteurDeMots
;CompteurDeMots
est une classe publiquement
dérivée de Compteur
.CompteurDeMots
hérite publiquement de
Compteur
.Compteur*
ou un Compteur&
permet de
pointer/référencer tout objet d'une classe publiquement dérivée.
Compteur* pc = new CompteurDeMots; // OK Compteur& rc = *pc; // OK
Compteur*
ou un Compteur&
on ne
peut bien sûr utiliser que ce qui est déclaré dans la classe
Compteur
, donc commun à tous les compteurs.
class Compteur { public: virtual void Compter(char); }; class CompteurDeLettres : public Compteur { public: void Compter(char); }; class CompteurDeMots : public Compteur { public: void Compter(char); }; class CompteurDeLignes : public Compteur { public: void Compter(char); }; class CompteurDeTout : public Compteur { public: void Compter(char); }; std::ostream& operator<<(std::ostream&, const Compteur&);
Compteur
déclare Compter
,
qui est commun à tous les compteurs.Compteur
(relation IS-A)
redéfinit la méthode virtuelle Compter
à sa manière.operator<<
est déclaré comme fonction amie plutôt
que membre car le paramètre de gauche doit être un ostream
.
Problème : Ne peut pas être virtuel ni redéfinit.#include <iosfwd> class Compteur { public: virtual void Compter(char) = 0; // méthode virtuelle pure virtual ~Compteur() {} // méthode (destructeur) virtuelle // fonction amie friend std::ostream& operator<<(std::ostream&, const Compteur&); private: virtual std::ostream& Output(std::ostream&) const = 0; }; std::ostream& operator<<(std::ostream&, const Compteur&);
Compteur
est une classe abstraite (une interface) car elle
contient des méthodes virtuelles pures.Compteur
:
Compteur c;
ne compile pas.
Compter
et Output
.Output
est une méthode virtuelle pure privée que
operator<<
peut appeler pour résoudre son problème de
non virtualité.operator<<
est ami pour pouvoir appeler la méthode
privée Output
.#include <ostream> #include <iomanip> class Compteur { public: virtual void Compter(char) = 0; virtual ~Compteur() {} friend std::ostream & operator<<(std::ostream &, const Compteur &); private: virtual std::ostream & Output(std::ostream &) const = 0; }; std::ostream& operator<<(std::ostream& strm, const Compteur& compteur) { return compteur.Output(strm); } class CompteurDeLettres : public Compteur { public: void Compter(char c) { ++compte; } private: int compte; std::ostream & Output(std::ostream & strm) const { return strm << std::setw(4) << compte; } };
class CompteurDeTout : public Compteur { public: void Compter(char); private: std::ostream & Output(std::ostream &) const; CompteurDeLignes lignes; // HAS-A CompteurDeMots mots; // HAS-A CompteurDeLettres lettres; // HAS-A }; void CompteurDeTout::Compter(char c) { lettres.Compter(c); mots.Compter(c); lignes.Compter(c); } std::ostream & CompteurDeTout::Output(std::ostream & strm) const { return strm << lignes << ' ' << mots << ' ' << lettres; }
CompteurSimple
ce qui est
commun aux compteurs qui ont un seul membre compte
.
class CompteurSimple : public Compteur { public: CompteurSimple(); protected: int compte; private: std::ostream & Output(std::ostream &) const; }; class CompteurDeLettres : public CompteurSimple { public: void Compter(char); }; class CompteurDeLignes : public CompteurSimple { public: void Compter(char); }; class CompteurDeMots : public CompteurSimple { public: CompteurDeMots(); void Compter(char); private: bool last_in_word; };
compte
est protégé, ce qui permet aux classes dérivées
d'y accéder (pour l'incrémenter) sans le rendre public.
#ifndef COMPTEUR_H
#define COMPTEUR_H
#include <ostream>
class Compteur {
public:
virtual void Compter(char) = 0;
virtual ~Compteur() {}
friend std::ostream & operator<<(std::ostream &, const Compteur &);
private:
virtual std::ostream & Output(std::ostream &) const = 0;
};
std::ostream & operator<<(std::ostream &, const Compteur &);
class CompteurSimple : public Compteur {
public:
CompteurSimple();
protected:
int compte;
private:
std::ostream & Output(std::ostream &) const;
};
class CompteurDeLettres : public CompteurSimple {
public:
void Compter(char);
};
class CompteurDeLignes : public CompteurSimple {
public:
void Compter(char);
};
class CompteurDeMots : public CompteurSimple {
public:
CompteurDeMots();
void Compter(char);
private:
bool last_in_word;
};
class CompteurDeTout : public Compteur {
public:
void Compter(char);
private:
CompteurDeLignes lignes;
CompteurDeMots mots;
CompteurDeLettres lettres;
std::ostream & Output(std::ostream &) const;
};
#endif
class FileOpenDialog : public Dialog { TextInput filter_text; TextList directories_list; TextList files_list; Button open; Button filter; Button cancel; Button help; public: FileOpenDialog(const string& dir); }; En général, une classe applicative suffit à remplir un fichier d'en-tête et un fichier source pour l'implémentation. |
L'abstraction et le polymorphisme sont très adaptés à la représentation des règles business avec toutes leurs variantes.
class Compte { /*...*/ }; class ComptePersonnel : public Compte { /*...*/ }; class ComptePersonnelEUR : public ComptePersonnel { /*...*/ }; class ComptePersonnel60Plus : public ComptePersonnel { /*...*/ }; class ComptePersonnelGeneration : public ComptePersonnel { /*...*/ }; class ComptePersonnelCampus : public ComptePersonnel { /*...*/ }; class CompteCourant : public Compte { /*...*/ }; class CompteEpargne : public Compte { /*...*/ }; class CompteEpargneBonus : public CompteEpargne { /*...*/ }; class CompteEpargneJeunesse : public CompteEpargne { /*...*/ }; class CompteFondPlacement : public Compte { /*...*/ }; class CompteATerme : public Compte { /*...*/ };
CompteurDeTout
est un seul Compteur
et est aussi un CompteurDeLignes
, un CompteurDeMots
et
un CompteurDeLettres
.
class Compteur { /* ... */ }; class CompteurSimple : virtual public Compteur { /* ... */ }; class CompteurDeLettres: public CompteurSimple { /* ... */ }; class CompteurDeMots : public CompteurSimple { /* ... */ }; class CompteurDeLignes : public CompteurSimple { /* ... */ }; class CompteurDeTout : public CompteurDeLignes , public CompteurDeMots , public CompteurDeLettres { public: void Compter(char); protected: std::ostream & Output(std::ostream &) const; }; void CompteurDeTout::Compter(char c) { CompteurDeLettres::Compter(c); CompteurDeMots::Compter(c); CompteurDeLignes::Compter(c); } std::ostream & CompteurDeTout::Output(std::ostream & strm) const { CompteurDeLignes::Output(strm) << ' ' << CompteurDeMots::Output(strm) << ' ' << CompteurDeLettres::Output(strm); return strm; }
virtual public
, CompteurDeTout
aurait
trois classes de base Compteur
, autrement dit Compteur
serait une classe de base ambiguë de CompteurDeTout
, donc
CompteurDeTout*
ne pourrait pas être converti en
Compteur*
.
ComteurSimple
, on n'aurait
plus qu'une variable compte
incrémentée par tous les compteurs.
class Incopiable { Incopiable(Incopiable&); Incopiable& operator=(Incopiable&); public: Incopiable() {} }; class CompteurDeTout : public Compteur, Incopiable { /*...*/ }; void f () { CompteurDeTout compteur; Compteur * pc = &compteur; Incopiable * pi = &compteur; }
class Stack { char* v; int max_size; int top; public: class UnderFlow {}; class OverFlow {}; Stack(int size); ~Stack(); void Push(char); char Pop(); }; void Stack::Push(char c) { if (top == max_size) throw OverFlow(); v[top++] = c; } char Stack::Pop() { if (top == 0) throw UnderFlow(); return v[--top]; }
void f () { Stack stk(3); try { stk.Push("foo"); } catch (const Stack::Overflow & e) { Alerte(); } catch (const Stack::Underflow & e) { Oups(); } catch (...) { Arg(); } }
void f () throw (E1);
template <typename T> const T& Max (const T& a, const T& b) { return a > b ? a : b; } #include <iostream> #include <string> using namespace std; int main () { cout << Max (5, 3) << ' ' // int max(int,int) << Max ('a', 'z') << ' ' // char max(char,char) << Max (1.21e-5, 0.0000131) << ' ' // float max(float,float) << Max (string("titi"), string("toto")) << ' ' // std::string max(std::string,std::string << Max<int>(100, 'Z') << '\n'; }
5 z 1.31e-05 toto 100
std::max
et std::min
existent.export
qui permettrait de mettre les définitions
hors des fichiers d'en-tête est très peu supporté. Cela demande au compilateur
de maintenir un répertoire d'instances de templates.
// max.h template <typename T> const T& Max (const T& a, const T& b);
// max.cc export template <typename T> const T& Max (const T& a, const T& b) { return a > b ? a : b; }
template<class T> class Stack { T* v; int max_size; int top; public: class UnderFlow {}; class OverFlow {}; Stack(int size); ~Stack(); void Push(T); T Pop(); }; template<class T> void Stack<T>::Push(T c) { if (top == max_size) throw OverFlow(); v[top++] = c; } template<class T> T Stack<T>::Pop() { if (top == 0) throw UnderFlow(); return v[--top]; }
#include <cstring> template <class T> void Copy (T * dest, const T * source) { while (*dest++ = *source++); } template <char> void Copy (char * dest, const char * source) { std::strcpy (dest, source); } int main() { char sc[] = { 'h', 'e', 'l', 'l', 'o', '\0' }; int si[] = { 1, 2, 3, 4, 5, 0 }; char tc[10]; int ti[10]; Copy (tc, sc); Copy (ti, si); }
template<class T> class Vector<T> { /*...*/ }; template<> class Vector<void*> { /*...*/ }; template <class T> class Vector<T*> : private Vector<void*> { public: typedef Vector<void*> Base; Vector() : Base() {} explicit Vector(int i) : Base(i) {} T*& elem(int i) { return static_cast<T*&>(Base::elem(i)); } T*& operator[](int i) { return static_cast<T*&>(Base::operator[] (i)); } };
void*
.T*
implémentée avec la spécialisation de void*
.void*
avec
tous les pointeurs, tout en conservant la vérification des types :
Vector<double*> vd; Vector<Client*> vc; Vector<Facture*> vf;
// définition récursive template <int N> struct factorielle { enum { value = N * factorielle<N-1>::value }; }; // condition terminale avec une spécialisation template<> struct factorielle<0> { enum { value = 1 }; }; #include <iostream> int main() { std::cout << factorielle<0>::value << ' ' << factorielle<1>::value << ' ' << factorielle<2>::value << ' ' << factorielle<3>::value << '\n'; }
template <bool b, typename TRUE, typename FALSE> struct IF; template <typename TRUE, typename FALSE> struct IF <true, TRUE, FALSE> { typedef TRUE RESULT; }; template <typename TRUE, typename FALSE> struct IF <false, TRUE, FALSE> { typedef FALSE RESULT; }; #include <iostream> int main() { IF<true, int, float>::RESULT i = 1.23; IF<false, int, float>::RESULT f = 1.23; std::cout << i << ' ' << f << '\n'; }
template <class T> class Foo : private IF <sizeof(T) <= sizeof(int), Machin, Truc> { //... };
template<class charT, class traits = char_traits<charT>, class Allocator = allocator<charT> > class basic_string; typedef basic_string<char> string; template<class charT, class traits = char_traits<charT> > class basic_ostream; typedef basic_ostream<char> ostream; ostream cout;
pair
, auto_ptr
, limites numeriques,
min()
, max()
, swap()
vector
, deque
,
list
, string
, set
,
multiset
, map
, multimap
.
istream
ostream
list
,
set
, multiset
, map
,
multimap
.
vector
, deque
,
string
, array
.
for_each
, find
,
count
, copy
, replace
,
remove_copy_if
, reverse
,
random_shuffle
, sort
, etc.stack
, queue
,
priority_queue
, bitset
.valarray
, table optimisée pour le calcul numérique#include <iostream> #include <iomanip> #include <vector> #include <iterator> #include <algorithm> using namespace std; int main() { vector<string> v; string line; while (getline(cin, line)) v.push_back(line); random_shuffle(v.begin(), v.end()); copy(v.begin(), v.end(), ostream_iterator<string>(cout, "\n")); }
// random_shuffle utilise une fonction int alea(int max) { return std::rand() % max; } random_shuffle(v.begin(), v.end(), alea); // random_shuffle utilise un objet class Alea { int a; public: Alea (int i) : a(i) {} int operator() (int max) { ++a; return a %= max; } }; Alea obj (22); random_shuffle(v.begin(), v.end(), obj);
// tri d'un vecteur de double en ordre descendant sort(vec.begin(), vec.end(), greater<double>);
Que se passe-t-il lorsqu'une exception est jetée par une opération dans un conteneur de la bibliothèque standard.
list
, set
,
multiset
, map
, multimap
).
La destruction d'un nœud réussit toujours.
Les opérations multi-nœuds peuvent échouer au milieuvector
,
deque
), sauf si le constructeur de copie et l'assignation
ne jettent pas d'exception.
C'est pas très joli, mais possible.
#include <iostream> #include <iomanip> using namespace std; int main () { int i = 10; cout << i << ' ' << hex << i << ' ' << oct << i << '\n'; double f[] = { 0, 132.456, -0.0113, 12345678 }; cout << fixed << setprecision(2) << setfill('_'); for (size_t i = 0; i < sizeof f / sizeof f[0]; ++i) cout << setw(8) << f[i] << '\n'; }
10 a 12 ____0.00 __132.46 ___-0.01 12345678.00
#include <iostream> int main() { int a, b; std::cout << "Entrez deux entiers :" << std::endl; std::cin >> a >> b; std::cout << "somme = " << a + b << " produit = " << a * b << std::endl; }
#include <sstream> std::string fullpath (const char* basedir, const char* filename) { std::ostringstream strm; strm << basedir << '/' << filename; return strm.str(); }
#include <fstream> int main (int argc, char *argv[]) { std::ifstream file (argv[1]); //... }
©2006, Marc Mongenet
Ce cours est disponible selon les termes de la
Creative Commons Attribution 2.5 License.