Sie sind auf Seite 1von 51

LES OPERATEURS ET LES EXPRESSIONS

Eléments de contenu :
•Les opérateurs arithmétiques
•La conversion implicite de type dans les expressions.
•Les opérateurs relationnels
•Les opérateurs logiques
•L’opérateur d’affectation simple
•Les opérateurs d’incrémentations et de décrémentation
•Les opérateurs de manipulations de bits
•Les opérateurs d’affectation élargie
•L opérateur conditionnel
•Les con versions explicites
•L’opérateur de “cast”
•L opérateur séquentiel
•L opérateur sizeof
1- LES OPERATEURS ARITHMETIQUES
I.1 Leur rôle
On trouve en C
• 5 opérateurs BINAIRES “ (c’est à dire qui portent sur deux opérandes)
• 2 opérateurs unaires qui portent sur un seul opérande.
Le tableau suivant vous fournit la liste de ces différents opérateurs
TYPE OPERATEUR ROLE
Binaire + somme
- différence
* produits
/ quotient
% reste de division
(modulo)
Unaire - opposé
+ Identité
A l’exception de l’opérateur “%“ qui ne peut porter que sur des entiers, ces opérateurs peuvent
s’appliquer à tous les types numériques. Cela signifie qu’un même symbole tel que ”*” définit
on fait plusieurs opérations différentes suivant le type des quantités sur lesquelles il porte.
Les opérateursBINAIRES ne sont, à priori, définie que pour des opérandes de même type et
ils fournissent un résultat de ce même type. Ce dernier point mérite un peu d’attention. En effet,
si ‘on s’attend effectivement à ce que la somme e deux entiers soit un entier, les choses sont
moins évidentes pour l’opérateur quotient (“/”). Ainsi par exemple, 5/2 est le quotient de deux
valeurs doubles, et à ce titre fournit bien le résultat 2.5 qui est aussi (le type double. Par contre
5/2 est le quotient de deux valeurs de type int et le résultat est de type int, a savoir ici 2.
Remarque
Il n’existe pas d’opérateur d’élévation à la puissance, il est nécessaire de faire appel soit â des
produits successifs, soit à la fonction power de la bibliothèque.
L2 Leurs priorités relatives
Lorsque plusieurs opérateurs apparaissent. dans une même expression, il est nécessaire de
savoir dans quel ordre ils sont mis enjeu. En C, comme dans les autres langages, les règles sont
naturelles et rejoignent celles de l’algèbre traditionnelle ( en ce qui concerne les opérateurs
arithmétiques)
Les opérateur unaire + et - ont la priorité la plus élevée, On trouve on suite à un même niveau
es opérateurs *,/ et % . Enfin sur un dernier niveau apparaissent les opérateurs binaires + et - .
En cas de priorité identique, les calculs s’effectuent de gauche à droite. Enfin, des parenthèses
permettent d’outre passer ces règles de porte en forçant e calcul préalable de l’expression
qu’elles contiennent. Notez que ces parenthèses peuvent également être employées jour assurer
une meilleure lisibilité d’une expression.
Voici quelques exemples d’expressions équivalentes
a+b*c a+(b* c)
a*b+c%d (a*b)+(c%c)
-c % d (-c)%d
-a/-b+c ((-a)/(-b))+c
Il- LES CONVERSIONS IMPLICITES DE TYPE DANS LES EXPRESSIONS
A l’opposé de ce qui se passe dans un langage comme Pascal, Le langage C est très tolérant vis
a vis du mélange des types dans les expressions.
Ainsi, par exemple, si n est le type int, et x de type double, une expression telle que:
n+x
sera acceptée par le compilateur. Elle conduira à la mise en place d’instructions réalisant :
• la conversion de n en double. Cette opération a un sens et est toujours possible.
• l’addition des deux valeurs de type double. Le résultat sera lui aussi de type double.
On dit qu’il y a eu mise en place de “conversions implicites” puisque celles—ci n’ont pas été
explicitées au sein des instructions.
D’une manière générale, on peu dire que si un opérateur porte sur deux opérandes de type
différents, il y a en quelque sorte, conversion du type “le plus faible’ dans le type “le plus fort”.
Ainsi, dans notre exemple double était plus fort que int.
Une grande originalité du C est d’étendre ces facilités de conversions aux types caractères.
Cette fois cependant le mécanisme sera un peu diffèrent car tout opérande de
type char apparaissant dans une expression sera systématiquement converti en int avant
d’intervenir dans les calculs.
Il est à noter aussi que tous les calculs flottants ne s’effectuent que sur des valeurs de
type double. Pour ce faire tout opérande de type float est systématiquement converti en double.
11.1 Exemples de conversions implicites sur des types numériques
Supposons que nous avons effectué les déclarations suivantes :
int, p
Long q, r
float x
double z
Voici trois exemples d’expressions et la manière dont elles vont être évaluées :
n+q
La valeur de n est convertie en long avant d’être ajoutée à celle de q. Le résultat est de
type long
p + n *p
Le produit n * p est évalué en int puis le résultat est converti en long pour être ajouté à. la
valeur de q. Le résultat est de type long
x*n
Les valeurs de x et de n sont converties en double . Le résultat est de type double.Bien
entendu, rien n’empêchera ce résultat d’être assigné à une variable de type floa.t(moyennant
une conversion supplémentaire ).
II .2 Exemples de conversions implicites de char et int
Supposons que nous avons effectué les déclarations suivantes :
int n
Char cl , c2
Voici, à nouveau quelques exemples d’expressions et la manière dont elles sont évaluées :
C1 + 1
La valeur de cl est d’abord convertie on int. Cette conversion revient en fait à considérer que
les 8 bits occupés par cl forment une valeur entière codée de la même manière que les entiers
(mais sur 8 bits aura lieu de 16). Il faudra toutefois distinguer deux cas suivant que cl possède
l’attribut signed ou unsigned :
• si cl est unsigned la conversion revient effectivement à prendre la valeur du code ASCII du
caractère on obtiendra ainsi, une valeur entière comprise entre 0 et 255.
• si cl est signed, le premier des 8bits Sera interprété comme un bit de signe. On obtiendra
ainsi une valeur entière comprise entre -128 et 127.
au résultat de cette conversion, on ajoutera la valeur entière de 1.
cl + n
Là encore, la valeur de cl sera convertie en int. Le résultat sera ajoutée la valeur n pour fournir
une valeur de type int.
c2 - cl
Ici, bien que les deux opérandes soient de type char, il y aura néanmoins conversion préalable
des valeurs de cl et c2 on int .avant qu’en soit calculée la différence. Le résultat est de type int.
II-3 Règles générales de con version implicite
a) Conversions systématiques et conversion d’ajustement de type
Comme on a vu dans es exemples précédents, il faut distinguer les conversions qui ont lieu
systématiquement de celles qui ne sont réalisées qu’en cas de besoin par ”absorption” par un
type plus fort.
Les conversions systématique se résument
char —> int
float —> double
Quant aux autres conversions, elles permettent de faire porter un opérateur sur des valeurs
de même type. Le type commun étant imposé par celui des opérandes ayant le type le plus fort.
Voici le schéma récapitulant ces règles. Les flèches horizontales correspondent à des
conversions systématiques. Les flèches verticales correspondent â des conversions d’ajustement
de type.
char —> int —> long —> float —> double
De plus, ces règles doivent être complétées par la manière dont sont pris en compte les attributs
signed et unsigned. Pour ce faire on va distinguer le type char des types entiers.
b) Prise en compte de l’attribut signe pour les caractères
Cet attribut intervient lors de la conversion systématique en int. S’il s’agit
d’un caractère non signé le résultat de la conversion est un entier compris entre O et 255 ; la
conversion revient simplement dans ce cas à compléter le motif de 8 bits par des zéros à
gauche. Lorsqu’il s’agit d’un caractère signé, le résultat de la conversion est un entier compris
entre - 128 et +127 ; la conversion revient à “propager le bit de signe du caractère dans les 8
bits de gauche de L’entier à fabriquer, <i C~) ~r Ct)
c) prise en compte de l’attribut signe pour les entiers
En ce qui concerne la prise en compte de l’attribut signe pour les types entiers, la règle prévoir
que unsigned .
III- LES OPERATEURS RELATIONNELS
Comme tout Langage, C permet de ‘‘comparer” les expressions à laide d’opérateurs classiques
de comparaison. En voici un exemple :
2 * a> b+5
Par contre, C se distingue des autres langages sur deux points :
.Le résultat de la comparaison est, non pas une valeur booléenne (on dit aussi ‘logique”)
prenant l’une des deux valeurs vrai ou faux, mais un entier valant :
0 si le résultat de a comparaison est faux
1 si le résultat de la comparaison est vrai.
Ainsi la comparaison ci-dessus devient en fait une expression de type entier. Cela signifie
quelle pourra éventuellement intervenir dans des calculs arithmétiques
.les expression comparées pourront être d’un type de base quelconque et elles seront soumises
aux règles de conversion présentées dans le paragraphe précédent. Cela Signifie qu’au bout du
compte on ne sera amené à comparer que des expression des types numérique.
Le tableau suivant fournit la liste des opérateurs relationnels existant en C. Remarquez bien que
la notation (==)de l’opérateur de légalité, le signe (=)étant , comme nous le verrons, réservé aux
affectations.
OPERATEUR SIGNIFICATION
< Inférieur à
<= Inférieur ou égal à
> Supérieur à
>= Supérieur ou égal à
== Egal à
!= Différent de
En ce qui concerne leurs priorités, il faut savoir que les quatre premiers opérateurs (<, <=,
>, <=) sont de même priorité. Les deux derniers (== et ! =) possédant également la même priori
té, mais celle-ci est inférieure à celle des précédents. Ainsi l’expression
a <b == c< d
est interprétée comme suit
(a <b)== (c< d)
ce qui, en C, a effectivement une signification, compte tenu de ce que les expressions a<b et
c<d sont, finalement, des quantités entières.
D’autre part ,ces opérateurs relationnels sont moins prioritaires, les opérateurs arithmétiques.
Cela permet souvent d’éviter certaines parentheses dans des expressions. Ainsi :
x + y < a+2
est équivalent à
(x+y ) < (a + 2)
IV- LES OPERATEURS LOGIQUES
C dispose de trois opérateurs logiques classiques

OPERATEUR SIGNIFICATION
&& et
II ou(inclusif)
! négation
Par exemple :
(a < b) && (c < d )
prend la valeur 1 (vrai) si les deux expressions a <b et c < d sont toutes deux vrais(de

valeur 1), la valeur 0 (faux) dans le cas contraire.


