Académique Documents
Professionnel Documents
Culture Documents
Sofia DOUDA
Année 2010/2011
Année 2010/2011 1
Introduction au langage C
Petite histoire du C
Le langage C a été mis au point par D. Ritchie et B. W. Kernighan au début des années 70. Leur but
était de permettre de développer un langage qui permettrait d'obtenir un système d'exploitation de
type UNIX portable. D. Ritchie et B.W. Kernighan se sont inspirés des langages B et BCPL, pour
créer un nouveau langage: le langage C.
La première définition de ce langage a été donnée dans leur livre commun "The C programming
language".
Les atouts du C
Le langage C reste un des langages les plus utilisés actuellement. Cela est dû au fait que le langage
C est un langage comportant des instructions et des structures de haut niveau (contrairement à
l'assembleur par exemple) tout en générant un code très rapide grâce à un compilateur très
performant.
Un des principaux intérêts du C est que c'est un langage très portable. Un programme écrit en C en
respectant la norme ANSI est portable sans modifications sur n'importe quel système d'exploitation
disposant d'un compilateur C : Windows, UNIX, VMS (système des VAX) ou encore OS/390 ou
z/Os (l'OS des mainframes IBM).
La rapidité des programmes écrits en C est en grande partie due au fait que le compilateur
présuppose que le programmeur sait ce qu'il fait: il génère un code ne contenant pas de vérifications
sur la validité des pointeurs, l'espace d'adressage, etc. Ainsi, les programmes en C sont très
compacts.
De plus, une des caractéristiques du C est qu'il est un langage "faiblement typé" : les types de
données qu'il manipule sont très restreints, et proches de la représentation interne par le processeur :
par exemple, le type 'Chaîne de caractères' n'existe pas en C. A l'inverse, comparer un entier et un
caractère a un sens en C car un caractère est bien représenté en interne par le processeur par une
valeur de type entier (le code ASCII ou le code EBCDIC).
Caractéristiques du langage C
Le fichier source
Le fichier source d'un programme écrit en langage C est un simple fichier texte dont l'extension est
par convention .c.
Ce fichier source doit être un fichier texte non formaté, c'est-à-dire un fichier texte dans sa plus
simple expression, sans mise en forme particulière ou caractères spéciaux (il contient uniquement
les caractères ASCII de base).
Lorsque le programme est prêt à être essayé, il s'agit de le compiler (le traduire en langage
machine).
De nombreux compilateurs C existent: sous les systèmes de type UNIX par exemple, le compilateur
C est fourni en standard, si bien que la programmation en langage C est aisée sous ce type de
système. La compilation sous UNIX se fait par la ligne de commande suivante:
cc fichier.c
L’édition
Il s’agit de la phase de saisie, éventuellement de modifications, du texte du programme. Elle se fait
de façon classique en mémoire centrale, avec des facilités de sauvegarde sur disque dur.
Le terme de programme source est réservé au texte placé en mémoire et le terme fichier source est
utilisé pour désigner ce même texte lorsqu’il sera placé dans un fichier disque.
La compilation
Cette étape consiste à traduire en langage machine l’ensemble du texte constituant le programme.
Elle pourra porte, soit sur un programme source (situé en mémoire centrale), soit sur un fichier
source (situé sur disque).
L’une des originalités du langage C réside dans l’existence d’un ″préprocesseur″. Il permet au
programmeur d'inclure des fichiers source dans d'autres fichiers source (directive #include), de
définir des macros (directive #define), et de procéder à des compilations conditionnelles.
La directive #include
Le rôle de cette directive est de recopier le contenu d'un fichier dans le fichier courant. On l'emploie
généralement pour inclure les en-têtes de bibliothèques, telles que les fonctions mathématiques
(<math.h>) ou les fonctions d'entrée/sortie standard (<stdio.h>). Cette directive peut s’appliquer
également à des fichiers de votre cru. Cela peut s’avérer utile, par exemple pour écrire une seule
fois les déclarations communes à plusieurs fichiers sources différents.
Cette directive possède deux syntaxes semblables :
#include <nom_fichier>
recherche le fichier mentionné dans le répertoire « include »
#include ″nom_fichier″″
recherche d’abord le fichier mentionné dans le répertoire courant puis, en cas d’échec, dans le
répertoire ″include″
La directive #define
Elle offre deux possibilités :
- définition de symboles
- définition de macros
Définition de symboles
Une directive telle que :
#define nb_max 10
demande de substituer au symbole nb_max le texte 5, et ceci à chaque fois que ce symbole
apparaîtra dans la suite du fichier source.
#define entier int
Placée en début de programme, permettra d’écrire « ne français » les déclarations de variables
entières. Ainsi, par exemple, ces instructions :
entier a,b ;
entier *p ;
seront remplacées par :
int a,b ;
int *p ;
Voici quelques derniers exemples vous montrant comment résumer en un seul mot une instruction
C:
#define bonjour printf(″bonjour ″)
#define affiche printf(″resultat %d\n″,a)
#define ligne printf(″\n″)
Définition de macros
La définition de macros ressemble à la définition de symboles mais elle fait intervenir la notion de
paramètres.
Par exemple, avec cette directive :
#define carre(a) a*a
le préprocesseur remplacera dans la suite, tous les textes de la forme :
carre(x) dans lesquels x représente en fait un symbole quelconque par : x*x :
Par exemple :
carre(z) deviendra z*z
carre(valeur) deviendra valeur*valeur
Les définitions peuvent s’imbriquer. Ainsi, avec les deux définitions précédente, le texte :
diff(carre(p),carre(q))
sera, dans un premier temps, remplacé par : diff(p*p,q*q) puis, dans un deuxième temps, par :
p*p-q*q.
Remarque : il faut ajouter des parenthèses pour éviter des erreurs inattendues :
Exemple :
#define DOUBLE(x) x+x
…
DOUBLE(a)/b
Le texte généré par le préprocesseur sera le suivant :
a+a/b
ce qui différent de (a+a)/b.
Ce problème est lié aux priorités relatives des opérateurs et peut être résolu en introduisant des
parenthèses dans la définition de la macro. Ainsi, avec :
#define DOUBLE(x) ((x)+(x))
L’édition de liens
Le module objet crée par la compilation, bien que constitué d’instructions en langage machine,
n’est pas directement exécutable. Il lui manque pour cela les différents modules objets
correspondants aux fonctions (de la bibliothèque) appelées par votre programme.
C’est le rôle de l’éditeur de liens que de réaliser cette incorporation de modules.
Ces modules peuvent être obtenus par l’éditeur de liens de deux manières :
- Par simple recopie de fichiers d’extension obj,
- par recherche dans fichier d’extension lib, lequel regroupe en fait plusieurs modules objets ayant
trait à un même domaine (par exemple maths.lib contient tous les modules objets correspondants
aux routines mathématiques).
Le résultat de l’édition de liens est un programme exécutable.
Typologie
La manière d'écrire les choses en langage C a son importance.
Le langage C est par exemple sensible à la casse (en anglais case sensitive), cela signifie qu'un
nom contenant des majuscules est différent du même nom écrit en minuscules. Ainsi, les
spécifications du langage C précisent que la fonction principale doit être appelée main() et non
Main() ou MAIN().
Ajout de commentaires
/*Voici un commentaire!*/
En plus des symboles /* et */, fonctionnant un peu comme des parenthèses, le symbole // permet de
mettre en commentaire toute la ligne qui la suit (i.e. les caractères à droite de ce symbole sur la
même ligne.
Il convient toutefois d'utiliser préférablement la notation /* */ que //, car c'est beaucoup plus joli et
plus propre.
• int x;
• x = (int)8.324;
x contiendra après affectation la valeur 8
La déclaration de variables
Pour pouvoir utiliser une variable, il faut la définir, c'est-à-dire lui donner un nom, mais surtout un
type de donnée à stocker afin qu'un espace mémoire conforme au type de donnée qu'elle contient
lui soit réservé.
Une variable se déclare de la façon suivante:
type Nom_variable;
ou bien s'il y a plusieurs variables du même type:
type Nom_variable1, Nom_variable2, ...;
Une variable locale : est déclarée à l'intérieur d'un bloc d'instructions (dans une fonction ou une
boucle par exemple) et aura une portée limitée à ce seul bloc d'instruction, c'est-à-dire qu'elle est
inutilisable ailleurs.
Définition de constantes
a = b = c = 1;
ce qui correspond à :
a = (b = (c = 1));
Les opérateurs de comparaison
Opérateur Dénomination Effet Exemple Résultat
(avec x valant 7)
== opérateur d'égalité Compare deux valeurs et x==3 Retourne 1 si X est
A ne pas confondre avec vérifie leur égalité égal à 3, sinon 0
le signe d'affectation (=)
< opérateur Vérifie qu'une variable est x<3 Retourne 1 si X est
d'infériorité stricte strictement inférieure à une inférieur à 3, sinon 0
valeur
<= opérateur Vérifie qu'une variable est x<=3 Retourne 1 si X est
d'infériorité inférieure ou égale à une inférieur ou égal à 3,
valeur sinon 0
> opérateur de Vérifie qu'une variable est x>3 Retourne 1 si X est
supériorité stricte strictement supérieure à une supérieur à 3, sinon 0
valeur
>= opérateur de Vérifie qu'une variable est x>=3 Retourne 1 si X est
supériorité supérieure ou égale à une supérieur ou égal à 3,
valeur sinon 0
!= opérateur de Vérifie qu'une variable est x!=3 Retourne 1 si X est
différence différente d'une valeur différent de 3, sinon 0
L’opérateur & permet d’accéder à une partie des bits d’une valeur en ″masquant″ les autres. Par
exemple, si n est de type int, l’expression :
n & 0x000F
permet de ne prendre en compte que les quatre bits de droite de n.
De même n & 0x8000 permet d’extraire le ″bit de signe″ de n.
Voici un exemple de programme qui décide si un entier est pair ou impair, en examinant
simplement le dernier bit de sa représentation binaire.
void main()
{
int n ;
printf(″donner un entier : ″) ;
scanf(″%d″,&n) ;
if (n & 0x0001==1)
printf(″%d est impair\n″,n) ;
else printf(″%d est pair\n″,n) ;
}
L’opérateur conditionnel
Considérons la structure de choix :
if (a > b) max = a ;
else
max = b ;
max = a > b ? a : b
D’une manière générale, cet opérateur évalue la première expression qui joue le rôle d’une
condition. Comme toujours en C, celle-ci peut être en fait de n’importe quel type type.
Instructions :
Une expression comme i=0 ou i++ ou printf(...) devient une instruction, si elle est suivie
d'un point-virgule.
Exemples:
i=0;
i++;
X=pow(A,4);
printf(" Bonjour !\n");
a=(5*x+10*y)*2;
Évaluation et résultats
En C toutes les expressions sont évaluées et retournent une valeur comme résultat:
(3+4==7) retourne la valeur 1 (vrai)
A=5+6 retourne la valeur 8
Comme les affectations sont aussi interprétées comme des expressions, il est possible de profiter de
la valeur rendue par l'affectation:
((A=cos(X)) == 0.5)
L'instruction switch
L'instruction switch permet de faire plusieurs tests de valeurs sur le contenu d'une même variable.
Ce branchement conditionnel simplifie beaucoup le test de plusieurs valeurs d'une variable, car
cette opération aurait été compliquée (mais possible) avec des if imbriqués. Sa syntaxe est la
suivante:
switch (Variable) {
case Valeur1: Liste d'instructions;
break;
case Valeur2: Liste d'instructions;
break;
case Valeurs...: Liste d'instructions;
break;
default: Liste d'instructions;
}
Les boucles
Les boucles sont des structures qui permettent d'exécuter plusieurs fois la même série d'instructions
jusqu'à ce qu'une condition ne soit plus réalisée...
L'instruction while
L'instruction while représente un autre moyen d'exécuter plusieurs fois la même série d'instructions.
La syntaxe de cette expression est la suivante:
while (condition) {
liste d'instructions;
}
Cette instruction exécute la liste d'instructions tant que la condition est réalisée.
La condition de sortie pouvant être n'importe quelle structure conditionnelle, les risques de boucle
infinie (boucle dont la condition est toujours vraie) sont grands, c'est-à-dire qu'elle risque de
provoquer un plantage du programme en cours d'exécution.
Exemple :
Considérons le programme suivant :
#include <stdio.h>
void main()
{int n, som ;
som=0 ;
while (som < 100)
{ printf(″donner un nombre : ″) ;
scanf(″%d″,&n) ;
som=som + n ;
}
printf(″somme obtenue : %d″, som) ;
}
La construction « while (som < 100) » répète l’instruction qui suit (ici un bloc d’instructions) tant
que la condition mentionnée est vraie. La condition est examinée avant chaque parcours de la
boucle. Une telle boucle peut très bien n’être parcourue aucune fois si la condition est fausse dés
qu’on l’aborde comme le montre l’exemple suivant :
…
int i ;
i= 13 ;
while (i<10)
{ printf(″i=%d \n″,i) ;
i++ ; }
Dans cet exemple, la condition est fausse dés le début.
La 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));
La boucle for
La syntaxe de cette expression est la suivante:
for (initialisation du compteur; condition ; modification du compteur)
{liste d'instructions; }
Par exemple :
i=1 ;
for (i=1; i<6; i++) i=1 ;
for ( ; i<6; )
printf("%d ", i); for ( ; i<6; i++)
⇔ ⇔ { printf("%d ", i);
printf("%d ", i);
i++ ; }
Notez que lorsque expression2 est absente, elle considérée comme vraie.
La richesse de la notion d’expression en C permet de grouper plusieurs actions dans une expression.
Ainsi :
j=1 ; k=5 ; i=0 ; j=1 ;k=5 ;
for(i=0, j=1, k=5 ;… ; …) for(i=0 ;… ; …) for( ;… ; …)
…. ; ⇔ ⇔
…. ; …. ;
De même :
est équivalent à :
printf(″Début\n″) ;
for(i=1; i<=5; i++)
{ printf(″Fin de tour\ n″) ;
instruction ;
}
printf(″Fin de tour\ n″) ;
Les constructions :
for( ; ; ) ;
for ( ; ; ) { }
sont syntaxiquement correctes. Elles représentent des boucles infinies de corps vide. En pratique,
elles ne présentent aucun intérêt.
Par contre, cette construction
for( ; ; )
instruction ;
est une boucle à priori infinie dont on pourra éventuellement sortir par une instruction break.
Saut inconditionnel
x=1;
while (x<=10) {
if (x == 7) {
printf("Division par zéro!");
continue;
}
a = 1/(x-7);
printf("%f", a);
x++;
}
Il y a une erreur dans ce programme: lorsque x est égal à 7, le compteur ne s'incrémente plus, il
reste constamment à la valeur 7, il aurait fallu écrire:
x=1;
while (x<=10) {
if (x == 7) {
printf("division par 0");
x++;
continue;
}
a = 1/(x-7);
printf("%f", a);
x++;
}
Arrêt inconditionnel
for (x=1; x<=10; x++) {
a = x-7;
if (a == 0) {
printf("division par 0");
break;
}
printf("%f", 1/a);
}
L’instruction goto
for (x=1; x<=10; x++) {
a = x-7;
if (a == 0) goto etiq;
printf("%f", 1/a);
}
etiq : printf("division par 0"); /* fin */
Les fonctions en C
Une fonction est un sous-programme qui permet d'effectuer un ensemble d'instruction par simple
appel de la fonction dans le corps du programme principal.
Les fonctions permettent d'exécuter dans plusieurs parties du programme une série d'instructions,
cela permet une simplicité du code et donc une taille de programme minimale.
Une fonction peut faire appel à elle-même, on parle alors de fonction récursive (il ne faut pas
oublier de mettre une condition de sortie au risque sinon de ne pas pouvoir arrêter le programme...).
La déclaration d'une fonction
type_retourne nom_fonction(type1 argument1, type2 argument2, ...)
{ liste d'instructions }
Remarques:
• type_retourne représente le type de valeur que la fonction est sensée retourner (char, int,
float,...)
• Si la fonction ne renvoie aucune valeur, on la fait alors précéder du mot-clé void
• Si aucun type de donnée n'est précisé, le type int est pris par défaut
• le nom de la fonction suit les mêmes règles que les noms de variables:
le nom doit commencer par une lettre
un nom de fonction peut comporter des lettres, des chiffres et les caractères _ et &
(les espaces ne sont pas autorisés!)
le nom de la fonction, comme celui des variables est sensible à la casse
(différenciation entre les minuscules et majuscules)
• Les arguments sont facultatifs, mais s'il n'y a pas d'arguments, les parenthèses doivent rester
présentes
• Il ne faut pas oublier de refermer les accolades
• Le nombre d'accolades ouvertes (fonction, boucles et autres structures) doit être égal au
nombre d'accolades fermées
• La même chose s'applique pour les parenthèses, les crochets ou les guillemets
Appel de fonction
Pour exécuter une fonction, il suffit de faire appel à elle en écrivant son nom (une fois de plus en
respectant la casse) suivie d'une parenthèse ouverte (éventuellement des arguments) puis d'une
parenthèse fermée: nom_fonction();
Exemple 1:
void affiche(int n) ; void affiche(int n)
void main() {
{ int a=10, b=20 ; printf(″valeur : %d\n ″,n) ;
affiche(a) ; }
affiche(b) ;
affiche(a+b) ;
}
Exemple 2 :
void affiche_trois(int a, int b, float x) ; void affiche_trois(int a, int b, float x)
{
void main() printf(″%d %d %f\n ″,a, b, x) ;
{ int n, p; }
float x;
printf(″Donner deux entiers\n″) ;
scanf(″%d%d″,&n,&p) ;
printf(″Donner un réel\n″) ;
scanf(″%f″,&x) ;
affiche(n,p,x) ;
affiche(n+p, 5*n, x-20) ;
}
L’instruction return
avant appel : 10 20
début echange : 10 20
fin echange : 20 10
après appel : 10 20
Portée
Les variables globales ainsi définies ne sont connues du compilateur que dans la partie du source
suivant leur déclaration. On dit que leur ″portée″ (ou encore leur ″espace de validité″) est limitée à
la partie du source qui suit leur déclaration.
…fct1(…) ;
… fct2(…) ;
void main()
{
…
}
int n ;
float x ;
…
fct1(..)
{
/* on peut utiliser n et x*/
…
}
…fct2(…)
{
/* on peut utiliser n et x*/
…
}
Les variables n et x sont accessibles aux fonctions fct1 et fct2, mais pas au programme principal (la
fonction main).
Pour des raisons de lisibilité du programme, on préférera regrouper les déclarations de toutes les
variables globales au début du source.
Classe d’allocation
A l’exception de l’exemple précédent, les variables que nous avions rencontrées jusqu’ici n’étaient
pas des variables globales. Plus précisément, elles étaient définies au sein d’une fonction (qui
pouvait être la fonction main). De telles variables sont dits ″locales″ à la fonction dans laquelle elles
sont déclarées.
Les variables locales ne sont connues qu’à l’intérieur de la fonction où elles sont déclarées. Leur
portée est donc limitée à cette fonction.
Les variables locales n’ont aucun lien avec des variables globales de même nom ou avec d’autres
variables locales à d’autres fonctions.
void fct1() ;
int n ; /* variables globales */
void main()
{
int p ; /*variables locales*/
…
}
void fct1()
{
int n ; /*variables locales*/
int p ; /*variables locales*/
…
}
Le langage C autorise la récursivité des appels de fonctions. Celle peut prendre deux aspects :
- récursivité directe : une fonction comporte, dans sa définition, au moins un appel à elle-
même,
- récursivité croisée : l’appel d’une fonction entraîne celui d’une autre fonction qui, à son
tour appelle la fonction initiale.
Exemple 1 :
Le factoriel peut être définie de deux manières:
Définition itérative
N ! = 1 si N = 0
Sinon N ! = 1 x 2 x … (N–1) x N
Définition récursive
Si N = 0 alors N != 1
Sinon N !=N x (N-1) !
Ainsi on peut écrire la fonction qui calcule le factoriel d’un nombre d’une manière récursive de la
manière suivante :
long factoriel(int n)
{
if (n <= 1) return (1);
else return (n * factoriel (n-1));
}
int Fib(int n)
{ if (n == 0) return (0);
if (n==1) return(1);
else return (Fib (n-1) + Fib (n-2));
}
int Fib_iter(int n)
{ int u0,u1, u2;
int i;
if (n == 0) return (0);
if (n==1) return(1);
u0 = 0; u1 = 1;
for (i = 2; i <=n; ++i)
{u2=u0+u1;
u0=u1;
u1 = u2;
}
return (u2);
}