Structure générale
Un code C est découpé en fonctions. Les instructions sont toujours écrites dans le corps des fonctions. À l’extérieur des fonctions (c’est-à-dire au niveau global du module), vous pouvez déclarer de nouveaux types, de nouvelles variables, ou indiquer l’existence d’autres fonctions. Tout programme C a un point d’entrée. C’est la fonction main. Lorsque vous exécuterez votre programme, le flot d’exécution commencera par rentrer dans cette fonction. Dans cette fonction, vous effectuerez des opérations, calculs, mais vous pouvez bien sûr appeler d’autres fonctions.
Les blocs d’instructions sont placés entre accolades { }. Les instructions sont terminées par un point virgule ;.
int main(void) {
// instructions ici
return 0;
}
Pour créer des objets complexes, il faut d’abord disposer de types simples. Les types suivants :
- Caractère :
char - Entiers :
char,short,int,long,long long - Boolean : c’est le type
int! (tout ce qui n’est pas zero est vrai)- Vous pouvez
#include <stdbool.h>pour avoirbool, true, falsede défini mais ce sont des raccourcis
- Vous pouvez
- Nombres à virgule flottante :
float,double,long double - Type pointeur : pointeur d’un autre type, qui est toujours représenté par une adresse en mémoire (sur 32 ou 64 bit selon la taille du bus d’adresse).
Attention la place que prennent chaqu’un de ces types dépend de l’architecture matériel cible l’or de la compilation. Et donc a destination d’un système hôte précis et indirectement de la taille du bus d’adresse. Il est possible de lancé un binnaire 32bits sur une architecture 64bits. Dans ce cas les systeme passe par un couche d’abstraction (WOW64 sous windows). Il existent également des architecture étendue (128bits, 256bits, 512bits, …).
| Type C | 8 bits | 16 bits | 32 bits | 64 bits |
|---|---|---|---|---|
char |
1 octet 0:1 | 1 octet | 1 octet | 1 octet |
short |
1 ou 2 octets | 2 octets | 2 ou 4 octets | 2 ou 4 octets |
int |
1 ou 2 octets | 2 ou 4 octets | 4 octets | 4 ou 8 octets |
long |
2 ou 4 octets | 4 octets | 4 ou 8 octets | 8 octets (LP64) |
long long |
4 ou 8 octets | 8 octets | 8 octets | 8 octets |
float |
4 octets | 4 octets | 4 octets | 4 octets |
double |
4 ou 8 octets | 8 octets | 4 ou 8 octets | 8 octets |
long double |
4 ou 8 octets | 8 ou 12 octets | 8, 12 ou 16 octets | 16 octets (selon ABI) |
Pointeur (void*) |
1 ou 2 octets | 2 octets | 4 octets | 8 octets |
La taille d’un pointeur dépend uniquement de la largeur d’adresse, pas du type pointé.
Notez également le return 0; dans la fonction main c’est une convention sur les système Unix. C’est une information que le système pourras récupéré pour savoir si le programme c’est terminé correctement. Dans ce cas (0) tout c’est bien déroulé. Dans le cas contraire (1) c’est une erreur général ou autre autre valeur -> à vous de définir, juste pour dire fin du programme imprévue. C’est un peu la version C des exceptions (pour ceux qui ont fait du Java ou du C#), il n’y a pas de message direct mais c’est super rapide.
Déclaration et affectations
Une affectation est l’opération qui attribue une valeur à une variable. L’opérateur d’affectation simple est =.
int x ; // Déclaration de la variable (Dit coucou @x éxiste)
x = 20; // @x prend la valeur 20
int y = 10; // Les deux à la fois
Le C propose également des opérateurs d’affectation composés qui combinent une opération arithmétique avec l’affectation :
+=: addition et affectation-=: soustraction et affectation*=: multiplication et affectation/=: division et affectation%=: modulo et affectation&=,|=,^=: opérateurs bit à bit avec affectation<<=,>>=: décalages avec affectation
int x = 10;
x += 5; // équivalent à x = x + 5; donc x = 15
x -= 3; // équivalent à x = x - 3; donc x = 12
x *= 2; // équivalent à x = x * 2; donc x = 24
x /= 4; // équivalent à x = x / 4; donc x = 6
Affectation multiple
Le C permet les affectations multiples dans une même expression :
int a, b, c;
a = b = c = 5; // a, b et c reçoivent tous la valeur 5
Cela fonctionne parce que l’affectation est une expression qui retourne la valeur affectée. L’évaluation se fait de droite à gauche : c reçoit 5, puis b reçoit le résultat de c = 5 (qui est 5), puis a reçoit le résultat de b = 5.
Portée des variables
La portée (scope) d’une variable détermine la région du programme où cette variable peut être utilisée. Le C reconnaît plusieurs niveaux de portée : globale, locale (au niveau d’une fonction) et au niveau d’un bloc (sur les nouvelles normes).
Variables globales
Une variable déclarée en dehors de toute fonction a une portée globale. Elle est accessible depuis n’importe quel endroit du programme, y compris depuis d’autres fichiers sources si elle est déclarée comme extern.
int compteur = 0; // variable globale
void incrementer(void) {
compteur++;
}
int main(void) {
printf("Compteur = %d\n", compteur); // 0
incrementer();
printf("Compteur = %d\n", compteur); // 1
return 0;
}
Les variables globales sont stockées dans la section des données globales du programme. Elles existent tout aux long de l’éxécution du processus.
Variables locales
Une variable déclarée à l’intérieur d’une fonction a une portée locale à cette fonction. Elle n’existe que durant l’exécution de la fonction et n’est pas accessible depuis l’extérieur.
void ma_fonction(void) {
int x = 5; // variable locale à ma_fonction
printf("x = %d\n", x);
}
int main(void) {
ma_fonction();
// printf("%d\n", x); // ERREUR : x n'est pas accessible ici
return 0;
}
Les variables locales sont stockées sur la pile (stack). Leur durée de vie est limitée à l’exécution de la fonction.
Variables de bloc
Une variable peut être déclarée à l’intérieur d’un bloc (section de code délimitée par { }), y compris à l’intérieur d’une boucle ou d’une condition. Elle est alors locale à ce bloc.
int main(void) {
int x = 10;
printf("x = %d\n", x); // 10
{
int x = 20; // nouvelle variable, même nom, portée limitée au bloc
int y = 42;
printf("x = %d\n", x); // 20
}
printf("x = %d\n", x); // 10 (x du bloc n'existe plus) et y n'existe plus
return 0;
}
Cette technique, appelée shadowing, permet de réutiliser un nom de variable dans un bloc imbriqué. Cependant, elle peut rendre le code confus et est généralement déconseillée.
Durée de vie
La durée de vie (lifetime) d’une variable est distinct de sa portée. Une variable peut avoir une portée locale mais une durée de vie plus longue grâce au mot-clé static.
void compteur_appels(void) {
static int appels = 0; // initialisée une seule fois, persiste entre les appels
appels++;
printf("Appelée %d fois\n", appels);
}
int main(void) {
compteur_appels(); // Appelée 1 fois
compteur_appels(); // Appelée 2 fois
compteur_appels(); // Appelée 3 fois
return 0;
}
Une variable statique locale est initialisée une seule fois au lancement du programme, puis sa valeur persiste entre les appels de la fonction.
Visibilité
En préfixant une variable globale par le mot-clé static, on limite sa visibilité au fichier courant. Cela crée une portée au niveau du fichier.
fichier1.c :
static int secret = 42; // visible uniquement dans fichier1.c
int get_secret(void) {
return secret;
}
fichier2.c :
extern int secret; // ERREUR : secret n'est pas accessible (static)
Le préprocesseur
Le préprocesseur est un étape de compilation qui traite les directives débutant par # avant que le compilateur ne voie le code. Le préprocesseur effectue des transformations textuelles sur le code source : inclusions de fichiers, substitutions de macros, compilation conditionnelle, etc.
Inclusions de fichiers
La directive #include demande au préprocesseur d’insérer le contenu d’un fichier à l’endroit où elle est écrite. Il existe deux formes :
#include <fichier.h>: cherche le fichier dans les répertoires standards du système (fichiers d’en-tête système)#include "fichier.h": cherche le fichier dans le répertoire courant, c’est généralement un path relatif
#include <stdio.h> // bibliothèque standard d'entrée-sortie
#include <math.h> // bibliothèque mathématique
#include "mon_header.h" // en-tête local
Les fichiers *.h contiennent typiquement des déclarations de fonctions, des définitions de types et des constantes. Ils ne doivent pas contenir l’implémentation de fonctions (sauf pour les fonctions inline). Une bonne pratique est de protéger les en-têtes contre les inclusions multiples :
// mon_header.h
#ifndef MON_HEADER_H
#define MON_HEADER_H
// contenu de l'en-tête
#endif // MON_HEADER_H
Si MON_HEADER_H a déjà été défini, le fichier entier est ignoré. Cela prévient les redéfinitions.
Dans les nouvelles normes C20 et C++ il y a la directive #pragma once qui simplifie la démarche, plus qu’une ligne pour protéger.
Substitution
La directive #define définit une macro : une substitution textuelle effectuée par le préprocesseur.
#define PI 3.14159265
#define MAX_TAILLE 100
float aire = PI * rayon * rayon;
int tableau[MAX_TAILLE];
À la compilation, PI est remplacé partout par 3.14159265 et MAX_TAILLE par 100. Contrairement à une variable, une macro n’a pas de type et n’occupe pas d’espace mémoire à l’exécution : c’est une substitution pure du texte. Dans le cours précédent nous avons vue le flag --save-temp qui sauvegarde les fichier intermédiaires. Notament le fichier .i qui est la transformation après le préprocessing. On auras donc :
float aire = 3.14159265 * rayon * rayon;
int tableau[10];
Macros avec arguments
Les macros peuvent accepter des arguments, ce qui permet de créer des pseudo-fonctions :
#define CARRE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
int resultat = CARRE(5); // remplacé par ((5) * (5)) = 25
int max_val = MAX(10, 20); // remplacé par ((10) > (20) ? (10) : (20)) = 20
Attention : les macros n’effectuent qu’une substitution textuelle. Les parenthèses sont essentielles pour éviter les problèmes de priorité d’opérateurs
#define MAUVAISE_MACRO(x) x * x
int resultat = MAUVAISE_MACRO(2 + 3); // remplacé par 2 + 3 * 2 + 3 = 11, pas 25 !
Opérateurs du préprocesseur
Stringification : # convertit l’argument en chaîne de caractères :
#define AFFICHER(x) printf(#x " = %d\n", x)
int a = 42;
AFFICHER(a); // affiche : a = 42
Concatenation : ## fusionne deux tokens :
#define CONCATENER(a, b) a ## b
int xy = 100;
int z = CONCATENER(x, y); // z = xy = 100
Compilation conditionnelle
Les directives #if, #ifdef, #ifndef, #else, #elif et #endif permettent d’inclure ou d’exclure du code selon des conditions évaluées à la compilation.
#ifdef DEBUG
printf("Mode debug activé\n");
#endif
#if defined(WIN32)
// code spécifique à Windows
#elif defined(__linux__)
// code spécifique à Linux
#else
// code par défaut
#endif
Cela permet d’adapter le code à différentes plateformes ou configurations sans devoir maintenir plusieurs versions du source. Les defines WIN32, __linux__ sont principalement géré par le system hôte ou par la chaine de compilation (ex: CMake)
Porté des defines
En C/C++, les #define sont traités par le préprocesseur, avant la compilation. Ils n’ont pas de portée au sens des blocs { } : ils sont valides depuis l’endroit où ils sont définis jusqu’à la fin du fichier, sauf si on les annule avec #undef.
void fonction() {
#define TRUC 42 // Défini ici...
}
// ...mais valable en réalité au-delà du bloc !
int main() {
int x = TRUC; // OK : TRUC existe ici !
}
Le préprocesseur ignore les blocs ({ }) : il ne connaît que le texte. Donc définir une macro dans une fonction est possible mais déconseillé car la macro sort du bloc et pollue le code après. Dans ce cas il faut utiliser la directive #undef qui annule la définition d’une macro :
#define TEMP 100
// utiliser TEMP
#undef TEMP
// TEMP n'existe plus après
Expressions
Une expression en C est une combinaison de variables, de constantes et d’opérateurs qui se réduit à une valeur unique. Le C supporte plusieurs catégories d’opérateurs permettant de construire des expressions complexes et puissantes.
Opérateurs arithmétiques
Les opérateurs arithmétiques permettent des calculs numériques. Les cinq opérateurs de base sont :
- Addition :
+ - Soustraction :
- - Multiplication :
* - Division :
/(division entière si les deux opérandes sont des entiers, division réelle sinon) - Modulo :
%(reste de la division entière, n’est pas alloué sur les float et double)
int a = 10, b = 3;
int somme = a + b; // 13
int diff = a - b; // 7
int prod = a * b; // 30
int div = a / b; // 3 (division entière)
int reste = a % b; // 1
L’ordre de priorité des opérateurs arithmétiques suit les règles mathématiques usuelles : la multiplication, la division et le modulo sont évalués avant l’addition et la soustraction. Les parenthèses permettent de forcer un ordre d’évaluation.
int resultat = 2 + 3 * 4; // 14 (3*4 d'abord, puis +2)
int resultat2 = (2 + 3) * 4; // 20 (2+3 d'abord, puis *4)
**Attention aux risque d’overflow et d’underflow : **
unsigned int x = UINT_MAX; // 4294967295
x = x + 1; // devient 0
x = x - 1; // devient UINT_MAX
Opérateurs de comparaison
Les opérateurs de comparaison produisent une valeur booléenne (0 pour faux, 1 pour vrai en C) :
- Égalité :
== - Inégalité :
!= - Inférieur :
< - Inférieur ou égal :
<= - Supérieur :
> - Supérieur ou égal :
>=
int a = 5, b = 3;
int est_egal = a == b; // 0 (faux)
int est_different = a != b; // 1 (vrai)
int est_plus_grand = a > b; // 1 (vrai)
Opérateurs logiques
Les opérateurs logiques permettent de combiner des conditions :
- ET logique :
&&(évalue le deuxième opérande seulement si le premier est vrai) - OU logique :
||(évalue le deuxième opérande seulement si le premier est faux) - NON logique :
!(négatif d’une expression booléenne)
int a = 5, b = 3, c = 8;
int cond1 = (a > b) && (b < c); // 1 (vrai ET vrai)
int cond2 = (a < b) || (b > c); // 0 (faux OU faux)
int cond3 = !(a == b); // 1 (NON(faux) = vrai)
Opérateurs bit à bit
Pour manipuler directement les bits :
- ET bit à bit :
& - OU bit à bit :
| - OU exclusif :
^ - Décalage à gauche :
<< - Décalage à droite :
>> - Complément bit à bit :
~
unsigned int a = 5; // 0101 en binaire
unsigned int b = 3; // 0011 en binaire
unsigned int et = a & b; // 0001 = 1
unsigned int ou = a | b; // 0111 = 7
unsigned int xor = a ^ b; // 0110 = 6
unsigned int dec_g = a << 1; // 1010 = 10
unsigned int dec_d = a >> 1; // 0010 = 2
Opérateur ternaire
L’opérateur ternaire ? : permet de choisir entre deux expressions selon une condition :
int a = 5, b = 3;
int max = (a > b) ? a : b; // max = 5
C’est une condition ! Le code fais la même chose avec if / else (modulo une petite opération fut un temps)
Tableaux statiques
Un tableau est une collection d’éléments du même type, stockés de manière contiguë en mémoire. Un tableau statique a sa taille fixée à la compilation.
int scores[10]; // tableau de 10 entiers (non initialisés)
int notes[5] = {18, 15, 20, 17, 19}; // initialisation au moment de la déclaration
int valeurs[3] = {0}; // tous les éléments à zéro
int data[] = {1, 2, 3, 4}; // taille déduite de l'initialisation : 4 (uniquement depuis C99)
En C, les indices commencent à zéro. L’accès à un élément se fait via l’opérateur [] :
notes[0] = 18;
printf("%d\n", notes[2]); // affiche 20
L’accès hors limites (par exemple notes[100]) n’est pas vérifié par le compilateur : le programme accède à une zone mémoire arbitraire, conduisant généralement à une erreur d’exécution ou à un comportement imprévisible. C’est un des pièges courants du C.
Tableaux multidimensionnels
Les tableaux peuvent avoir plusieurs dimensions :
int matrice[3][4]; // tableau 3x4 d'entiers
matrice[0][2] = 42;
En mémoire, un tableau multidimensionnel est stocké de façon linéaire, ligne par ligne (ordre row-major) :
int tableau[2][3] = {{1, 2, 3}, {4, 5, 6}};
// En mémoire : 1, 2, 3, 4, 5, 6
Tableaux de caractères (chaînes)
Une chaîne de caractères en C est un tableau de char terminé par un caractère nul \0 :
char nom[20] = "Alice"; // "Alice\0" en réalité (6 caractères dont le \0)
char msg[] = "Bonjour"; // taille déduite
De nombreuses fonctions de la bibliothèque standard (comme strlen, strcpy, printf) exploitent ce caractère nul pour détecter la fin de la chaîne.
Taille et allocation statique
Pour obtenir le nombre d’éléments d’un tableau statique, on peut utiliser :
int tab[] = {1, 2, 3, 4, 5};
int nb_elements = sizeof(tab) / sizeof(tab[0]); // 5
Cette technique ne fonctionne que pour les tableaux statiques. Pour les tableaux passés en paramètre à une fonction, sizeof retourne la taille d’un pointeur, non celle du tableau. DANS LA TRÈS GRANDE MAJORITÉ DES CAS VOUS VOUS PLANTÉ !

Instructions conditionnelles
Les instructions conditionnelles permettent d’exécuter du code différent selon que certaines conditions sont vraies ou fausses.
L’instruction if
L’instruction if exécute un bloc d’instructions si et seulement si une condition est vraie :
if (age >= 18) {
printf("Vous êtes adulte\n");
}
En C, une condition est considérée comme vraie si sa valeur est non zéro, et fausse si sa valeur est zéro. Pour les booliens, les compilateurs C99 et ultérieurs reconnaissent le type _Bool (ou bool si on inclut <stdbool.h>) cela reste des alias du type int. Et c’est une propriété intéréssante !
Assembler (x86, pseudo-code) :
mov eax, [age] ; charge la valeur de age dans eax
cmp eax, 18 ; compare eax à 18
jl fin_if ; si age < 18, saute la partie printf
; ici : code pour printf("Vous êtes adulte\\n")
fin_if:
Ici, cmp effectue la comparaison, et jl (“jump if less”) saute l’instruction si le résultat est inférieur à 18. Sinon, on exécute le bloc du if.
if-else
La clause else permet de spécifier du code à exécuter si la condition est fausse :
if (age >= 18) {
printf("Vous êtes adulte\n");
} else {
printf("Vous êtes mineur\n");
}
Assembler (x86, pseudo-code) :
mov eax, [age]
cmp eax, 18
jl else_block ; saute vers else_block si age < 18
; ici : code pour printf("Vous êtes adulte\\n")
jmp fin_ifelse ; saute vers la fin de la structure
else_block:
; ici : code pour printf("Vous êtes mineur\\n")
fin_ifelse:
On utilise un saut conditionnel suivi d’un saut inconditionnel pour gérer les deux branches.
else if (chaînage)
On peut enchaîner plusieurs conditions avec else if :
if (note >= 16) {
printf("Excellent\n");
} else if (note >= 12) {
printf("Bien\n");
} else if (note >= 10) {
printf("Passable\n");
} else {
printf("Insuffisant\n");
}
Assembler (x86, pseudo-code) :
mov eax, [note]
cmp eax, 16
jl test_bien ; si note < 16, passe à la suite
; printf("Excellent")
jmp fin_ifelse
test_bien:
cmp eax, 12
jl test_passable ; si note < 12, passe à la suite
; printf("Bien")
jmp fin_ifelse
test_passable:
cmp eax, 10
jl else_block
; printf("Passable")
jmp fin_ifelse
else_block:
; printf("Insuffisant")
fin_ifelse:
Chaque comparaison est suivie d’un saut conditionnel vers le test suivant ou, en cas de succès, vers la fin de la structure, comme en C.
Le switch
L’instruction switch permet de sélectionner une branche d’exécution parmi plusieurs cas discrets. Elle est souvent plus lisible que les chaînes if-else if pour comparer une variable à plusieurs valeurs constantes.
int jour = 2;
switch (jour) {
case 1:
printf("Lundi\n");
break;
case 2:
printf("Mardi\n");
break;
case 3:
printf("Mercredi\n");
break;
default:
printf("Jour invalide\n");
}
Pseudo-code goto + label :
switch_start:
if (jour == 1) goto case1;
if (jour == 2) goto case2;
if (jour == 3) goto case3;
goto default_case;
case1:
printf("Lundi\n");
goto switch_end; // correspond au break;
case2:
printf("Mardi\n");
goto switch_end; // correspond au break;
case3:
printf("Mercredi\n");
goto switch_end; // correspond au break;
default_case:
printf("Jour invalide\n");
switch_end:
Chaque condition teste la valeur, puis saute vers le bon bloc. Le break est simulé par le saut vers le label de fin.
Sans break, l’exécution continue dans le cas suivant (fall-through) :
int choix = 1;
switch (choix) {
case 1:
case 2:
printf("Options 1 ou 2 sélectionnées\n");
break;
case 3:
printf("Option 3 sélectionnée\n");
// pas de break ici
case 4:
printf("Option 3 ou 4\n"); // exécuté si choix == 3
break;
default:
printf("Choix invalide\n");
}
Pseudo-code goto + label avec fall-through :
switch_fallthrough_start:
if (choix == 1) goto case1_2;
if (choix == 2) goto case1_2;
if (choix == 3) goto case3;
if (choix == 4) goto case4;
goto default_ft;
case1_2:
printf("Options 1 ou 2 sélectionnées\n");
goto switch_fallthrough_end; // correspond au break;
case3:
printf("Option 3 sélectionnée\n");
// pas de break, on continue sur case4
case4:
printf("Option 3 ou 4\n");
goto switch_fallthrough_end; // correspond au break;
default_ft:
printf("Choix invalide\n");
switch_fallthrough_end:
Ici, l’absence de break provoque la continuation du flot sur le bloc case4.
Structures répétitives
Les structures répétitives (boucles) permettent d’exécuter un bloc d’instructions plusieurs fois.
La boucle for
La boucle for est particulièrement adaptée quand le nombre d’itérations est connu.
for (int i = 0; i < 10; i++) {
printf("%d\n", i);
}
Pseudo-code avec goto + label :
int i = 0;
for_start:
if (i >= 10) goto for_end;
printf("%d\n", i);
i++;
goto for_start;
for_end:
On revient sans cesse au début tant que la condition est vraie.
La boucle while
La boucle while exécute un bloc tant qu’une condition reste vraie :
int compteur = 0;
while (compteur < 5) {
printf("%d\n", compteur);
compteur++;
}
Pseudo-code goto + label :
int compteur = 0;
while_start:
if (compteur >= 5) goto while_end;
printf("%d\n", compteur);
compteur++;
goto while_start;
while_end:
Structure identique à la boucle for, l’incrément se fait dans le corps.
La boucle do-while
La boucle do-while garantit que le bloc est exécuté au moins une fois :
int choix;
do {
printf("Entrez un nombre entre 1 et 10 : ");
scanf("%d", &choix);
} while (choix < 1 || choix > 10);
Pseudo-code goto + label :
do_while_start:
printf("Entrez un nombre entre 1 et 10 : ");
scanf("%d", &choix);
if (choix < 1 || choix > 10) goto do_while_start;
On exécute le bloc une première fois avant de tester. D’un point de vue compilation on a simplement déplacé la condition.
Contrôle des boucles
L’instruction break :
for (int i = 0; i < 100; i++) {
if (i == 10) {
break; // sort de la boucle
}
printf("%d\n", i);
}
Pseudo-code goto + label :
int i = 0;
for_break_start:
if (i >= 100) goto for_break_end;
if (i == 10) goto for_break_end;
printf("%d\n", i);
i++;
goto for_break_start;
for_break_end:
Le break est représenté par un saut vers le label de sortie.
L’instruction continue :
for (int i = 0; i < 10; i++) {
if (i == 5) {
continue; // saute à i++ et à l’itération suivante
}
printf("%d\n", i); // n’affiche pas 5
}
Pseudo-code goto + label :
int i = 0;
for_continue_start:
if (i >= 10) goto for_continue_end;
if (i == 5) goto for_continue_next;
printf("%d\n", i);
for_continue_next:
i++;
goto for_continue_start;
for_continue_end:
Le continue saute la partie affichage pour l’itération courante.
Boucles imbriquées
Les boucles peuvent être imbriquées, chaque break ne sort que de la boucle la plus interne :
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
break; // sort uniquement de la boucle j
}
printf("(%d, %d) ", i, j);
}
printf("\n");
}
Le break n’affecte que la boucle interne, l’externe continue normalement.
Entrées-Sorties Simples
La bibliothèque standard C fournit plusieurs fonctions pour lire des données depuis l’entrée et afficher des résultats à la sortie.
printf : sortie formatée
La fonction printf affiche du texte formaté à la sortie standard (l’écran) :
printf("Bonjour, monde!\n");
printf("Un entier : %d\n", 42);
printf("Un réel : %f\n", 3.14);
Les formats spécifiés commencent par % :
%dou%i: entier (int)%u: entier non signé (unsigned int)%f: nombre flottant (float ou double)%e: notation scientifique%x: hexadécimal%c: caractère (char)%s: chaîne de caractères (char*)%p: adresse mémoire (pointeur)%%: caractère pourcent
On peut spécifier la largeur et la précision :
printf("%5d\n", 42); // " 42" (5 caractères)
printf("%05d\n", 42); // "00042" (remplissage avec zéros)
printf("%.2f\n", 3.14159); // "3.14" (2 décimales)
printf("%10.2f\n", 3.14); // " 3.14" (largeur 10, 2 décimales)
getchar et putchar
La fonction getchar lit un caractère depuis l’entrée standard :
int c = getchar(); // lit un caractère et retourne son code ASCII
if (c != EOF) {
printf("Caractère reçu : %c\n", c);
}
getchar retourne un int (le code ASCII du caractère ou EOF en fin de fichier).
La fonction putchar affiche un caractère :
putchar('A'); // affiche 'A'
putchar('\n'); // affiche une nouvelle ligne
scanf : entrée formatée
La fonction scanf lit des données depuis l’entrée standard selon un format :
int age;
printf("Quel âge avez-vous ? ");
scanf("%d", &age);
printf("Vous avez %d ans.\n", age);
Important : on doit passer l’adresse de la variable (avec &), pas la variable elle-même. C’est parce que scanf doit modifier la variable en place.
Formats courants :
%d: entier%f: flottant (float)%lf: flottant (double)%s: chaîne (attention : pas de protection contre le débordement de buffer)%c: caractère%p: addresse
int entier;
float reel;
char nom[50];
char c;
scanf("%d", &entier);
scanf("%f", &reel);
scanf("%s", nom); // pas de & pour les tableaux (ils se convertissent en pointeur)
scanf("%c", &c);
gets et puts (déconseillés)
La fonction gets lit une ligne complète depuis l’entrée, mais est dangereuse car elle ne vérifie pas la taille du buffer. Elle est dépréciée depuis C99 et supprimée en C11.
char ligne[100];
gets(ligne); // DANGEREUX : risque de débordement
Préférer fgets :
fgets(ligne, 100, stdin); // lit au maximum 99 caractères
La fonction puts affiche une chaîne suivie d’une nouvelle ligne :
puts("Bonjour"); // équivalent à printf("Bonjour\n");