.
(a <b) II ( c <d)
prend la valeur 1 (vrai) si l’une au moins des deux expressions a < b et c< d est vraie (de valeur
1), la valeur 0 (faux) dans le cas contraire.
!(a<b)
prend la valeur I (vrai) si la condition a < b est fausse (de valeur 0 ) et la valeur (faux) dans le
cas contraire. Cette expression est équivalente à :a>= b.
Notez bien que, ne disposant pas de type logique, C représente vrai par 1 et faux par 0 c’est
pourquoi ces opérateurs produisent un résultat numérique (de type int).
De plus, on pourrait s’attendre à ce que les opérandes de ces opérateurs ne puissent être que
des expressions prenant soit la valeur 0, soit la valeur1. En fait ces opérateurs acceptent
n’import e quel opérande numérique, y compris les types flottants, avec les règles de
conversion implicite déjà rencontrées. Leur signification reste celle évoquée ci-dessus, à
condition de considérer que :
- 0 correspond à faux,
- toute valeur non nulle correspond à vrai.
Ainsi en C, si n et p sont des entiers, des expressions telles que :
n&&p n//p !n
sont acceptées par le compilateur. Notez que Ion rencontre fréquemment
if( !n)
pour dire:
if (n ==0)
L’opérateur (!) a une priorité supérieure à celle de tous les opérateurs arithmétiques binaires et
aux opérateurs relationnels. Ainsi, pour écrire la condition contraire de :
t
a == b
il est nécessaire d’utiliser des parenthèses en écrivant :
!(a== b)
En effet, l’expression
!a == b
serait interprétée comme :
(!a) ==b
L’opérateur // est mois prioritaire que &&. Tous deux sont de priorité inférieure aux opérateurs
arithmétiques ou relationnels. Ainsi, l’expression :
a < b && c < d
est équivalente à :
(a <b) && (c < d)
Enfin, les deux opérateur && et // jouissent en C d’une propriété intéressante : leur second
opérande (celui qui figure à droite de l’opérateur) n’est évaluée que si la connaissance de sa
valeur est indispensable pour décider si l’expression correspondante est vraie ou fausse. Par
exemple, dans une expression telle que :
a < b && c < d
on commence par évaluer a < b. si le résultat est faux ; il est inutile d’évaluer c < d puisque, de
toutes façons, l’expression complète aura la valeur faux (0).
V- L’OPERATEUR D’AFFECTATION SIMPLE
V. 1 Introduction
Nous avons déjà eu l’occasion de remarquer que/
i=5
était un expression qui :
- réalisait un action : l’affectation de la valeur 5 à i
- possédait une valeur: celle de i après affectation, c’est à dire 5
Cet opérateur d’affectation (=) peut faire intervenir d’autres expressions, comme dans : c=b+3
La faible priorité de cet opérateur = ( elle est inférieure à celle de tous les opérateurs
arithmétiques et de comparaison ) fait qu’il y a d’abord évaluation de l’expression
b + 3. La valeur ainsi obtenue est ensuite affectée à c.
Par contre, il n’est pas possible de faire apparaître une expression comme premier opérande de
cet opérateur =. Ainsi, l’expression suivante n’aurait pas de sens.
c+5=x
V.2 Notion de ‘‘IvaIue”
Nous voyons donc que cet opérateur d’affectation impose des restrictions sur son premier
opérande. En effet ce dernier doit être une “référence” a un“ emplacement mémoire” dont on
pourra effectivement modifier la valeur.
Dans Les autres langages, on désigne souvent une telle référence par le nom de “variable” ;on
précise généralement que ce terme, recouvre par exemple les éléments d’un tableaux ou les
composantes d’une structure.
On langage C, la syntaxe du langage est lette que cette notion de variable n’est pas assez
précise. Il faut introduire un mot nouveau : la IvaIue. Ce terme désigne une “valeur à gauche”,
c’est à dire tout ce qui peut apparaître à gauche d’un opérateur d’affectation.
V.3 Associativité
Contrairement à tous les opérateurs que nous avons rencontrés jusqu’ici, cet opérateur
d’affectation possède une associativité de “droite à gauche”. C’est ce qui permet à une
expression telle que :
i=j=5
d’évaluer d’abord l’expression j=5 avant d’en affecter la valeur (5) à la variable j. La valeur
finale de cette expression est celle de i après affectation, c’est à dire 5.
VI- LES OPERATEURS D’INCREMENTATION ET DE DECREMENTATION
VI.1 Présentation
Dans des programmes écrits dans un langage autre que C, on rencontre souvent des expressions
(ou des instructions) telles que
i=i+1
n=n-1
qui “incrémente” ou qui “décrémente” de 1 la valeur d’une“ variable” (ou plus généralement
d’une “lvalue”).
En C ces actions peuvent être réalisées par des opérateurs unaires portant sur cette “lvalue”.
Ainsi, l’expression :
++i
a pour effet l’incrémenter de 1 la valeur de i et sa valeur est celle de i après incrémentation.
Notez bien que, comme pour l’affectation, nous avons une expression qui, non seulement a une
valeur, mais qui réalise aussi une action (incrémentation de i).
Il est important de voir que la valeur de cette expression est celle de après incrémentation.
Ainsi, si la valeur de i est 5, l’expression :
n =++i-5
affectera à i la valeur de 6 et à n la valeur de 1.
Par contre, lorsque cet opérateur est placé après la “lvalue”sur laquelle il porte, la valeur de
l’expression correspondante est celle de la variable avant incrémentation. Ainsi, si la valeur
de i est 5, l’expression :
n = i++ - 5
affectera à i la valeur de 6 et à n la valeur de 0(car ici la valeur de l’expression i++ est 5).
On dit que ++est :
• un opérateur de pré-incrémentation lorsqu’il est placé à gauche de la “lvalue” sur laquelle il
porte.
• un opérateur de post-incrémentation lorsqu’il est placé à droite de la “lvalue” sur laquelle il
porte.
Lorsque seul importe l’effet d’incrémentation d’une “lvalue”, cet opérateur peut être
indifféremment placé avant ou après. Ainsi, ces deux instructions sont équivalentes :
i++ ;
++i ;
De la même manière, il existe un opérateur de décrémentation noté :
qui, suivant les ces, sera :
• un opérateur de pré-décrémentation lorsqu’il est placé à gauche de la “lvalue” sur laquelle il
porte.
• un opérateur de post-décrémentation lorsqu’il est placé à droite de la “lvalue” sur laquelle il
porte.
VI.2 Leurs priorités
Les priorités élevées de ces opérateurs unaires permettent d’écrire des expressions assez
compliquées sans qu’il ne soit nécessaire d’employer des parenthèses pour isoler la Ivalue sur
laquelle ils portent. Ainsi, l’expression suivante a un sens :
3 * I++ *j-- + k++
VI.3Leur intérêt
Ces opérateurs allègent l’écriture de certaines expressions et offrent surtout le grand avantage
d’éviter la ‘redondance” qui est de mise dans la plus part des autres langages. En effet, dans une
notation telle que :
i++
on ne“ cite” qu’une seule fois la “lvalue” concernée alors qu’on est amené à le faire deux fois
dans la notation
i=i+1
Les risques d’erreurs de programmation s’en trouvent ainsi quelque peu limités. Cet aspect
prendra d’autant plus d’importance que la“ lvalue” correspondante sera d’autant plus complexe.
D’une manière générale, nous utiliserons fréquemment ces opérateurs dans la manipulation de
tableaux ou de chaînes de caractères.
VII- LES OPERATEURS DE MANIPULATION DE BITS
VII.1Présentation des opérateurs de manipulations de bits
Le langage C dispose d’opérateurs permettant de travailler directement sur le“ motifBINAIRE
“ d’une valeur. Ceux-ci lui procurent ainsi les possibilités traditionnelles réservées à la
programmation en langage assembleur.
A priori, ces opérateurs ne peuvent porter que sur des types entiers. Toutefois, compte tenu des
règles de conversion implicite, ils pourront également s’appliquer à des caractères (mais le
résultat en sera entier).
TYPE OPERATEUR SIGNIFICATION
BINAIRE & et (bit à bit)
I ou (bit à bit)
^ ou exclusif bit à bit)
<< décalage à gauche
>> Décalage à droite
Unaire ~ Complément à un (bit à
bit)
VII.2 Les opérateurs “bit à bit”
Les trois opérateurs &, I et ^ appliquent en fait la même opération à chacun des bits dés deux
opérandes. Leur résultat peut ainsi être défini à partir ‘une table dite “table de vérité”
fournissant le résultat de cette opération. lorsqu’on la fait porter sur deux bits de même rang de
chacun des deux opérandes. Cette table est donnée dans le tableau suivant :
Opérande 1 0 0 1 1
Opérande 2 0 1 0 1
ET(&) 0 0 0 1
OU inclusif (I) 0 1 1 1
OU exclusif (^) 0 1 1 0
L’opérande unaire~ (“dit de complément à un”) est également de type “bit à bit”. Il se contente
d’inverser chacun des bits de sont unique opérande (0 donne 1 et 1 donne 0).
Notez que la qualificatif signed /unsigned des opérandes n’a pas incidence sur le
motifBINAIRE créé par ces opérateurs.
VII..3 Les opérateurs de décalage
Ils permettent de réaliser des “décalages à droite ou à gauche” sur le motif binaire
correspondant à leur premier opérande. L’amplitude du décalage exprimée en nombre de bits
est définie par le second opérande. Par exemple :
n <<2
fournit comme résultat la valeur obtenue en décalant le “motif binaire” de n de 2 bits vers la
gauche ; les bits de gauche sont perdus et des bits à zéro apparaissent à droite.
De même
n>>3
fournit comme résultat la valeur obtenue en décalant le “motif binaire” de n de 3 bits vers la
droite les bits de droite sont perdus et des bits apparaissent à gauche.
Ces derniers dépendent du qualificatif signe/unsigned du premier opérande. S’il s’agit
d’un unsigned, les bits ainsi créés à gauche seront à zéro. S’il s’agit d’un signed, les bits ainsi
crées seront identiques au bit de signe du premier opérande. On dit qu’il y propagation du bit de
signe.
VIII- LES OPERATEURS D’AFFECTATION ELARGIE
Nous avons déjà vu comment les opérateur d’incrémentation permettaient de simplifier
l’écriture de certaines affectations. Ainsi, par exemple :
i++
remplaçait avantageusement :
i=i+ 1
Mais C dispose d’opérateurs encore plus puissants. Ainsi, vous pourrez remplacer :
i=i+k
par:
i+= k
ou encore :
a=a*b
par :
a *= b
ou même
n = n <<3
par :
n <<= 3
D’une manière générale C permet de condenser les affectations de la forme :
Ivalue = ivalue opérateur expression
En :
ivalue opérateur= expression
Cette possibilité concerne tous les opérateurs binaires arithmétiques et de manipulation de bits.
Voici la liste complète de tous ces nouveaux opérateurs nommés “opérateurs d’affectation
élargie“ :
+= -= *= /= %= I= ^= &= <<= >>=
Ces opérateurs, comme ceux d’incrémentation permettent de condense l’écriture de certaines
Instructions et contribuent à éviter le redondance introduite fréquemment par l’opérateur
d’affectation classique.
IX- L’OPERATEUR CONDITIONNEL
Considérons cette “structure de choix” :
If(a>b)
max= a;
p
e/se
max=b ;
Elle attribut à la variable max la plus grande des deux valeurs de a et de b. La valeur de max
pourrait être définie par cette phrase :
Si a>b alors a sinon b
En langage C, il est possible, grâce à laide de “l’opérateur conditionnel”, de traduire presque
littéralement la phrase ci-dessus de la manière suivante :
max = a>b ? a: b
L’expression figurant à droite de l’opérateur d’affectation est en fait constituer de trois
expressions (a>b, a et b) qui sont les trois opérandes de l’opérateur conditionnel, lequel se
matérialise par 2 symboles séparés :? et :
D’une manière générale, cet opérateur évalue la première expression qui joue le rôle d’une
condition .Comme toujours en C, celle-ci peut être de n’importe quel type. Si Sa valeur est
diffèrent de zéro, il y a évaluation du second opérande, ce qui fournit le résultat, par contre, si
sa valeur est nulle, il y a évaluation du troisième opérande, ce qui fournit le résultat.
La priorité de l’opérateur conditionnel est faible (il arrive juste avant l’affectation), de sorte
qu’il est rarement nécessaire d’employer des parenthèses pour en délimiter les différents
opérandes.
X- LES CONVERSIONS EXPLICITES - L’OPERATEUR DE ‘‘CAST”
Nous avons vu comment le compilateur pouvait être amené à mettre en place des “conversions
implicite” dans l’évaluation de certaines expressions.
par ailleurs, nous avons dit que l’affectation pouvait conduire à la mise en place d’une
”conversion d’office” dans le cas où le type de l’expression était différent de celui de la
“lvalue” réceptrice. De plus, en C, le programmeur peut, s’il le souhaite, forcer la conversion
d’un expression quelconque dans un type de son choix, à laide d’un opérateur unaire nommé en
anglais “cast’
Ces deux sortes de conversions “forcées” (affectation ou “cast”) suivent les mêmes règles, de
sorte qu’il nous suffira de vous les présenter sur l’opérateur de “cast”.
X.1 L’opérateur de “cast”
Si, par exemple n et p sont des variables entière, l’expression
(double) (n/p)
aura comme valeur celle de l’expression entière n/p convertie en double.
La notation (double) correspond en fait à un opérateur unaire dont le rôle est d’effectuer la
conversion dans le type double de l’expression sur laquelle il porte. Notez bien que cet
opérateur force la conversion du résultat de l’expression et non celle des différentes valeurs qui
concourent à son évaluation, autrement dit, ici, il y a d’abord calcul, dans je type bit du quotient
de n par p ; c’est seulement ensuite que le résultat sera converti en double.
D’une manière générale, il existe autant d’opérateur “cast” que de types différents (y compris
les types dérivés que nous rencontrerons ultérieurement). Leur priorité élevée fait qu’il est
généralement nécessaire de placer entre parenthèses l’expression concernée. Ainsi,
l’expression :
(double) n/p
conduirait d’abord à convertir n en double ; les régles de conversions implicites amènerait alors
à convertir p en double avant que n’ait lieu la division (en double). Le résultat serait alors
différent de celui obtenu par l’expression donnée en début de ce paragraphe.
Xl- L’OPERATEUR SEQUENTIEL
L’opérateur dit “séquentiel” permet, en quelque sorte, d’exprimer plusieurs calculs successifs
au sein d’une même expression. Par exemple :
a * b, i +j
est une expression qui évalue d’abord a * b, puis i+j et qui prend comme valeur la dernière
calculée (donc celle de i + j). Certes, dans cet exemple le calcul préalable de a*b est inutile,
puisqu’il n’intervient pas dans la valeur de l’expression globale et qu’il ne réalise aucune
action.
Par contre, une expression telle que :
I++, a+b
peut présenter un intérêt puisque la première expression (dont la valeur ne sera pas utilisée)
réalise en fait l’incrémentation de la variable i.
Il en est de même de l’expression suivante :
i++,j= i + k
dans laquelle il y a:
- évaluation de l’expression i++,
- évaluation de l’affectation j= i+k, notez alors qu’on utilise la valeur de i après
incrémentation par l’expression précédente.
Cet opérateur séquentiel qui jouit dune associativité de “gauche à droite”, peut facilement faire
intervenir plusieurs expressions (sa faible priorité évite l’usage des parenthèses)
i++,j=I+k,j- -
i++;j+I+k;j--
Un tel opérateur peut être utilisé pour réunir plusieurs instructions en une seule. Ainsi, les deux
formulations suivantes sont équivalentes :
i++,j=I+k,j- -
i++;j=I+k;j- -
Dans la pratique, ce n’est pas là le principal usage de cet opérateur séquentiel. Par contre, ce
dernier pourra fréquemment intervenir dans les instructions de choix ou dans les boucles
comme dans l’exemple suivant
If (i++,k>0)….
remplace
i++;if(k>0)…
Remarque importante:
Ne confondez pas l’opérateur séquentiel (,) avec la virgule utilisée (avec la même priorité)
comme séparateur dans une liste d’arguments dans un appel de fonction comme, par exemple
printf (“%d %d”,n+2,p);
Si vous souhaitez utiliser cet opérateur séquentiel dans une telle liste, il est nécessaire d’en
placer le résultat entre parenthèses. Par exemple :
printf (“%d %d”,a,(b==5,3*b));
- imprime la valeur de a,
- évalue l’expression : b=5,3*b, ce qui conduit à affecter 5 à b et à calculer ensuite la valeur
de 3 * b.
- afficher la valeur de cette expression, c’est à dire la valeur de 3 * b.
XII- L’OPERAXEUR SIZEOF
l’opérateur sizeof dont l’emploi ressemble à celui d’une fonction fournit la taille d’une variable
de nom donné. Par exemple, si x est de type int ,l’expression :
sizeof(x)
vaudra 2
Il peut également s’appliquer à un type de nom donné. Par exemple :
sizeof (int) vaut 2
.sizeof(long int) vaut 4
sizeof(double) vaut 8
Cet opérateur offre un intérêt:
lorsqu’on souhaite écrire des programmes portables dans lesquels il est nécessaire de connaître
la taille exacte de certains objets.

