Académique Documents
Professionnel Documents
Culture Documents
MECANIQUE CASABLANCA
SUPPORT DE COURS
PROGRAMMATION EN C
Avantages
Le grand succès du langage C s'explique par les avantages suivants; C est un langage:
(1) universel :
C n'est pas orienté vers un domaine d'applications spéciales, comme par exemple FORTRAN (applications scientifiques et
techniques) ou COBOL (applications commerciales ou traitant de grandes quantités de données).
(2) compact :
C est basé sur un noyau de fonctions et d'opérateurs limité, qui permet la formulation d'expressions simples, mais efficaces.
(3) moderne :
C est un langage structuré, déclaratif et récursif; il offre des structures de contrôle et de déclaration comparables à celles
des autres grands langages de ce temps (FORTRAN, ALGOL68, PASCAL).
(4) près de la machine :
comme C a été développé en premier lieu pour programmer le système d'exploitation UNIX, il offre des opérateurs qui sont
très proches de ceux du langage machine et des fonctions qui permettent un accès simple et direct aux fonctions internes de
l'ordinateur (p.ex: la gestion de la mémoire).
(5) rapide :
comme C permet d'utiliser des expressions et des opérateurs qui sont très proches du langage machine, il est possible de
développer des programmes efficients et rapides.
(6) indépendant de la machine :
bien que C soit un langage près de la machine, il peut être utilisé sur n'importe quel système en possession d'un compilateur
C. Au début C était surtout le langage des systèmes travaillant sous UNIX, aujourd'hui C est devenu le langage de
programmation standard dans le domaine des micro-ordinateurs.
(7) portable :
en respectant le standard ANSI-C, il est possible d'utiliser le même programme sur tout autre système (autre hardware,
autre système d'exploitation), simplement en le recompilant.
(8) extensible :
C ne se compose pas seulement des fonctions standard; le langage est animé par des bibliothèques de fonctions privées ou
livrées par de nombreuses maisons de développement.
Désavantages
Evidemment, rien n'est parfait. Jetons un petit coup d'oeil sur le revers de la médaille:
(1) efficience et compréhensibilité :
En C, nous avons la possibilité d'utiliser des expressions compactes et efficientes. D'autre part, nos programmes doivent
rester compréhensibles pour nous-mêmes et pour d'autres. Comme nous allons le constater sur les exemples suivants, ces
deux exigences peuvent se contredire réciproquement.
Page 2
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Exemple 1
Les deux lignes suivantes impriment les N premiers éléments d'un tableau A[], en insérant un espace entre les éléments et
en commençant une nouvelle ligne après chaque dixième chiffre:
for (i=0; i<n; i++)
printf("%6d%c", a[i], (i%10==9)?'\n':' ');
Cette notation est très pratique, mais plutôt intimidante pour un débutant. L'autre variante, plus près de la notation en
Pascal, est plus lisible, mais elle ne profite pas des avantages du langage C:
for (I=0; I<N; I=I+1)
{
printf("%6d", A[I]);
if ((I%10) == 9)
printf("\n");
else
printf(" ");
}
Exemple 2
La fonction copietab() copie les éléments d'une chaîne de caractères T[] dans une autre chaîne de caractères S[]. Voici
d'abord la version 'simili-Pascal' :
void copietab(char S[], char T[])
{
int I;
I=0;
while (T[I] != '\0')
{
S[I] = T[I];
I = I+1;
}
}
Cette définition de la fonction est valable en C, mais en pratique elle ne serait jamais programmée ainsi. En utilisant les
possibilités de C, un programmeur expérimenté préfère la solution suivante:
void copietab(char *S, char *T)
{
while (*S++ = *T++);
}
La deuxième formulation de cette fonction est élégante, compacte, efficace et la traduction en langage machine fournit un
code très rapide; mais bien que cette manière de résoudre les problèmes soit le cas normal en C, il n'est pas si évident de
suivre le raisonnement.
Conclusions
Bien entendu, dans les deux exemples ci-dessus, les formulations 'courtes' représentent le bon style dans C et sont de loin
préférables aux deux autres. Nous constatons donc que la programmation efficiente en C nécessite beaucoup d'expérience
et n'est pas facilement accessible à des débutants. sans commentaires ou explications, les programmes peuvent devenir
incompréhensibles, donc inutilisables.
Remarque
Au fil de l'introduction du langage C, ce manuel contiendra quelques recommandations au sujet de l'utilisation des
différents moyens de programmation. Il est impossible de donner des règles universelles à ce sujet, mais les deux conseils
suivants sont valables pour tous les langages de programmation
Page 3
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Chapitre 1
L'ENVIRONNEMENT ET LES BIBLIOTHEQUES
Pour le travail pratique en C, il faut utiliser un compilateur et un éditeur facile à utiliser. A titre d'exemple on décrit ici
l'utilisation de l'environnement Borland C++ (Version 3.1). Ce programme nous offre une surface de programmation
confortable et rapide. Tout autre compilateur qui permet la programmation selon le standard ANSI-C fait aussi bien
l'affaire.
Page 4
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
nos programmes. Ces fichiers contiennent des 'prototypes' des fonctions définies dans les bibliothèques et créent un lien
entre les fonctions précompilées et nos programmes.
#include
L'instruction #include insère les fichiers en-tête indiqués comme arguments dans le texte du programme au moment de la
compilation.
Identification des fichiers
Lors de la programmation en Borland C, nous travaillons donc avec différents types de fichiers qui sont identifiés par leurs
extensions:
*.C : fichiers source
*.OBJ : fichiers compilés (versions objet)
*.EXE : fichiers compilés et liés (versions exécutables)
*.LIB : bibliothèques de fonctions précompilées
*.H : fichiers en-tête (header files)
Exemple
Nous avons écrit un programme qui fait appel à des fonctions mathématiques et des fonctions graphiques prédéfinies. Pour
pouvoir utiliser ces fonctions, le programme a besoin des bibliothèques:
MATHS.LIB
GRAPHICS.LIB
Nous devons donc inclure les fichiers en-tête correspondants dans le code source de notre programme à l'aide des
instructions:
#include <math.h>
#include <graphics.h>
Après la compilation, les fonctions precompilées des bibliothèques seront ajoutées à notre programme pour former une
version exécutable du programme (voir schéma).
Remarque: La bibliothèque de fonctions graphics.h est spécifique aux fonctionnalités du PC et n'est pas incluse dans le
standard ANSI-C
Page 5
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Chapitre 2
NOTIONS DE BASE
Avant de pouvoir comprendre ou même écrire des programmes, il faut connaître la composition des programmes dans le
langage de programmation. Dans ce chapitre, nous allons discuter un petit programme en mettant en évidence les structures
fondamentales d'un programme en C.
1. Hello C !
Suivons la tradition et commençons la découverte de C par l'inévitable programme 'hello world'. Ce programme ne fait rien
d'autre qu'imprimer les mots suivants sur l'écran:
HELLO_WORLD en C
#include <stdio.h>
main()
/* Notre premier programme en C */
{
printf("hello, world\n");
return 0;
}
Dans la suite du chapitre, nous allons discuter les détails de cette implémentation.
Page 6
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Instructions
En C, toute instruction simple est terminée par un point-virgule; (même si elle se trouve en dernière position dans un bloc
d'instructions). Par exemple:
printf("hello, world\n");
Remarque avancée:
Il est possible de faire passer des arguments de la ligne de commande à un programme. Dans ce cas, la liste des paramètres
doit contenir les déclarations correspondantes. Dans notre cours, nous n'allons pas utiliser des arguments de la ligne de
commande. Ainsi la liste des paramètres de la fonction main sera vide (void) dans tous nos exemples et nous pourrons
employer la déclaration suivante qui fait usage des valeurs par défaut:
main() { ... }
Exercice 2.1
Comparez la syntaxe de la définition d'une fonction en C avec celle des fonctions et des procédures dans Pascal. (Basez-
vous sur les observations mentionnées ci-dessus.)
Remarques:
- Il est déconseillé d'utiliser le symbole '_' comme premier caractère pour un identificateur, car il est souvent employé pour
définir les variables globales de l'environnement C.
- Le standard dit que la validité de noms externes (p.ex. noms de fonctions ou var. globales) peut être limité à 6 caractères
(même sans tenir compte des majuscules et minuscules) par l'implémentation du compilateur, mais tous les compilateurs
modernes distinguent au moins 31 caractères de façon à ce que nous pouvons généraliser qu'en pratique les règles ci-dessus
s'appliquent à tous les identificateurs.
Exemples
Identificateurs corrects : Identificateurs incorrects
nom1 : 1nom
nom_2 : nom.2
_nom_3 : -nom-3
Nom_de_variable : Nom de variable
Page 7
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
deuxieme_choix : deuxième_choix
mot_francais : mot_français
Exercice 2.2
Lesquels des identificateurs suivants sont acceptés par C ?
fonction-1 _MOYENNE_du_MOIS_ 3e_jour limite_inf. lim_supérieure __A_ a 3
HELLO_WORLD en C
#include <stdio.h>
main()
/* Notre premier programme en C */
{
printf("hello, world\n");
return 0;
}
Discussion
- La fonction main ne reçoit pas de données, donc la liste des paramètres est vide.
- La fonction main fournit un code d'erreur numérique à l'environnement, donc le type du résultat est int et n'a pas besoin
d'être déclaré explicitement.
- Le programme ne contient pas de variables, donc le bloc de déclarations est vide.
- La fonction main contient deux instructions:
* l'appel de la fonction printf avec l'argument "hello, world\n"; Effet: Afficher la chaîne de caractères "hello world\n".
* la commande return avec l'argument 0; Effet: Retourner la valeur 0 comme code d'erreur à l'environnement.
- L'argument de la fonction printf est une chaîne de caractères indiquée entre les guillemets. Une telle suite de caractères
est appelée chaîne de caractères constante (string constant).
- La suite de symboles '\n' à la fin de la chaîne de caractères "hello, world\n" est la notation C pour 'passage à la ligne'
(angl: new line). En C, il existe plusieurs couples de symboles qui contrôlent l'affichage ou l'impression de texte. Ces
séquences d'échappement sont toujours précédées par le caractère d'échappement '\'. (voir exercice 2.4).
Exercice 2.3
Modifiez le programme 'hello world' de façon à obtenir le même résultat sur l'écran en utilisant plusieurs fois la fonction
printf.
Exercice 2.4
Expérimentez avec les séquences d'échappement que vous trouvez dans le tableau ci-dessous et complétez les colonnes
vides.
séq. d'échapp. description. anglaise description. française
\n new line passage à la ligne
\t
\b
\r
\"
\\
\0
\a
Page 8
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Exercice 2.5
Ci-dessous, vous trouvez un simple programme en C. Essayez de distinguer et de classifier autant que possible les éléments
qui composent ce programme (commentaires, variables, déclarations, instructions, etc.)
#include <stdio.h>
/* Ce programme calcule la somme de 4 nombres entiers
introduits au clavier.
*/
main()
{
int NOMBRE, SOMME, COMPTEUR;
/* Initialisation des variables */
SOMME = 0;
COMPTEUR = 0;
/* Lecture des données */
while (COMPTEUR < 4)
{
/* Lire la valeur du nombre suivant */
printf("Entrez un nombre entier :");
scanf("%i", &NOMBRE);
/* Ajouter le nombre au résultat */
SOMME += NOMBRE;
/* Incrémenter le compteur */
COMPTEUR++;
}
/* Impression du résultat */
printf("La somme est: %i \n", SOMME);
return 0;
}
Page 9
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
CHAPITRE 3
TYPES DE BASE, OPERATEURS ET EXPRESSIONS
Récapitulation du vocabulaire
Les variables et les constantes sont les données principales qui peuvent être manipulées par un
programme. Les déclarations introduisent les variables qui sont utilisées, fixent leur type et parfois
aussi leur valeur de départ. Les opérateurs contrôlent les actions que subissent les valeurs des données.
Pour produire de nouvelles valeurs, les variables et les constantes peuvent être combinées à l'aide des
opérateurs dans des expressions. Le type d'une donnée détermine l'ensemble des valeurs admissibles,
le nombre d'octets à réserver en mémoire et l'ensemble des opérateurs qui peuvent y être appliqués.
Motivation
La grande flexibilité de C nous permet d'utiliser des opérandes de différents types dans un même
calcul. Cet avantage peut se transformer dans un terrible piège si nous ne prévoyons pas correctement
les effets secondaires d'une telle opération (conversions de type automatiques, arrondissements, etc.).
Une étude minutieuse de ce chapitre peut donc aider à éviter des phénomènes parfois 'inexplicables'
Exemple
Supposons que la mantisse du type choisi ne comprenne que 6 positions décimales (ce qui est très réaliste pour le type
float):
Page 10
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
char caractère -128 127 1
short Entier court -32768 32767 2
int entier -32768 32767 2
long Entier double -2147483648 +2147483647 4
- char : caractère
Une variable du type char peut contenir une valeur entre -128 et 127 et elle peut subir les mêmes opérations que les
variables du type short, int ou long.
- int : entier standard
Sur chaque machine, le type int est le type de base pour les calculs avec les entiers. Le codage des variables du type int est
donc dépendant de la machine. Sur les IBM-PC sous MS-DOS, une variable du type int est codée dans deux octets.
- short : entier court
Le type short est en général codé dans 2 octets. Comme une variable int occupe aussi 2 octets sur notre système, le type
short devient seulement nécessaire, si on veut utiliser le même programme sur d'autres machines, sur lesquelles le type
standard des entiers n'est pas forcément 2 octets.
- Les modificateurs signed/unsigned:
Si on ajoute le préfixe unsigned à la définition d'un type de variables entières, les domaines des variables sont déplacés
comme suit:
Remarques
1. Le calcul avec des entiers définis comme unsigned correspond à l'arithmétique modulo 2 n. Ainsi, en utilisant une
variable X du type unsigned short, nous obtenons le résultat suivant:
Affectation : X = 65500 + 100
Résultat : X = 64 /* [+216] */
2. Par défaut, les types entiers short, int, long sont munis d'un signe. Le type par défaut de char est dépendant du
compilateur et peut être signed ou unsigned. Ainsi, l'attribut signed a seulement un sens en liaison avec char et peut
forcer la machine à utiliser la représentation des caractères avec signe (qui n'est cependant pas très usuelle).
3. Les valeurs limites des differents types sont indiquées dans le fichier header <limits.h>.
4. En principe, on peut dire que
sizeof(short) <= sizeof(int) <= sizeof(long)
Ainsi sur certaine architecture on peut avoir: short = 2 octets, int = 2 octets, long = 4 octets et sur d'autre: short = 2 octets,
int = 4 octets, long = 4 octets
Exemples
3.14159*100 1.25003*10-12
4.3001*10321 -1.5*103
En C, nous avons le choix entre trois types de rationnels: float, double et long double. Dans le tableau ci-dessous, vous
trouverez leurs caractéristiques:
min et max : représentent les valeurs minimales et maximales positives. Les valeurs négatives peuvent varier dans les
mêmes domaines et Mantisse : indique le nombre de chiffres significatifs de la mantisse.
(1) 1 12 4 0 -125
(2) 1 12 -4 0 250
(3) 1 12 4 0 250
(4) 1 12 -4 0.5 125
(5) -220 32000 0
(6) -3000005.000000001
(7) 410 50000 2
(8) 410 50000 -2
(9) 3.14159265 1015
(10) 2*107 10000001
(11) 2*10-7 10000001
(12) -1.05*1050 0.0001
(13) 305.122212 0 -12
Exercice 3.2
Traduisez les déclarations suivantes en C, sachant que vous travaillerez dans les ensembles de nombres indiqués.
Choisissez les types les plus économiques, sans perdre en précision.
(1) entier COMPTEUR {0 ,..., 300}
(2) entier X,Y {-120 ,..., 100}
(3) entier MESURE {-10 ,..., 104}
(4) réel SURFACE1 {0.5 ,..., 150075}
(5) réel SURFACE2 {-12 ,..., 1500750.5}
(6) entier N1 {0 ,..., 210}
(7) entier N2 {-47 ,..., 47}
(8) entier N3 {0 ,..., 326}
(9) entier N4 {-1280 ,..., 1285}
(10) booléen TROUVE {vrai, faux}
Exemples
12345 type int
52000 type long
-2 type int
0 type int
1u type unsigned int
52000u type unsigned long
22lu Erreur !
Le caractère NUL
La constante '\0' qui a la valeur numérique zéro (ne pas à confondre avec le caractère '0' !!) a une signification spéciale dans
le traitement et la mémorisation des chaînes de caractères: En C le caractère '\0' définit la fin d'une chaîne de caractères.
Exercice 3.3
Complétez le tableau suivant:
Page 14
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
3.3. Les opérateurs standard
Affectation en C
<NomVariable> = <Expression>;
Exemples d'affectations
- L'affectation avec des valeurs constantes
LONG = 141 ; (const. entière)
PI = 3.1415926; (const. réelle)
NATION = 'L'; (caractère const.)
- L'affectation avec des valeurs de variables
LETTRE = COURRIER;
- L'affectation avec des valeurs d'expressions
AIRE = PI*pow(R,2);
MOYENNE = (A+B)/2;
UN=pow(sin(X),2)+pow(cos(X),2);
RES = 45+5*X;
PLUSGRAND = (X>Y);
CORRECT = ('a' == 'a');
Observations
Les opérateurs et les fonctions arithmétiques utilisées dans les expressions seront introduites dans la suite du chapitre.
Observons déjà que:
* il n'existe pas de fonction standard en C pour calculer le carré d'une valeur; on peut se référer à la fonction plus générale
pow(x,y) qui calcule xy).
* le test d'égalité en C se formule avec deux signes d'égalité == , l'affectation avec un seul = .
Opérateurs logiques
&& et logique (and)
|| ou logique (or)
! négation logique (not)
Opérateurs de comparaison
== égal à
!= différent de
<, <=, >, >= plus petit que, ...
Opérations logiques
Les résultats des opérations de comparaison et des opérateurs logiques sont du type int:
- la valeur 1 correspond à la valeur booléenne vrai ;
- la valeur 0 correspond à la valeur booléenne faux
Les opérateurs logiques considèrent toute valeur différente de zéro comme vrai et zéro comme faux:
Opérateurs d'affectation
+= ajouter à
Page 15
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
-= diminuer de
*= multiplier par
/= diviser par
%= modulo
Avantages
La formulation avec un opérateur d'affectation est souvent plus près de la logique humaine: Un homme dirait <<Ajoute 2 à
I>> plutôt que <<Ajoute 2 à I et écris le résultat dans I>>. Les opérateurs d'affectation peuvent aider le compilateur à
générer un code plus efficient parce que expr1 n'est évaluée qu'une seule fois. Les opérateurs d'affectation deviennent plus
intéressant si expr1 est une expression complexe. Ceci peut être le cas si nous calculons avec des tableaux.
L'expression:
Element[n*i+j] = Element[n*i+j] * x[j]
peut être formulée de manière plus efficiente et plus claire:
Element[n*i+j] *= x[j]
Exemple
Supposons que la valeur de N est égal à 5:
Incrém. Postfixe: X = N++; Résultat: N=6 et X=5
Incrém. préfixe: X = ++N; Résultat: N=6 et X=6
Instructions
Une expression comme I=0 ou I++ ou printf(...) devient une instruction, si elle est suivie d'un point-virgule.
Exemples
i=0;
i++;
X=pow(A,4);
printf(" Bonjour !\n");
a=(5*x+10*y)*2;
Évaluation et résultats
En C toutes les expressions sont évaluées et retournent une valeur comme résultat:
(3+2==5) ; retourne la valeur 1 (vrai)
Page 16
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
A=5+3 ; retourne la valeur 8
Comme les affectations sont aussi interprétées comme des expressions, il est possible de profiter de la valeur rendue par
l'affectation: ((A=sin(X)) == 0.5)
Exemple
Dans l'instruction: X = 2*(A+3)*B+4*C; l'ordinateur évalue d'abord l'expression entre parenthèses, ensuite les
multiplications, ensuite l'addition et enfin l'affectation. (En reprenant les valeurs de l'exemple ci-dessus, le résultat sera
164)
Entre les opérateurs que nous connaissons jusqu'ici, nous pouvons distinguer les classes de priorités suivantes:
Classes de priorités
Priorité 1 (la plus forte): ( )
Priorité 2: ! ++ --
Priorité 3: * / %
Priorité 4: + -
Priorité 5: < <= > >=
Priorité 6: == !=
Priorité 7: &&
Priorité 8: ||
Priorité 9 (la plus faible): = += -= *= /= %=
Exemples
L'expression 10+20+30-40+50-60 sera évaluée comme suit:
10+20 ==> 30
30+30 ==> 60
60-40 ==> 20
20+50 ==> 70
70-60 ==> 10
Pour A=3 et B=4, l'expression A *= B += 5 sera évaluée comme suit:
Page 17
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Les parenthèses
Les parenthèses sont seulement nécessaires si nous devons forcer la priorité, mais elles sont aussi
permises si elles ne changent rien à la priorité. En cas de parenthèses imbriquées, l'évaluation se fait de
l'intérieur vers l'extérieur.
Exemple
En supposant à nouveau que A=5, B=10, C=1 l'expression suivante s'évaluera à 134:
X = ((2*A+3)*B+4)*C
Observez la priorité des opérateurs d'affectation :
X *= Y + 1 <=> X = X * (Y + 1)
X *= Y + 1 n'équivaut PAS à X = X * Y + 1
Exercice 3.5
Evaluer les expressions suivantes en supposant
a=20 b=5 c=-10 d=2 x=12 y=15
Notez chaque fois la valeur rendue comme résultat de l'expression et les valeurs des variables dont le contenu a changé.
(1) (5*X)+2*((3*B)+4)
(2) (5*(X+2)*3)*(B+4)
(3) A == (B=5)
(4) A += (X+5)
(5) A != (C *= (-D))
(6) A *= C+(X-D)
(7) A %= D++
(8) A %= ++D
(9) (X++)*(A+C)
(10) A = X*(B<C)+Y*!(B<C)
(11) !(X-D+C)||D
(12) A&&B||!0&&C&&!D
(13) ((A&&B)||(!0&&C))&&!D
(14) ((A&&B)||!0)&&(C&&(!D))
Exercice 3.6
Eliminer les parenthèses superflues dans les expressions de l'exercice 3.5.
Fonctions arithmétiques
COMMANDE C EXPLICATION
exp(X) fonction exponentielle
log(X) logarithme naturel
log10(X) logarithme à base 10
pow(X,Y) X exposant Y
sqrt(X) racine carrée de X
fabs(X) valeur absolue de X
floor(X) arrondir en moins
ceil(X) arrondir en plus
fmod(X,Y) reste rationnel de X/Y (même signe que X)
sin(X) cos(X) tan(X) sinus, cosinus, tangente de X
asin(X) acos(X) atan(X) arcsin(X), arccos(X), arctan(X)
sinh(X) cosh(X) tanh(X) sinus, cosinus, tangente hyperboliques de X
Remarque avancée
La liste des fonctions ne cite que les fonctions les plus courantes. Pour la liste complète et les constantes prédéfinies voir
<math.h>.
Page 18
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Exercice 3.7
Essayez le programme suivant et modifiez-le de façon à ce qu'il affiche:
* AB,
* l'hypoténuse d'un triangle rectangle de côtés A et B,
* la tangente de A en n'utilisant que les fonctions sin et cos,
* la valeur arrondie (en moins) de A/B,
* la valeur arrondie (en moins) à trois positions derrière la virgule de A/B.
#include <stdio.h>
main()
{
double A;
double B;
double RES;
/* Saisie de A et B */
printf("Introduire la valeur pour A : ");
scanf("%lf", &A);
printf("Introduire la valeur pour B : ");
scanf("%lf", &B);
/* Calcul */
RES = A*A;
/* Affichage du résultat */
printf("Le carré de A est %f \n", RES);
/* Calcul */
RES = B*B;
/* Affichage du résultat */
printf("Le carré de B est %f \n", RES);
return 0;
Exemple
Considérons le calcul suivant:
int I = 8;
float X = 12.5;
double Y;
Y = I * X;
Pour pouvoir être multiplié avec X, la valeur de I est convertie en float (le type le plus large des deux). Le résultat de la
multiplication est du type float, mais avant d'être affecté a Y, il est converti en double. Nous obtenons comme résultat:
Y=100.00
Appels de fonctions
Lors de l'appel d'une fonction, les paramètres sont automatiquement convertis dans les types déclarés dans la définition de
la fonction.
Exemple
Au cours des expressions suivantes, nous assistons à trois conversions automatiques:
int A = 200;
int RES;
RES = pow(A, 2);
A l'appel de la fonction pow, la valeur de A et la constante 2 sont converties en double, parce que pow est définie pour des
données de ce type. Le résultat (type double) retourné par pow doit être converti en int avant d'être affecté à RES.
Page 19
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Exemple
Observons les conversions nécessaires lors d'une simple division:
int X;
float A=12.48;
char B=4;
X=A/B;
B est converti en float (règle 2). Le résultat de la division est du type float (valeur 3.12) et sera converti en int avant d'être
affecté à X (règle 4), ce qui conduit au résultat X=3 .
Exemple
Dans cet exemple, nous divisons 3 par 4 à trois reprises et nous observons que le résultat ne dépend pas seulement du type
de la destination, mais aussi du type des opérandes.
char A=3;
int B=4;
float C=4;
float D,E;
char F;
D = A/C;
E = A/B;
F = A/C;
* Pour le calcul de D, A est converti en float (règle 2) et divisé par C. Le résultat (0.75) est affecté à D qui est aussi du type
float. On obtient donc: D=0.75.
* Pour le calcul de E, A est converti en int (règle 1) et divisé par B. Le résultat de la division (type int, valeur 0) est converti
en float (règle 4). On obtient donc: E=0.000.
* Pour le calcul de F, A est converti en float (règle 2) et divisé par C. Le résultat (0.75) est retraduit en char (règle 4). On
obtient donc: F=0
Perte de précision
Lorsque nous convertissons une valeur en un type qui n'est pas assez précis ou pas assez grand, la valeur est coupée sans
arrondir et sans nous avertir.
Exemple
unsigned int A = 70000;
/* la valeur de A sera: 70000 mod 65536 = 4464 */
Exercice 3.8
Soient les déclarations:
long A = 15;
char B = 'A'; /* code ASCII : 65 */
short C = 10;
Quels sont le type et la valeur de chacune des expressions:
(1) C+3
(2) B+1
(3) C+B
(4) 3*C+2*B
(5) 2 * B + (A + 10) / C
(6) 2 * B + (A + 10.0) / C
Page 20
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Il est possible de convertir explicitement une valeur en un type quelconque en forçant la transformation à l'aide de la
syntaxe: Casting (conversion de type forcée)
(<Type>) <Expression>
Exemple
Nous divisons deux variables du type entier. Pour avoir plus de précision, nous voulons avoir un résultat de type rationnel.
Pour ce faire, nous convertissons l'une des deux opérandes en float. Automatiquement C convertira l'autre opérande en float
et effectuera une division rationnelle:
char A=3;
int B=4;
float C;
C = (float)A/B;
La valeur de A est explicitement convertie en float. La valeur de B est automatiquement convertie en float (règle 2). Le
résultat de la division (type rationnel, valeur 0.75) est affecté à C. (Résultat: C=0.75 )
Attention !
Les contenus de A et de B restent inchangés; seulement les valeurs utilisées dans les calculs sont converties !
Page 21
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
chapitre 4
LECTURE ET ECRITURE DES DONNEES
La bibliothèque standard <stdio.h> contient un ensemble de fonctions qui assurent la communication de la machine avec le
monde extérieur. Dans ce chapitre, nous allons en discuter les plus importantes:
Ecriture formatée en C
printf("<format>",<Expr1>,<Expr2>, ... )
La partie "<format>" est en fait une chaîne de caractères qui peut contenir:
* du texte
* des séquences d'échappement
* des spécificateurs de format
Les spécificateurs de format indiquent la manière dont les valeurs des expressions <Expr1..N> sont imprimées.
La partie "<format>" contient exactement un spécificateur de format pour chaque expression <Expr1..N>.
Les spécificateurs de format commencent toujours par le symbole % et se terminent par un ou deux caractères qui indiquent
le format d'impression.
Les spécificateurs de format impliquent une conversion d'un nombre en chaîne de caractères. Ils sont encore appelés
symboles de conversion.
Exemple 1
La suite d'instructions:
int A = 1234;
int B = 567;
printf("%i fois %i est %li\n", A, B, (long)A*B);
Exemple 2
La suite d'instructions:
char B = 'A';
printf("Le caractère %c a le code %i !\n", B, B);
va afficher sur l'écran: Le caractère A a le code 65 !. La valeur de B est donc affichée sous deux formats différents:
- %c comme caractère: A
%i entier relatif: 65
Exemple
long N = 1500000;
printf("%ld, %lf", N, N);
==> 1500000, 16e360
printf("%f, %f" , N);
==> e360, 16
printf("%d, %d" , N);
==> -7328, 22
2. Arguments rationnels
Les spécificateurs %f et %e peuvent être utilisés pour représenter des arguments du type float ou double. La mantisse des
nombres représentés par %e contient exactement un chiffre (non nul) devant le point décimal. Cette représentation s'appelle
la notation scientifique des rationnels. Pour pouvoir traiter correctement les arguments du type long double, il faut utiliser
les spécificateurs %Lf et %Le.
Exemple
float N = 12.1234;
double M = 12.123456789;
long double P = 15.5;
printf("%f", N);
==> 12.123400
printf("%f", M);
==> 12.123457
printf("%e", N);
==> 1.212340e+01
printf("%e", M);
==> 1.212346e+01
printf("%Le", P);
==> 1.550000e+01
3. Largeur minimale pour les entiers
Pour les entiers, nous pouvons indiquer la largeur minimale de la valeur à afficher. Dans le champ ainsi réservé, les
nombres sont justifiés à droite.
Exemples
( _ <=> position libre)
printf("%4d", 123);
==> _123
printf("%4d", 1234);
==> 1234
printf("%4d", 12345);
==> 12345
printf("%4u", 0);
==> ___0
printf("%4X", 123);
==> __7B
printf("%4x", 123);
__7b
Exercice 4.1
#include <stdio.h>
main()
{
int N=10, P=5, Q=10, R;
char C='S';
N = 5; P = 2;
Q = N++ > P || P++ != 3;
Page 23
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
printf ("C : N=%d P=%d Q=%d\n", N, P, Q);
N = 5; P = 2;
Q = N++ < P || P++ != 3;
printf ("D : N=%d P=%d Q=%d\n", N, P, Q);
N = 5; P = 2;
Q = ++N == 3 && ++P == 3;
printf ("E : N=%d P=%d Q=%d\n", N, P, Q);
N=5; P=2;
Q = ++N == 6 && ++P == 3;
printf ("F : N=%d P=%d Q=%d\n", N, P, Q);
N=C;
printf ("G : %c %c\n", C, N);
printf ("H : %d %d\n", C, N);
printf ("I : %x %x\n", C, N);
return 0;
}
a) Sans utiliser l'ordinateur, trouvez et notez les résultats du programme ci-dessus.
b) Vérifiez vos résultats à l'aide de l'ordinateur.
* La fonction scanf reçoit ses données à partir du fichier d'entrée standard stdin (par défaut le clavier).
* La chaîne de format détermine comment les données reçues doivent être interprétées.
* Les données reçues correctement sont mémorisées successivement aux adresses indiquées par <AdrVar1>,... .
* L'adresse d'une variable est indiquée par le nom de la variable précédé du signe &.
Exemple
int JOUR, MOIS, ANNEE;
scanf("%i %i %i", &JOUR, &MOIS, &ANNEE);
La suite d'instructions (ci-dessus) lit trois entiers relatifs, séparés par des espaces, tabulations ou interlignes. Les valeurs
sont attribuées respectivement aux trois variables JOUR, MOIS et ANNEE.
* scanf retourne comme résultat le nombre de données correctement reçues (type int).
Le symbole * indique que l'argument n'est pas une variable, mais l'adresse d'une variable de ce type (c.-à-d.: un pointeur sur
une variable).
1. Le type long
Si nous voulons lire une donnée du type long, nous devons utiliser les spécificateurs %ld, %li, %lu, %lo, %lx. (Sinon, le
nombre est simplement coupé à la taille de int).
2. Le type double
Si nous voulons lire une donnée du type double, nous devons utiliser les spécificateurs %le ou %lf.
3. Le type long double
Si nous voulons lire une donnée du type long double, nous devons utiliser les spécificateurs %Le ou %Lf.
4. Indication de la largeur maximale
Page 24
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Pour tous les spécificateurs, nous pouvons indiquer la largeur maximale du champ à évaluer pour une donnée. Les chiffres
qui passent au-delà du champ défini sont attribués à la prochaine variable qui sera lue !
Exemple
Soient les instructions:
int A,B;
scanf("%4d %2d", &A, &B);
Si nous entrons le nombre 1234567, nous obtiendrons les affectations suivantes: A=1234 et B=56. Le
chiffre 7 sera gardé pour la prochaine instruction de lecture.
5. Les signes d'espacement
Lors de l'entrée des données, une suite de signes d'espacement (espaces, tabulateurs, interlignes) est évaluée comme un seul
espace. Dans la chaîne de format, les symboles \t, \n, \r ont le même effet qu'un simple espace.
Exemple
Pour la suite d'instructions
int JOUR, MOIS, ANNEE;
scanf("%i %i %i", &JOUR, &MOIS, &ANNEE);
les entrées suivantes sont correctes et équivalentes:
12 4 1980 ou 12 004 1980 ou 12 4 1980
6. Formats 'spéciaux'
Si la chaîne de format contient aussi d'autres caractères que des signes d'espacement, alors ces symboles doivent être
introduits exactement dans l'ordre indiqué.
Exemple
La suite d'instructions
int JOUR, MOIS, ANNEE;
scanf("%i/%i/%i", &JOUR, &MOIS, &ANNEE);
Exemple
La suite d'instructions
int JOUR, MOIS, ANNEE, RECU;
RECU = scanf("%i %i %i", &JOUR, &MOIS, &ANNEE);
Exercice 4.2
En vous référant aux exemples du paragraphe 4.2, écrivez un programme qui lit la date du clavier et écrit les données ainsi
que le nombre de données correctement reçues sur l'écran.
Exemple:
Introduisez la date (jour mois année): 11 11 1991
données reçues : 3
jour : 11
mois : 11
année : 1991
* Testez les réactions du programme à vos entrées. Essayez d'introduire des nombres de différents formats et différentes
grandeurs.
* Changez la partie format du programme de façon à séparer les différentes données par le symbole '-' .
Page 25
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Type de l'argument
Pour ne pas être confondue avec un caractère, la constante EOF doit nécessairement avoir une valeur qui sort du domaine
des caractères (en général EOF a la valeur -1). Ainsi, les arguments de putchar sont par définition du type int et toutes les
valeurs traitées par putchar (même celles du type char) sont d'abord converties en int.
Exemples
char A = 225;
char B = '\a';
int C = '\a';
putchar('x'); /* afficher la lettre x */
putchar('?'); /* afficher le symbole ? */
putchar('\n'); /* retour à la ligne */
putchar(65); /* afficher le symbole avec */ /* le code 65 (ASCII: 'A') */
putchar(A); /* afficher la lettre avec */ /* le code 225 (ASCII: 'ß') */
putchar(B); /* beep sonore */
putchar(C); /* beep sonore */
putchar(EOF); /* marquer la fin du fichier */
Type du résultat
Les valeurs retournées par getchar sont ou bien des caractères (0 - 255) ou bien le symbole EOF. Comme la valeur du
symbole EOF sort du domaine des caractères, le type résultat de getchar est int. En général, getchar est utilisé dans une
affectation:
int C;
C = getchar();
getchar lit les données de la zone tampon de stdin et fournit les données seulement après confirmation par 'Enter'. La
bibliothèque <conio> contient une fonction du nom getch qui fournit immédiatement le prochain caractère entré au clavier.
La fonction getch n'est pas compatible avec ANSI-C et elle peut seulement être utilisée sous MS-DOS.
Exercice 4.3
Ecrire un programme qui lit un caractère au clavier et affiche le caractère ainsi que son code numérique:
a) en employant getchar et printf,
b) en employant getch et printf.
Page 27
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Chapitre 5
LA STRUCTURE ALTERNATIVE
Les structures de contrôle définissent la suite dans laquelle les instructions sont effectuées. Dans ce chapitre, nous allons
voir comment les instructions de sélection connues fonctionnent en C et nous allons faire connaissance d'un couple
d'opérateurs spécial qui nous permet de choisir entre deux valeurs à l'intérieur d'une expression.
Constatons déjà que la particularité la plus importante des instructions de contrôle en C est le fait que les 'conditions' en C
peuvent être des expressions quelconques qui fournissent un résultat numérique. La valeur zéro correspond à la valeur
logique faux et toute valeur différente de zéro est considérée comme vrai.
* Si l'<expression> fournit une valeur différente de zéro, alors le <bloc d'instructions 1> est exécuté
* Si l'<expression> fournit la valeur zéro, alors le <bloc d'instructions 2> est exécuté
La partie <expression> peut désigner : une variable d'un type numérique, une expression fournissant un résultat numérique.
La partie <bloc d'instructions> peut désigner : un (vrai) bloc d'instructions compris entre accolades, une seule instruction
terminée par un point-virgule ( ;).
Exemple 1
if (a > b) max = a;
else max = b;
Exemple 2
if (EGAL) printf("A est égal à B\n");
else printf("A est différent de B\n");
Exemple 3
if (A-B) printf("A est différent de B\n");
else printf("A est égal à B\n");
Exemple 4
if (A > B)
{
AIDE = A;
A = C;
C = AIDE;
}
else
{
AIDE = B;
B = C;
C = AIDE;
}
if ( <expression> )
<bloc d'instructions>
Attention !
Comme la partie else est optionnelle, les expressions contenant plusieurs structures if et if - else peuvent mener à des
confusions.
Exemple
L'expression suivante peut être interprétée de deux façons:
if (N>0)
if (A>B)
MAX=A;
else
MAX=B;
Page 26
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
if (N>0)
if (A>B)
MAX=A;
else
MAX=B;
Convention
En C une partie else est toujours liée au dernier if qui ne possède pas de partie else. Dans notre exemple, C utiliserait donc
la première interprétation.
Solution
Pour éviter des confusions et pour forcer une certaine interprétation d'une expression, il est recommandé d'utiliser des
accolades { } .
Exemple
Pour forcer la deuxième interprétation de l'expression ci-dessus, nous pouvons écrire:
if (N>0) { if (A>B) MAX=A; }
else MAX=B;
Exercice 5.1
Considérez la séquence d'instructions suivante:
if (A>B) printf ("premier choix \n"); else
if (A>10) printf ("deuxième choix \n");
if (B<10) printf ("troisième choix \n");
else printf ("quatrième choix \n");
a) Copiez la séquence d'instructions en utilisant des tabulateurs pour marquer les blocs if - else appartenant ensemble.
b) Déterminez les réponses du programme pour chacun des couples de nombres suivants.
- A=10 et B=5
- A=5 et B=5
- A=5 et B=10
- A=10 et B=10
- A=20 et B=10
- A=20 et B=20
if ( <expr1> )
<bloc1>
else if (<expr2>)
<bloc2>
else if (<expr3>)
<bloc3>
else if (<exprN>)
<blocN>
else <blocN+1>
Les expressions <expr1> ... <exprN> sont évaluées du haut vers le bas jusqu'à ce que l'une d'elles soit différente de zéro. Le
bloc d'instructions y lié est alors exécuté et le traitement de la commande est terminé.
Exemple
#include <stdio.h>
main()
{
int A,B;
printf("Entrez deux nombres entiers :");
scanf("%i %i", &A, &B);
if (A > B)
printf("%i est plus grand que %i\n", A, B);
else if (A < B)
Page 27
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
printf("%i est plus petit que %i\n", A, B);
else
printf("%i est égal à %i\n", A, B);
return 0;
}
La dernière partie else traite le cas où aucune des conditions n'a été remplie. Elle est optionnelle, mais elle peut être utilisée
très confortablement pour détecter des erreurs.
Exemple
printf("Continuer (O)ui / (N)on ?");
getchar(C);
if (C=='O')
{
...
}
else if (C=='N')
printf("Au revoir ...\n");
else
printf("\aErreur d'entrée !\n");
Exercice 5.2
Considérez la séquence d'instructions suivante:
if (A>B)
if (A>10)
printf ("premier choix \n"); else if (B<10)
printf ("deuxième choix \n"); else
if (A==B) printf ("troisième choix \n");
else printf ("quatrième choix \n");
a) Copiez la séquence d'instructions en utilisant des tabulateurs pour marquer les blocs if - else appartenant ensemble.
b) Pour quelles valeurs de A et B obtient-on les résultats: premier choix, deuxième choix, ... sur l'écran?
c) Pour quelles valeurs de A et B on n’obtient pas de réponse sur l'écran?
d) Notez vos réponses et choisissez vous-mêmes des valeurs pour A et B pour les vérifier l'aide de l'ordinateur.
* Si <expr1> fournit une valeur différente de zéro, alors la valeur de <expr2> est fournie comme résultat
* Si <expr1> fournit la valeur zéro, alors la valeur de <expr3> est fournie comme résultat
Exemple
if (A>B)
MAX=A;
else
MAX=B;
La suite d'instructions peut être remplacée par: MAX = (A > B) ? A : B;
Employés de façon irréfléchie, les opérateurs conditionnels peuvent nuire à la lisibilité d'un programme, mais si on les
utilise avec précaution, ils fournissent des solutions très élégantes:
Exemple
printf("Vous avez %i carte%c \n", N, (N==1) ? ' ' : 's');
Les règles de conversion de types s'appliquent aussi aux opérateurs conditionnels ? : Ainsi, pour un entier N du type int et
un rationnel F du type float, l'expression (N>0) ? N : F va toujours fournir un résultat du type float, n'importe si N est
plus grand ou plus petit que zéro!
Page 28
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
5.5. Exercices d'application
Exercice 5.3
Ecrivez un programme qui lit trois valeurs entières (A, B et C) au clavier et qui affiche la plus grande des trois valeurs, en
utilisant:
a) if - else et une variable d'aide MAX
b) if - else if - ... - else sans variable d'aide
c) les opérateurs conditionnels et une variable d'aide MAX
d) les opérateurs conditionnels sans variable d'aide
Exercice 5.4
Ecrivez un programme qui lit trois valeurs entières (A, B et C) au clavier. Triez les valeurs A, B et C par échanges
successifs de manière à obtenir : val(A) val(B) val(C)
Affichez les trois valeurs.
Exercice 5.5
Ecrivez un programme qui lit deux valeurs entières (A et B) au clavier et qui affiche le signe du produit de A et B sans faire
la multiplication.
Exercice 5.6
Ecrivez un programme qui lit deux valeurs entières (A et B) au clavier et qui affiche le signe de la somme de A et B sans
faire l'addition. Utilisez la fonction fabs de la bibliothèque <math>.
Exercice 5.7
Ecrivez un programme qui calcule les solutions réelles d'une équation du second degré ax2+bx+c = 0 en discutant la
formule:
Utilisez une variable d'aide D pour la valeur du discriminant b2-4ac et décidez à l'aide de D, si l'équation a une, deux ou
aucune solution réelle. Utilisez des variables du type int pour A, B et C.
Considérez aussi les cas où l'utilisateur entre des valeurs nulles pour A; pour A et B; pour A, B et C. Affichez les résultats
et les messages nécessaires sur l'écran.
Page 29
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Chapitre 6
LA STRUCTURE REPETITIVE
En C, nous disposons de trois structures qui nous permettent la définition de boucles conditionnelles:
1) la structure : while
2) la structure : do - while
3) la structure : for
Théoriquement, ces structures sont interchangeables, c.-à-d. il serait possible de programmer toutes sortes de boucles
conditionnelles en n'utilisant qu'une seule des trois structures. Comme en Pascal, il est quand même absolument
recommandé de choisir toujours la structure la mieux adaptée au cas actuel (voir paragraphe 6.4.).
* Tant que l'<expression> fournit une valeur différente de zéro, le <bloc d'instructions> est exécuté.
* Si l'<expression> fournit la valeur zéro, l'exécution continue avec l'instruction qui suit le bloc d'instructions.
* Le <bloc d'instructions> est exécuté zéro ou plusieurs fois.
La partie <expression> peut désigner une variable d'un type numérique ou une expression fournissant un résultat
numérique.
La partie <bloc d'instructions> peut désigner un (vrai) bloc d'instructions compris entre accolades ou une seule instruction
terminée par un point-virgule.
Exemple 1
/* Afficher les nombres de 0 à 9 */
int I = 0;
while (I<10)
{
printf("%i \n", I);
I++;
}
Exemple 2
int I;
/* Afficher les nombres de 0 à 9 */
I = 0;
while (I<10)
printf("%i \n", I++);
/* Afficher les nombres de 1 à 10 */
I = 0;
while (I<10)
printf("%i \n", ++I);
Exemple 3
/* Afficher les nombres de 10 à 1 */
int I=10;
while (I)
printf("%i \n", I--);
Remarque
Parfois nous voulons seulement attendre un certain événement, sans avoir besoin d'un traitement de données. Dans ce cas,
la partie <bloc d'instructions> peut être vide (notation: ; ou {} ). La ligne suivante ignore tous les espaces entrés au clavier
et peut être utilisée avant de lire le premier caractère significatif: while (getch()==' ');
Page 30
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
* do - while évalue la condition après avoir exécuté le bloc d'instructions. Ainsi le bloc d'instructions est exécuté au moins
une fois.
La structure do - while en C
do
<bloc d'instructions>
while ( <expression> );
Le <bloc d'instructions> est exécuté au moins une fois et aussi longtemps que l'<expression> fournit une valeur différente
de zéro.
En pratique, la structure do - while n'est pas si fréquente que while; mais dans certains cas, elle fournit une solution plus
élégante. Une application typique de do - while est la saisie de données qui doivent remplir une certaine condition:
Exemple 1
float N;
do
{
printf("Introduisez un nombre entre 1 et 10 :");
scanf("%f", &N);
}
while (N>1 || N<10);
Exemple 2
int n, div;
printf("Entrez le nombre à diviser : ");
scanf("%i", &n);
do
{
printf("Entrez le diviseur ( 0) : ");
scanf("%i", &div);
}
while (!div);
printf("%i / %i = %f\n", n, div, (float)n/div);
do - while est comparable à la structure répéter du langage algorithmique (repeat until en Pascal) si la condition finale est
inversée logiquement.
Exemple 3
Le programme de calcul de la racine carrée :
#include <stdio.h>
#include <math.h>
main()
{
float N;
do
{
printf("Entrer un nombre (>= 0) : ");
scanf("%f", &N)
}
while (N < 0);
printf("La racine carrée de %.2f est %.2f\n", N, sqrt(N));
return 0;
}
<expr1> est évaluée une fois avant le passage de la boucle. Elle est utilisée pour initialiser les données de la boucle.
<expr2> est évaluée avant chaque passage de la boucle. Elle est utilisée pour décider si la boucle est répétée ou non.
<expr3> est évaluée à la fin de chaque passage de la boucle. Elle est utilisée pour réinitialiser les données de la boucle.
Le plus souvent, for est utilisé comme boucle de comptage :
Page 31
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
for ( <init.> ; <cond. répétition> ; <compteur> )
<bloc d'instructions>
Exemple
int I;
for (I=0 ; I<=20 ; I++)
printf("Le carré de %d est %d \n", I, I*I);
En pratique, les parties <expr1> et <expr2> contiennent souvent plusieurs initialisations ou réinitialisations, séparées par
des virgules.
Exemple
int n, tot;
for (tot=0, n=1 ; n<101 ; n++)
tot+=n;
printf("La somme des nombres de 1 à 100 est %d\n", tot);
Exemple
Cet exemple nous présente différentes variations pour réaliser le même traitement et nous montre la puissance de la
structure for. Les expressions suivantes lisent un caractère au clavier et affichent son code numérique en notation binaire :
/* a */
/* notation utilisant la structure while */
int C, I;
C=getchar();
I=128;
while (I>=1)
{
printf("%i ", C/I);
C%=I;
I/=2;
}
/* b */
/* notation utilisant for - très lisible - */
/* préférée par les débutants en C */
int C, I;
C=getchar();
for (I=128 ; I>=1 ; I/=2)
{
printf("%i ", C/I);
C%=I;
}
/* c */
/* notation utilisant for - plus compacte - */
/* préférée par les experts en C */
int C, I;
C=getchar();
for (I=128 ; I>=1 ; C%=I, I/=2)
printf("%i ", C/I);
/* d */
/* notation utilisant for - à déconseiller - */
/* surcharge et mauvais emploi de la structure */
int C, I;
for(C=getchar(),I=128; I>=1 ;printf("%i ",C/I),C%=i,i/=2);
Page 32
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
* for nous permet de réunir avantageusement les instructions qui influencent le nombre de répétitions au début de la
structure.
* while a l'avantage de correspondre plus exactement aux structures d'autres langages de programmation (while, tant que).
* for a le désavantage de favoriser la programmation de structures surchargées et par la suite illisible.
* while a le désavantage de mener parfois à de longues structures, dans lesquelles il faut chercher pour trouver les
instructions qui influencent la condition de répétition.
Exercice 6.2
Complétez la 'meilleure' des trois versions de l'exercice 6.7 :
Répétez l'introduction du nombre N jusqu'à ce que N ait une valeur entre 1 et 15.
Quelle structure répétitive utilisez-vous? Pourquoi?
Exercice 6.3
Calculez par des soustractions successives le quotient entier et le reste de la division entière de deux entiers entrés au
clavier.
Exercice 6.4
Calculez la factorielle N! = 123...(N-1)N d'un entier naturel N en respectant que 0!=1.
a) Utilisez while,
b) Utilisez for.
Exercice 6.5
Calculez par multiplications successives XN de deux entiers naturels X et N entrés au clavier.
Exercice 6.6
Calculez la somme des N premiers termes de la série harmonique : 1 + 1/2 + 1/3 + ... + 1/N
Exercice 6.7
Calculez la somme, le produit et la moyenne d'une suite de chiffres non nuls entrés au clavier, sachant que la suite est
terminée par zéro. Retenez seulement les chiffres (0, 1 ... 9) lors de l'entrée des données et effectuez un signal sonore si les
données sortent de ce domaine.
Exercice 6.8
Calculez le nombre lu à rebours d'un nombre positif entré au clavier en supposant que le fichier d'entrée standard contient
une suite de chiffres non nuls, terminée par zéro (Contrôlez s'il s'agit vraiment de chiffres). Exemple: Entrée: 1 2 3 4 0
Affichage: 4321
Exercice 6.9
Calculez le nombre lu à rebours d'un nombre positif entré au clavier en supposant que le fichier d'entrée standard contient
le nombre à inverser. Exemple: Entrée: 1234 Affichage: 4321
Exercice 6.10
Calculez pour une valeur X donnée du type float la valeur numérique d'un polynôme de degré n:
P(X) = AnXn + An-1Xn-1 + ... + A1X + A0
Page 33
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Exercice 6.11
Calculez le N-ième terme UN de la suite de FIBONACCI qui est donnée par la relation de récurrence:
Déterminez le rang N et la valeur UN du terme maximal que l'on peut calculer si on utilise pour UN :
- le type int
- le type long
- le type double
- le type long double
Exercice 6.12
Affiche la table des produits pour N variant de 1 à 10 :
Page 34
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Chapitre 7
LES VARIABLES STRUCTUREES
I- LES TABLEAUX
Les tableaux sont certainement les variables structurées les plus populaires. Ils sont disponibles dans tous les langages
de programmation et servent à résoudre une multitude de problèmes. Dans une première approche, le traitement des
tableaux en C ne diffère pas de celui des autres langages de programmation.
Nous allons cependant voir plus loin (paragraphe 3 : Les Pointeurs), que le langage C permet un accès encore plus
direct et plus rapide aux données d'un tableau.
Les chaînes de caractères sont déclarées en C comme tableaux de caractères et permettent l'utilisation d'un certain
nombre de notations et de fonctions spéciales. Les particularités des tableaux de caractères seront traitées dans le 2 ème
paragraphe.
Définitions
Un tableau (uni-dimensionnel) A est une variable structurée formée d'un nombre entier N de variables simples du
même type, qui sont appelées les composantes du tableau. Le nombre de composantes N est alors la dimension du
tableau.
En faisant le rapprochement avec les mathématiques, on dit encore que "A est un vecteur de dimension N"
Exemple
La déclaration
Cette ligne définit un tableau du type int de dimension 12. Les 12 composantes sont initialisées par les valeurs
respectives 31, 28, 31, ... , 31.
On peut accéder à la première composante du tableau par JOURS[0], à la deuxième composante par JOURS[1], . . . , à
la dernière composante par JOURS[11].
Les noms des tableaux sont des identificateurs qui doivent correspondre aux restrictions définies précédemment.
Exemples
Les déclarations suivantes en C :
int A[25]; ou bien long A[25];
float B[100]; ou bien double B[100];
int C[10];
char D[30];
Page 35
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Mémorisation
En C, le nom d'un tableau est le représentant de l'adresse du premier élément du tableau. Les adresses des autres
composantes sont calculées (automatiquement) relativement à cette adresse.
Exemple:
short A[5] = {1200, 2300, 3400, 4500, 5600};
Si un tableau est formé de N composantes et si une composante a besoin de M octets en mémoire, alors le tableau
occupera N*M octets.
Exemple
En supposant qu'une variable du type long occupe 4 octets (c.-à-d: sizeof(long)=4), pour le tableau T déclaré par: long
T[15]; C réservera N*M = 15*4 = 60 octets en mémoire.
Exemples
int A[5] = {10, 20, 30, 40, 50};
float B[4] = {-1.05, 3.33, 87e-5, -12.3E4};
int C[10] = {1, 0, 0, 1, 1, 1, 0, 1, 0, 1};
Il faut évidemment veiller à ce que le nombre de valeurs dans la liste corresponde à la dimension du tableau. Si la liste
ne contient pas assez de valeurs pour toutes les composantes, les composantes restantes sont initialisées par zéro.
Réservation automatique
Si la dimension n'est pas indiquée explicitement lors de l'initialisation, alors l'ordinateur réserve automatiquement le
nombre d'octets nécessaires.
Exemples
int A[] = {10, 20, 30, 40, 50}; ==> réservation de 5*sizeof(int) octets (dans notre cas: 10 octets)
float B[] = {-1.05, 3.33, 87e-5, -12.3E4}; ==> réservation de 4*sizeof(float) octets (dans notre cas: 16 octets)
int C[] = {1, 0, 0, 1, 1, 1, 0, 1, 0, 1}; ==> réservation de 10*sizeof(int) octets (dans notre cas: 20 octets)
Exemples
Page 36
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
C°). Accès aux composantes
En déclarant un tableau par: int A[5];
On a défini un tableau A avec cinq composantes, auxquelles on peut accéder par: A[0], A[1], ... , A[4]
Exemple
main()
{
int A[5];
int I; /* Compteur */
for (I=0; I<5; I++)
printf("%d ", A[I]);
return 0;
printf("\n");
}
Remarques
* Avant de pouvoir afficher les composantes d'un tableau, il faut évidemment leur affecter des valeurs.
* Rappelez-vous que la deuxième condition dans la structure for n'est pas une condition d'arrêt, mais une condition de
répétition! Ainsi la commande d'affichage sera répétée aussi longtemps que I est inférieur à 5. La boucle sera donc bien
exécutée pour les indices 0,1,2,3 et 4 !
* Pour être sûr que les valeurs sont bien séparées lors de l'affichage, il faut inclure au moins un espace dans la chaîne
de format. Autres possibilités:
Affectation
- Affectation avec des valeurs provenant de l'extérieur
Le programme suivant permet de lire des valeurs à partir du clavier et les affectent aux composantes du tableau A
main()
{
int A[5];
int I; /* Compteur */
for (I=0; I<5; I++)
scanf("%d", &A[I]);
return 0;
Page 37
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
}
Remarques
* Comme scanf a besoin des adresses des différentes composantes du tableau, il faut faire précéder le terme A[I] par
l'opérateur adresse '&'.
* La commande de lecture scanf doit être informée du type exact des données à lire. (Ici: %d ou %i pour lire des
valeurs du type int)
Exercice 1
Ecrire un programme qui lit la dimension N d'un tableau T du type int (dimension maximale: 50 composantes), remplit
le tableau par des valeurs entrées au clavier et affiche le tableau. Calculer et afficher ensuite la somme des éléments du
tableau.
Exercice 2
Ecrire un programme qui lit la dimension N d'un tableau T du type int (dimension maximale: 50 composantes), remplit
le tableau par des valeurs entrées au clavier et affiche le tableau. Effacer ensuite toutes les occurrences de la valeur 0
dans le tableau T et tasser les éléments restants. Afficher le tableau résultant.
Exercice 3
Ecrire un programme qui lit la dimension N d'un tableau T du type int (dimension maximale: 50 composantes), remplit
le tableau par des valeurs entrées au clavier et affiche le tableau.
Ranger ensuite les éléments du tableau T dans l'ordre inverse sans utiliser de tableau d'aide. Afficher le tableau
résultant.
Idée: Echanger les éléments du tableau à l'aide de deux indices qui parcourent le tableau en commençant
respectivement au début et à la fin du tableau et qui se rencontrent en son milieu.
Exercice 4
Ecrire un programme qui lit la dimension N d'un tableau T du type int (dimension maximale: 50 composantes), remplit
le tableau par des valeurs entrées au clavier et affiche le tableau.
Copiez ensuite toutes les composantes strictement positives dans un deuxième tableau TPOS et toutes les valeurs
strictement négatives dans un troisième tableau TNEG. Afficher les tableaux TPOS et TNEG.
Définitions
En C, un tableau à deux dimensions A est à interpréter comme un tableau (uni-dimensionnel) de dimension L dont
chaque composante est un tableau (uni-dimensionnel) de dimension C.
On appelle L le nombre de lignes du tableau et C le nombre de colonnes du tableau. L et C sont alors les deux
dimensions du tableau. Un tableau à deux dimensions contient donc L*C composantes.
Exemple
Considérons un tableau NOTES à une dimension pour mémoriser les notes de 20 élèves d'une classe dans un devoir:
Page 38
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
int NOTE[20] = {45, 34, ... , 50, 48};
Pour mémoriser les notes des élèves dans les 10 devoirs d'un trimestre, nous pouvons rassembler plusieurs de ces
tableaux uni-dimensionnels dans un tableau NOTES à deux dimensions :
Dans une ligne nous retrouvons les notes de tous les élèves dans un devoir. Dans une colonne, nous retrouvons toutes
les notes d'un élève.
Mémorisation
Comme pour les tableaux à une dimension, le nom d'un tableau est le représentant de l'adresse du premier élément du
tableau (c.-à-d. l'adresse de la première ligne du tableau). Les composantes d'un tableau à deux dimensions sont
stockées ligne par ligne dans la mémoire.
Exemple
short A[3][2] = {{1, 2 },
{10, 20 },
{100, 200}};
Un tableau de dimensions L et C, formé de composantes dont chacune a besoin de M octets, occupera L*C*M octets
en mémoire.
Exemple
En supposant qu'une variable du type double occupe 8 octets (c.-à-d: sizeof(double)=8), pour le tableau T déclaré par:
Page 39
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
double T[10][15];
C réservera L*C*M = 10*15*8 = 1200 octets en mémoire.
Exemples
int A[3][10] = {{ 0,10,20,30,40,50,60,70,80,90},
{10,11,12,13,14,15,16,17,18,19},
{ 1,12,23,34,45,56,67,78,89,90}};
Lors de l'initialisation, les valeurs sont affectées ligne par ligne en passant de gauche à droite. Nous ne devons pas
nécessairement indiquer toutes les valeurs: Les valeurs manquantes seront initialisées par zéro. Il est cependant
défendu d'indiquer trop de valeurs pour un tableau.
Exemples
Réservation automatique
Si le nombre de lignes L n'est pas indiqué explicitement lors de l'initialisation, l'ordinateur réserve automatiquement le
nombre d'octets nécessaires.
Attention !
Considérons un tableau A de dimensions L et C. En C, on a :
- les indices du tableau varient de 0 à L-1, respectivement de 0 à C-1.
- la composante de la Nième ligne et Mième colonne est notée: A[N-1][M-1]
main()
{
int A[5][10];
int I,J;
/* Pour chaque ligne ... */
for (I=0; I<5; I++)
{
/* ... considérer chaque composante */
for (J=0; J<10; J++)
printf("%7d", A[I][J]);
/* Retour à la ligne */
printf("\n");
}
return 0;
Page 41
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
}
Remarques
* Avant de pouvoir afficher les composantes d'un tableau, il faut leur affecter des valeurs.
* Pour obtenir des colonnes bien alignées lors de l'affichage, il est pratique d'indiquer la largeur minimale de l'affichage
dans la chaîne de format. Pour afficher des matrices du type int (valeur la plus 'longue': -32768), nous pouvons utiliser
la chaîne de format "%7d" : printf("%7d", A[I][J]);
main()
{
int A[5][10];
int I,J;
/* Pour chaque ligne ... */
for (I=0; I<5; I++)
/* ... considérer chaque composante */
for (J=0; J<10; J++)
scanf("%d", &A[I][J]);
return 0;
}
Il n'existe pas de type spécial chaîne ou string en C. Une chaîne de caractères est traitée comme un tableau à une
dimension de caractères (vecteur de caractères). Il existe quand même des notations particulières et une bonne quantité
de fonctions spéciales pour le traitement de tableaux de caractères.
Déclaration
Exemples
Espace à réserver
Lors de la déclaration, nous devons indiquer l'espace à réserver en mémoire pour le stockage de la chaîne.
La représentation interne d'une chaîne de caractères est terminée par le symbole '\0' (NUL). Ainsi, pour un texte de n
caractères, nous devons prévoir n+1 octets.
Malheureusement, le compilateur C ne contrôle pas si nous avons réservé un octet pour le symbole de fin de chaîne;
l'erreur se
fera seulement remarquer lors de l'exécution du programme ...
Mémorisation
Le nom d'une chaîne est le représentant de l'adresse du premier caractère de la chaîne. Pour mémoriser une variable qui
Page 42
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
doit être capable de contenir un texte de N caractères, nous avons besoin de N+1 octets en mémoire:
* Les chaînes de caractères constantes (string literals) sont indiquées entre guillemets. La chaîne de caractères vide est
alors:
""
* Dans les chaînes de caractères, nous pouvons utiliser toutes les séquences d'échappement définies comme caractères
constants:
* Le symbole " peut être représenté à l'intérieur d'une chaîne par la séquence d'échappement \":
* Le symbole ' peut être représenté à l'intérieur d'une liste de caractères par la séquence d'échappement \' :
{'L','\'','a','s','t','u','c','e','\0'}
* Plusieurs chaînes de caractères constantes qui sont séparées par des signes d'espacement (espaces, tabulateurs ou
interlignes) dans le texte du programme seront réunies en une seule chaîne constante lors de la compilation:
sera évalué à
Ainsi il est possible de définir de très longues chaînes de caractères constantes en utilisant plusieurs lignes dans le texte
du
programme.
Observation
'x'
est un caractère constant, qui a une valeur numérique:
Page 43
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
"x"
est un tableau de caractères qui contient deux caractères:
'x'
est codé dans un octet
"x"
est codé dans deux octets
Lors de l'initialisation par [], l'ordinateur réserve automatiquement le nombre d'octets nécessaires pour la chaîne, c.-
à-d.: le nombre de caractères + 1 (ici: 6 octets). Nous pouvons aussi indiquer explicitement le nombre d'octets à
réserver, si celui-ci est supérieur ou égal à la longueur de la chaîne d'initialisation.
Exemples
Exercice
Lesquelles des chaînes suivantes sont initialisées correctement ? Corrigez les déclarations fausses et indiquez pour
chaque chaîne de caractères le nombre d'octets qui sera réservé en mémoire.
a) char a[] = "un\ndeux\ntrois\n";
b) char b[12] = "un deux trois";
c) char c[] = 'abcdefg';
d) char d[10] = 'x';
Page 44
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
e) char e[5] = "cinq";
f) char f[] = "Cette " "phrase" "est coupée";
g) char g[2] = {'a', '\0'};
h) char h[4] = {'a', 'b', 'c'};
i) char i[4] = "'o'";
Exemple
Relation de précédence
De la précédence alphabétique des caractères, on peut déduire une relation de précédence 'est inférieur à' sur
l'ensemble des caractères. Ainsi, on peut dire que '0' est inférieur à 'Z' et noter ‘0' < 'Z' car dans l'alphabet de la
machine, le code du caractère '0' (ASCII: 48) est inférieur au code du caractère 'Z' (ASCII: 90).
Exemples
"ABC" précède "BCD" car 'A'<'B'
"ABC" précède "B" car 'A'<'B'
"Abc" précède "abc" car 'A'<'a'
"ab" précède "abcd" car "" précède "cd"
" ab" précède "ab" car ' '<'a' (le code ASCII de ' ' est 32, et le code ASCII de 'a' est 97)
Page 45
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Remarque
Malheureusement, il existe différents codes de caractères (p.ex. ASCII, EBCDIC, ISO) et l'ordre lexicographique
est dépendant de la machine. Même la fonction strcmp qui indique la précédence lexicographique de deux chaînes
de caractères dépend du code de caractères utilisé.
Conversions et tests
En tenant compte de l'ordre alphabétique des caractères, on peut contrôler le type du caractère (chiffre, majuscule,
minuscule).
Exemples
if (C>='0' && C<='9') printf("Chiffre\n", C);
if (C>='A' && C<='Z') printf("Majuscule\n", C);
if (C>='a' && C<='z') printf("Minuscule\n", C);
Remarque
Le code EBCDIC est organisé en zones, de façon que les caractères des trois grands groupes ne sont pas codés
consécutivement. (P.ex.: les codes des caractères 'i' et 'j' diffèrent de 8 unités). Les méthodes de conversion
discutées ci-dessus ne fonctionnent donc pas correctement dans le code EBCDIC et un programme portable doit être
écrit à l'aide des fonctions (isalpha, islower, toupper, ...) de la bibliothèque <ctype> qui sont indépendantes du code
de caractères.
Comme nous l'avons déjà vu au chapitre 4, la bibliothèque <stdio> nous offre des fonctions qui effectuent l'entrée et
la sortie des données. A côté des fonctions printf et scanf que nous connaissons déjà, nous y trouvons les deux
fonctions puts et gets, spécialement conçues pour l'écriture et la lecture de chaînes de caractères.
Exemples
char NOM[] = "hello, world";
puts est idéale pour écrire une chaîne constante ou le contenu d'une variable dans une ligne isolée.
Effet: puts écrit la chaîne de caractères désignée par <Chaîne> sur stdout et provoque un retour à la ligne.
En pratique, puts(TXT); est équivalent à printf("%s\n",TXT);
Exemples
char TEXTE[] = "Voici une première ligne.";
puts(TEXTE);
puts("Voici une deuxième ligne.");
scanf avec le spécificateur %s permet de lire un mot isolé à l'intérieur d'une suite de données du même ou d'un
autre type.
Effet: scanf avec le spécificateur %s lit un mot du fichier d'entrée standard stdin et le mémorise à l'adresse qui est
associée à %s.
Exemple
char LIEU[25];
int JOUR, MOIS, ANNEE;
printf("Entrez lieu et date de naissance : \n");
scanf("%s %d %d %d", LIEU, &JOUR, &MOIS, &ANNEE);
Remarques importantes
- La fonction scanf a besoin des adresses de ses arguments:
* Les noms des variables numériques (int, char, long, float, ...) doivent être marqués par le symbole '&' (voir
chap 4.4.).
* Comme le nom d'une chaîne de caractères est le représentant de l'adresse du premier caractère de la chaîne, il
ne
doit pas être précédé de l'opérateur adresse '&' !
- La fonction scanf avec plusieurs arguments présuppose que l'utilisateur connaisse exactement le nombre et l'ordre
des données à introduire! Ainsi, l'utilisation de scanf pour la lecture de chaînes de caractères est seulement
conseillée si on est forcé de lire un nombre fixé de mots en une fois.
gets est idéal pour lire une ou plusieurs lignes de texte (p.ex. des phrases) terminées par un retour à la ligne.
Exemple
int MAXI = 1000;
char LIGNE[MAXI];
gets(LIGNE);
Exercice
1°) Ecrire un programme qui lit 5 mots, séparés par des espaces et qui les affiche ensuite dans une ligne, mais dans
l'ordre inverse.
Les mots sont mémorisés dans 5 variables M1, ... ,M5. (Exemple [voici une petite phrase !] [! phrase petite une
voici])
2°) Ecrire un programme qui lit une ligne de texte (ne dépassant pas 200 caractères) la mémorise dans une variable
TXT et affiche ensuite:
a) la longueur L de la chaîne.
Page 47
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
b) le nombre de 'e' contenus dans le texte.
c) toute la phrase à rebours, sans changer le contenu de la variable TXT.
d) toute la phrase à rebours, après avoir inversé l'ordre des caractères dans TXT:
( voici une petite phrase ! ! esarhp etitep enu iciov)
3°) Ecrire un programme qui lit un texte TXT (de moins de 200 caractères) et qui enlève toutes les apparitions du
charactère 'e' en tassant les éléments restants. Les modifications se feront dans la même variable TXT.
(Exemple: [ Cette ligne contient quelques lettres e.] [ Ctt lign contint qulqus lttrs .])
B°) Les fonctions de <string>
La bibliothèque <string> fournit une multitude de fonctions pratiques pour le traitement de chaînes de caractères.
Voici une brève description des fonctions les plus fréquemment utilisées.
Dans le tableau suivant, <n> représente un nombre du type int. Les symboles <s> et <t> peuvent être remplacés
par :
* une chaîne de caractères constante
* le nom d'une variable déclarée comme tableau de char
* un pointeur sur char (voir paragraphe III)
Exercices
1°) Ecrire un programme qui demande l'introduction du nom et du prénom de l'utilisateur et qui affiche alors la
longueur totale du nom sans compter les espaces. Employer la fonction strlen.
(Exemple: Introduisez votre nom et votre prénom: Mickey Mouse Bonjour Mickey Mouse ! Votre nom est
composé de 11 lettres.)
2°) Ecrire un programme qui lit deux chaînes de caractères CH1 et CH2, les compare lexicographiquement et
affiche le résultat:
(Exemple: Introduisez la première chaîne: ABC Introduisez la deuxième chaîne: abc "ABC" précède "abc")
3°) Ecrire un programme qui lit deux chaînes de caractères CH1 et CH2 et qui copie la première moitié de CH1 et la
première moitié de CH2 dans une troisième chaîne CH3. Afficher le résultat.
a) Utiliser les fonctions spéciales de <string>.
b) Utiliser uniquement les fonctions gets et puts.
4°) Ecrire un programme qui lit un verbe régulier en "er" au clavier et qui en affiche la conjugaison au présent de
l'indicatif de ce verbe. Contrôlez s'il s'agit bien d'un verbe en "er" avant de conjuguer. Utiliser les fonctions gets,
puts, strcat et strlen.
Exemple: verbe : fêter
je fête nous fêtons
tu fêtes vous fêtez
il fête ils fêtent
Page 48
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
C°) Les fonctions de <stdlib>
La bibliothèque <stdlib> contient des déclarations de fonctions pour la conversion de nombres en chaînes de
caractères et vice-versa.
Les fonctions de conversion suivantes fournissent une valeur du type int qui peut être représentée comme caractère;
la valeur originale de <c> reste inchangée:
Page 49
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
III LES POINTEURS
L'importance des pointeurs en C
La plupart des langages de programmation offrent la possibilité d'accéder aux données dans la mémoire de
l'ordinateur à l'aide de pointeurs, càd. à l'aide de variables auxquelles on peut attribuer les adresses d'autres
variables.
En C, les pointeurs jouent un rôle primordial dans la définition de fonctions: Comme le passage des paramètres en
C se fait toujours par la valeur, les pointeurs sont le seul moyen de changer le contenu de variables déclarées dans
d'autres fonctions.
Ainsi le traitement de tableaux et de chaînes de caractères dans des fonctions serait impossible sans l'utilisation de
pointeurs. En outre, les pointeurs nous permettent d'écrire des programmes plus compacts et plus efficaces et
fournissent souvent la seule solution raisonnable à un problème. Ainsi, la majorité des applications écrites en C
profitent extensivement des pointeurs.
Exemple
Exemple
Soit A une variable contenant la valeur 10 et P un pointeur qui contient l'adresse de A. En mémoire, A et P peuvent
se présenter comme suit:
Remarque
Les pointeurs et les noms de variables ont le même rôle: Ils donnent accès à un emplacement dans la mémoire. Il
faut quand même bien faire la différence:
* Un pointeur est une variable qui peut 'pointer' sur différentes adresses.
* Le nom d'une variable reste toujours lié à la même adresse.
A°) Les opérateurs de base
Lors du travail avec des pointeurs, nous avons besoin :
- d'un opérateur 'adresse de' : & pour obtenir l'adresse d'une variable.
- d'un opérateur 'contenu de' : * pour accéder au contenu d'une adresse.
- d'une syntaxe de déclaration pour pouvoir déclarer un pointeur.
Exemple
int N;
printf("Entrez un nombre entier : ");
scanf("%d", &N);
Attention !
L'opérateur & peut seulement être appliqué à des objets qui se trouvent dans la mémoire interne, c.-à-d. à des
variables et des tableaux. Il ne peut pas être appliqué à des constantes ou des expressions.
Représentation schématique
Soit P un pointeur non initialisé et A une variable (du même type) contenant la valeur 10 :
Exemple
Soit A une variable contenant la valeur 10, B une variable contenant la valeur 50 et P un pointeur non initialisé:
Exemple
Le programme complet effectuant les transformations de l'exemple ci-dessus peut se présenter comme suit:
main()
{
/* déclarations */
short A = 10;
short B = 50;
short *P;
/* traitement */
P = &A;
B = *P;
*P = 20;
return 0;
}
Remarque
Lors de la déclaration d'un pointeur en C, ce pointeur est lié explicitement à un type de données. Ainsi, la variable
PNUM déclarée comme pointeur sur int ne peut pas recevoir l'adresse d'une variable d'un autre type que int.
Nous allons voir que la limitation d'un pointeur à un type de variables n'élimine pas seulement un grand nombre de
sources d'erreurs très désagréables, mais permet une série d'opérations très pratiques sur les pointeurs.
Exemple
Après l'instruction P = &X; les expressions suivantes, sont équivalentes:
Y = *P+1 Y = X+1
*P = *P+10 X = X+10
*P += 2 X += 2
++*P ++X
Page 52
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
(*P)++ X++
Dans le dernier cas, les parenthèses sont nécessaires, comme les opérateurs unaires * et ++ sont évalués de droite à
gauche, sans les parenthèses le pointeur P serait incrémenté et non pas l'objet sur lequel il pointe. On peut
uniquement affecter des adresses à un pointeur.
Le pointeur NUL
Seule exception: La valeur numérique 0 (zéro) est utilisée pour indiquer qu'un pointeur ne pointe 'nulle part'.
int *P; P = 0;
Finalement, les pointeurs sont aussi des variables et peuvent être utilisés comme telles. Soit P1 et P2 deux pointeurs
sur int, alors l'affectation P1 = P2; copie le contenu de P2 vers P1. P1 pointe alors sur le même objet que P2.
Remarque P1=&A;
Int *P ; P2=*P1/=*P2;
Int A ; return 0;
P désigne l'adresse du pointeur P }
*A est illégal (puisque A n'est pas un pointeur) Copiez le tableau suivant et complétez-le pour
chaque instruction du programme ci-dessus.
Exercice 9.1 A B C P1
main() P2
{ Init. 1 2 3 /
int A = 1; /
int B = 2; P1=&A 1 2 3 &A
int C = 3; /
int *P1, *P2; P2=&C
P1=&A; *P1=(*P2)++
P2=&C; P1=P2
*P1=(*P2)++; P2=&B
P1=P2; *P1-=*P2
P2=&B; ++*P2
*P1-=*P2; *P1*=*P2
++*P2; A=++*P2**P1
*P1*=*P2; P1=&A
A=++*P2**P1; *P2=*P1/=*P2
Exemple
En déclarant un tableau A de type int et un pointeur P sur int,
int A[10];
Page 53
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
int *P;
l'instruction: P = A; est équivalente à P = &A[0];
Si P pointe sur une composante quelconque d'un tableau, alors P+1 pointe sur la composante suivante. Plus
généralement, P+i pointe sur la ième composante derrière P et P-i pointe sur la i ème composante devant P.
Remarque
Au premier coup d'oeil, il est bien surprenant que P+i n'adresse pas le i-ième octet derrière P, mais la i-ième
composante derrière P ...
Ceci s'explique par la stratégie de programmation 'défensive' des créateurs du langage C:
Si on travaille avec des pointeurs, les erreurs les plus perfides sont causées par des pointeurs mal placés et des
adresses mal calculées. En C, le compilateur peut calculer automatiquement l'adresse de l'élément P+i en ajoutant à
P la grandeur d'une composante multipliée par i. Ceci est possible, parce que:
- chaque pointeur est limité à un seul type de données ;
- le compilateur connaît le nombre d'octets des différents types.
Exemple
Soit A un tableau contenant des éléments du type float et P un pointeur sur float:
float A[20], X;
float *P;
X contient la valeur du 10-ième élément de A, (c.-à-d. celle de A[9]). Une donnée du type float ayant besoin de 4 octets, le
compilateur obtient l'adresse P+9 en ajoutant 9 * 4 = 36 octets à l'adresse dans P.
Attention !
Il existe toujours une différence essentielle entre un pointeur et le nom d'un tableau:
- Un pointeur est une variable, donc des opérations comme P = A ou P++ sont permises.
- Le nom d'un tableau est une constante, donc des opérations comme A = P ou A++ sont impossibles.
Résumons, soit un tableau A d'un type quelconque et i un indice pour les composantes de A, alors
Si P = A, alors
P pointe sur l'élément A[0]
P+i pointe sur l'élément A[i]
*(P+i) désigne le contenu de A[i]
Page 54
Support de cours OUTILS DE DEVELOPPEMENT SADIK .M
Formalisme tableau et formalisme pointeur
A l'aide de ce bagage, il nous est facile de 'traduire' un programme écrit à l'aide du 'formalisme tableau' dans un
programme employant le 'formalisme pointeur'.
Exemple
Les deux programmes suivants copient les éléments positifs d'un tableau T dans un deuxième tableau POS.
Page 55
Formalisme tableau
main() main()
{ {
int T[10] = {-3, 4, 0, -7, 3, 8, 0, -1, 4, -9}; int T[10] = {-3, 4, 0, -7, 3, 8, 0, -1, 4, -9};
int POS[10]; int POS[10];
int I,J; /* indices courants dans T et POS */ int I,J; /* indices courants dans T et POS */
for (J=0,I=0 ; I<10 ; I++) for (J=0,I=0 ; I<10 ; I++)
if (T[I]>0) if (*(T+I)>0)
{ {
POS[J] = T[I]; *(POS+J) = *(T+I);
J++; J++;
} }
return 0; return 0;
} }
Formalisme pointeur
Sources d'erreurs
Un bon nombre d'erreurs lors de l'utilisation de C provient de la confusion entre contenu et adresse, pointeur et
variable. Revoyons donc les trois types de déclarations que nous connaissons jusqu'ici et résumons les possibilités
d'accès aux données qui se présentent.
Exercice 2
Ecrire un programme qui lit deux tableaux A et B et leurs dimensions N et M au clavier et qui ajoute
les éléments de B à la fin de A. Utiliser le formalisme pointeur à chaque fois que cela est possible.
Comme les pointeurs jouent un rôle si important, le langage C soutient une série d'opérations
arithmétiques sur les pointeurs que l'on ne rencontre en général que dans les langages machines. Le
confort de ces opérations en C est basé sur le principe suivant:
Toutes les opérations avec les pointeurs tiennent compte automatiquement du type et de la grandeur des
objets pointés.
Exemples
int A[10];
int *P;
P = A+9;
/* dernier élément -> légal */
P = A+10;
/* dernier élément + 1 -> légal */
P = A+11;
/* dernier élément + 2 -> illégal */
P = A-1;
/* premier élément - 1 -> illégal */
Exercice 1
Soit P un pointeur qui 'pointe' sur un tableau A:
int A[] = {12, 23, 34, 45, 56, 67, 78, 89, 90};
int *P;
P = A;
Quelles valeurs ou adresses fournissent ces expressions:
a) *P+2 b) *(P+2) c) &P+1 d) &A[4]
e) A+3 f) &A[7]-P g) P+(*P-10) h) *(P+*(P+8)-A[7])
Exercice 2
Ecrire un programme qui lit un entier X et un tableau A du type int au clavier et élimine toutes les occurrences de X
dans A en tassant les éléments restants. Le programme utilisera les pointeurs P1 et P2 pour parcourir le tableau.
Exercice 3
Ecrire un programme qui range les éléments d'un tableau A du type int dans l'ordre inverse. Le programme utilisera
des pointeurs P1 et P2 et une variable numérique AIDE pour la permutation des éléments.
De la même façon qu'un pointeur sur int peut contenir l'adresse d'un nombre isolé ou d'une composante
d'un tableau, un pointeur sur char peut pointer sur un caractère isolé ou sur les éléments d'un tableau de
caractères. Un pointeur sur char peut, en plus, contenir l'adresse d'une chaîne de caractères constante et il
peut même être initialisé avec une telle adresse.
A la fin de ce paragraphe, nous allons anticiper avec un exemple et montrer que les pointeurs sont les
éléments indispensables mais effectifs des fonctions en C.
Affectation
On peut attribuer l'adresse d'une chaîne de caractères constante à un pointeur sur char:
Exemple
char *C;
C = "Ceci est une chaîne de caractères constante";
Nous pouvons lire cette chaîne constante (p.ex: pour l'afficher), mais il n'est pas recommandé de la modifier, parce
que le résultat d'un programme qui essaie de modifier une chaîne de caractères constante n'est pas prévisible en
ANSI-C.
Initialisation
Un pointeur sur char peut être initialisé lors de la déclaration si on lui affecte l'adresse d'une chaîne de caractères
constante: char *B = "Bonjour !";
Attention !
Il existe une différence importante entre les deux déclarations:
char A[] = "Bonjour !"; /* un tableau */
char *B = "Bonjour !"; /* un pointeur */
A est un tableau qui a exactement la grandeur pour contenir la chaîne de caractères et la terminaison '\0'. Les
caractères de la chaîne peuvent être changés, mais le nom A va toujours pointer sur la même adresse en mémoire.
B est un pointeur qui est initialisé de façon à ce qu'il pointe sur une chaîne de caractères constante stockée quelque
part en mémoire. Le pointeur peut être modifié et pointer sur autre chose. La chaîne constante peut être lue, copiée
ou affichée, mais pas modifiée.
Modification
Si nous affectons une nouvelle valeur à un pointeur sur une chaîne de caractères constante, nous risquons de perdre
la chaîne constante. D'autre part, un pointeur sur char a l'avantage de pouvoir pointer sur des chaînes de n'importe
quelle longueur:
Exemple
char *A = "Petite chaîne";
char *B = "Deuxième chaîne un peu plus longue";
A = B;
Attention !
Les affectations discutées ci-dessus ne peuvent pas être effectuées avec des tableaux de caractères:
Exemple
char A[45] = "Petite chaîne";
char B[45] = "Deuxième chaîne un peu plus longue";
char C[30];
A = B; /* IMPOSSIBLE -> ERREUR !!! */
C = "Bonjour !"; /* IMPOSSIBLE -> ERREUR !!! */
Dans cet exemple, nous essayons de copier l'adresse de B dans A, respectivement l'adresse de la chaîne constante
dans C. Ces opérations sont impossibles et illégales parce que l'adresse représentée par le nom d'un tableau reste
toujours constante.
Pour changer le contenu d'un tableau, nous devons changer les composantes du tableau l'une après l'autre (p.ex.
dans une boucle) ou déléguer cette charge à une fonction de <stdio> ou <string>.
Conclusions:
Utilisons des tableaux de caractères pour déclarer les chaînes de caractères que nous voulons modifier.
Utilisons des pointeurs sur char pour manipuler des chaînes de caractères constantes (dont le contenu ne change
pas).
Utilisons de préférence des pointeurs pour effectuer les manipulations à l'intérieur des tableaux de caractères. (voir
aussi les remarques ci-dessous).
Perspectives et motivation
- Avantages des pointeurs sur char
Comme la fin des chaînes de caractères est marquée par un symbole spécial, nous n'avons pas besoin de connaître la
longueur des chaînes de caractères; nous pouvons même laisser de côté les indices d'aide et parcourir les chaînes à
l'aide de pointeurs.
Cette façon de procéder est indispensable pour traiter de chaînes de caractères dans des fonctions. En anticipant sur
la matière du chapitre 10, nous pouvons ouvrir une petite parenthèse pour illustrer les avantages des pointeurs dans
la définition de fonctions traitant des chaînes de caractères:
Pour fournir un tableau comme paramètre à une fonction, il faut passer l'adresse du tableau à la fonction. Or, les
paramètres des fonctions sont des variables locales, que nous pouvons utiliser comme variables d'aide. Bref, une
fonction obtenant une chaîne de caractères comme paramètre, dispose d'une copie locale de l'adresse de la chaîne.
Cette copie peut remplacer les indices ou les variables d'aide du formalisme tableau.
Discussion d'un exemple
Reprenons l'exemple de la fonction strcpy, qui copie la chaîne CH2 vers CH1. Les deux chaînes sont les arguments
de la fonction et elles sont déclarées comme pointeurs sur char. La première version de strcpy est écrite entièrement
à l'aide du formalisme tableau:
Dans une première approche, nous pourrions remplacer simplement la notation tableau[I] par *(tableau + I), ce qui
conduirait au programme:
Exercice 9.7
Ecrire un programme qui lit deux tableaux d'entiers A et B et leurs dimensions N et M au clavier et qui ajoute les
éléments de B à la fin de A. Utiliser deux pointeurs PA et PB pour le transfer et afficher le tableau résultant A.
Exercice 9.8
Ecrire de deux façons différentes, un programme qui vérifie sans utiliser une fonction de <string>, si une chaîne CH
introduite au clavier est un palindrome:
a) en utilisant uniquement le formalisme tableau
b) en utilisant des pointeurs au lieu des indices numériques
Rappel: Un palindrome est un mot qui reste le même qu'on le lise de gauche à droite ou de droite à gauche:
Exemples:
PIERRE ==> n'est pas un palindrome
OTTO ==> est un palindrome
23432 ==> est un palindrome
Exercice 9.9
Ecrire un programme qui lit une chaîne de caractères CH et détermine la longueur de la chaîne à l'aide d'un pointeur
P. Le programme n'utilisera pas de variables numériques.
Exercice 9.10
Ecrire un programme qui lit une chaîne de caractères CH et détermine le nombre de mots contenus dans la chaîne.
Utiliser un pointeur P, une variable logique, la fonction isspace et une variable numérique N qui contiendra le
nombre des mots.
Exercice 9.11
Ecrire un programme qui lit une chaîne de caractères CH au clavier et qui compte les occurrences des lettres de
l'alphabet en ne distinguant pas les majuscules et les minuscules. Utiliser un tableau ABC de dimension 26 pour
mémoriser le résultat et un pointeur PCH pour parcourir la chaîne CH et un pointeur PABC pour parcourir ABC.
Afficher seulement le nombre des lettres qui apparaissent au mois une fois dans le texte.
Exemple:
Entrez une ligne de texte (max. 100 caractères) :
Jeanne
La chaîne "Jeanne" contient :
1 fois la lettre 'A'
2 fois la lettre 'E'
1 fois la lettre 'J'
3 fois la lettre 'N'
Exercice 9.12
Ecrire un programme qui lit un caractère C et une chaîne de caractères CH au clavier. Ensuite toutes les occurrences
de C dans CH seront éliminées. Le reste des caractères dans CH sera tassé à l'aide d'un pointeur et de la fonction
strcpy.