Vous êtes sur la page 1sur 42

Langage de Programmation

(Première Année Licence)


2020 - 2021

LANGAGE C
DEFITECH - TOGO

Chargé de cours : AMEVOR Kossi A.


Contact : 91 25 93 37
Email : kjamevor@gmail.com
SEQUENCE I : ELEMENTS DE BASE DU LANGAGE C

Le langage C est un langage évolué et structuré, assez proche du langage machine destiné à
des applications de contrôle de processus (gestion d'entrées/sorties, applications en temps
réel ...). Le langage C possède assez peu d'instructions, il fait par contre appel à des
bibliothèques, fournies en plus ou moins grand nombre avec le compilateur.

Exemples math.h : calcul des fonctions mathématiques. stdio.h : gestion


des entrées/sorties standard (fonction printf par exemple). time.h :
conversion entre différents formats de date et d'heure. signal.h : gestion
des signaux.

On ne saurait développer un programme en langage C sans se munir de la documentation


concernant ces bibliothèques.

Note
• Les compilateurs C sont remplacés petit à petit par des compilateurs C++.
• Un programme écrit en C est en principe compris par un compilateur C++. Le
cours qui suit est un cours de langage C écrit dans un contexte C++.

I. ETAPES PERMETTANT L'EDITION, LA MISE AU POINT ET L'EXECUTION D'UN


PROGRAMME

1- Edition du programme source, à l'aide d'un éditeur (traitement de textes). Le nom du


fichier contient l'extension .cpp (ou .c selon l’éditeur).
Exemple: mon_exercice.cpp.
2- Compilation du programme source, c'est à dire création des codes machine destinés au
microprocesseur utilisé. Le compilateur indique les erreurs de syntaxe mais ignore les
fonctions-bibliothèques appelées par le programme.
Le compilateur génère un fichier binaire, non listable, appelé fichier objet:
mon_exercice.obj.
3- Editions de liens : Le code machine des fonctions-bibliothèque est chargé, création d'un
fichier binaire, non listable, appelé fichier exécutable: mon_exercice.exe. 4- Exécution du
programme
Les compilateurs permettent en général de construire des programmes composés de
plusieurs fichiers sources, d'ajouter à un programme des unités déjà compilées.

Le langage C distingue les minuscules, des majuscules. Les mots réservés du langage
C doivent être écrits en minuscules.

On a introduit dans ce programme la notion d’interface homme/machine (IHM).


• L’utilisateur visualise une information sur l’écran,
• L’utilisateur, par une action sur le clavier, fournit une information au programme.

II. DIFFERENTS TYPES DE VARIABLES

II.1. Entiers

Le langage C distingue plusieurs types d'entiers:

Cours langage C 2020- 2021 2


TYPE DESCRIPTION TAILLE MEMOIRE
int unsigned entier standard (signé) 4 octets: - 231 ≤ n ≤ 231-1
int entier positif entier 4 octets: 0 ≤ n ≤ 232
short court signé 2 octets: - 215 ≤ n ≤ 215-1
unsigned short entier court non signé 2 octets: 0 ≤ n ≤ 216
char unsigned caractère signé caractère 1 octet : - 27 ≤ n ≤ 27-1
char non signé 1 octet : 0 ≤ n ≤ 28

Remarque
En langage C, le type char est un cas particulier du type entier: un caractère est un entier
de 8 bits

Exemples
Les caractères alphanumériques s'écrivent entre ' ' Le
caractère 'b' a pour valeur 98 (son code ASCII).
• Le caractère 22 a pour valeur 22.
• Le caractère 127 a pour valeur 127.

Quelques constantes caractères

Caractère Valeur (code ASCII) NOM ASCII


'\n' interligne 0x0a LF
'\t' tabulation horizontale 0x09 HT
'\v' tabulation verticale 0x0b VT
'\r' retour chariot 0x0d CR
'\f' saut de page 0x0c FF
'\\' backslash 0x5c \
'\'' cote 0x2c '
'\"' guillemets 0x22 "

II.2. Réels

Un réel est composé :


d'un signe,
d'une mantisse
• et d'un exposant.
Un nombre de bits est réservé en mémoire pour chaque élément.

Le langage C distingue 2 types de réels :

TYPE DESCRIPTION TAILLE MEMOIRE


float réel standard 4 octets
double réel de précision supérieure 8 octets

III. INITIALISATIONS

Le langage C permet l'initialisation des variables dans la zone des déclarations :

Cours langage C 2020- 2021 3


char c; est équivalent à char c = 'A'; c = 'A';

int i; est équivalent à int i = 50; i = 50;

Cette règle s'applique à tous les nombres, char, int, float ...

IV. SORTIES DE NOMBRES OU DE TEXTE A L'ECRAN

IV.1. Fonction printf

Ce n'est pas une instruction du langage C, mais une fonction de la bibliothèque stdio.h.
Exemple
Affichage d'un texte printf("Belle voiture"); /* pas de retour à la ligne du curseur
après l'affichage. */ printf("Belle voiture \n"); /* affichage du texte, puis retour à la
ligne du curseur. */

Exercice I.1
Tester le programme suivant et conclure.
#include <stdio.h>
#include <conio.h> void
main()
{
printf("BONJOUR "); /* equivalent à puts("BONJOUR"); */
printf("IL FAIT BEAU\n"); printf("BONNES VACANCES");
puts("Pour continuer frapper une touche...");
getch(); /* Attente d'une saisie clavier */ }

La fonction printf exige l'utilisation de formats de sortie, avec la structure suivante:


printf("%format", nom_de_variable);

Exercice I.2
Affichage d'une variable de type char.
Tester le programme suivant et conclure. Dans un deuxième temps, modifier ce programme
pour améliorer l’interface utilisateur.

#include <stdio.h>
#include <conio.h> void
main()
{
char c;
c =66; /* c est le caractère alphanumérique B */
printf("%d\n", c); /* affichage du code ASCII en décimal et retour à la ligne */
printf("%o\n", c); /* affichage du code ASCII en base huit et retour à la ligne */
printf("%x\n", c); /* affichage du code ASCII en hexadécimal et retour à la ligne */
printf("%c\n", c); /* affichage du caractère et retour à la ligne */
puts("Pour continuer frapper une touche..."); getch();
/* Attente d'une saisie clavier */
}
Pour l’affichage multiple de structure la syntaxe est la suivante :
printf("format1 format2 .... formatn", variable1, variable2, ......., variablen);

Exercice I.3
Cours langage C 2020- 2021 4
Tester le programme suivant et conclure :

#include <stdio.h>
#include <conio.h>