Pour éviter de calculer soi-même la taille d’objets d’un type relativement complexe pour lequel
on ne sera pas certain de la manière dont il sera “implémenté” par le compilateur. Ce sera
notamment des structures.
Le tableau suivant fournit la liste complète de tous les opérateurs du C, classés par ordre de
priorité décroissante, accompagnés de leurs mode d’associativité.
Notez bien qu’en langage C, un certain nombre de notations servant à “référencer” des objets
sont considérés comme des opérateurs et, entant que tels, soumises à des régies de priorités Ce
sont essentiellement
les références à des élément d’un tableau réalisées par [ ],
des références à des champs de structures : opérateurs - > et . ,
des opérateurs d’adressage : * et &.
Ces opérateurs seront étudier ultérieurement dans les chapitres correspondant, néanmoins, ils
figurent dans le tableau proposé
CATEGORIE OPERATEURS ASSOCIATIVITE
Référence () [] -> . —>
Unaire + - ++ -- ! ~ * & (cast) sizeof <—
Arithmétique */ % —>
Arithmétique +- —>
Décalage << >> —>
Relationnel < <=> > = —>
Relationnel == != —>
Manipulation de bits & —>
Manipulation de bits ^ —>
Manipulation de bits | —>
Logique && —>
Logique || —>
Conditionnel :? —>
Affectation = += -= *= /= %= &= ^= |= <<= <—
>>=
Séquentiel , —>
Objectifs
• Connaître les différentes instructions de contrôle du langage C.
• Connaître la syntaxe de chacune de ces instructions.
Eléments de Contenu
• L’instruction if
• L’instruction switch
• L’ Instruction do … while
• L’instruction while
• L’instruction for
• Les instructions de branchement inconditionnel : break, continue et goto.
A priori, dans un programme, les instructions sont exécutées séquentiellement, c’est à dire dans
L’ordre où elles apparaissent. Or, la puissance et le “comportement intelligent” d’un
programme
Proviennent essentiellement :
• de la possibilité d’effectuer des “choix’, de se comporter différemment suivant les
“circonstances” (celles-ci pouvant être, par exemple, une réponse de l’utilisateur, un résultat de
calcul, …).
X
by Counterflix
• de la possibilité d’effectuer des “boucles”, c’est à dire de répéter plusieurs fois un ensemble
donné d’instructions.
Tous les langages disposent d’instructions, nommées “ instructions de contrôle’’, permettant de
réaliser Ces choix ou ces boucles. Suivant le cas, celles-ci peuvent être :
• basées essentiellement sur la notion de branchement (conditionnel ou inconditionnel); c’est,
par exemple, le cas du langage Basic,
• ou, au contraire, traduire fidèlement les structures fondamentale de la programmation
structurée; c’est, par exemple, le cas du langage Pascal bien qu’eu toute rigueur, ce dernier
dispose d’une instruction de branchement inconditionnel GOTO.
Le langage C est assez proche du pascal sur ce point puisqu’il dispose “d’instructions
structurées” permettent de réaliser :
• des choix : instructions if... elseet switch
• des boucles : instructions : do…while, while et for
Toutefois, la notion du branchement n’est pas totalement absente du langage C puisque, il
dispose (l’instructions de branchement inconditionnel :goto, break et continue,
Ce sont ces différentes instructions de contrôle du langage C que nous nous proposons d’étudier
dans ce chapitre.
I-L’INSTRUCTION IF
I.1 Exemples d’introduction de l’instruction if
Soit l’exemple suivant :
if(a<=b)
printf(”croissant”);
e/se
printf(”décroissant’);
Ces quelques lignes constituent une instruction if Elles s’interprètent ainsi :si la condition a <=
b est vraie (c’est à dire non nulle), on exécute l’instruction :
printf(”croissant”);
dans le cas contraire, on exécute l’instruction suivant le mot eIse :
printf(’décroissant ”);
Dans ce premier exemple, chacune des deux parties du choix se limite à une seule instruction
simple. Il est possible d’en placer plusieurs en les regroupant en ce que l’on nomme un “bloc”.
En voici un exemple :
if(a<=b)
{
Printf(”croissant ’);
max.=b ;
}
e/se
{
printf(”décroissant”);
max =a;
}
Le bloc joue un rôle important un en C et il peu être employé ailleurs que dans une instruction
if .Etudions cette notion on détail.
1.2 Les blocs d’instructions
Il s’agit d’une suite d’instructions placées entre { et } . Les instructions figurant dans un bloc
sont absolument quelconques. IL peut s’agir aussi bien d’instructions simples (terminés par un
point—virgule) que d’instructions structurées (choix, boucles) lesquelles peuvent à leur tour
renfermer d’autre blocs...
Vous voyez qu’il y a on C une sorte de récursivité de la notion d’instruction. D’une manière
générale, dans la description de la syntaxe des différentes instructions, nous seront souvent
amenés à mentionner ce terme d’instruction, celui désignera toujours n’importe quelle
instruction C simple, structuré ou un bloc.
Un bloc peut se réduire à une seule instruction, voire même être vide. Voici deux exemples de
bloc corrects :
{}
{ i=1 ; }
Le second bloc ne présente aucun intérêt en pratique puisqu’il pourra toujours être remplacé par
‘instruction simple qu’il contient.
1.3 Syntaxe de l‘instruction if
Le mot else et l’instruction qu’il introduit sont facultatifs, de sorte que cette instruction if
présente deux formes.
if (expression) if (expression)
instruction 1 instruction 1
else
instruction 2
avec,:
• expression :expression quelconque.
• instruction I et instruction2 instructions quelconques, c’est à dire
- simple,
- bloc,
- instruction structurée.
Remarque :
La syntaxe de cette instruction n’impose en soi aucun point-virgule, s’ ce n’est ceux qui
terminent naturellement les instructions simples qui y figurent.
1.4Quelques exemples
l’expression conditionnant le choix est quelconque. La richesse de la notion d’expression en C
fait que celle-ci peut elle-même réaliser certaines actions. Ainsi :
if(++i <limite) printf(”ok’’);
est équivalent à
i=i+1
if (i< limite) printf(”ok’’);
de même:
if((c= getchar())!=’\n’)…
peut remplacer
c= getchar() ;
if(c !=’\n’)…
Par contre, if faut noter que:
if((+ +i< limite)&& ((c =getchar())!=’\n’))…
n’est pas équivalent à :
++i ;
c = getchar()
if((I<limite) && (c!= ‘\n’))
car, comme nous l’avons déjà dit, l’opérateur && n’évalue le second opérande que lorsque cela
est nécessaire. Autrement dit, dans la première formulation, l’expression :
c= getchar()
n’est pas évaluée lorsque la condition + +i<limite est fausse ; elle l’est par contre dans a
deuxième formulation.
1.5 Le cas des instructions if imbriquées
Nous avons déjà mentionné que les instructions figurant dans chaque partie du choix d’une
instruction if pouvaient être absolument quelconques. En particulier, elles peuvent à leur tour
renfermer des instructions if .Or, compte tenu de ce que cette instruction peut comporter ou ne
pas comporter de ‘’else” il existe certaines situations où une ambiguïté apparaît. C’est le cas
dans cet exemple :
if(‘a<=b) if(b<=c) printf (‘‘ordonné”);
else printf (‘‘non ordonné ‘‘) ;
Est-il interprété comme le suggère cette présentation
if(a<=b) if(b<=c) printf (‘‘ordonné”);
else printf(“non ordonné”);
Ou bien comme le suggère celle-ci :
if(a<=b) if(b<=c) printf (‘‘ordonné”);
else printf(“non ordonné”);
La première interprétation conduirait â afficher « non ordonné lorsque la condition a<=b est
fausse, tandis que la seconde n’afficherait rien dans ce cas la. La règle adoptée par le langage C
pour lever une telle ambiguïté est la suivante :
Un else se rapporte toujours au dernier if rencontré auquel un else n’est pas encore
attribué.
Ainsi, dans notre cas c’est la deuxième représentation qui suggère le mieux ce qui se passe.
II - L’INSTRUCTION SWITCH
11.1 Exemples d’introduction à l’instruction switch
a) premier exemple
Voyons ce premier exemple de programme accompagné de trois exemples d’exécution.
main ()
{
int n ; Donner un entier : 0
printf ( “donnez un entier entier : “); nul
scanf(”%d”, &n); au revoir
switch( n)
{ Donner un entier :1
case O : printf (“nul\n“); un
break; au revoir
case 1: printf (“un\n“);
break;
case 2 : printf (“deux\n”); Donner un entier : 2
break; deux
} au revoir
printf(“au revoir\n”);
}
L’instruction switch s’étend ici sur 9 lignes (elle commence au mot switch). Son exécution se
déroule comme suit : On commence tout d’abord par évaluer expression figurant après le mot
switch (ici n). En suite en recherche dans le bloc qui suit s’il existe une étiquette de ta
forme “case x” correspondant à la valeur ainsi obtenue. Si c’est e cas, on “se branche” à
l’instruction figurant après cette étiquette. Dans le cas contraire on passe à instruction qui suit le
bloc.
ainsi, par exemple quand n vaut 0, on trouve effectivement une étiquette “case 0” et l’on
exécute Instruction correspondante, c’est à dire :
pintf (“nul\n“) ;\
On passe ensuite, naturellement, à l’instruction suivante, à savoir ici :
break;
Celle-ci demande en fait de sortir du bloc. Le rôle de cette instruction est fondamental. Voyez, à
titre d’exemple, ce que produirait ce même programme en l’absence de break.
main()
{
int n ; Donner un entier : 0
printf(“donnez un entier : “) ; nul
scanf(“%d,&n) ; un
switch(n) deux
{ au revoir
case 0 :printf(“nul\n“); Donner un entier :2
case1:printf(“un\n“) ; deux
case2:printf(“deux\n“) ; au revoir
}
printf(“au revoir\n“) ;
}
b) l’étiquette“ default“
Il est possible d’utiliser le mot-clé “default” comme étiquette à laquelle je programme
se “branchera“ dans le cas où aucune valeur satisfaisante n’aura été rencontrée. En voici un
Exemple :
main()
{
int n ; donnez un entier :
printf(“donnez un entier : “) ; deux
scanf(“%d“ ;&n) ; au revoir
switch (n)
{
case 0 :printf(“nul\n“); donner un entier:25
break; grand
case 1 :printf(“un\n“); au revoir
break ;
case 2 :printf(“deux\n“);
break ;
default :printf(“grand \n“);
}
printf(“au revoir \n“);
}
II.2 Syntaxe de l’ instruction switch
Switch(expression)
{
case constante 1 :[ suite_d’instruction_1]
case constante 2 : [ suite d’instruction_ 2]
case constante_ n :[ suite d’instruction_n]
default :[ suite_d’instruction]
}
Avec:
Constante : expression constante entière (mais char est accepté car il sera converti en int). Suite
_d’instruction : séquence d’instructions quelconques.
N.B. : les crochets ([et]) signifient que ce qu’ils renferment est facultatif.
IIl- L’INSTRUCTION DO …WHILE
III.1 Exemple d’introduction de l’instruction do ... while
main()
{
int n ;
do donner un nombre positif: -5
{ vous avez fourni -5
printf (“donner un nombre positif : ‘‘) ; donner un nombre positif: - I
scanf (‘‘%d’’, &n ) ; vous avez fourni –1
donner un nombre positif: 3
vous avez fourni 3
}
réponse correcte
while (n<=0) ;
printf (“réponse correcte‘‘) ;
}
l’instruction
do{…} while (n<=0)
répète l’instruction qu’elle ”contient” (ici un bloc) tant que la condition mentionnée (n<=0) est
vraie (c’est à dire en C non nulle). Autrement dit, dans l’exemple ci-dessus, elle demande un
nombre à l ‘utilisateur tant qu’il ne fournit pas une valeur positive.
On ne sait pas à priori combien de fois cette boucle sera répétée. Toutefois elle est toujours
parcourue au moins une fois. En effet la condition qui régit cette boucle n’est examinée qu’à.
La fin de chaque répétition (puisque la partie while figure en fin).
Il faut noter que la sortie de la boucle ne se fait qu’après un parcours complet de ses
instructions et non dès que la condition mentionnée devient fausse. Ainsi, dans l’exemple ci-
dessus, même après que .l‘utilisateur ait fourni une réponse convenable, il y a exécution de
L‘instruction d’affichage:
printf (”vous avez fourni %d \n”,n) ;
111.2 Syntaxe de l’instruction do …while
do instruction
while (expression,);
Commentaires :
• Notez bien la présence de parenthèses autour de l’expression qui régit la poursuite de la
boucle et d’un point-virgule àla fin de cette instruction.
• Lorsque l’ instruction à répéter se limite à une seule instruction simple, n’omettez pas le
point- virgule qui la termine. Ainsi:
do c = getchar() while (c != ‘x’);
est incorrecte. Il faut écrire :
do c= getchar() ;while(c!=‘x’);
• N’oubliez pas que l’expression suivant le mot while peut être aussi élaborée que vous le
souhaitez et qu’elle permet ainsi de réaliser certaines actions.
• L’instruction à répéter peut être vide. toutefois, elle doit être terminée par un point-virgule.
Ainsi ces expressions sont correctes :
do; while(... )
do {} while (... )
• L’instruction :
do{} while (1)
représente une boucle infinie ; elle est syntaxique ment correcte, bien quelle ne présente en
pratique aucun intérêt.
IV- L’INSTRUCTION WHILE
IV. 1 Exemple d’introduction de l’instruction while
main()
{
int n,som ;
som=0
while(som<100)
{
printf (”donne un nombre :“); donner un nombre : 30
scanf (“%d”, &n); donner un nombre : 50
som + = n; donner un nombre :35
} somme obtenue : 115
printf(”somme obtenue : %d”,som,) ;
}
L’instruction
while (som<100)
répète l’instruction. qui suit (ici un bloc) tant que la condition mentionnée est. vraie (non nulle),
comme le ferait do... while Par contre, cette fois la condition de poursuite est examinée avant
chaque parcours de la boucle et non après. Ainsi une telle boucle peut très bien n’être
parcourue aucune fois si la condition est fausse dés le départ.
IV.2 Syntaxe de l’instruction while
While (expression)
Instruction
Commentaires :
• Notez bien la présence de parenthèses autour de l’expression qui régit la poursuite dela
boucle.
• L’expression utilisée comme condition de poursuite est évaluée avant le premier tour. Il est
donc nécessaire que sa valeur soit définie à. ce moment.
• La construction
while(expression1 , expression2) ;
est équivalente à :
do expression 1
while (expression 2)
V- L’INSTRUCTION FOR
V.1 Exemple d’introduction de I’instruction for
main; ()
{
int i; bonjour I fois
for (i=1 ; i <= 5; I++) bonjour 2fois
{ bonjour 3fois
printf (“bonjour”); bonjour 4fois
printf (‘’%dfois\n’’ ,i) ; bonjour 5fois
}
{
La ligne
for (i=I;i <=5; i++)
Comporte trois expressions. La première est évaluée (une seule fois) avant d’entrer dans la
boucle. La seconde conditionne la poursuite de la boucle elle est évaluée avant chaque parcours
.la troisième, enfin, est évaluée à la fin de chaque parcours.
Le programme précédent est en fait équivalent au suivant :
Main()
{
int I;
while(I<=5)
{
printf(“bonjour”);
printf(“%d fois\n”,i) ;
i++;
}
}
V.2 Syntaxe de l’instruction for
For([expression 1];[expression 2];[expression 3])
instruction
Commentaires :
· D’une manière générale, nous pouvons dire que :
for ([expression1] ;[ expression2 ]; [expression3]) instruction
est équivalent à :
expression1 ;
while (expression2)
{
instruction
expression3
}
Chacune des trois expressions est facultative (pas obligatoire). Ainsi, ces instruction sont
équivalentes à l’instruction for de notre premier exemple de programme :
i=1 ;
for (; i <= 5, i++)
{
printf ( “bonjour’’) ;
printf(‘’%d fois\n’’,i);
}
i=I;
for (; i <= 5;)
{
printf (“bonjour’’);
printf(‘’%d fois\n’’,i);
i++;
}
Notez que lorsque expression 2 est absente elle est considérée comme vraie.
· La richesse de a notion d’expression on C permet de regrouper plusieurs actions dans une
expression. Ainsi :
for (i=0,j=1,k=5;...;…)
est équivalent à :
j=1;
k = 5;
for (i =0;…;…)
· Les constructions :
for ( ; ; );
for ( ; ; ) {}
Sont syntaxiquement correctes. Elles représentent des boucles infinies de corps vide.
VI- LES INSTRUCTION DE BRANCHEMENT INCONDITIONNEL: BREAK,
CONTINUE ET GOTO
Ces trois instructions fournissent des possibilités diverses de branchement inconditionnel. Les
deux premières s’emploient principalement au sein de boucles tandis que la dernière est d’un
usage libre mais peu répandu, à partir du moment où on cherche à structurer ses programmes.
VI.1 L’instruction break
Nous avons déjà vu le rôle de break au sein du bloc l’une instruction switch.
Le langage C autorise également l’emploi de cette instruction dans une boucles. Dans ce cas,
elle sert à interrompre le déroulement de la boucle, en passant à l‘instruction qui suit cette
boucle. Bien entendu, cette instruction n’a d’intérêt que si son exécution est conditionnée par
un choix.
Voici un exemple montrant le fonctionnement de break.
main()
{
int i début de tout /
for (i=1 ;i<=10 ;i++) bonjour
{ fin de tour 1
printf (‘’début de tour %d\n “i) ; début de tour 2
printf (“bonjour\n”); bonjour
if(i == 3) break ; fin de tour 2
printf(‘’fin de tour %d\n’’, i); début de tour 3
} bonjour
printf (“après la boucle\n’’); après la boucle
}
Remarque
En cas de boucles “imbriquées ’’, break fait sortir de la boucle la plus interne. De même,
si break apparaît dans un switch imbriqué dans une boucle, elle ne fait sortir que du switch.
VI.2 L’instruction continue
L’instruction continue permet de passer “prématurément” au tour de la boucle suivant En voici
un premier exemple avec for :
Main()
{
int i ; début de tour 1
for (i=1 ;i <= 5; i++) début de tour 2
{ début de tour 3
printf (“début de tour %d\n “, i) ; début de tour 4
if(i<4) continue ; bonjour
début de tour 5
bonjour
}
}
et voici un second exemple avec do …while :
main ()
{
int n ; donnez un nombre > 0 :4
do son carré est: 16
{
printf (”donner un nombre >0”)
scanf (”%d”,n&n) ; donner un nombre supérieur à 0 :-5
if(n<0) svp>0 {
donner un nombre >0 :2
son carre est : 4
donner un nombre>0 :0
printf (” svp >0\n”), son carré est : O
continue
}
printf (“son carré est : %d\n “, n*n) ;
}
while(n) ;
}
Remarques :
Notez bien que lorsqu’elle est utilisée dans une boucle for, cette instruction continue effectue
bien un branchement sur l’évaluation de l’expression de fin de parcours de boucle (nommée
expression2 dans la présentation de Sa syntaxe) et non après.
En cas de boucles imbriquées, l’instruction continue ne concerne que la boucle la plus interne.
VI.3 L’instruction goto
Elle permet le branchement en un point quelconque. Et, voici Un exemple
Main()
{
int i ; début de tour 1
for (i=1 ;<=10 ;i++) bonjour
{ fin de tour 1
printf (“début de tour %d\n “,i) ; début de tour 2
printf(“bonjour\n“) ; bonjour
if(i= =3)goto sortie ; fin de tour 2
printf(“fin de tour %d\n“,i) début“ de tour 3
sortie printf (‘après la boucle\n“) ; après la boucle
}
LES FONCTIONS

Objectifs
• Comprendre la notion de fonction en C
• Connaître les arguments d’une fonction
• Connaître la notion de prototype en C
• Différencier entre les variables globales et les variables locales
• Comprendre la notion de transmission d’arguments entre fonctions
• Connaître les classes d’allocation des variables
•Définir la portée d’une variable
Eléments de contenu
• La notion de fonction en C
• Exemple de fonction en C
• Les arguments d’une fonction
• Les fonctions fournissant un résultat
• Les prototypes et le type void
• Les arguments sont transmis par valeurs
• Les variables globales
• Les variables locales
• La compilation séparée et ses conséquences
• Les classes d’allocation des variables- leur portée et leur initialisation
Comme tous les langages, C permet de découper un programmes en plusieurs parties nommées
souvent “modules”. Cette programmation dite “modulaire’’ se justifie pour plusieurs raisons:
X
by Counterflix
• Un programme écrit en un seul module devient difficile à comprendre dès qu’il dépasse une
ou deux pages de texte. Une écriture modulaire permet de le diviser en plusieurs parties.
Chacune de Ces parties peut d’ailleurs, si nécessaire, être décomposée à son tour en des
modules plus élémentaires; Ce processus de décomposition pouvant être répété autant de fois
que nécessaire comme le préconisent les méthodes de programmation structurée.
• La programmation modulaire permet d’éviter des séquences d’instructions répétitives, et
ceci d’autant plus que la notion “d’argument” permet de “paramétrer” certains modules.
• La programmation structurée permet le partage d’outils communs qu’il suffit d’avoir écrit
et mis au point une seule fois. Cet aspect sera plus visible grâce â la possibilité de compilation
séparée offerte par le langage C.
I-LA NOTION DE FONCTION EN C
Dans beaucoup de langages, on trouve deux sortes de modules :
• Les “fonctions”, assez proches de la notion mathématique correspondante. Notamment, une
fonction dispose d’arguments qui correspondent à des informations qui lui sont transmises et
elle fournit un unique résultat scalaire simple désigné parle nom même de la fonction, ce
dernier peut apparaître dans une expression.
• Les “procédures” (terme Pascal) ou “sous-programme” qui élargissent la notion de
fonction. La procédure ne possède pas de valeur et son appel ne peut pas apparaître au sein
d’une expression quelconque. Par contre, elle dispose d’argument. Parmi ces derniers certains
peuvent, comme pour la fonction, correspondre à des informations qui lui sont transmises. Mais
d’autres, contrairement à ce qui se passe pour les fonction correspondent à des informations
qu’elle produit en retour de son appel.
En C, il n’existe qu’une seule sorte de module: la fonction. On l’utilise à la manière de la
fonction mathématique, en faisant suivre son nom d’une liste d’arguments; le tout possède une
valeur et peut donc apparaître dans une expression quelconque. Mais cette valeur peut ne pas
être utilisée ; c’est ce qui se passe fréquemment lorsque vous utilisez printf ou scanf.
D’autre part, en C, une fonction peut modifier certains de ses arguments et donc fournir des
informations en retour. Ainsi, malgré son nom, en C, la fonction peut jouer un rôle aussi
général que la procédure ou le sous-programme d’autres langages.
Par ailleurs, nous verrons qu’en C plusieurs fonctions peuvent partager des informations
autrement que par passage d’arguments, Nous retrouverons la notion classique de “variable
globale”.
Il- EXEMPLE DE FONCTION EN C
Voici un exemple très simple de définition et d’utilisation de fonction en C
nain()
{
ptimiste () il fait beau
}
optimiste()
{
printif (“Il fait beau“);
}
Nous y trouvons tout d’abord un programme principal formé d’un bloc (limité ici à une seule
instruction). Mais cette fois à sa suite apparaît la définition d’une fonction. Celle-ci comporte :
• un “en-tête“ qui précise le nom de la fonction (ici optimiste). Bien que cette fonction ne
comporte aucun argument, les parenthèses qui suivent son nom sont obligatoires (comme elles
l’étaient pour le nom main). Notez bien qu’un point-virgule n’apparaît pas à la fin de l’en-tête.
• un “corps” matérialisé par un bloc dans Lequel se trouvent les instruction que l’on souhaite
voir exécutées par la fonction lorsqu’elle sera “appelée”
Ici, nous avons placé la fonction à la suite du programme principal, mais nous aurions pu la
placer avant.
D’autre part, il faut noter que les fonctions sont toujours indépendantes les unes des autres.
Aucune fonction ne peut être contenue dans une autre.
Au sein du programme principal, nous trouvons l’appel de la fonction. Il se fait par son nom
suivi d’une paire de parenthèses obligatoires (même lorsque la fonction ne comporte aucun
argument).
Vous voyez que le programme principal apparaît finalement comme une fonction de nom
(main) imposé. La seule différence est que, par convention, l’exécution d’un programme
commence toujours par cette fonction nommée main. Cette dernière n’est donc exécutée qu’une
seule fois, alors que les autres fonctions peuvent l’être un nombre quelconque de fois.
III- LES ARGUMENTS D’UNE FONCTION
Soit l’exemple suivant :
main ()
{
int a =10, b=20;
ecris(a) ; valeur: 10
ecris(b) ; valeur: 20
ecris(a+b) ; valeur: .30
}
ecris( int n)
{
printf (“valeur :%d\n“ ,n) ;
}
Dans la définition de la fonction ecris, nous trouvons l’en-tête :
ecris(int .n)
Cette fois, en pLus du nom de la fonction, nous y trouvons une indication signifiant que cette
fonction possède un argument de type int nommé n. Cela signifie que lorsque cette fonction
sera appelée, on lui transmettra une valeur de type int. Quant à l’usage que ecris doit faire de
cette valeur, celui-ci lui est spécifié au sein des instructions du corps de la fonction, sachant que
c est l’identificateur n qui désigne la valeur en question.
Le symbole n n’a de signification qu’au sein de la fonction ecris et il n’a aucun rapport avec
d’éventuelles variables de même nom qui pourraient être définis en dehors. On dit souvent que
n est un argument muet ou encore un argument formel.
Eu ce qui concerne l’utilisation de notre fonction ecris dans le programme principal, vous
constatez que nous faisons suivre son nom d’une expression de type int, placée entre
parenthèses. Cette expression porte je nom d’argument effectif .C’est sa valeur qui sera
effectivement transmise à la fonction lors de l’appel.
En C, un argument effectif porte la forme de n’importe quelle expression. Au contraire un
argument muet ne peut être qu’un identificateur de variable
D’une façon générale, une fonction peut avoir plusieurs arguments séparés par des virgules.
Voici un exemple incomplet de définition et d’utilisation d’une fonction nommée fct possédant
trois arguments.
main
{
int n, p ;
double x,y;

fct (n, p,x) ;

fct (n+p, 5 *n,x-y);

}
fct (int i, int j, double v)
{

}
Dans ce cas, la correspondance entre argument effectifs et argument formels se fait en fonction
de leur ordre d’apparition dans la liste.
Remarque:
Dans le C tel qu’il est défini par K&R, la déclaration de notre fonction ecris se présente de la
façon suivante :
ecris (n)
int n ;
{

}
L’entête est découpée en deux parties :
• une première ligne précisant le nom de la fonction et la liste des arguments formels.
• une suite de déclarations précisant les types des arguments.
IV- LES FONCTIONS FOURNISSANT UN RESULTAT
IV .1 Exemple de fonction calculant la somme de deux valeurs
soit cet exemple :
main()
{
int a, b, c, d, x, y;
int som() ;
a=1; b=2; c=S; d=4;
x = som(a,b)+ ; x=8
printf( “r = %d\n”,x); y = 21
y = 3*som(c,d) ;
printf( “y =%d\n“,y) ;
}
int som (int u,int vi)
{
int s;
s = u+v
return(s)
}
L’entête de la fonction précise toujours la liste des arguments formels avec leurs types ; mais de
plus, ici, on y trouve (au début) l’indication du type du résultat que fournira la fonction.
Par ailleurs, dans le corps de la fonction on trouve une instruction :
return (s);
qui spécifie le résultat qui sera fournit par la fonction lors de son appel.
L’utilisation de la fonction som au sein du programme se fait toujours en faisant suivre son nom
d’une liste d’arguments effectifs (ici deux arguments), comme par exemple :
som (a,b)
Comme la fonction fournit un résultat, une telle notation désigne effectivement une expression
qu’il est possible de l’utiliser à son tour dans une expression plus complète, comme nous
l’avons fait dans l’expression :
x= som (a, b) + 5;
De plus vous constatez la présence dans le programme principal d’une déclaration
supplémentaire :
int som();
Cette déclaration précise au compilateur que som est une fonction et qu’elle fournit un résultat
de type int. Nous reviendrons en détail sur la justification de cette déclaration dans la suite de
cette leçon.
IV.2 L’instruction return
D’une manière générale :
• L’instruction return peut mentionner n’importe quelle expression. Ainsi nous aurions pu
écrire la fonction précédente d’une manière plus simple :
int som (int x, int y)
{
return (u+v);
}
• L’instruction returnpeut apparaître à plusieurs reprises dans une fonction, comme dans cet
exemple :
double absom(double u, double v) // va leur absolue de la somme
{
double s;
s = a +b;
if (s> 0)
return (s),
else
return (-s);
}
il faut noter que non seulement l’instruction return définit la valeur du résultat, mais en même
temps elle interrompt l’exécution de la fonction en revenant à la fonction (y compris la fonction
principale main)qui l’a appelée. En l’absence de l’instruction return ce retour est mis en place
automatiquement par le compilateur à la fin de la fonction.

IV.3 Quand on ne déclare pas le type du résultat


Dans notre exemple du paragraphe “fonctions fournissant un résultat”, nous avons placé, dans
le programme principal, une déclaration précisant le type de la fonction som.On se demande
pourquoi, puisque la définition de cette même fonction sont figure dans le même ficher source.
En fait, il faut savoir que le compilateur travaille d’une manière relativement séquentielle. Plus
précisément, vous ne pouvez pas faire référence à “quelque chose qui n’est définie que plus tard
dans le source. C’est pourquoi la déclaration en question est indispensable ici.
Par contre, si, comme l’autorise C, vous aviez placé la définition de la fonction avant
programme principal, vous auriez pu omettre cette déclaration; en effet le compilateur aura
disposé de l’information de type de la fonction som.
Dune manière générale lorsque le compilateur rencontre l’appel d’une fonction, deux cas
peuvent se présenter :
• Il connaît le type de la fonction, soit parce qu’il la déjà compilé au sein du même source,
soit qu’il existe une déclaration appropriée. Aucun problème particulier ne se pose alors.
• Il ne connaît pas le type de la fonction. Dans ce cas il ne fournit pas de message d’erreur ;
se contente de lui attribuer, par défaut, le type int.
Cette dernière particularité peut être gênante lorsque la fonction se trouve fournie
ultérieurement avec un type différent de int. En effet le compilateur découvrira une incohérence
entre le type qu’il avait attribué par défaut et le type effectif déclaré dans l’en-tête de la
fonction.
V- LES PROTOTYPES ET LE TYPE VOID
V.1 Notion de prototype
Nous venons -de voir comment déclarer le type du résultat d’une fonction dans les modules on
est amené à les utiliser. Le langage C autorise la déclaration d’un en-tête de fonction complet
nommé “prototype’’ et qui fournit les types des paramètres.
Ainsi, dans notre précédent programme du paragraphe “Fonctions fournissant un résultat”,
déclaration :
Int som() ;
du programme principal pourrait être remplacée par :
int som(int ,int) ;
ou encore par:
int som(int x, int y);
Dans le deuxième cas les paramètres x et y sont des paramètres fictifs.
V.2 Le râle du prototype
Si le prototype et l’en-tête sont compilés en même temps (c’est à dire qu’ils figurent dans le
même source), Le compilateur déclarera les incompatibilités entre les deux. Par contre; dans le
cas où ils sont compilés séparément, aucune vérification n’est possible.
Dans tous les cas, le prototype sera utilisé pour forcer certaines conversions. Ainsi, si dans ni
module, on a placé les déclarations :
double som, (double x1, double x2)
int n, p ;
et qu’on trouve un appel de la fonction som
som (n,p);
Le compilateur mettra en place une conversion des valeurs de n et p en valeurs de
type double avant de les transmettre à la fonction som.
En l’absence de prototype, les valeurs des arguments effectifs sont soumises au règles de
conversions en vigueur pour les expressions.
V.3 Les fichiers de type H
Les fichiers d’extension “.H” sont des fichiers qui correspondent chacun à une classe de
fonctions. On y trouve entre autres chose des prototypes de ces fonctions. Ceci est très utile :
• d’une part pour effectuer des contrôles sur le nombre et le type des arguments mentionnés
dans les appels de ces fonctions,
• d’autre part pour forcer d’éventuelles conversions auxquelles on risque de ne pas penser.
Pour utiliser ces fonctions dans vos programmes, il faut placer au début de vos sources une
directives de genre :
#include <nomfichier.h>
Ainsi, pour utiliser la fonction mathématique sqrt() (qui calcule la racine carré d’un nombre),
on doit mettre, au début du programme source la directive :
#mnclude <math.h>
Le fichier math.h contient les prototypes des fonctions mathématiques.
V.4 Le type void
Il sert à spécifier qu’une fonction ne retourne pas de résultat. Il peut s’employer aussi bien dans
l’en-tête que dans le prototype. Ainsi on peut écrire :
void optimiste()
ou aussi
void ecrit (int n)
VI- LES ARGUMENT SONT TRANSMIS PAR VALEURS
Soit l’exemple suivant :
Main()
{
int n =10, p =20;
avant appel :10 20

printf (“avant appel :%d %d\n“,n,p) ; début échange :10 20

echange (n,p) ; fin échange :20 10


printf (après appel : %d %d\n“,n,p) ; après appel :10 20
}
echange (int a, int b)
{
int c ;
printif (“début échange : %d %d\n“,a,b);
c=a ;
a=b ;
b=c ;
printf (“fin échange : %d %d\n“,a,b);
}
La fonction echange reçoit deux valeurs correspondant à ses deux arguments muets a et b. ELle
effectue un échange de ces deux valeurs. Mais lorsque l’on est revenue dans le programme
principal, aucune trace de cet échange ne subsiste sur les arguments effectifs n et p.
En effet, lors de l’appel de echange il y a eu transmission de la valeur des expressions n et p.
On peut dire que ces valeurs on été recopiées “localement” dans la fonction echange dans des
emplacements nommes a et b. C’est effectivement sur ses copies qu’a travaillé la
fonction echange, de sorte que les valeurs des variables n et p n’ont pas été modifiés. C’est ce
qui explique le résultat constaté.
Ce mode de transmission semble donc interdire à priori une fonction de produire une ou
plusieurs valeurs ‘en retour’. Or, en C tous les “modules” doivent être écrits sous forme de
fonctions. Ce problème d’échange des valeurs de deux variables doit pouvoir être résolue à
l’aide d’une fonction.
Nous verrons que ce problème possède plusieurs solutions à savoir:
• utiliser des variables “globales” comme nous le verrons
• transmettre en argument la valeur de la dresse d’une variable. La fonction agira donc sur le
contenu de cette adresse. C’est ce que nous verrons dans la leçon consacrée aux “pointeurs”.
VII- LES VARIABLES GLOBALES
En C, plusieurs fonctions (dont la fonction principale main) peuvent partager des variables
communes qu’on qualifie alors de globales.
VII. 1 Exemple d’utilisation de variables globales
voyons cet exemple de programme:
int i;
main()
{ Passage n0 I
for( i=1; I<=5; i++) Passage n02
passage(); Passage n° 3
} Passage n0 4
Passage n05
passage()
{
printf (“Passage n° %\n“,i);
}
La variable i a été déclarée en dehors de la fonction main. Elle est alors commune de toutes les
fonctions qui seront compilées par la suite au sein du même source. Ainsi, ici, le programme
principale affecte à i des valeurs qui se trouvent utilisées par la fonction passage().
VII.2 Les propriétés des variables globales
a) portée
Les variables globales ainsi définies ne sont connues du compilateur que dans la partie du
source suivant leur déclaration. On dit que leur “portée” (ou encore espace de validité) est
limité à la partie du source qui suit leur déclaration.
Ainsi dans l’exemple suivant :
nain()
{

}
int n ;
float x;

fct1(….)
{

}
fct2(….)
{
….
}
Les variables n et x sont accessibles aux fonctions fct1 et fct2, mais pas au programme
principal.
b) Classe d’allocation
D’une manière générale, les variables globales existent pendant toute l’exécution du
programme dans lequel elles apparaissent. Leurs emplacement mémoires sont complètement
définis lors de l’édition des liens. On dit quelles font partie de la “classe d’allocation statique“
De plus, Ces variables sont initialisées à zéro avant le début de l’exécution du programme, saut
bien sûr, si vous les initialisez explicitement lors de leur déclaration.
VIII- LES VARIABLES LOCALES
Les variables définies au sein d’une fonction (qui peut être la fonction main) sont dites locales
à la fonction dans laquelle elles sont déclarées.
VIII. 1 Portée des variables locales
Les variables locales ne sont connues qu’à l’intérieur des fonctions où elles sont déclarées. Leur
portées est donc limitée à cette fonction.
Les variables locales n’ont aucun lien avec des variables globales de même nom ou avec
d’autres variables locales à d’autres fonctions. Voyez cet exemple :
int n ;
main()
{
int p ;

}
int n ; p
float x ;
fct(...)
{
int n ;
int p;

}
La variable p de main n’a aucun rapport avec la variable p de fct. De même, la variable n de fct
n’a aucun rapport avec la variable globale n. Notez qu’il est impossible d’utiliser, dans la
fonction fct, cette variable globale n.
VIII.2 Les variables locales automatiques
Par défaut, les variables locales ont une “durée de vie” limitée à celle d’une exécution de la
fonction dans laquelle elles figurent.
Plus précisément, leurs emplacements ne sont pas définis de manière permanente comme ceux
des variables globales un nouvel espace mémoire leur est alloué à chaque entrée dans la
fonction et libéré à chaque sortie. Il sera donc généralement différent d’un appel à l’autre. On
dit que la classe d’allocation de ces variables est automatique. Il faut souligner que : les valeurs
des variables locales ne sont pas conservées d’un appel au suivant.
D’autre part, les valeurs transmises à une fonction sont traitées de la même manière que les
variables locales. Leur durée de vie correspond également à celle de la fonction.
VIII.3 Les variables locales statiques
Il est toutefois possible d’attribuer un emplacement permanent à une variable locale et qu’ainsi
sa valeur se conserve d’un appel au suivant. Il suffit pour cela de la déclarer à l’aide du mot-
clé :
Static
En voici un exemple :
main()
{
void fct(); appel n°1
int n ; appel n0 2
for( n=1 ; n<=5 ;n++) appel n0 3
appel n0 4
appel n0 5
fct() ;

void fct()
{
static int i;
i++;
printf(“appel n° %d\n“ ,i) ;
}
La variable locale i a été déclarée de classe “statique”, On constate bien que sa valeur progresse
de un à chaque appel. De plus on note qu’au premier appel sa valeur est nulle. En effet, comme
pour les variables globales (lesquelles sont aussi de classe statique) :
les variables locales de classe statique sont, par défaut initialisée à zéro.
Remarque:
Le mot staticemployé sans indication de type est équivalent à static int.
VIll.4Le cas des fonctions récursives
Le langage C autorise la récursivité des appel des fonctions. Celle-ci peut prendre deux aspects:
• récursivité directe :une fonction comporte, dans sa définition, au mois un appel à elle
même.
• récursivité croisée : l’appel d’une fonction entraîne celui d’une autre fonction qui, à son
tour appelle la fonction initiale ( on peut faire intervenir plus de deux fonctions).
Voici un exemple classique d’une fonction calculant le produit factoriel d’un nombre d’une
manière récursive :
long fact (int n)
{
if(n > 1) return(fact( n - 1) * n);
e/se return(1) ;
}
Il faut bien voir qu’alors chaque appel de fact entraîne une allocation d’espace pour les
éventuelles variables locales et pour son argument. Or, chaque nouvel appel de fact “à
l’intérieur” de fact provoque une telle allocation sans que les emplacements précédents ne
soient libérés. Il y a donc empilement des appels de la fonction. Ce n’est que lors de l’exécution
de la première instruction return que l’on commence à ‘‘dépiler” les appels et les
empLacements et donc à libérer de l’espace mémoire.
IX- LA COMPILATION SEPAREE ET SES CONSEQUENcES
C permet de compiler séparément plusieurs fichiers sources et de rassembler les
modules objets correspondants au moment de l’édition des liens.
IX.1 La portée d’une variable globale - la déclaration extern
A priori, la portée d’une variable globale semble limitée au fichier source dans lequel elle a été
définie. Ainsi, supposez que l’on compile séparément ces deux fichiers sources
source I source 2
int x; fct2()
main() {
{ ….
}
….

} fct3()
fct() {
….
}
{
….
}
A priori, il ne semble pas possible, dans les fonctions fct2 et fct3 de faire référence à la variable
globale x déclarée dans le premier fichier source (alors qu’aucun problème ne se poserait si l’on
réunissait ces deux fichiers sources en un seul, du moins si l’on prend soin de placer les
instructions du second fichier à la suite de celles du premier).
En effet, le langage C prévoit une déclaration permettant de spécifier qu’une variable globale a
déjà été déclarée dans un autre fichier source. Celle-ci se fait à l’aide du mot-clé extern.. Ainsi
en faisant précéder notre second fichier source de la déclaration
extern int x;
il devient possible de mentionner la variable globale x (déclarée dans le premier fichier source)
dans les fonctions fct2 etfct3.
IX.2 Les variables globales et l’édition des liens
Supposons que nous avons compilé les deux fichiers sources précédents, après avoir introduit
dans le second la déclaration extern dont nous avons parlé ; voyons alors comment l’éditeur de
lien est en mesure de rassembler correctement les deux modules objets ainsi obtenus. En
particulier, examinons comment il fait correspondre au symbole x du second fichier source
l’adresse effective de la variable x définie dans le premier.
D’une part, après compilation du premier fichier source, on trouve dans le
module objet correspondant, une indication associant le symbole x et son adresse dans le
module objet. Autrement dit, contrairement à ce qui se passe pour les variables locales pour
lesquelles ne subsiste aucune trace de nom après compilation, le nom des variables globales
continue à exister dans les modules objets. On retrouve là un mécanisme analogue à ce qui se
passe pour les noms de fonctions, lesquels doivent bien subsister pour que l’éditeur de lien soit
en mesure de retrouver les modules objets correspondants.
D’autre part, après compilation du second fichier source, on trouve dans le
module objet correspondant une indication mentionnant qu’une certaine variable de nom x
provient de l’extérieur et qu’il faudra en fournir l’adresse effective.
Ce sera effectivement le rôle de l’éditeur de liens que de retrouver dans le premier
module objet l’adresse effective de la variable x et de la reporter dans le second module objet.
X- LES CLASSES D’ALLOCATION DES VARIABLES - LEUR PORTEE ET LEUR
INITIALISATION
X.1 La portée des variables
On peut classer les variables en quatre catégories en fonction de leur portée (ou espace de
validité).
a) Les variables globales
Elles sont accessibles depuis n’importe quel endroit du fichier source faisant suite à
l’emplacement où elles ont été définies. Elles sont également accessible depuis un autre fichier
source ou depuis une autre partie du fichier source qui précède l’endroit où elles sont définies,
par l’intermédiaire de la déclaration extern.
b) Les variables globales cachées
Ce sont des variables globales faisant l’objet d’une déclaration static. Elles ne sont accessible
que depuis le fichier source où elles ont été définies.
c) Les variables locales à une fonction
N’oubliez pas que les variables locales au programme principal (main) entrent dans cette
catégorie.
d) Les variables locales à un bloc
Le langage C permet de déclarer des variables au début d’un bloc de la même façon qu’au
début d’une fonction. Dans ce cas la portée de telles variables est limitée au bloc en question.
X.2 Les classes d’allocation des variables
Il est également possible de classer les variables en trois catégories en fonction de leur classe
d’allocation
a) La classe stade
On trouve dans cette catégorie les variables globales et les variables locales faisant l’objet
d’une déclaration static. Les emplacements mémoire correspondants sont alloués une fois pour
toutes au moment de l’édition de lien.
b) La classe automatique
Par défaut les variables locales entrent dans cette catégorie. Les emplacements mémoire
correspondants sont alloués à chaque entrée dans la fonction et ils sont libérés à chaque sortie.
c) La classe register
Toute variable entrant à priori dans la classe automatique peut être déclarée explicitement par le
qualificatif register. Celui-ci demande au compilateur d’utiliser dans la mesure du possible, un
registre pour y ranger la variable ceci peut amener quelques gains de temps d’exécution.
Cette possibilité ne peut s’appliquer qu’aux variables scalaires.
X3.Le cas des fonctions
La fonction est considérée par le langage C comme un “objet global”. c’est ce qui permet
d’ailleurs à l’éditeur de lien d’effectuer correctement son travail. Il n’est pas nécessaire
d’utiliser une déclaration extern pour les fonction définies dans un fichier source différent de
celui où elles sont appelées.
X.4 Initialisation des variables
a) Les variables de classe statique
Ces variables sont permanentes. Elles sont initialisées une seule fois avant le début de
l’exécution du programme.
Elles peuvent être initialisées explicitement lors de leur déclaration. Les valeurs servant à cette
initialisation ne peuvent être que des constantes. En l’absence d’initialisation explicite, Ces
variables seront initialisées à’’zéro ’’.
b) Les variables de classe automatique
Ces variables ne sont pas initialisées par défaut. Par contre, comme les variables de classe
statique, elles peuvent être initialisées explicitement lors de leur déclaration.
LES TABLEAUX ET LES POINTEURS

