Vous êtes sur la page 1sur 30

Introduction

Entre les années 1980 et 2000, aucun langage de programmation n’a pu se


vanter d’une croissance en popularité comparable à celle de C et de son frère
C++. Ce n’est que vers l’an 2000 où le langage C doit passer sa première place
au langage Java - l’un de ses petit-fils. Même en 2011, C occupe toujours la
seconde place dans le classement de la popularité, entouré de Java, C++, C #
et PHP qui sont tous en quelque sorte des dérivés de C. Le langage C trouve
ses sources en 1972 dans les ’Bell Laboratories’ : Pour développer une version
portable du système d’exploitation UNIX, Dennis M. Ritchie a conçu ce langage
de programmation structuré, mais très ’près’ de la machine.
En 1983 un groupe de développeurs de AT & T sous la direction de Bjarne
Stroustrup a créé le langage C++. Le but était de développer un langage qui
garderait les avantages de ANSI-C (portabilité, efficience) et qui permettrait en
plus la programmation orientée objet. Depuis 1990 il existe une ébauche pour
un standard ANSI-C++. Entre-temps AT& T a développé deux compilateurs
C++ qui respectent les nouvelles déterminations de ANSI et qui sont considérés
comme des quasi-standards (AT & T-C++ Version 2.1 [1990] et AT & T-C++
Version 3.0 [1992]).
La portabilité est l’un des avantages les plus importants de C : en écrivant
des programmes qui respectent le standard ANSI-C, nous pouvons les utiliser sur
n’importe quelle machine possédant un compilateur ANSI-C. D’autre part, le
répertoire des fonctions ANSI-C est assez limité. Si un programmeur désire faire
appel à une fonction spécifique de la machine (p.ex : utiliser une carte graphique
spéciale), il est assisté par une foule de fonctions ’préfabriquées’, mais il doit
être conscient qu’il risque de perdre la portabilité. Ainsi, il devient évident que
les avantages d’un programme portable doivent être payés par la restriction des
moyens de programmation.

1
2
Chapitre 1

Les bibliothèques et notions


de base

Pour le travail pratique en C, il faut utiliser un compilateur et un éditeur


facile à utiliser. A titre d’exemple je décris ici l’utilisation de l’environnement
Eclipse. Ce programme nous offre une surface de programmation confortable et
rapide. Tout autre compilateur qui permet la programmation selon le standard
ANSI-C fait aussi bien l’affaire.

La pratique en C exige l’utilisation de bibliothèques de fonctions. Ces biblio-


thèques sont disponibles dans leur forme précompilée (extension : .LIB). Pour
pouvoir les utiliser, il faut inclure des fichiers en-tête (header files - extension
.H) dans nos programmes. Ces fichiers contiennent des ’prototypes’ des fonctions
définies dans les bibliothèques et créent un lien entre les fonctions précompilées
et nos programmes.

L’instruction # include insère les fichiers en-tête indiqués comme argu-


ments dans le texte du programme au moment de la compilation.

Si vous écrivez un programme qui fait appel à des fonctions mathématiques


et des fonctions graphiques prédéfinies. Pour pouvoir utiliser ces fonctions, le
programme a besoin des bibliothèques, il inclure les fichiers en-tête correspon-
dants dans le code source de notre programme à l’aide des instruction :

# include<math.h>

#include <graphics.h>

3
4 CHAPITRE 1. LES BIBLIOTHÈQUES ET NOTIONS DE BASE

1.1 Notions de base


Suivons la tradition et commençons la découverte de C par l’inévitable pro-
gramme ’hello world’. Ce programme ne fait rien d’autre qu’imprimer les mots
suivants sur l’écran : Hello world.
# include < stdio .h >
# define N 10
/* Notre premier p r o g r a m m e en C */
int main ()
{
printf ( " Hello world \ n " ) ;
return 0;
}

Les programmes en C sont composés essentiellement de fonctions et de va-


riables. Pour la pratique, il est donc indispensable de se familiariser avec les
caractéristiques fondamentales de ces éléments.

1.1.1 Fonction main()


La fonction main est la fonction principale des programmes en C : Elle se
trouve obligatoirement dans tous les programmes. L’exécution d’un programme
entraîne automatiquement l’appel de la fonction main.
int main ()
{
// d e c l a r a t i o n s
// i n s t r u c t i o n s
return 0;
}

Pramètres de la fonction main() :


— Si la liste des paramètres de la fonction main est vide, il est d’usage de
la déclarer par ().
— Si nous utilisons des fonctions prédéfinies (par exemple : printf), il faut
faire précéder la définition de main par les instructions # include cor-
respondantes.

1.1.2 Les variables


Les variables contiennent les valeurs qui sont utilisées pendant l’exécution
du programme. Les noms des variables sont des identificateurs quelconques.
1.1. NOTIONS DE BASE 5

Les noms des fonctions et des variables en C sont composés d’une suite de
lettres et de chiffres. Le premier caractère doit être une lettre. Le symbole ’_’
est aussi considéré comme une lettre.