void main()
{
char c;
c ='A'; /* c est le caractère alphanumérique A */ printf("Décimal = %d Caractère =
%c\n", c, c);
puts("Pour continuer frapper une touche...");
getch(); /* Attente d'une saisie clavier */ }

les différents formats de sortie


pour les entiers:
o %d affichage en décimal (entiers de type int), o %x
affichage en hexadécimal (entiers de type int), o %u
affichage en décimal (entiers de type unsigned int),

pour les réels:


o %f affichage des réels, o %lf affichage des
doubles, o %e affichage des réels en notation
scientifique.

pour les caractères:


o %c affichage d’un caractère, o %s
affichage d’une chaîne de caractères.

Exercice I.4 a et b sont des entiers, a = -21430 b = 4782, calculer et afficher a+b, a-b, a*b,
a/b, a%b en format décimal, et en soignant l’interface homme/machine.

Note a/b donne le quotient de la division; a%b donne le reste de la


division.

Exercice I.5 a et b sont des réels, a = -21,43 b = 4,782, calculer et afficher a+b, a-b, a*b,
a/b, en soignant l’interface homme/machine.

IV.2. Autres Fonctions de sorties

• Affichage d'un caractère


La fonction putchar permet d'afficher un caractère. c étant une variable de type char,
l'écriture putchar(c); est équivalente à printf("%c\n", c);

• Affichage d'un texte


La fonction puts permet d'afficher un texte. L'écriture puts("bonjour"); est équivalente
à printf("bonjour\n");

Il vaut mieux utiliser puts et putchar si cela est possible. Ces fonctions, non formatées, sont
d'exécution plus rapide et nécessitent moins de place en mémoire lors de leur chargement.

Cours langage C 2020- 2021 5


V. OPERATEURS

V.1. Opérateurs arithmétiques sur les réels

+ - * / avec la hiérarchie habituelle.

V.2. Opérateurs arithmétiques sur les entiers

+ - * / (quotient de la division) % (reste de la division) avec la hiérarchie habituelle.

V.3. Opérateurs logiques

&& ET logique pour évaluer une expression,


|| OU logique pour évaluer une expression,
== Test d’égalité,
!= Test de différence,
! Négation,
<, >, <=, >= Test de comparaison.

Exemples

OPERATION INTERPRETATION RESULTAT


a = 2+3 2 plus 3 Valeur de a : 5
a = 5%2 Reste de 5 divisé par 2 Valeur de a : 1
a = (3==3) 3 égal à 3 ? Valeur de a : 1
a = (6==5) 6 égal à 5 ? Valeur de a : 0
a = (2 !=3) 2 différent de 3 ? Valeur de a : 1
a = (6<=3) 6 inférieur ou égal à 3 ? Valeur de a : 0
a = !1 Non 1 Valeur de a : 0
a = ((3==3) || (6<=3)) 3==3? Ou 6<= 3 ? Valeur de a : 1
a = ((3==3) && (6<=3)) 3==3? Et 6<= 3 ? Valeur de a : 0

V.4. Opérateur sizeof(type)

Il renvoie le nombre d'octets réservés en mémoire pour chaque type d'objet.

Exemple
n = sizeof(char); /* n vaut 1 */

Exercice I.8
Quels nombres va renvoyer le programme suivant ?

#include <stdio.h>
#include <conio.h>
void main()
{
printf("Taille d'un caractère :%d\n", sizeof(char));
printf("Taille d'un entier :%d\n",sizeof(int));
Cours langage C 2020- 2021 6
printf("Taille d'un réel :%d\n",sizeof(float));
printf("Taille d'un double :%d\n",sizeof(double));
puts("Pour continuer frapper une touche..."); getch();
/* Attente d'une saisie clavier */
}

INCREMENTATION - DECREMENTATION

Le langage C autorise des écritures simplifiées pour l'incrémentation et la décrémentation de


variables :

i = i+1; est équivalent à i++;


i = i-1; est équivalent à i--;

OPERATEURS COMBINES

Le langage C autorise des écritures simplifiées lorsqu'une même variable est utilisée de
chaque côté du signe = d'une affectation. Ces écritures sont à éviter lorsque l'on débute l'étude
du langage C car elles nuisent à la lisibilité du programme.

a = a+b; est équivalent à a+= b;


a = a-b; est équivalent à a-= b;

VI. LES DECLARATIONS DE CONSTANTES

Le langage C autorise 2 méthodes pour définir des constantes.

1ère méthode déclaration d'une variable, dont la valeur sera constante pour tout
le programme:

Exemple
void main()
{
const float PI = 3.14159;

float perimetre, rayon = 8.7; perimetre


= 2*rayon*PI;
....
}

Dans ce cas, le compilateur réserve de la place en mémoire (ici 4 octets), pour la variable PI,
mais dont on ne peut changer la valeur.

2ème méthode définition d'un symbole à l'aide de la directive de


compilation #define.

Exemple
#define PI 3.14159
void main()
{

Cours langage C 2020- 2021 7


float perimetre, rayon = 8.7; perimetre
= 2*rayon*PI;
....
}

Le compilateur ne réserve pas de place en mémoire. Les constantes déclarées par #define
s'écrivent traditionnellement en majuscules, mais ce n'est pas une obligation.

VII. LES CONVERSIONS DE TYPES

Le langage C permet d'effectuer des opérations de conversion de type : on utilise pour cela
l'opérateur de "cast" ().

Exemple et exercice I.10


#include <stdio.h>
#include <conio.h>
void main()
{
int i=0x1234, j;
char d,e; float
r=89.67, s; j =
(int)r; s =
(float)i; d =
(char)i; e =
(char)r;
printf("Conversion float en int: %5.2f vaut %d\n",r,j);
printf("Conversion int en float: %d vaut %5.2f\n",i,s);
printf("Conversion int en char: %x vaut %x\n",i,d); printf("Conversion
float en char: %5.2f vaut %d\n",r,e); printf("Pour sortir frapper une
touche ");
getch();
}

Cours langage C 2020- 2021 8


SEQUENCE II : SAISIE DE NOMBRES ET DE CARACTERES

I. LA FONCTION GETCH

La fonction getch, appartenant à la bibliothèque conio.h permet la saisie clavier d'un


caractère alphanumérique, sans écho écran. La saisie s'arrête dès que le caractère a été
frappé.

On peut utiliser getch de deux façons :


• sans retour de variable au programme Exemple
printf("POUR CONTINUER FRAPPER UNE TOUCHE
"); getch();
• avec retour de variable au programme Exemple
char alpha;
printf("ENTRER UN CARACTERE (ATTENTION PAS DE RETURN) "); alpha
= getch();
printf("\nVOICI CE CARACTERE: %c",alpha);

Les parenthèses vides de getch() signifient qu'aucun paramètre n'est passé à cette fonction
par le programme.

II. LA FONCTION SCANF

La fonction scanf, appartenant à la bibliothèque stdio.h, permet la saisie clavier de n'importe


quel type de variable. Les variables à saisir sont formatées, le nom de la variable est précédé
du symbole & désignant l'adresse de la variable. La saisie s'arrête avec "RETURN" ; les
éléments saisis s'affichent à l'écran (saisie avec écho écran).

Tous les éléments saisis après un caractère d'espacement (espace, tabulation) sont ignorés.

Exemple
char alpha;
int i; float
r;
scanf("%c",&alpha); /* saisie d'un caractère */
scanf("%d",&i); /* saisie d'un nombre entier en décimal */
scanf("%x",&i); /* saisie d'un nombre entier en hexadécimal*/
scanf("%f",&r); /* saisie d'un nombre réel */

Remarque
Si l'utilisateur ne respecte pas le format indiqué dans scanf, la saisie est ignorée. Aucune
erreur n'est générée.

Exemple
char alpha; scanf("%d",&alpha);

Si l'utilisateur saisie 97 tout va bien, alpha devient le caractère dont le code ASCII vaut 97.
Si l'utilisateur saisie a, sa saisie est ignorée.

Exercice II.1
Saisir un caractère au clavier, afficher son code ASCII à l'écran. Soigner l'affichage.
Cours langage C 2020- 2021 9
III. NOTION DE FLUX D'ENTREE

Lorsque l'on saisit au clavier une suite de caractères terminés par "RETURN" ces caractères
sont rangés dans un tampon (ou buffer) de type FIFO (First In/First Out) ; le dernier caractère
rangé dans le tampon est LF (code ASCII 0x0A). Cette suite de caractères est appelée flux
d'entrée.

La taille du tampon dépend de la machine et du compilateur utilisés. Sur un PC et en


TURBOC, la taille du tampon est de 127 caractères.

Une compilation du programme vide le tampon.

IV. DEUXIEME APPROCHE DE LA FONCTION SCANF

La fonction scanf ne se comporte pas tout à fait comme décrit plus haut. Si le tampon est
vide, tout se passe comme précédemment décrit.
Au contraire, si le tampon n'est pas vide, la fonction scanf en teste le premier élément, s'il
correspond au format de la variable invoquée, le tampon perd cet élément et la variable en
prend la valeur.

Tout caractère ou nombre saisi au clavier et non pris en compte par la fonction scanf
est rangé dans le tampon.

Exercice II.2
#include <stdio.h>
#include <conio.h>
void main()
{
char c1, c2; printf("Entrer un
caractère: "); scanf("%c",
&c1);
printf("Voici son code ASCII en hexadécimal: %x\n", c1);
printf("Entrer un autre caractère: "); scanf("%c",
&c2);
printf("Voici son code ASCII en hexadécmal: %x\n", c2);
printf("Pour continuer frapper une touche "); getch();
}

Si l'utilisateur saisit K pour c1, le programme donnera l'écran d'exécution suivant :

Entrer un caractère: K
Voici son code ASCII en hexadéimal : 4b
Entrer un autre caractèe: Voici son code ASCII en hexadecimal: a

Lors de la saisie de K, le caractère LF est rangé dans le tampon. Lors du deuxième appel à
scanf, le tampon n'est pas vide, l'utilisateur ne peut effectuer sa saisie clavier, le code ASCII
de LF est affiché à l'écran.
A l'issue de l'exécution, le tampon est vide.

Remarque

Cours langage C 2020- 2021 10


En TURBO C la fonction flushall() ou fflush(stdin) permet de vider le tampon d'entrée selon
la version du C. En l'invoquant après un appel à scanf, on se débarrasse des problèmes de
flux d'entrée.

V. LA FONCTION GETCHAR

La fonction getchar pemet la saisie d'un caractère (char). Elle appartient à la bibliothèque
stdio.h. Les 2 écritures suivantes sont équivalentes:

char c; char c;
printf("Entrer un caractère : "); printf("Entrer un caractère: ");
scanf("%c", &c); c = getchar();

Non formatée, la fonction getchar est moins gourmande en place mémoire que scanf. Il vaut
mieux l'utiliser quand cela est possible; getchar utilise le flux d'entrée exactement comme
scanf.

Cours langage C 2020- 2021 11


SEQUENCE III : STRUCTURES DE CONTROLE

I. STRUCTURES CONDITIONNELLES

I.1. Instruction SI ... ALORS ... SINON ...

Il s'agit de l'instruction :
si (expression conditionnelle vraie) alors
{
BLOC 1 D'INSTRUCTIONS
}
Sinon
{
BLOC 2 D'INSTRUCTIONS
}

Syntaxe en C :
if (expression)
{
............; /* bloc 1 d'instructions */
............;
}
else
{
............; /* bloc 2 d'instructions */
............; }

Le bloc "sinon" est optionnel :

si (expression vraie) alors


{
BLOC D'INSTRUCTIONS
}

Syntaxe en C : if
(expression)
{
............; /* bloc d'instructions */
............;
}

Remarque
Les {} ne sont pas nécessaires lorsque les blocs ne comportent qu'une seule instruction.

LES OPERATEURS LOGIQUES

test d'égalité if (a==b) si a égal b


test de non égalité if (a!=b) si a différent de b

Cours langage C 2020- 2021 12


tests de relation d'ordre if (a<b) if (a<=b) if (a>b) if
(a>=b)
test de ET LOGIQUE if ((expression1) && si l'expression1 ET
(expression2)) l'expression2 sont vraies
test de OU LOGIQUE if ((expression1) || si l'expression1 OU
(expression2)) l'expression2 est vraie
test de NON LOGIQUE if (!(expression1)) si l'expression1 est fausse
Toutes les combinaisons sont possibles entre ces tests.

Exercice III.1
L'utilisateur saisit un caractère, le programme teste s'il s'agit d'une lettre majuscule, si oui il
renvoie cette lettre en minuscule, sinon il renvoie un message d'erreur.

Le langage C admet des écritures contractées dans les expressions de test :

char reponse; est équivalent à char reponse; printf(" Voulez-vous jouer ?");
printf("Voulez-vous jouer ?"); reponse = getchar();
if(reponse == 'o') if((reponse = getchar()) =='o')
printf(" BONJOUR\n "); printf("BONJOUR\n");

else printf(" TANT-PIS\n "); else printf("TANT-PIS"\n);

I.2. INSTRUCTION AU CAS OU ... FAIRE ...

L'instruction switch permet des choix multiples uniquement sur des entiers (int) ou des
caractères (char).

Syntaxe :
switch(variable de type char ou int) //au cas où la variable vaut:
{
case valeur1: .......; // cette valeur1 : exécuter ce bloc d'instructions.
.......;
break;
case valeur2: ........; // cette valeur2 : exécuter ce bloc d'instructions.
........;
break; ...
. etc ...

default : ........; // aucune des valeurs précédentes : exécuter ce bloc
........; // d'instructions, pas de "break" ici.
}

Le bloc "default" n'est pas obligatoire.

L’instruction switch correspond à une cascade d’instructions if ... else

Exemple
Cette instruction est commode pour fabriquer des "menus"

Cours langage C 2020- 2021 13


char choix;
printf("Liste par groupe taper 1\n");
printf("Liste alphabetique taper 2\n");
printf("Pour sortir taper S\n"); printf("\n
Votre choix : ");
choix = getchar();

switch(choix)
{
case '1': ........;
........;
break;
case '2': ......;
......;
break;
case 'S': printf("\n FIN DU PROGRAMME ....");
break;
default :printf("\n Ce choix n'est pas prévu "); /* pas de break ici */
}

II. STRUCTURES REPETITIVES

II.1. La boucle TANT QUE ... FAIRE ...

Il s'agit de l'instruction :
tant que (expression vraie) faire
{
BLOC D'INSTRUCTIONS
}

Syntaxe en C :
while (expression)
{
............; /* bloc d'instructions */
............;
}
Le test se fait d'abord, le bloc d'instructions n'est pas forcément exécuté.

Remarque
• Les {} ne sont pas nécessaires lorsque le bloc ne comporte qu'une seule instruction.
• On peut rencontrer la construction suivante :
while (expression); terminée par un ; et sans la présence du bloc d'instructions. Cette
construction signifie: "tant que l'expression est vraie attendre".

II.2. Instruction POUR ...

Il s'agit de l'instruction :
pour (initialisation; condition de continuité vraie; modification)
{
BLOC D'INSTRUCTIONS
}
Cours langage C 2020- 2021 14
Syntaxe en C :
for(initialisation ; condition de continuité ; modification)
{
............; /* bloc d'instructions */
............;
}

Remarque
Les {} ne sont pas nécessaires lorsque le bloc ne comporte qu'une seule instruction.
Les 3 instructions du for ne portent pas forcément sur la même variable.

Exemples
for(i = 0 ; i<10 ; i++)
{
............; /* bloc d'instructions */
............;
}

La boucle
for(;;) {
............; /* bloc d'instructions */
............;
}

est une boucle infinie (répétition infinie du bloc d'instructions).

Utilisation de variables différentes

resultat = 0;
for(i = 0 ; resultat<30 ; i++)
{
............; /* bloc d'instructions */
............;
resultat = resultat + 2*i;
}

Exercice III.2
Saisir un entier, calculer n!
Utiliser une boucle while puis une boucle for.

II.3. L'instruction REPETER ... TANT QUE ...

Il s'agit de l'instruction :
Répéter
{
BLOC D'INSTRUCTIONS
}
tant que (expression vraie)

Cours langage C 2020- 2021 15


Syntaxe en C :
do
{
............; /* bloc d'instructions */
............;
}
while (expression);

Le test se faisant après, le bloc est exécuté au moins une fois.

Remarque
Les {} ne sont pas nécessaires lorsque le bloc ne comporte qu'une seule instruction.

III. COMPLEMENT SUR LES TESTS

En langage C, une expression nulle de type entier (int) est fausse, une expression non
nulle de type entier (int) est vraie.

Exemples :
int a, b, c, delta; est équivalent à int a, b, c, delta;
delta = b*b-4*a*c; delta = b*b-4*a*c;
if(delta != 0) if(delta)
{ ....... } { ....... }

int a, b, c, delta; est équivalent à int a, b, c, delta;


delta = b*b-4*a*c; delta = b*b-4*a*c;
if(delta == 0) if(!delta)
{ ....... } {.......}

EXERCICES RECAPITULATIFS

Exercice III.3
Soit le programme suivant : #include
<stdio.h>
int main()
{
int i,n,som; som
= 0;
for(i=0;i<4;i++)
{
printf(‘’donnez un entier ’’);
scanf(‘’ %d ’’,&n); som+=n;
}
printf(‘’Somme : %d\n’’, som); return
0;
}
Ecrire un programme réalisant exactement la même chose, en employant :
une instruction while;
une instruction do ... while.

Cours langage C 2020- 2021 16


Exercice III.4
Résoudre ax2 + bx + c = 0.

Exercice III.5
Saisir une suite de caractères, compter et afficher le nombre de lettres e et d'espaces. Utiliser
les propriétés du tampon.

Exercice III.6
La fonction kbhit appartient à la bibiothèque conio.h. Une fonction équivalente peut exister
avec d'autres compilateurs. La fonction kbhit teste si un caractère a été frappé au clavier.

Tant que ce n'est pas vrai kbhit renvoie 0 (ceci signifie que la valeur de la fonction kbhit est
0).

Exemple
while(kbhit() == 0) /*tant qu'aucun caractère n'a été frappé exécuter la boucle*/
{
.....
}

Cette écriture est équivalente à :

while(!kbhit()); /* tant que kbhit est faux, exécuter la boucle */


{
.....
}

Ecrire un programme qui affiche le carré des entiers 1, 2, 3 ......, toutes les 500 ms tant
qu'aucun caractère n'a été frappé au clavier. Générer la temporisation à l’aide d’une boucle
for et d’un décompteur.

Cours langage C 2020- 2021 17


SEQUENCE IV : PROGRAMMATION MODULAIRE ET FONCTIONS

Comme tous les langages, le C permet de découper un programme en plusieurs parties


nommées souvent "modules". Cette programmation dite "modulaire" se justifie pour de
multiples raisons :
• un programme écrit d'un seul tenant devient difficile à comprendre dès qu'il dépasse
une ou deux pages de texte. Une écriture modulaire permet de le scinder en plusieurs
parties et de regrouper dans le "programme principal" les instructions en décrivant les
enchaînements. Chacune de ces parties peut d'ailleurs, si nécessaire, être décomposée
à son tour en modules plus élémentaires; ce processus de décomposition pouvant être
répété autant de fois que nécessaire, comme le préconisent les méthodes de
"programmation structurée".
• la programmation modulaire permet d'éviter des séquences d'instructions répétitives,
et cela d'autant plus que la notion d'argument" permet de "paramétrer" certains
modules.
• la programmation modulaire permet le partage d'outils communs qu'il suffit d'avoir
écrits et mis au point une seule fois. Cet aspect sera d'autant plus marqué que le C
autorise effectivement la compilation séparée de tels modules.

I. LA FONCTION : LA SEULE SORTE DE MODULE EXISTANT EN C

En C, il n'existe qu'une seule sorte de module, nommé fonction. Ce terme, quelque peu abusif,
pourrait laisser croire que les modules du C sont moins généraux que ceux des autres
langages. Or il n'en est rien, bien au contraire! Certes, la fonction pourra y être utilisée comme
dans d'autres langages, c'est-à-dire recevoir des arguments et fournir un résultat scalaire
qu'on utilisera dans une expression, comme, par exemple, dans :
y = sqrt(x)+3;

Mais, en C, la fonction pourra prendre des aspects différents, pouvant complètement


dénaturer l'idée qu'on se fait d'une fonction.
Par exemple :
• La valeur d'une fonction pourra très bien ne pas être utilisée; c'est ce qui se passe
fréquemment lorsque vous utilisez printf ou scanf. Bien entendu, cela n'a d'intérêt que
parce que de telles fonctions réalisent une action (ce qui, dans d'autres langages, serait
réservé aux sous-programmes ou procédures).
• Une fonction pourra ne fournir aucune valeur.
• Une fonction pourra fournir un résultat non scalaire (nous n'en parlerons toutefois
que dans le chapitre consacré aux structures.
• Une fonction pourra modifier les valeurs de certains de ses arguments (il vous faudra
toutefois attendre d'avoir étudié les pointeurs pour voir par quel mécanisme elle y
parviendra).

Ainsi, donc, malgré son nom, en C, la fonction pourra jouer un rôle aussi général que la
procédure ou le sous-programme des autres langages.

II. EXEMPLE DE DEFINITION ET D'UTILISATION D'UNE FONCTION

Nous vous proposons d'examiner tout d'abord un exemple simple de fonction correspondant
à l'idée usuelle que l'on se fait d'une fonction, c'est-à-dire recevant des arguments et
fournissant une valeur.

Cours langage C 2020- 2021 18


/***** le programme principal (fonction main) *****/ int
main(void)
{
double fexple (double m, int n, int p) ; /* déclaration de la fonction fexple */
double x = 1.5 ; double
y, z ;
int n = 3, p = 5, q = 10 ;

/* appel de fexple avec les arguments x, n et p */ y


= fexple (x, n, p) ;
printf ("Valeur de y : %f\n", y) ;

/* appel de fexple avec les arguments x+0.5, q et n-1 */


z = fexple (x+0.5, q, n-1) ; printf ("valeur de z : %f\n", z)
; return 0 ;
}

/*************** la fonction fexple ****************/ double


fexple (double x, int b, int c)
{
double val ; /* déclaration d'une variable "locale" à
fexple*/ val = x * x + b * x + c ; return val ;
}

Nous y trouvons tout d'abord, de façon désormais classique, un programme principal formé
l'un bloc. Mais, cette fois, à sa suite, apparaît la définition d'une fonction. Celle-ci possède
me structure voisine de la "fonction" main, à savoir un en-tête et un corps délimité par des
accolades ({ et }). Mais l'en-tête est plus élaboré que celui de la fonction main puisque, outre
le nom de la fonction (fexple), on y trouve une "liste d'arguments" (nom + type), ainsi que le
type de la valeur qui sera fournie par la fonction (on la nomme indifféremment résultat ‘’valeur
de la fonction", "valeur de retour"...):

Les noms des arguments n'ont d'importance qu'au sein du corps de la fonction. Ils servent à
décrire le travail que devra effectuer la fonction quand on l'appellera en lui fournissant trois
valeurs.
Si on s'intéresse au corps de la fonction, on y rencontre tout d'abord une déclaration :
double val ;

Celle-ci précise que, pour effectuer son travail, notre fonction a besoin d'une variable de type
double nommée « val ». On dit que « val » est une "variable locale" à la fonction fexple, de
même que les variables telles que n, p, y ... sont des variables locales à la fonction main (mais
comme jusqu'ici nous avions à faire à un programme constitué d'une seule fonction, cette
distinction n'était pas utile). Un peu plus loin, nous examinerons plus en détail cette notion
de variable locale et celle de "portée" qui s'y attache.

L'instruction suivante (val = x * x + b * x + c ;) de notre fonction fexple est une affectation


classique (faisant toutefois intervenir les valeurs des arguments x, n et p).

Enfin, l'instruction return val (return val ;) précise la valeur que fournira la fonction à la fin
de son travail.

Cours langage C 2020- 2021 19


En définitive, on peut dire que fexple est une fonction telle que fexple (x, b, c) fournisse la
valeur de l'expression x2 + bx + c.

Examinons maintenant la fonction main.


Vous constatez qu'on y trouve une déclaration :
double fexple (double m, int n , int p) ;

Elle sert à prévenir le compilateur que fexple est une fonction et elle lui précise le type de ses
arguments ainsi que celui de sa valeur de retour. Nous reviendrons plus loin en détail sur le
rôle d'une telle déclaration.

Quant à l'utilisation de notre fonction fexple au sein de la fonction main, elle est classique et
comparable à celle d'une fonction prédéfinie telle que scanf ou sqrt. Ici, nous nous sommes
contentés d'appeler notre fonction à deux reprises avec des arguments différents.

III. QUELQUES REGLES

III.1. Arguments muets et arguments effectifs

Les noms des arguments figurant dans l'en-tête de la fonction se nomment des "arguments
muets" (ou encore "arguments formels" ou "paramètres formels"). Leur rôle est de permettre,
au sein du corps de la fonction, de décrire ce qu'elle doit faire.

III.2. Instruction return

Voici quelques règles générales concernant cette instruction.


• L'instruction return peut mentionner n'importe quelle expression. Ainsi,
nous aurions pu définir la fonction fexple précédente d'une manière plus
simple :
double fexple (double x, int b, int c)
{
return (x * x + b * x + c) ;
}
• L'instruction return peut apparaître à plusieurs reprises dans une fonction,
comme dans cet autre exemple :
double absom (double u, double v)
{
double s ; s
= a + b ; if
(s>0) return (s) ;
else return (-s)
}

Notez bien que non seulement l'instruction return définit la valeur du résultat,
mais, en même temps, elle interrompt l'exécution de la fonction en revenant
dans la fonction qui l'a appelée.

Nous verrons qu'une fonction peut ne fournir aucune valeur : aucune


instruction return ne figurera alors dans sa définition. Dans ce cas (absence
d'instruction return), le retour est mis en place automatiquement par le
compilateur à la "fin" de la fonction.

Cours langage C 2020- 2021 20


Si le type de l'expression figurant dans return est différent du type du résultat tel qu'il
a été déclaré dans l'entête, le compilateur mettra automatiquement en place des
instructions de conversion.

Il est toujours possible de ne pas utiliser le résultat d'une fonction, même si elle en produit
un. C'est d'ailleurs ce que nous avons fait fréquemment avec printf ou scanf. Bien entendu,
cela n'a d'intérêt que si la fonction fait autre chose que de calculer un résultat. En revanche,
il est interdit d'utiliser la valeur d'une fonction ne fournissant pas de résultat (si certains
compilateurs l'acceptent, vous obtiendrez, lors de l'exécution, une valeur aléatoire!).
III.3. Cas des fonctions sans valeur de retour ou sans arguments

Quand une fonction ne renvoie pas de résultat, on le précise, à la fois dans l'en-tête et dans
sa déclaration, à l'aide du mot clé void.

Par exemple, voici l'en-tête d'une fonction recevant un argument de type int et ne fournissant
aucune valeur :
void sansval (int n)
et voici quelle serait sa déclaration :
void sansval (int) ;

Naturellement, la définition d'une telle fonction ne doit, en principe, contenir aucune


instruction return.

Quand une fonction ne reçoit aucun argument, on place le mot clé void (le même que
précédemment, mais avec une signification différente!) à la place de la liste d'arguments. Voici
l'en-tête d'une fonction ne recevant aucun argument et renvoyant une valeur de type float (il
pourrait s'agir, par exemple, d'une fonction fournissant un nombre aléatoire !): double tirage
(void)
Sa déclaration serait très voisine (elle ne diffère que par la présence du point-virgule!) :
double tirage (void) ;

Enfin, rien n'empêche de réaliser une fonction ne possédant ni arguments ni valeur de retour.
Dans ce cas, son entête sera de la forme :
void message (void)
et sa déclaration sera :
void message (void);

IV. EN C, LES ARGUMENTS SONT TRANSMIS PAR VALEUR

Nous avons déjà eu l'occasion de dire qu'en C les arguments d'une fonction étaient transmis
''par valeur''. Cependant, dans les exemples que nous avons rencontrés dans ce chapitre, les
conséquences et les limitations de ce mode de transmission n'apparaissaient guère.

Or voyez cet exemple : #include


<stdio.h>
int main(void)
{
void echange (int a, int b) ;
int n=10, p=20 ;

Cours langage C 2020- 2021 21


printf ("Avant appel: %d %d\n", n, p) ; echange
(n, p) ;
printf ("Après appel: %d %d", n, p) ; return
0;
}

void echange (int a, int b)


{
int c ;

printf ("Début échange : %d %d\n", a, b) ;


c=a;a=b;b=c;
printf ("Fin échange : °/d %d\n", a, b) ;
}

Résultat :
Avant appel : 10 20
Début échange : 10 20
Fin échange : 20 10
Après appel : 10 20

La fonction echange reçoit deux valeurs correspondant à ses deux arguments muets a et b.
Elle effectue un échange de ces deux valeurs. Mais, lorsque l'on est revenu dans le programme
principal, aucune trace de cet échange ne subsiste sur les arguments effectifs n et p.

En effet, lors de l'appel de echange, il y a eu transmission de la valeur des expressions n et


p. On peut dire que ces valeurs ont été recopiées "localement" dans la fonction echange dans
des emplacements nommés a et b. C'est effectivement sur ces copies qu'a travaillé la fonction
echange, de sorte que les valeurs des variables n et p n'ont, quant à elles, pas été modifiées.
C'est ce qui explique le résultat constaté.
Ce mode de transmission semble donc interdire a priori qu'une fonction produise une ou
plusieurs valeurs "en retour", autres que celle de la fonction elle-même. Or, il ne faut pas
oublier qu'en C tous les "modules" doivent être écrits sous forme de fonction. Autrement dit,
ce simple problème d'échange des valeurs de deux variables doit pouvoir se résoudre à l'aide
d'une fonction.

Nous verrons que ce problème possède plusieurs solutions, à savoir :


• Transmettre en argument la "valeur'' de l'adresse" d'une variable. La fonction pourra
éventuellement agir sur le "contenu" de cette adresse. C'est précisément ce que nous
faisons lorsque nous utilisons la fonction scanf. Cette technique sera examinée en
détail dans le chapitre consacré aux "pointeurs".
• Utiliser des "variables globales", comme nous le verrons dans le prochain paragraphe;
cette deuxième solution devra toutefois être réservée à des cas exceptionnels, compte
tenu des risques qu'elle présente.

Remarque
C'est bien parce que la transmission des arguments se fait "par valeur" que les arguments
effectifs peuvent prendre la forme d'une expression quelconque. Dans les langages où le seul
mode de transmission est celui "par adresse", les arguments effectifs ne peuvent être que
l'équivalent d'une lvalue.

V. RESUME SUR VARIABLES


Cours langage C 2020- 2021 22
On a donc vu qu'une variable globale est déclarée au début du programme et qu'elle est
connue de tout le programme. Les variables globales sont initialisées à 0 au début de
l'exécution du programme, sauf si on les initialise à une autre valeur.

On a vu aussi qu'une variable locale (déclarée au début d'une fonction ou de main()) n'est
connue que de cette fonction ou de main(). Une variable locale est encore appelée
automatique.

Les variables locales ne sont pas initialisées (sauf si on le fait dans le programme) et elles
perdent leur valeur à chaque appel à la fonction.

On peut allonger la durée de vie d'une variable locale en la déclarant static. Lors d'un nouvel
appel à la fonction, la variable garde la valeur obtenue à la fin de l'exécution précédente. Une
variable static est initialisée à 0 lors du premier appel à la fonction.

Exemple
int i; devient static int i;

Exercice VI.1
Quelle sera la valeur finale de n si i est déclarée comme variable static, puis comme variable
automatique ?

#include <stdio.h>
#include <conio.h>
#include "c:\bc5\courc_C\teach_c\chap7\chap7.h" /* fichier d'en-tete */ int
n; /* initialisee … 0 */ void calcul()
{
static int i; /* initialisee … 0 */ i++;
printf("i=%d\n",i); n
= n+i;
}
void main()
{
calcul(); printf("n=
%d\n",n); calcul();
printf("n= %d\n",n);
printf("\nPour sortir frapper une touche "); getch();
}

VI. FONCTIONS RECURSIVES

Le langage C autorise la récursivité des appels de fonctions.

Exemple
long fac(n)
{
if (n>1)
return (fac(n-1)*n); else
return (1);
}

Cours langage C 2020- 2021 23


Exercice VI.2
Ecrire une fonction, nommée f1, se contentant d'afficher ''bonjour'' (elle ne possédera aucun
argument ni valeur de retour).

Exercice VI.3
Ecrire une fonction, nommée f2 qui affiche "bonjour" un nombre de fois égal à la valeur reçue
en argument (int) et qui ne renvoie aucune valeur.

Exercice VI.4
Ecrire une fonction, nommée f3 qui fait la même chose que f2 mais qui, de plus, renvoie la
valeur (int) 0.
Exercice VI.5
Ecrire un petit programme appelant successivement chacune de ces trois fonctions, après les
avoir convenablement déclarées sous forme d'un prototype.

Exercice VI.6
Ecrire une fonction de prototype int puissance(int a, int b) qui calcule ab, a et b sont des
entiers (cette fonction n'existe pas en bibliothèque). La mettre en oeuvre dans main().
Actualiser le fichier d’en-tête en conséquence.

Exercice VI.7
Ecrire une fonction premier permettant de déterminer si un nombre entier donné est premier.

Exercice VI.8
Ecrire un programme donnant la liste des nombres premiers de 1 à N.

Cours langage C 2020- 2021 24


SEQUENCE V : TABLEAUX ET CHAINES DE CARACTERES

I. TABLEAUX

Les variables, telles que nous les avons vues, ne permettent de stocker qu'une seule donnée
à la fois. Or, pour de nombreuses données, comme cela est souvent le cas, des variables
distinctes seraient beaucoup trop lourdes à gérer. Heureusement, le langage C propose des
structures de données permettant de stocker l'ensemble de ces données dans une « variable
commune ». Ainsi, pour accéder à ces valeurs il suffit de parcourir la variable de type tableau.

On appelle tableau une variable composée de données de même type, stockée de manière
contiguë en mémoire (les unes à la suite des autres).

Un tableau est donc une suite de cases (espace mémoire) de même taille. La taille de chacune
des cases est conditionnée par le type de donnée que le tableau contient.
Les éléments du tableau peuvent être :
• des données de type simple : int, char, float, long, double ...
(la taille d'une case du tableau est alors le nombre d'octets sur lequel la donnée est
codée)
• des pointeurs (objets contenant une adresse mémoire)
• des tableaux
• des structures

Voici donc une manière de représenter un tableau :

Donnée Donnée Donnée Donnée … Donnée Donnée

Lorsque le tableau est composé de données de type simple, on parle de tableau


monodimensionnel (ou vecteur).

Lorsque celui-ci contient lui-même d'autres tableaux on parle alors de tableaux


multidimensionnels (aussi matrice ou table).

I.1. Tableaux unidimensionnels

I.1.1. Déclaration

Un tableau unidimensionnel est un tableau qui contient des éléments simples (des éléments
qui ne sont pas des tableaux). Un tableau unidimensionnel est donc une suite de « cases » de
même taille contenant des éléments d'un type donné (de la longueur de la case en quelque
sorte).

Un tableau contenant des entiers peut se représenter de la façon suivante :

int int int … int int

En langage C, la syntaxe de la définition d'un tableau unidimensionnel est la suivante :


type Nom_du_tableau [Nombre d'éléments] où
:

Cours langage C 2020- 2021 25


• type définit le type d'élément que contient le tableau (rappel : un tableau en langage
C est composé uniquement d'éléments de même type), c'est-à-dire qu'il définit la taille
d'une case du tableau en mémoire.
• Nom_du_tableau est le nom que l'on décide de donner au tableau, le nom du tableau
suit les mêmes règles qu'un nom de variable.
• Nombre d'éléments est un nombre entier qui détermine le nombre de cases que le
tableau doit comporter.

Voici par exemple la définition d'un tableau qui doit contenir 8 éléments de type char :
char tableau[8] ;

I.1.2. Calcul de la taille du tableau

Etant donné qu'un tableau est composé d'un nombre fixé d'éléments d'un type donné, la
taille d'un tableau est déterminée dès sa définition.

Pour connaître la taille d'un tableau, c'est-à-dire déterminer le nombre d'octets que celui-ci
occupe en mémoire, il y a deux possibilités :
• Calculer manuellement la taille du tableau : il suffit de multiplier la taille du type
d'élément par le nombre d'éléments qui composent le tableau.
• Utiliser l'opérateur sizeof() : l'opérateur sizeof() permet de retourner directement la
taille de l'élément qui lui est passé en argument, ainsi en lui passant un tableau
comme opérande, sizeof() est capable de vous retourner directement la taille de celuici.
Voici différents exemples de tableaux, et leurs tailles respectives :

Taille
char tableau[12] 1 * 12 = 12
int tableau2[10] 2 * 10 = 20
float tableau3[8] 4 * 8 = 32
double tableau4[15] 8 * 15 = 120

I.1.3. Accéder aux éléments

Pour accéder à un élément du tableau, le nom que l'on a donné à celui-ci ne suffit pas car il
comporte plusieurs éléments. Ainsi, on définit un nombre appelé indice (en anglais index)
qui, combiné avec le nom du tableau, permet de décrire exactement chaque élément.

Pour accéder à un élément du tableau, il suffit donc de donner le nom du tableau, suivi de
l'indice de l'élément entre crochets : Nom_du_tableau[indice] ;

Note
• L'indice du premier élément du tableau est 0 ;
• Un indice est toujours positif ;
• L'indice du dernier élément du tableau est égal au nombre d'éléments-1.

Ainsi, on accédera au 5ème élément du tableau en écrivant : Nom_du_tableau[4]


;

I.1.4. Manipuler les éléments

Cours langage C 2020- 2021 26


Un élément du tableau (repéré par le nom du tableau et son indice) peut être manipulé
exactement comme une variable, on peut donc effectuer des opérations avec (ou sur) des
éléments de tableau.

Définissons un tableau de 10 entiers :


int toto[10];

Pour affecter la valeur 6 au huitième élément on écrira :


toto[7] = 6;

Pour affecter au 10ème élément le résultat de l'addition des éléments 1 et 2, on écrira: Toto[9]
= Toto[0] + Toto[1];

I.1.5. Initialiser les éléments

Lorsque l'on définit un tableau, les valeurs des éléments qu'il contient ne sont pas définies,
il faut donc les initialiser, c'est-à-dire leur affecter une valeur.

Une méthode rustique consiste à affecter des valeurs aux éléments un par un :
toto[0] = toto[1] = toto[2] = 0;

L'intérêt de l'utilisation d'un tableau est alors bien maigre ...

Une manière plus élégante consiste à utiliser le fait que pour passer d'un élément du tableau
à l'élément suivant il suffit d'incrémenter son indice. Il est donc possible d'utiliser une boucle
qui va permettre d'initialiser successivement chacun des éléments grâce à un compteur qui
servira d'indice :

int toto[10];
int indice;

for (indice = 0; indice < 9; indice++)


{
toto[indice] = 0;
}

Cette méthode, aussi utile soit elle, n'a d'intérêt que lorsque les éléments du tableau doivent
être initialisés à une valeur unique ou une valeur logique (proportionnelle à l'indice par
exemple).

Pour initialiser un tableau avec des valeurs spécifiques, il est possible d'initialiser le tableau
à la définition en plaçant entre accolades les valeurs, séparées par des virgules :
int toto[10] = {1, 2, 6, 5, 2, 1, 9, 8, 1, 5};

Note
• Le nombre de valeurs entre accolades ne doit pas être supérieur au nombre d'éléments
du tableau.
• Les valeurs entre accolades doivent être des constantes (l'utilisation de variables
provoquera une erreur du compilateur).
• Si le nombre de valeurs entre accolades est inférieur au nombre d'éléments du tableau,
les derniers éléments sont initialisés à 0.
Cours langage C 2020- 2021 27
• Il doit y avoir au moins une valeur entre accolades.

Ainsi, l'instruction suivante permet d'initialiser tous les éléments du tableau à zéro :
int Toto[10] = {0};

Exercice IV.1
Saisir 10 réels, les ranger dans un tableau. Calculer et afficher la moyenne et l'écart-type.

I.1.6. Recherche du minimum et du maximum

Exercice IV.2
Ecrire un programme qui lit 10 nombres entiers dans un tableau avant d'en rechercher le
plus grand et le plus petit.
I.1.7. Tri des éléments d’un tableau

I.1.7.1. Méthode de tri par recherches successives de minimum (ou par


sélection)

Le principe de cette méthode très intuitive consiste à :


• chercher le minimum dans un sous-tableau (au départ le tableau complet contenant
les N valeurs non ordonnées)
• permuter ce minimum avec le premier élément du sous-tableau
• puis itérer ce traitement sur un nouveau sous-tableau de N-1 éléments (on ne tient
plus compte du premier élément qui est maintenant à sa place)

I.1.7.2. Méthode de tri par bulles (ou par propagation)

Le principe du tri à bulles est de comparer deux valeurs adjacentes et d'inverser leur position
si elles sont mal placées. Alors, qu'entend-t-on par "mal placé" ? C'est très simple et surtout,
c'est logique : si un premier nombre x est plus grand qu'un deuxième nombre y et que l'on
souhaite trier l'ensemble par ordre croissant, alors x et y sont mal placés et il faut les inverser.
Si, au contraire, x est plus petit que y, alors on ne fait rien et l'on compare y à z, l'élément
suivant. C'est donc itératif. Et on parcourt ainsi la liste jusqu'à ce qu'on ait réalisé n-1
passages (n représentant le nombre de valeurs à trier) ou jusqu'à ce qu'il n'y ait plus rien à
inverser lors du dernier passage.
Avec de la logique, on s'aperçoit qu'au premier passage, on place le plus grand élément de la
liste au bout du tableau, au bon emplacement. Pour le passage suivant, nous ne sommes
donc plus obligés de faire une comparaison avec le dernière élément ; et c'est bien plus
avantageux ainsi. Donc à chaque passage, le nombre de valeurs à comparer diminue de 1.

Pour illustrer cette rubrique, prenons la suite de nombres suivante : 6


035142

Nous voulons trier ces valeurs par ordre croissant. Commençons par le commencement !!!
Nous allons faire un premier passage.

6 0 3 5 1 4 2 // On compare 6 et 0 : on inverse
0 6 3 5 1 4 2 // On compare 6 et 3 : on inverse
0 3 6 5 1 4 2 // On compare 6 et 5 : on inverse
0 3 5 6 1 4 2 // On compare 6 et 1 : on inverse
0 3 5 1 6 4 2 // On compare 6 et 4 : on inverse

Cours langage C 2020- 2021 28


0 3 5 1 4 6 2 // On compare 6 et 2 : on inverse
0 3 5 1 4 2 6 // Nous avons terminé notre premier passage

La question à se poser est la suivante : Devons-nous continuer ? Oui, car lors du dernier
passage, au moins un échange a été effectué.

Nous allons donc refaire un passage mais en omettant la dernière case.

0 3 5 1 4 2 6 // On compare 0 et 3 : on laisse
0 3 5 1 4 2 6 // On compare 3 et 5 : on laisse
0 3 5 1 4 2 6 // On compare 5 et 1 : on inverse
0 3 1 5 4 2 6 // On compare 5 et 4 : on inverse
0 3 1 4 5 2 6 // On compare 5 et 2 : on inverse
0 3 1 4 2 5 6 // Nous avons terminé notre passage

Comme promis, on ne compare pas 5 et 6 car c'est inutile et ce qui est inutile est à éviter,
d'autant plus que cela ralenti l'algorithme.

Vous avez bien compris le principe ; on suppose. Alors, ce que nous vous proposons
maintenant, c'est juste de vous montrer les dernières étapes avant d'aboutir à la liste triée.

0 3 1 4 2 5 6 // On compare 0 et 3 : On laisse
0 3 1 4 2 5 6 // On compare 3 et 1 : On inverse
0 1 3 4 2 5 6 // On compare 3 et 4 : On laisse
0 1 3 4 2 5 6 // On compare 4 et 2 : On inverse
0 1 3 2 4 5 6 // Nous avons terminé notre passage

0 1 3 2 4 5 6 // On compare 0 et 1 : On laisse
0 1 3 2 4 5 6 // On compare 1 et 3 : On laisse
0 1 3 2 4 5 6 // On compare 3 et 2 : On inverse
0 1 2 3 4 5 6 // Nous avons terminé notre passage

0 1 2 3 4 5 6 // On compare 0 et 1 : On laisse
0 1 2 3 4 5 6 // On compare 1 et 2 : On laisse
0 1 2 3 4 5 6 // Nous avons terminé notre passage

À ce moment-là, l'algorithme s'arrête car il n'y a plus eu d'échange lors du dernier passage et
nous retrouvons notre liste belle et bien triée !

Exercice IV.3
Ecrire un programme permettant de trier par ordre croissant les valeurs entières d'un tableau
de taille quelconque. Le tri pourra se faire par réarrangement des valeurs au sein du tableau
lui-même.

I.2. Tableaux multidimensionnels

Les tableaux multidimensionnels sont des tableaux qui contiennent des tableaux.

Par exemple le tableau bidimensionnel (3 lignes, 4 colonnes) suivant, est en fait un tableau
comportant 3 éléments, chacun d'entre eux étant un tableau de 4 éléments:

Cours langage C 2020- 2021 29


est stocké en mémoire de la manière suivante :

I.2.1. Définition

Un tableau multidimensionnel se définit de la manière suivante :


type Nom_du_tableau [a1][a2][a3] ... [aN]

Chaque élément entre crochets désigne le nombre d'éléments dans chaque dimension
Le nombre de dimensions n'est pas limité.

Un tableau d'entiers positifs à deux dimensions (3 lignes, 4 colonnes) se définira avec la


syntaxe suivante :
int Tableau [3][4] ;

On peut représenter un tel tableau de la manière suivante :

Il va de soi que cette représentation est arbitraire, car elle suppose que le premier indice est
l'indice de ligne et le second est l'indice de colonne.

On aurait tout aussi bien pu représenter le tableau de la manière suivante :

Cours langage C 2020- 2021 30


On utilise toutefois généralement la première représentation, car elle correspond mieux à la
façon selon laquelle le tableau est stocké en mémoire.

I.2.2. Initialiser les éléments

L'initialisation d'un tableau multidimensionnel se fait à peu près de la même façon que pour
les tableaux unidimensionnels. Il y a donc plusieurs façons d'initialiser un tableau
multidimensionnel :

• Initialisation individuelle de chaque élément :


Nom_du_tableau [0][0] = 2;
Nom_du_tableau [0][1] = 3;
...

• Initialisation grâce à des boucles :


Il faut faire des boucles imbriquées correspondant chacune à un indice d'une
dimension. Par exemple les éléments de tableau[3][4] pourront être initialisés à 0 par
les instructions suivantes :
int i, j;
for (i=0; i<2; i++)
{
for (j=0; j<3; j++)
{
Tableau[i][j] = 0;
}
}

Initialisation à la définition : type Nom_du_tableau


[Taille1][Taille2]...[TailleN] = {a1, a2, ... aN};

II. CHAINES DE CARACTERES

En langage C, les chaînes de caractères sont des tableaux de caractères. Leur manipulation
est donc analogue à celle d'un tableau à une dimension.

II.1. Déclaration

char nom[dim]; ou bien char *nom;


nom = (char*)malloc(dim);
Exemple
char texte[dim]; ou bien char *texte;
texte = (char*)malloc(10);

Le compilateur réserve (dim-1) places en mémoire pour la chaîne de caractères. En effet, il


ajoute toujours le caractère NUL ('\0') à la fin de la chaîne en mémoire.

II.2. Affichage à l'écran

On peut utiliser la fonction printf et le format %s:


char texte[10] = "BONJOUR";
Cours langage C 2020- 2021 31
printf("VOICI LE TEXTE: %s\n", texte);

On utilisera si possible la fonction puts non formatée:


puts(texte); est équivalent à printf("%s\n",texte);

II.3. Saisie

On peut utiliser la fonction scanf et le format %s. Une chaîne étant un pointeur, on n'écrit
pas le symbole &. On utilisera de préférence la fonction gets non formatée.

char texte[10];
printf("ENTRER UN TEXTE: ");
scanf("%s", texte); est équivalent à gets(texte);

Remarque
scanf ne permet pas la saisie d'une chaîne comportant des espaces: les caractères saisis à
partir de l'espace ne sont pas pris en compte (l'espace est un délimiteur au même titre que
LF) mais rangés dans le tampon d'entrée. Pour saisir une chaîne de type "il fait beau", il faut
utiliser gets.

A l'issue de la saisie d'une chaîne de caractères, le compilateur ajoute '\0' en mémoire après
le dernier caractère.

Exercice IV.4
Saisir une chaîne de caractères, afficher les éléments de la chaîne et leur adresse (y compris
le dernier caractère '\0').

Exercice IV.5
Saisir une chaîne de caractères. Afficher le nombre de e et d'espaces de cette chaîne.

II.4. Fonctions permettant la manipulation des chaînes

Les bibliothèques fournies avec les compilateurs contiennent de nombreuses fonctions de


traitement des chaînes de caractères. En BORLAND C++, elles appartiennent aux
bibliothèques string.h ou stdlib.h.

En voici quelques exemples :

Générales (string.h):
• void strcat(chaine1, chaine2) concatène les 2 chaînes,
résultat dans chaine1, renvoie l'adresse de
chaine1autrement dit, recopie la seconde chaîne chaine2 à la suite de la
première chaine1.
• int strlen(chaine) renvoie la longueur de la chaine ('\0' non comptabilisé).
• void strrev(chaine) inverse la chaîne et, renvoie l'adresse de la chaine
inversée.

Comparaison (string.h):
• int strcmp(chaine1, chaine2) renvoie un nombre :

Cours langage C 2020- 2021 32


o positif si la chaîne1 est supérieure à la chaine2 (au sens de
l'ordre alphabétique)
o négatif si la chaîne1 est inférieure à la chaîne2 o nul si les
chaînes sont identiques.
• int stricmp(chaine1, chaine2) travaille comme
strcmp mais sans tenir compte de la différence
entre majuscules et minuscules.

Copie (string.h):
• void strcpy(chaine1, chaine2) recopie chaine2
dans chaine1 et renvoie l'adresse de chaîne1.

Recopie (string.h):
Ces fonctions renvoient l'adresse de l'information recherchée en cas de succès, sinon le
pointeur NULL (c'est à dire le pointeur de valeur 0 ou encore le pointeur faux).
• void strchr(chaine, caractère) recherche dans
chaine, la première position où apparait le
caractère mentionné.
• void strrchr(chaine, caractère) opère de même
mais en partant de la fin de chaine;
• void strstr(chaîne, sous-chaîne) recherche la
sous-chaine dans la chaîne.

Conversions (stdlib.h):
• int atoi(char *chaîne) convertit la chaîne en entier
• float atof(char *chaine) convertit la chaîne en réel

Exemple printf("ENTRER UN
TEXTE: "); gets(texte); n =
atoi(texte) ;
printf("%d", n); /* affiche 123 si texte vaut "123" */
/* affiche 0 si texte vaut "bonjour" */

• void itoa(int n, char *chaîne, int base) convertit un


entier en chaîne:
où base = base dans laquelle est exprimé le nombre ; cette fonction renvoie l'adresse
de la chaîne.

Exemple itoa(12, texte,10); texte


vaut "12"

Pour tous ces exemples, la notation void* signifie que la fonction renvoie un pointeur (l'adresse
de l'information recherchée), mais que ce pointeur n'est pas typé. On peut ensuite le typer à
l'aide de l'opérateur cast.

Exemple
int *adr;
char texte[10] = "BONJOUR";
adr = (int*)strchr(texte,'O');

Exercice IV.6
Cours langage C 2020- 2021 33
L'utilisateur saisit le nom d'un fichier. Le programme vérifie que celui-ci possède l'extension
.PAS

Exercice IV.7
Ecrire un programme déterminant le nombre de lettres e (minuscules) présentes dans un
texte de moins d'une ligne (supposée ne pas dépasser 132 caractères) fourni au clavier.

Exercice IV.8
Ecrire un programme qui supprime toutes les lettres e (minuscules) d'un texte de moins d'une
ligne (supposée ne pas dépasser 132 caractères) fourni au clavier. Le texte ainsi modifié sera
affiché à l’écran.

Exercice IV.9
Ecrire un programme qui lit au clavier un mot (d'au plus 30 caractères) et qui l'affiche "à
l'envers ''.

Exercice IV.10
Ecrire un programme qui lit un verbe du premier groupe et qui en affiche la conjugaison au
présent de l'indicatif, sous la forme :
je chante tu
chantes
il chante nous
chantons vous
chantez
ils chantent

Le programme devra vérifier que le mot fourni se termine bien par ''er". On supposera qu'il
ne peut comporter plus de 26 lettres et qu'il s'agit d'un verbe régulier. Autrement dit, on
admettra que l'utilisateur ne fournira pas un verbe tel que "manger" (le programme afficherait
alors : ''nous mangons" !).

Cours langage C 2020- 2021 34


SEQUENCE VI : STRUCTURES

Nous avons déjà vu comment le tableau permettait de désigner sous un seul nom un
ensemble de valeurs de mêm9e type, chacune d'entre elles étant repérée par un indice.

La structure, quant à elle, va nous permettre de désigner sous un seul nom un ensemble de
valeurs pouvant être de types différents. L'accès à chaque élément de la structure (nommé
champ) se fera, cette fois, non plus par une indication de position, mais par son nom au sein
de la structure.

I. DECLARATION D'UNE STRUCTURE

Voyez tout d'abord cette déclaration :

struct enreg
{
int numero;
int qte;
double prix;
}

Celle-ci définit un modèle de structure mais ne réserve pas de variables correspondant à cette
structure. Ce modèle s'appelle ici enreg et il précise le nom et le type de chacun des "champs"
constituant la structure (numero, qte et prix).

Une fois un tel modèle défini, nous pouvons déclarer des "variables" du type correspondant
(souvent, nous parlerons de structure pour désigner une variable dont le type est un modèle
de structure).

Par exemple struct enreg


art1 ;
réserve un emplacement nommé artl ''de type enreg'' destiné à contenir deux entiers et un
double.

De manière semblable struct


enreg art1, art2 ;
réserverait deux emplacements artl et art2 du type enreg.

II. UTILISATION D'UNE STRUCTURE

En C, comme en Pascal, il est possible d'utiliser une structure de deux manières :


en travaillant individuellement sur chacun de ses champs,
en travaillant de manière "globale'' sur l'ensemble de la structure.

II.1. Utilisation des champs d'une structure

Chaque champ d'une structure peut être manipulé comme n'importe quelle variable du type
correspondant. La désignation d'un champ se note en faisant suivre le nom de la variable
structure de l'opérateur ''point'' (.) suivi du nom de champ tel qu'il a été défini dans le modèle
(le nom de modèle lui-même n'intervenant d'ailleurs pas).

Cours langage C 2020- 2021 35


Voici quelques exemples utilisant le modèle enreg et les variables artl et art2 déclarées de ce
type.

artl.numero = 15 ;
// affecte la valeur 15 au champ numero de la structure artl.
printf ("%e", artl.prix) ; affiche, suivant le code format %e, la valeur du champ prix de
la structure artl. scanf ("%e", &art2.prix) ;
// lit, suivant le code format %e, une valeur qui sera affectée au champ prix
de la structure art2. Notez bien la présence de l'opérateur &. artl.numero++ ;
// incrémente de 1 la valeur du champ numero de la structure artl.

Remarque
La priorité de l'opérateur ''.'' est très élevée, de sorte qu'aucune des expressions ci-dessus ne
nécessite de parenthèses.

II.2. Utilisation globale d'une structure

Il est possible d'affecter à une structure le contenu d'une structure définie à partir du même
modèle. Par exemple, si les structures artl et art2 ont été déclarées suivant le modèle enreg
défini précédemment, nous pourrons écrire :
artl = art2;

Une telle affectation globale remplace avantageusement :


art1.numero = art2.numero ;
art1.qte = art2.qte ;
art1.prix = art2.prix ;

Notez bien qu'une affectation globale n'est possible que si les structures ont été définies avec
le même nom de modèle; en particulier, elle sera impossible avec des variables ayant une
structure analogue mais définies sous deux noms différents.

L'opérateur d'affectation et, comme nous le verrons un peu plus loin, l'opérateur d'adresse &
sont les seuls opérateurs s'appliquant à une structure (de manière globale).

Remarque
L'affectation globale n'est pas possible entre tableaux. Elle l'est, par contre, entre structures.
Aussi est-il possible, en créant artificiellement une structure contenant un seul champ qui
est un tableau, de réaliser une affectation globale entre tableaux.

II.3 Initialisations de structures

On retrouve pour les structures les règles d'initialisation qui sont en vigueur pour tous les
types de variables, à savoir :
• En l'absence d'initialisation explicite, les structures de classe ''statique" sont, par
défaut, initialisées à zéro; celles possédant la classe "automatique'' ne sont pas
initialisées par défaut (elles contiendront donc des valeurs aléatoires).
• Il est possible d'initialiser explicitement une structure lors de sa déclaration. On ne
peut toutefois employer que des constantes ou des expressions constantes et cela aussi
bien pour les structures statiques que pour les structures automatiques (alors que,
pour les variables scalaires automatiques, il était possible d'employer une expression
quelconque):

Cours langage C 2020- 2021 36


Voici un exemple d'initialisation de notre structure artl, au moment de sa déclaration
:
struct enreg art1 = {100, 285, 2000 };
Vous voyez que la description des différents champs se présente sous la forme d'une
liste de valeurs séparées par des virgules, chaque valeur étant une constante ayant le
type du champ correspondant. Là encore, il est possible d'omettre certaines valeurs.

III. POUR SIMPLIFIER LA DÉCLARATION DE TYPES DÉFINIR DES SYNONYMES


AVEC TYPEDEF

La déclaration typedef permet de définir ce que l'on nomme en langage C des types
synonymes. A priori, elle s'applique à tous les types et pas seulement aux structures. C'est
pourquoi nous commencerons par l'introduire sur quelques exemples avant de montrer
l'usage que l'on peut en faire avec les structures.

III.1. Exemples d'utilisation de typedef

La déclaration :
typedef int entier;
signifie que entier est ''synonyme'' de int, de sorte que les déclarations suivantes sont
équivalentes:
int n, p ; entier n, p;

III.2. Application aux structures

En faisant usage de typedef, les déclarations des structures artl et art2 peuvent être réalisées
comme suit:
struct enreg
{
int numero ;
int qte ;
double prix ;
}
typedef struct enreg s_enreg ; s_enreg
art1, art2

ou encore, plus simplement :

typedef struct
{
int numero ;
int qte ;
float prix ;
}
s_enreg ;
s_enreg art1, art2 ;

Par la suite, nous ne ferons appel qu'occasionnellement à typedef, afin de ne pas vous
enfermer dans un style de notations que vous ne retrouverez pas nécessairement dans les
programmes que vous serez amené à utiliser.

Cours langage C 2020- 2021 37


IV. IMBRICATION DE STRUCTURES

Dans nos exemples d'introduction des structures, nous nous sommes limités à une structure
simple ne comportant que trois champs d'un type de base. Mais chacun des champs d'une
structure peut être d'un type absolument quelconque : pointeur, tableau, structure,... De
même, un tableau peut être constitué d'éléments qui sont eux-mêmes des structures. Voyons
ici quelques situations classiques.

IV.1. Structure comportant des tableaux

Soit la déclaration suivante :

struct personne
{
char nom[30] ;
char prenom [20] ;
double heures [31] ;
}
employe, courant ;

Celle-ci réserve les emplacements pour deux structures nommées employe et courant. Ces
dernières comportent trois champs :
• nom qui est un tableau de 30 caractères,
• prenom qui est un tableau de 20 caractères, heures qui est un tableau de 31
flottants.

On peut, par exemple, imaginer que ces structures permettent de conserver pour un
employé d'une entreprise les informations suivantes : prenom,
• nombre d'heures de travail effectuées pendant chacun des jours du mois courant.

La notation :
employe.heures[4]
désigne le cinquième élément du tableau heures de la structure employe. Il s'agit d'un élément
de type float.

Notez que, malgré les priorités identiques des opérateurs . et [ ], leur associativité de gauche
à droite évite l'emploi de parenthèses.
De même:
employe.nom[0]
représente le premier caractère du champ nom de la structure employe.
Par ailleurs :
&courant.heures[4] représente l'adresse du cinquième élément du tableau
heures de la structure courant.

Notez que, la priorité de l'opérateur & étant inférieure à celle des deux autres, les parenthèses
ne sont, 1à encore, pas nécessaires.

Enfin :
courant.nom
représente le champ nom de la structure courant, c'est-à-dire plus précisément l'adresse de
ce tableau.
Cours langage C 2020- 2021 38
A titre indicatif, voici un exemple d'initialisation d'une structure de type personne lors de sa
déclaration :
struct personne emp = { "Dupont", "Jules", { 8, 7, 8, 6, 8, 0, 0, 8}}

IV.2. Tableaux de structures

Voyez ces déclarations :

struct point
{
char nom ;
int x ; int y
;
};
struct point courbe[50];
La structure point pourrait, par exemple, servir à représenter un point d'un plan, point qui
serait défini par son nom (caractère) et ses deux coordonnées.

La structure courbe, quant à elle, pourrait servir à représenter un ensemble de 50 points du


type ainsi défini.

Notez bien que point est un nom de modèle de structure, tandis que courbe représente
effectivement un ''objet'' de type "tableau de 50 éléments du type point". Si i est un entier, la
notation : courbe[i].nom représente le nom du point de rang i du tableau courbe. Il s'agit donc
d'une valeur de type char. Notez bien que la notation :
courbe.nom[i] pas de sens.

De même, la notation :
courbe[i].x
représente la valeur du champ x de l’élément de rang i du tableau courbe.

V. A PROPOS DE LA PORTEE DU MODELE DE STRUCTURE

A l'image de ce qui se produit pour les identificateurs de variables, la "portée" d'un modèle de
structure dépend de l'emplacement de sa déclaration :
• si elle se situe au sein d'une fonction (y compris, la "fonction main"), elle n'est
accessible que depuis cette fonction,
• si elle se situe en dehors d'une fonction, elle est accessible de toute la partie du fichier
source qui suit sa déclaration; elle peut ainsi être utilisée par plusieurs fonctions.

Il est néanmoins toujours possible de placer un certain nombre de déclarations de modèles


de structures dans un fichier séparé que l'on incorpore par #include à tous les fichiers source
où l'on en a besoin. Cette méthode évite la duplication des déclarations identiques avec les
risques d'erreurs qui lui sont inhérents.

VI. TRANSMISSION D'UNE STRUCTURE EN ARGUMENT D'UNE FONCTION

Jusqu'ici, nous avons vu qu'en C la transmission des arguments se fait "par valeur", ce qui
implique une recopie de l'information transmise à la fonction. Par ailleurs, il est toujours
possible de transmettre la "valeur d'un pointeur" sur une variable, auquel cas la fonction

Cours langage C 2020- 2021 39


peut, si besoin est, en modifier la valeur. Ces remarques s'appliquent également aux
structures (notez qu'il n'en allait pas de même pour un tableau, dans la mesure où la seule
chose qu'on puisse transmettre dans ce cas soit la valeur de l'adresse de ce tableau).

VI.1. Transmission de la valeur d'une structure

Aucun problème particulier ne se pose. Il s'agit simplement d'appliquer ce que nous


connaissons déjà.

Voici un exemple simple :

#include <stdio.h>
struct enreg
{
int a;
double b;
}

int main(void)
{
struct enreg x;

void fct(struct enreg y);

x.a = 1; x.b = 12.5; printf ("\n Avant appel


fct: %d %e", x.a, x.b); fct (x);
printf ("\n Au retour dans main: %d %e", x.a, x.b);
}

void fct (struct enreg s)


{
s.a = 0;
s.b=1; printf ("\n Dans fct: %d %e",
s.a, s.b);
}

Résultat :
Avant appel fct : 1 1.25000e+01
Dans fct : 0 1.00000e+00
Au retour dans main : 1 1.25000e+01

Naturellement, les valeurs de la structure x sont recopiées localement dans la fonction fct
lors de son appel; les modifications de s au sein de fct n'ont aucune incidence sur les valeurs
de x.

VI.2. Transmission de l'adresse d'une structure: l'opérateur ->

Cherchons à modifier notre précédent programme pour que la fonction fct reçoive
effectivement l'adresse d'une structure et non plus sa valeur. L'appel de fct devra donc se
présenter sous la forme :
fct (&x) ;

Cours langage C 2020- 2021 40


Cela signifie que son en-tête sera de la forme :
void fct (struct enreg * ads) ;

Comme vous le constatez, le problème se pose alors d'accéder, au sein de la définition de fct,
à chacun des champs de la structure d'adresse ads. L'opérateur "." ne convient plus, car il
suppose comme premier opérande un nom de structure et non une adresse. Deux solutions
s'offrent alors à vous :
• adopter une notation telle que (*ads).a ou (*ads).b pour désigner les champs de la
structure d'adresse ads.
• faire appel à un nouvel opérateur noté -> , lequel permet d'accéder aux différents
champs d'une structure à partir de son adresse de début. Ainsi, au sein de fct, la
notation ads -> b désignera le second champ de la structure reçue en argument; elle
sera équivalente à (*ads).b.

Voici ce que pourrait devenir notre précédent exemple en employant l'opérateur -> :

#include <stdio.h>
struct enreg
{
int a;
float b;
}
int main(void)
{
struct enreg x;
void fct (struct enreg *);

x.a = 1; x.b = 12.5;


printf ("\navant appel fct: %d %e",x.a,x.b); fct
(&x);
printf ("\n Au retour dans main : %d %e", x.a, x.b); retunr
0;
}
void fct (struct enreg * ads)
{
ads->a = 0; ads->b
= 1;
printf ("\n Dans fct: %d %e", ads->a, ads->b);
}

Résultat :
Aavant appel fct: 1 1.25000e+01
Dans fct: 0 1.00000e+00
Au retour dans main: 0 1.00000e+00

Exercice VII.1
Ecrire un programme qui :
lit au clavier des informations dans un tableau de structures du type point défini comme
suit: struct point
{
Cours langage C 2020- 2021 41
int num;
double x ;
double y;
}
Le nombre d'éléments du tableau sera fixé par une instruction #define.
affiche à l'écran l'ensemble des informations précédentes.

Exercice VII.2
Réaliser la même chose que dans l'exercice précédent, mais en prévoyant, cette fois, une
fonction pour la lecture des informations et une fonction pour l'affichage.

Cours langage C 2020- 2021 42

Vous aimerez peut-être aussi