Objectifs
• comprendre la non on de tableau en langage c
• connaître la syntaxe de déclaration des tableaux à une dimension et à plusieurs dimensions
• Comprendre la notion pointeur en langage C
•Identifier le lien entre les pointeurs et les tableaux
• Appliquer l’utilisation des pointeurs en argument d’une fonction
Eléments de contenu
• Les tableaux à une dimension
• Les tableaux a plusieurs dimension
• Classe d’allocation des tableaux et initialisation
•Notion de pointeurs - Les opérateur * et &
• Utilisation des pointeurs en argument d’une fonction
• Lien entre les pointeurs et les tableaux
•Les tableaux transmis en arguments d’une fonction
Comme tous les langages, C permet d’utiliser des tableaux. On nomme ainsi Un ensemble
d’éléments de même type désignés par un identificateur unique chaque élément est repéré par
un indice précisant Sa position au sein de l’ensemble
X
by Counterflix
Par ailleurs, comme certains langages, C dispose (le ‘pointeurs”, c’est à dire des variables
destinées à contenir des adresses d’autres “objets” (variables, fonctions,...).
A priori, ces deux notions de tableau et de pointeur peuvent paraître éloignées l’une de ‘autre.
Toutefois, il se trouve qu’en C un lien indirect existe entre les deux notions, à savoir qu’un
identificateur de tableau est un constant pointeur.
I-LES TABLEAUX A UNE DIMENSION
Supposons que nous souhaitions déterminer, à partir de 20 notes d’élèves
(fournies on données), combien d’entre elles sont supérieures à la moyenne de la classe.
S’il ne s’agissait que de calculer simplement la moyenne de ces notes, il nous. suffirait d’en
calculer la somme on les cumulant dans une variable, au fur et à mesure de leur lecture. Mais,
ici, il nous faut à nouveau pouvoir consulter Les notes pour déterminer combien d’entre elles
sont supérieures à la moyenne ainsi obtenue, il est donc nécessaire de mémoriser ces 20 notes.
Pour ce faire, il paraît peu raisonnable de prévoir 20 variables scalaires (méthode qui serait.
Difficilement transposable à un nombre important de notes).
Le tableau va nous offrir une solution convenable à ce problème, comme le montre le
programme suivant
Main()
{
int i,som,nbm ;
float moy;
int t[20] ;
for (i=0; i<20;I++)
{
printf( “donner la note numéro %d :’’,i+1’’)
scanf(‘’%d’’,&t[i]) ;
}
som=0;
for(I=0; i< 20 ;i++)som+=t[i] ;
moy=som\20;
printf,(‘’\n\n La moyenne de cette classe est :%f\n’’,som) ;
nbm=0 ;
for(i=0 ;i<20 ;i++)
if(t[i]>moy) nbm++;
printf(‘’%d élève ont plus de cette moyenne \n’’, nbm);
}
La déclaration :
int t [20]
réserve emplacement pour 20 élément de type int , Chaque élément est repéré par Sa position
dans le tableau ,nommée ‘‘indice”. Conventionnellement, en langage C, la première position
porte le numéro 0. Ici, donc, nos indices vont de O à 19, le premier élément du tableau sera
désigné par t[0], le troisième par t[2], le dernier par t[l9].
Plus généralement, la notion t[i] désigne un élément dont la position dans le tableau est fournie
parla valeur de i. Elle joue le même rôle qu’une variable scalaire de type int.
La notation &t[i] désigne l’adresse de cet élément t[i] de même que &n désigne l’adresse de n
D’une façon générale
1) Un élément d’un tableau est une ivalue. Il peut donc apparaître à gauche d’un opérateur
d’affectation comme dans :
t[2] = 5
Il peut aussi apparaître comme opérande d’un opérateur d’incrémentation, comme dans :
t[3]++ - -t[i]
2) L’indice peut prendre la forme de n’importe quelle expression arithmétique de type entier
(ou caractère compte tenu des règle de conversion implicite). Par exemple, si n, p, k, et j sont de
type int Ces notations sont correctes :
t[n-3]
t[3*p-2*k+j%n]
Il en va de même si c1 et c2 de type char, de :
t [c1+3]
t[c2-c1]
3) Aucun contrôle de “débordement d’indice’’ n’est, mis en place par le compilateur. Pour en
comprendre les conséquences, il faut savoir que lorsque le compilateur rencontre
une L’aine telle que t[i], il en détermine l’adresse en ajoutant à l’adresse de début t, un
décaLage proportionnel à. la valeur de i (et aussi proportionnel à la taille de chaque élément du
tableau). De sorte qui est très facile de désigner et de modifier un emplacement situé avant ou
après le tableau.
Il- LES TABLEAUX A PLUSIEURS DIMENSIONS
Comme tous les langages, C autorise les tableaux à plusieurs indices (ou à plusieurs
dimensions).
Par exemple, la déclaration :
int t[5][3] ;
réserve un tableau de 15(5*3) éléments. Un élément quelconque de ce tableau se trouve alors
repéré par deux indices comme dans ces notations :
t[3][2] t[i] [j] t[i-3][i+j]