L’ensemble des symboles utilisables est donc :

— 0,1,2,...,9,A,B,...,Z,_ ,a,b,...,z
— Le premier caractère doit être une lettre (ou le symbole ’_’)
— C distingue les majuscules et les minuscules.
— Nom_de_variable’ est différent de ’nom_de_variable’

Remarques :

— Il est déconseillé d’utiliser le symbole ’_’ comme premier caractere pour


un identificateur, car il est souvent employé pour définir les variables
globales de l’environment C.
— Le standard dit que la validité de noms externes (p.ex. noms de fonctions
ou var. globales) peut être limité à 6 charactères (même sans tenir compte
des majuscules et minuscules) par l’implémentation du compilateur, mais
tous les compilateurs modernes distinguent au moins 31 charactères de
façon à ce que nous pouvons généraliser qu’en pratique les règles ci-dessus
s’appliquent à tous les identificateurs.

Nom correcte Non incorrecte


nom1 1nom
nom_2 nom.2
_nom_3 -nom-3
Nom_de_variable Nom de variable
deuxieme_choix deuxième_choix
mot_francais mot_français

1.1.2.1 Les commentaires

Les commentaires en langages C se distigues en 2 types :

— Un commentaire sur une ligne commence par //


— Un commentaire sur plusieurs lignes commence par /* et ce termine */
6 CHAPITRE 1. LES BIBLIOTHÈQUES ET NOTIONS DE BASE

1.1.2.2 Les séquences d’échappement

Séquence d’échapp Description


\n passage à la ligne
\t tabulation horizontale
\b Retour arrière
\r retour à la ligne
\" Les guillemets
\\ trait oblique (back-slash)
\0 fin de chaîne
\a sonnerie

1.1.2.3 Les priorités des opérateurs

Ordres de priorités Opérateurs


priorité 1 (la plus forte) ()
priorité 2 ! ++ –
priorité 3 */%
priorité 4 +-
priorité 5 < <= > >=
priorité 6 == !=
priorité 7 &&
priorité 8 ||
priorité 9 (la plus faible) = += -= *= /= %=

1.2 Types de variables


Les variables et les constantes sont les données principales qui peuvent être
manipulées par un programme. Les déclarations introduisent les variables qui
sont utilisées, fixent leur type et parfois aussi leur valeur de départ. Les opéra-
teurs contrôlent les actions que subissent les valeurs des données. Pour produire
de nouvelles valeurs, les variables et les constantes peuvent être combinées à
l’aide des opérateurs dans des expressions. Le type d’une donnée détermine
l’ensemble des valeurs admissibles, le nombre d’octets à réserver en mémoire et
l’ensemble des opérateurs qui peuvent y être appliqués.
1.2. TYPES DE VARIABLES 7

La grande flexibilité de C nous permet d’utiliser des opérandes de différents


types dans un même calcul. Cet avantage peut se transformer dans un terrible
piège si nous ne prévoyons pas correctement les effets secondaires d’une telle
opération (conversions de type automatiques, arrondissements, etc.). Une étude
minutieuse de ce chapitre peut donc aider à éviter des phénomènes parfois ’in-
explicables’ ...

1.2.1 Les types entiers

Avant de pouvoir utiliser une variable, nous devons nous intéresser à deux
caractéristiques de son type numérique :
— le domaine des valeurs admissibles
— le nombre d’octets qui est réservé pour une variable
Le tableau suivant résume les caractéristiques des types numériques entiers de
C:

Définition Description Nombre min Nombre max Nombre d’octets


char caractère -128 127 1
short entier court -32768 32767 2
int entier standard -32768 -32767 2
long entier long -2147483648 2147483647 4
unsigned char caractère 0 255 1
unsigned short entier court 0 65535 2
unsigned int entier standard 0 65535 2
unsigned long entier long 0 4294967295 4

1.2.2 Les types rationels

En informatique, les rationnels sont souvent appelés des flottants. Ce terme


vient de en virgule flottante et trouve sa racine dans la notation traditionnelle
des rationnels :
En C, nous avons le choix entre trois types de rationnels : float, double et
long double. Dans le tableau ci-dessous, vous trouverez leurs caractéristiques :
8 CHAPITRE 1. LES BIBLIOTHÈQUES ET NOTIONS DE BASE

Précision Nombre
Définition mantisse Nombre min Nombre max d’octets
float simple 6 3.4 × 10−38 3.4 × 1038 4
double double 15 1.7 × 10−308 1.7 × 10308 8
double long suppl 19 3.4 × 10−4932 1.1 × 104932 10

1.2.2.1 Déclaration des variables

Maintenant que nous connaissons les principaux types de variables, il nous


faut encore la syntaxe pour leur déclaration :

int main ()
{
// d e c l a r a t i o n s
int a ;
int b , c , d ; // d e c l a r a t i o n of servel v a r i a b l e s with same type
float x ;
double y ;
// i n s t r u c t i o n s
return 0;
}

