Cours ebook : la pratique du langage C de CEPTA
1.1 L’analyse une étape indispensable pour un projet important.
C’est la phase la plus importante lors de la réalisation d’un programme informatique. Un projet important qui ne commence pas par une analyse sérieuse est destiné à de grandes difficultés ou à l’échec. Les coûts de développements de logiciels sont très élevés, le temps investi dans cette analyse permettra de diminuer le temps de développement global.
1.2 L’éditeur
Crée les fichiers sources *.c et les fichiers header *.h
1.3 Le précompilateur
Il transforme le code source C en code source C pur en exécutant les directives du précompilateur.
1.4 Le compilateur
Il transforme le code C pur en code objet relogeable *.obj 1.5 Le linker (Éditeur de lien)
Il relie les fichiers objets et les librairies utilisateurs et systèmes pour créer le fichier exécutable *.exe
1.6 Le debugger
Permet de tester le programme. 1.7 Les commentaires
Il existe deux méthodes pour écrire des commentaires en C. Le commentaire délimité par /* indiquant le début et */ indiquant la fin du commentaire qui peut être sur plusieurs lignes.
/* J’affirme que cette ligne est un commentaire pouvant se répartir sur plusieurs lignes.*/
Il est aussi possible d’avoir un commentaire se terminant par la fin de ligne. (attention, ne fonctionne par sur tous les compilateurs).
// Ce commentaire se termine à la fin de la ligne.
// Il ne fonctionne pas sur tous les compilateurs (issu du C++)
1.7.1 La pertinence des commentaires
Documenter un programme est une nécessité. Cette documentation doit permettre de comprendre pour pouvoir exécuter des modifications et des corrections de bugs éventuels.
1.7.2 En-tête du programme.
Le programme doit commencer par un en-tête, il doit comprendre au minimum le nom de l’entreprise, l’auteur du programme, le nom du fichier, la description du projet, la date de création, les versions avec une description des modifications ainsi que la date.
Nom de fichier : Monfichier
Auteur : XXX
Description : Programme de gestion de cartes à puces
Version : 1.0 10 jan 00 JMC Version de base.
Version : 1.1 20 jan 00 JMC
Correction du bug 123 (voir rapport x)
1.8 Premier programme
Fichier: PrPrg.cpp
Auteur: JMC
Description: Premier exemple de programme
Version: 1.0
Modifications: pas de modification
#include // accès à la librairie standard input/output
programme principal
void main (void) // fonction main obligatoire
{ // début de bloc
unsigned char MonChar; // Variable locale de type caractère
printf("Voulez vous afficher Hello ? [O/N] \n");
MonChar = getchar(); // lecture d'un caractère du clavier
if(MonChar == 'O') // test si le caractère lu est O
printf("Hello"); // Si O => Affichage à écran
} // fin de bloc
end
1.9 Règles de présentation
L'écriture en C est très compacte. Il faut tout faire pour rendre le programme le plus lisible possible. Pour faciliter la compréhension des logiciels, beaucoup d'entreprises éditent des règles de codage interne. Voici quelques règles de présentation des programmes.
2 Les variables
Les variables permettent de stocker les données en RAM. Par défaut les variables sont signées. Pour gagner un bit supplémentaire on peut déclarer des variables non signées en ajoutant le préfixe unsigned.
Tous les types de variables permettent d’effectuer des calculs ou des opérations logiques.
Attention : Les tailles des variables peuvent changer en fonction de l’implémentation Il faut vérifier la taille dans la documentation du compilateur.
2.1 Les types de variables
2.1.1 Le type char
Elle peut contenir au minimum 1 caractère du système.
char peut être utilisé pour des opérations arithmétiques.
Si les caractères sont en ASCII => char est un byte (octet).
char ma_data; // ou signed char)
unsigned char ma_ data; //
2.1.2 Le type int
Elle correspond à la taille d’un mot machine, peut être de 8, 16. 32, 64 bits etc.
int ma_data; // ou signed int
unsigned int ma_ data; //
2.1.3 Le type short
Il est plus petit ou égal à un integer short <= int.
short ma_data; // ou short int ou signed short int
unsigned short ma_data; // ou unsigned short int
2.1.4 Le type long.
il est plus grand ou égal à integer long >= int.
long ma_data; // ou long int ou signed short int
unsigned long ma_data; // ou unsigned long int
...
4 Les chaînes de caractères (strings)
Les chaînes de caractères (ASCII ou autres) sont entourées de deux caractères ″.
″ceci est une chaîne de caractères″
4.1 Les caractères non imprimables :
Il peut être nécessaire d’incorporer des caractères non imprimables dans une chaîne de caractères. Ces caractères non imprimables font partie des caractères de contrôle. ils permettent principalement des manipulations du curseur.
\n new line nouvelle ligne
\t horizontal tabulation tabulation horizontale
\v vertical tabulation tabulation verticale
\b back space back space
\r carriage return retour du chariot
\f form feed saut de page
\a audible alert alarme sonore
\000 caractère octal 0 nul ASCII exprimé en octal
\x00 caractère hexadécimal nul ASCII exprimé en hexa
4.2 La représentation des caractères réservés à la syntaxe des strings.
Un certain nombre de caractères sont utilisés pour la syntaxe des chaînes de caractères, pour cette raison ils ne peuvent pas être utilisés telle quel dans des chaînes de caractères, ils sont remplacés par une séquence de caractères.
4.3 Utilisation des caractères de contrôle
″c’est la ligne 1 \n c’est la ligne 2 \n c’est la ligne 3″
Le string s’affiche sur 3 lignes.
4.4 Le string de 1 caractère et le caractère. Il ne faut pas confondre :
‘A’ // est un caractère ASCII A
”A” // représente deux caractères A ASCII et la fin de string NULL ASCII
...
5.2 La fonction de librairie scanf()
Cette fonction fait partie de la libraire stdio (standard input output) Elle permet la saisie de données depuis le clavier. Exemple :
scanf(″%d″,&i) // La variable i est la destination
// %d indique une variable entière signée
On passe à cette fonction le type de donnée et l’adresse de la variable de destination. 5.2.1 Acquisition en une fois de plusieurs variables.
scanf(″%d %d″,&i,&j) // On saisit plusieurs variables en une fois.
L’utilisateur répond en séparant les variables a acquérir par des caractères espace ou par des caractères carriage return.
La fin de l’acquisition étant toujours signalée par un carriage return.
5.2.2 Imposition du format maximum
scanf(″%3d″,&i) // On impose un format maximum pour la variable
Dans ce cas l’acquisition s’interrompt lorsque le nombre de maximum de caractères est atteint. 5.2.3 Caractère invalide dans un format :
La réception d’un caractère invalide termine l’acquisition de la donnée. Par exemple si on entre une variable alphabétique dans un entier.
5.2.4 La valeur de retour de scanf()
int compte ;
compte = scanf(″%3d″,&i) // compte = nombre de champs mis à jour
Le retour de la fonction scanf indique le nombre de champs correctement mis à jour.
compte = scanf(″%3d %3d %3d ″,&i,&j,&k); //compte = nbr de champs
// saisis correctement
Dans ce cas valeur de compte sera de 3 seulement si les 3 champs ont été correctement remplis. Il est donc possible de contrôler si tous les champs à acquérir ont été correctement remplis.
6 Quelques fonctions de librairie 6.1 La fonction getchar()
Cette fonction permet d’acquérir un seul caractère ASCII au clavier. Elle ne requière aucun paramètre. Le caractère de retour est le code de la touche qui doit être mis dans une variable de type char. Exemple :
char c; //
c = getchar(); // keyboard input
6.2 La fonction de librairie putchar()
Cette fonction permet d’afficher un seul caractère ASCII sur l’écran (imprimante). Le paramètre d’entrée est le nom de la variable contenant le caractère à afficher. Exemple :
char c ;
c = ‘A’; // A ASCII dans c
putchar(c); // A ASCII à l’écran
6.3 La fonction de librairie strlen()
Cette fonction string length retourne la longueur d’une chaîne de caractère.
On peut l’appeler de 2 manières, en lui passant le string en paramètre, ou une solution plus élégante en passant l’adresse du string en paramètre.
Exemple avec passage du string sur le stack ( peu recommandable).
int lng; // déclaration, stock la longueur du string
lng = strlen(″hello″); // ici tout le string est mis sur le stack
Autre solution :
int lng; // stock la longueur du string
char *MonString = ″hello″; // pointeur sur le string hello
lng = strlen(MonString); // ici seule l’adresse du string est mise // sur le stack.
7 Les opérateurs les plus usuels. 7.1 L’affectation
= Affectation MaData = 12 ;
7.2 Les opérateurs arythmétiques.
...
9 Les opérateurs logiques
&& and logique pour des conditions (i == TempData) && (j == TempData2);
¦¦ ou logique pour des conditions (i == TempData) ¦¦ (j == TempData2);
! not inversion logique, complément à 1 MaData = (!TempData);
9.1.1 Deux conditions doivent être réalisées
if ((MaData == ProvData) && (MaData2 == ProvData2)) // test avec and
{ // le début de bloc
MaData = MaData + 1; // les instructions
MaData2 = MaData + 2;
} // la fin du bloc if
9.1.2 La conditions 1 ou 2 doit être réalisée
if ((MaData == ProvData) ¦¦ (MaData2 == ProvData2)) // test avec or
{ // le début de bloc
MaData = MaData + 1; // les instructions
MaData2 = MaData + 2;
} // la fin du bloc if
9.1.3 Test avec la condition vraie => différente de 0.
if (MaData) // si MaData est != 0
{ // le début de bloc
MaData = MaData + 1; // les instructions
MaData2 = MaData + 2;
} // la fin du bloc if
9.1.4 Test avec condition non vraie => égale à 0.
if (!MaData) // Si MaData == 0
{ // le début de bloc
MaData = MaData + 1; // les instructions
MaData2 = MaData + 2;
} // la fin du bloc if
9.1.5 Remarque sur les parenthèses. A la place de
if ((MaData == ProvData) && (MaData2 == ProvData2))
On peut écrire
if (MaData == ProvData && MaData2 == ProvData2)
Les parenthèses clarifient le programme, elles sont recommandées.
10 L’incrémentation et la décrémentation
Pour ajouter ou soustraire 1 à une variable on peut écrire :
i = i + 1 ; // ajoute 1 à la variable i
i = i – 1 ; // soustrait 1 à la variable i
On peut simplifier ces écritures en les remplaçant par :
i++ ; // ajoute 1 à la variable i
i-- ; // soustrait 1 à la variable i
10.1 La pré incrémentation et la pré décrémentation L’incrémentation (décrémentation) est faite avant l’affectation
j = ++i ; // i est incrémenté puis affecté à j
j = --i ; // i est décrémenté puis affecté à j
10.2 La post incrémentation et la post décrémentation L’incrémentation (décrémentation) est faite après l’affectation
j = i++ ; // i est affecté à j puis incrémenté
j = i-- ; // i est affecté à j puis décrémenté.
10.3 Exemple avec pré-incrémentation On va affecter 1 à la variable n, et i = 6
i = 5 ; // initialisation de i
n = ++i -5 ; // n = 1 car i est incrémenté avant !
10.4 Exemple avec post-incrémentation On va affecter 0 à la variable n, et i = 6
i = 5 ; // initialisation de i
n = i++ -5 ; // n = 0 car i est incrémenté après !
...
13 Les contrôles de flux 13.1 La boucle while (tant que)
La boucle while est exécutée tant que la condition entre parenthèse est réalisée.
Le test est fait en début de boucle, le traitement peut ne jamais être exécuté si la condition est déjà réalisée.
while(MaData <= ProvData) //
// condition entre () est testée en début de bloc
{ // le début de bloc du while
MaData = MaData + 1;
MaData2 = MaData + 2;
} // la fin du bloc du while
13.1.1 La boucle while infinie.
Il peut être nécessaire de disposer d’une boucle infinie. Cette boucle infinie peut être réalisée de la manière suivante :
while(1) //La condition est toujours réalisée
{ //le début de bloc while
MaData = MaData + 1; MaData2 = MaData + 2;
} //la fin du bloc while
13.2 La boucle do while (faire tant que)
La boucle do while est exécutée tant que la condition entre parenthèse est réalisée.
Le test est fait en fin de boucle, le traitement est toujours exécuté au moins une fois quelque soit la condition d’entrée.
do
{ // le début de bloc du while
MaData = MaData + 1;
MaData2 = MaData + 2;
} while(MaData <= ProvData); // condition entre () est testée
// en fin de bloc, traitement est // effectué au moins une fois.
13.3 La boucle for
La boucle for est exécutée selon trois conditions spécifiées en début de boucle. Elle est surtout utilisée pour des boucles avec compteurs de boucles.
for(cond-1 ;cond-2 ;cond-3) // les conditions de la boucles
{ // le début de bloc for
MaData = MaData + 1;
MaData2 = MaData + 2;
} // la fin du bloc for
13.3.1 Les conditions de la boucle for
for(cond-1 ;cond-2 ;cond-3) // les 3 conditions de la boucles
// sont entre les ()
Remarque : Il est possible d’indiquer plusieurs critères séparés par des virgules et terminés par un ;
cond-2 C’est la condition de test, si elle vraie la boucle est exécutée.
.Le test est effectué au début de la boucle.
Remarque : Il est possible d’indiquer une combinaison logique de plusieurs critères.
cond-3 C’est la modification du compteur de boucle. En pratique on utilise la post incrémentation, c’est à dire une incrémentation en fin de boucle.
Remarque : Il est possible d’indiquer plusieurs critères séparés par des virgules et terminés par un ;
13.3.2 Exemple d’une boucle for
for(i=0;i<3;i++) // conditions de la boucles
{ // le début de bloc for
printf (”la valeur de i = %d/n”,i);
} // la fin du bloc for
L’exécution de cette boucle donnera le résultat suivant :
la valeur de i = 0
la valeur de i = 1
la valeur de i = 2
Il est possible d’omettre des conditions du for. Si la condition n’existe pas elle considérée comme vraie.
13.3.3 La boucle for infinie
for(;;) // Sans condition la boucles
{ // est sans fin
MaData = MaData + 1; MaData2 = MaData + 2;
} // la fin du bloc for
Remarque : Chaque condition est optionnelle.
13.3.4 La boucle for a plusieurs conditions.
for(i=0,j=4;i0;i++,j--) // Conditions multiples
{ // Début de bloc
printf (”la valeur de i = %d j=%d/n”,i,j);
} // fin du bloc for
L’exécution de cette boucle donnera le résultat suivant :
la valeur de i = 0 j = 4
la valeur de i = 1 j = 3
la valeur de i = 2 j = 2
13.4 L’instruction break (rupture)
L’instruction break permet de sortir d’une boucle for, while, do while par un saut à la fin de la boucle. (nous verrons que cette instruction est aussi utilisée dans un switch) Exemple :
while(1) // La boucle est infinie
{ //
MaData = MaData + 1; if (MaData >= 10)
break; // sortie du while par un saut
} // à la fin de la boucle.
L’instruction break fonctionne de la même manière dans les boucles while, do while et for. 13.5 L’instruction continue
L’instruction continue permet de sauter à l’itération suivante est de sauter un traitement. La fin du traitement de la boucle est sauté et l’on passe directement à la boucle suivante. Exemple :
for(i=0 ;i<3;i++) //
{ //
if (i == 1) //Si cette condition est vraie
continue ; //On saute à la boucle suivante
printf (”la valeur de i = %d/n”,i);
} // la fin du bloc for
L’exécution de cette boucle donnera le résultat suivant :
la valeur de i = 0 la valeur de i = 2
L’instruction continue fonctionne de la même manière dans les boucles while, do while et for.
13.6 L’instruction GOTO(Aller à)
L’instruction goto a régné sur la programmation informatique jusqu’à l’arrivée du langage Pascal. Son usage irraisonné a donné naissance à des problèmes de logiciel totalement insurmontables. Actuellement le goto est bannit de la programmation sérieuse. Il reste cependant quelques cas ou le goto est encore nécessaire.
Il s’agit par exemple du traitement d’exceptions nécessitant un redémarrage du programme, ou d’un programmation devant être optimisée en vitesse d’exécution. Attention danger, a utiliser avec extrême prudence et modération.
reset_all: // label pour le saut du programme
if (b==0) // risque de division par 0
goto reset_all // erreur, nouvelle initialisation
else
{
a = a/b ;
}
13.7 L’instruction switch (sélection)
L’instruction switch permet un aiguillage vers différents blocs d’instructions en fonction d’une condition. La condition du switch est entre les (), le programme saute au cas correspondant.
switch(i) // i est la condition du switch
{
case 0 : // Est exécuté si i == 0
j++ ;
break ; // Saut à la fin du switch
case 1 : // Est exécuté si i == 1
j-- ;
break ; // Saut à la fin du switch
case 2 : // Est exécuté si i == 2
j = j + 2;
break ; // Saut à la fin du switch
default : // Est exécuté en cas d’erreur,
i = 0; // la valeur de i n’est pas prévue.
break ; // saut à la fin du switch
}
Remarque : l’instruction break permet le saut à la fin du switch. En cas d’absence du break, le traitement du case suivant est effectué.
switch(i) {
case 0 : // Exécuté si i == 0
printf (”C’est le cas 0”);
break ; // saut à la fin du switch
case 2 : // Exécuté si i == 2
printf (”C’est le cas 2”);
case ’A’ : // Exécuté si i == au caractère ascii A
printf (”C’est le cas A”);
break ; // saut à la fin du switch
default : // Erreur, valeur de i non prévue.
printf (”C’est le cas d’erreur”);
break ; // saut à la fin du switch
}
Remarques :
avec i == 0 => on exécute le case 0, on affiche C’est le cas 0
avec i == 1 ou (i > 2 et i != ’A’) =>On exécute la case default car le cas n’est pas prévu.
C’est le cas d’erreur
avec i == 2 => on exécute le case 2 et 3 car l’instruction break du cas 2 manque. C’est le cas 2
C’est le cas A
avec i == ‘A’ => on exécute le case ‘A’ C’est le cas A
14 Le type enum (énuméré)
Il peut être nécessaire d'affecter des valeurs à une liste de symboles. Le type enum permet de réaliser cette liste. Exemple:
enum couleur // nom facultatif de l'énumération
{
ROUGE, // rouge vaut 0
BLEU, // bleu vaut 1
VERT = 4, // vert vaut 4
JAUNE // jaune vaut 5
};
Si aucune valeur n'est précisée le premier élément vaut 0. Chaque élément suivant vaut 1 de plus que le précédent excepté si sa valeur est définie.
14.1 Le switch et l'énumération
Un programme sérieux ne doit contenir aucun chiffre. C'est aussi vrai pour le switch.
Pour assurer la sécurité et fiabilité du switch, il est indispensable chaque fois que c'est faisable de baser chaque cas (case) sur une énumération. Un switch sera ainsi fiable et modifiable. On a alors :
Pour obtenir une fiabilité maximum il est indispensable de baser le switch sur une énumération. On obtient ainsi une fiabilité maximum et on crée ainsi un programme modifiable.
15 Les problèmes dus à la fonction scanf( )
La fonction scanf() pose des problèmes insurmontables. Elle ne peut pas être utilisée dans un programme professionnel pour les raisons suivantes.
15.1 La maîtrise de la conversion de type.
Pour maîtriser la conversion de type il faut faire l'acquisition des caractères frappés au clavier en deux temps.
15.1.1 La fonction gets()
Elle permet l'acquisition d'un string sans conversion. 15.1.2 La fonction sscanf()
Cette fonction convertit un string en une valeur numérique. Elle est appelée avec plusieurs paramètres.
15.1.2.1 En entrée
15.1.2.2 En sortie
Exemple: void main (void)
unsigned char TempString[80]; // string temporaire
unsigned int CntInput = 0; // nombre de champs acquis
unsigned int JourMois; //
unsigned int Mois; //
unsigned int Année; //
clrscr();
while(1)
{
printf("entrer une date selon le format jj mm aa\n");
gets(TempString);
CntInput = sscanf(TempString,"%d %d %d", &JourMois, &Mois, &Année);
if ((CntInput == 3) && (JourMois >= 1) && (JourMois <= 31)
&& (Mois >= 1) && (Mois <= 12) && (Année <= 99))
break;
printf("le format n'est pas juste, recommencer !\n");
};
Cette méthode permet d'effectuer la conversion ASCII => numérique après coup avec sécurité.