Vue d'ensemble du langage C++
Le C++ est un langage de programmation compilé, à typage statique, qui prend en charge plusieurs paradigmes : programmation procédurale, orientée objet et générique. En tant qu'extension du langage C, il conserve ses performances élevées tout en intégrant des concepts objets tels que l'encapsulation, l'héritage et le polymorphisme, facilitant ainsi la maintenance et l'évolutivité du code.
Syntaxe essentielle et types de données
La syntaxe fondamentale du C++ comprend la déclaration de variables, la définition de fonctions, les structures de contrôle conditionnelles (if-else, switch) et les boucles (for, while). Les types de données se répartissent en types primitifs (int, char, float, double), types composés (tableaux, structures) et pointeurs.
#include <iostream>
int main() {
int compteur = 42;
double moyenne = 12.75;
std::cout << "Compteur : " << compteur << ", Moyenne : " << moyenne << std::endl;
return 0;
}
Principes de la programmation orientée objet
La POO repose sur cinq concepts fondamentaux : les classes, les objets, l'encapsulation, l'héritage et le polymorphisme. Une classe sert de modèle pour créer des objets, définissant leurs attributs et comportements. L'héritage permet de réutiliser du code en créant de nouvelles classes à partir de classes existantes. Le polymorphisme autorise un même appel de méthode à produire des comportements différents selon le type d'objet.
class Utilisateur {
public:
virtual void sePresenter() {
std::cout << "Je suis un utilisateur." << std::endl;
}
};
class Enseignant : public Utilisateur {
public:
void sePresenter() override {
std::cout << "Je suis un enseignant." << std::endl;
}
};
int main() {
Utilisateur u;
Enseignant e;
u.sePresenter();
e.sePresenter();
return 0;
}
Développement d'interfaces graphiques
Fondamentaux des interfaces graphiques en C++
Les interfaces graphiques utilisateur (GUI) offrent une représentation visuelle pour l'interaction entre l'utilisateur et le logiciel. La bibliothèque standard du C++ ne fournit pas de fonctionnalités GUI natives, il est donc nécessaire de recourir à des frameworks tiers comme Qt, wxWidgets ou MFC (Microsoft Foundation Classes).
Le choix du framework dépend des exigences du projet, de l'expertise de l'équipe et des besoins de portabilité. Qt se distingue par sa portabilité multiplateforme, tandis que MFC est dédié à l'écosystème Windows.
Les étapes typiques de création d'une application GUI en C++ comprennent la conception de l'interface, la création et l'enregistrement des classes de fenêtre, l'implémentation de la boucle de messages et la gestion des événements, puis l'implémentation de la logique applicative.
Voici un exemple de création d'une fenêtre basique avec Qt :
#include <QApplication>
#include <QWidget>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget fenetre;
fenetre.setFixedSize(300, 200);
fenetre.setWindowTitle("Première fenêtre GUI");
fenetre.show();
return app.exec();
}
L'objet QApplication gère le flux de contrôle et les paramètres principaux de l'application, tandis que QWidget constitue la classe de base pour les fenêtres et les contrôles. Les méthodes setFixedSize et setWindowTitle configurent respectivement la taille et le titre de la fenêtre, et show l'affiche à l'écran.
Configuration de l'environnement de développement
Avant de développer une application GUI, il convient de préparer l'environnement de développement en installant un compilateur, les bibliothèques tierces et un IDE. Pour Qt, l'IDE Qt Creator est recommendé. Les étapes d'installation incluent le téléchargement de l'installateur Qt, la sélection des composants (bibliothèques et compilateur), la configuration des variables d'environnement, et la création d'un projet de type Qt Widgets Application.
Développement GUI avec MFC
MFC est une bibliothèque C++ de Microsoft qui encapsule l'API Windows pour simplifier le développement d'interfaces graphiques. Les classes principales incluent CWinApp (application), CFrameWnd (fenêtre principale), CWnd (classe de base des fenêtres), CMDIChildWnd (fenêtres MDI) et CDialog (boîtes de dialogue).
Créer une fenêtre MFC implique de dériver une classe de CWnd, de surcharger les méthodes de dessin et de mettre en place des tables de messages pour associer les événements aux gestionnaires.
// MaFenetre.h
class CMaFenetre : public CWnd {
public:
virtual BOOL Creer(LPCTSTR szNomClasse, LPCTSTR szTitre, DWORD dwStyle,
const RECT& rect, CWnd* pParent, UINT nID);
afx_msg void OnClicGauche(UINT nFlags, CPoint point);
DECLARE_MESSAGE_MAP()
};
// MaFenetre.cpp
BOOL CMaFenetre::Creer(LPCTSTR szNomClasse, LPCTSTR szTitre, DWORD dwStyle,
const RECT& rect, CWnd* pParent, UINT nID) {
return CWnd::Create(szNomClasse, szTitre, dwStyle, rect, pParent, nID);
}
void CMaFenetre::OnClicGauche(UINT nFlags, CPoint point) {
// Traitement du clic gauche
}
BEGIN_MESSAGE_MAP(CMaFenetre, CWnd)
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
Cas pratique : calcualtrice et menus dynamiques
Pour réaliser une calculatrice simple, on définit une classe de fenêtre contenant des boutons et des champs texte. Avec l'aide de l'assistant de classes de MFC, on génère rapidement le code de gestion des événements pour chaque élément de l'interface.
Les menus dynamiques et les boîtes de dialogue sont également des besoins fréquents. Un menu dynamique peut adapter ses options selon l'état de l'application au moment de l'exécution :
void CMonEditeur::OnUpdateFichierEnregistrer(CCmdUI* pCmdUI) {
pCmdUI->Enable(m_bDocumentModifie);
}
void CMonEditeur::OnFichierEnregistrer() {
// Logique de sauvegarde du fichier
}
Opérations sur fichiers et accès aux bases de données
Manipulation du système de fichiers
Les opérations sur fichiers en C++ s'appuient sur l'en-tête <fstream> qui fournit trois classes : ifstream pour la lecture, ofstream pour l'écriture, et fstream pour les deux opérations simultanément.
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ofstream sortie("donnees.txt");
if (sortie.is_open()) {
sortie << "Contenu de test" << std::endl;
sortie.close();
}
std::ifstream entree("donnees.txt");
if (entree.is_open()) {
std::string ligne;
while (getline(entree, ligne)) {
std::cout << ligne << std::endl;
}
entree.close();
}
std::remove("donnees.txt");
return 0;
}
Parcours et gestion des répertoires
Depuis C++17, la bibliothèque <filesystem> offre une API type-safe pour parcourir, créer et supprimer des répertoires :
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
void explorerDossier(const fs::path& chemin) {
if (fs::exists(chemin) && fs::is_directory(chemin)) {
for (const auto& entree : fs::directory_iterator(chemin)) {
auto p = entree.path();
if (fs::is_directory(p)) {
std::cout << "[Dossier] " << p << std::endl;
} else if (fs::is_regular_file(p)) {
std::cout << "[Fichier] " << p << std::endl;
}
}
}
}
int main() {
explorerDossier("/chemin/vers/dossier");
return 0;
}
Accès aux bases de données via ODBC
ODBC (Open Database Connectivity) définit une API standard permettant aux applications d'accéder à différents systèmes de gestion de bases de données via des pilotes. La procédure consiste à configurer un pilote ODBC, créer un DSN, puis établir la connexion depuis le code C++.
#include <iostream>
#include <windows.h>
#include <sql.h>
#include <sqlext.h>
int main() {
SQLHENV hEnv;
SQLHDBC hDbc;
SQLHSTMT hStmt;
SQLRETURN ret;
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);
ret = SQLConnect(hDbc, (SQLCHAR*)"MON_DSN", SQL_NTS,
(SQLCHAR*)"utilisateur", SQL_NTS,
(SQLCHAR*)"motdepasse", SQL_NTS);
if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
SQLExecDirect(hStmt, (SQLCHAR*)"SELECT * FROM clients", SQL_NTS);
SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
}
SQLDisconnect(hDbc);
SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
return 0;
}
Pour récupérer les résultats d'une requête, on utilise SQLFetch et SQLGetData dans une boucle :
SQLRETURN ret;
while ((ret = SQLFetch(hStmt)) == SQL_SUCCESS) {
SQLCHAR nom[100];
SQLLEN longueur;
SQLGetData(hStmt, 1, SQL_C_CHAR, nom, sizeof(nom), &longueur);
std::cout << "Nom : " << nom << std::endl;
}
Synchronisation fichiers et base de données
Lors de l'importation ou l'exportation de gros volumes de données, l'insertion par lots améliore significativement les performances. La stratégie consiste à écrire les données dans un fichier temporaire, puis à les importer en masse dans la base.
Pour garantir la cohérence entre fichiers et base de données, il est recommandé d'utiliser des transactions qui englobent les deux opérations, permettant un rollback en cas d'erreur.
Structures de données et algorithmes en C++
Implémentation des structures de données fondamentales
Listes chaînées
Une liste chaînée est une structure linéaire composée de nœuds, chacun contenant une valeur et un pointeur vers le nœud suivant :
class Noeud {
public:
int donnee;
Noeud* suivant;
Noeud(int val) : donnee(val), suivant(nullptr) {}
};
class ListeChainee {
private:
Noeud* tete;
public:
ListeChainee() : tete(nullptr) {}
void ajouter(int val) {
Noeud* nouveau = new Noeud(val);
nouveau->suivant = tete;
tete = nouveau;
}
void afficher() {
Noeud* courant = tete;
while (courant) {
std::cout << courant->donnee << " ";
courant = courant->suivant;
}
std::cout << std::endl;
}
};
Pile et File
La pile (LIFO) et la file (FIFO) sont des structures d'accès restreint. Une pile permet l'ajout et le retrait d'éléments au sommet, tandis qu'une file fonctoinne en ajoutant en queue et en retirant en tête.
#include <vector>
#include <list>
template <typename T>
class Pile {
std::vector<T> elements;
public:
void empiler(const T& val) { elements.push_back(val); }
void depiler() { if (!elements.empty()) elements.pop_back(); }
T& sommet() { return elements.back(); }
};
template <typename T>
class File {
std::list<T> elements;
public:
void enfiler(const T& val) { elements.push_back(val); }
void defiler() { if (!elements.empty()) elements.pop_front(); }
T& tete() { return elements.front(); }
};
Arbres et graphes
Les arbres sont des structures hiérarchiques non linéaires où chaque nœud possède un parent et potentiellement plusieurs enfants. Les graphes sont composés de sommets reliés par des arêtes, couramment représentés par listes d'adjacence :
#include <vector>
class Graphe {
std::vector<std::vector<int>> listeAdj;
public:
Graphe(int n) : listeAdj(n) {}
void ajouterArete(int source, int destination) {
listeAdj[source].push_back(destination);
}
void afficher() {
for (size_t i = 0; i < listeAdj.size(); ++i) {
std::cout << i << " -> ";
for (int v : listeAdj[i]) {
std::cout << v << " ";
}
std::cout << std::endl;
}
}
};
Algorithmes et conception orientée objet
Les algorithmes de tri et de recherche sont essentiels. Le C++ fournit std::sort dans la bibliothèque standard, mais on peut également implémenter des algorithmes personnalisés comme le tri rapide.
L'encapsulation d'algorithmes dans des classes permet de les rendre réutilisables et extensibles. Le pattern Strategy permet d'interchanger dynamiquement des algorithmes :
class StrategieTri {
public:
virtual void trier(std::vector<int>& arr) = 0;
virtual ~StrategieTri() = default;
};
class TriRapide : public StrategieTri {
public:
void trier(std::vector<int>& arr) override {
quickSort(arr, 0, arr.size() - 1);
}
private:
void quickSort(std::vector<int>& arr, int debut, int fin) {
// Implémentation du tri rapide
}
};
class ContexteTri {
StrategieTri* strategie;
public:
ContexteTri(StrategieTri* s) : strategie(s) {}
void executer(std::vector<int>& arr) { strategie->trier(arr); }
};
Le pattern Template Method définit le squelette d'un algorithme dans une classe de base, déléguant certaines étapes aux sous-classes :
class ModeleAlgorithme {
public:
void executer() {
etape1();
etape2();
etape3();
}
protected:
virtual void etape1() = 0;
virtual void etape2() = 0;
virtual void etape3() = 0;
};
class AlgorithmeConcret : public ModeleAlgorithme {
protected:
void etape1() override { /* implémentation */ }
void etape2() override { /* implémentation */ }
void etape3() override { /* implémentation */ }
};
Programmation réseau et protocole TCP/IP
Bases de la programmation socket
La programmation réseau en C++ s'appuie sur l'API sockets du système d'exploitation. Trois types de sockets existent : les sockets de flux (SOCK_STREAM, TCP) pour une communication fiable et orientée connexion, les sockets de datagrammes (SOCK_DGRAM, UDP) pour une communication sans connexion, et les sockets bruts (SOCK_RAW) pour un accès direct aux protocoles bas niveau.
Le modèle TCP/IP comporte quatre couches : application (HTTP, FTP, SMTP), transport (TCP, UDP), réseau (IP) et liaison (Ethernet, Wi-Fi).
Modèle client-serveur
Le modèle client-serveur est le schéma de communication le plus répandu. Le serveur écoute les connexions entrantes, accepte les demandes des clients et échange des données.
Côté serveur :
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
int main() {
int socketServeur, socketClient;
struct sockaddr_in adresse;
int taille = sizeof(adresse);
char tampon[1024] = {0};
const char* message = "Bienvenue sur le serveur";
socketServeur = socket(AF_INET, SOCK_STREAM, 0);
adresse.sin_family = AF_INET;
adresse.sin_addr.s_addr = INADDR_ANY;
adresse.sin_port = htons(8080);
bind(socketServeur, (struct sockaddr*)&adresse, sizeof(adresse));
listen(socketServeur, 3);
socketClient = accept(socketServeur, (struct sockaddr*)&adresse, (socklen_t*)&taille);
send(socketClient, message, strlen(message), 0);
close(socketServeur);
return 0;
}
Côté client :
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int sock = 0;
struct sockaddr_in addrServ;
char tampon[1024] = {0};
sock = socket(AF_INET, SOCK_STREAM, 0);
addrServ.sin_family = AF_INET;
addrServ.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &addrServ.sin_addr);
connect(sock, (struct sockaddr*)&addrServ, sizeof(addrServ));
read(sock, tampon, 1024);
std::cout << "Réponse du serveur : " << tampon << std::endl;
close(sock);
return 0;
}
Techniques avancées
Les entrées/sorties non bloquantes permettent à plusieurs connexions d'être actives simultanément. Les E/S asynchrones notifient le programme lorsqu'une opération est terminée, réduisant la consommation CPU. Pour sécuriser les communications, les protocoles SSL/TLS chiffrent les connexions TCP, et la bibliothèque OpenSSL fournit les API nécessaires à leur implémentation en C++.