En général. nous avons le choix entre plusieurs types et nous devons trouver celui
qui correspond le mieux au domaine et aux valeurs à traiter. Voici quelques règles
générales qui concernent la traduction des déclarations de variables numériques
du langage algorithmique en C :
— La syntaxe des déclarations en C ressemble à celle du langage algorith-
mique. Remarquez quand même les points-virgules à la fin des déclara-
tions en C.
— entier : Nous avons le choix entre tous les types entiers (inclusivement
char) dans leurs formes signed ou unsigned. Si les nombres deviennent
trop grands pour unsigned long, il faut utiliser un type rationnel (p.ex :
double)
— réel : Nous pouvons choisir entre les trois types rationnels en observant
non seulement la grandeur maximale de l’exposant, mais plus encore le
nombre de chiffres significatifs de la mantisse.
— caractère : Toute variable du type char peut contenir un (seul) carac-
tère. En C, il faut toujours être conscient que ce caractère n’est autre
chose qu’un nombre correspondant à un code (ici : code ASCII). Ce
1.2. TYPES DE VARIABLES 9

nombre peut être intégré dans toute sorte d’opérations algébriques ou


logiques ...
— chaîne : En C il n’existe pas de type spécial pour chaînes de caractères.
Les moyens de traiter les chaînes de caractères seront décrits au chapitre.
— booléen : En C il n’existe pas de type spécial pour variables booléennes.
Tous les types de variables numériques peuvent être utilisés pour expri-
mer des opérations logiques.
En C il est possible d’initiatilier les variables l’or de la déclaration comme
suit :

int main ()
{
// d e c l a r a t i o n s
int a = 10;
float b = 3.4;
// i n s t r u c t i o n s
return 0;
}

En utilisant l’attribut const, nous pouvons indiquer que la valeur d’une variable
ne change pas au cours d’un programme :

int main ()
{
// d e c l a r a t i o n s
const int MAX = 767;

// i n s t r u c t i o n s
return 0;
}

1.2.3 Les opérateurs

En C, l’affectation est un opérateur à part entière. Elle est symbolisée par


le signe =. Sa syntaxe est la suivante :

variable = expression

Le terme gauche de l’affectation peut être une variable simple, un élément de ta-
bleau mais pas une constante. Cette expression a pour effet d’évaluer expression
et d’affecter la valeur obtenue à variable.
10 CHAPITRE 1. LES BIBLIOTHÈQUES ET NOTIONS DE BASE

L’affectation effectue une conversion de type implicite : la valeur de l’expres-


sion (terme de droite) est convertie dans le type du terme de gauche.

1.2.3.1 Les opérateurs arithmétiques

Les opérateurs classiques sont l’opérateur unaire ainsi que les opérateurs
binaires :
+ addition
− soustraction
∗ multiplication
/ division
% reste de la divison (modulo)
Ces opérateurs agissent de la façon attendue sur les entiers comme sur les
flottants. Notons qu’il n’y a pas en C d’opérateur effectuant l’élévation à la
puissance. De façon générale, il faut utiliser la fonction pow(x, y) de la librairie
math.h pour calculer xy .

1.2.3.2 Les opérateurs relationnels

> strictement supérieur


>= supérieur ou égal
< strictement inférieur
<= inférieur ou égal
== égal
! = différent
Attention à ne pas confondre l’opérateur de test d’égalité == avec l’opérateur
d’affectation =.

1.2.3.3 Les opérateurs logiques booléens

&& et logique
|| ou logique
! négation logique
Comme pour les opérateur de comparaison, la valeur retournée par ces opéra-
teurs est un int qui vaut i si la condition est vraie et 0 sinon.

1.2.3.4 Les opérateurs d’affectation composée

Les opérateurs d’affectation composée sont :


1.2. TYPES DE VARIABLES 11

— a+ = b
— −=
— ∗=
— /=
— %=
— &=
=

— |=
— <<=
— >>=
Pour tout opérateur op, l’expression :

expression1 op = expression2

et equivalente à :

expression1 = expression1 op expression2

Toutefois, avec l’affectation composée, expression1 n’est évaluée qu’une seule


fois.

1.2.3.5 Les opérateurs d’incrémentation et de décrémentation

Les opérateurs d’incrémentation ++ et de décrémentation −− s’utilisent


aussi bien en suffixe (i + +) qu’en préfixe (+ + i).Dans les deux cas la variable
i sera incrémentée toutefois dans la notation suffixe la valeur retournée sera
l’ancienne valeur de i alors dans la notation préfixe se sera la nouvelle. Par
exemple :

int main ()
{
// d e c l a r a t i o n s
int a = 3 , b , c ;
b = ++ a ; /* a et b valent 4 */
c = b ++; /* c vaut 4 et b vaut 5 */

return 0;
}
12 CHAPITRE 1. LES BIBLIOTHÈQUES ET NOTIONS DE BASE

1.2.3.6 L’opération virgule

