Académique Documents
Professionnel Documents
Culture Documents
LANGAGE C
DEFITECH - TOGO
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.
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++.
Le langage C distingue les minuscules, des majuscules. Les mots réservés du langage
C doivent être écrits en minuscules.
II.1. Entiers
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.
II.2. Réels
III. INITIALISATIONS
Cette règle s'applique à tous les nombres, char, int, float ...
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 */ }
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 */ }
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.
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.
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.
Exemples
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
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.
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;
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.
Exemple
#define PI 3.14159
void main()
{
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.
Le langage C permet d'effectuer des opérations de conversion de type : on utilise pour cela
l'opérateur de "cast" ().
I. LA FONCTION GETCH
Les parenthèses vides de getch() signifient qu'aucun paramètre n'est passé à cette fonction
par le programme.
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 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();
}
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
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.
I. STRUCTURES CONDITIONNELLES
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 */
............; }
Syntaxe en C : if
(expression)
{
............; /* bloc d'instructions */
............;
}
Remarque
Les {} ne sont pas nécessaires lorsque les blocs ne comportent qu'une seule instruction.
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.
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");
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.
}
Exemple
Cette instruction est commode pour fabriquer des "menus"
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 */
}
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".
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 */
............;
}
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.
Il s'agit de l'instruction :
Répéter
{
BLOC D'INSTRUCTIONS
}
tant que (expression vraie)
Remarque
Les {} ne sont pas nécessaires lorsque le bloc ne comporte qu'une seule instruction.
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)
{ ....... } { ....... }
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.
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*/
{
.....
}
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.
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;
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.
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.
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.
Enfin, l'instruction return val (return val ;) précise la valeur que fournira la fonction à la fin
de son travail.
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.
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.
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.
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) ;
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);
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.
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.
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.
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();
}
Exemple
long fac(n)
{
if (n>1)
return (fac(n-1)*n); else
return (1);
}
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.
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
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).
Voici par exemple la définition d'un tableau qui doit contenir 8 éléments de type char :
char tableau[8] ;
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
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.
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];
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;
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;
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.
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
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.
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
La question à se poser est la suivante : Devons-nous continuer ? Oui, car lors du dernier
passage, au moins un échange a été effectué.
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.
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:
I.2.1. Définition
Chaque élément entre crochets désigne le nombre d'éléments dans chaque dimension
Le nombre de dimensions n'est pas limité.
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.
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 :
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
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.
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 :
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" */
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" !).
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.
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).
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).
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.
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;
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.
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):
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.
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;
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
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.
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.
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}}
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.
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.
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.
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
#include <stdio.h>
struct enreg
{
int a;
double b;
}
int main(void)
{
struct enreg x;
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.
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) ;
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 *);
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.