il faut noter que là encore, la notation désignant un élément d’un tel tableau est

une Ivalue Arrangement en mémoire des tableaux à plusieurs dimensions


les éléments d’un tableau sont rangés suivant l’ordre obtenu en faisant varier le dernier indice le
premier. Ainsi, dans la déclaration suivante :
int[5][3] ;
les éléments du tableau t sont ordonnés comme suit :
t[0][0]
t[0][1]
t[0][2]
t[1][0]
t[1][1]
t[1][2]
t[2] [0]

t[4][0]
t[4][1]
t[4][2]
Nous verrons que Cet ordre a une incidence dans au moins trois circonstances :
• lorsque l’on omet de préciser certaines dimensions d’un tableau,
• lorsque l’on souhaite accéder à laide d’un pointeur aux différents éléments d’un tableau,
• lorsque l’un des indices déborde. Suivant l’indice concerné et les valeurs qu’il prend, il peut
y avoir débordement d’indice sans ‘’sortie’’ du tableau. Par exemple, toujours avec notre
tableau t de 5*3 éléments, vous voyez que la notation t[0][5] désigne en fait l’élément t[1][2].
Par contre la notation t[5][0] désigne un emplacement mémoire situé juste au delà du tableau.
III- CLASSE D’ALLOCATION DES TABLEAUX ET INITIALISATION
111.1 Règles concernant l’initialisation des tableaux
• Rappelons quelques points que nous avons déjà évoqués
Il est possible d’initialiser explicitement dans leur déclaration les tableaux, qu ils soient
statiques ou automatiques. Les tableaux statiques sont initialisées une fois pour toutes avant
l’exécution du programme. Les tableaux automatiques par contre, sont initialisés à chaque
appel de la Fonction où ils sont déclarés.
• En l’absence d’initialisation explicite, les tableaux statiques sont par défaut initialisés à zéro.
Par contre les tableau automatiques ne sont pas initialisés.
III.2 Initialisation des tableaux à une dimension
La déclaration
Int tab[5] ={ 10,20,5,0,3}
place les valeurs 10,20,5,0 et 3 dans chacun des 5 éléments du tableau tab.
Il est possible de ne mentionner dans les accolades que certaines valeurs seulement comme
dans ces exemples :
int tab[5] ={10, 20}
int tab[5] ={,,5,, 3}
Les valeurs manquantes seront, suivant la classe d’allocation du tableau initialisés à zéro
(statique) ou aléatoires (automatique).
De plus, il est possible, d’omettre la dimension du tableau, le compilateur pourra la déterminer
par le nombre des valeurs énumérés dans l’initialisation. Ainsi, on peut écrire :
int tab []={I0,20,5,0,3}
III.3 Initialisation des tableaux à plusieurs dimensions
Voyons ces deux exemples équivalents :
int tab [3][4] = {{ 1,2,3,4 },
{5,6,7,8 },
{9,10,11,12}}
int tab [3][4] = {1,2,3,4,5,6,7, 8,9, 10, 11,12}
La première forme revient à considérer notre tableau comme formé de 3 tableaux de 4
éléments. La seconde exploite la manière dont les éléments sont effectivement rangés en
mémoire.
Certaines valeurs peuvent être omises !
int tab [3][4] = {1,,2,,, 6,7}
IV- NOTION DE POINTEURS - LES OPERATEUR * ET &
IV.1 Introduction
Nous avons déjà été amenés à utiliser l’opérateur & pour désigner l’adresse d’une Ivalue. D’une
manière générale, le langage C permet de manipuler des adresses par intermédiaire de variables
nommées “pointeurs“.
En guise d’introduction à cette section, considérons les instructions suivantes :
int*ad;
int n;
n=20 ;
ad=&n;
*ad =30;
La première instruction réserve une variable nommée ad comme étant un pointeur sur des
entiers. On verra que * est un opérateur qui désigne le contenu de l’adresse qui le suit. Ainsi, on
peut ditre que cette déclaration signifie que *ad c’est à dire l’objet d’adresse ad, est de type int ;
ce qui signifie bien que ad est l’adresse d’un, entier.
L’instruction:
Ad=&n;
affecte à la variable ad la valeur de l’expression &n. L’opérateur & est un opérateur unaire qui
fournit comme résultat adresse de son opérande. Ainsi, cette instruction place dans la variable
ad l‘adresse de la variable n.
l’instruction :
*
ad = 30;
signifie : affecter à la valuer *ad la valeur 30. Or, *ad représente l’entier ayant pour
adresse ad (notez bien qu’on parle d’un entier et pas simplement d’une valeur quelconque
car ad est un pointeur sur des entiers)
Nous aurions obtenus le même résultat avec :
n=30;
IV.2 Quelques exemples :
Voici quelques exemples d’utilisation de ces deux opérateurs. Supposons que nous ayons
effectué ces déclarations :
int ad 1, ad2,ad ;
int n= 10,p =20;
Considérons maintenant les instructions suivantes :
Ad 1 =&n ;
ad2=&p;
*ad 1=*ad2 +2;
Les deux premières placent dans ad 1 et ad2 les adresses de n et de p. La troisième affecte à
*ad1 la valeur de l’expression
*a<2 +2
Autrement dit, elle place dans l’adresse désignée par ad1 la valeur entière
d’adresse ad2, augmentée de 2. Cette instruction est donc équivalctte à
n=p+2
De manière comparable, l’expression :
ad1+=3 ;
joue le même rôle que :
n =n +3 ;
et l‘expression :
(*ad1)++ ;
est équivalente à :
n+ +;
Remarques :
· Si ad est un pointeur, les expressions ad et *ad sont des Ivalue. Par Contre, il n’en est pas
de même pour &ad. En effet, cette expression désigne non plus une variable pointeur
comme ad, mais l’adresse de la variable ad telle quelle est définie par le compilateur. Cette
adresse est nécessairement fixe et on ne peut pas la modifier (la même remarque s’applique à
&n où n est une variable scalaire quelconque). D’une manière, générale, des expressions telles
que:
(&ad) ++ ou(&p)++
seront rejetées à la compilation.
Une déclaration telle que :
int *ad ;
· réserve un emplacement pour un pointeur sur un entier, bile ne réserve pas on plus un
emplacement pour un tel entier. Cette remarque est valable lorsque les objets pointés seront des
chaînes ou des tableaux.
1V.3 Arithmétiques sur les pointeurs
Il est possible de manipuler, non seulement les variables pointées, mais aussi Les variables
pointeurs. Soit La déclaration :
int *ad ;
une expression telle que :
ad+1
a un sens pour C.
En effet, est censé contenir l’adresse d’un entier, et pour C. L’expression ci-dessus représente
l’adresse de l’entier suivant ceci n’a pas dans notre exemple, mais nous verrons que cela peut
être très utile dans le traitement de chaînes ou de tableaux.
Notez bien qu’il ne faut pas confondre un pointeur avec un nombre entier. En effet, l’expression
ci-dessus ne présente pas l’adresse de ad augmentée de un (octet). plus précisément, la
différence entre ad +1et ad est ici de deux octets si.
D’une manière générale, expression :
ad+ +
Incrément l’adresse contenue dans ad de manière à ce qu’elle désigne objet suivant.
D’une manière générale, les opérations arithmétiques autorisées sur les pointeurs sont :
• addition ou soustraction d’un entier à un pointeur. Le résultat est du même type pointeur.
• différence entre deux pointeurs de même type (c’est à dire qui pointent sur des objets de
même type) le résultat est un entier.
V- UTILISATION DES POINTEURS EN ARGUMENT D’UNE FONCTION
Nous avons vu que le mode de transmission par valeur interdisait à une fonction de modifier la
valeur de ses arguments effectifs et nous avons mentionné que les pointeurs fourniraient une
solution à ce problème.
Voici un exemple de programme qui réalise la permutation des valeurs de deux entier
main()
{
int n = 20, p = 30 ;
printf(‘’avant appel %d %d\n”, n ,p) ; avant appel 20 30
échange( &n, &p) ; après appel 30 20
printf (‘‘après appel %d %d\n’’ ,n , p) ;
}
échange( int* ad 1,int*ad2)
{
int x;
x = *ad 1 ;
*
ad1 = *ad2;
*ad2 = x;
}
Les argument effectifs de 1appel de échange sont, cette fois, Les adresses des variables n et p
(et non plus leurs valeurs). On transmet à la fonction échange les valeur de &n et &p.