Une expression peut être constituée d’une suite d’expression séparées par les
virgules : expression1, expression2,. . .
Cette expression est alors évaluée de gauche à droite. Sa valeur sera la valeur
de l’expression de droite. Par exemple, le programme
main ()
{
int a , b ;
b = (( a = 3) , ( a + 2) ) ;
printf ( " \ n b = % d \ n " ,b ) ;
}

imprime b = 5

1.2.3.7 L’opérateur conditionnel ternaire

L’opérateur conditionnel ? est un opérateur ternaire. Sa syntaxe est la sui-


vante :
condition ? expression − 1 : expression − 2
Cette expression est égale à expression − 1 si condition est satisfaite, et à
expression − 2 sinon. Par exemple, l’expression
main ()
{
x >= 0 ? x : -x ;
}

correspond à la valeur absolue d’un nombre. De même l’instruction


main ()
{
m = (( a > b ) ? a : b ) ;
}

affecte à m le maximum de a et de b.

1.2.3.8 L’opérateur de conversion de type

L’opérateur de conversion de type, appelé cast, permet de modifier explici-


tement le type d’un objet. On écrit (type)objet.
1.3. LES INSTRUCTIONS DE BRANCHEMENT CONDITIONNEL 13

main ()
{
int i = 3 , j = 2;
printf ( " % f \ n " ,( float ) i / j ) ;
}

retourne la valeur 1.5.

1.3 Les instructions de branchement condition-


nel
On appelle instruction de contrôle toute instruction qui permet de contrô-
ler le fonctionnement d’un programme. Parmi les instructions de contrôle, on
distingue les instructions de branchement et les boucles. Les instructions de
branchement permettent de déterminer quelles instructions seront exécutées et
dans quel ordre.

1.3.1 Branchement conditionnel if—else


La forme la plus générale est celle-ci :
main ()
{
if ( expression -1)
instruction -1

else if ( expression -2)


instruction -2
...

else if ( expression - n )
instruction - n

else
instruction
}

1.3.2 Branchement multiple switch


Sa forme la plus générale est celle-ci :
switch ( expression )
14 CHAPITRE 1. LES BIBLIOTHÈQUES ET NOTIONS DE BASE

{
case constante -1 :
liste d ’ instructions 1
break ;

case constante -2:


liste d ’ instructions 2
break ;
...
case constante - n :
liste d ’ instructions n
break ;

default :
liste d ’ instructions
break ;
}

Si la valeur de expression est égale à l’une des constantes, la liste d’instructions


correspondant est exécutée. Sinon la liste d’instructions correspondant à default
est exécutée. L’instruction default est facultative.

1.4 Les Boucles


Les boucles permettent de répéter une série d’instructions tant qu’une cer-
taine condition n’est pas vérifiée.

1.4.1 Boucle while


La syntaxe de while est la suivante :
while ( expression )
instruction

