1242.2 Langage C++ - 2024-2025
1. Vue d'ensemble
2. Flux et fichiers
3. Flux et
4. Conteneurs séquentiels
5. Itérateurs
6. Conteneurs associatifs
7. Smart pointers
Ensemble de classes et de fonctions qui fournissent des fonctionnalités courantes
Divisée en plusieurs parties, chacune ayant un but spécifique
Définie dans le namespace
Incluse par défaut dans la plupart des compilateurs C++ modernes
Plus de 50 fichiers d'en-tête :
- La bibliothèque standard C
- Gestion des exceptions
- Gestion des flux
- Gestion des chaînes de caractères
- La bibliothèque standard template library (STL)
- Support pour le calcul numérique
- Des codes éprouvés (algorithmes, ...)
Un flux est une séquence d'octets transférés entre la mémoire et les périphériques
C'est le programme qui donne une signification aux octets d'un flux
Les flux fournissent une méthode unifiée pour traiter les E/S quels que soient les périphériques et les types de données à échanger
Classes implémentant des flux :
-
-
-
- ...
Toutes ces classes redéfinissent les opérateurs
2 types de fichiers : texte (par défaut) ou binaire
2 modes d'accès : séquentiel ou direct (random access)
-
-
-
L'ouverture peut se faire par l'appel du constructeur ou de la méthode
Fermeture avec la méthode
Le paramètre
Le paramètre
On le combine avec l'opérateur bit à bit
Exemple : ouverture d'un fichier texte en écriture par ajout (à la fin)
Modes
-
-
-
-
-
-
2 manières d'ouvrir un fichier
1. Par le constructeur de la classe
2. Par la méthode
Pour des fichiers en écriture et lecture, utiliser fstream
#include <iostream>
#include <fstream>
int main()
{
std::ofstream fOutput("myFile.txt");
fOutput << "PI = " << 3.14159 << '.' << std::endl;
fOutput.close();
std::ifstream fInput("myFile.txt");
std::string s; double d; char c;
fInput >> s >> d >> c;
fInput.close();
std::cout << s << d << c << std::endl;
return 0;
}
L'utilisation des flux pour accéder aux fichiers soulève les mêmes problématiques que pour la console
En particulier, les caractères blancs sont considérés comme des séparateurs
On peut utiliser la fonction
Traitement d'erreurs identique pour tous les flux (fichier, écran/clavier, mémoire)
Basé sur 4 bits (flags
On peut tester et manipuler l'état du flux avec les méthodes suivantes :
-
-
-
-
-
#include <iostream> // std::cerr
#include <fstream> // std::ifstream
int main () {
std::ifstream file;
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
file.open("test.txt");
while(!file.eof()) file.get();
file.close();
}
catch (const std::ifstream::failure &e) {
std::cerr << "Exception opening/reading/closing file" << std::endl;
}
return 0;
}
Méthode
Les flux peuvent être utilisés pour lire et écrire des chaînes de caractères
-
-
-
La bibliothèque standard implémente une classe
Spécialisation d'un modèle :
- Plus simple et plus sûre que les tableaux de caractères
- Gestion automatique de la mémoire
- Définition de méthodes et d'opérateurs
Opérateurs
Affectation :
Méthodes
-
-
-
-
-
-
-
-
#include <iostream>
#include <string>
#include <cstdio>
int main()
{
auto p = "Hello"; // const char *
auto s = std::string(p);
auto s2 = std::string("World!");
std::cout << s << " " << s2 << std::endl;
printf("%s %s\n", s, s2); // ⚠️ warning C4477: 'printf'...
auto p2 = s2.c_str(); // const char *
std::cout << p << " " << p2 << std::endl;
printf("%s %s\n", p, p2);
return 0;
}
#include <iostream>
#include <string>
#include <sstream>
int main()
{
std::ostringstream toto;
toto << 10;
auto texte = toto.str();
std::istringstream titi("10");
int nombre;
titi >> nombre;
return 0;
}
Bibliothèque de classes et d'algorithmes génériques (A. Stepanov)
Implémentée en C++
Intégrée à la procédure de standardisation de C++
Basée sur les modèles de classes
Contient
- Conteneurs séquentiels
- Conteneurs associatifs
- Itérateurs
- Algorithmes
- Objets fonctions (foncteurs)
- Adaptateurs
- Allocateurs
Les conteneurs sont des classes qui permettent de stocker des objets
Ils sont définis dans le namespace
Gérer les objets contenus : ajout, suppression (parfois tri, recherche, ...)
Accéder aux objets contenus grâce aux itérateurs
Les itérateurs permettent de parcourir une collection d'objets sans avoir à se préoccuper de l'implémentation
⇒ Avoir une interface de manipulation commune pour les algorithmes génériques de la STL (tri, recherche, remplacement, ...)
Conteneurs séquentiels
Conteneurs dont les éléments sont ordonnés. Parcourir le conteneur suivant cet ordre et insérer ou supprimer un élément en un endroit explicitement choisi.
Conteneurs associatifs
Conteneurs dont les éléments sont identifiés par une clé et ordonnés suivant celle-ci. Pour insérer un élément, il n'est en théorie pas utile de préciser un emplacement.
Tableau dynamique contigu :
Tableau statique contigu :
File à double entrée :
Liste doublement chainée :
Liste simplement chainée :
File :
Pile :
#include <iostream>
#include <vector>
#include <string>
int main()
{
std::vector<std::string> vect;
vect.push_back("Introduction");
vect.push_back("une phrase");
vect.push_back("Conclusion");
for (const auto &elem : vect)
std::cout << elem << std::endl;
for (int i = 0; i < vect.size(); ++i)
std::cout << vect[i] << std::endl;
return 0;
}
#include <list>
int main()
{
std::list<int> lst;
lst.push_back(1); // 1
lst.push_front(0); // 0 1
lst.insert(lst.begin(), 2); // 2 0 1
lst.insert(lst.end(), 3); // 2 0 1 3
lst.pop_front(); // 0 1 3
return 0;
}
#include <iostream>
#include <array>
int main()
{
std::array<int, 3> a1{ {10,11,12} };
// std::array<int, 3> a1 = { 10,11,12 };
for (int i = 0; i < a1.size(); ++i)
{
std::cout << a1[i] << std::endl; // ⚠️ no range check
std::cout << a1.at(i) << std::endl; // ✅ range check (throws exception)
}
return 0;
}
⇒ Tout dépend de la nature de ce qui est stocké dans le
Objets
Les objets sont détruits lors du retrait (pop), ou lorsque le vecteur est détruit
Pointeurs
Les pointeurs sont détruits lorsque le vecteur est détruit
⇒ Il faut
Il est possible d'imbriquer les conteneurs de la STL
On peut ainsi, par exemple, créer un vecteur de listes de chaînes de caractères
Exemple
La définition d'un type intermédiaire peut augmenter la lisibilité :
Des
#include <iostream>
#include <vector>
typedef vector<double> Line;
typedef vector<Line> Matrix;
Matrix m(5); // 5 rows of size 0 initially
for(int i=0; i<5; i++)
{
m[i].resize(5); // rows are now of size 5
for(int j=0; j<5; j++)
{
if (i==j) m[i][j] = 1;
else m[i][j] = 0;
}
}
Les itérateurs sont une généralisation des pointeurs
Définis dans le namespace
Utilisés par les algorithmes de la STL
Utiliser les conteneurs de façon uniforme
Spécifier une position à l'intérieur d'un conteneur. Ils peuvent être incrémentés, et déréférencés (avec *); deux itérateurs peuvent être comparés.
Tous les conteneurs disposent des méthodes suivantes
Un itérateur peut s'initialiser grâce aux méthodes
1)
2)
#include <iostream>
#include <list>
int main()
{
std::list<int> lst;
std::list<int>::iterator it;
for (it = lst.begin(); it != lst.end(); ++it)
std::cout << *it << std::endl; // *it = each element
// C++11
auto it = lst.begin();
for (; it != lst.end(); ++it)
std::cout << *it << std::endl;
// C++11 range-based for loop
for (auto &elem : lst)
std::cout << elem << std::endl; // elem = each element
}
Les conteneurs associatifs sont des conteneurs qui stockent des éléments sous forme de paires clé/valeur
Les clés permettent d'accéder aux valeurs
Les conteneurs associatifs sont ordonnés selon la clé
Ces conteneurs utilisent
Généralisation des tableaux : les indices peuvent être non entiers
- Tableau que l'on pourrait indicer par des chaînes de caractères et écrire par exemple
- On parle d'association "clé - valeur"
Les tables associatives sont définies dans
Elles nécessitent 2 types pour leur déclaration : le type des "clés" (les index) et le type des éléments indexés
#include <string>
#include <map>
std::map<std::string, double> moyenneParBranche; // Unique key
std::multimap<std::string, double> notesParBranche; // Duplicated keys
#include <string>
#include <map>
#include <iostream>
int main()
{
std::map<std::string, double> moyenne;
moyenne["Informatique"] = 5.5;
moyenne["Physique"] = 4.5;
// if->first to access the key, if->second to access the value
for (auto it = moyenne.begin(); it != moyenne.end(); ++it)
std::cout << "En " << it->first << ", j'ai " << it->second <<
" de moyenne." << std::endl ;
std::cout << "Ma moyenne en Informatique est de ";
std::cout << moyenne.find("Informatique")->second << std::endl;
return 0;
}
// First element of map moyenne
auto it = moyenne.begin();
// Found element in map (or end() if not found)
auto it2 = moyenne.find("Informatique");
// End of map (after last element)
// ⚠️ it2 == it3 if not found
auto it3 = moyenne.end();
⇒
Dans le code
Il est possible de déréférencer l'itérateur pour accéder à la paire clé/valeur avec
Une paire est une structure contenant 2 éléments pouvant être de types différents
Certains algorithmes de la STL retournent des paires. La méthode
// Not needed in practice,
// because it is implicit when using e.g. <vector>
#include <pair>
#include <iostream>
#include <string>
int main()
{
auto p = std::make_pair(8, "coeur");
std::cout << p.first << ' ' << p.second << std::endl;
return 0;
}
#include <iostream>
#include <map>
int main()
{
std::multimap notes;
// ⚠️ Indices do not work with multimap!
// moyenne["Informatique"] = 5.5;
notes.insert(std::pair ("Informatique", 5.5));
notes.insert(std::pair ("Informatique", 4.0));
for (auto it = notes.begin(); it != notes.end(); ++it)
std::cout << "En " << it->first << ", j'ai " << it->second <<
" de moyenne." << std::endl;
return 0;
}
Les smart pointers sont des classes qui gèrent la mémoire de manière automatique
Utiliser
Définis dans le namespace
Gérer la mémoire de manière plus sûre et plus efficace que les pointeurs bruts
#include <iostream>
#include <memory>
int main()
{
// Raw pointers
{
auto ptr = new int(5); // dynamic allocation
std::cout << *ptr; // 5
delete ptr; // ⚠️ free memory to avoid memory leak
ptr = nullptr; // avoid dangling pointer
}
// Smart pointers
{
auto s_ptr = std::unique_ptr(new int(5)); // dynamic allocation
std::cout << *s_ptr; // 5
// ✅ No need to delete, memory is automatically freed when ptr goes out of scope
}
return 0;
}
#include <iostream>
#include <memory>
int main()
{
// Smart pointers
{
auto s_ptr = std::unique_ptr(new int(5)); // dynamic allocation
std::cout << *s_ptr; // 5
// ⛔ error: use of deleted function...
auto s_ptr1 = std::unique_ptr(s_ptr);
// ⛔ error: use of deleted function...
auto s_ptr2 = s_ptr; // ⛔ error: use of deleted function...
}
return 0;
}
#include <iostream>
#include <memory>
int main()
{
// Raw pointers
{
auto ptr = new int(5); // dynamic allocation
std::cout << *ptr; // 5
delete ptr; // ⚠️ free memory to avoid memory leak
ptr = nullptr; // avoid dangling pointer
}
// Smart pointers
{
auto s_ptr = std::shared_ptr(new int(5)); // dynamic allocation
std::cout << *s_ptr; // 5
// ✅ Memory is automatically freed when ptr goes out of scope
}
return 0;
}
On ne peut pas affecter directement une adresse à un
Plusieurs
En particulier, les
⇒ Utiliser
⇒ Il faut utiliser
#include <iostream>
#include <memory>
int main()
{
std::shared_ptr sp1;
{
auto circle = new Circle(2);
auto sp2 = std::shared_ptr(circle);
// 📌 circle has now 1 reference (sp2)
// ✅ OK
sp1 = sp2;
// 📌 circle has now 2 references (sp2 and sp1)
...
}
// 📌 circle has 1 reference (sp2)
...
return 0;
}
// 📌 circle has 0 reference and is deleted