Chapitre 6
Programmation procédurale en langage C : structures de contrôle, fonctions et algorithmes
Lecture : 32 minQuiz de 12 questions
# Fiche de cours : Programmation procédurale en langage C
## Introduction
Le langage C, créé par Dennis Ritchie en 1972, est un langage de programmation procédural qui constitue la base de nombreux systèmes d'exploitation et applications embarquées. Pour les élèves de Terminale TI, la maîtrise du C est essentielle car elle permet de comprendre le fonctionnement interne des ordinateurs et de développer des algorithmes efficaces. Ce chapitre couvre l'ensemble des structures de contrôle, la création de fonctions, la manipulation de tableaux, la récursivité, la gestion des erreurs et la production d'applications complètes comme une calculatrice. L'approche par compétences du programme MINESEC (juillet 2020) vise à rendre l'élève capable de résoudre des problèmes concrets en écrivant des programmes C structurés et fiables.
## I. Structures alternatives : if, if...else, switch
### 1. La structure alternative simple : if
La structure `if` permet d'exécuter un bloc d'instructions uniquement si une condition est vraie. En C, la condition doit être placée entre parenthèses et le bloc d'instructions entre accolades.
```c
#include <stdio.h>
int main() {
int age = 18;
// Vérification de la majorité
if (age >= 18) {
printf("Vous êtes majeur.\n");
}
return 0;
}
```
> **À RETENIR** : La syntaxe de base de `if` est `if (condition) { instructions }`. Si la condition est fausse, le bloc est ignoré. En C, toute valeur non nulle est considérée comme vraie, et zéro comme faux.
### 2. La structure alternative complète : if...else
La structure `if...else` permet de traiter les deux cas : condition vraie ou condition fausse.
```c
#include <stdio.h>
int main() {
int note = 12;
if (note >= 10) {
printf("Admis\n");
} else {
printf("Ajourné\n");
}
// Version avec else if pour plusieurs cas
if (note >= 16) {
printf("Mention Très Bien\n");
} else if (note >= 14) {
printf("Mention Bien\n");
} else if (note >= 12) {
printf("Mention Assez Bien\n");
} else if (note >= 10) {
printf("Passable\n");
} else {
printf("Échec\n");
}
return 0;
}
```
### 3. La structure switch
La structure `switch` est une alternative à `if...else if...else` lorsque l'on compare une variable à plusieurs valeurs constantes. Elle est plus lisible et parfois plus efficace.
```c
#include <stdio.h>
int main() {
int jour = 3;
switch (jour) {
case 1:
printf("Lundi\n");
break;
case 2:
printf("Mardi\n");
break;
case 3:
printf("Mercredi\n");
break;
case 4:
printf("Jeudi\n");
break;
case 5:
printf("Vendredi\n");
break;
case 6:
printf("Samedi\n");
break;
case 7:
printf("Dimanche\n");
break;
default:
printf("Jour invalide\n");
}
return 0;
}
```
> **À RETENIR** : Le mot-clé `break` est indispensable dans chaque `case` pour éviter que l'exécution continue dans les cas suivants (phénomène appelé "fall-through"). Le cas `default` est optionnel et s'exécute si aucun cas ne correspond.
### 4. Opérateurs de comparaison et logiques
Les conditions utilisent les opérateurs suivants :
- Comparaison : `==` (égal), `!=` (différent), `<`, `>`, `<=`, `>=`
- Logiques : `&&` (ET), `||` (OU), `!` (NON)
```c
#include <stdio.h>
int main() {
int age = 25;
char sexe = 'F';
// Condition composée
if ((age >= 18 && age <= 35) && (sexe == 'F')) {
printf("Candidate éligible pour le concours.\n");
}
// Utilisation de la négation
if (!(age < 18)) {
printf("Vous êtes majeur.\n");
}
return 0;
}
```
## II. Boucles : for, while, do...while
### 1. La boucle for
La boucle `for` est utilisée lorsque le nombre d'itérations est connu à l'avance. Elle comporte trois parties : initialisation, condition de continuation, incrémentation.
```c
#include <stdio.h>
int main() {
int i;
// Affichage des nombres de 1 à 10
for (i = 1; i <= 10; i++) {
printf("%d ", i);
}
printf("\n");
// Boucle décroissante
for (i = 10; i >= 1; i--) {
printf("%d ", i);
}
printf("\n");
// Boucle avec pas de 2
for (i = 0; i <= 20; i += 2) {
printf("%d ", i);
}
printf("\n");
return 0;
}
```
> **À RETENIR** : La syntaxe de `for` est `for (initialisation; condition; incrémentation) { instructions }`. Les trois parties sont facultatives mais les points-virgules sont obligatoires. Une boucle infinie s'écrit `for(;;)`.
### 2. La boucle while
La boucle `while` exécute un bloc d'instructions tant qu'une condition est vraie. La condition est testée avant chaque itération.
```c
#include <stdio.h>
int main() {
int nombre = 1234;
int somme = 0;
// Calcul de la somme des chiffres d'un nombre
while (nombre > 0) {
somme += nombre % 10; // Ajoute le dernier chiffre
nombre /= 10; // Supprime le dernier chiffre
}
printf("Somme des chiffres : %d\n", somme);
// Lecture de valeurs jusqu'à saisie de 0
int valeur;
printf("Entrez des nombres (0 pour arrêter) : ");
scanf("%d", &valeur);
while (valeur != 0) {
printf("Vous avez saisi : %d\n", valeur);
printf("Entrez un autre nombre : ");
scanf("%d", &valeur);
}
return 0;
}
```
### 3. La boucle do...while
La boucle `do...while` est similaire à `while`, mais la condition est testée après l'exécution du bloc. Elle garantit donc au moins une exécution.
```c
#include <stdio.h>
int main() {
int choix;
// Menu avec validation de saisie
do {
printf("=== MENU ===\n");
printf("1. Ajouter\n");
printf("2. Modifier\n");
printf("3. Supprimer\n");
printf("4. Quitter\n");
printf("Votre choix : ");
scanf("%d", &choix);
switch (choix) {
case 1:
printf("Ajout effectué.\n");
break;
case 2:
printf("Modification effectuée.\n");
break;
case 3:
printf("Suppression effectuée.\n");
break;
case 4:
printf("Au revoir !\n");
break;
default:
printf("Choix invalide, réessayez.\n");
}
} while (choix != 4);
return 0;
}
```
> **À RETENIR** : La différence fondamentale entre `while` et `do...while` est que `do...while` exécute le bloc au moins une fois, car la condition est vérifiée à la fin. Utilisez `do...while` pour les menus et les validations de saisie.
### 4. Instructions break et continue
- `break` : sort immédiatement de la boucle
- `continue` : passe à l'itération suivante
```c
#include <stdio.h>
int main() {
int i;
// break : arrête la boucle
for (i = 1; i <= 10; i++) {
if (i == 5) {
break; // Sort de la boucle quand i vaut 5
}
printf("%d ", i);
}
printf("\nAprès break : %d\n", i);
// continue : saute une itération
for (i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // Saute les nombres pairs
}
printf("%d ", i);
}
printf("\n");
return 0;
}
```
## III. Déclaration et appel d'une fonction en C
### 1. Structure d'une fonction
Une fonction en C est un bloc de code réutilisable qui effectue une tâche spécifique. Elle se compose de :
- Un type de retour
- Un nom
- Des paramètres (optionnels)
- Un corps d'instructions
```c
#include <stdio.h>
// Déclaration de fonction (prototype)
int addition(int a, int b);
float moyenne(float notes[], int taille);
void afficherMessage(char message[]);
int main() {
// Appel de la fonction addition
int resultat = addition(15, 27);
printf("15 + 27 = %d\n", resultat);
// Appel de la fonction moyenne
float notes[] = {12.5, 14.0, 9.5, 16.5};
float moy = moyenne(notes, 4);
printf("Moyenne : %.2f\n", moy);
// Appel de la fonction void
afficherMessage("Bienvenue en Terminale TI");
return 0;
}
// Définition de la fonction addition
int addition(int a, int b) {
return a + b;
}
// Définition de la fonction moyenne
float moyenne(float notes[], int taille) {
float somme = 0;
for (int i = 0; i < taille; i++) {
somme += notes[i];
}
return somme / taille;
}
// Définition de la fonction void (ne retourne rien)
void afficherMessage(char message[]) {
printf("%s\n", message);
}
```
> **À RETENIR** : Une fonction se déclare avec un prototype avant `main()` et se définit après. Le prototype indique le type de retour, le nom et les types des paramètres. Une fonction `void` ne retourne aucune valeur. Le mot-clé `return` renvoie une valeur et termine la fonction.
### 2. Types de fonctions selon le retour
```c
#include <stdio.h>
// Fonction sans paramètre et sans retour
void afficherTitre() {
printf("=== CALCULATRICE ===\n");
}
// Fonction sans paramètre avec retour
int saisirNombre() {
int n;
printf("Entrez un nombre : ");
scanf("%d", &n);
return n;
}
// Fonction avec paramètres et retour
int puissance(int base, int exposant) {
int resultat = 1;
for (int i = 0; i < exposant; i++) {
resultat *= base;
}
return resultat;
}
int main() {
afficherTitre();
int a = saisirNombre();
int b = saisirNombre();
printf("%d^%d = %d\n", a, b, puissance(a, b));
return 0;
}
```
### 3. Fonctions avec passage de tableaux
```c
#include <stdio.h>
#define TAILLE 5
// Fonction qui remplit un tableau
void remplirTableau(int tab[], int taille) {
for (int i = 0; i < taille; i++) {
printf("tab[%d] = ", i);
scanf("%d", &tab[i]);
}
}
// Fonction qui affiche un tableau
void afficherTableau(int tab[], int taille) {
printf("[");
for (int i = 0; i < taille; i++) {
printf("%d", tab[i]);
if (i < taille - 1) printf(", ");
}
printf("]\n");
}
// Fonction qui retourne la somme
int sommeTableau(int tab[], int taille) {
int somme = 0;
for (int i = 0; i < taille; i++) {
somme += tab[i];
}
return somme;
}
int main() {
int tableau[TAILLE];
remplirTableau(tableau, TAILLE);
afficherTableau(tableau, TAILLE);
printf("Somme : %d\n", sommeTableau(tableau, TAILLE));
return 0;
}
```
## IV. Variable locale et variable globale, transmission de paramètres
### 1. Variables locales
Une variable locale est déclarée à l'intérieur d'une fonction ou d'un bloc. Elle n'est accessible que dans ce bloc et est détruite à la sortie du bloc.
```c
#include <stdio.h>
void fonctionTest() {
int x = 10; // Variable locale à fonctionTest
printf("Dans fonctionTest : x = %d\n", x);
// x n'est pas accessible depuis main()
}
int main() {
int x = 5; // Variable locale à main
printf("Dans main : x = %d\n", x);
{
int y = 20; // Variable locale au bloc
printf("Dans le bloc : y = %d\n", y);
}
// y n'est plus accessible ici
fonctionTest();
return 0;
}
```
### 2. Variables globales
Une variable globale est déclarée en dehors de toutes les fonctions. Elle est accessible depuis toutes les fonctions du programme.
```c
#include <stdio.h>
// Variable globale
int compteur = 0;
void incrementer() {
compteur++; // Accès à la variable globale
}
void afficherCompteur() {
printf("Compteur : %d\n", compteur);
}
int main() {
afficherCompteur(); // Compteur : 0
incrementer();
incrementer();
incrementer();
afficherCompteur(); // Compteur : 3
compteur = 100; // Modification directe
afficherCompteur(); // Compteur : 100
return 0;
}
```
> **À RETENIR** : Les variables globales sont accessibles partout mais rendent le programme difficile à maintenir. Il est recommandé de privilégier les variables locales et de passer les données en paramètres. Une variable locale peut masquer une variable globale si elle porte le même nom.
### 3. Transmission de paramètres par valeur
En C, les paramètres sont transmis par valeur par défaut. La fonction reçoit une copie de la valeur, et les modifications effectuées dans la fonction n'affectent pas la variable d'origine.
```c
#include <stdio.h>
void modifierValeur(int x) {
x = x * 2;
printf("Dans la fonction : x = %d\n", x);
}
int main() {
int nombre = 10;
printf("Avant l'appel : nombre = %d\n", nombre);
modifierValeur(nombre);
printf("Après l'appel : nombre = %d\n", nombre);
// La valeur de nombre n'a pas changé !
return 0;
}
```
### 4. Transmission de paramètres par adresse (pointeurs)
Pour modifier une variable dans une fonction, on utilise les pointeurs. On transmet l'adresse de la variable avec `&` et on la reçoit avec un pointeur `*`.
```c
#include <stdio.h>
void echanger(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void incrementer(int *x) {
(*x)++;
}
int main() {
int x = 5, y = 10;
printf("Avant échange : x = %d, y = %d\n", x, y);
echanger(&x, &y);
printf("Après échange : x = %d, y = %d\n", x, y);
incrementer(&x);
printf("Après incrémentation : x = %d\n", x);
return 0;
}
```
> **À RETENIR** : Pour modifier une variable dans une fonction, il faut passer son adresse avec `&` et utiliser un pointeur `*` dans la fonction. L'opérateur `*` permet d'accéder à la valeur pointée. Les tableaux sont automatiquement passés par adresse en C.
### 5. Exemple complet : fonction de permutation avec retour multiple
```c
#include <stdio.h>
// Fonction qui calcule quotient et reste
void division(int dividende, int diviseur, int *quotient, int *reste) {
*quotient = dividende / diviseur;
*reste = dividende % diviseur;
}
int main() {
int a = 17, b = 5;
int q, r;
division(a, b, &q, &r);
printf("%d / %d = %d (reste %d)\n", a, b, q, r);
return 0;
}
```
## V. Algorithme de recherche séquentielle sur un tableau de nombres en C
### 1. Principe de la recherche séquentielle
La recherche séquentielle (ou recherche linéaire) consiste à parcourir le tableau élément par élément jusqu'à trouver la valeur recherchée ou atteindre la fin du tableau.
```c
#include <stdio.h>
#define TAILLE 10
// Fonction de recherche séquentielle
// Retourne l'indice de la valeur trouvée, ou -1 si absente
int rechercheSequentielle(int tab[], int taille, int valeur) {
for (int i = 0; i < taille; i++) {
if (tab[i] == valeur) {
return i; // Valeur trouvée à l'indice i
}
}
return -1; // Valeur non trouvée
}
int main() {
int tableau[TAILLE] = {15, 23, 8, 42, 7, 31, 19, 5, 28, 12};
int valeurRecherchee;
printf("Tableau : ");
for (int i = 0; i < TAILLE; i++) {
printf("%d ", tableau[i]);
}
printf("\n");
printf("Entrez la valeur à rechercher : ");
scanf("%d", &valeurRecherchee);
int position = rechercheSequentielle(tableau, TAILLE, valeurRecherchee);
if (position != -1) {
printf("Valeur %d trouvée à l'indice %d (position %d)\n",
valeurRecherchee, position, position + 1);
} else {
printf("Valeur %d non trouvée dans le tableau.\n", valeurRecherchee);
}
return 0;
}
```
### 2. Recherche séquentielle avec comptage des occurrences
```c
#include <stdio.h>
#define TAILLE 10
// Fonction qui compte les occurrences d'une valeur
int compterOccurrences(int tab[], int taille, int valeur) {
int compteur = 0;
for (int i = 0; i < taille; i++) {
if (tab[i] == valeur) {
compteur++;
}
}
return compteur;
}
// Fonction qui affiche toutes les positions
void afficherPositions(int tab[], int taille, int valeur) {
int trouve = 0;
printf("Positions de %d : ", valeur);
for (int i = 0; i < taille; i++) {
if (tab[i] == valeur) {
printf("%d ", i + 1);
trouve = 1;
}
}
if (!trouve) {
printf("aucune");
}
printf("\n");
}
int main() {
int tableau[TAILLE] = {5, 12, 5, 8, 5, 20, 5, 3, 5, 15};
int valeur = 5;
int nb = compterOccurrences(tableau, TAILLE, valeur);
printf("La valeur %d apparaît %d fois.\n", valeur, nb);
afficherPositions(tableau, TAILLE, valeur);
return 0;
}
```
> **À RETENIR** : La recherche séquentielle a une complexité en O(n) dans le pire cas (la valeur est à la fin ou absente). Elle fonctionne sur des tableaux non triés. Le retour de -1 est une convention pour indiquer que la valeur n'a pas été trouvée.
### 3. Recherche séquentielle avec arrêt anticipé
```c
#include <stdio.h>
#define TAILLE 8
int main() {
int notes[TAILLE] = {12, 15, 8, 19, 10, 14, 7, 16};
int seuil = 10;
int i;
// Recherche de la première note supérieure au seuil
for (i = 0; i < TAILLE; i++) {
if (notes[i] > seuil) {
printf("Première note > %d : %d à l'indice %d\n",
seuil, notes[i], i);
break; // Arrêt dès la première occurrence
}
}
if (i == TAILLE) {
printf("Aucune note supérieure à %d.\n", seuil);
}
return 0;
}
```
## VI. Algorithme de tri par sélection sur un tableau
### 1. Principe du tri par sélection
Le tri par sélection consiste à :
1. Trouver le plus petit élément du tableau
2. L'échanger avec le premier élément
3. Recommencer avec le sous-tableau restant (à partir de l'indice 1)
4. Continuer jusqu'à ce que tout le tableau soit trié
```c
#include <stdio.h>
#define TAILLE 8
// Fonction de tri par sélection
void triSelection(int tab[], int taille) {
int i, j, minIndex, temp;
for (i = 0; i < taille - 1; i++) {
// Recherche de l'indice du minimum dans tab[i..taille-1]
minIndex = i;
for (j = i + 1; j < taille; j++) {
if (tab[j] < tab[minIndex]) {
minIndex = j;
}
}
// Échange de tab[i] et tab[minIndex]
if (minIndex != i) {
temp = tab[i];
tab[i] = tab[minIndex];
tab[minIndex] = temp;
}
}
}
// Fonction d'affichage
void afficherTableau(int tab[], int taille) {
printf("[");
for (int i = 0; i < taille; i++) {
printf("%d", tab[i]);
if (i < taille - 1) printf(", ");
}
printf("]\n");
}
int main() {
int tableau[TAILLE] = {64, 25, 12, 22, 11, 89, 33, 45};
printf("Tableau original : ");
afficherTableau(tableau, TAILLE);
triSelection(tableau, TAILLE);
printf("Tableau trié : ");
afficherTableau(tableau, TAILLE);
return 0;
}
```
> **À RETENIR** : Le tri par sélection a une complexité de O(n²) dans tous les cas (pire, moyen et meilleur). Il effectue n-1 échanges au maximum. Il est simple à implémenter mais inefficace pour de grands tableaux.
### 2. Tri par sélection avec suivi pas à pas
```c
#include <stdio.h>
#define TAILLE 6
void triSelectionAvecTrace(int tab[], int taille) {
int i, j, minIndex, temp;
printf("État initial : ");
for (int k = 0; k < taille; k++) printf("%d ", tab[k]);
printf("\n");
for (i = 0; i < taille - 1; i++) {
minIndex = i;
for (j = i + 1; j < taille; j++) {
if (tab[j] < tab[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
temp = tab[i];
tab[i] = tab[minIndex];
tab[minIndex] = temp;
}
printf("Après itération %d : ", i + 1);
for (int k = 0; k < taille; k++) printf("%d ", tab[k]);
printf("\n");
}
}
int main() {
int tableau[TAILLE] = {29, 10, 14, 37, 13, 25};
triSelectionAvecTrace(tableau, TAILLE);
return 0;
}
```
### 3. Tri par sélection avec ordre décroissant
```c
#include <stdio.h>
#define TAILLE 6
void triSelectionDecroissant(int tab[], int taille) {
int i, j, maxIndex, temp;
for (i = 0; i < taille - 1; i++) {
maxIndex = i;
for (j = i + 1; j < taille; j++) {
if (tab[j] > tab[maxIndex]) {
maxIndex = j;
}
}
if (maxIndex != i) {
temp = tab[i];
tab[i] = tab[maxIndex];
tab[maxIndex] = temp;
}
}
}
int main() {
int tableau[TAILLE] = {29, 10, 14, 37, 13, 25};
printf("Avant tri : ");
for (int i = 0; i < TAILLE; i++) printf("%d ", tableau[i]);
printf("\n");
triSelectionDecroissant(tableau, TAILLE);
printf("Après tri décroissant : ");
for (int i = 0; i < TAILLE; i++) printf("%d ", tableau[i]);
printf("\n");
return 0;
}
```
## VII. Écriture d'une fonction récursive en C
### 1. Principe de la récursivité
Une fonction récursive est une fonction qui s'appelle elle-même. Elle doit comporter :
- Un cas de base (condition d'arrêt)
- Un cas récursif (appel à elle-même avec des paramètres modifiés)
```c
#include <stdio.h>
// Factorielle : n! = n * (n-1) * ... * 1
long factorielle(int n) {
// Cas de base
if (n <= 1) {
return 1;
}
// Cas récursif
return n * factorielle(n - 1);
}
int main() {
int n;
printf("Entrez un nombre : ");
scanf("%d", &n);
if (n < 0) {
printf("La factorielle n'est pas définie pour les nombres négatifs.\n");
} else {
printf("%d! = %ld\n", n, factorielle(n));
}
return 0;
}
```
> **À RETENIR** : Toute fonction récursive doit avoir un cas de base qui arrête la récursion. Sans cas de base, la fonction s'appelle indéfiniment et provoque un débordement de pile (stack overflow). La récursivité est élégante mais peut être moins efficace que l'itération.
### 2. Suite de Fibonacci
```c
#include <stdio.h>
// Suite de Fibonacci : F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)
int fibonacci(int n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
int n;
printf("Entrez le rang n : ");
scanf("%d", &n);
printf("Les %d premiers termes de Fibonacci :\n", n);
for (int i = 0; i < n; i++) {
printf("F(%d) = %d\n", i, fibonacci(i));
}
return 0;
}
```
### 3. Puissance récursive
```c
#include <stdio.h>
// Calcul de base^exposant par récursivité
double puissanceRecursive(double base, int exposant) {
// Cas de base
if (exposant == 0) {
return 1;
}
// Cas des exposants négatifs
if (exposant < 0) {
return 1.0 / puissanceRecursive(base, -exposant);
}
// Cas récursif
return base * puissanceRecursive(base, exposant - 1);
}
int main() {
double base;
int exposant;
printf("Base : ");
scanf("%lf", &base);
printf("Exposant : ");
scanf("%d", &exposant);
printf("%.2f^%d = %.4f\n", base, exposant,
puissanceRecursive(base, exposant));
return 0;
}
```
### 4. Recherche récursive dans un tableau
```c
#include <stdio.h>
#define TAILLE 8
// Recherche récursive d'une valeur dans un tableau
int rechercheRecursive(int tab[], int taille, int valeur, int indice) {
// Cas de base : fin du tableau
if (indice >= taille) {
return -1;
}
// Cas de base : valeur trouvée
if (tab[indice] == valeur) {
return indice;
}
// Cas récursif
return rechercheRecursive(tab, taille, valeur, indice + 1);
}
int main() {
int tableau[TAILLE] = {12, 45, 23, 67, 34, 89, 56, 78};
int valeur;
printf("Tableau : ");
for (int i = 0; i < TAILLE; i++) printf("%d ", tableau[i]);
printf("\n");
printf("Valeur à rechercher : ");
scanf("%d", &valeur);
int position = rechercheRecursive(tableau, TAILLE, valeur, 0);
if (position != -1) {
printf("Trouvée à l'indice %d\n", position);
} else {
printf("Non trouvée\n");
}
return 0;
}
```
### 5. Tour de Hanoï (exemple classique)
```c
#include <stdio.h>
// Déplacement de n disques de A vers C en utilisant B
void hanoi(int n, char depart, char intermediaire, char arrivee) {
if (n == 1) {
printf("Déplacer disque 1 de %c vers %c\n", depart, arrivee);
return;
}
hanoi(n - 1, depart, arrivee, intermediaire);
printf("Déplacer disque %d de %c vers %c\n", n, depart, arrivee);
hanoi(n - 1, intermediaire, depart, arrivee);
}
int main() {
int n;
printf("Nombre de disques : ");
scanf("%d", &n);
printf("Solution pour %d disques :\n", n);
hanoi(n, 'A', 'B', 'C');
return 0;
}
```
## VIII. Gestion des erreurs de compilation
### 1. Types d'erreurs en C
Les erreurs en programmation C se classent en trois catégories :
- Erreurs de compilation (syntaxe, types)
- Erreurs de liaison (linker)
- Erreurs d'exécution (logique, mémoire)
```c
// Exemple d'erreurs de compilation courantes
#include <stdio.h>
int main() {
// Erreur 1 : point-virgule manquant
int a = 5 // ERREUR : ; manquant
// Erreur 2 : variable non déclarée
b = 10; // ERREUR : b n'est pas déclarée
// Erreur 3 : type incompatible
int x = "texte"; // ERREUR : conversion invalide
// Erreur 4 : accolade manquante
if (a > 0) {
printf("Positif\n");
// ERREUR : accolade fermante manquante
return 0;
}
```
> **À RETENIR** : Les erreurs de compilation sont détectées par le compilateur. Les messages d'erreur indiquent généralement le fichier, la ligne et la nature de l'erreur. Il faut corriger les erreurs dans l'ordre où elles apparaissent, car une erreur peut en générer d'autres.
### 2. Erreurs de syntaxe courantes et corrections
```c
#include <stdio.h>
int main() {
// Erreur : utilisation de = au lieu de ==
int x = 10;
if (x = 5) { // ERREUR : affectation au lieu de comparaison
printf("x vaut 5\n");
}
// Correction : if (x == 5)
// Erreur : oubli de & dans scanf
int age;
printf("Âge : ");
scanf("%d", age); // ERREUR : il faut &age
// Erreur : indice de tableau hors limites
int tab[5] = {1, 2, 3, 4, 5};
tab[5] = 10; // ERREUR : indice 5 hors limites (0-4)
// Erreur : division par zéro
int a = 10, b = 0;
int resultat = a / b; // ERREUR : division par zéro
return 0;
}
```
### 3. Utilisation des directives de préprocesseur pour la gestion des erreurs
```c
#include <stdio.h>
// Définition de constantes
#define TAILLE_MAX 100
#define PI 3.14159
// Assertions pour vérifier des conditions
#include <assert.h>
int diviser(int a, int b) {
assert(b != 0); // Vérifie que b n'est pas nul
return a / b;
}
int main() {
// Vérification de la taille
int tableau[TAILLE_MAX];
printf("Taille max : %d\n", TAILLE_MAX);
printf("PI : %.5f\n", PI);
// Test de la fonction diviser
printf("10 / 2 = %d\n", diviser(10, 2));
// printf("10 / 0 = %d\n", diviser(10, 0)); // Provoque une assertion
return 0;
}
```
### 4. Gestion des erreurs d'exécution
```c
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
int n;
printf("Taille du tableau : ");
scanf("%d", &n);
// Allocation dynamique avec vérification
ptr = (int*)malloc(n * sizeof(int));
if (ptr == NULL) {
printf("Erreur : allocation mémoire impossible.\n");
return 1; // Code d'erreur
}
// Utilisation du tableau
for (int i = 0; i < n; i++) {
ptr[i] = i * i;
}
// Libération de la mémoire
free(ptr);
// Vérification de la saisie
int age;
printf("Entrez votre âge : ");
if (scanf("%d", &age) != 1) {
printf("Erreur : saisie invalide.\n");
return 1;
}
if (age < 0 || age > 150) {
printf("Erreur : âge invalide.\n");
return 1;
}
printf("Âge valide : %d\n", age);
return 0;
}
```
> **À RETENIR** : La gestion des erreurs est essentielle pour produire des programmes fiables. Vérifiez toujours les retours de fonctions (malloc, scanf, fopen), validez les saisies utilisateur et utilisez des codes de retour pour signaler les erreurs.
### 5. Erreurs de logique et débogage
```c
#include <stdio.h>
// Exemple de fonction avec erreur de logique
int calculerMoyenne(int notes[], int taille) {
int somme = 0;
// Erreur : boucle de 1 à taille au lieu de 0 à taille-1
for (int i = 1; i <= taille; i++) { // ERREUR : commence à 1
somme += notes[i]; // ERREUR : accès hors limites
}
return somme / taille;
}
// Version corrigée
int calculerMoyenneCorrigee(int notes[], int taille) {
int somme = 0;
for (int i = 0; i < taille; i++) {
somme += notes[i];
}
return somme / taille;
}
int main() {
int notes[] = {12, 15, 10, 18, 14};
int taille = 5;
printf("Moyenne : %d\n", calculerMoyenneCorrigee(notes, taille));
return 0;
}
```
## IX. Écriture d'un algorithme intégrant un enregistrement
### 1. Définition d'un enregistrement (struct) en C
Un enregistrement (ou structure) permet de regrouper plusieurs données de types différents sous un même nom.
```c
#include <stdio.h>
#include <string.h>
// Définition d'une structure Étudiant
struct Etudiant {
char nom[50];
char prenom[50];
int age;
float moyenne;
char matricule[20];
};
int main() {
// Déclaration et initialisation
struct Etudiant etudiant1;
// Affectation des champs
strcpy(etudiant1.nom, "MBALLA");
strcpy(etudiant1.prenom, "Jean");
etudiant1.age = 18;
etudiant1.moyenne = 14.5;
strcpy(etudiant1.matricule, "24TI001");
// Affichage
printf("=== FICHE ÉTUDIANT ===\n");
printf("Nom : %s\n", etudiant1.nom);
printf("Prénom : %s\n", etudiant1.prenom);
printf("Âge : %d ans\n", etudiant1.age);
printf("Moyenne : %.2f\n", etudiant1.moyenne);
printf("Matricule : %s\n", etudiant1.matricule);
return 0;
}
```
> **À RETENIR** : Une structure se déclare avec le mot-clé `struct`. Chaque champ est accessible avec l'opérateur point `.`. Les chaînes de caractères se copient avec `strcpy()` et se comparent avec `strcmp()`.
### 2. Tableau d'enregistrements
```c
#include <stdio.h>
#include <string.h>
#define NB_ETUDIANTS 3
struct Etudiant {
char nom[50];
char prenom[50];
float notes[5];
float moyenne;
};
// Fonction pour calculer la moyenne
void calculerMoyenne(struct Etudiant *e) {
float somme = 0;
for (int i = 0; i < 5; i++) {
somme += e->notes[i];
}
e->moyenne = somme / 5;
}
// Fonction pour afficher un étudiant
void afficherEtudiant(struct Etudiant e) {
printf("Nom : %s %s\n", e.prenom, e.nom);
printf("Notes : ");
for (int i = 0; i < 5; i++) {
printf("%.1f ", e.notes[i]);
}
printf("\nMoyenne : %.2f\n", e.moyenne);
printf("-------------------\n");
}
int main() {
struct Etudiant classe[NB_ETUDIANTS];
// Saisie des étudiants
for (int i = 0; i < NB_ETUDIANTS; i++) {
printf("Étudiant %d\n", i + 1);
printf("Nom : ");
scanf("%s", classe[i].nom);
printf("Prénom : ");
scanf("%s", classe[i].prenom);
printf("Notes (5 notes) : ");
for (int j = 0; j < 5; j++) {
scanf("%f", &classe[i].notes[j]);
}
calculerMoyenne(&classe[i]);
printf("\n");
}
// Affichage de tous les étudiants
printf("=== LISTE DES ÉTUDIANTS ===\n");
for (int i = 0; i < NB_ETUDIANTS; i++) {
afficherEtudiant(classe[i]);
}
// Recherche du meilleur étudiant
int meilleur = 0;
for (int i = 1; i < NB_ETUDIANTS; i++) {
if (classe[i].moyenne > classe[meilleur].moyenne) {
meilleur = i;
}
}
printf("Meilleur étudiant : %s %s (moyenne %.2f)\n",
classe[meilleur].prenom, classe[meilleur].nom,
classe[meilleur].moyenne);
return 0;
}
```
### 3. Enregistrement avec fonctions et pointeurs
```c
#include <stdio.h>
#include <string.h>
// Structure Produit
struct Produit {
char reference[20];
char designation[50];
float prixUnitaire;
int quantiteStock;
};
// Fonction qui initialise un produit
void initialiserProduit(struct Produit *p, char ref[], char des[],
float prix, int qte) {
strcpy(p->reference, ref);
strcpy(p->designation, des);
p->prixUnitaire = prix;
p->quantiteStock = qte;
}
// Fonction qui calcule la valeur du stock
float valeurStock(struct Produit p) {
return p.prixUnitaire * p.quantiteStock;
}
// Fonction qui met à jour la quantité
void mettreAJourStock(struct Produit *p, int nouvelleQte) {
p->quantiteStock = nouvelleQte;
}
// Fonction qui affiche un produit
void afficherProduit(struct Produit p) {
printf("Référence : %s\n", p.reference);
printf("Désignation : %s\n", p.designation);
printf("Prix unitaire : %.2f FCFA\n", p.prixUnitaire);
printf("Quantité en stock : %d\n", p.quantiteStock);
printf("Valeur du stock : %.2f FCFA\n", valeurStock(p));
printf("-------------------\n");
}
int main() {
struct Produit produits[3];
initialiserProduit(&produits[0], "REF001", "Cahier 200 pages", 500, 100);
initialiserProduit(&produits[1], "REF002", "Stylo bleu", 150, 250);
initialiserProduit(&produits[2], "REF003", "Sac à dos", 8500, 30);
printf("=== ÉTAT DU STOCK ===\n");
for (int i = 0; i < 3; i++) {
afficherProduit(produits[i]);
}
// Mise à jour du stock
mettreAJourStock(&produits[0], 150);
printf("Après mise à jour :\n");
afficherProduit(produits[0]);
return 0;
}
```
> **À RETENIR** : Avec les pointeurs sur structures, on utilise l'opérateur `->` pour accéder aux champs. Les structures peuvent être passées par valeur (copie) ou par adresse (pointeur). Pour les grandes structures, il est préférable de passer par adresse.
### 4. Enregistrement avec structures imbriquées
```c
#include <stdio.h>
#include <string.h>
// Structure Date
struct Date {
int jour;
int mois;
int annee;
};
// Structure Adresse
struct Adresse {
char ville[50];
char quartier[50];
char rue[50];
int numero;
};
// Structure Personne avec structures imbriquées
struct Personne {
char nom[50];
char prenom[50];
struct Date dateNaissance;
struct Adresse adresse;
};
void afficherPersonne(struct Personne p) {
printf("=== FICHE PERSONNE ===\n");
printf("Nom complet : %s %s\n", p.prenom, p.nom);
printf("Date de naissance : %02d/%02d/%d\n",
p.dateNaissance.jour, p.dateNaissance.mois, p.dateNaissance.annee);
printf("Adresse : %d %s, %s, %s\n",
p.adresse.numero, p.adresse.rue, p.adresse.quartier, p.adresse.ville);
}
int main() {
struct Personne personne1;
strcpy(personne1.nom, "FOTSO");
strcpy(personne1.prenom, "Marie");
personne1.dateNaissance.jour = 15;
personne1.dateNaissance.mois = 3;
personne1.dateNaissance.annee = 2006;
strcpy(personne1.adresse.ville, "Yaoundé");
strcpy(personne1.adresse.quartier, "Ngoa Ekélé");
strcpy(personne1.adresse.rue, "Rue 1234");
personne1.adresse.numero = 45;
afficherPersonne(personne1);
return 0;
}
```
## X. Production d'une calculatrice
### 1. Calculatrice simple avec menu
```c
#include <stdio.h>
// Fonctions de calcul
double addition(double a, double b) { return a + b; }
double soustraction(double a, double b) { return a - b; }
double multiplication(double a, double b) { return a * b; }
double division(double a, double b) {
if (b == 0) {
printf("Erreur : division par zéro !\n");
return 0;
}
return a / b;
}
double puissance(double a, double b) {
double resultat = 1;
for (int i = 0; i < (int)b; i++) {
resultat *= a;
}
return resultat;
}
double modulo(int a, int b) {
if (b == 0) {
printf("Erreur : modulo par zéro !\n");
return 0;
}
return a % b;
}
// Fonction d'affichage du menu
void afficherMenu() {
printf("\n=== CALCULATRICE ===\n");
printf("1. Addition\n");
printf("2. Soustraction\n");
printf("3. Multiplication\n");
printf("4. Division\n");
printf("5. Puissance\n");
printf("6. Modulo\n");
printf("0. Quitter\n");
printf("Votre choix : ");
}
int main() {
int choix;
double a, b;
do {
afficherMenu();
scanf("%d", &choix);
if (choix >= 1 && choix <= 6) {
printf("Entrez le premier nombre : ");
scanf("%lf", &a);
printf("Entrez le deuxième nombre : ");
scanf("%lf", &b);
}
switch (choix) {
case 1:
printf("%.2f + %.2f = %.2f\n", a, b, addition(a, b));
break;
case 2:
printf("%.2f - %.2f = %.2f\n", a, b, soustraction(a, b));
break;
case 3:
printf("%.2f * %.2f = %.2f\n", a, b, multiplication(a, b));
break;
case 4:
printf("%.2f / %.2f = %.2f\n", a, b, division(a, b));
break;
case 5:
printf("%.2f ^ %.2f = %.2f\n", a, b, puissance(a, b));
break;
case 6:
printf("%.0f %% %.0f = %.0f\n", a, b, modulo((int)a, (int)b));
break;
case 0:
printf("Au revoir !\n");
break;
default:
printf("Choix invalide. Réessayez.\n");
}
} while (choix != 0);
return 0;
}
```
### 2. Calculatrice avancée avec historique
```c
#include <stdio.h>
#include <string.h>
#define MAX_HISTORIQUE 50
// Structure pour l'historique
struct Operation {
char description[100];
double resultat;
};
// Fonction pour ajouter à l'historique
void ajouterHistorique(struct Operation historique[], int *nbOps,
char desc[], double res) {
if (*nbOps < MAX_HISTORIQUE) {
strcpy(historique[*nbOps].description, desc);
historique[*nbOps].resultat = res;
(*nbOps)++;
} else {
printf("Historique plein !\n");
}
}
// Fonction pour afficher l'historique
void afficherHistorique(struct Operation historique[], int nbOps) {
printf("\n=== HISTORIQUE ===\n");
if (nbOps == 0) {
printf("Aucune opération effectuée.\n");
return;
}
for (int i = 0; i < nbOps; i++) {
printf("%d. %s = %.2f\n", i + 1,
historique[i].description, historique[i].resultat);
}
}
int main() {
struct Operation historique[MAX_HISTORIQUE];
int nbOperations = 0;
int choix;
double a, b, resultat;
char description[100];
do {
printf("\n=== CALCULATRICE AVANCÉE ===\n");
printf("1. Addition\n");
printf("2. Soustraction\n");
printf("3. Multiplication\n");
printf("4. Division\n");
printf("5. Afficher l'historique\n");
printf("0. Quitter\n");
printf("Choix : ");
scanf("%d", &choix);
switch (choix) {
case 1:
printf("a + b\n");
printf("a = "); scanf("%lf", &a);
printf("b = "); scanf("%lf", &b);
resultat = a + b;
printf("Résultat : %.2f\n", resultat);
sprintf(description, "%.2f + %.2f", a, b);
ajouterHistorique(historique, &nbOperations, description, resultat);
break;
case 2:
printf("a - b\n");
printf("a = "); scanf("%lf", &a);
printf("b = "); scanf("%lf", &b);
resultat = a - b;
printf("Résultat : %.2f\n", resultat);
sprintf(description, "%.2f - %.2f", a, b);
ajouterHistorique(historique, &nbOperations, description, resultat);
break;
case 3:
printf("a * b\n");
printf("a = "); scanf("%lf", &a);
printf("b = "); scanf("%lf", &b);
resultat = a * b;
printf("Résultat : %.2f\n", resultat);
sprintf(description, "%.2f * %.2f", a, b);
ajouterHistorique(historique, &nbOperations, description, resultat);
break;
case 4:
printf("a / b\n");
printf("a = "); scanf("%lf", &a);
printf("b = "); scanf("%lf", &b);
if (b != 0) {
resultat = a / b;
printf("Résultat : %.2f\n", resultat);
sprintf(description, "%.2f / %.2f", a, b);
ajouterHistorique(historique, &nbOperations, description, resultat);
} else {
printf("Erreur : division par zéro !\n");
}
break;
case 5:
afficherHistorique(historique, nbOperations);
break;
case 0:
printf("Au revoir !\n");
break;
default:
printf("Choix invalide.\n");
}
} while (choix != 0);
return 0;
}
```
### 3. Calculatrice avec fonctions mathématiques avancées
```c
#include <stdio.h>
#include <math.h>
// Fonction pour le factoriel
long factorielle(int n) {
if (n <= 1) return 1;
return n * factorielle(n - 1);
}
// Fonction pour la racine carrée
double racineCarree(double x) {
if (x < 0) {
printf("Erreur : racine carrée d'un nombre négatif !\n");
return -1;
}
return sqrt(x);
}
// Fonction pour les fonctions trigonométriques
double sinus(double x) { return sin(x * M_PI / 180); } // en degrés
double cosinus(double x) { return cos(x * M_PI / 180); }
double tangente(double x) { return tan(x * M_PI / 180); }
// Fonction pour le logarithme
double logarithme(double x) {
if (x <= 0) {
printf("Erreur : logarithme d'un nombre non positif !\n");
return -1;
}
return log(x);
}
int main() {
int choix;
double x, y;
do {
printf("\n=== CALCULATRICE SCIENTIFIQUE ===\n");
printf("1. Addition\n");
printf("2. Soustraction\n");
printf("3. Multiplication\n");
printf("4. Division\n");
printf("5. Puissance\n");
printf("6. Racine carrée\n");
printf("7. Factorielle\n");
printf("8. Sinus (degrés)\n");
printf("9. Cosinus (degrés)\n");
printf("10. Tangente (degrés)\n");
printf("11. Logarithme népérien\n");
printf("0. Quitter\n");
printf("Choix : ");
scanf("%d", &choix);
switch (choix) {
case 1:
printf("a + b\n");
printf("a = "); scanf("%lf", &x);
printf("b = "); scanf("%lf", &y);
printf("Résultat : %.4f\n", x + y);
break;
case 2:
printf("a - b\n");
printf("a = "); scanf("%lf", &x);
printf("b = "); scanf("%lf", &y);
printf("Résultat : %.4f\n", x - y);
break;
case 3:
printf("a * b\n");
printf("a = "); scanf("%lf", &x);
printf("b = "); scanf("%lf", &y);
printf("Résultat : %.4f\n", x * y);
break;
case 4:
printf("a / b\n");
printf("a = "); scanf("%lf", &x);
printf("b = "); scanf("%lf", &y);
if (y != 0) {
printf("Résultat : %.4f\n", x / y);
} else {
printf("Erreur : division par zéro !\n");
}
break;
case 5:
printf("a ^ b\n");
printf("a = "); scanf("%lf", &x);
printf("b = "); scanf("%lf", &y);
printf("Résultat : %.4f\n", pow(x, y));
break;
case 6:
printf("sqrt(x)\n");
printf("x = "); scanf("%lf", &x);
double r = racineCarree(x);
if (r >= 0) printf("Résultat : %.4f\n", r);
break;
case 7:
printf("n!\n");
int n;
printf("n = "); scanf("%d", &n);
if (n >= 0) {
printf("Résultat : %ld\n", factorielle(n));
} else {
printf("Erreur : factorielle d'un nombre négatif !\n");
}
break;
case 8:
printf("sin(x) en degrés\n");
printf("x = "); scanf("%lf", &x);
printf("Résultat : %.4f\n", sinus(x));
break;
case 9:
printf("cos(x) en degrés\n");
printf("x = "); scanf("%lf", &x);
printf("Résultat : %.4f\n", cosinus(x));
break;
case 10:
printf("tan(x) en degrés\n");
printf("x = "); scanf("%lf", &x);
printf("Résultat : %.4f\n", tangente(x));
break;
case 11:
printf("ln(x)\n");
printf("x = "); scanf("%lf", &x);
double l = logarithme(x);
if (l >= 0) printf("Résultat : %.4f\n", l);
break;
case 0:
printf("Au revoir !\n");
break;
default:
printf("Choix invalide.\n");
}
} while (choix != 0);
return 0;
}
```
> **À RETENIR** : Une calculatrice complète intègre toutes les notions du chapitre : structures de contrôle (menu avec do...while et switch), fonctions (chaque opération est une fonction), gestion des erreurs (division par zéro, racine carrée négative), et structures de données (historique avec struct).
## Conclusion
La programmation procédurale en langage C constitue le socle fondamental de la formation en Informatique pour les élèves de Terminale TI. Ce chapitre a permis de maîtriser les structures de contrôle (alternatives et boucles), la création et l'utilisation de fonctions, la distinction entre variables locales et globales, ainsi que la transmission de paramètres par valeur et par adresse. Les algorithmes de recherche séquentielle et de tri par sélection sur les tableaux, la récursivité, la gestion des erreurs de compilation, l'utilisation des enregistrements (struct) et la production d'une calculatrice complète illustrent l'application concrète de ces concepts.
Ces compétences sont essentielles pour aborder des projets plus complexes et constituent une base solide pour l'étude de langages de programmation plus avancés. La maîtrise du C développe la rigueur, la logique algorithmique et la capacité à résoudre des problèmes de manière structurée. Pour progresser, l'élève est encouragé à pratiquer régulièrement en écrivant des programmes variés, à analyser les messages d'erreur du compilateur et à tester systématiquement ses programmes avec des cas limites. La programmation est une compétence qui s'acquiert par la pratique constante et la curiosité intellectuelle.
Quiz du chapitre
12 questions — vérifie chaque réponse pour voir l'explication.