Tant que expression est vérifiée (i.e.,non nulle),instruction est exécutée. Si ex-
pression est nulle au départ, instruction ne sera jamais exécutée. instruction
peut évidemment être une instruction composée. Par exemple, le programme
suivant imprime les entiers de 1 à 9.
i = 1;
while ( i < 10)
{
printf ( " \ n i = % d " ,i ) ;
i ++;
1.4. LES BOUCLES 15

1.4.2 Boucle do—while


Il peut arriver que l’on ne veuille effectuer le test de continuation qu’après
avoir exécuté l’instruction. Dans ce cas, on utilise la boucle do − − − while . Sa
syntaxe est
do
instruction
while ( expression ) ;

Ici, instruction sera exécutée tant que expression est non nulle. Cela signifie
donc que instruction est toujours exécutée au moins une fois. Par exemple, pour
saisir au clavier un entier entre 1 et 10 :
int a ;
do
{
printf ( " \ n Entrez un entier entre 1 et 10 : " ) ;
scanf ( " % d " ,& a ) ;
}
while (( a <= 0) || ( a > 10) ) ;

1.4.3 Boucle for


La syntaxe de :
for ( expr 1; expr 2; expr 3)
instruction ;

Par exemple, pour imprimer tous les entiers de 0 à 9, on ecrit :


for ( i = 0; i < 10; i ++)
printf ( " \ n i = % d " ,i ) ;

A la fin de cette boucle, i vaudra 10. Les trois expressions utilisées dans une
boucle for peuvent être constituées de plusieurs expressions séparées par des
virgules. Cela permet par exemple de faire plusieurs initialisations à la fois. Par
exemple, pour calculer la factorielle d’un entier, on peut écrire :
int n , i , fact ;
for ( i = 1 , fact = 1; i <= n ; i ++)
fact *= i ;
printf ( " % d ! = % d \ n " ,n , fact ) ;
16 CHAPITRE 1. LES BIBLIOTHÈQUES ET NOTIONS DE BASE

1.5 Les instructions de branchement non condi-


tionnel

1.5.1 Branchement non conditionnel break

On a vu le rôle de l’instruction break ; au sein d’une instruction de bran-


chement multiple switch. L’instruction break peut, plus généralement, être em-
ployée à l’intérieur de n’importe quelle boucle. Elle permet d’interrompre le
déroulement de la boucle, et passe à la première instruction qui suit la boucle.
En cas de boucles imbriquées, break fait sortir de la boucle la plus interne. Par
exemple, le programme suivant :

main ()
{
int i ;
for ( i = 0; i < 5; i ++)
{
printf ( " i = % d \ n " ,i ) ;
if ( i == 3)
break ;
}
printf ( " valeur de i a la sortie de la boucle = % d \ n " ,i ) ;
}

imprime à l’écran :
i=0
i=1
i=2
i=3
valeur de i a la sortie de la boucle = 3

1.5.2 Branchement non conditionnel continue

L’instruction continue permet de passer directement au tour de boucle sui-


vant, sans exécuter les autres instructions de la boucle. Ainsi le programme

main ()
{
int i ;
for ( i = 0; i < 5; i ++)
{
printf ( " i = % d \ n " ,i ) ;
1.5. LES INSTRUCTIONS DE BRANCHEMENT NON CONDITIONNEL17

if ( i == 3)
break ;
}
printf ( " valeur de i a la sortie de la boucle = % d \ n " ,i ) ;
}

imprime
i=0
i=1
i=2
i=4
valeur de i a la sortie de la boucle = 5

1.5.3 La fonction d’écriture printf


Il s’agit des fonctions de la librairie standard stdio.h utilisées avec les unités
classiques d’entrées-sorties, qui sont respectivement le clavier et l’écran. Sur cer-
tains compilateurs, l’appel à la librairie stdio.h par la directive au préprocesseur

Format Conversion Ecriture


%d int décimale signée
%ld long int décimale signée
%f double décimale virgule fixe
%lf long double décimale virgule fixe
%c char caractère
%s char* chaîne de caractères

1.5.4 La fonction de saisie scanf


La fonction scanf permet de saisir des données au clavier et de les stocker
aux adresses spécifiées par les arguments de la fonctions. Par exemple :
# include < stdio .h >
main ()
{
int i ;
printf ( " entrez un entier sous forme hexadecimale i = " ) ;
scanf ( " % x " ,& i ) ;
printf ( " i = % d \ n " ,i ) ;
}

Si on entre au clavier la valeur 1a, le programme affiche i = 26.


18 CHAPITRE 1. LES BIBLIOTHÈQUES ET NOTIONS DE BASE

1.5.5 Lecture et imprission d’un caractère


Les fonctions getchar et putchar permettent respectivement de lire et d’im-
primer des caractères. Il s’agit de fonctions d’entrées-sorties non formatées.
La fonction getchar retourne un int correspondant au caractère lu. Pour
mettre le caractère lu dans une variable caractère, on écrit caractere = getchar();
Chapitre 2

Les types composés

A partir des types prédéfinis du C (caractères, entiers, flottants), on peut


créer de nouveaux types, appelés types composés, qui permettent de représenter
des ensembles de données organisées.

2.1 Les tableaux

2.1.1 Tableau à une dimension

Un tableau est un ensemble fini d’éléments de même type, stockés en mémoire


à des adresses contiguës.
La déclaration d’un tableau à une dimension se fait de la façon suivante :

type nom − du − tableau[nombre − éléments];

où nombre éléments est une expression constante entière positive. Par exemple,
la déclaration int tab[10] ; indique que tab est un tableau de 10 éléments de
type int. Cette déclaration alloue donc en mémoire pour l’objet tab un espace
de 104 octets consécutifs.
Pour plus de clarté, il est recommandé de donner un nom à la constante
nombre éléments par une directive au préprocesseur, par exemple :
#def ine nombreéléments 10
On accède à un élément du tableau en lui appliquant l’opérateur []. Les
éléments d’un tableau sont toujours numérotés de 0 à nombre − éléments − 1.
Le programme suivant imprime les éléments du tableau tab :

19
20 CHAPITRE 2. LES TYPES COMPOSÉS

# define N 10
main ()
{
int tab [ N ];
int i ;
...
for ( i = 0; i < N ; i ++)
printf ( " tab [% d ] = % d \ n " ,i , tab [ i ]) ;
}

Un tableau correspond en fait à un pointeur vers le premier élément du tableau.


Ce pointeur est constant. Cela implique en particulier qu’aucune opération glo-
bale n’est autorisée sur un tableau. Notamment, un tableau ne peut pas figurer
à gauche d’un opérateur d’affectation.
Par exemple, on ne peut pas écrire tab1 = tab2;. Il faut effectuer l’affectation
pour chacun des éléments du tableau :
# define N 10
main ()
{
int tab1 [ N ] , tab2 [ N ];
int i ;
...
for ( i = 0; i < N ; i ++)
tab1 [ i ] = tab2 [ i ];
}

On peut initialiser un tableau lors de sa déclaration par une liste de constantes


de la façon suivante :
# define N 4
int tab [ N ] = {1 , 2 , 3 , 4};
main ()
{
int i ;
for ( i = 0; i < N ; i ++)
printf ( " tab [% d ] = % d \ n " ,i , tab [ i ]) ;
}

Si le nombre de données dans la liste d’initialisation est inférieur à la dimension


du tableau, seuls les premiers éléments seront initialisés. Les autres éléments se-
ront mis à zéro si le tableau est une variable globale (extérieure à toute fonction)
ou une variable locale de classe de mémorisation static.
De la même manière un tableau de caractères peut être initialisé par une
liste de caractères, mais aussi par une chaîne de caractères littérale. Notons que
2.1. LES TABLEAUX 21

0
le compilateur complète toute chaîne de caractères avec un caractère nul
00 . Il faut donc que le tableau ait au moins un élément de plus que le nombre
de caractères de la chaîne littérale.
# define N 8
char tab [ N ] = " exemple " ;
main ()
{
int i ;
for ( i = 0; i < N ; i ++)
printf ( " tab [% d ] = % c \ n " ,i , tab [ i ]) ;
}

Lors d’une initialisation, il est également possible de ne pas spécifier le nombre


d’éléments du tableau. Par défaut, il correspondra au nombre de constantes
de la liste d’initialisation. Ainsi le programme suivant imprime le nombre de
caractères du tableau tab.
char tab [] = " exemple " ;
main ()
{
int i ;
printf ( " Nombre de caracteres du tableau = % d \ n " , sizeof ( tab ) /
sizeof ( char ) ) ;
}

2.1.2 Tableau de deux dimension


De manière similaire, on peut déclarer un tableau à plusieurs dimensions.
Par exemple, pour un tableau à deux dimensions.
En fait, un tableau à deux dimensions est un tableau unidimensionnel dont
chaque élément est lui-même un tableau. On accède à un élément du tableau
par l’expression tableau[i][j].
Pour initialiser un tableau à plusieurs dimensions à la compilation, on utilise
une liste dont chaque élément est une liste de constantes :
# define M 2
# define N 3
int tab [ M ][ N ] = {{1 , 2 , 3} , {4 , 5 , 6}};
main ()
{
int i , j ;
for ( i = 0 ; i < M ; i ++)
22 CHAPITRE 2. LES TYPES COMPOSÉS

{
for ( j = 0; j < N ; j ++)
printf ( " tab [% d ][% d ]=% d \ n " ,i ,j , tab [ i ][ j ]) ;
}
}

2.1.3 Les structures


Une structure est une suite finie d’objets de types différents. Contrairement
aux tableaux, les différents éléments d’une structure n’occupent pas nécessaire-
ment des zones contiguës en mémoire. Chaque élément de la structure, appelé
membre ou champ, est désigné par un identificateur. On distingue la déclaration
d’un modèle de structure de celle d’un objet de type structure correspondant à
un modèle donné. La déclaration d’un modèle de structure dont l’identificateur
est modele suit la syntaxe suivante :
struct modele
{
type -1 membre -1;
type -2 membre -2;
...
type - n membre - n ;
};

On accède aux différents membres d’une structure grâce à l’opérateur membre


de structure, noté “.”. Le i − ème membre de objet est désigné par l’expression
On peut effectuer sur le i − ème membre de la structure toutes les opérations
valides sur des données de type type-i. Par exemple, le programme suivant définit
la structure complexe,composée de deux champs de type double ; il calcule la
norme d’un nombre complexe.
# include < stdio .h >
# include < stdlib .h >
# include < math .h >

struct complexe {
double reelle ;
double imaginaire ;
};
int main ( void ) {
struct complexe z ;
double norme ;
printf ( " entrer la partie r e l de nombre " ) ;
2.1. LES TABLEAUX 23

scanf ( " % lf " ,& z . reelle ) ;


printf ( " entrer la partie imaginaire de nombre " ) ;
scanf ( " % lf " ,& z . imaginaire ) ;

norme = sqrt ( z . reelle * z . reelle + z . imaginaire * z . imaginaire ) ;


printf ( " norme de (% f + i % f ) = % f \ n " ,z . reelle , z . imaginaire ,
norme ) ;

return 0;
}

2.1.4 Champss de bits


Il est possible en C de spécifier la longueur des champs d’une structure au bit
près si ce champ est de type entier (int ou unsigned int). Cela se fait en précisant
le nombre de bits du champ avant le ; qui suit sa déclaration. Par exemple, la
structure suivante
struct registre
{
u n s i g n e d int actif : 1;
u n s i g n e d int valeur : 31;
};

possède deux membres, actif qui est codé sur un seul bit, et valeur qui est
codé sur 31 bits.Tout objet de type struct registre est donc codé sur 32 bits.
Toutefois, l’ordre dans lequel les champs sont placés à l’intérieur de ce mot de 32
bits dépend de l’implémentation. Le champ actif de la structure ne peut prendre
que les valeurs 0 et 1. Aussi, si r est un objet de type struct registre, l’opération
r.actif += 2 ; ne modifie pas la valeur du champ. La taille d’un champ de bits
doit être inférieure au nombre de bits d’un entier. Notons enfin qu’un champ de
bits n’a pas d’adresse ; on ne peut donc pas lui appliquer l’opérateur &.

2.1.4.1 Utilité des typedef

Il est parfois nécessaire de manipuler des variables qui ne peuvent prendre


comme valeurs qu’un sous-ensemble des valeurs d’un type de base. Supposons
que nous voulions manipuler des booléens. Comme le type booléen n’existe pas
dans le langage, il faudra utiliser des int, en se restreignant à deux valeurs, par
exemple 0 et 1. Il est alors intéressant de redéfinir à l’aide d’un typedef, le type
int. On écrira par exemple :
24 CHAPITRE 2. LES TYPES COMPOSÉS

# define VRAI 1
# define FAUX 0
typedef int BOOLEAN ;

On pourra par la suite déclarer des booléens de la manière suivante :


BOOLEAN b1 , b2 ;
b1 = VRAI ;
if ( b2 == FAUX )

mais bien entendu, ce sera à la charge du programmeur d’assurer que les va-
riables b1 et b2 ne prennent comme valeurs que VRAI ou FAUX.
Chapitre 3

Fonctions

Comme dans la plupart des langages, on peut en C découper un programme


en plusieurs fonctions. Une seule de ces fonctions existe obligatoirement ; c’est
la fonction principale appelée main. Cette fonction principale peut, éventuelle-
ment, appeler une ou plusieurs fonctions secondaires. De même, chaque fonction
secondaire peut appeler d’autres fonctions secondaires ou s’appeler elle-même
(dans ce dernier cas, on dit que la fonction est récursive).

3.1 Définition d’une fonction


La définition d’une fonction est la donnée du texte de son algorithme, qu’on
appelle corps de la fonction. Elle est de la forme :
int produit ( int a , int b )
{
return ( a * b ) ;
}

La première ligne de cette définition est l’en-tête de la fonction. Dans cet en-
tête, type désigne le type de la fonction, c’est-à-dire le type de la valeur qu’elle
retourne. Contrairement à d’autres langages, il n’y a pas en C de notion de
procédure ou de sous-programme. Une fonction qui ne renvoie pas de valeur est
une fonction dont le type est spécifié par le mot-clef void. Les arguments de la
fonction sont appelés paramètres formels, par opposition aux paramètres effec-
tifs qui sont les paramètres avec lesquels la fonction est effectivement appelée.
Les paramètres formels peuvent être de n’importe quel type. Leurs identifica-
teurs n’ont d’importance qu’à l’intérieur de la fonction. Enfin, si la fonction ne

25
26 CHAPITRE 3. FONCTIONS

possède pas de paramètres, on remplace la liste de paramètres formels par le


mot-clef void.
Le corps de la fonction débute éventuellement par des déclarations de va-
riables, qui sont locales à cette fonction. Il se termine par l’instruction de retour
à la fonction appelante, return, dont la syntaxe est :

return ( expression ) ;

La valeur de expression est la valeur que retourne la fonction. Son type doit être
le même que celui qui a été spécifié dans l’en-tête de la fonction. Si la fonction
ne retourne pas de valeur (fonction de type void), sa définition s’achève par :

return ;

Plusieurs instructions return peuvent apparaître dans une fonction. Le retour


au programme appelant sera alors provoqué par le premier return rencontré lors
de l’exécution. Voici quelques exemples de définitions de fonctions :

return ; int produit ( int a , int b )


{
return ( a * b ) ;
}
int puissance ( int a , int n )
{
if ( n == 0)
return (1) ;
return ( a * puissance (a , n -1) ) ;
}
void imprime_tab ( int * tab , int nb_elements )
{
int i ;
for ( i = 0; i < nb_elements ; i ++)
printf ( " % d \ t " , tab [ i ]) ;
printf ( " \ n " ) ;
return ;
}

3.2 Appel d’une fonction


L’appel d’une fonction se fait par l’expression :

nom − f onction(para − 1, para − 2, ..., para − n)


3.3. DÉCLARATION D’UNE FONCTION 27

L’ordre et le type des paramètres effectifs de la fonction doivent concorder avec


ceux donnés dans l’en-tête de la fonction. Les paramètres effectifs peuvent être
des expressions. La virgule qui sépare deux paramètres effectifs est un simple
signe de ponctuation ; il ne s’agit pas de l’opérateur virgule. Cela implique en
particulier que l’ordre d’évaluation des paramètres effectifs n’est pas assuré et
dépend du compilateur. Il est donc déconseillé, pour une fonction à plusieurs pa-
ramètres, de faire figurer des opérateurs d’incrémentation ou de décrémentation
(++ ou –).

3.3 Déclaration d’une fonction

Le C n’autorise pas les fonctions imbriquées. La définition d’une fonction


secondaire doit donc être placée soit avant, soit après la fonction principale
main. Toutefois, il est indispensable que le compilateur “connaisse” la fonction
au moment où celle-ci est appelée. Si une fonction est définie après son premier
appel (en particulier si sa définition est placée après la fonction main), elle doit
impérativement être déclarée au préalable. Une fonction secondaire est déclarée
par son prototype, qui donne le type de la fonction et celui de ses paramètres,
sous la forme :

Les fonctions secondaires peuvent être déclarées indifféremment avant ou au


début de la fonction main. Par exemple, on écrira

int puissance ( int , int ) ;

int puissance ( int a , int n )


{
if ( n == 0)
return (1) ;
return ( a * puissance (a , n -1) ) ;
}
main ()
{
int a = 2 , b = 5;
printf ( " % d \ n " , puissance (a , b ) ) ;
}
}
28 CHAPITRE 3. FONCTIONS

