Cours de
Langage C (ANSI)
Version de septembre 2002
II.2.3)
constantes chaînes de caractères
II.4) types
définis par l’utilisateur
II.6)
identificateur d’une variable
II.7)
existence d’une variable
III.1.1)
opérateurs arithmétiques
III.1.2)
opérateurs logique bit à bit
III.2.3)
expression conditionnelle
III.3) opérateurs
sur pointeurs, tableaux
III.4)
opérateur d’affectation
III.5)
conversion de types (transtypages)
IV.2.2) switch (test à
choix multiple)
IV.3.1) while
(boucle tant que)
IV.3.2) do ... while
(boucle jusqu’à ce que)
IV.3.3) for
(boucle avec compteur)
IV.4.1) break
(échappement de boucle ou de test à choix multiple)
* intérêt : portabilité (réputation), langage
système (Unix) et indépendant de tout processeur, « proche de la
machine » (il demande plus au programmeur et il permet de mieux comprendre
certains mécanismes systèmes), permissif (!)
* référence : « Le langage C » de
B.W. Kernighan et D.M. Ritchie, édition Masson (la bible !)
* histoire : début d’Unix en 1968, écriture
successive du système en assembleur, en langage B, en langage NB et en fin en
langage C en 1973, première commercialisation d’Unix en 1975, norme ANSI
(American Standards National Institute) en 1983 pour un langage C non ambiguë
(c’est-à-dire prototypage des fonctions : conformité entre déclaration et
utilisation)
type |
nature |
taille en octets |
void |
rien (!!!) |
0 |
char |
caractère ou entier signé |
1 |
short |
entier signé |
2 |
int |
entier signé |
4 |
long |
entier signé |
4 |
float |
réel |
4 |
double |
réel |
8 |
Le type void est utilisé lors des
déclarations, c’est un type créé avec la norme ANSI.
Les types short et long sont les abréviations de short
int et long int.
Sur certaines machines (pas les PC), un int occupe 2 octets.
Rappel : 1 octet (ou byte) = 8 bits, et 1 bit
est la plus petite représentation informatique qui vaut soit 0, soit 1
(représentation binaire).
Les variables sont signées par défaut (les deux
mots-clés signed et unsigned indiquent si le bit de poids fort doit être considéré ou non comme un
bit de signe, i.e. si la variable est signée ou non. Ainsi un unsigned int désignera une quantité comprise entre 0 et
4 294 967 295 (c’est-à-dire 232-1, où 32 correspond 4
octets x 8 bits) ; à la différence de signed
int, qui se
comporte comme un int, qui désignera une quantité
comprise entre –2 147 483 648 (-231) et
2 147 483 647 (231-1). Le calcul avec des entiers
définis comme unsigned correspond à l’arithmétique
modulo 2n.
Un flottant (un réel
défini en virgule flottante) comporte un signe positif ou négatif, une mantisse
qui est un
décimal positif avec un seul chiffre devant la virgule, puis l’exposant de la
puissance de 10 qui est un entier. La précision
minimum d’un réel est 3.4 * 10-38, la valeur maximum étant 3.4
* 1038, le nombre de chiffres significatifs de la mantisse est 6.
Pour expliquer la gestion de la mémoire, nous
pouvons représenter cette mémoire, sous forme d’un ruban découpé en case
(attention la taille en octets de ces cases dépend du type de l’objet et donc
deux adresses de deux objets consécutifs sont distantes du nombre d’octet
occupé par le premier objet), et indiquer au dessus de la case le nom des
variables employées, et en dessous l’adresse de ces variables.
exemple :
char c,d; int i,j; float r,s;
nom des variables : |
|
|
c |
d |
|
|
|
i |
j |
|
|
|
r |
s |
|
|
valeur des
variables : |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
adresse des
variables : |
|
|
&c |
&d |
|
|
|
&i |
&j |
|
|
|
&r |
&s |
|
|
Attention si les objets consécutifs ne sont pas du
même type, le déplacement dans la mémoire n’est pas aisé.
Les constantes entières de
type entier sont codées :
en base 10 : 12, 52, 1034 ; en base 8 : 014, 064, 02012 ; en base 16 : 0xc, 0x34, 0x40a.
Les constantes entières de
type long sont codées de façon identique mais suffixées par la lettre L (10000L, 0L, 0x1fffL).
Les constantes réelles sont
implicitement de type double et sont codées (en fractionnaire : 3.0, 12.514 ; en format
exponentielle : 2.54E-3, 3E5, -1.1E+12).
Une constante char est un
caractère écrit entre apostrophes : 'A' (code ascii 65, soit en
binaire 01000001 = 0x27+1x26+0x25+0x24+0x23+0x22+0x21+1x20,
codé sur 8 bits=1 octet), 'a' (code ascii 97), '+' (code ascii 43), '1' (code ascii 49)
Il existe des caractères
spéciaux :
'\n' saut de ligne ; '\t' tabulation ; '\b' backspace ; '\\' caractère \ ; '\'' caractère ' ; '\0' caractère nul
Attention lorsque l’on
rentre une information au clavier, on termine l’acquisition par la touche
Entrée (ou Enter, ou Return) qui correspond à un passage à la ligne,
c’est-à-dire au caractère '\n'.
Il est facile, de convertir des lettres majuscules dans des minuscules :
if (c>='A' && c<='Z') c = c-'A'+'a';
ou vice-versa :
if (c>='a' && c<='z') c = c-'a'+'A';
II.2.3) constantes chaînes de caractères
Une chaîne de caractères est
une succession de plusieurs caractères se terminant par le caractère nul :
'\0', et écrite entre
guillemets ; exemple : "paris6" contient 7 caractères 'p', 'a', 'r', 'i', 's', '6' et '\0'.
Plusieurs chaînes de caractères séparées par des signes d’espacement (espaces, tabulateurs ou interlignes) seront réunies en une seule chaîne lors de la compilation.
Un tableau de n éléments est
une série de n cellules mémoires consécutives, de même type (de la même
taille).
int vecteur[100];
(tableau d’entiers à une
dimension, contenant 100 éléments)
double matrice[10][20];
(tableau de réels double
précision à deux dimensions, constitué de 10 lignes de 20 colonnes)
float tab3d[4][6][5];
(tableau de réels simple
précision à trois dimensions)
la case mémoire
correspondant à l’élément tab3d[i][j][k] est &tab3d[0][0][0]+(i*6*5+j*5+k)*sizeof(float)
Dans un tableau de dimension supérieur à 1, les indices de droite sont les plus internes.
Le constante entière donne le nombre d’éléments du tableau, qui sont numérotés de 0 à (nombre-1).
Quand on déclare et
initialise un tableau, la taille n’est pas nécessaire.
exemples :
int tab1d[ ]={19,63,-8,29} est un vecteur à 4 éléments
int tab2d[3][2]={{1,2},{3,4},{5,6}} est une matrice à 3 lignes, 2 colonnes,
dont tab2d[2][0]
est l’élément de la 3ème ligne, 1ère colonne, soit 5
II.3.2) structures (struct)
Une structure est une suite
d’éléments de type quelconque appelés champs accessibles par leur nom, qui sert
à lier entre des variables de type disparate. Lors de la définition d’une
structure des objets peuvent être déclarés ; si ils sont déclarés
ultérieurement, la structure devra nécessairement avoir un nom.
exemple de définition :
struct date {
int an;
short mois, jour;
};
exemple de déclaration de
variables :
struct date t={1789,7,14}, *pt;
date est une structure de
trois champs (pour accéder au champ : t.an, ... et pt->an équivalent de (*pt).an ...)
Une structure qui contient un pointeur sur cette structure définit une liste chaînée (exemple : nœud d’un maillage relié à ses voisins).
Exemple d’utilisation :
struct duree {
int heur, minut, second;
};
int conversion_duree_sec(duree d)
{
return(3600*d.heur+60*d.minut+d.second);
}
void conversion_sec_duree(int nbsec, duree *d)
{
*d.heur=nbsec/60; /* équivalent à : d->heur=nbsec/60; */
*d.minut=(nbsec%3600)/60; /* équivalent à : d->minut=(nbsec%3600)/60; */
*d.second=nbsec%60; /* équivalent à : d->second=nbsec%60; */
}
Une énumération est un regroupement d’un ensemble de constantes. Ces constantes ont une valeur entière affectée automatiquement par le compilateur, en partant de 0 par défaut et avec une progression de 1.
exemple de définition :
enum couleur { rouge, vert, bleu}; alors rouge=0, vert=1 et bleu=2
II.3.4) unions (union)
Une union est un regroupement de plusieurs types sur une même zone mémoire.
II.4) types définis par l’utilisateur
L’utilisateur peut donner de nouveaux noms aux
objets qu’il manipule.
typedef float reel;
%d pour un entier signé (%u pour un entier non signé, %x pour un entier en
héxadécimal, %o pour un entier en octal), %ld pour un entier long avec signe, %f pour un réel en virgule
flottante (%e pour un réel en
exponentielle), %lf pour un réel double
précision, %c pour un caractère, %s pour une chaîne de caractères, …
Les codes peuvent être précédés de nombre : %3d signifie que l’on cherche à
afficher un entier signé sur 3 chiffres, %5.2f signifie que l’on cherche à
afficher un réel avec 2 chiffres après la virgule (le 5 correspond au nombre
minimum de chiffres utilisées).
II.6) identificateur d’une variable
Les identificateurs (nom des variables) contiennent
des lettres non accentuées (en minuscule, les majuscules étant réservé aux
variables du pré-compilateur), des chiffres (sauf le 1er caractère)
et _ (« underscore »).
II.7) existence d’une variable
Les variables locales (définies entre { et }) ne sont connues que dans
le bloc ou la fonction où elles sont déclarées ; par défaut elles sont
temporaires, pour qu’elles soient permanentes (leur valeur étant sauvegardée
entre deux passages dans le bloc ou la fonction) il suffit de les spécifier static à leur déclaration.
Les variables globales dans un fichier source ne
sont connues que dans les fonctions qui suivent leur déclaration.
Les variables globales exportées (spécifiées extern à la déclaration) sont
connues dans tous les fichiers où elles sont déclarées (grâce à une compilation
séparée des différents fichiers).
III.1.1) opérateurs arithmétiques
+ addition
- soustraction
(ou changement de signe)
* multiplication
/ division
(entière si les deux opérandes sont entiers, flottante sinon)
% modulo (reste de la division
entière)
++ incrémentation
++i; (pré-incrémentation) : incrémentation (augmentation de 1) puis
utilisation de i
i++; (post-incrémentation) : utilisation de i puis incrémentation
n=++i; |
º |
i=i+1; n=i; |
et |
n=I++; |
º |
n=i; i=i+1; |
-- décrémentation
III.1.2) opérateurs logique bit à bit
~ complément à 1 (si x=101101 en binaire, ~x devient 010010)
& et bit à bit (si a=0011 et b=1010 en binaire, a&b devient 0010)
| ou
inclusif bit à bit (a|b devient 1011)
^ ou
exclusif bit à bit (Xor) (a^b devient 1001)
<< décalage à gauche (x<<2 devient 10110100)
>> décalage à droite (x>>1 devient 10110)
Il n’a pas de type booléen en C.
Toute expression sera interprétée comme vraie si sa
valeur est différente de 0 (c’est-à-dire 1, 36, -1963, …).
Toute expression sera interprétée comme fausse si sa
valeur est 0.
> strictement supérieur
< strictement inférieur
>= supérieur ou égal
<= inférieur ou égal
== égalité
!= différence
! non
logique
&& et logique
|| ou
logique
III.2.3) expression conditionnelle
condition ? expression1 : expression2 signifie
expression1 si condition est vraie et expression2 si condition est fausse
exemple :
le minimum de deux
expressions a et b peut s’écrire : ((a)<(b)) ? (a) : (b) ) ; c’est d’ailleurs
ainsi qu’est définit min(a,b) dans stdlib.h
III.3) opérateurs sur pointeurs, tableaux
* contenu (attention à ne pas confondre avec l’opérateur de
multiplication)
& adresse (attention à ne pas confondre avec l’opérateur et
logique bit à bit)
. champ de structure
-> champ de structure avec pointeur
III.4) opérateur d’affectation
= peut s’utiliser sous deux formes :
variable = expression1 opérateur expression2
variable
opérateur = expression (º variable =variable opérateur
expression)
(exemple : x+=5 º x=x+5)
(ce qui le
plus proche de la logique humaine, on ajoute 5 à x …)
III.5) conversion de types (transtypages)
L’opérateur (cast) correspond à une commande
pour changer le type d’un objet (exemple : si on a deux déclarations int i; et float r; alors on peut écrire i=(int)r;)
L’opérateur sizeof permet de connaître la
taille d’un objet ou tableau (exemple : si on une déclaration int i, tab[4]; alors sizeof(i) donnera 2=sizeof(int) et sizeof(tab) donnera 8=4sizeof(int))
Lorsque des opérandes de types différents interviennent dans le calcul d’une même expression, ou lorsqu’ils sont des arguments transférés à une fonction, ils sont tous convertis en un seul type suivant les lois suivantes :
- toute opérande char ou short est transformé en int
- tout opérande float est transformé en double
- si un des opérandes est de type double, les autres sont transformés en double et le résultat sera double
- si un des opérandes est de type long, les autres sont transformés en long et le résultat sera long
- autrement les opérandes doivent être int et le résultat sera int
Attention : (int)(2*2.5) peut donner 4 ou 5 selon
les compilateurs !
opérateur |
associativité |
priorité |
( ) [ ] -> . |
de gauche à droite |
niveau le plus prioritaire |
!
~ ++ --
- (cast) *
& sizeof |
de droite à gauche |
|
*
/ % |
de gauche à droite |
|
+
- |
de gauche à droite |
|
<< >> |
de gauche à droite |
|
< <= > >= |
de gauche à droite |
|
==
!= |
de gauche à droite |
|
& |
de gauche à droite |
|
^ |
de gauche à droite |
|
| |
de gauche à droite |
|
&& |
de gauche à droite |
|
|| |
de gauche à droite |
|
?
: |
de droite à gauche |
|
=
+= -= *=
/= %= &=
^= |= <<=
>>= |
de droite à gauche |
|
, |
de gauche à droite |
niveau le moins
prioritaire |
exemples :
1) |
a=b=c+d**p++>5&&c<*p--; |
º |
a=(b=(((c+(d*(*(p++))))>5)&&(c<(*(p--))))); |
2) |
int tab[ ]={8,4,2,1}; int *p; p=tab; |
|
º |
|
|
|
||||||||||||||||||
|
printf("%d\n",*p++); |
º |
printf("%d\n",*(p++)); ou encore : x=*p; p=p+1; printf("%d\n",x); |
º |
|
affichage : 8 |
||||||||||||||||||
|
printf("%d\n",*++p); |
º |
printf("%d\n",*(++p)); |
º |
|
affichage : 2 |
||||||||||||||||||
|
printf("%d\n",++*p); |
º |
printf("%d\n",++(*p)); ou encore : *p=*p+1; y=*p; printf("%d\n",y); |
º |
|
affichage : 3 |
||||||||||||||||||
|
printf("%d\n",(*p)++); |
|
º |
|
|
affichage : 3 |
Une instruction en C se termine toujours par ; (instruction = expression + ; ). Les expressions (constantes, variables)
peuvent être combinées ensemble à l’aide d’opérateur, pour former des
expressions plus complexes ; elles peuvent contenir des appels à des
fonctions, et aussi apparaître comme paramètres dans des appels de fonctions.
Différentes instructions peuvent être rassemblées en
bloc à l’aide d’accolades { et }
Lorsqu’un bloc ne contient qu’une et une seule
instruction alors les accolades ne sont plus obligatoires.
IV.2.1) if (test)
if ( condition )
{
instruction1;
instruction2;
}
else
{
instruction3;
instruction4;
}
Si la condition est vraie
(c’est-à-dire sa valeur différente de 0) alors les instruction1 et instruction2
sont réalisées, si la condition est fausse alors les instruction3 et
instruction4 sont réalisées.
La négation de la condition else est totalement facultative ; si elle existe, la clause else se rapporte au dernier if ouvert :
if ( condition1 ) if ( condition2 ) instruction1; else instruction2; |
º |
if ( condition1 ) { if ( condition2 ) instruction1; else instruction2; } |
si après une instruction else , un autre if est ouvert, on peut écrire else if
if ( condition1 ) instruction1; else { if ( condition2 ) instruction2; else instruction3; } |
º |
if ( condition1 ) instruction1; else if ( condition2 ) instruction2; else instruction3; |
IV.2.2) switch (test à choix multiple)
switch ( expression )
{
case constante1 :
instruction1.1;
instruction1.2;
case constante2 :
instruction2;
case constante3 :
case constante4 :
instruction34.1;
instruction34.2;
default :
instructionD;
}
L’expression est évaluée
puis comparée aux différentes valeurs de la liste qui sont des
constantes ; si la valeur de l’expression est trouvée dans la liste ou si
l’option default est présente, l’exécution
commence à partir de la liste d’instructions et continue ensuite jusqu’à la fin
du bloc switch ; l’option default est facultative.
IV.2.3) remarque
Dans un test avec plusieurs
membres, en cas d’égalité de priorité, le membre le plus à gauche est évalué en
premier. Si la valeur du premier membre est suffisante pour définir la valeur
du test, les autres membres ne sont pas évalués.
IV.3.1) while (boucle tant que)
while ( condition )
{
instruction1;
instruction2;
}
les deux instructions
s’exécuteront tant que la condition sera vraie
IV.3.2) do ... while (boucle jusqu’à ce que)
do
{
instruction1;
instruction2;
}
while (
condition );
les deux instructions
s’exécuteront une fois puis autant de fois que la condition sera vraie
IV.3.3) for (boucle avec compteur)
for ( initialisation ; condition ; in(dé)crémentation )
{
intsruction1;
instruction2;
}
l’initialisation est
exécutée avant d’entrer dans la boucle, la condition est évaluée à chaque tour
de boucle, l’in(dé)crémentation est effectuée après les instructions et avant
de remonter à la condition
exemple :
int i, j, k;
for ( i=0 , j=10 ; i<j && k>0 ; i++ , j-- )
{
}
boucle infinie :
while ( 1 ) { ... } |
º |
#define ever ;; for ( ever ) { ... } |
boucle qui permet de se
déplacer dans un tableau de caractères à l’aide d’un pointeur sur caractères
pointant sur un tableau, à la fin le pointeur se trouve sur la case contenant '\0' :
while ( *p != '\0' ) { ++p; } |
º |
for ( ; *p != '\0' ; p++ ) ; |
attention la boucle suivante
permet aussi de se déplacer dans un tableau de caractères à l’aide d’un
pointeur sur caractères pointant sur un tableau, mais à la fin le pointeur se
trouve sur la case suivant celle contenant '\0' :
while ( *p++ != '\0' )
;
IV.4.1) break (échappement de boucle ou de test à choix multiple)
permet d’arrêter
prématurément une et une seule instruction itérative (while, do ... while, for) ou une sélection sur choix (switch) avant la fin normale
while ( condition1 )
{
instruction1;
if ( condition2 )
{
instruction2;
break;
}
instruction3;
}
dès que la condition2 est
vraie, on effectue l’instruction2 et on sort de la boucle while quelque soit l’état de la condition1
exemple :
char c;
switch ( c )
{
case 'a' :
case 'e' :
case 'i' :
case 'o' :
case 'u' :
case 'y' :
printf("voyelles\n");
break;
default :
printf("consonnes\n");
}
IV.4.2) continue (au suivant)
permet de remonter à la
partie condition dans la boucle courante
for ( initialisation ; condition1 ; in(dé)crémentation )
{
intsruction1;
if ( condition2 )
{
instruction2;
continue;
}
instruction3;
}
dès que la condition2 est
vraie, on effectue l’instruction2 et on revient au début de la boucle for sans effectuer l’instruction3
goto : à ne pas
utiliser !
Un pointeur est une variable dont le contenu a pour
valeur l’adresse d’un autre objet.
Il n’existe pas de type pointeur à proprement
parler.
avec :
int i,*p;
p=&i;
p est un pointeur (indiqué par l’*) vers l’entier i (i.e. la valeur de p, c’est l’adresse de i, i.e. &i), *p représente la valeur (le
contenu) de l’objet pointé, i.e. l’entier i.
Le pointeur nul (NULL) est un pointeur qui pointe
nulle part.
Le contenu d’un pointeur (=adresse de l’objet pointé) est du même type que l’objet pointé, c’est-à-dire qu’il occupe une place identique (une adresse n’est pas forcément un entier !).
L’arithmétique sur les pointeurs est une
arithmétique qui se soucie du type de l’objet pointé (un pointeur p + un entier
n donne l’ancienne adresse + n éléments du type pointé ; différence de
deux pointeurs du même type donne le nombre d’éléments situés entre les deux
pointeurs).
exemple :
int i,j,*p;
i=5;
p=&i;
j=*p;
*p=j+2;
au début il y a l’allocation de l’espace mémoire
nécessaire pour les trois variables : i, j et p, à la fin le contenu est
le suivant : 7 pour i, 5 pour j, et l’adresse de i pour p
après la déclaration : int i,j,*p; , on a :
nom : |
|
|
i |
|
|
|
j |
|
|
|
p |
|
|
valeur : |
|
|
|
|
|
|
|
|
|
|
|
|
|
adresse : |
|
|
&i |
|
|
|
&j |
|
|
|
&p |
|
|
puis après : i=5; , on a :
nom : |
|
|
i |
|
|
|
j |
|
|
|
p |
|
|
valeur : |
|
|
5 |
|
|
|
|
|
|
|
|
|
|
adresse : |
|
|
&i |
|
|
|
&j |
|
|
|
&p |
|
|
puis après : p=&i; , on a :
nom : |
|
|
i |
|
|
|
j |
|
|
|
p |
|
|
valeur : |
|
|
5 |
|
|
|
|
|
|
|
&i |
|
|
adresse : |
|
|
&i |
|
|
|
&j |
|
|
|
&p |
|
|
puis après : j=*p; , on a :
nom : |
|
|
i |
|
|
|
j |
|
|
|
p |
|
|
valeur : |
|
|
5 |
|
|
|
5 |
|
|
|
&i |
|
|
adresse : |
|
|
&i |
|
|
|
&j |
|
|
|
&p |
|
|
puis après : *p=j+2; , on a :
nom : |
|
|
i |
|
|
|
j |
|
|
|
p |
|
|
valeur : |
|
|
7 |
|
|
|
5 |
|
|
|
&i |
|
|
adresse : |
|
|
&i |
|
|
|
&j |
|
|
|
&p |
|
|
La déclaration d’un tableau de n éléments réserve en
mémoire la place pour loger les n éléments.
Les éléments d’un tableau sont situés
consécutivement en mémoire ; le premier élément a pour indice 0, le
dernier n-1.
si : int tab[4];
tab (l’identificateur du nom du tableau) = &tab[0] (adresse de la 1ère case de ce tableau en mémoire), tab est
donc un pointeur
nom : |
|
|
tab[0] |
tab[1] |
tab[2] |
tab[3] |
|
|
valeur : |
|
|
|
|
|
|
|
|
adresse : |
|
|
&tab[0] ou tab |
&tab[1] |
&tab[2] |
&tab[3] |
|
|
&tab[0]+1 correspond à &tab[1], ...
char **c; c est un
pointeur de pointeurs de caractères,
*c est pointeur de caractères,
**c est un caractère
char *d[ ]; d est un tableau de pointeurs de
caractères
d[ ] est un pointeur de caractères
*d[
] est un
caractère
char e[ ][ ]; e est un tableau
de tableau de caractères
(tableau de chaînes de
caractères)
e[ ] est un tableau de caractères (chaîne de caractères)
e[ ][ ] est un caractère
Il y a équivalence entre ces différentes
définitions.
exemple :
#include
<stdio.h>
main()
{
static char *f[ ]={"MOINS","PLUS","GRAND"};
static char **g[ ]={f,f+1,f+2};
/* les tableaux f et g se trouvent dans la zone d’allocation fixe */
char *p;
int i;
printf("%c\n",*(f[0]+2)); /* donne 'I' (3ème caractère de la 1ère chaîne) */
printf("%c\n",*(*f+2)); /* donne 'I' (3ème caractère de la 1ère chaîne) */
printf("%c\n",**(f+2)); /* donne 'G' (1er caractère de la 3ème chaîne) */
printf("%s\n",*(f+1)); /* donne "PLUS" */
for ( p=**(g+1) ; *p ; p++ )
*p='A';
printf("%s\n",*(f+1)); /* donne "AAAA" */
for
( i=0 ; i<4 ; i++ )
*(**(g+1)+i)=' ';
printf("%s",*(f+1)); /* donne " " */
}
alors la mémoire contient au départ :
(attention : ci-dessous les adresses sont des
exemples numériques à but démonstratif)
valeur : |
|
'M' |
'O' |
'I' |
'N' |
'S' |
'\0' |
'P' |
'L' |
'U' |
'S' |
'\0' |
'G' |
'R' |
'A' |
'N' |
'D' |
'\0' |
|
adresse : |
|
100 |
|
|
|
|
|
106 |
|
|
|
|
111 |
|
|
|
|
|
|
et :
nom : |
|
|
f[0] |
f[1] |
f[2] |
|
|
|
g[0] |
g[1] |
g[2] |
|
|
valeur : |
|
|
100 |
106 |
111 |
|
|
|
130 |
131 |
132 |
|
|
adresse : |
|
|
130 |
131 |
132 |
|
|
|
|
|
|
|
|
Notons que :
(g+1) contient l’adresse (pointe
vers) g[1]
*(g+1) contient le contenu de g[1], c’est-à-dire l’adresse de
f[1] (soit &f[1])
**(g+1) contient l’adresse de "PLUS", c’est-à-dire l’adresse de 'P'
**(g+1)+i contient du ième
caractère de "PLUS"
C’est le passage de la valeur de la variable.
Un paramètre passé par valeur ne peut pas être modifié.
exemple : printf("%d\n",i);
Le passage par valeur a l’avantage que nous pouvons utiliser les paramètres comme des variables locales bien initialisées. De cette façon, nous avons besoin de moins de variables d’aide.
Habituellement, on utilise un passage par valeur, quand on veut transmettre une valeur (sans plus) à une fonction, c’est-à-dire pour un « argument en entrée ».
Attention : on peut passer un pointeur par valeur, on passe alors le contenu du pointeur, c’est-à-dire une adresse, mais pas l’adresse du pointeur !
C’est le passage d’un pointeur sur la variable,
c’est-à-dire de l’adresse de la variable.
exemple : scanf("%d",&i);
Comme il est impossible de passer « la valeur » de tout un tableau à une fonction, on fournit l’adresse d’un élément du tableau. En général, on fournit l’adresse du premier élément du tableau, qui est donnée par le nom du tableau.
Habituellement, on utilise un passage par adresse, quand on veut récupérer une valeur d’une fonction, ou quand on veut transmettre une valeur à une fonction et récupérer sa nouvelle valeur ; c’est-à-dire pour un « argument en entrée-sortie », ou pour un « argument en sortie ».
Attention : on peut passer un pointeur par adresse, on passe alors l’adresse du pointeur qui contient lui-même une adresse !
#include
<stdio.h> void fct1(int); void fct2(int *); main() { int x=10, y=20; printf("%d\n",x); fct1(x); printf("%d\n",x); printf("%d\n",y); fct2(&y); printf("%d\n",y); } /* (suite à droite) */ |
┌──► │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ──┘ |
void fct1(int valx) /* par valeur */ { valx=100; } void fct2(int *px) /* par adresse */ { *px=100; } l’exécution de ce programme donne 10 10 20 100 |
Dans le programme principal, après la
déclaration-initialisation des deux variables, on a :
nom : |
|
|
x |
y |
|
|
|
|
|
|
|
|
|
|
valeur : |
|
|
10 |
20 |
|
|
|
|
|
|
|
|
|
|
adresse : |
|
|
&x |
&y |
|
|
|
|
|
|
|
|
|
|
à l’entrée de la fonction fct1 (quand on passe de main à fct1, il y a identification entre la valeur de la variable en
argument d’appel de fct1 dans main, et la valeur de la
variable propre à fct1 dans fct1), on a :
nom : |
|
|
x |
|
|
|
|
valx |
|
|
|
|
|
|
valeur : |
|
|
10 |
|
|
|
|
10 |
|
|
|
|
|
|
adresse : |
|
|
&x |
|
|
|
|
&valx |
|
|
|
|
|
|
après l’instruction de la fonction fct1, on a :
nom : |
|
|
x |
|
|
|
|
valx |
|
|
|
|
|
|
valeur : |
|
|
10 |
|
|
|
|
100 |
|
|
|
|
|
|
adresse : |
|
|
&x |
|
|
|
|
&valx |
|
|
|
|
|
|
au retour dans le programme principal, on a :
nom : |
|
|
x |
y |
|
|
|
|
|
|
|
|
|
|
valeur : |
|
|
10 |
20 |
|
|
|
|
|
|
|
|
|
|
adresse : |
|
|
&x |
&y |
|
|
|
|
|
|
|
|
|
|
à l’entrée de la fonction fct2, on a :
nom : |
|
|
|
y |
|
|
|
|
|
|
|
px |
|
|
valeur : |
|
|
|
20 |
|
|
|
|
|
|
|
&y |
|
|
adresse : |
|
|
|
&y |
|
|
|
|
|
|
|
&px |
|
|
après l’instruction de la fonction fct2 (quoique la
variable y ne soit pas connu dans la fonction fct2, mais du fait de
l’identification entre la valeur de la variable en argument d’appel de fct2
dans main, et la valeur de la
variable propre à fct2 dans fct2), on a :
nom : |
|
|
|
y |
|
|
|
|
|
|
|
px |
|
|
valeur : |
|
|
|
100 |
|
|
|
|
|
|
|
&y |
|
|
adresse : |
|
|
|
&y |
|
|
|
|
|
|
|
&px |
|
|
au retour dans le programme principal, on a :
nom : |
|
|
x |
y |
|
|
|
|
|
|
|
|
|
|
valeur : |
|
|
10 |
100 |
|
|
|
|
|
|
|
|
|
|
adresse : |
|
|
&x |
&y |
|
|
|
|
|
|
|
|
|
|
VII.1) fonction main
C’est une fonction comme les autres, plus certaines
choses ; en fait elle retourne un entier et reçoit en entrée trois
arguments :
int
main(int argc,char **argv,char **envp)
où :
argc correspond à un compteur d’arguments,
c’est-à-dire au nombre de mots tapés sur la ligne de commande.
argv correspond aux arguments, c’est-à-dire à un
tableau de pointeur sur des chaînes de caractères correspondant chacune aux
mots tapés sur la ligne de commande.
envp correspond à un pointeur d’environnement (ne
sert plus à rien).
Une fonction qui ne retourne rien est de type void, son exécution se termine par la commande return; (si la sortie de cette fonction se fait normalement en fin de
fonction, l’instruction return est facultative).
Une fonction qui retourne quelque chose (uniquement
un, et un seul, objet, variable simple ou pointeur, de type primitif : char, int, short, long, float, double ; donc pas de tableau)
renvoie cette information par l’instruction return( objet retourné );
Une fonction qui n’a pas d’argument est une fonction
de type : fct(void)
Le nombre d’arguments d’une fonction n’est pas
limité.
Attention : une variable définie dans une
fonction (vrai aussi pour un bloc), n’existe pas en dehors de cette fonction.
Une fonction peut appeler une autre fonction
(exemple : main()) ou même s’appeler
elle-même (fonction récursive).
exemple de fonction :
/* calcul de la factorielle
de n */ long factorielle(int n) { int i; long produit=1; if ( n > 1 ) for ( i=2 ; i<=n ; ++i ) produit *= i; return( produit ); } |
º |
/* calcul de la factorielle
de n en récursif */ long factorielle(int n) { long produit=1; if ( n > 1 ) produit = n * factorielle(n-1); return( produit ); } |
L’allocation dynamique permet d’allouer et de libérer de la mémoire, au fur et à mesure des besoins (c’est-à-dire qu’il n’est plus nécessaire de fixer arbitrairement une taille limite d’un tableau ; la taille nécessaire est définie lors de l’exécution et non de la compilation).
La fonction malloc (dont le prototype est void *malloc(size_t taille)) retourne un pointeur sur le premier élément du tableau que l’on
cherche à allouer (il faut convertir le résultat dans le type d’objet
désiré) ; la valeur retournée est un pointeur nul (NULL) dans le cas où l’allocation a échoué. L’argument de la fonction est
de type prédéfini (par un typedef dans stddef.h inclus dans stdlib.h), il correspond à la taille
désirée en octet.
La fonction realloc (dont le prototype est void *realloc(void *pointeur,size_t taille)) contient en argument, le pointeur sur le premier élément du tableau
déjà alloué et dont on veut modifier la taille (la taille passée en argument
est la nouvelle taille désirée). La fonction retourne un pointeur sur le
premier élément du tableau que l’on cherche à réallouer (il faut convertir le
résultat dans le type d’objet désiré) ; si la modification de taille
demandée ne peut se faire à l’adresse initiale (par exemple, parce qu’il n’y a
pas assez de mémoire contiguë disponible à cet endroit là), alors la fonction
déplace le bloc à un emplacement mémoire approprié et donc l’adresse du bloc
n’est plus la même. Dans le cas où la ré-allocation a échoué, la valeur
retournée est un pointeur nul (NULL).
La fonction free (dont le prototype est void free(void *pointeur)) permet de libérer l’espace
mémoire qui a été alloué par les fonctions précédentes. L’argument est le
pointeur contenant l’adresse du premier élément réservé par une des fonctions
d’allocation.
exemple d’allocation :
#include
<stdio.h>
#include <stdlib.h>
main()
{
int nbelt, *pt, *qt;
nbelt=10;
pt=(int *)malloc(nbelt*sizeof(int)); /* allocation de 10 places */
if ( !pt )
{
printf("allocation memoire
impossible\n");
exit(1);
}
qt=pt;
…
nbelt=30;
pt=(int *)realloc(qt,nbelt*sizeof(int)); /* rajout de 20 places */
if ( !pt )
{
printf("re-allocation memoire
impossible\n");
exit(2);
}
…
free(pt); /* liberation des 30 places,
mais attention
contient
toujours la même adresse */
}
L’écriture d’un programme informatique en langage C
(vrai également avec les autres langages !) demande de la rigueur,
de la préparation, ... Chaque programme devra être lisible, modifiable,
certaines parties ré-utilisables, par un autre programmeur ainsi que par le
créateur plusieurs mois après la création. C’est-à-dire qu’un programme doit
être correctement indenté, commenté, modulaire (découpé en
fonctions le plus élémentaires possibles), ..., qu’un organigramme précis et complet
doit être écrit au préalable à l’écriture du code informatique.
La présentation de l’indentation peut suivre des
règles diverses :
while ( condition ) { instruction; } |
º |
while ( condition ) { instruction; } |
º |
while ( condition ) { instruction; } |
l’important est de garder la même règle au cours de
l’écriture d’un programme
Par clarté, on a habitude d’utiliser des lignes blanches pour séparer les déclarations et les instructions.
/*
programme d’impression des carrés des dix chiffres : CECI EST UN COMMENTAIRE
QUI COMMENCE AVEC LE SYMBOLE slash-étoile ET SE TERMINE AVEC LE SYMBOLE
étoile-slash */
#include
<stdio.h> /*
LE COMPILATEUR AURA ACCES AU FICHIER
stdio.h
QUI DEFINIT NOTAMMENT LA FONCTION
printf ; LE LIEN ENTRE LE COMPILATEUR ET CE
FICHIER
EST AUTOMATIQUE ; LE NOM DE CE
FICHIER,
EST INDIQUE ENTRE CHEVRONS CAR IL
N’EST
PAS PRESENT DANS LE REPERTOIRE
COURANT
*/
int power(int ,int ); /* PROTOTYPAGE DE
LA FONCTION :
DECLARATION
DU TYPE DE L’OBJET RETOURNE
PAR
LA FONCTION, ET DU TYPE DES */
/* ARGUMENTS DE LA FONCTION power */
main() /*
LA FONCTION main
EST TOUJOURS LE PROGR.
PRINCIPAL
; LES PARENTHESES, USUELLES POUR
TOUTE FONCTION,
INDIQUENT QUE LA
FONCTION main N’A PAS D’ARGUMENT */
{ /*
DEBUT DE BLOC,
DU
PROGRAMME PRINCIPAL */
int i;
int res; /*
DECLARATION DES VARIABLES i ET res
EN
ENTIER */
i=1; /* AFFECTATION A 1 DE LA VARIABLE i :
INITIALISATION
*/
while ( i < 10
) /*
BOUCLE TANT QUE : LES INSTRUCTIONS
COMPRISES
DANS LE BLOC SERONT EFFECTUES
TANT
QUE LA CONDITION i<10 SERA VERIFIEE */
{ /* DEBUT DE BLOC,
DE LA BOUCLE TANT QUE */
res=power(i,2); /* APPEL DE LA
FONCTION power,
LES
ARGUMENTS SONT PASSES PAR VALEUR,
C’EST-A-DIRE
QUE LES VALEURS SONT
TRANSMISES
A LA FONCTION power */
printf("%d
au carré = %d\n",i,res);
/* AFFICHAGE A L’ECRAN DE : (la valeur
de i) au
carré
= (résultat calculé par power)
LE
%d CORRESPOND AU
TYPE ET AU FORMAT
DES
DONNEES */
i=i+1; /*
INCREMENTATION DE LA VARIABLE i */
} /* FIN DE BLOC,
DE LA BOUCLE TANT QUE */
} /*
FIN DE BLOC, DU PROGRAMME PRINCIPAL */
int power(int x,int
n) /*
LA FONCTION power RETOURNE UN ENTIER,
ET
A DEUX ARGUMENTS ENTIERS
CES
ARGUMENTS SERONT UTILISES, MAIS SI ILS
SONT
MODIFIES, CETTE NOUVELLE VALEUR NE
SERA
PAS TRANSMISE AU PROGRAMME
PRINCIPAL
*/
{ /*
DEBUT DE BLOC, DE LA FONCTION power */
int res; /*
VARIABLE LOCALE A LA FONCTION,
DONC
DIFFERENTE DE LA VARIABLE res
DU
PROGRAMME PRINCIPAL */
res=1;
/* AFFECTATION A
1 DE LA VARIABLE res :
INITIALISATION
*/
while ( n > 0
) /*
BOUCLE TANT QUE */
{ /* DEBUT DE BLOC,
DE LA BOUCLE TANT QUE */
res=res*x; /* AFFECTION DE
LA MULTIPLICATION DE LA
VALEUR
INITIALE DE res PAR x, A LA */
/* VARIABLE res */
n=n-1; /* DECREMENTATION
DE LA VARIABLE n */
} /* FIN DE BLOC,
DE LA BOUCLE TANT QUE */
return(res); /* SPECIFICATION
DE LA VALEUR RETOURNEE
PAR
LA FONCTION power */
} /*
FIN DE BLOC, DE LA FONCTION power */
/* ON ECRIT
USUELLEMENT LES PROGRAMMES EN MINUSCULE */
/* CHAQUE
INSTRUCTION EN LANGAGE C SE TERMINE PAR ;
*/
/* TOUTE LIBERTE
EST LAISSEE DANS L’USAGE DES BLANCS ET */
/* DE L’INDENTATION ; MAIS COMMENTAIRES,
LIGNES VIDES ET
INDENTATIONS
PERMETTENT DE HIERARCHISER LES DIFFERENTES
PARTIES
D’UN PROGRAMME ET D’EN AUGMENTER LA LISIBILITE.
DONC
BIEN QUE FACULTATIVES, CES REGLES FONT PARTIE
D’UNE
BONNE PROGRAMMATION */
Voici
quelques fonctions prédéfinies dans la bibliothèque standard :
printf : fonction, définit dans stdio.h, qui permet d’écrire à l’écran (sortie standard : stdout) du texte
scanf : fonction, définit dans stdio.h, qui permet d’acquérir en mémoire une valeur tapée au clavier (entrée
standard : stdin)
getchar : fonction, définit dans stdio.h, qui permet d’acquérir en mémoire un, et un seul, caractère tapé au
clavier (exemple : char c; c=getchar();)
putchar : fonction, définit dans stdio.h, qui permet d’écrire à l’écran un, et un seul, caractère
(exemple : char c; putchar(c);)
fopen : fonction, définit dans stdio.h, qui permet d’ouvrir un fichier (exemple : FILE *fp; fp=fopen(nomdefichier,mode); où fp est le
pointeur sur le fichier, où nomdefichier est une chaîne de caractères
correspondant au nom de fichier à ouvrir, et mode une chaîne de caractères
correspondant au mode d’ouverture du fichier correspondant, "r" pour une ouverture en
lecture, "w" pour une ouverture en écriture, "r+" pour une ouverture en lecture et écriture d’un fichier devant exister,
"w+" pour une ouverture en lecture et écriture d’un fichier vide, si il
existe, son contenu est détruit, …)
fread : fonction, définit dans stdio.h, qui permet de lire des informations dans un fichier
fwrite : fonction, définit dans stdio.h, qui permet d’écrire des informations dans un fichier
fscanf : fonction, définit dans stdio.h, qui permet de lire des informations formatées dans un fichier
fprintf : fonction, définit dans stdio.h, qui permet d’écrire des informations formatées dans un fichier
fseek :
fonction, définit dans stdio.h, qui permet de positionner
le pointeur à une position indiquée du fichier
rewind :
fonction, définit dans stdio.h, qui permet de positionner
le pointeur au début du fichier
fclose :
fonction, définit dans stdio.h, qui permet de fermer un
fichier
strcmp : fonction, définit dans string.h, qui permet de comparer deux chaînes de caractères (la valeur
retournée est négative sur la première chaîne en argument est inférieure à la
deuxième chaîne en argument ; la valeur est positive si la première est
supérieure à la deuxième ; la valeur est zéro quand les deux chaînes sont
identiques)
strlen : fonction, définit dans string.h, qui fournit la longueur d’une chaîne de caractères
sin (« cos », « tan », …) : fonction, définit dans math.h, qui fournit la valeur du
sinus (« cosinus », « tangente », …) (exemple : double x,y; y=sin(x); y aura pour valeur sin(x))
atan2 : fonction, définit dans math.h, qui fournit la valeur de l’arctan (exemple : double x,y,z; z=atan2(y,x); z aura pour valeur arctan(y/x))
pow : fonction, définit dans math.h, qui fournit la valeur d’une variable à une certaine puissance
(exemple : double x,y,z; z=pow(y,x); z aura pour valeur xy)
log10 (« log ») : fonction, définit dans math.h, qui fournit la valeur du
logarithme décimal (« népérien ») (exemple : double x,y; y=log10(x); y aura pour valeur
log10(x))
sqrt : fonction, définit dans math.h, qui fournit la valeur de la racine carrée (exemple : double x,y; y=sqrt(x); y aura pour
valeur sqrt(x))
fabs : fonction, définit dans math.h, qui fournit la valeur de la valeur absolue (exemple : double x,y; y=fabs(x); y aura pour valeur
abs(x))
rand : fonction, définit dans stdlib.h, qui fournit un nombre entier aléatoire, en fait pseudo-aléatoire,
compris entre 0 et RAND_MAX (cette dernière valeur est
au moins égale à 32767)
malloc : fonction, définit dans stdlib.h, qui permet d’allouer un emplacement d’un certain nombre d’octets,
sans l’initialiser, et qui fournit l’adresse correspondante lorsque
l’allocation a réussi ou sinon un pointeur nul
exit : fonction, définit dans stdlib.h, qui termine l’exécution d’un programme
Le langage C++ est une extension du C (il s’appelle C++ en référence à l’opérateur d’incrémentation du C). Il est considéré comme un langage orienté objet car il répond aux trois principes : 1) encapsulation (ou peut définir des classes qui sont des structures évoluées dont certains champs, appelés membres, sont des fonctions, appelées méthodes), 2) polymorphisme (deux fonctions peuvent avoir des paramètres facultatifs dont la valeur par défaut est mentionné dans le prototypage, appelé signature) et 3) héritage (les classes peuvent être définies à partir d’autres classes).
Référence : « Le langage C++ » de B.
Stroustrup, édition Campus Press (une autre bible !)
exemple d’un programme qui effectue des conversions
pouce vers centimètre et inversement (l’utilisateur rentre un nombre suivi d’un
caractère indiquant l’unité : i pour une valeur rentrée en pouce, c pour
une valeur rentrée en centimètre ; le programme affiche la valeur
correspondante dans l’autre unité) :
// CECI EST UN COMMENTAIRE,
QUI SE TERMINE A LA FIN DE LA LIGNE
#include <iostream.h>
int main()
{
const float factor=2.54; // 1 pouce = 2.54 cm
float x, in, cm;
char ch;
cout<<”Entrez une longueur (suivi
d’un caractere, i pour pouce, c pour cm) : “;
cin>>x; // LECTURE D’UN NOMBRE REEL
VIRGULE FLOTTANTE
cin>>ch; // LECTURE D’UN SUFFIXE
switch ( ch )
{
case ‘i’ :
in=x;
cm=x*factor;
break;
case ‘c’ :
in=x/factor;
cm=x;
break;
default :
in=cm=0;
break;
}
cout<<in<<” in = ”<<cm<<” cm\n”;
}