VI- LIEN ENTRE LES POINTEURS ET LES TABLEAUX

En langage C l’identificateur d’un tableau, lorsqu’il est employé seul (sans indice à sa suite)
désigne on fait adresse de début de ce tableau, c’est à dire l’adresse de son premier élément.

VI.1 Cas des tableaux à une dimension

Soit la déclaration suivante:


int t[20]
La notation t est alors équivalente à &t[0].
De plus cet identificateur est considéré comme étant de type pointeur sur le type correspondant
aux éléments du tableau, c’est à dire, ici, int, Ainsi, voici quelques exemples de notations
équivalentes :
t+1 &t[1]
t+i &t[i]
t[i] *(t+I)
• Pour illustrer Ces nouvelles possibilités de notation, plusieurs façons de placer la valeur I dans
chacun des 20 éléments de notre tableau t.
int i;
for (i = 0; 1<20; i++)
*(t+I)=1;
int I;
int*p;
for (p = t, =i =0;i<20; i++, p++)
*p=1
Notez bien que dans la seconde façon, nous avons dû recopier a valeur représentée par t dans un
pointeur nommé p. Fn effet il ne faut pas oublier que le symbole t est une adresse constante (t
est une constante de type pointeur sur des entiers). Autrement dit, une expression telle que
t++ aurait été invalide, au même titre que, par exemple, 3++.un nom de tableau n’est pas une
Ivalue.
Remarque importante :
nous venons de voir que la notation t[i] est équivalente à *(t+i) lorsque t est déclaré comme un
tableau. En fait cela reste vrai, quelque soit la manière dont t a été déclaré. Ainsi, avec :
int *t ;
les deux notations précédentes resteraient équivalentes, autrement dit, on peut utiliser t[i] dans
un programme où t est simplement déclaré comme un pointeur (sous condition d’avoir alloué
l’espace mémoire nécessaire).
Vl.2 Cas des tableaux à plusieurs dimensions
Comme pour les tableaux à une dimension, l’identificateur d’un tableau à plusieurs dimensions
employé seul, en représente toujours l’adresse de début. Mais de nouvelle possibilités
apparaissent. Ainsi, si L’on a déclaré
int t[3][4] ;
t est effectivement équivalent à &t[0][0] mais, de plus, les notation
t[0] t[1] t[i]
ont un sens. En effet, comme nous l’avons vu, notre tableau t peut être considéré comme une
succession de 3 tableaux de 4 éléments. Pour C, t[0] représente l’adresse de début du premier
de ces “sous” tableaux, t[1] est l’adresse de début du second et ainsi de suite.
Autrement dit, les notations suivantes sont équivalentes :
t[0] &t[0][0]
t [1] &t[1][0]
t[2] &t[2][0]
D’autre part ,ces notations de la forme t[i] sont considérées comme des constantes pointeur sur
des entiers. Ainsi, ces deux expressions sont équivalentes :
t[i]+1 &t[i][0]+1
Remarques :
t[i] est une constante ; ce n’est pas une Ivalue l’expression :
t[i]++
est invalide. Par contre t[1][2] est bien une Ivalue.
VII- LES TABLEAUX TRANSMIS EN ARGUMENTS D’UNE FONCTION
Lorsqu’un identificateur de tableau apparaît en argument effectif de l’appel d’une fonction, il
représente on fait l’adresse. De sorte que c’est effectivement cette adresse qui sera transmise
à la fonction, laquelle pourra effectuer toutes les manipulations voulues à partir de cette
adresse.
VII.1 Tableau à nombre fixe d’éléments
Voici un exemple qui met la valeur 1 dans tous les éléments (l’un tableau de 10 éléments
l’adresse de ce tableau étant transmise on argument :
Void fct(int t[10])
{
int i;
for (i=0;I< 10;I++)
t[I]=1;
}
Voici deux exemples d’appel possibles de cette fonction :
Int t1[10],t2[10]

fct(t1) ;

fct(t2) ;

Il existe en fait d’autres formulations possibles pour cette fonction. Ainsi, par exemple, son en-
tête peut être remplacée par :
Void fct(int t[])
En effet, t désigne un argument. muet la réservation de l’emplacement mémoire du tableau dont
on recevra ici l’adresse est réalisé dans la fonction appelante. D’autre part la connaissance de la
taille exacte du tableau n’est pas indispensable au compilateur ; il est capable de déterminer
l’adresse d’un élément quelconque à partir de son rang et de l’adresse de début.
La fonction peut s’écrire sans faire apparaître de tableau en utilisant simplement des pointeurs
sur des entiers. En voici un exemple:
Void fct(int/*p)
{
int I;
for (I=0;i<10;I++,p++) *p=1;
}

Il faut noter que les deux en-têtes


Void fct (int* p)
et
void fct (int p[])
Sont parfaitement équivalentes.
Dans les deux cas, vous pouvez utiliser le formalisme pointeur ou le formalisme tableau. Les
trois instruction suivantes sont équivalentes :
for (i=0; i<10; i++,p++)*p=1;
for (i=0; i<10; i++) *(p+i)=1;
for (i=0; i<10; i++) p[i]=1;
VII.2 Tableau à nombre variable d’éléments
Soit l’exemple d’une fonction qui calcule la somme des éléments d’un tableau d’entiers de
taille quelconque :
int som, (int[]int nb)
{
int s=0,i;
for (I=0;i<nb;i++)
s+=t[i];
return(s);
}
Voici quelque exemple d’appel de cette fonction :
main()
{
int t1[30],t2[15],t3[10] ;
int s1,s2,s3
….
S1=som(t1,30);
s2= son(t2,15)+som(t3,10) ;
….
}
Remarque en pratique, les dimensions effectives des tableaux seront plutôt exprimées sous
forme de symboles définis par #difine, plutôt que directement sous forme de constante
numérique. Cela facilite l’adaptation éventuelle des programmes. Ainsi le programme ci-dessus
peut s’écrive, plus judicieusement, de la façon suivante :
#difineN1 30
#define N2 15
#define N3 10
main()
}
int t1[30],t2[15],t3[10];
int s1,s2,s3 ;
….
S1=som(t1,N1);
S2=som(t2,N2)+som(t3,N3);
}
VII.3 Cas des tableaux à plusieurs dimensions
Nous avons vu comment réaliser une fonction travaillant sur un tableau à un seul indice, de
taille quelconque. Si l’on souhaite généraliser cela à un tableau plusieurs dimension, il faut être
en mesure de tenir compte de la manière dont un tel tableau est organisé en mémoire. Car il ne
suffit pas de connaître l’adresse de début d’un tableau et qu’il possède deux indices pour
retrouver l’adresse d’un élément quelconque t[i][j]. En effet son emplacement exacte dépend de
la seconde dimension du tableau.
Supposons par exemple que nous souhaitions écrire une fonction qui place la valeur 0 dans
chacun des éléments de la diagonale d’un tableau carré, Voici une manière de procéder :
Diag(int t[10][10])
{
int I,j;
for (i=0;i>10;i++)
for(j=0;j<10;j++)
t[i][j]=0;
}
l’en-tête de cette fonction aurait pu être :
diag(int t[][10])
Diag( int t[][])
aurait été inutilisable puisqu’il serait impossible au compilateur de calculer l‘adresse Cette
fonction ne convient pas pour un tableau de dimensions différente de 10*10. Plus précisément
nous pourrons toujours l’appeler, comme dans cet exemple
Int mat[12][12] ;
….
Diag(mat) ;
L’exécution de ces instructions conduira à placer 10 zéros dans le tableau mat, à des
emplacements pour la plupart incorrects.
Le résultat serait plus catastrophique si le tableau comporte moins de 100 éléments puisque
nous trouverions des zéros en dehors du tableau
Une façon de résoudre ce problème consiste à adresser les éléments voulus par des pointeurs en
effectuant le “calcul d’adresse approprié”. Voici, par exemple une fonction qui place des zéros
dans tous les éléments de la diagonale d’un tableau carré de taille quelconque.
Diag(int*p,int n)
{
int I;
for (i= O;i<n;I++)
{
*p=0;
p+=n+1;
}
}
Ici nous avons tenu compte que deux éléments consécutifs de la diagonale sont séparés par n
éléments. Si un pointeur désigne un élément de la diagonale, pour pointer Sur le suivant, il
suffit d’incrémenter ce pointeur de n+1 unités (l’unité étant ici la taille d’un entier)

LES CHAINES DE CARACTERES

Objectifs
• Connaître la convention de représentation des chaînes de caractères en C
• Comprendre les entrées -sorties de chaînes
• Manipuler les fonctions de traitement et de conversion
Elément de contenu
• La convention de représentation de chaînes de caractères en C
• Les entrées sorties de chaînes
• Les fonctions de concaténation de chaînes
• Les fonctions de comparaison de chaînes
• Les fonctions de copie de chaînes
• Les fonctions de recherche dans une chaîne
• Les fonctions de conversion
Certains langages (tels que le Basic ou le Turbo Pascal) disposent d’un véritable “type
chaînes’’. Les variables d’un tel type sont destinées à recevoir des suites de caractères qui
peuvent évoluer, à la fois en contenue et en longueur, au fil du déroulement du programme.
Elles peuvent être manipulées d’une manière globale.
X
by Counterflix
D’autres langages (tels que Fortran ou le Pascal standard) ne dispose pas d’un tel type chaîne.
Pour traiter de telles informations, il est nécessaire de travailler sur des tableaux de caractères,
dont la taille est nécessairement fixe (ce qui impose une longueur maximum aux chaînes et
entraîne donc une perte de l ‘emplacement mémoire).
En langage C, il l’existe pas de véritable type chaîne, dans la mesure où on ne peut pas déclarer
des variables de ce type. Par contre, il existe une convention de représentation des chaînes.
Celle-ci est utilisée à la fois :
• par te compilateur pour représenter les constantes chaînes (notées entre double quotes),
• par un certain nombre de fonctions qui permettent de réaliser
- les lectures ou écritures de chaînes,
- les traitements classiques tels que la concaténation, recopie, comparaison, extraction
de sous-chaîne, conversions,….
Mais, comme il n’existe pas de type chaîne, il faudra prévoir un emplacement pour accueillir
ces informations. Un tableau de caractères pourra faire l’affaire a c’est ce que nous allons
utiliser dans ce chapitre. Mais nous verrons plus tard comment créer dynamiquement. des
emplacements mémoires, lesquels seront alors repérés par des pointeurs.
1- LA CONVENTION DE REPRESENTATION DES CHAINES EN C
En C, une chaîne de caractères est représentée par une suite d’octets correspondant à chacun de
ses caractères codés en ASCII, le tout étant terminé par un octet supplémentaire de code nul
cela signifie que, d’une manière générale, une chaîne de n caractères occupe en mémoire un
emplacement de n +1 octets.
1.1 Les constantes chaînes de caractères
C’est cette convention qu’utilise le compilateur pour représenter les constantes chaînes que
vous introduisez dans vos programmes, sous des notations de la forme :
“bonjour“
De plus, une tel notation sera traduite par le compilateur on un pointeur sur des éléments de
type char sur la zone mémoire correspondante.
Voici un programme illustrant ces deux particularités :
main()
{
char * adr ;
adr=“bonjour“ ;
do
printf(“%c“,*adr) ;
while(*adr++);
}
La déclaration :
char *adr
réserve un emplacement pour un pointeur sur un caractère (ou une suite de caractères)En ce qui
concerne la constante :
‘‘bonjour’’
le compilateur a créé en mémoire la suite d’octets correspondants mais, dans l’affectation
adr- = “bonjour”
la notation bonjour a pour valeur, non pas la valeur de la chaîne elle même, mais son adresse ;
on retrouve ici le même phénomène que pour les tableaux.
Voici un schéma illustrant la situation après l’exécution de cette affectation
b o n j o u r \o
adr
1.2 Initialisation de tableaux de caractères
Comme on l’a dit, nous sommes souvent amenés, en C, a placer des chaînes dans des tableaux
de caractères.
Mais, si on déclare, par exemple :
char ch[20] ;
On ne pourra pas transférer une chaîne constante dans ch en écrivant une affectation du genre :
ch =” bonjour”;
En effet, ch est une constante pointeur et non une ivalue ; il n’est donc pas possible de lui
attribuer une valeur.
Par contre, C nous autorise a initialiser notre tableau de caractères à l’aide dune chaîne
constante. Ainsi, on pourra écrire :
char ch[20] =’’bonjour’’
Ceci sera parfaitement équivalent à une initialisation de ch réalisée par une énumération de
caractères (sans oublier le code 0 noté \0) :
char ch[20]={‘b’,’o’ ,’n’,’j’,’o’,’u’,’r’,’\0’} ;
I.3 Initialisation de tableaux de pointeurs sur des chaînes
Nous avons vu qu’une constante chaîne de caractères était traduite par le compilateur en une
adresse que l’on pouvait, par exemple, affecter à un pointeur sur une chaîne. Cela peut se
généraliser à un tableau de pointeurs, comme dans :
char * jour [7] ={ “lundi”, “mardi”, “mercredi “, “jeudi”,
‘‘Vendredi’’, “ samedi’’, “ dimanche’’} ;
Cette déclaration réalise donc, à la fois
- la création des 7 constantes chaînes correspondant aux 7 jours de la semaine,
- l’initialisation du tableau jour avec les 7 adresses de ces 7 chaînes.
Voici un exemple utilisation cette déclaration :
main()
{
char *jour [7] = { “lundi “, “mardi “, “mercredi “, “jeudi
‘‘vendredi “, “samedi “, ‘‘dimanche ‘‘) ;
int i ;
printf(’’donnez un entier 1 et 7 :’’)
scanf(”%d’’, &i) ;
printf (“le jour n° %d de la semaine est %s”, i, jour[i-1]) ;
}
donnez un entier 1 et 7 :3
le jour n° 3 de la semaine est mercredi
Il- LES ENTREES-SORTIE DE CHAINES
Le langage C offre plusieurs possibilités de lecture ou d’écriture de chaînes
• l’utilisation du code format %s dans les fonctions printf et scanf ou (esscanf)
• les fonctions spécifiques de lecture (gels) ou d’affichage (puts) d’une chaîne (une seule à la
fois),
• a fonction de lecture directe au clavier (cgets) qui présente l’avantage d’autoriser un
contrôle de la taille de la chaîne lue et, partant, de vous protéger contre un éventuel risque de
débordement.
Ici nous nous limitons aux entrées-sorties conversationnelles les autres possibilités seront
examinées lors de l’étude des fichiers.
II.1 Les fonctions gets, puts et le code format %s
Examinons cet exemple de programme :
main()
{
char nom [ 20], prénom[2 0], ville[25] ;
printf(’’quelle est votre ville :’’) ;
gets(ville) ;
printf(’’donnez votre nom et votre prénom :’’) ;
scanf(‘’%s%s’’, nom ,prénom) ;
printf(‘’bonjour cher %s %s qui habite à ‘’,nom ,prénom) ;
puts(ville) ;
}
Quelle est votre ville : Tunis
Donnez votre nom et votre prénom : Foulen ben Foulen
Bonjour cher Foulen ben Foulen qui habite à Tunis
Les fonctions printfet sarnf permettent de lire et d’afficher simultanément plusieurs
informations le type quelconque. Par contre gets et puts ne traitent qu’une chaîne à la fois.
De plus, les délimitations de la chaîne lue ne s’effectuent pas de la même façon avec scanf et
gets. Plus précisément :
· avec le code %s de scanf les délimiteurs sont classiquement l’espace, la tabulation ou la fin de
ligne. Ceci interdit la lecture d’une chaîne contenant des espaces.
· avec gets, seule la fin de ligne sert de délimiteur.
Dans tous les cas, on remarque que la lecture de n caractères implique le stockage on mémoire
de n+1 caractères car le caractère de fin de chaîne (\0) est généré automatiquement par les
fonctions de lecture.
Ainsi, dans notre précédent programme, il n’est pas souhaitable que le nom fourni en donnée
contienne plus de 19 caractères.
Remarques :
1) Dans les appels des fonctions scanf et puts, les identificateurs de tableaux comme nom,
prénom ou ville ne doivent pas être précédés de l’opérateur & puisqu’ils représentent déjà des
adresse.
2) La fonction gets fournit en résultat, soit un pointeur sur la chaîne lue, soit un pointeur nul on
cas d’anomalie.
3) La fonction puts réalise un changement de ligne en fin de l’affichage de la chaîne, ce qui
n’est pas le cas de la fonction printf avec le code format %s.
Les fonctions cgets et sscanf
Le problème de risque de débordement de l’espace alloué à une chaîne lue on donnée peut être
résolue par l’utilisation de la fonction cgets à la place de gets. Celle-ci permet d’imposer un
nombre maximum de caractères à lire.
Voici un exemple qui lit une chaîne comportant au maximum 27 caractères (28 avec le
caractère nul de fin de chaîne).
# inc/ude <stdio h>
main()
{
char texte[30];
printf(‘’donnez un texte : ‘’) ;
texte [0]=28 ;
cgets(texte) ;
printf(‘’vous avez fini %d caractères \n ‘’,texte[1]) ;
puts(&texte[2]= ;
}
Donnez un texte :bonjour
Vous avez fini 7caractères
Bonjours
Donnez un texte abcdefghijklmnopqrstuvwxyza
Vous avez fourni 27 caractères abcdefghijklmnopqrstuvwxyza
En fait cgets utilise les deux premiers caractères de la “zone” dont on lui fournit l’adresse pour
gérer sa lecture
· le premier octet doit être initialisé par le programme au nombre maximum de caractères
que l’on souhaite ranger en mémoire, y compris le caractère nul de fin
· le second octet sera renseigné par cgets à la fin de l’opération pour indiquer le nombre de
caractères effectivement lus.
Cette fonction ne lira pas plus de caractères que prévu. Si l’utilisateur cherche à en fournir
davantage, il en sera informé par l’émission d’un bip sonore. Notez que, dans tous les cas, il
doit valider sa réponse par la touche return.
Nous avons vu les problèmes posés par scanf on cas de réponse incorrecte de la part de
l’utilisateur.
Il est possible de régler la plupart de ces problèmes on travaillant en deux temps
• lecture d’une chaîne de caractères par cgets
• vérification du format de manière comparable à ce que ferait scanf.
Voici un exemple de programme permettant de questionner l’utilisateur jusqu’à ce qu’il ait
fournit une réponse satisfaisante :
# define LG 80
# def;ne LG3 LG+3
#include <stdio.h>
main()
{
int n, compte;
char c;
char tampon[LG3] ;
tampon[0]=LG+1;
do
{
printf(“Donncz un entier et un caractère :’’) ;
cgcts tampon() -
compte = sscanf(&tanpon[2],%d %c”, &n, &c,);
} while (compte <2) ;
printf <“merci pour, ‘‘%d %c “, n, c) ;
}
Donnez un enter et un caractère : bof
Donnez un entier et un caractère : a 125
Donnez un entier et un caractère : 12 bonjour
Merci pour 12 b
III- LES PONCTIONS DE CONCATENATION DE CHAINES
La concaténation est la juxtaposition de chaînes afin d’en former une seule. Elle est réalisée en
C à l’aide de l ‘une des deux fonctions strcat et strncat (dont les prototypes figurent dans string.
h)
111.1 La fonction strcat
Examinons cet exemple :
#include <string.h>
main()
{
char ch l[50]=’’bonjour’’ ; avant bonjour
char *0h2=’’monsieur”; après : bonjour monsieur:
printf (“avant: %s\n “, ch1);
strcat(ch 1, ch2);
printf (“après: %s\n’’,ch 1);
}
Il faut noter la différence entre les deux déclarations (avec initialisation) de chacune des deux
chaînes ch1 et ch 2. La première permet de réserver un emplacement plus grand que la
constante chaîne qu’y trouve place initialement.
L’appel de strcat se présente ainsi :
strat (destination, source)
Cette fonction recopie [a seconde chaîne source à la suite de a première destination, après en
avoir effacé le caractère de fin.
Remarque :
1) Aucun contrôle de longueur n’est réalisé par cette fonction ;il est nécessaire que
emplacement réservé pour la première chaîne soit suffisant pour y recevoir la partie à lui
concaténer.
2) Après exécution de srtcat, la première chaîne n’existe p\us en tant que telle.
3) strcat fournit un résultat
- l’adresse de la chaîne correspondant à la concaténation des deux chaînes fournies en
argument, lorsque l’opération s’est bien déroulée. Ce n’est rien d’autre que l’adresse de ch1.
Le pointeur nul lorsque l’opération s’est mal déroulée.
II.2 La fonction strncat
Cette fonction, dont l’appel se présente ainsi :
strcat (destination, source, igmax)
Travail le de façon semblable a strcat en offrant on outre un contrôle sur le nombre de
caractères qui seront concaténés à la chaîne d’arrivée (destination).
#include <string. h>
main()
{
char ch[50] = “bonjour’’ ; avant :bonjour
char *ch2 = “ monsieur’; après :bonjour monsieur
printf(’’avant %s\n’’;ch1);
strncat (ch1,ch2,6);;
printf(’’apres:%s\n’’;ch1);
}
Il faut noter que le contrôle ne porte pas sur la longueur de la chaîne finale. Fréquemment on
déterminera ce nombre maximal de caractères à recopier comme étant la différence entre la
taille totale de la zone réceptrice et la longueur courante de la chaîne qui s’y trouve. Cette
dernière s’obtiendra par la fonction strlen. Par exemple :
strlen(ch1)
Fournit une valeur de type int correspondant à la longueur de la chaîne ch1. Le caractère nul de
fin, n’étant pas comptabilisé.
IV- LES FONCTIONS DE COMPARAISON DE CHAINES
Il est possible de comparer deux chaînes en utilisant l’ordre des caractères défini par le code
ASCII.
a) La fonction :
strcmp (ch1, ch2) (string. h)
compare deux chaînes dont on lui fournit les adresses et elle fournit une valeur entière définie
comme étant :
· positive si ch1 > ch2 (c’est à dire chl vient après ch2 au sens de l’ordre défini par le code
ASCII)
· nulle si ch1 = ch2 (c’est à dire que chl et ch2 contiennent exactement la même suite de
caractères).
· négative si ch 1 < ch2
Par exemple :
strcmp(”bonjour “, “monsieur’’)
est négatif et
strcmp(”Tunis2 “, ‘Tunis10’’)
est positif.
b) La fonction
strncmp (ch,ch2,lgmax) (string.h)
Travaille comme strcmop mais elle limite la comparaison au nombre maximum de caractères
indiqué par l’enlier lgmax.
Par exemple :
strncmp(’’bonjour “, ‘‘bon’’, 4)
est positif tandis que
strcmp(’’bonjour “, ’’bon “, 2)
vaut zéro.
c) Enfin, les deux fonctions
stricmp (ch1, ch2) (string.h)
strnicmp (ch1, ch2, lgmax) (string.h)
Travaillent respectivement comme strcmp et strncmp, mais sans tenir compte de la différence
entre majuscules et minuscules (pour les seuls caractères alphabétiques).
V- LES FONCTIONS DE COPIE DE CHAINES
a) La fonction :
strcpy (destin, source) (string.h)
Recopie la chaîne située à l’adresse source dans l’emplacement d’adresse destin. Là aussi, il
faut que la taille du second emplacement soit suffisante pour accueillir la chaîne à recopier.
b) La fonction :
strncpy (destin, source, lgmax,) (string.h)
Procède de manière analogue à strcpy, en faisant la recopie au nombre de caractères précisés
par l’expression entière lgmax.
Il faut noter que si la longueur de la chaîne source est inférieure à cette longueur maximale son
caractère de fin sera effectivement recopié. Mais dans le cas contraire, il ne le sera pas.
L’exemple suivant illustre les deux situations :
#include <string. h>
main()
{
char ch1 [20] = “xxxxxxxxxxxxxxxxx”; donnez un mot: bon
char ch2[20]; bon
printf(’’donnez un mot:’’);
gets(ch2) ; donnez un mot: bonjour
strncpy(ch1, ch2, 6); bonjourxxxxxxxxxxxx
printf(”%s”, ch1);
}
c) Enfin, la fonction :
strcpy(chaîne) (string.h)
Effectue une recopie de la chaîne dont on lui fournit l’adresse dans un emplacement qu’elle
alloue dynamiquement et dont elle fournit l’adresse on résultat (elle fournit le pointeur nul si
elle n’a pas pu disposer de suffisamment de place mémoire.)
VI- LES FONCTIONS DE RECHERCHE DANS UNE CHAINE
On trouve, on langage C, des fonctions de recherche de “l’occurrence’’ dans une chaîne d’un
caractère ou d’une autre chaîne (nommée alors sous-chaîne).
Ces fonctions fournissent comme résultat l’adresse de l’information cherchée on cas de succès
et le pointeur nul dans le cas contraire.
strchr (chaîne, caractère) (string.h)
Recherche, dans chaîne la première position où apparaît le caractère mentionné
strchr (chaîne, caractère) (string.h)
Réalise le même traitement que strchr, mais en explorant la chaîne mentionné à partir de la fin.
Elle fournit donc la dernière occurrence du caractère mentionné.
strstr (chaîne, sous-chaîne) (string.h)
Recherche dans chaîne la première occurrence complète de la sous-chaîne mentionnée.
strpbrk (chaîne 1, chaine2) (string. h)
Recherche, dans chaîne1 la première occurrence d’un caractère quelconque de chaine2.
Voici un programme illustrant le fonctionnement de ces quatre fonctions :
#înc/ude <string.h>
#define c ’e’
#define s ch ‘re’
#define voy ‘aeiou’
main()
{
char ,mot[40];
char *adr ;
printf(’’donnez un mots:);
gets (mot);
if (adr=strchr(mot,c))
printf(’’première occurrence de%c en %s\n’’,c,adr);
if (adr=strrchr(mot,c))
printf(’’dernière occurrence de %c en %s\n’’,c, adr) ;
if (adr = strstr(mot,sch))
printf (“première occurence de %s en %s\n’’;sch,adr) ;
if (adr =strpbrk(mot,voy))
printf (“première occurrence de l’une des lettres de %s en %s\n’’,voy,adr) ;
}
Donnez un mot : correspondance
Première occurrence de e en espondances
Dernière occurrence de e en es
Première occurrence de re en repondances
Première occurrence de l’une des lettres de aeiou en orréspondance
Donnez un mot : bonjour
Première occurrence de l’une des lettres de aeiou en onjour

VII- LES FONCTIONS DE CONVERSION

VII.1 Conversion d’une chaîne on une valeur numérique


Il existe trois fonctions permettant de convertir une chaîne de caractères en une valeur
numérique de type int, long ou double. Ces fonctions ignorent les éventuels espaces de début
de chaîne et utilisent les caractères suivant pour fabriquer une valeur numérique. Le premier
caractère invalide arrête l’exploration. Par contre, si aucun caractère n’est exploitable, ces
fonctions fournissent un résultat nul.
atoi (chaine) (stdlib.h)
fournit un résultat de type int;
atol(chaine) (stdlib.h)
fournit un résultat de type long;
atof(chaine) (stdlib.h)
fournit un résultat de type double;
Voici un programme illustrant les possibilités de atoi et atof:
#include <stdlib. h>
main()
{
char ch[40] ;
int n ;
do
{
printf(’’donnez une chaîne :’’) ;
gets(ch);
printf(‘’----->int :%d\n’’,n=atoi(ch));
printf(“----->double :%e\n” ,atof(ch));
} while(n);
}
donnez une chaîne :123
----->int :123
----->double :1.230000e+02
donnez une chaine :-123.45
----->int :-123
----->double :-1.234500e+02
donnez une chaîne : 123.45e6
----->int :123
----->double :1.234500e+08
donnez une chaîne :0
----->int :0
----->double :0.000000e+00
VII.2 Conversion d’une valeur numérique en une chaîne
Nous nous limiterons ici au fonctions de conversion d’un entier en une chaîne (il existe des
fonction permettant de convenir un nombre flottant on une chaîne). Celles-ci permettent
d’exprimer un nombre non seulement sous forme d’une suite de chiffres en base 10, mais
également dans n’importe quelle base comprise entre 2 et 36. Elles sont particulièrement bien
adaptées pour afficher par exemple un nombre en binaire.
Ces trois fonctions fournissent en résultat un pointeur sur la chaîne obtenue
itoa ( entier,chaine,base ) (stdlib.h)
travaille avec un entier de type int.
itoa ( entier,chaine,base ) (stdlib.h)
travaille avec un entier de type long.
ultoa (entier,chaine,base ) (stdlib.h)
travaille avec un entier de type unsigned long.
Voici un exemple d’utilisation de la fonction itoa :
#include <stdlib. h>
main()
{
char ch[40];
int n,b;
do
{
printf(‘’donner un nombre et une base : &b);
printf(” %s\n”, itoa(n, ch, b));
}while(n) ;
}
donner un nombre et une base123
donner un nombre et une base :-12310
-123
donner un nombre et une base :8711
7a
donner un nombre et une base :20016
c8 une base :-20016
ff38
donner un nombre et une base :3536
z
donner un nombre et une base :2002
11001000
donner un nombre et une base :-12
1111111111111111
donner un nombre et une base :00

Das könnte Ihnen auch gefallen