3.4 Durée de vie des variables

Les variables manipulées dans un programme C ne sont pas toutes traitées


de la même manière. En particulier, elles n’ont pas toutes la même durée de vie.
On distingue deux catégories de variables.
— Les variables permanentes (ou statiques) : Une variable perma-
nente occupe un emplacement en mémoire qui reste le même durant
toute l’exécution du programme. Cet emplacement est alloué une fois
pour toutes lors de la compilation. La partie de la mémoire contenant
les variables permanentes est appelée segment de données. Par défaut,
les variables permanentes sont initialisées à zéro par le compilateur. Elles
sont caractérisées par le mot-clef static.
— Les variables temporaires : Les variables temporaires se voient allouer
un emplacement en mémoire de façon dynamique lors de l’exécution du
programme. Elles ne sont pas initialisées par défaut. Leur emplacement
en mémoire est libéré par exemple à la fin de l’exécution d’une fonction
secondaire. Par défaut, les variables temporaires sont situées dans la par-
tie de la mémoire appelée segment de pile. Dans ce cas, la variable
est dite automatique. Le spécificateur de type correspondant, auto, est
rarement utilisé puisqu’il ne s’applique qu’aux variables temporaires qui
sont automatiques par défaut.
— Variable globale : On appelle variable globale une variable déclarée en
dehors de toute fonction. Une variable globale est connue du compilateur
dans toute la portion de code qui suit sa déclaration. Les variables glo-
bales sont systématiquement permanentes. Dans le programme suivant,
n’est une variable globale :

