Logique et techniques de programmation
Logique et techniques de programmation
Chapitre 1 : ____________________________________________________________________ 7
Vers un premier programme C _______________________________________________________ 7
1. Structure d'une instruction______________________________________________________ 7
2. Définitions __________________________________________________________________ 8
Chapitre 2 : ___________________________________________________________________ 17
La programmation structurée_______________________________________________________ 17
1. Introduction _________________________________________________________________ 17
4. Commentaires________________________________________________________________ 19
Chapitre 3 : ___________________________________________________________________ 21
Les types de données numériques et les opérateurs _____________________________________ 21
1. Déclaration des variables _______________________________________________________ 21
Chapitre 4 : ___________________________________________________________________ 27
Priorité des opérateurs ___________________________________________________________ 27
Chapitre 5 : ___________________________________________________________________ 29
Séquence ______________________________________________________________________ 29
Chapitre 6 : ___________________________________________________________________ 31
Les Alternatives ________________________________________________________________ 31
1. Utilité _____________________________________________________________________ 31
2. En pseudo-code ______________________________________________________________ 31
3. En utilisant un organigramme____________________________________________________ 32
4. En langage C ________________________________________________________________ 33
6. Exercice __________________________________________________________________ 34
9. Remarques _________________________________________________________________ 39
Chapitre 7 : ___________________________________________________________________ 45
La répétitive : "TANT QUE ... FAIRE ... FIN TANT QUE" ______________________________ 45
1. En pseudo-code : _____________________________________________________________ 45
2. Organigramme : _____________________________________________________________ 45
3. En C : _____________________________________________________________________ 45
4. Exemple 1 :_________________________________________________________________ 46
5. Exemple 2: _________________________________________________________________ 47
6. Exemple 3: _________________________________________________________________ 48
7. Exemple 4 : ________________________________________________________________ 49
8. Exemple 5 : ________________________________________________________________ 50
Chapitre 8 : ___________________________________________________________________ 53
La répétitive : "FAIRE ... TANT QUE ..." ___________________________________________ 53
1. en pseudo code ______________________________________________________________ 53
2. Organigramme : _____________________________________________________________ 53
3. En C : _____________________________________________________________________ 53
4. Exemple : __________________________________________________________________ 54
Chapitre 9 : ___________________________________________________________________ 55
Le type CARACTERE : char _______________________________________________________ 55
1. Caractères ASCII normalisés ___________________________________________________ 55
Chapitre 10 : __________________________________________________________________ 65
Compatibilité et portabilité ________________________________________________________ 65
Chapitre 11 : __________________________________________________________________ 67
Les tableaux ___________________________________________________________________ 67
1. Définitions _________________________________________________________________ 67
2. Exemples : _________________________________________________________________ 68
Chapitre 12 : __________________________________________________________________ 77
Adresses et pointeurs ____________________________________________________________ 77
1. Les adresses________________________________________________________________ 77
3. Exemple ___________________________________________________________________ 78
4. Résumé ___________________________________________________________________ 78
5. Exemples __________________________________________________________________ 79
7. Exemples ___________________________________________________________________ 81
Chapitre 13 : __________________________________________________________________ 87
Les fonctions ___________________________________________________________________ 87
1. Un premier exemple __________________________________________________________ 87
6. Valeur de retour______________________________________________________________ 91
2. Déclarations________________________________________________________________ 127
Chapitre 1 :
L'instruction est analysée par le µP. Le code opératoire est reconnu, ensuite les 2
opérandes sont lues et l'instruction est exécutée. Pour ce faire, le µP doit avoir accès aux
données dans les deux sens : lecture et écriture.
Dans cet exemple, 60 n'est pas une variable, il s'agit d'une valeur (constante).
MOV AX, a
ADD AX, b
MOV r, AX
a 12
b 16 AX 12 28
r 28
2. DEFINITIONS
Une variable est une zone de mémoire identifiée par un nom. Son contenu peut être
modifié.
Une constante est une valeur numérique, un texte, une valeur logique, … Sa valeur est
fixe.
3. PREMIERES INSTRUCTIONS
Prenons par exemple une variable de type entier et appelons-la a.
a:
Si on effectue l'opération
b = 4;
On peut également effectuer une opération mathématique dans la partie droite d'une
instruction d'affectation : le résultat de cette opération sera stocké à l'emplacement
mémoire désigné par la variable donnée à gauche du signe d'affectation (=).
a = 5;
b = 6;
r = a + b;
La variable r prendra pour valeur le résultat de la somme des valeurs stockées dans les
emplacements mémoires a et b. r prendra donc la valeur 11.
4.1. Lecture
Il n'est pas très utile d'additionner toujours les mêmes valeurs. L'intérêt est de
demander à l'utilisateur de donner les valeurs qu'il veut additionner.
Il faudrait donc arriver à placer dans a et b des valeurs obtenues par "lecture" ou
saisie. Il faut pouvoir "lire" ou "saisir" des valeurs par l'intermédiaire d'un
périphérique.
L'instruction a = 5;
va devenir "lire a" et va correspondre à la saisie d'une valeur au clavier qui sera
affectée à la variable a.
Remarque : scanf est une fonction peu professionnelle, on la voit en début d'année
car elle est simple à utiliser. Mais elle présente deux inconvénients majeurs :
Si on entre <ENTER> avant d'avoir tapé une valeur, l'instruction ne donne pas de
message d'erreur. Elle se contente de ne pas modifier le contenu de la variable.
Si le buffer (zone tampon entre les périphériques et l'unité centrale) n'est pas
vide, scanf va lire le contenu de celui-ci, avant que l'utilisateur n'ait entré quoi que ce
soit au clavier.
µP
Clavier
Autres solutions : utiliser gets ou faire une boucle avec l'instruction getchar.
Cette dernière solution est de loin la meilleure et sera abordée plus tard dans ce cours.
4.2. Ecriture
Maintenant qu'on a vu comment demander des valeurs à l'utilisateur afin de ne
pas toujours additionner les mêmes nombres, il peut être intéressant de pouvoir lui
communiquer le résultat.
Il s'agit ici d'un transfert d'information depuis la mémoire centrale vers l'écran,
ou plus exactement, vers la carte vidéo.
lire a;
lire b;
r = a + b;
afficher a;
afficher b;
afficher r;
écran
Mémoire centrale
µP
A:
clavier
HD c:
B:
imprimante
main()
{
int a, b, r;
r = a + b;
Le problème est que le µP ne comprend pas les fonctions telles que scanf, printf, … Il
faut donc rendre notre programme compréhensible par l'ordinateur.
mémoire
µP test.c
programme
d'encodage : éditeur
6.3. Compilation
Le programme que l'on a écrit en C précédemment ne peut être compris tel quel
par la machine. On va, à cet effet, utiliser un autre programme qui s'appelle un
compilateur.
Le compilateur est capable de traduire un fichier .c en une autre entité, un autre
fichier, qui aura pour extension ".obj" et que l'on appelle le code objet.
Schéma de la compilation.
[Link]
6.4. Link
Le programme qui s'occupe du link est le linker. Il s'agit d'un éditeur de liens.
En fait, dans le fichier .obj créé par le compilateur, on trouve des informations
sur l'emplacement, le répertoire, où se trouvent les bibliothèques de fonctions à inclure
dans le programme.
Schéma du link.
Programme de link
[Link]
7. REMARQUE GENERALE
Comme on a pu le voir dans les paragraphes précédents, les problèmes surviennent
surtout lors de l'écriture du programme.
Un des outils à notre disposition est le pseudo-code. C'est une écriture du programme
qui ne tient compte ni du langage de programmation, ni de la machine, ni des fautes de frappe
=> on se concentre uniquement sur la logique du problème.
Tout programme C doit respecter une certaine structure sans quoi la compilation ne
pourra s'effectuer correctement.
#include <stdio.h>
#include <stdlib.h> Fichiers d'entête à inclure : prototype des fonctions
/* Affichage du résultat */
printf ("\nLa moyenne des trois cotes est : %f", fMoyenne);
Voici les différentes parties que l'on peut analyser dans un programme écrit en
langage C :
Chapitre 2 :
La programmation structurée
1. INTRODUCTION
Par exemple :
Voyons un exemple :
#include <stdio.h>
#include <conio.h>
main ()
{
float a, b, m;
/* calcul du résultat */
m = a / b;
/* affichage du résultat */
printf ("\nLe résultat de la division est : %f", m);
}
/*********************************************/
/* INPUT : */ description
/* Process : affiche un message */ de la
/* OUTPUT : */ fonction
/*********************************************/
void affiche_message (void) Entête de la fonction
{
printf("Bonjour, ce programme effectue une division"); le code de
} la fonction
Remarques :
il s'agit d'une fonction qui ne reçoit aucun paramètre et ne retourne aucune
valeur;
dans le prototype qui indique le nombre de paramètres reçus, leur type ainsi
que la valeur retournée, on inscrit le terme "void";
l'appel de la fonction n'est composé que du nom de celle-ci : effectivement, on
ne passe pas de paramètres;
3. ENTETE DE PROGRAMME
/*********************************************************/
/* Auteur : NOM Prénom */
/* Groupe : 21XX */
/* Application : nom et description de l'exercice */
/* Date de la dernière mise à jour : 22/09/2005 */
/*********************************************************/
4. COMMENTAIRES
Un programme, ensemble de fonctions, doit contenir des commentaires pertinents dans
le but de rendre celui-ci lisible.
Le programme doit être lisible pour permettre au programmeur d'intervenir rapidement
au sein de celui-ci pour en assurer la mise au point et la maintenance.
Chapitre 3 :
Dans la représentation interne des valeurs entières signées, le bit de poids fort
("bit de gauche") représente le signe. (0 positif et 1 négatif).
Remarque : il faut surtout retenir les valeurs pour les entiers, pour les, autres
types il faut pouvoir rechercher l'information (limits.h et float.h).
int i ;
float r ;
i = 7/2; /* => i = 3 */
r = 7/2; /* => r = 3.0 */
i = 7.0/2; /* => i = 3 */
r = 7.0/2; /* => r = 3.5 */
Pourquoi ?
Si i est un int, il ne peut contenir qu’une valeur entière ! Donc quel que soit le résultat
du calcul effectué à droite du signe ‘=’, i ne pourra jamais prendre qu’une valeur entière !
Quant à r, qui est un réel, pourquoi prend-t-il aussi la valeur 3.0 dans le deuxième
calcul ?
7 et 2 sont des entiers ! En C, diviser un entier par un entier donnera toujours un
entier : il s’agit d’une division entière ! Même si on met le résultat dans un réel, le résultat
sera converti : 3 -> 3.0 !
Par contre, 7.0 est un float et 2 est un entier, et diviser un float par un entier donne un
float. Le résultat du calcul est donc un float et si on met ce résultat dans un variable de type
float, le type sera conservé.
De même, on obtient :
int i, a, b ;
float r, c;
a = 7;
b = 2;
c = 7.0;
i = a/b; /* => i = 3 */
r = a/b; /* => r = 3.0 */
i = c/b; /* => i = 3 */
r = c/b; /* => r = 3.5 */
Que faire si l’on veut diviser 2 entiers et mettre le résultat dans un réel tout en
conservant la partie décimale ?
2. Casting
r = (float)a / b ;
On dit explicitement au compilateur qu’il doit interpréter le contenu de la
variable a comme un float.
i = (int)c ;
Dans ce cas, le compilateur interprète c comme un entier pour copier sa
valeur dans la zone mémoire donnée par i. C’est donc syntaxiquement plus
correct même si l’instruction "i = c ;" donnera le même résultat !
3. DEPASSEMENTS DE VALEUR
Si on écrit :
short int i ;
i = 123456 ;
Il est très important de faire attention aux limites permises par les types des
variables et aux résultats des expressions intermédiaires.
Exemple :
short int a;
int b;
double c, r;
a = 20000;
b = 2000000;
c = 5000000.0;
r = c + a * b;
printf ("%lf + %hd * % d = %lf\n", c, a, b, r);
Vu la priorité des opérateurs (cette théorie sera vue ultérieurement), les instructions
sont exécutées dans un certain ordre (semblable aux opérateurs utilisés en mathématique) :
1. exécution de a * b;
Le résultat de cette expression (short int * int) est du type int.
Le résultat prévu 40 000 000 000 dépasse la capacité d'un int; donc, ce résultat
intermédiaire est dès lors faux (soit x)
2. exécution de c + x
Le résultat de cette expression (double + int) est du type double, ce qui implique une
conversion implicite de x int vers du double. La valeur entière de x fausse convertie
en double sera toujours fausse.
Le résultat de l'addition sera donc faux.
3. assignation à r
r reçoit ce résultat faux !
r = c + (double)a * b;
Exemple :
5. LES IDENTIFICATEURS
Les identificateurs servent à identifier un objet, que ce soit une variable, une
constante, une fonction, un type, etc.
Convention :
variables, fonctions : ensembles de majuscules et minuscules avec une prédominance de
minuscules
Constantes : entièrement en majuscules
Le premier caractère doit être impérativement une lettre ou un ‘_’ (pas de chiffre en
première position)
On ne peut pas utiliser d’espace, sinon, seule la première partie du nom est prise en
considération, le reste étant perdu (et provoquant une erreur à la compilation).
Chapitre 4 :
Exemple :
int c ;
c = 4 + 6 / 2 ; /* => c = 7 */
c = (4 + 6) / 2 ; /* => c = 5 */
c = 4 + 6 * 2 ; /* => c = 16 */
c = (4 + 6) * 2 ; /* => c = 20 */
()
fonctions
*, /, %
+, -
=
Pour rappel, ‘%’ est l’opérateur modulo, il donne donc le reste de la division entière du
premier nombre par le second.
Dans l'aide du logiciel "Visual C++ 6.0" de Microsoft, vous trouverez un tableau complet
reprenant l'ordre de priorité des opérateurs.
Voir la rubrique "Precedence and order of evaluation".
Chapitre 5 :
Séquence
De manière implicite, nous avons vu jusqu’à présent que l’ordinateur exécute les
opérations les unes après les autres dans l’ordre dans lequel elles apparaissent dans le
programme. Il exécute donc le programme en séquence.
Exemple :
DEBUT
Module X
Module Y
Module Z
FIN
Chapitre 6 :
Les Alternatives
1. UTILITE
On veut parfois conditionner l'exécution d'une ou plusieurs opérations au résultat de
l'évaluation d'une condition.
Exemple : s'il fait beau, j'irai me promener.
On peut aussi décider de faire une ou plusieurs autres actions dans le cas où la condition
n'est pas vraie.
Exemple : s'il fait beau, j'irai me promener, sinon, j'irai au cinéma.
2. EN PSEUDO-CODE
Si (condition)
alors
Quand la condition est vraie
sinon
Quand la condition est fausse
La condition testée est soit vraie soit fausse, il n'y a pas d'autre possibilité !
Il y a un seul point d'entrée et un seul de sortie.
exemple :
a b
Si (a > b)
alors
Quand a > b
sinon
Quand a < b ou a == b
3. EN UTILISANT UN ORGANIGRAMME
a, b
traitement X traitement Y
Autre exemple :
a, b
traitement Y traitement Z
Si (a > b)
alors Traitement X
sinon
Si (a < b)
alors
traitement Y
sinon
traitement Z
4. EN LANGAGE C
Syntaxe :
if (condition)
{
... /* la condition est vraie */
}
else
{
... /* la condition n'est pas vraie */
}
Remarques :
le "alors" n'est pas traduit en C
les accolades ne sont nécessaires que si il y a plusieurs instructions à
exécuter
4.1. Exemple 1
system ("cls");
if (a < b)
{
printf ("a est plus petit que b");
}
else
{
printf ("a n'est pas plus petit que b\n");
printf ("c\'est-à-dire : a est plus grand ou égal à b");
}
4.2. Exemple 2
system ("cls");
if (a < b)
{
printf ("a est plus petit que b");
}
else
{
if (a > b)
{
printf ("a est plus grand que b\n");
}
else
{
printf ("a est égal à b");
}
}
Exemple :
a = 4;
b = 5;
system("cls");
if (a < b)
{
printf ("a est plus petit que b\n");
}
else
{
if (a > b)
printf ("a est plus grand que b\n");
else
printf ("a est égal à b\n");
}
6. EXERCICE
Soient a et b, deux données entières à saisir au clavier.
Le programme doit afficher l'un des deux messages suivants :
a = … b = … résultat = …
a = … b = 0 message d'erreur
Pseudo code :
DEBUT
lire a, b
afficher a
afficher b
SI (b == 0)
ALORS afficher "la division par 0 n'est pas permise !"
SINON résultat = a/b
afficher résultat
FIN SI
FIN
7. ALTERNATIVE INCOMPLETE
Jusqu'à présent, nous avons considéré des alternatives du genre :
SI (condition)
ALORS
Traitement X
SINON
Traitement Y
FIN SI
SI (condition)
ALORS
Traitement X
FIN SI
a, b
oui non
a > b
?
traitement X
if (condition)
{
...
}
7.1. Exemples
L'exemple d'alternative donné précédemment permettait de séparer les 3 cas suivants :
"a < b", "a == b" et "a > b".
Nous avions écrit le pseudo-code suivant :
SI (a > b)
ALORS Traitement X
SINON
SI (a < b)
ALORS traitement Y
SINON
traitement Z
FIN SI
FIN SI
Autre proposition :
SI (a == b) oui non
a == b
ALORS Traitement Z ?
FIN SI traitement Z
SI (a < b)
ALORS
traitement Y
FIN SI
traitement Y traitement X
Nouvelle proposition :
SI (a == b)
oui a == b non
ALORS
Traitement Z ?
FIN SI
SI (a < b) traitement Z
ALORS
traitement Y
FIN SI oui a<b non
SI (a > b) ?
ALORS
traitement X
traitement Y
FIN SI
traitement X
Ce qui donne en C :
if (a == b)
{
...
}
if (a < b)
{
...
}
if (a > b)
{
...
}
Exemple : écrire un pseudo-code qui permet d'intervertir la valeur des deux variables a
et b dans le cas où a est supérieur à b.
SI (a > b)
ALORS c <- a
a <- b
b <- c
FIN SI
ce qui donne en C :
if (a > b)
{
c = a;
a = b;
b = c;
}
Quand on utilise le ET, c'est-à-dire '&&' en C, les deux conditions données doivent être
vraies pour que la condition globale soit vraie.
Il suffit donc qu'une des deux soit fausse pour que la condition composée soit fausse.
Quand on utilise le OU, c'est-à-dire '||' en C, au moins une des deux conditions données
doit être vraie pour que la condition globale soit vraie.
Il faut donc que les deux conditions soient fausses pour que la condition composée soit
fausse.
8.1. Exemples :
1. if (0 < m && m < 13)
m doit être compris entre 0 et 13 pour que la condition soit vraie : m doit
être à la fois plus grand que 0 ET plus petit que 13.
pour que la condition soit fausse, il suffit que m soit inférieur ou égal à 0
OU supérieur ou égal à 13.
! (0 < m && m < 13) équivaut à (0 >= m || m >= 13)
/* ! représente l'opérateur NOT */
2. if (a > b || a > c)
a ne peut pas être le plus petit des trois nombres : il doit soit être plus
grand que b, soit plus grand que c. Pour que la condition composée soit
vraie, il suffit qu'une au moins des deux sous-conditions soit vraie
pour que la condition soit fausse, il faut que les deux sous-conditions soient
fausses : a doit être inférieur ou égal à b ET inférieur ou égal à c.
! (a > b || a > c) = (a <= b && a <=c)
9. REMARQUES
9.1. 0 ou différent de 0
une condition est fausse si elle vaut 0
une condition est vraie si elle est différente de 0
une condition du type "if (i != 0)" peut s'écrire "if (i)" mais est moins lisible
"if (i == 0)" peut donc aussi s'écrire "if (! i)"
c'est correct au niveau de la syntaxe, mais cela ne réagit pas exactement comme
if (a == b).
En effet, il ne s'agit pas d'un test d'égalité mais d'une affectation !
La valeur de b est copiée dans a, ensuite, vu la remarque précédente, si a vaut 0,
la condition est considérée comme fausse, sinon,dans tous les autres cas, elle est
considérée comme vraie !!
Il faut donc être prudent et bien utiliser "==" quand on veut effectuer un test
d'égalité !!
9.3. lisibilité
Si on écrit en C :
if (cond1)
{
instr1;
}
else
{
if (cond2)
{
instr2;
}
else
{
if (cond3)
{
instr3;
}
else
{
instr4;
}
}
}
Dans cet exemple, toutes les accolades sont facultatives et peuvent donc être
supprimées, rien que l'indentation aide beaucoup à une meilleure compréhension !
On peut voir que l'on arrive rapidement à un certain nombre de "if" imbriqués. Dans ce
cas, si on avait prévu un message différent pour chaque jour, on en aurait encore eu plus !!!
Il existe une structure sélective qui permet de simplifier la notation de ce type de
code. En pseudo-code, cela donne :
SELECTIONNER
POUR x = y : Traitement 1
POUR x = z : Traitement 2
POUR x = w : Traitement 3
AUTREMENT : Traitement 4
FIN SELECTIONNER
10.2. Exemple 1
case 22:
case 23:
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
printf ("Bonne nuit !");
break;
default:
printf ("Heure impossible !")
}
Dans cet exemple, on peut voir qu'en n’utilisant pas systématiquement l'instruction
"break" dans tous les cas, cela nous permet d'exécuter la même instruction quand la valeur de
la variable est, par exemple, 6, 7, 8 et 9.
10.3. Exemple 2
On peut également se servir de l'instruction switch pour déterminer le nombre de jours
que contient un mois donné en tenant compte de l'année pour le mois de février.
Chapitre 7 :
La structure répétitive permet de répéter un traitement un certain nombre de fois : tant qu’une
condition donnée est vraie. Dès qu’elle est fausse, on arrête d’effectuer le traitement.
1. EN PSEUDO-CODE :
2. ORGANIGRAMME :
non
Condition
oui
Traitement X
3. EN C :
Cette instruction en C s’exprime :
while (condition)
{
...
}
4. EXEMPLE 1 :
Nous devons écrire un programme qui permet d’afficher les nombres 1, 2, 3 et 4.
4.1. En pseudo-code :
i 1
TANT QUE (i < 5)
FAIRE afficher i
i i + 1
FIN TANT QUE
4.2. Organigramme :
i1
non
i<5
oui
afficher i
ii+1
4.3. En C :
main ()
{
int i ;
i = 1 ;
while (i < 5)
{
printf ("%d", i);
i = i + 1;
}
}
il faut absolument modifier le contenu d’au moins une des variables sur
lesquelles porte la condition, à l’intérieur du bloc correspondant à la boucle. En
effet, si on entre une première fois dans la boucle, c’est que la condition est
vraie. Si on ne modifie pas la valeur de la variable sur laquelle porte la
condition, la condition continuera a être vraie, et on aura une boucle infinie : le
programme va rester indéfiniment dans la boucle ! La manière de modifier le
contenu de la variable doit faire en sorte que tôt ou tard, on finira par rendre
la condition fausse.
Attention à ne jamais mettre de ‘ ;’ après la condition, tout comme dans les alternatives.
Sinon, l’ordinateur interprète le code en disant que le corps de la boucle ne contient qu’une
! seule instruction vide.
Ce scénario, si la condition est vraie, débouchera sur une boucle infinie.
5. EXEMPLE 2:
Saisir un entier compris entre 0 et 20 et calculer son carré.
5.1. En pseudo-code :
lire a
TANT QUE (a < 0 OU a > 20)
FAIRE lire a
FIN TANT QUE
afficher a * a
5.2. En C :
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
main ()
{
int a, b ;
b = (int)pow(a, 2) ; /* ou b = a * a */
printf ("\nLe carré de %d vaut %d\n", a, b) ;
}
6. EXEMPLE 3:
Saisir deux entiers a et b. Calculer la valeur de "a/b". Si b vaut 0 on donnera un
message d’erreur approprié. On recommencera l’opération autant de fois que l’utilisateur le
désire.
6.1. En pseudo-code :
c 1
TANT QUE (c == 1)
FAIRE lire a, b
SI (b != 0)
ALORS r = a / b
afficher r
SINON afficher un message d’erreur
FIN SI
lire c
FIN TANT QUE
6.2. En C :
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
void main ()
{
int a, b, c ;
float r ;
system ("cls") ;
c = 1 ;
while (c == 1)
{
fflush (stdin);
printf ("Entrez le numérateur : \n");
scanf ("%d", &a);
printf ("\nEntrez le dénominateur : \n");
scanf ("%d", &b);
if (b != 0)
{
r = (float)a / b;
printf ("\n%d / %d = %f", a, b, r);
}
else
printf ("La division par 0 n’est pas permise");
printf ("\nEntrez 1 pour recommencer …") ;
fflush (stdin) ;
scanf ("%d", &c);
}
}
7. EXEMPLE 4 :
Calculer la valeur de a + b, où a et b sont donnés par l’utilisateur, autant de fois que
l’utilisateur désire le faire. On arrête lorsque a et b sont égaux à 0.
7.1. En pseudo-code :
lire a et b
TANT QUE (a != 0 OU b != 0)
FAIRE r = a + b ;
afficher a, b, r
lire a et b
FIN TANT QUE
7.2. En C :
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
void main ()
{
int a, b, c ;
system ("cls");
printf ("Entrez le premier nombre : \n");
fflush (stdin) ;
scanf ("%d", &a) ;
printf ("\nEntrez le deuxième nombre : \n");
fflush (stdin) ;
scanf ("%d", &b) ;
while (a != 0 || b != 0)
{
c = a + b;
printf ("\n%d + %d = %d", a, b, c);
printf ("\nEntrez deux nouvelles valeurs (0 et 0 pour
arrêter \n") ;
fflush (stdin) ;
scanf ("%d", &a) ;
fflush (stdin);
scanf ("%d", &b);
}
}
8. EXEMPLE 5 :
Saisir un entier a.
Calculer et afficher r = a * x avec x qui varie de 4 à 5 par pas de 0.1.
Par exemple, si a vaut 2 :
x r
4.0 8.0
4.1 8.2
4.2 8.4
4.3 8.6
4.4 8.8
4.5 9.0
4.6 9.2
4.7 9.4
4.8 9.6
4.9 9.8
5.0 10.0
8.1. En pseudo-code :
lire a
x = 4.0
TANT QUE (x < 5.1)
FAIRE r = a * x ;
afficher r
x = x + 0.1
FIN TANT QUE
8.2. En C :
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
void main ()
{
int a;
float r, x;
system ("cls");
printf ("Entrez le multiplicateur : \n");
scanf ("%d", &a) ;
x = 4.0 ;
while (x < 5.1)
{
r = a * x;
printf ("\n%5.1f | %5.1f", x, r);
x = x + 0.1;
}
}
Avec les variables de type float, il est impératif de se servir d’une condition contenant
une comparaison avec un des signes <, >, <= ou >= . Il faut éviter à tout prix == ou !=
! Par contre, avec les entiers, tous les signes peuvent bien entendu être utilisés.
Chapitre 8 :
1. EN PSEUDO CODE
FAIRE Traitement
2. ORGANIGRAMME :
Traitement X
non
Condition
oui
3. EN C :
Cette instruction en C s’exprime :
do
{
...
} while (condition);
4. EXEMPLE :
Reprenons l’exemple que nous avons vu dans le cadre de la boucle "TANT QUE
(condition) FAIRE … FIN TANT QUE"
lire a
TANT QUE (a < 0 OU a > 20)
FAIRE lire a
FIN TANT QUE
afficher (a * a)
On voit que l’instruction "lire a" apparaît deux fois dans l'algorithme. La boucle "FAIRE
… TANT QUE (condition)" va nous permettre de simplifier cet algorithme :
FAIRE lire a
TANT QUE (a < 0 OU a > 20)
afficher (a * a)
Chapitre 9 :
La variable de type char occupe 1 octet en mémoire. Elle est déclarée de la manière
suivante :
char x; /* par défaut, char est signé */
signed char x;
unsigned char x;
char x;
4 1
x = 'A'; x représentation hexadécimale
0100 0001
x représentation binaire
valeur décimale : 65
2.1. Remarque
98 est une constante entière, 4 octets, exprimée en décimal
00 00 00 62
'b' est une constante char, 1 octet, exprimée par son caractère.
62
2.2. Exemples
char y;
62 62
y = 'b'; ou y = '\x62';
y constante
char y;
y = 0x62; ou y = 98; 62 00 00 00 62
y constante
Dans cet exemple, on assigne la valeur d'une constante de type int à une variable de
type char (char = int). Il n'y a cependant pas de warning à la compilation car il n'y a pas
de perte de digits significatifs.
int y;
y = 'b'; ou y = '\x62';
00 00 00 62 62
y constante (signée)
répétition du bit de signe 0 dans y
int y;
y = 0x62; ou y = 98;
00 00 00 62 00 00 00 62
y constante (signée)
On obtient à l'affichage :
ç -121 ffffff87
12110 = 7916
0 00 00 00 00
-121 - 00 00 00 79
_______ _____________________
-121 10 ff ff ff 8716
char c = 0x87;
printf ("%c %d %x", c, c, c);
Dans ce cas, on obtient un warning à la compilation : "truncation from const int to char".
c 87 00 00 00 87
c est signé, sa valeur maximale est 127. Or, 8716 = 13510 Warning
On obtient à l'affichage :
ç 135 87
char c = 'ç';
printf ("%c %d %x", c, c, c);
On obtient à l'affichage :
þ –25 ffffffe7 00 00
- ff e7
_________
00 19 hex = 25déc
Autre possibilité :
unsigned char c = 'ç';
printf ("%c %d %x", c, c, c);
On obtient à l'affichage :
þ 231 e7 e7hex = 231déc
affichage lecture
getchar ()
Prototype de la fonction :
int getchar (void);
Rappel à propos du buffer d'entrée : 2 cas peuvent se présenter : soit stdin est vide,
soit il ne l'est pas.
Si stdin est vide : getchar attend un entrée au clavier terminée par un " " (enter)
Si stdin n'est pas vide : getchar extrait le premier caractère du buffer stdin.
5.2. Exemple 1
stdin est vide
int i, k;
i = getchar() b => stdin : 62 0A
5.3. Exemple 2
char ca, cb, cc, cd, ce; /* stdin est vide */
5.4. Exemple 3
char c;
c = getchar();
printf ("%c %d %x", c, c, c);
unsigned char c;
c = getchar();
printf ("%c %d %x", c, c, c);
Ici, on obtient bien le caractère 'ç' car il donné sous DOS à la fonction getchar !!
5.5. Récapitulatif
getchar() retourne un entier qui contient :
soit le caractère saisi dans stdin, étendu à 4 octets (par des 0)
soit la valeur –1 en cas d'erreur de lecture ou s'il lit le caractère de fin
de fichier
getchar() a pris le 1er caractère de stdin et le pointeur du buffer se place au
caractère suivant
5.6. Conclusion
Si on utilise l'instruction getchar() dans un programme dans le but de lire un
caractère et de l'utiliser dans un traitement, il faut impérativement que le buffer
"stdin" soit vide.
Il faut faire attention au fait que getchar() n'est pas la seule instruction à
utiliser stdin : scanf() l'utilise également.
Exemple :
main()
{
int i;
char ca;
getch ()
Cette autre fonction permet également de saisir (ou lire) un caractère au clavier.
Prototype de la fonction :
int getch(void);
Cette fonction ne reçoit aucun paramètre, elle retourne un entier qui contient :
dans le byte de poids faible : le code ASCII du caractère
dans les 3 autres bytes : 0
6.1. Exemple :
#include <stdio.h>
#include <conio.h>
main ()
{
int ia;
char ca;
ia = getch(); /* on tape ç */
printf ("%c", ia); /* affichage de ç */
printf ("%d", ia); /* affichage de 135 */
printf ("%x", ia); /* affichage de 87 */
ca = getch(); /* on tape ç */
printf ("%c", ca); /* affichage de ç */
printf ("%d", ca); /* affichage de -121 */
printf ("%x", ca); /* affichage de FF FF FF 87 */
ca = (char) getch(); /* on tape ç */
printf ("%c", ca); /* affichage de ç */
printf ("%d", ca); /* affichage de -121 */
printf ("%x", ca); /* affichage de FF FF FF 87 */
getch();
}
6.2. Conclusion
L'instruction getch() est intéressante car elle saisit un caractère immédiatement sans
que l'utilisateur ne doive appuyer sur
! Mais sa portabilité (voir chapitre suivant) est restreinte car elle ne fait pas partie de la
norme ANSI.
Cette fonction reçoit comme paramètre le caractère à afficher (byte de poids faible de
l'entier).
Elle retourne un entier qui contient :
soit le code ASCII du caractère affiché à l'écran
soit –1 (EOF) pour indiquer une erreur ou une condition de fin de fichier.
7.1. Exemple :
#include <stdio.h>
#include <conio.h>
main ()
{
char ca, cb;
ca = getch(); /* on tape ç */
cb = putchar(ca); /* on affiche ç */
printf ("\n%c", cb); /* on affiche ç */
}
Remarque : cette fonction putch écrit le caractère directement sur la console, sans
passer par le buffer (de la même façon que getch).
Chapitre 10 :
Compatibilité et portabilité
On dit souvent qu'un programme écrit en langage C peut être compilé par n'importe quel
compilateur C : ce n'est pas tout à fait exact !!
Par exemple : les instructions de stdio.h sont portables sur tous les compilateurs C,
tandis que celles de conio.h ne le sont pas !
Exemple : clrscr(); => printf ("\x27]1J"); en code ansi
On ne peut jamais dire à l'avance si un programme devra être porté sur un autre
système ou pas. C'est pour cette raison qu'il faut essayer d'utiliser, lorsque c'est possible,
des instructions qui offrent la plus grande portabilité.
Mais les instructions spécifiques à un compilateur offrent parfois des facilités
intéressantes => il faut faire un choix !!
Chapitre 11 :
Les tableaux
1. DEFINITIONS
Un tableau est
un ensemble fini de variables de même type, donc
un ensemble fini de zones mémoire de même taille
int v[10];
v:
0 1 2 3 4 5 6 7 8 9
Exemples :
scanf ("%d", &v[4]);
printf ("%d", v[4]);
2. EXEMPLES :
main()
{
int i, va[5], vb[6];
i = 3;
scanf ("%d", &va[i]); /* l'utilisateur donne 12 */
i = i – 3;
scanf ("%d", &vb[i]); /* l'utilisateur donne 14 */
...
/*
Il n'existe, par défaut, aucun lien entre i, va et vb.
Les seuls liens qui peuvent exister sont ceux créés
par le programmeur !!!
*/
...
va[i + 1] = 17; /* i = 0 => i + 1 = 1 */
i = 1;
vb[i] = vb[i-1] + va[i+2]; /* vb[0] + va[3] */
...
}
i 301 va 17 12 vb 14 26
0 1 2 3 4 0 1 2 3 4 5
main ()
{
int i, n;
int va[50];
i = 0;
while (i < n)
{
scanf ("%d", &va[i]);
i++;
}
...
/*
* Pour afficher le contenu des n cases actives de va, il
* suffit de remplacer scanf par printf dans la boucle.
* Il ne faut pas oublier non plus de remettre la variable
* compteur à 0 !!!
*/
i = 0;
while (i < n)
{
printf ("%d", va[i]);
i++;
}
...
}
2.3. Exemple 3
Soit va, un vecteur d'entiers
Lire n : nombre d'éléments utilisés dans va
Lire les n éléments de va
Afficher les n éléments de va
Calculer la somme de n éléments de va
Pseudo-code :
res = 0;
i = 0;
lire n;
TANT QUE (i < n)
FAIRE
lire va[i];
i++;
FIN TANT QUE
i = 0;
TANT QUE (i < n)
FAIRE
afficher va[i];
i++;
FIN TANT QUE
i = 0;
TANT QUE (i < n)
FAIRE
res = res + va[i];
i++;
FIN TANT QUE
afficher res;
C'est-à-dire :
Initialisation
int va[40];
int n, i;
Condition
scanf ("%d", &n); /* 5 ↵ */
Opération de fin de boucle
i = 0; n=5
while (i < n)
{
traitement; …
i = i + 1; 0 1 2 3 4 5 6 7 8 9 39
}
avec 1 <= n <= 40
Autre méthode : lorsque l'on travaille avec des vecteurs, il est plus facile d'utiliser
l'instruction for. La logique de cette instruction est exactement la même que celle de
l'instruction "while", elles seront donc traduites de la même manière en pseudo-code, par
contre, la syntaxe est, elle, différente :
On voit bien dans cet exemple, que l'on retrouve les mêmes éléments dans les deux
types de boucles : initialisation au départ, condition pour savoir si on doit encore effectuer le
traitement de la boucle, incrémentation de la variable i en fin de boucle !
Initialisation
non
condition
oui
Traitement
Opération(s) de fin
de boucle
exemple :
i=0
non
i<n
oui
lire va[i]
i ++
Le raisonnement est le même pour la boucle "for" que pour la boucle "while" (TANT QUE
…). Sa syntaxe la rend seulement plus facile à écrire et à lire dans le cas des tableaux car
toute l'information concernant la boucle est placée sur la même ligne.
4. INCREMENTATION ET DECREMENTATION
Quand on écrit "i = i + 1;" on augmente la valeur contenue dans la variable 'i' d'une unité
=> on l'incrémente d'une unité.
Exemples :
a = ++b;
1) la valeur de b est incrémentée
2) a reçoit la nouvelle valeur de b
Si b = 4 => a = 5 et b = 5 après cette instruction !
a = b++;
1) a reçoit la valeur de b
2) la valeur de b est incrémentée
si b = 4 => a = 4 et b = 5 après cette instruction !
j = ++i + 5;
1) i = i + 1
2) j = i + 5
si i = 3 = > j = 9 et i = 4 après cette instruction !
j = i++ + 5;
1) j = i + 5
2) i = i + 1
si i = 3 => j = 8 et i = 4 après cette instruction !
main ()
{
int va[40];
int i, n;
fflush (stdin);
scanf ("%d", &n);
for (i = 0; i < n; i++)
{
scanf ("%d", &va[i]);
}
...
}
Exemple :
n=5 va : 4 8 3 0 7
=> 7 0 3 8 4
n=4 va : 1 2 3 4
=> 4 3 2 1
Pseudo-code :
Lire n;
i = 0;
TANT QUE (i < n)
FAIRE
lire va[i];
i++;
FIN TANT QUE
k = 0;
i = n – 1;
TANT QUE (k < n/2)
FAIRE
ech = va[k];
va[k] = va[i];
va[i] = ech;
k++;
i--;
FIN TANT QUE
i = 0;
TANT QUE (i < n)
FAIRE
afficher va[i];
i++;
FIN TANT QUE
Ce qui donne en C :
main ()
{
int i, n, np, inter;
int v[40];
np = n/2;
...
}
On peut voir dans cette exemple que l'on peut avoir plusieurs instructions dans la
première et la troisième partie de l'instruction for. Dans ce cas, ces instructions sont
séparées par des ','.
Par exemple :
v[10] => v
0 1 2 3 4 5 6 7 8 9
En C, on peut également avoir des tableaux qui comportent plus d'une dimension.
Pour un tableau à deux dimensions, on peut également utiliser le terme de "matrice".
Exemple : tab[4][5]
nombre de lignes
tab :
0 1 2 3 4
0
1 7
2
3
Tab[1][2] = 7
Indice de colonne
Indice de ligne
6.2. Exemple
int tab[4][10];
i <- 0 ;
TANT QUE i < n FAIRE
j <- 0
TANT QUE j < m FAIRE
lire t[i][j]
j <- j + 1
FIN TANT QUE
i <- i + 1
FIN TANT QUE
i <- 0 ;
TANT QUE i < n FAIRE
j <- 0
TANT QUE j < m FAIRE
afficher t[i][j]
j <- j + 1
FIN TANT QUE
i <- i + 1
FIN TANT QUE
int tab[5][4][10];
Chapitre 12 :
Adresses et pointeurs
1. LES ADRESSES
On a vu que le nom d'une variable est un nom symbolique donné à une zone de la
mémoire.
Par exemple, si on écrit :
int a; /* 4 octets */
a = 2; a 2
a n'est qu'un nom que le programmeur a décidé de donner à une zone de la mémoire.
L'ordinateur, quant à lui, accède à cette zone grâce à son adresse.
a 2
x1B00
2. LES POINTEURS
Rappel : lorsque l'on écrit
float r; /* 4 octets */
on déclare que l'on va utiliser une variable de type float
?
r: contenu indéterminé !
Un pointeur est un type de variable qui occupe 4 octets et qui peut recevoir comme
valeur une adresse.
Remarques importantes :
la taille d'une variable de type pointeur est toujours la même (4 octets), que
ce pointeur contienne l'adresse d'un short, d'un int, d'un float ou d'un
double.
sur d'autres systèmes (OS et HW), la taille d'un pointeur peut être
différente de 4 octets, MAIS sur ce système, tous les pointeurs auront la
même taille.
exemple :
float *pv;
/* on déclare une variable de type pointeur de float */
int *pa;
/ * on déclare une variable de type pointeur de int */
3. EXEMPLE
14.0
11.0
main () 4.0 7.0
{
v w
float v, w ;
float *pv, *pa; pv pa
pv = &v; /* pv reçoit l’adresse de v */
/* on dit également que pv pointe sur v */
/*
* Comment modifier la valeur contenue dans la variable v ? :
* soit v = … soit *pv = … C’est la même chose !!!
* *pv permet d’accéder à la variable dont l'adresse est
* contenue dans pv ou à la variable pointée par pv
*/
*pv = 4.0 ;
pa = &w ;
scanf ("%f", &w) ; /* ou scanf("%f", pa) ; */
/* l’utilisateur entre 7.0 ↵ */
v = *pv + *pa ; /* v = 4.0 + 7.0 */
pv = pa; /* pv reçoit l’adresse contenue dans pa
* => pv contient maintenant l’adresse de la variable w
*/
v = *pv + *pa ; /* v = 7.0 + 7.0 = 14.0 */
}
4. RÉSUMÉ
5. EXEMPLES
5.1. Exemple 1 :
char c ; c ‘A’
char *pc ; ? ‘B’
pc ? ? ? ?
c = 'A' ; Adresse pointée est indéterminée au départ !!
*pc = 'B' ;
/*
* Attention, cette instruction va écrire n’importe où en
* mémoire avec le risque d’endommager d’autres données, voire
* le programme lui-même !
*/
5.2. Exemple 2 :
*pa = 'A' ;
*pb = 'B' ; 200 252
pa pb
if (*pa == *pb)
/*
* On compare le contenu des addresses pointées,
* donc ici, on compare 'A' avec 'B' !!
*/
{
...
}
...
if (pa == pb)
/*
* ici, on compare les addresses elles-mêmes !!
* donc ici, on compare 200 avec 252 !!
*/
{
...
}
5.3. Exemple 3 :
pc 200
pf 252
if (pc == pf)
...
/*
* comparaison de 2 adresses qui n’ont pas le même
* type
* => on doit forcer la comparaison en faisant un
* casting !!!
*/
int i ;
int *pi ; 4
75
float f; i 3
float *pf; adr 100
i = 3; 104
f = 5.0;
pi 100
i++;
pi = &i;
pi = pi + 1; /* ou pi++ */
/*
* pi est incrémenté d’une unité, c’est-à-dire de 4
* puisque pi est un pointeur d’entiers
* et qu’un entier occuper 4 octets !
*/ f 5.0
*pi = 75 ;
pf = &f ;
pf ++ ; pf
/*
* pf est incrémenté de 4 puisque pf est un pointeur de
* float !
*/
6.1. Exemple 1 :
float va [10] ;
float *pfa ; va 37.25 20.11
Adr 100 0 1 2 3 4 5 6 7 8 9
pfa = &va[2] ;
*pfa = 37.25 ; 108 112
pfa
pfa++ ;
*pfa = 20.11 ;
6.2. Exemple 2 :
v 20
int v[10];
Adr 200 0 1 2 3 4 5 6 7 8 9
int *pv;
pv = &v[0] ; 0204
*pv = 20 ; 0200
pv = pv + 1; pv
MOV BX,offset v
MOV [BX],20 => adressage indirect
ADD BX,4 => car un integer prend 4 octets
7. EXEMPLES
main ()
{
int i, n, np, inter;
int v[40];
np = n/2;
main ()
{
int i, np, *pa, *pb, inter;
int v[40];
pa = &v[0];
pb = &v[n-1];
i = 0;
pa = &v[0];
while (i < np)
{
inter = *pa;
*pa = *pb;
*pb = inter;
pa++;
pb--;
i++;
}
}
/*
* somme des éléments de chaque ligne
* en utilisant boucle POUR
*/
POUR (i variant de 0 à n-1 par pas de 1)FAIRE
total = 0
POUR (j variant de 0 à m-1 par pas de 1) FAIRE
total = total + ta[i][j]
FIN POUR
afficher total
FIN POUR
/*
* somme des éléments de chaque colonne
* en utilisant boucle TANT QUE …
*/
j = 0
TANT QUE (j < m) FAIRE
total = 0
i = 0
TANT QUE (i < n) FAIRE
total = total + ta[i][j]
i = i + 1
FIN TANT QUE
afficher total
j = j + 1
FIN TANT QUE
Un tableau tel que int ta[5][9] est implanté en mémoire ligne par ligne :
int ta[5][9] ;
0 A/D E
1 B
2
3
4 C
0 1 2 3 4 5 6 7 8
A : p = adresse de ta[0][0]
B :p = p + 9 (m = 9)
C : … p = adresse de ta[4][0]
D :p = p – 5 * 9 5 = n; 9 = m
=> retour à la première case de la colonne 0
E :p = p + 1 => passage à la première case de la colonne suivante !
/*
* somme des éléments de chaque ligne
* en utilisant boucle POUR
*/
p = adresse de ta[0][0]
POUR (i variant de 0 à n-1 par pas de 1)FAIRE
total = 0
POUR (j variant de 0 à m-1 par pas de 1) FAIRE
total = total + *p
p++
FIN POUR
afficher total
FIN POUR
/*
* somme des éléments de chaque colonne
* en utilisant boucle TANT QUE …
*/
j = 0
TANT QUE (j < m) FAIRE
p = adresse de ta[0][j]
total = 0
i = 0
TANT QUE (i < n) FAIRE
total = total + *p
p = p + m
i = i + 1
FIN TANT QUE
afficher total
j = j + 1
FIN TANT QUE
Chapitre 13 :
Les fonctions
1. UN PREMIER EXEMPLE
void message1()
{
printf ("Hello\n") ;
}
void message2()
{
printf ("Salut \n") ;
}
1
Hello
2
Salut
3
2. LE PROTOTYPE DE LA FONCTION
Le prototype de la fonction est écrit au début du fichier, avant la fonction main().
Le prototype de la fonction est utilisé par le compilateur pour vérifier le bon usage
de la fonction, c’est-à-dire si les appels à cette fonction sont corrects : bon passage de
paramètre(s) et bonne utilisation de la valeur de retour, s’il y en a !
3. COMMENTAIRE D’EN-TETE
/***********************************************************/
/* INPUT : description des paramètres d’entrée */
/* PROCESS : description de la tâche effectuée par la fct */
/* OUTPUT : description de la valeur retournée */
/***********************************************************/
Exemple :
main()
{
int n ;
/**************************************************/
/* INPUT : entier i : bon résultat du calcul */
/* à afficher */
/* PROCESS : affiche le bon résultat */
/* OUTPUT : / */
/**************************************************/
void erreur (int i)
{
printf ("\nla réponse correcte est : %d \n", i) ;
}
/**************************************************/
/* INPUT : / */
/* PROCESS : affiche un message de félicitation */
/* OUTPUT : / */
/**************************************************/
void bravo(void)
{
printf ("\nBravo, votre réponse est correcte\n");
}
Dans les appels de la fonction “erreur()”, les valeurs 7 et 12 sont les paramètres
effectifs, tandis que i, dans la fonction est un paramètre fictif.
Fonctionnement :
affichage de 4+3=?
si l’utilisateur entre 15
affichage de la réponse correcte est 7
affichage de 4*3=?
si l’utilisateur entre 12
affichage de Bravo, votre réponse est correcte
Main, erreur et bravo sont trois fonctions. Main et erreur possèdent chacune des
variables locales qui ne sont connues que au sein de celles-ci :
main erreur
n i
5. PASSAGE DE PARAMETRE(S)
Exemple :
void affiche(int) ;
main ()
{
int i ;
Ce qui apparaît à l’écran
i = 5 ;
affiche (2) ; val = 2
affiche (i) ; val = 5
affiche (i * 3 + 4) ; val = 19
affiche (2.3) ; val = 2
affiche (i / 2) ; val = 2
}
Dans cet exemple, à chaque appel de la fonction "affiche", la fonction main lui passe un
et un seul paramètre.
Peu importe s’il s’agit :
- d’une constante
- d’une variable
- d’une expression
La conversion est même effectuée implicitement si nécessaire (2.3 2).
5.1. Exemple 1
main ()
{
float x=2.7;
Ce qui apparaît à l’écran
afficheProduit (5, x) ;
printf ("fin\n") ; 5 * 2.7 = 13.5
} fin
/********************************************/
/* input : int i, float n (normalement, le */
/* commentaire doit être plus parlant) */
/* process : calcule et affiche le produit */
/* de deux nombres : un entier et */
/* un réel */
/* output : / */
/********************************************/
void afficheProduit (int i, float n)
{
printf ("%d * %f = %f\n", i, n, i*n);
}
5.2. Exemple 2
void Fct(int) ;
main ()
{
int i=4; i 4
Fct (i + 3) ;
Ce qui apparaît à l’écran
printf ("i = %d\n", i);
}
i = 4
void Fct (int j)
{
int i;
10
i = j + 1; j 7 i 8
j = j + 3;
}
Attention, dans cet exemple, la variable i dont le contenu est modifié dans Fct, (i = j
+ 1;), est une variable locale à la fonction Fct(), elle ne correspond pas à la variable i de la
fonction main : il s’agit d’un autre emplacement mémoire.
void Fct(int) ;
main ()
{
int i, j; i j
i = 4;
j = 3;
f(i);
}
k = i; n k
}
La variable i de la fonction main n’est pas connue dans les autres fonctions
6. VALEUR DE RETOUR
Dans l'exemple suivant, la fonction retourne une valeur :
int triple(int) ;
main ()
{ 4 12
int i = 4, j; i j
j = triple (i);
printf ("j = %d\n", j);
}
6.1. Exercice
Saisir 2 entiers a et b
Calculer soit r = a + b si a > b A réaliser avec une
soit r = a + 3 * b si a <= b fonction
afficher a, b, r
main ()
{
int a, b;
4 6 22
/********************************************/
/* input : int x, int y : les deux valeurs */
/* utilisées dans le calcul */
/* process : effectue une opération en */
/* fonction des valeurs de x et y */
/* output : le résultat de l'opération */
/* effectuée */
/********************************************/
int calcul (int x, int y)
{
int w;
if (x > y)
w = x + y;
else
w = x + 3 * y;
return (w);
}
OU
Conclusions :
une fonction a un et un seul point d'entrée
une fonction a un ou plusieurs points de sortie
float moitie(int);
main ()
{
int i = 4;
float x;
x = moitie (i + 3);
/********************************************/
/* input : int n : nombre dont on doit */
/* calculer la moitié */
/* process : calcule la moitié du nombre */
/* donné */
/* output : le résultat de la division */
/********************************************/
float moitie (int n)
{
return (n / 2); main : i 4 x 3.0
}
moitie : n 7 => n/2 = 3
/*
* Dans cette première solution, le résultat donné
* sera toujours un entier : casting
* => meilleures solutions ci-dessous :
*/
OU
7.1. Exemple :
main ()
{
int j; Ce qui apparaît à l’écran
i = 5;
3
fct (3);
printf("%d\n", i);
}
7.2. Définitions
Une variable locale est définie dans une fonction et connue uniquement dans
cette fonction (stack segment).
Une variable globale est définie hors de toute fonction et est connue de toutes
les fonctions (data segment).
7.3. Exemple
void fct (int);
int i = 4; i 4
main ()
{
int i; i 5
i = 5;
fct (i + 1); Ce qui apparaît à l’écran
La variable globale i n'est accessible dans une fonction que si cette fonction ne
! contient pas de variable locale i .
Il est de loin préférable d'utiliser au maximum les variables locales et donner aux
fonctions toutes les données dont elles ont besoin au moyen des paramètres. Elles
peuvent également utiliser la valeur de retour pour communiquer un résultat à la
fonction appelante.
8.1. Exemple :
main ()
{
int a, b, r;
/********************************************/
/* input : x et y deux entiers positifs */
/* process : calcule le PGCD des 2 nombres */
/* output : le PGCD */
/********************************************/
/********************************************/
/* input : x et y deux entiers positifs */
/* process : détermine le max des 2 nb */
/* output : le maximum */
/********************************************/
if (x > y)
imax = x;
else
imax = y;
return imax;
}
Dans cet exemple, la fonction pgcd fait appel à la fonction max pour déterminer quel est
le plus grand des deux nombres.
Remarquons également la manière dont la valeur de retour de la fonction max est
utilisée dans une condition de la fonction pgcd.
i = 2;
j = triple (&i);
Le triple de 2 vaut 6
On peut voir sur cet exemple que cela fonctionne également correctement quand
on utilise des pointeurs comme paramètres.
9.3. Exemple
fct (&j);
Lorsque l'on passe l'adresse d'une variable à une fonction, on peut modifier la
valeur de cette variable
Cela fonctionne bien, mais on aurait pu tout aussi bien retourner une valeur au
moyen d'un "return".
main ()
{
int v[50], n;
/************************************************/
/* input : nbr : nombre d'éléments ds le vect */
/* pt : adresse du vecteur */
/* process : lit nbr éléments et les place dans */
/* le vecteur dont l'adresse est donnée */
/* output : / */
/************************************************/
void LireVect (int nbr, int *pt)
{
int i;
/************************************************/
/* input : nbr : nombre d'éléments ds le vect */
/* pt : adresse du vecteur */
/* process : affiche les nbr éléments du vecteur*/
/* output : / */
/************************************************/
void AfficherVect (int nbr, int *pt)
{
int i;
v 10 20 30 40 50
0 1 2 3 4
int x, *p;
10.1. Exemple
main ()
{ v 10 20 30 40 50
int v[5];
int x, *pa; 0 1 2 3 4
x = *pa + 1; /* x reçoit 21 */
x = *(pa + 1); /* pa pointe sur v[1] */
/* pa + 1 pointe sur v[2] MAIS pa n'est
pas modifié !!! */
/* x reçoit 30 (contenu de v[2]) */
/*
* Ces deux instructions sont différentes des
* deux suivantes !!!
*/
10.2. Exercice
Rechercher l'élément le plus grand (le maximum) du vecteur.
Principe :
ind_max = 0;
for (i = 1 ; i < n ; i++)
{
if (v[i] > v[ind_max])
ind_max = i;
}
On pourrait écrire ce code tel quel dans la fonction main, mais on demande
d'écrire une fonction qui recherche l'élément maximum d'un vecteur et retourne un
pointeur sur cet élément.
Cette fonction ne reçoit que l'adresse du début du vecteur et le nombre
d'éléments utilisés.
main ()
{
int v[10], n, *p;
/************************************************/
/* input : nbr : nombre d'éléments ds le vect */
/* pt : adresse du vecteur */
/* process : lit nbr éléments et les place dans */
/* le vecteur dont l'adresse est donnée */
/* output : / */
/************************************************/
void LireVect (int nbr, int *pt)
{
int i;
/************************************************/
/* input : nbr : nombre d'éléments ds le vect */
/* pt : adresse du vecteur */
/* process : recherche l'élément max du vecteur */
/* output : adresse de l'élément max */
/************************************************/
int *ad_max (int nbr, int *pt)
{
int *pmax, i;
pmax = pt++;
/*
* pmax reçoit l'adresse contenue dans pt
* => pmax pointe vers v[0]
* pt pointe vers l'élément suivant du vecteur
*/
return (pmax);
}
/************************************************/
/* input : nbr : nombre d'éléments ds le vect */
/* pt : adresse du début du vecteur */
/* process : recherche l'élément max du vecteur */
/* output : indice de l'élément max */
/************************************************/
int ind_max (int nbr, int *pt)
{
int imax=0, i;
return (imax);
}
11.1. Exemple :
Ecrire une fonction qui compte le nombre de 0 dans un tableau et qui retourne le
nombre obtenu.
main ()
{
int tab[4][5], x;
/************************************************/
/* input : pt : adresse du tableau */
/* l : nombre de lignes utiles du tableau */
/* c : nombre de colonnes utiles du tableau*/
/* process : compte le nombre de '0' dans le */
/* tableau */
/* output : retourne le nombre trouvé */
/************************************************/
int CompteZero (int *pt, int l, int c)
{
int i, j, cptr = 0;
/************************************************/
/* input : pt : adresse du tableau */
/* l : nombre de lignes utiles du tableau */
/* c : nombre de colonnes utiles du tableau*/
/* process : initialise le contenu du tableau */
/* output : / */
/************************************************/
void LireTab (int *pt, int l, int c)
{
int i, j;
/************************************************/
/* input : pt : adresse du tableau */
/* l : nombre de lignes utiles du tableau */
/* c : nombre de colonnes utiles du tableau*/
/* process : affiche le contenu du tableau */
/* output : / */
/************************************************/
void AfficheTab (int *pt, int l, int c)
{
int i, j;
On peut choisir de définir le nombre réel de lignes ainsi que le nombre réel
de colonnes au moyen d'une instruction "#define" au début du programme.
Exemple :
#define MAXCOL 50
#define MAXLIG 50
#define MAXCOL 50
#define MAXLIG 50
main ()
{
int tab[MAXLIG][MAXCOL], x;
/************************************************/
/* input : pt : adresse du tableau */
/* maxcol : nombre réel de colonnes */
/* dans le tableau */
/* l : nombre de lignes utiles du tableau */
/* c : nombre de colonnes utiles du tableau*/
/* process : compte le nombre de '0' dans le */
/* tableau */
/* output : retourne le nombre trouvé */
/************************************************/
int CompteZero (int *pt, int maxcol,
int l, int c)
{
int i, j, cptr = 0, *ptmp;
/************************************************/
/* input : pt : adresse du tableau */
/* maxcol : nombre réel de colonnes */
/* dans le tableau */
/* l : nombre de lignes utiles du tableau */
/* c : nombre de colonnes utiles du tableau*/
/* process : initialise le contenu du tableau */
/* output : / */
/************************************************/
void LireTab (int *pt, int maxcol, int l, int c)
{
int i, j, *ptmp;
/************************************************/
/* input : pt : adresse du tableau */
/* maxcol : nombre réel de colonnes */
/* dans le tableau */
/* l : nombre de lignes utiles du tableau */
/* c : nombre de colonnes utiles du tableau*/
/* process : affiche le contenu du tableau */
/* output : / */
/************************************************/
void AfficherTab(int *pt, int maxcol,
int l, int c)
{
int i, j, *ptmp;
n 7 v 4 8 12 15 20 18 19
0 1 2 3 4 5 6
6
n 7 v 4 8 12 20 18 19 1199
0 1 2 3 4 5 6
Exemples d'initialisation :
int v[5] = {1, 2, 3, 4, 5};
int tab[4][3] = {
{4, 9, 3},
{5, 3},
{1, 4, 6}
}
Les cases du tableau pour lesquelles l'utilisateur n'a pas donné de valeur seront
initialisées à '0'.
! MAIS si on n'initialise aucune case du tableau, celles-ci contiendront toutes une
valeur indéterminée !!!
Chapitre 14 :
Tableaux de caractères
1. UN PREMIER EXEMPLE
char VCh[10];
/*
* Réservation d'un tableau de 10 octets :
* un caractère occupe un octet
*/
VCh[0] = 'W';
VCh[1] = 'I'; VCh W I N N I E ? ? ? ?
VCh[2] = 'N'; 0 1 2 3 4 5 6 7 8 9
VCh[3] = 'N';
VCh[4] = 'I';
VCh[5] = 'E';
Ce tableau est un tableau comme on en a vu jusqu'à présent, les cases peuvent contenir
chacune un caractère. Le tableau est donc constitué de 10 cases d'un octet chacune.
/************************************************/
/* input : pc : adresse du tableau */
/* nbElem : nombre de caractères */
/* contenus dans le vecteur */
/* process : affiche le contenu du tableau */
/* output : / */
/************************************************/
void AfficherVCar (char *pc, int nbElem)
{
int i;
/************************************************/
/* input : pc : adresse du tableau */
/* nbElem : nombre de caractères */
/* maximum permis dans le vecteur */
/* process : saisit le contenu du tableau */
/* output : nombre de caractères saisis */
/************************************************/
int LireVCar (char *pc, int nbElem)
{
int i;
i = 0;
fflush(stdin);
do
{
*pc = getchar();
i++;
pc++;
} while (*(pc-1) != '\n' && i < nbElem);
return(i);
}
3. EXERCICE 1
Ecrire un programme qui indique le nombre de voyelles dans un vecteur de caractères.
#define MAXELEM 50
main ()
{
char v[MAXELEM];
int n;
/************************************************/
/* input : pc : adresse du tableau */
/* nbElem : nombre de caractères */
/* maximum permis dans le vecteur */
/* process : saisit le contenu du tableau */
/* output : nombre de caractères saisis */
/************************************************/
int LireVCar (char *pc, int nbElem)
{
int i=0;
fflush(stdin);
do
{
*pc = getchar();
i++;
pc++;
} while (*(pc-1) != '\n' && i < nbElem);
return(i);
}
/************************************************/
/* input : pc : adresse du tableau */
/* nbElem : nombre de caractères */
/* dans le vecteur */
/* process : compte le nombre de voyelles dans */
/* ce vecteur */
/* output : nombre de voyelles trouvées */
/************************************************/
int NbrVoyelles (int nbElem, char *pc)
{
int k, cpt = 0;
/************************************************/
/* input : c : caractère voyelle ou consonne ? */
/* process : détermine si le caractère est une */
/* voyelle */
/* output : 1 si le caractère est une voyelle */
/* 0 sinon */
/************************************************/
int EstVoyelle (char c)
{
int i = 0;
switch (c)
{
case 'A':
case 'E':
case 'I':
case 'O':
case 'U':
case 'Y': i = 1;
}
return (i);
}
4. BOUCLE ET GETCHAR()
Lorsqu'on a abordé l'instruction scanf au début du cours, on a dit qu'il s'agissait d'une
instruction dangereuse car elle comporte deux inconvénients majeurs :
1. Si on entre <ENTER> avant d'avoir tapé une valeur, l'instruction ne donne
pas de message d'erreur. Elle se contente de ne pas modifier le contenu de
la variable.
Voyons comment on peut se passer entièrement de scanf(), même pour saisir les
entiers !
Rappel :
char a, b, c, d;
stdin '1' '2' '5' ↵
a = getchar(); ↵
125↵ a '1' 0 1 2 3
b = getchar(); b '2'
c = getchar(); c '5'
d = getchar(); d ↵
char c;
int i = 0;
c = getchar ();
while ((c >= '0' && c <= '9') && (i <= 999))
{
i = i * 10 + (c – 48);
/* c – 48 : code ASCII => chiffre */
c = getchar();
}
=> ↵
127↵ : i => i 1
=> i 12
=> i 127
=> 12A ↵ : i => i 1
=> i 12
=> sortie de la boucle
Si la valeur saisie n'est pas dans les limites 0 à 9999, la valeur de i qui en résulte ne
correspondra pas à la valeur saisie.
Notes :
↵' = '0A' en hexadécimal
'↵
48 en décimal = 30 en hexadécimal; or, en code ASCII, les nombres sont
de 30 à 39 en hexadécimal => en faisant, par exemple 38 – 30 en
hexadécimal, il reste 8, qui est le chiffre dont on a besoin
c = getchar ();
OU
else
{
printf ("erreur, ");
printf ("i doit être un entier positif < 10000");
fflush (stdin);
i = 0;
c = getchar();
}
Une telle boucle est évidemment plus longue à écrire que l'instruction scanf, mais
elle offre une plus grande sécurité. Elle permet également de contrôler les
dépassements de valeur suivant le type de données attendu.
Chapitre 15 :
1. DEFINITION
1.1. Exemples
char ch1[10]={"Donald"};
char ch2[]={"Donald"};
char ch3[6]={"Donald"};
! ch1 'D'
0
'o'
1
'n'
2
'a'
3
'l'
4
'd'
5
'\0'
ATTENTION, ce dernier exemple va poser problème : on utilise le byte qui suit l'espace
mémoire que l'on a réservé pour la chaîne de caractères => on risque d'effacer une
information nécessaire au bon fonctionnement du programme !
On doit donc impérativement réserver une case du vecteur pour y
placer le '\0'
Le '\0' sert à détecter la fin de la chaîne de caractères
On appelle le '\0' le caractère de fin de chaîne
char nom1[10]={"Donald"};
char nom[30];
Exemple : Donald ↵
[Link]. Gets
Prototype :
dans stdio.h
[Link]. printf
char nom[30];
[Link]. puts
char nom[30];
puts (nom);
ou
puts (&nom[0]);
Puts affiche une chaîne sur le stdout suivie d'un caractère de fin de ligne (donc
d'un passage à la ligne).
Si l'instruction s'est déroulée sans problème, la fonction retourne le dernier
caractère affiché, sinon, elle retourne EOF.
puts (nom);
est équivalent à :
3.3. Exercice
Ecrire une fonction qui reçoit l'adresse du début d'un vecteur de caractères et qui
saisit au clavier une chaîne à placer dans ce vecteur.
Il s'agit donc d'écrire une fonction qui effectue le même travail que "gets", sans
utiliser ni "gets", ni "scanf".
/************************************************/
/* input : pc, adresse d'un vecteur de max */
/* elements */
/* process : la fonction saisit une suite de */
/* caractères qu'elle assigne au vecteur */
/* d'adresse pc. */
/* output : // */
/************************************************/
fflush (stdin);
c = getchar();
while ((i < max-1) && (c != '\n'))
{
*pc = c;
pc++;
i++;
c = getchar();
}
Le paramètre max permet de contrôler que l'on ne sort pas de l'espace réservé !
Cette méthode est la plus sûre pour saisir une chaîne de caractères; elle est
d'ailleurs recommandée par B. W. Kernighan et D. M. Ritchie (principaux créateurs du
langage C).
/************************************************/
/* input : pc, adresse d'un vecteur de max */
/* elements */
/* process : la fonction saisit une suite de */
/* caractères qu'elle assigne au vecteur */
/* d'adresse pc. */
/* output : renvoie le nombre de caractères de */
/* la chaîne */
/************************************************/
fflush (stdin);
c = getchar();
while ((i < max-1) && (c != '\n'))
{
*pc = c;
pc++;
i++;
c = getchar();
}
return (i);
}
char nom[10];
nom = "Donald" ;
Pour pouvoir affecter une valeur à une chaîne de caractères, il faut procéder
comme suit :
char nom[10];
nom[0] = 'D';
nom[1] = 'o';
nom[2] = 'n';
nom[3] = 'a';
nom[4] = 'l';
nom[5] = 'd';
nom[6] = '\0';
[Link]. Exercice
Ecrire une fonction qui copie le contenu d'une chaîne de caractères dans une
autre chaîne de caractères.
Proposition de solution :
/*******************************************************/
/* input : pta : adresse de la chaîne de destination */
/* ptb : adresse de la chaîne source */
/* process : copie le contenu de la chaîne source */
/* dans la chaîne de destination */
/* output : adresse de la chaîne de destination */
/*******************************************************/
char * CopierChaineCaract (char * pta, char * ptb)
{
char *p;
p = pta;
return (p);
}
[Link]. strcpy
Il existe une fonction, dont le prototype est donné dans le fichier "string.h", qui
effectue la même tâche que celle de l'exercice précédent : il s'agit de strcpy.
Prototype :
dans string.h
[Link]. strncpy
Il existe également une fonction semblable strncpy :
Prototype :
dans string.h
[Link]. Exercice
Ecrire une fonction qui détermine la longueur d'une chaîne de caractères.
Proposition de solution :
/************************************************/
/* input : pc : adresse de la chaîne de caract */
/* process : détermine la longueur de la chaîne */
/* output : longueur de la chaîne reçue en param*/
/************************************************/
int LongueurChaineCaract (char *pc)
{
int lg=0;
return (lg);
}
[Link]. strlen
Il existe une fonction, dont le prototype est donné dans le fichier "string.h", qui
effectue la même tâche que celle de l'exercice précédent : il s'agit de strlen
Prototype :
dans string.h
[Link]. Exercice
L'exercice consiste à comparer le contenu d'une chaîne de caractères avec le
contenu d'une autre chaîne de caractères.
Proposition de solution :
/************************************************/
/* input : pa : adresse de la première chaine */
/* pb : adresse de la deuxième chaine */
/* process : compare le contenu des 2 chaines */
/* output : 1 si 1er chaine est > 2eme */
/* 0 si 2 chaines sont identiques */
/* -1 si 2eme chaine est > 1er */
/************************************************/
int ComparerChainesCaract (char *pa, char *pb)
{
int comp=0;
if (comp == 0)
{
if (*pa == '\0')
{
if (*pb != '\0')
comp = -1;
}
else
comp = 1;
return (comp);
}
[Link]. strcmp
Il existe une fonction, dont le prototype est donné dans le fichier "string.h", qui
effectue la même tâche que celle de l'exercice précédent : il s'agit de strcmp
Prototype :
dans string.h
[Link]. strcat
Prototype :
dans string.h
[Link]. strchr
Prototype :
dans string.h
[Link]. strstr
Prototype :
dans string.h
Exemples :
ou
Chapitre 16 :
Tableaux de pointeurs
1. RAPPEL
main ()
{ a b
int a, b; X0156 X015A
int *pa, *pb;
int v[2]; X0156 X015A
pa pb
pa = &a;
pb = &b; 10 20
v
v[0] = 10; 0 1
v[1] = 20;
...
}
2. UN VECTEUR DE POINTEURS
main ()
{ a 5 8
b
int a, b; X0156 X015A
int * vp[2];
3. EXERCICE 1
Ecrire une fonction qui permet de connaître l'adresse du maximum et du minimum d'un
vecteur.
Remarque : il est impossible pour une fonction d'avoir 2 valeurs de retour, il faut donc
utiliser un vecteur pouvant contenir 2 adresses d'entier (une adresse pour le maximum et une
pour le minimum). Nous utiliserons donc un vecteur de pointeurs d'entiers (vp).
main ()
{
int v[10];
int * vp[2];
LireVect(&v[0], 10);
pv++;
for (i = 1; i < nbElem; i++, pv++)
{
if (*pv > max)
{
max = *pv;
*ppv = pv;
}
else
{
if (*pv < min)
{
min = *pv;
*(ppv + 1) = pv;
}
}
}
}
Chapitre 17 :
Les structures
1. DEFINITIONS
Les types connus du C sont entre autre : int, char, float, double, etc.
2. DECLARATIONS
struct Date {
int jour;
int mois;
int annee;
};
Les composants d'une structure sont appelés les champs. Dans l'exemple ci-
dessus, la structure "Date" contient 3 champs dont les noms sont "jour", "mois" et
"annee".
main ()
{
struct Date examen;
...
}
en mémoire :
examen
jour mois annee
1B00 1B04 1B08
struct Date {
int jour;
int mois;
int annee; examen 15 6 2003
};
main ()
{
struct Date examen;
[Link] = 15;
[Link] = 6;
[Link] = 2003;
struct Fiche {
char nom[20];
int age;
int cotisation;
char genre;
};
main ()
{
struct Fiche membre;
gets ([Link]);
scanf ("%d", &[Link]);
[Link] = 25;
[Link] = getchar();
...
} nom age cotisation genre
membre
X1B00 X1B14 X1B18 X1B1C
struct Fiche {
char nom[20];
int age;
int cotisation;
char genre;
};
void afficherStruct (struct Fiche);
main ()
{
struct Fiche membre;
gets ([Link]);
scanf ("%d", &[Link]);
[Link] = 25;
[Link] = getchar();
afficherStruct(membre);
...
}
On peut également choisir de donner l'adresse d'une variable de type structuré à une
fonction. Dans ce cas,
Voici un exemple :
struct Fiche {
char nom[20];
int age;
int cotisation;
char genre;
};
main ()
{
struct Fiche membre;
InitStruct(&membre);
...
}
Exemple :
...
}
Exemple :
struct Date {
int jour;
int mois;
int annee;
};
struct Fiche {
char nom[20];
struct Date naissance;
int age;
int cotisation;
char genre;
};
7. TYPEDEF
On a vu dans les exemples donnés jusqu'à présent que chaque fois que l'on désire
déclarer une variable d'un type structuré, on doit répéter le mot clé "struct". Afin d'éviter
cela, il suffit d'utiliser le mot clé "typedef" au moment de la déclaration de la structure, on
peut alors se contenter du nom du type défini lors de la déclaration ultérieure de variables.
Syntaxe :
OU
Exemple :
struct TypeDate {
int jour;
int mois;
int annee;
};
typedef struct TypeDate Date;
typedef struct {
char nom[20];
Date naissance;
int age;
int cotisation;
char genre;
} Fiche;
main()
{
Fiche membre, listeMembre[10];
Fiche *pf;
...
}
struct SPers {
char nom[10];
unsigned short poids;
};
struct SPers {
char nom[9];
unsigned short poids;
};
sizeof(struct sPers) ≡ 12
(9 + 1 + 2)
Un byte pour forcer
l'alignement à une adresse
paire du champ "poids"
Chapitre 18 :
1. DEFINITION
Un fichier est un ensemble de données qui se trouvent sur un disque.
HD
buffer
La taille par défaut du buffer est de 512 octets. Lors de l'ouverture d'un
fichier, on recopie donc au maximum 512 octets de ce fichier dans la mémoire RAM. La
taille du buffer peut être modifiée à l'aide de l'instruction setbuf().
L'ouverture d'un fichier est réalisée à l'aide de la fonction fopen().
Pour accéder au contenu du fichier, il faut en réalité accéder au contenu du
buffer. Pour cela, il faut se servir d'un pointeur que l'on va déplacer dans le buffer
(l'évolution de ce pointeur se fait par l'utilisation de certaines fonctions "disque").
FILE * fp;
fp = fopen("a:[Link]", "rb");
/* fp = adresse logique du fichier */
La valeur retournée par la fonction fopen est de type "FILE *"; c'est-à-dire une
adresse vers le type prédéfini FILE. Ce type FILE est un type structuré. Les
différents champs de ce type FILE sont utilisés par les fonctions "disque" pour assurer
la gestion du fichier.
Dans l'exemple ci-dessus, le fichier "a:[Link]" est ouvert en mode binaire b et
en lecture seule r.
La valeur retournée est stockée dans le pointeur fp.
Le premier paramètre est une chaîne de caractères qui donne le chemin d'accès
ainsi que le nom du fichier que l'on désire ouvrir.
Exemples : a:[Link]
c:\ETUDIANT\[Link]
Les différentes lettres données ci-dessus seront suivies soit de 'b' pour binaire
soit de 't' pour texte. Ces lettres 'b' et 't' permettent de préciser la manière dont les
caractères doivent être interprétés. Voir ci-dessous pour plus de détails.
[Link]. r+ :
Si le fichier n'existe pas, la fonction fopen retourne NULL
Si le fichier existe, il est ouvert
On peut accéder au fichier en lecture ET écriture
Après l'ouverture, le pointeur contient l'adresse du début du fichier
[Link]. w+ :
Si le fichier n'existe pas, un nouveau fichier vide est créé
Si le fichier existe, il est détruit et un nouveau fichier vide est créé
On peut accéder au fichier en lecture ET écriture
Après l'ouverture, le pointeur contient l'adresse du début du fichier
[Link]. a+ :
Si le fichier n'existe pas, un nouveau fichier vide est créé
Si le fichier existe, il est ouvert
On peut y accéder en lecture ET écriture (l'écriture se faisant
uniquement en fin de fichier)
Après l'ouverture, le pointeur donne l'adresse du début du fichier (si on
y accède en lecture) ou celle de la fin du fichier (si on y accède en
écriture).
[Link]. Remarque
En mode '+', les créateurs du turbo C conseillent de toujours positionner le
! pointeur de fichier avant chaque accès (lecture ou écriture).
[Link]. Résumé :
! r w a r+ w+ a+
destruction et destruction et
le fichier création d'un création d'un
ouverture ouverture ouverture ouverture
existe nouveau nouveau
fichier fichier
position du
lecture : au
pointeur de début du au début du
début du à la fin du au début du début
fichier juste fichier (qui est fichier
fichier fichier fichier écriture : à la
après vide !) (qui est vide)
fin
l'ouverture
2.5. Exemple
#include <stdio.h>
main()
{
FILE *fp;
typedef struct {
short level; /* fill/empty level of buffer */
unsigned flags; /* File status flags */
char fd; /* File descriptor */
unsigned char hold; /* Ungetc char if no buffer */
short bsize; /* Buffer size */
unsigned char *buffer; /* Data transfer buffer */
unsigned char *curp; /* Current active pointer */
unsigned istemp; /* Temporary file indicator */
short token; /* Used for validity checking */
} FILE; /* This is the FILE object */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
main()
{
FILE *fp;
/*
* Affiche simple du code d'erreur
*/
printf ("\n%d", errno);
/*
* strerror retourne le message d'erreur correspondant au
* code d'erreur actuel
*/
printf ("\n\n%s\n", strerror(errno));
}
On trouve les différentes valeurs possibles de "errno" ainsi que leur signification
dans le fichier d'entête errno.h
Paramètres d'entrée :
void *ptr : adresse de la RAM où il faut écrire les données lues
size_t size : taille d'un enregistrement
size_t n : nombre d'enregistrements à lire
FILE *fp : adresse logique du fichier.
Exemple : on souhaite lire les 5 premiers enregistrements d'un fichier contenant des
chaînes de 20 caractères.
#include <stdio.h>
main()
{
FILE *fp;
char tabChar[50][20];
int nbElem;
size_t fwrite (const void *ptr, size_t size, size_t n, FILE *fp);
Paramètres d'entrée :
void *ptr : adresse de la RAM où on trouve les données à écrire
size_t size : taille d'un enregistrement
size_t n : nombre d'enregistrements à écrire
FILE *fp : adresse logique du fichier.
Cette fonction retourne 0 si le fichier a pu être fermé sans problème. Sinon, 'EOF' est
retourné.
Imaginons par exemple un fichier dans lequel on va enregistrer les fiches des chiens
participants à un concours.
Chaque fiche est en réalité une structure composée des champs suivants :
une chaîne de 30 caractères : nom du chien
une chaîne de 30 caractères : nom du propriétaire
une chaîne de 40 caractères : l'adresse du propriétaire du chien
3 entiers permettant de donner la date de naissance du chien : jour, mois,
année
un entier pour le numéro de pédigrée
un entier pour le classement en fin de concours.
Dans le fichier :
date naissance
L'avantage d'un fichier structuré est que tous ses enregistrements ont la même taille.
On peut donc facilement calculer le nombre d'octets qui nous séparent de l'enregistrement
qui nous intéresse.
Cette fonction retourne '0' si elle s'est bien déroulée, et une valeur différente de '0'
en cas de problème.
Paramètres d'entrée :
FILE *fp : adresse logique du fihcier dans lequel on veut modifier la
position du pointeur
long offset : nombre d'octets dont le pointeur doit se déplacer par rapport
à un point de référence. Cet offset peut être positif : on se rapproche de
la fin du fichier ou négatif on revient vers le début du fichier.
int whence : point de référence du déplacement :
SEEK_SET = 0 : début du fichier
SEEK_CUR = 1 : position actuelle du pointeur de fichier
SEEK_END = 2 : fin du fichier
"fseek" nous permettra donc de nous déplacer directement vers l'enregistrement qui
nous intéresse, sans devoir lire tous les enregistrements précédents.
Exemple :
#include <stdio.h>
main()
{
FILE *fp;
char tabChar[50][20];
int nbElem;
! le buffer, le contenu actuel du buffer est enregistré sur le disque et le buffer est rempli
avec d'autres données.
8.1. ftell
8.2. sizeof
#include <stdio.h>
typedef struct {
...
} FICHE;
main()
{
FILE *fp;
int nbEnreg;
8.3. feof
8.4. rewind
8.5. perror
8.6. rename
8.7. remove
Remarque : il s'agit d'une macro, non d'une fonction. Cela signifie qu'au cours de
la compilation, le compilateur va recopier le code de la macro à l'emplacement de son
appel => accroissement du nombre de lignes de code. Cela est donc différent d'un appel
à une fonction.
Cette macro fait appel à la fonction suivante : unlink().
Chapitre 19 :
1. L'ALLOCATION DYNAMIQUE
1.1. Introduction
Rappel :
main ()
{
int v[10];
...
}
Pire encore :
main ()
{
char tab[50][20];
...
}
Le tableau occupe toujours la même place, quel que soit le nombre de lignes utilisées et
quelle que soit la longueur de chaque nom !
Rappel :
main ()
{
int vb[10]; /* variable locale à la fct main */
int *pi; /* variable locale à la fct main */
...
}
CS
DS
va
HEAP
SS
vb, pi
Exemple :
main ()
{
int i, *pi;
char *pc;
if (pc == NULL)
...
...
}
Exemple :
main ()
{
char *pc;
1.4. Exemple
Prenons le cas d'un vecteur de chaînes de caractères ou vecteurs de noms ou
encore tableau de caractères.
SS HEAP
0 J U L E S \0
1 R O S E \0
2
E M I L E \0
3 A N T O I N E \0
4
char *x;
char *t[5];
char buf[20];
/* saisie clavier */
for (i = 0 ; i < 5 ; i++)
{
gets(buf);
t[i] = (char *) malloc (strlen(buf) + 1);
strcpy (t[i], &buf[0]);
}
Exemple de code pour trier les chaînes de caractères par ordre alphabétique :
Dans cet exemple, on ne réserve que les octets qui sont réellement utilisés par les
chaînes de caractères. Mais le nombre de chaînes maximum est toujours fixé à la
compilation (taille du tableau de pointeurs).
Exemple :
struct FICHE {
char nom[20];
int age;
struct FICHE *psvt;
};
Cette instruction ne réserve aucun espace en mémoire, elle définit simplement une
structure de données qui pourra être utilisée par la suite.
Exemple d'utilisation :
struct _FICHE {
char nom[20];
int age;
struct _FICHE *psvt;
};
main ()
{
int i = 0;
FICHE *pdebut = NULL, *ptemp = NULL;
char cont = 'O';
}
printf ("Vous avez enregistré %d élément(s)", i);
ptemp = pdebut;
n = i;
for (i = 0; i < n; i++)
/*
* ou mieux car on n'a pas besoin de connaître le nombre
* d'éléments dans la SDD :
* while (ptemp != NULL)
*/
{
puts (ptemp->nom);
printf ("age : %d\n", ptemp->age);
ptemp = ptemp->psvt;
}
}
struct FICHE {
struct FICHE *pprec;
char nom [20];
int age;
struct FICHE *psvt;
};
En informatique, le concept de pile peut être réalisé soit dans un tableau, soit à
l'aide d'un chaînage (SDD).
Dans une pile, on ne sait accéder qu'au dernier élément qui a été placé dans celle-
ci. On peut comparer ce principe à une pile de livres, on ne sait prendre que le livre qui
se trouve au sommet, si on veut accéder à un autre, on doit d'abord enlever ceux qui se
trouvent au-dessus de lui :
On peut implémenter une pile soit au moyen d'un tableau, soit au moyen d'un chaînage,
donc d'une structure de données dynamique.
Dans un tableau :
0 A i 3
1 B
2 C
3 D
4
5
struct PILE {
char nom[20];
int age;
struct PILE *psvt;
};
struct PILE ppile;
236
175
Nous allons écrire la fonction qui permet d'ajouter un élément au sommet de la pile en
considérant que celle-ci est implémentée au moyen d'une SDD
/*******************************************************
* input : adresse du pointeur du début de pile
* adresse d'une structure contenant l'info à
* copier dans l'element à ajouter au sommet
* de la pile
* process : ajoute un nouvel élément au sommet de la
* pile et copie dans cet élément les valeurs
* reçues dans la structure donnee en 2° param
* output : 1 si l'allocation de mémoire OK
* 0 si KO
******************************************************/
int empiler (struct PILE **Ad_Pile, struct PILE *t)
{
struct PILE *ptemp;
int contr=0;
return (contr);
}
autre possibilité :
/*******************************************************
* input : adresse du début de pile
* adresse d'une structure contenant l'info à
* copier dans l'élément à ajouter au sommet
* de la pile
* process : ajoute un nouvel élément au sommet de la
* pile et copie dans cet élément les valeurs
* reçues dans la structure donnée en 2° param
* output : adresse du sommet de la pile
******************************************************/
struct PILE * empiler (struct PILE * PPile,
struct PILE *t)
{
struct PILE *ptemp;
return (ptemp);
}
Nous allons maintenant écrire la fonction qui permet d'enlever un élément du sommet de
la pile en considérant que celle-ci est implémentée au moyen d'une SDD
ptemp = *Ad_Pile;
if (ptemp != NULL)
{
*Ad_Pile = ptemp->psvt;
}
return ptemp;
}
Cette fonction retourne donc l'adresse de l'élément qui a été enlevé du sommet de la
pile.
Chapitre 20 :
Autres notions
Jusqu'à présent, nous avons toujours défini la fonction main par l'instruction "main()".
Ce qui implique qu'elle ne reçoit aucun paramètre.
Pour passer des paramètres à un programme, il faut, en réalité, passer ces paramètres à
la fonction main().
Le passage de paramètres à la fonction main() se fait d'une autre manière que pour une
autre fonction.
Dans le cas où elle reçoit 2 paramètres, le premier est un entier qui indique le nombre
de termes séparés par un espace présents sur la ligne de commande lors de l'exécution du
programme (y compris le nom du programme). Le deuxième paramètre est l'adresse d'un
tableau de pointeurs de chaînes de caractères. Ces chaînes de caractères étant les termes
présents sur la ligne de commande.
argc 3
argv "param1\0"
argv + 1 "param2\0"
argv + 2 "[Link]\0"
NULL
On utilise souvent comme noms de paramètres argc pour "argument count" et argv pour
"argument vector". En effet, le premier paramètre nous permet de savoir combien de
paramètres se trouvent sur la ligne de commande tandis que le second nous permet d'accéder
à la liste des paramètres.
On peut également voir dans le schéma ci-dessus que, par convention, l'élément
argv(argc) contient la valeur NULL.
#include <stdio.h>
#include <conio.h>
Ligne de commande :
[Link]
4
SALUT
CESAR
C:\etudiant>
#include <stdio.h>
#include <string.h>
#include <conio.h>
if (argc != 1)
printf ("Usage : trouver -x -n modele \n");
else
while (lireligne(ligne, MAXLINE) > 0)
{
numligne++;
if ((strstr(ligne , *argv) != NULL) != sauf)
{
if (numero)
printf ("%d : ", numligne);
printf ("%s\n", ligne);
trouvees++;
}
}
return trouvees;
}
i = 0;
while (--lim > 0 && (c=getchar())!=EOF && c!='\n')
s[i++] = c;
s[i]= '\0';
return i;
}
Pour définir ces fonctions, on écrit entre les parenthèses les paramètres qui seront
toujours passés, (comme pour toutes les fonctions, pour printf, un seul paramètre obligatoire :
la chaîne de caractères), et on termine la liste par ", …)".
Les paramètres obligatoires seront reçus comme tous les paramètres que l'on a utilisés
jusqu'à présent.
Les autres paramètres seront placés sur une pile. Le programmeur doit aller les
rechercher lui-même, ce qui peut poser quelques difficultés si on ne connaît ni leur nombre, ni
leur type.
Exemple :
main ()
{
int x, y, z;
x = 5;
y = 10;
z = 15;
fct(x, y, z);
...
}
#include <stdio.h>
#include <stdlib.h>
if (a > 1)
{
for (i = 1; i < a ; i++)
{
somme += *(&a+i);
}
moyenne = somme/(a-1);
}
return moyenne;
}
pointer sur l'argument suivant. Enfin, va_end réalise le nettoyage nécessaire. Il faut
l'appeler avant que la fonction rende la main.
#include <stdarg.h>
#include <stdio.h>
void main(void)
{
int i = 5;
double euro = 40.3399;
char nom[]="Leonard";
va_end (pa);
}
3. UNION
Une union, tout comme une structure, permet de créer un nouveau type de variables.
La déclaration du TYPE d'une union ne prend pas de place en mémoire et doit être
globale afin d'être connue de toutes les fonctions susceptibles de l'utiliser. (Même principe
que pour les structures).
La déclaration d'une variable de type union réserve de la place en mémoire et doit se
faire en local (à nouveau même principe que pour les variables de type structuré).
Une variable de type union permet de stocker des valeurs de types différents.
Exemple :
union Deux_types {
int val_entiere;
char val_car;
};
main ()
{
union Deux_types a, b;
a.val_entiere = 10;
b.val_car = 'I';
Les compilateurs qui respectent la norme ANSI réservent pour chaque variable de type
Union un nombre d'octets égal à la taille du champ le plus grand.
Cependant, certains compilateurs se contentent de réserver un nombre d'octets égal à
la taille du 1er champ. C'est pourquoi il est préférable de commencer la définition d'une union
par le champ le plus grand.
Comme tous les autres types de variables, une variable de type union a une adresse. Il
s'agit de l'adresse du 1er octet qui la compose. Il y a moyen de déclarer un pointeur d'un type
d'union et de lui affecter l'adresse d'une variable de ce type.
Exemple :
union Deux_types {
int val_entiere;
char val_car;
};
main ()
{
union Deux_types a, *pa;
pa = &a;
...
pa->val_entiere = 20;
...
}
Chapitre 21 :
Bibliographie
Maîtrise du Turbo C
Gérard Leblanc
Edition Eyrolles 1989
Langage C
Claude Delannoy
Editions Eyrolles
Le Langage C
2° Edition
B. W. Kernighan et D. M. Ritchie
Editions Dunod Paris 2000
Visual C++ 6
Ivor Horton
Edition Eyrolles 2000
Programmation en C
Cours et Problèmes
Byron S. Gottfried
Serie Schaum