int n ;
void fonction () ;
void fonction ()
{
n ++;
printf ( " appel numero % d \ n " ,n ) ;
return ;
}
main ()
{
int i ;
for ( i = 0; i < 5; i ++)
3.4. DURÉE DE VIE DES VARIABLES 29

fonction () ;
}
}

3.4.1 Les variables globales


On appelle variable globale une variable déclarée en dehors de toute fonction.
Une variable globale est connue du compilateur dans toute la portion de code qui
suit sa déclaration. Les variables globales sont systématiquement permanentes.
Dans le programme suivant, n est une variable globale :
int n ;
void fonction () ;
void fonction ()
{
n ++;
printf ( " appel numero % d \ n " ,n ) ;
return ;
}
main () {
int i ;
for ( i = 0; i < 5; i ++)
fonction () ;
}

La variable n est initialisée a zéro par le compilateur et il s’agit d’une variable


permanente.

3.4.2 Les variables locales


On appelle variable locale une variable déclarée a l’intérieur d’une fonction
(ou d’un bloc d’instructions) du programme. Par défaut, les variables locales
sont temporaires. Quand une fonction est appelée, elle place ses variables locales
dans la pile. A la sortie de la fonction, les variables locales sont dépilées et donc
perdues.
Les variables locales n’ont en particulier aucun lien avec des variables glo-
bales de meme nom. Par exemple, le programme suivant :
int n = 10;
void fonction () ;
void fonction ()
{
int n = 0;
30 CHAPITRE 3. FONCTIONS

n ++;
printf ( " appel numero % d \ n " ,n ) ;
return ;
}
main () {
int i ;
for ( i = 0; i < 5; i ++)
fonction () ;
}

Les variables locales a une fonction ont une durée de vie limitée a une seule
exécution de cette fonction. Leurs valeurs ne sont pas conservées d’un appel au
suivant.
Il est toutefois possible de créer une variable locale de classe statique en
faisant précéder sa déclaration du mot-clef static :
static type nom - de - variable ;

Une telle variable reste locale a la fonction dans laquelle elle est déclarée, mais sa
valeur est conservée d’un appel au suivant. Elle est également initialisée a zéro
a la compilation. Par exemple, dans le programme suivant, n est une variable
locale a la fonction secondaire fonction, mais de classe statique.
int n = 10;
void fonction () ;
void fonction ()
{
static int n ;
n ++;
printf ( " appel numero % d \ n " ,n ) ;
return ;
}
main () {
int i ;
for ( i = 0; i < 5; i ++)
fonction () ;
}