|
|
Cours IDRIS : langage C Support de cours |
\def\SeminarTransparentsEnFrancais{o}% Transparents en français
% Customizing listings:
\lstloadlanguages{C}
%\lstset{language=C}
%\lstset{language=C,
% morecomment=[l][\color{red}]{/*},
% morecomment=[l][\color{red}]{*},
% morecomment=[l][\color{red}]{*/},
% backgroundcolor=\color{yellow},
% extendedchars=true,
% basewidth={0.58em,0.45em},
% showstringspaces=false,
% fancyvrb=true,
% basicstyle=}
% Pour centrer les légendes
Document réalisé à partir des transparents
du cours "Langage C" de l'IDRIS.
Pour une impression correcte, utilisez la version
PostScript.
Langage de programmation développé en 1970 par Dennie Ritchie aux Laboratoires Bell d'AT&T. Il est l'aboutissement de deux langages :
Il fut limité à l'usage interne de Bell jusqu'en 1978 date à laquelle Brian Kernighan et Dennie Ritchie publièrent les spécifications définitives du langage : «~The C programming Language~».
Au milieu des années 1980 la popularité du langage était établie. De nombreux compilateurs ont été écrits, mais comportant quelques incompatibilités portant atteinte à l'objectif de portabilité. Il s'est ensuivi un travail de normalisation effectué par le comité de normalisation X3J11 de l'ANSI qui a abouti en 1988 avec la parution par la suite du manuel : «~The C programming Language - 2ème édition~».
| ! | * | + | \ | " | < |
| # | ( | = | | | { | > |
| % | ) | ~ | ; | ] | / |
| ^ | - | [ | : | , | ? |
| & | _ | } | ' | . | (espace) |
Exemples
| x | y12 | somme_1 | _temperature |
| noms | surface | fin_de_fichier | TABLE |
| 4eme | commence par un chiffre |
| x#y | caractère non autorisé (#) |
| no-commande | caractère non autorisé (-) |
| taux change | caractère non autorisé (espace) |
| auto | extern | sizeof |
| break | float | static |
| case | for | struct |
| char | goto | switch |
| const | if | typedef |
| continue | int | union |
| default | long | unsigned |
| do | register | void |
| double | return | volatile |
| else | short | while |
| enum | signed | |
Exemple :
#include
Cette commande admet plusieurs options telles :
De plus il existe un type ensemble vide : le type void.
Les mots-clés short et long permettent d'influer sur la taille mémoire des entiers et des réels.
Liste des différents types de base
| Syntaxe | Type |
| void | ensemble vide |
| char | caractère |
| short int | entier court |
| int | entier par défaut |
| long int | entier long |
| float | réel simple précision |
| double | réel double précision |
| long double | réel précision étendue |
Remarques
Les deux mots-clés unsigned et signed peuvent
s'appliquer aux types caractère et entier pour indiquer si le bit
de poids fort doit être considéré ou non comme un bit de signe.
Les entiers sont signés par défaut, tandis que les caractères
peuvent l'être ou pas suivant le compilateur utilisé.
Une déclaration telle que unsigned char permettra de désigner
une quantité comprise entre 0 et 255,
tandis que signed char désignera une quantité comprise
entre -128 et +127.
De même unsigned long permettra de désigner une quantité
comprise entre 0 et
232-1, et long
une quantité comprise entre -231 et
231-1.
Exemple
#include <stdio.h>
unsigned char mask;
long val;
main()
{
unsigned int indice;
float x;
double y;
char c;
...
return;
}
double f(double x)
{
unsigned short taille;
int i;
unsigned long temps;
double y;
...
return y;
}
Les énumérations sont des types définissant un ensemble de constantes qui portent un nom que l'on appelle énumérateur. Elles servent à rajouter du sens à de simples numéros, à définir des variables qui ne peuvent prendre leur valeur que dans un ensemble fini de valeurs possibles identifiées par un nom symbolique.
Syntaxe
enum [nom]
{
énumérateur1,
énumérateur2,
énumérateur3,
...
énumérateurn
};
Les constantes figurant dans les énumérations ont une valeur entière affectée de façon automatique par le compilateur en partant de 0 par défaut et avec une progression de 1. Les valeurs initiales peuvent être forcées lors de la définition.
enum couleurs {noir, bleu, vert, rouge, blanc,
jaune};
enum couleurs
{
noir = -1,
bleu,
vert,
rouge = 5,
blanc,
jaune
};
Dans le 1er exemple, les valeurs générées par le compilateur seront :
| noir | 0 | vert | 2 | blanc | 4 |
| bleu | 1 | rouge | 3 | jaune | 5 |
et dans le 2e :
| noir | -1 | vert | 1 | blanc | 6 |
| bleu | 0 | rouge | 5 | jaune | 7 |
L'adresse d'un objet est indissociable de son type. On pourra se définir par exemple des pointeurs de caractères, des pointeurs d'entiers voire des pointeurs d'objets plus complexes.
L'opération fondamentale effectuée sur les pointeurs est l'indirection, c'est-à-dire l'évaluation de l'objet pointé. Le résultat de cette indirection dépend du type de l'objet pointé.
Par exemple si p_car et p_reel sont respectivement un pointeur de caractères et un pointeur de réel simple précision référençant la même adresse µ, une indirection effectuée sur p_car désignera le caractère situé à l'adresse µ, tandis qu'une indirection effectuée sur p_reel désignera le réel simple précision située à la même adresse.
Bien qu'ayant le même contenu (l'adresse µ), ces deux pointeurs ne sont pas identiques !
< type > < construction > [,< construction >,...];
où type est un type élémentaire (type de base, énumération de constantes) ou un type que l'on s'est défini, et construction est soit un identificateur soit un objet plus complexe construit à l'aide de constructeurs homogènes.
Symboles associés aux constructeurs homogènes
| Symbole | Objet construit |
| * | pointeur |
| [ ] | vecteur |
| ( ) | fonction |
Exemple
char lignes[100];
int *p_entier;
double fonc();
Les déclarations précédentes permettent de définir respectivement :
Ces constructeurs peuvent se combiner entre eux, permettant ainsi de définir des objets encore plus complexes.
Exemple
char *chaines[100];
int mat[100][40];
char **argv;
Le constructeur homogène * est moins prioritaire que
les deux autres.
De ce fait, les déclarations précédentes permettent de définir
respectivement :
L'utilisation de parenthèses permet de modifier la priorité et donc l'ordre d'évaluation.
Exemple
int (*tab)[10];
char (*f)();
char *(*g)();
float *(*tabf[20])();
Cet exemple permet de définir respectivement :
Il en existe 3 :
Les structures permettent de regrouper des objets dont les types peuvent être différents.
Syntaxe
struct [ nom ]
{
< liste de déclarations >
};
Les objets regroupés sont les membres ou composantes
de la structure les contenant.
Remarques
Exemple
struct
{
char c;
unsigned int i;
float tab[10];
char *p;
} a, b;
Exemples
struct cellule
{
char **p;
int *t[10];
int (*f)();
};
struct cellule cel1, *cel2;
struct cellule cel[15];
struct boite
{
struct cellule cel1;
struct cellule *cel2;
struct boite *boite_suivante;
int ent;
} b1, b2, *b3;
Un champ de bits est un ensemble de bits contigus à l'intérieur d'un même mot.
Le constructeur de structures permet de définir un découpage mémoire en champs de bits. Les membres de cette structure désignent les différents champs de bits. Ils doivent être du type unsigned int et indiquer le nombre de bits de chaque champ.
Syntaxe
struct [ nom ]
{
unsigned int champ1 : longueur1;
unsigned int champ2 : longueur2;
...
unsigned int champn : longueurn;
};
Exemple
struct
{
unsigned int actif : 1;
unsigned int type : 3;
unsigned int valeur : 14;
unsigned int suivant: 14;
} a, b;
Un champ peut ne pas avoir de nom. Sa longueur indique alors
le nombre de bits que l'on veut ignorer.
Une longueur égale à 0 permet de forcer l'alignement
sur le début du mot mémoire suivant.
Exemple
struct zone
{
unsigned int a: 8;
unsigned int : 0;
unsigned int b: 8;
unsigned int : 8;
unsigned int c: 16;
};
struct zone z1, *z2;
Remarques
Le constructeur union permet de définir des données de type différent ayant la même adresse mémoire.
Syntaxe
union [ nom ]
{
< liste de déclarations >
};
Remarques
Exemples
struct complexe
{
float x;
float y;
};
union valeur
{
long entier;
float reel;
struct complexe cmplx;
};
enum type {Entier, Reel, Complexe};
struct nombre
{
enum type type;
union valeur valeur;
};
struct nombre n;
Exemples
struct zone
{
int nb_parm;
char **parm;
union
{
unsigned char mask;
struct
{
unsigned int a: 1;
unsigned int b: 1;
unsigned int c: 1;
unsigned int d: 1;
unsigned int e: 1;
unsigned int f: 1;
unsigned int g: 1;
unsigned int h: 1;
} drapeaux;
} infos;
} z1, *z2;
Il existe plusieurs manières de se définir de nouveaux types :
Syntaxe
typedef < déclaration >
Exemples
typedeflong size_t;
typedefunsigned long Cardinal;
typedefchar *va_list;
typedefstruct complexe Complexe;
typedefint Matrice[10][20];
Complexe c1, *c2;
Cardinal nombre;
va_list arg;
size_t dimension;
Matrice tab, *ptr_mat;
typedef struct cellule
{
Cardinal n;
struct cellule *ptr_suivant;
} Cellule;
Cellule cel1, *cel2;
Une expression de type est une expression construite en retirant l'objet de la déclaration qui le définit.
Exemples
char *c;
int (*f)();
char (*tab)[100];
char (*(*x())[6])();
char (*(*vec[3])())[5];
Complexe (**ptr)[5][4];
Les types des objets déclarés précédemment sont donnés par
les expressions de types suivant :
char *
int (*)()
char (*)[100]
char (*(*())[6])()
char (*(*[3])())[5]
Complexe (**)[5][4]
Constantes entières
Une constante entière peut s'écrire dans les systèmes décimal, octal ou hexadécimal.
Une constante entière préfixée :
Une constante entière est par défaut de type int. Elle est de type long si elle est suffixée par les lettres l ou L et non signée lorsqu'elle est suffixée par les lettres u ou U.
Exemples
| base | 10 | 22 | 56 | 1789 | 32765 |
| 22u | 56L | 29UL | 1L | ||
| base | 8 | 0643 | 0177 | 0644 | 0755 |
| 0177L | 0222UL | 0777u | 0766ul | ||
| base | 16 | 0xff | 0Xabcd | 0x80 | 0X1 |
| 0xffL | 0X1uL | 0X7fU | 0x5fUL | ||
Constantes réelles
Une constante réelle (ou constante en virgule flottante) est un nombre exprimé en base 10 contenant un point décimal et éventuellement un exposant séparé du nombre par la lettre e ou E.
Une constante réelle est par défaut de type double. Elle sera du type float si on la suffixe par la lettre f ou F.
Exemples
| 0. | 1. | 0.2 | 1789.5629 |
| 50000. | 0.000743 | 12.3 | 315.0066 |
| 2E-8 | 0.006e-3 | 1.66E+8 | 3.1415927 |
| 1.6021e-19f | 6.0225e23F | 2.718281 | 6.6262e-34 |
Constantes caractères
Une constante caractère est assimilée à un entier sur un octet dont la valeur correspond au rang du caractère dans la table ASCII.
Une constante caractère est constituée soit :
Séquences d'échappement
| Syntaxe | Séq. d'éch. | Code ASCII |
| sonnerie | \a | 7 |
| retour arrière | \b | 8 |
| tabulation h. | \t | 9 |
| tabulation v. | \v | 11 |
| retour à la ligne | \n | 10 |
| nouvelle page | \f | 12 |
| retour chariot | \r | 13 |
| guillemets | \" | 34 |
| apostrophe | \' | 39 |
| point d'interr. | \? | 63 |
| antislash | \\ | 92 |
| caractère nul | \0 | 0 |
Exemples
Valeur entière associée
'A' ==> 65
'x' ==> 120
'3' ==> 51
'$' ==> 36
' ' ==> 32
'\n' ==> 10
'\t' ==> 9
'\b' ==> 8
'\"' ==> 34
'\\' ==> 92
'\'' ==> 39
'\0' ==> 0
'\177' ==> 127
'\x0a' ==> 10
'\000' ==> 0
Constantes chaîne de caractères
Une constante chaîne de caractères est une suite de caractères entre guillemets.
En mémoire cette suite de caractères se termine par le caractère NULL ('\0').
La valeur d'une chaîne de caractères est l'adresse du premier caractère de la chaîne qui est donc du type pointeur de caractères (char *).
Ne pas confondre "A" et 'A' qui n'ont pas du tout la même signification !
Pour écrire une chaîne de caractères sur plusieurs lignes on peut :
Exemples
char *chaine = "\
\n\
\t/----------------------------------------------\\\n\
\t| Pour écrire une chaîne sur plusieurs lignes, |\n\
\t| il suffit de terminer chaque ligne par \\ |\n\
\t\\---------------------------------------------/\n";
char *chaine = "écriture d'une "
"chaîne de caractères "
"sur plusieurs "
"lignes\n\n";
Exemple
char tab[100];
double func(int i)
{
...
}
const int nombre = 100;
const char *ptr1;
char const *ptr2;
char * const ptr3 = tab;
Les objets précédents sont respectivement :
Règles de conversions
Les opérateurs + et - admettent des opérandes de type pointeur, ceci pour permettre notamment de faire de la progression d'adresse.
| Opérateur | Op. 1 | Op. 2 | Résultat |
| + | pointeur | entier | pointeur |
| + | entier | pointeur | pointeur |
| - | pointeur | entier | pointeur |
| - | pointeur | pointeur | entier |
Exemples
char *pc;
int *pi;
int a, b, c;
...
...
c = 2*a + b%2;
pc = pc + a;
pi = pi - c;
Le type booléen n'existe pas. Le résultat d'une expression logique vaut 1 si elle est vraie et 0 sinon. Réciproquement toute valeur non nulle est considérée comme vraie et la valeur nulle comme fausse.
Les opérateurs logiques comprennent :
Exemple
int i;
float f;
char c;
i = 7;
f = 5.5;
c = 'w';
Expressions :
-----------
f > 5 ==> vrai (1)
(i + f) <= 1 ==> faux (0)
c == 119 ==> vrai (1)
c != 'w' ==> faux (0)
c >= 10*(i + f) ==> faux (0)
(i >= 6) && (c == 'w') ==> vrai (1)
(i >= 6) || (c == 119) ==> vrai (1)
(f < 11) && (i > 100) ==> faux (0)
L'opérateur sizeof renvoie la taille en octets de son opérande. L'opérande est soit une expression soit une expression de type.
Syntaxe
sizeof expression
sizeof (expression-de-type)
L'opérateur sizeof appliqué à une constante chaîne
de caractères renvoie le nombre de caractères de la chaîne y
compris le caractère NULL de fin de chaîne.
Si p est un pointeur sur un type t
et i un entier :
l'expression p + i
a pour valeur p + i*sizeof(t)
Exemples
int menu[1000];
typedef struct cel {
int valeur;
struct cel *ptr;
} Cel;
sizeof menu / sizeof menu[0]
==> nombre d'éléments
du vecteur menu.
sizeof(long) ==> taille d'un entier long.
sizeof(float) ==> taille d'un flottant
simple précision.
sizeof(struct cel) ==> taille d'un objet du
type struct cel.
sizeof(Cel) ==> taille d'un objet du
type Cel.
Exemples
int u;
int v;
int *pu;
int *pv;
typedef struct cel
{
int valeur;
struct cel *ptr;
} Cel;
Cel c1, *c2;
u = 3 ;
pu = &u ;
v = *pu ;
pv = &v ;
c2 = &c1 ;
Syntaxe
(type) expression
(expression-de-type) expression
Exemples
int n;
int tab[100];
int (*p)[2];
double puissance;
n = 10;
puissance = pow((double)2, (double)n);
p = (int (*)[2])tab;
**(p+49) = 1756;
*(*(p+49)+1) = 1791;
Schéma d'adressage
La fonction pow est la fonction exponentiation, elle renvoie 2n dans l'exemple précédent.
Opérateurs arithmétiques "bit à bit"
Ils correspondent aux 4 opérateurs classiques de l'arithmétique booléenne :
| b1 | b2 | ~b1 | b1&b2 | b1|b2 | b1^b2 |
| 1 | 1 | 0 | 1 | 1 | 0 |
| 1 | 0 | 0 | 0 | 1 | 1 |
| 0 | 1 | 1 | 0 | 1 | 1 |
| 0 | 0 | 1 | 0 | 0 | 0 |
Exemples
int a, b, c, flag;
int Mask;
a = 0x6db7; |-0-|-0-|0110 1101|1011 0111|
b = 0xa726; |-0-|-0-|1010 0111|0010 0110|
c = a&b ; |-0-|-0-|0010 0101|0010 0110| (0x2526)
c = a|b ; |-0-|-0-|1110 1111|1011 0111| (0xefb7)
c = a^b ; |-0-|-0-|1100 1010|1001 0001| (0xca91)
flag = 0x04;
c = Mask & flag;
c = Mask & ~flag;
Opérateurs de décalage
Il existe 2 opérateurs de décalage :
Dans le cas d'un décalage à gauche les bits les plus à gauche sont perdus. Les positions binaires rendues vacantes sont remplies par des 0.
Lors d'un décalage à droite les bits les plus à droite sont perdus. Si l'entier à décaler est non signé les positions binaires rendues vacantes sont remplies par des 0, s'il est signé le remplissage s'effectue à l'aide du bit de signe.
Exemple
int etat;
int oct;
int ent;
int a;
a = 0x6db7; |-0-|-0-|0110 1101|1011 0111|
a = a << 6; |-0-|0001 1011|0110 1101|1100 0000|
(0x1b6dc0)
a = 0x6db7;
a = a >> 6; |-0-|-0-|0000 0001|1011 0110|
(0x1b6)
ent = 0xf0000000;
ent = ent >> 10; |1111 1111|1111 1100|-0-|-0-|
(0xfffc0000)
oct = (etat >> 8) & 0xff;
Opérateurs d'affectation
Les opérateurs d'affectation sont :
| += | -= | *= | /= | %= |
| &= | |= | ^= | <<= | >>= |
On appelle g-valeur toute expression pouvant figurer à gauche d'une affectation. Un identificateur de vecteur n'est pas une g-valeur.
Une expression de la forme :
e1 op= e2 est équivalente à
e1 = e1 op e2
Exemple
int valeur;
int i;
char c;
unsigned char masque;
int n;
i = 2; n = 8;
...
i += 3;
n -= 4;
valeur >>= i;
c &= 0x7f;
masque |= 0x1 << (n - 1);
masque &= ~(0x1 << (n - 1));
Opérateurs d'incrémentation et de décrémentation
Les opérateurs d'incrémentation (++) et de décrémen\-tation (--) sont des opérateurs unaires permettant respectivement d'ajouter et de retrancher 1 au contenu de leur opérande.
Cette opération est effectuée après ou avant l'évaluation de l'expression suivant que l'opérateur suit ou précède son opérande.
Ces opérateurs ne s'appliquent qu'à des g-valeurs.
Exemple
int i;
int j;
int tab[100];
char buffer[2048];
char *ptr;
int *p_ent;
i = 99;
j = 2;
i++;
p_ent = tab;
*(p_ent + --i) = ++j;
ptr = buffer;
*ptr++ = '\n';
Syntaxe
expr1 ? expr2 : expr3
La valeur de l'expression expr1 est interprétée comme
un booléen. Si elle est vraie, c'est-à-dire non nulle,
seule l'expression expr2 est évaluée sinon c'est
l'expression expr3 qui est évaluée.
La valeur de l'expression conditionnelle est la valeur de l'une des expressions expr2 ou expr3 suivant que l'expression expr1 est vraie ou fausse.
Exemple
int i;
int indic;
int a, b;
int c;
...
indic = (i < 0) ? 0 : 100;
c += (a > 0 && a <= 10) ? ++a : a/b;
c = a > b ? a : b;
Exemple
int i;
float r;
double dble, d;
char *ptr;
char buffer[100];
d = (i = 1, r = 2.718f, dble = 2.7182818);
r = (float)(ptr = buffer, i = 10);
Opérateur d'indexation
Cet opérateur ([ ]) permet de référencer les différents éléments d'un vecteur. C'est un opérateur binaire dont l'un des opérandes est un identificateur de vecteur ou un pointeur et l'autre opérande un entier.
Si p est un pointeur ou un identificateur de vecteur et n un entier, alors l'expression p[n] désigne le (n+1)e élément à partir de l'adresse p, c'est-à-dire l'élément situé à l'adresse p+n.
Cette expression est donc équivalente à *(p+n).
Exemple
char buffer[4096];
char *ptr;
int i;
int ta[5], tb[5], tc[5];
buffer[10] = 'a';
*(buffer + 9) = 'b';
i = 0;
buffer[i++] = 'c';
i += 15;
ptr = buffer + i;
ptr[0] = 'd';
*++ptr = 'e';
*ta = 1, *tb = 2;
tc[0] = ta[0] + tb[0];
Opérateur d'appel de fonction
C'est l'opérateur () qui permet de déclencher l'appel à la fonction dont le nom précède. Ce nom peut être soit un identificateur de fonction, soit un pointeur de fonction.
A l'intérieur de ces parenthèses, apparaît éventuelle\-ment une liste d'expressions appelées paramètres qui sont évaluées puis transmises à la fonction. L'ordre d'évaluation de ces expressions est indéter\-miné.
Exemple
char *f( int i, float x );
char *(*pf)( int i, float x );
char *ptr;
int i;
float x;
i = 2, x = 1.;
ptr = f( i, x );
pf = f;
i = 10, x *= 2.;
ptr = (*pf)( i, x );
/* non portable */
ptr = (*pf)( i++, x = (float)i );
/* */
i++;
ptr = (*pf)( i, x = (float)i );
Opérateurs de sélection de champ
L'opérateur . permet d'accéder aux champs d'une structure ou d'une union. Il est binaire. Le 1er opérande doit être une structure ou une union et le 2e opérande doit désigner l'un de ses champs.
Le type et la valeur de l'expression op1.op2 sont ceux de op2.
Pour accéder à un champ ch d'une structure ou d'une union pointée par un pointeur ptr, on écrira l'expression (*ptr).ch qui est équivalente à ptr->ch.
Exemple
typedef struct cellule
{
int n;
char *c;
int nb_parm;
char **parm;
} Cel, *PtrCel;
typedef struct boite
{
int nb_boite;
PtrCel cel;
} Boite;
Cel c1;
PtrCel ptr_cel;
Boite b[10], *p_boite;
c1.n = 10;
c1.c = "nom de la cellule";
ptr_cel = &c1 ;
ptr_cel->n = 20;
b[0].cel = ptr_cel;
b->nb_boite = 5;
b[0].cel->n++;
b->cel->n++;
b[1] = b[0]; p_boite = &b[1];
p_boite->cel = (PtrCel)0;
Exemple
Cardinal nb_elements; /* niveau 0 */
size_t taille; /* niveau 0 */
main()
{
int i, j; /* niveau 1 */
char c; /* niveau 1 */
{
Complexe c1, *c2; /* niveau 2 */
int i; /* niveau 2 */
if (...)
{
char car; /* niveau 3 */
...
}
}
...
}
int ent; /* niveau 0 */
void f(void)
{
long i; /* niveau 1 */
...
}
Il existe quatre classes de mémorisation :
Exemples
extern int i;
static unsigned int j;
register int n;
Par défaut, en l'absence d'attribut de classe mémoire, une variable interne est temporaire et reçoit l'attribut auto. Ce type de variable est alloué dynamiquement dans le «~stack~» ou pile d'exécution (pile de type LIFO).
Lorsqu'une variable est très utilisée, il peut être avantageux de demander au compilateur qu'elle soit, dans la mesure du possible, rangée dans un registre de la machine. Cette possibilité, qui ne peut s'appliquer qu'à des variables temporaires, ne sera satisfaite que s'il existe des registres disponibles au format de la donnée. C'est l'attribut register spécifié à la déclaration qui permet de formuler une telle demande.
Exemple
main()
{
int a = 1, b = 2;
a++, b++;
{
char b = 'A'; int x = 10;
a++, b++, x++;
{
int a = 100, y = 200;
a++, b++, x++, y++;
{
char a = 'L'; int z = -5;
a++, b++, x++, y++, z++;
}
a++, b++, x++, y++;
}
a++, b++, x++;
}
}
A - Programme monofichier
La portée d'une variable externe est l'ensemble du source à partir de l'endroit où celle-ci a été déclarée.
Cela implique que seules les fonctions définies après la déclaration des variables externes peuvent y accéder.
Dans une fonction, une variable externe est masquée lorsqu'elle subit une redéclaration au sein de cette fonction.
Exemple
int i = 10;
main()
{
... /* la variable externe r
n'est pas visible. */
{
int i = 20; /* dans ce bloc la variable
externe i est masquée. */
...
}
}
float r = 3.14;
void f(...)
{
...
{
double r; /* dans ce bloc la variable
externe r est masquée. */
...
}
...
}
Dans une fonction, il est possible de rendre une variable externe visible si elle ne l'était pas déjà. Il suffit de la référencer en indiquant l'attribut extern.
Exemple
double y = 10.123;
...
main()
{
int y; /* y déclarée en externe
est masquée. */
...
{
extern double y; /* On rend la variable
externe y a nouveau
accessible. */
...
}
...
}
Exemple
main()
{
... /* la variable externe z
n'est pas visible */
}
void f(void)
{
extern float z; /* la variable externe
z est visible dans f. */
...
}
int g(void)
{
... /* la variable externe z
n'est pas visible */
}
float z = 2.0;
float h(int i)
{
/* la variable externe z
est visible dans h ainsi que
dans les fonctions suivantes. */
}
B - Programme multifichiers
L'unité de compilation est le fichier. Les différents fichiers sources constituant une application sont donc traités de façon indépendante par le compilateur.
Lorsque l'on a besoin de partager une variable entre ces différents fichiers, on devra allouer son emplacement mémoire dans un seul de ces fichiers et la référencer dans les autres. On parlera de définition lorsque la mémoire est allouée et de déclaration lors d'une référence.
Tous les compilateurs (avant et après la norme) considèrent :
Exemple
source1.c
main()
{
...
}
extern int i; /* déclaration de la
variable externe i. */
void f(int a)
{
...
}
source2.c
int i = 11; /* définition de la
variable externe i. */
int g(void)
{
...
}
source3.c
float h(void)
{
...
}
extern int i; /* déclaration de la
variable externe i. */
void func(void)
{
...
}
De plus la norme dit qu'une variable sans l'attribut extern
et sans initialisation fait l'objet d'une définition
potentielle.
Si pour une variable n'apparaissent que des définitions potentielles, l'une sera considérée comme définition et les autres comme déclarations. Cette variable sera initialisée avec des zéros binaires.
Exemple
sourceA sourceB sourceC
int x = 10; | extern int x; | extern int x;
extern int y; | extern int y; | extern int y;
int z; | extern int z; | extern int z;
int a; | | int a;
int b = 20; | int b = 21; |
int c; | int c = 30; |
| |
main() | int calcul(void) | int somme(void)
{ | { | {
... | ... | ...
} | } | }
On peut limiter la portée d'une variable au source au sein duquel elle est définie. Pour cela on indiquera l'attribut static au moment de sa définition.
Exemple
sourceA sourceB
static float r = 2.154; | void f2(void)
double dble = 17.89; | {
main() | ...
{ | }
... | extern double dble;
} | int f3(int i)
float f1(void) | {
{ | ...
... | }
} | static int n = 10;
| void f4(float r)
| {
| ...
| }
Syntaxe
type construction = expression;
L'initialisation des variables permanentes doit se
faire à l'aide d'expressions constantes :
Exemple
void f( void )
{
static int n = 10 ;
static char *ptr = "Aix-en-Provence" ;
static int *p = &n ;
static int etat = 0x1 «~5 ;
int flag = etat; <==> int flag;
flag = etat;
...
}
L'initialisation des vecteurs, des structures ou des
unions s'effectue au moyen de listes de valeurs entre
accolades :
{val1, val2, ..., valn}
Si l'élément d'un vecteur est lui-même un vecteur on applique
récursivement la notation précédente.
L'initialisation des vecteurs doit se faire au moyen
d'expressions constantes.
Seule la première composante d'une union peut être initialisée.
Exemple
int tab1[5] = { 2, 6, 8, 10, 17};
int tab2[] = { 3, 7, 10, 18, 16, 3, 1};
char v1[] = "Wolfgang Amadeus Mozart";
char v2[] = "musique";
char v3[] = { 'm', 'u', 's', 'i',
'q', 'u', 'e', '\0' };
char *p = "musique";
typedef struct date
{
int jour, mois, annee;
} Date;
typedef struct
{
char sexe;
char *nom;
Date annee_naiss;
} Individu;
Individu tab[] = {
{ 'F', "France Nathalie", { 1, 7, 65 } },
{ 'M', "Deneau Michel", { 8, 10, 61 } }
};
union donnees
{
int i;
float r;
} u = {2};
Exemple
int tab[3][4] = {
{1, 2, 7, 11},
{2, 3, 12, 13},
{4, 8, 10, 11}
};
int t1[][4] = {
{1},
{2, 3},
{4, 8, 10}
};
int t2[3][4] = {1, 0, 0, 0, 2, 3, 0, 0,
4, 8, 10, 0};
int t3[][3] = {0, 1, 2, 3, 8, 9, 9, 1};
int t4[2][3][4] = {
{
{1, 2, 3, 8},
{3, 2},
{1}
},
{
{3, 4, 9},
{2}
} };
La déclaration d'une fonction s'effectue au moyen de son prototype.
Lors de l'appel à une fonction, pour que le compilateur connaisse le type de la valeur qu'elle retourne et puisse vérifier le nombre ainsi que le type des arguments transmis, il est nécessaire qu'il ait visibilité sur le prototype de cette fonction.
Cette visibilité existe lorsque :
Une fonction ne peut être contenue dans une autre fonction, de ce fait toutes les fonctions sont externes. C'est pourquoi préciser l'attribut extern, que ce soit lors de la déclaration ou lors de la définition de la fonction, n'apporte aucune information supplémentaire.
A l'instar des variables, une fonction peut n'être connue que dans le fichier dans lequel elle a été définie. Pour cela on indiquera l'attribut static lors de sa définition.
Exemple
sourceA sourceB
float f(double d); | int g(void)
main() | {
{ | int i;
float r; | int j;
double d; | static int h(int i);
| ...
r = f(d); | j = h(i);
... | }
} | void func(int i)
static float f(double d) | {
{ | /* la fonction h n'est
int g(void); | pas visible ici. */
int i; | }
| static int h(int i)
i = g(); | {
... | ...
} | }
Une instruction élémentaire est une expression terminée par un ;.
Contrairement aux expressions, les instructions n'ont ni type ni valeur. Lorsqu'on forme une instruction à partir d'une expression la valeur de cette dernière est perdue.
N'importe quelle expression peut former une instruction, même lorsqu'elle ne génère pas d'effet de bord.
Une instruction composée ou bloc est un ensemble d'instructions élémentaires et/ou composées, précédées éventuellement de déclarations, délimité par des accolades.
Exemple
#include <stdio.h>
#include <math.h>
main()
{
int i = 10;
double r = acos(-1.);
i *= 2;
{
double cosh_pi;
cosh_pi = (exp(r) + exp(-r)) / 2;
printf( "cosh_pi : %f\n", cosh_pi );
}
}
Les structures de contrôle sont les tests,
les boucles et l'aiguillage.
.
6.2.1 - Les tests : syntaxe
if (expression)
partie-alors
[else
partie-sinon]
La partie-alors et la partie-sinon peuvent être
indifféremment une instruction élémentaire ou composée.
La partie-alors sera exécutée si la valeur de l'expression entre parenthèses est non nulle. Sinon, si le test comporte une partie-sinon c'est celle-ci qui sera exécutée.
Exemple
char buffer[2048];
void f( void )
{
static char *p = (char *)0;
if( ! p )
p = buffer;
else
{
*p = '1';
p++;
}
}
Si plusieurs tests sont imbriqués, chaque partie-sinon est reliée au if le plus proche qui n'est pas déjà associé à une partie-sinon.
Exemple
if( x > 0 ) if( x > 0 )
ecrire( "positif" ); ecrire( "positif" );
else if( x < 0 ) <==> else
ecrire( "négatif" ); {
else if( x < 0 )
ecrire( "nul" ); ecrire( "négatif" );
else
ecrire( "nul" );
}
6.2.2 - Les boucles «~tant-que~» : syntaxe
while (expression)
corps-de-boucle
do
corps-de-boucle
while (expression);
La partie corps-de-boucle peut être soit une instruction
élémentaire soit une instruction composée.
Dans la boucle while le test de continuation s'effectue avant d'entamer le corps-de-boucle qui, de ce fait, peut ne jamais s'exécuter.
Par contre dans la boucle do-while ce test est effectué après le corps-de-boucle, lequel sera alors exécuté au moins une fois.
Exemple
#include <stdio.h>
main()
{
int chiffre = 0;
printf( "Boucle \"while\"\n\n" );
while( chiffre )
{
printf( " %d", chiffre++ );
if( ! (chiffre%5) )
printf("\n");
}
printf( "Boucle \"do-while\"\n\n" );
do
{
printf( " %3d", ++chiffre );
if( ! (chiffre%5) )
printf("\n");
if( chiffre == 100 )
chiffre = 0;
}
while( chiffre );
}
6.2.3 - La boucle «~pour~» : syntaxe
for ([expr1]; [expr2]; [expr3])
corps-de-boucle
L'expression expr1 est évaluée une seule fois, au début
de l'exécution de la boucle.
L'expression expr2 est évaluée et testée avant chaque passage dans la boucle.
L'expression expr3 est évaluée après chaque passage.
Ces 3 expressions jouent respectivement le rôle :
Exemple
main()
{
int tab[] = {1, 2, 9, 10, 7, 8, 11};
int i, j;
char buffer[] = "Voici une chaîne"
" qui se termine "
"par un blanc ";
char *p;
int t[4][3];
for( i=0; i < sizeof tab / sizeof tab[0]; i++ )
printf( "tab[%d] = %d\n", i, tab[i] );
for( p=buffer; *p; p++ )
;
*--p = '\0';
printf( "buffer : %s$\n", buffer );
for( i=0; i < 4; i++ )
for( j=0; j < 3; j++ )
t[i][j] = i + j;
}
6.2.4 - L'aiguillage
L'instruction switch définit un aiguillage qui permet d'effectuer un branchement à une étiquette de cas en fonction de la valeur d'une expression.
Syntaxe
switch (expression)
{
case etiq1 :
[ liste d'instructions ]
case etiq2 :
[ liste d'instructions ]
...
case etiqn :
[ liste d'instructions ]
[ default:
[ liste d'instructions ]]
}
Les étiquettes de cas (etiq1, etiq2, ..., etiqn) doivent être des expressions constantes.
Une fois le branchement à l'étiquette de cas correspondante effectué, l'exécution se poursuit, par défaut, jusqu'à la fin du bloc switch. L'instruction d'échappement break; permet de forcer la sortie du bloc.
L'expression indiquée au niveau du switch doit être de type entier.
Exemple
#include <stdio.h>
main()
{
char *buffer = "\nCeci est une chaîne\n"
"de caractères\tsur\n\n"
"plusieurs lignes.\n";
int NbCar = 0, NbEsp = 0, NbLignes = 0;
for( ; *buffer; buffer++, NbCar++ )
switch( *buffer )
{
case '\n': NbLignes++;
break;
case '\t':
case ' ' : NbEsp++;
default : break;
}
printf( "NbCar = %d, NbEsp = %d, NbLignes = %d\n",
NbCar, NbEsp, NbLignes );
}
Instruction continue;
Le rôle de l'instruction continue; est de forcer le passage à l'itération suivante de la boucle la plus proche.
Exemple
#include <stdio.h>
main()
{
char *buffer = "\nCeci est une chaîne\n"
"de caractères\tsur\n\n"
"plusieurs lignes.\n";
int NbCar = 0, NbEsp = 0, NbLignes = 0;
for( ; *buffer; buffer++ )
{
switch( *buffer )
{
case '\n': NbLignes++;
break;
case '\t': continue;
case ' ' : NbEsp++;
default : break;
}
NbCar++;
}
printf( "NbCar = %d, NbEsp = %d, NbLignes = %d\n",
NbCar, NbEsp, NbLignes );
}
Instruction break;
L'instruction break; permet de quitter la boucle ou l'aiguillage le plus proche.
Exemple
#include <stdio.h>
main()
{
char buffer[] = "Wolfgang Amadeus Mozart\n"
" est un musicien divin.\n";
char *p;
for( p=buffer; *p; p++ )
if( *p == '\n' )
{
*p = '\0';
break;
}
printf( "Nom : %s\n", buffer );
}
Instruction return;
Syntaxe
return [expression];
Cette instruction permet de sortir de la fonction qui la contient :
Exemple
#include <stdio.h>
int main()
{
char c;
char majus( char c );
void impression( char c );
...
c = majus( c );
impression( c );
return 0;
}
char majus( char c )
{
return c >= 'a' && c <= 'z' ?
c + 'A' - 'a' : c;
}
void impression( char c )
{
printf( "%c\n", c );
return;
}
Instruction go to;
Cette instruction sert à effectuer un transfert inconditionnel
vers une autre partie du programme.
Syntaxe
goto étiquette;
Etiquette fait référence à une instruction étiquetée.
On utilisera cette instruction avec parcimonie car elle nuit
à l'écriture de programme structuré.
Elle peut à la rigueur être utilisée lorsque l'on désire
sortir de plusieurs boucles imbriquées, ce que ne permet
pas l'instruction break;.
Exemple
#include <stdio.h>
main()
{
int tab[][4] = {1, 2, 8, 9, 10, 12, 1, 9, 5};
int i, j;
for( i=0; i < sizeof tab / sizeof tab[0]; i++ )
for( j=0; j < 4; j++ )
if( tab[i][j] == 10 )
goto trouve;
fprintf( stderr, "Elément non trouvé.\n" );
return 1;
trouve:
printf( "L'élément tab[%d][%d]"
" est égal à 10.\n", i, j );
return 0;
}
Un programme peut être interrompu au moyen de la fonction exit.
Syntaxe
exit(expression);
L'argument de cette fonction doit être un entier indiquant
le code de terminaison du processus.
Exemple
#include <stdio.h>
int valeur = 10;
int tab[][4] = { 1, 2, 8, 9, 10, 12,
1, 9, 5, 7, 15, 16 };
int recherche( void )
{
int i, j;
for( i=0; i < sizeof tab / sizeof tab[0]; i++ )
for( j=0; j < 4; j++ )
if( tab[i][j] == valeur )
{
printf( "L'élément tab[%d][%d]"
" est égal à %d.\n", i, j, valeur );
return i;
}
fprintf( stderr, "Elément non trouvé.\n" );
exit(1);
}
main()
{
int ligne = recherche();
...
return 0;
}
Ce préprocesseur exécute des instructions particulières appelées directives.
Ces directives sont identifiées par le caractère # en tête. Elles peuvent se continuer sur plusieurs lignes, chaque ligne à continuer étant terminée par le caractère \ suivi d'un return.
Une pseudo-constante est un identificateur
composé de lettres et de chiffres commençant par une lettre.
(Le caractère _ est considéré comme une lettre).
Syntaxe
#define identificateur [chaîne-de-substitution]
Le préprocesseur remplace tous les mots du fichier source
identiques à l'identificateur par la chaîne-de-substitution.
On préférera n'utiliser que des majuscules pour écrire ces identificateurs afin de les différencier des autres (variables, vecteurs, fonctions).
Exemple
Remarque
La directive #undef permet d'annuler la définition
d'une pseudo-constante.
Pseudo-constantes prédéfinies
La plupart des préprocesseurs reconnaissent les
pseudo-constantes prédéfinies suivantes :
Exemple
Remarques
Exemple
Exemple
La directive #include permet d'insérer le contenu
d'un fichier dans un autre.
Ce mécanisme est en général réservé à l'inclusion de fichiers
appelés fichiers en-tête contenant des déclarations
de fonctions, de variables externes, de pseudo-constantes et
pseudo-fonctions, de définition de types.
Ces fichiers sont traditionnellement suffixés par .h.
Syntaxe
Si le nom du fichier est spécifié entre <>, il
est recherché par défaut dans le répertoire
/usr/include.
Exemple
def.h
Il existe une bibliothèque standard de fichiers en-tête
nécessaires lors de l'appel de certaines fonctions :
Test d'existence d'une pseudo-constante
Ce sont les directives #ifdef et #ifndef
qui permettent de tester l'existence d'une pseudo-constante.
Syntaxe
Exemple
Syntaxe
Évaluation de pseudo-expressions
Il est possible de construire des expressions interprétables par
le préprocesseur à l'aide :
Syntaxe
Remarque
Si l'on désire mettre en commentaire une portion de programme,
la solution consistant à l'encadrer par les caractères
/* et */ ne marche pas si elle contient
elle-même des commentaires.
Une solution simple est de placer en tête de la région à commenter
la directive #if 0, et à la fin la directive
#endif /* 0 */.
Exemple
~~~~~~~~~~~~~~~~~~~~~~source.c
Dans les langages de programmation il existe deux techniques
de passage d'arguments :
Le langage C a choisi la 2e solution.
Si un argument doit être passé par adresse, c'est le
programmeur qui en prend l'initiative et ceci grâce à
l'opérateur d'adressage (&).
Exemple
Il convient d'être prudent lors de l'utilisation d'une fonction
retournant un pointeur.
Il faudra éviter l'erreur qui consiste à retourner l'adresse
d'une variable temporaire.
Exemple
Lorsqu'un vecteur est passé en argument, c'est donc l'adresse
de son 1er élément qui est transmise par valeur.
Exemple
Exemple
Exemple
Exemple
Exemple
Ces arguments sont constitués de :
Par convention :
Exemple
La commande a.out toto titi tata génère
la structure de données suivante :
Tableau des arguments
Exemple
Exemple
A l'appel de la fonction puissance de l'exemple précédent il y a :
Processus de passage d'arguments
Ce type de passage d'arguments permet d'écrire des fonctions
avec un nombre variable d'arguments.
Dans le prototype d'une telle fonction, on indiquera
les arguments suivis de :
Comme les arguments sont rangés de façon consécutive dans la
pile, le programmeur a la possibilité d'aller chercher
les arguments en surnombre.
Exemple
Pour ce faire, le dernier argument fourni à l'appel de la fonction
peut par exemple indiquer le nombre d'arguments en surnombre, ou
bien ceux-ci peuvent être suivis par un argument supplémentaire de
même type avec une valeur spéciale (valeur sentinelle).
Par contre la norme ne précise pas l'ordre dans lequel les arguments
doivent être empilés, cette méthode de la pile n'est donc
pas portable.
Pour assurer cette portabilité, chaque système propose des
pseudo-constantes et pseudo-fonctions
permettant au programmeur de gérer cette recherche.
Version Unix System V
Les pseudo-constantes et pseudo-fonctions
sont stockées dans le fichier en-tête varargs.h :
Exemple
Exemple
Version ANSI
Les pseudo-constantes et pseudo-fonctions
sont stockées dans le fichier en-tête stdarg.h :
Exemple
On ne pourra pas appliquer les opérateurs d'indirection et
d'auto-incrémentation, auto-décrémentation à un
pointeur générique.
Par contre, si p et q sont deux pointeurs, les
affectations :
Exemples
Exemples
Cette structure de données est un objet de type FILE.
Dans le programme, un flot sera déclaré de type
FILE *.
Les informations précédentes sont contenues dans le fichier
en-tête stdio.h.
Ce fichier contient, de plus, les déclarations des différentes
fonctions d'entrée-sortie, ainsi que la déclaration d'un vecteur
(_iob) de type FILE dont la dimension
est définie à l'aide d'une pseudo-constante.
Extrait du fichier stdio.h sur
IBM/RS6000
9.2.1 - Fonctions d'ouverture et de fermeture
L'acquisition d'un nouveau flot s'effectue par l'appel
à la fonction fopen.
La fonction fclose permet de le fermer.
Syntaxe
Un pointeur NULL, pseudo-constante définie comme
(void *)0 dans stdio.h, indique une fin anormale.
La fonction fclose retourne 0 en cas de succès,
-1 sinon.
Le 2e argument de la fonction fopen indique le mode
d'ouverture du fichier.
Certains systèmes font la distinction entre les fichiers texte
et binaire. Pour manipuler ces derniers, il suffit de rajouter
le caractère b dans la chaîne indiquant le mode d'ouverture.
Sous UNIX, il est ignoré car il n'existe aucune différence
entre un fichier binaire et un fichier de données quelconques.
Exemple
9.2.2 - Lecture et écriture par caractère
Les fonctions getc, fgetc et putc, fputc permettent
de lire ou écrire un caractère sur un flot donné.
getc et putc sont des pseudo-fonctions.
Syntaxe
Ces fonctions retournent soit le caractère traité, soit la
pseudo-constante EOF, définie comme -1 dans
le fichier stdio.h, en cas d'erreur (fin de fichier par
exemple).
Deux pseudo-fonctions feof et ferror,
définies dans le fichier stdio.h, permettent de tester,
respectivement, la fin de fichier et une éventuelle erreur
d'entrée-sortie sur le flot passé en argument.
Dans le cas d'une entrée-sortie au terminal, c'est le
retour chariot qui provoque l'envoi au programme de la mémoire
tampon rattachée au pilote /dev/tty.
Exemple
Exemples corrects
9.2.3 - Lecture et écriture de mots
Les fonctions getw et putw permettent de lire ou
écrire des mots.
Syntaxe
Exemple
9.2.4 - Lecture et écriture d'une chaîne de caractères
Les fonctions gets, fgets et puts, fputs permettent
de lire et écrire des chaînes de caractères.
Syntaxe
Exemple
9.2.5 - Lecture et écriture de blocs
Les fonctions fread et fwrite permettent de lire
et d'écrire des blocs de données tels des structures
ou des tableaux.
Syntaxe
Exemple
9.2.6 - Accès direct
Par défaut, les fonctions précédentes travaillent en
mode séquentiel. Chaque lecture ou écriture s'effectue
à partir d'une position courante, et incrémente cette position
du nombre de caractères lus ou écrits.
Les fonctions fseek et ftell permettent,
respectivement, de modifier et récupérer la position courante.
La fonction ftell retourne la position courante en octets.
La fonction fseek permet de la modifier :
Exemple
Exemple
9.2.7 - Entrées-sorties formatées
Les fonctions scanf, fscanf, sscanf et
printf, fprintf, sprintf permettent d'effectuer
des entrées-sorties de données avec conversions.
Syntaxe
Fonctions printf, fprintf, sprintf
Le paramètre format désigne une chaîne de caractères
comprenant :
Une spécification de conversion est constituée du caractère
%, suivie dans l'ordre :
Exemples
Fonctions scanf, fscanf, sscanf
Ces fonctions permettent d'effectuer des entrées formatées.
Les données lues sont converties suivant les
spécifications de conversions indiquées dans la chaîne
format, puis stockées dans les arguments successifs fournis
à sa suite. Ces arguments doivent être des pointeurs.
La valeur retournée correspond au nombre d'arguments correctement
affectés.
La chaîne format peut contenir :
Chaque champ est défini comme une chaîne de caractères qui s'étend
soit :
Une spécification de conversion est constituée du caractère
% suivi dans l'ordre :
Remarques
Les arguments correspondant aux spécifications :
De même les spécifications e, f, g peuvent
être précédées de la lettre l pour référencer un
double *.
Exemples
9.2.8 - Autres fonctions
La fonction freopen permet de redéfinir un flot déjà
initialisé. Elle est principalement utilisée avec les flots
stdin, stdout, stderr, ce qui correspond à une redirection
d'entrées-sorties.
La fonction fflush permet de forcer le vidage de la mémoire
tampon associée à un flot en sortie. Sur un flot en entrée l'effet
est imprévisible.
Syntaxe
Exemple
Il existe, de plus, deux fonctions permettant de convertir les
majuscules en minuscules et réciproquement :
Exemple
Exemple
Exemple
Exemple
Il existe d'autres fonctions qui agissent sur des tableaux de caractères
plutôt que des chaînes de caractères :
Exemple
Syntaxe
Exemple
Exemple
#define TAILLE 256
#define TAILLE_EN_OCTETS \
TAILLE*sizeof(int)
main()
{
int tab[TAILLE];
int i;
for( i=0; i
7.3 - Pseudo-fonctions
Les pseudo-fonctions ou macros sont
des substitutions paramétrables.
#define ABS(x) x>0 ? x : -x
#define NB_ELEMENTS(t) sizeof t / sizeof t[0]
#include <stdio.h>
#include <math.h>
main()
{
int tab[][2] = { 1, 2, 3, 9,
10, 11, 13, 16};
double r = -acos(-1.);
int i, j;
for( i=0; i
#define CARRE(x) x*x
main()
{
float x = 1.12;
/*
* Erreur : l'instruction suivante
* calcule 2*x+1 et non pas
* le carré de x+1.
*/
printf( "%f\n", CARRE(x+1) );
}
#define CARRE(x) (x)*(x)
#define MAX(a,b) ( (a) > (b) ? (a) : (b) )
main()
{
float x = 3.1, y = 4.15;
printf( "%f\n", CARRE(x+1) );
printf( "%f\n", MAX(x+10., y) );
/*
* Erreur : l'instruction suivante
* provoque une double
* incrémentation de x.
*/
y = CARRE(x++);
}
7.4 - Inclusion de fichiers
#include <nom-de-fichier>
#include "nom-de-fichier"
Si le nom du fichier est spécifié entre guillemets, il est
recherché dans le répertoire courant.
On peut indiquer d'autres répertoires de recherche au moyen
de l'option -I de la commande cc.
#define NbElements(t) sizeof t / sizeof t[0]
#define TAILLE 256
typedef struct cellule
{
int tab[TAILLE];
struct cellule *ptr;
} Cel;
typedef enum bool {Faux, Vrai} logical;
extern void init(int t[], logical imp);
Cel c;
#include "def.h"
main()
{
int t[TAILLE] = {1, 2, 9, 10};
logical imp = Vrai;
init( t, imp );
}
#include "def.h"
#include
7.5 - Compilation conditionnelle
#ifdef identificateur
partie-alors
[#else
partie-sinon]
#endif
#ifndef identificateur
partie-alors
[#else
partie-sinon]
#endif
def.h source.c
#ifdef TAILLE_BUF
# undef TAILLE_BUF
#endif /* TAILLE_BUF */
#define TAILLE_BUF 4096
#ifdef DEBUG | #define DEBUG
#define trace(s) \ | #include "def.h"
printf s | #include <stdio.h>
#else | main()
#define trace(s) | {
#endif/* DEBUG */ | int f(float x);
| int i;
| float r;
|
| i = f(r);
| trace(("%d\n", i));
| }
La définition d'une pseudo-constante ainsi que sa valorisation
peuvent se faire à l'appel de la commande cc au moyen
de l'option -D.
cc -Dpseudo-constante[=valeur] ...
On peut appliquer ce principe à la pseudo-constante
DEBUG de l'exemple précédent au lieu de la définir
dans le fichier source.c :
cc -DDEBUG source.c
#if pseudo-expression
partie-alors
[#else
partie-sinon]
#endif
#define TBLOC 256
#if !defined TAILLE
# define TAILLE TBLOC
#endif
#if TAILLE%TBLOC == 0
# define TAILLEMAX TAILLE
#else
# define TAILLEMAX ((TAILLE/TBLOC+1)*TBLOC)
#endif
static char buffer[TAILLEMAX];
main()
{
printf( "Taille du vecteur : %d caractères\n",
sizeof buffer );
}
.
8 - Les fonctions
8.1 - Passage arguments-paramètres
Des langages comme Fortran ou PL/1 ont choisi
la 1ère solution, tandis qu'un langage comme Pascal
offre les deux possibilités au programmeur.
#include <stdio.h>
void main()
{
int a, b, c;
void somme(int a, int b, int *c);
a = 3;
b = 8;
somme(a, b, &c);
printf("Somme de a et b : %d\n", c);
return;
}
void somme(int a, int b, int *c)
{
*c = a + b;
return;
}
8.2 - Fonction retournant un pointeur
#include <stdio.h>
void main()
{
char *p;
char *ini_car(void);
p = ini_car();
printf("%c\n", *p);
}
char *ini_car(void)
{
char c;
c = '#';
return(&c); <=== ERREUR
}
8.3 - Passage d'un vecteur comme argument
Un vecteur est une constante symbolique dont la valeur
est l'adresse de son 1er élément.
#define NbElements(t) sizeof t / sizeof t[0]
#include <stdio.h>
main()
{
int tab[] = { 1, 9, 10, 14, 18};
int somme(int t[], int n);
void impression(int *t, int n);
printf("%d\n", somme(tab, NbElements(tab)));
impression(tab, NbElements(tab));
}
int somme(int t[], int n)
{
int i, som=0;
for (i=0; i < n; i++) som += t[i];
return som;
}
void impression(int *t, int n)
{
int i=0, *p;
for (p=t; t-p < n; t++)
printf("t[%d] = %d\n", i++, *t);
}
#define NbElements(t) sizeof t / sizeof t[0]
#include <stdio.h>
main()
{
int tab[][5] = {
{ 4, 7, 1, 9, 6},
{ 5, 9, 3, 4, 2},
{ 2, 9, 5, 9, 13}
};
int somme(int (*t)[5], int n);
printf("Somme des éléments de tab : %d\n",
somme(tab, NbElements(tab)));
}
int somme(int (*t)[5], int n)
{
int i, som = 0;
int (*p)[5] = t;
for(; t-p < n; t++)
for (i=0; i < 5; i++)
som += (*t)[i];
return som;
}
#define DIM1 10
#define DIM2 4
#define DIM3 5
main()
{
int tab[DIM1][DIM2][DIM3];
void init(int (*t)[DIM3], int n);
int i, n = DIM2;
for(i=0; i < DIM1; i++)
init(tab[i], i);
}
void init(int (*t)[DIM3], int n)
{
int i, j;
for(i=0; i < DIM2; i++)
for(j=0; j < DIM3; j++) {
t[i][j] = 2*(i+n*DIM2);
*(*(t+i)+j) += 1;
}
}
8.4 - Passage d'une structure comme argument
La norme ANSI a introduit la possibilité de transmettre une
structure en argument d'une fonction, elle-même pouvant retourner
un tel objet.
#include <stdio.h>
#define NbElts(v) ( sizeof v / sizeof v[0] )
typedef struct
{
float v[6];
} Vecteur;
main()
{
Vecteur vec = { {1.34f, 8.78f, 10.f,
4.f, 22.12f, 3.145f} };
Vecteur inv;
Vecteur inverse( Vecteur vecteur, int n );
int i, n = NbElts(vec.v);
inv = inverse( vec, n );
for( i=0; i < n; i++ )
printf( "inv.v[%d] : %f\n", i, inv.v[i] );
}
Vecteur inverse( Vecteur vecteur, int n )
{
Vecteur w;
int i;
for( i=0; i < n; i++ )
w.v[i] = vecteur.v[i] ? 1./vecteur.v[i] : 0.f;
return w;
}
8.5 - Passage d'une fonction comme argument
Le nom d'une fonction est une constante symbolique dont la valeur
est un pointeur sur la 1ère instruction exécutable du code
machine de la fonction.
Passer une fonction en argument, c'est donc transmettre l'adresse,
par valeur, du début du code machine constituant cette fonction.
double integrale(double b_inf, double b_sup,
int pas, double (*f)(double));
double carre(double x);
int main()
{
double b_inf, b_sup, aire;
int pas;
b_inf = 1., b_sup = 6., pas = 2000;
aire = integrale(b_inf, b_sup, pas, carre);
printf("Aire : %f\n", aire);
return 0;
}
double integrale(double b_inf, double b_sup,
int pas, double (*f)(double))
{
double surface = 0., h;
int i;
h = (b_sup - b_inf)/pas;
for(i=0; i < pas; i++)
surface += h*(*f)(b_inf+i*h);
return surface;
}
double carre(double x) {return x*x;}
8.6 - Passage d'arguments à la fonction main
Lorsqu'un exécutable est lancé sous un interprète de commandes
({shell}), un processus est créé et son exécution commence
par la fonction main à laquelle des arguments sont
transmis après avoir été générés par le shell.
Les premier et dernier sont transmis sous forme de
vecteurs de pointeurs de caractères.
Les arguments précédents sont transmis à la fonction main
dans cet ordre.
#include <stdio.h>
main( int argc, char **argv, char **envp )
{
void usage(char *s);
if( argc != 3 )
usage( argv[0] );
for( ; *argv; argv++ )
printf( "%s\n", *argv );
for( ; *envp; envp++ )
printf( "%s\n", *envp );
}
void usage( char *s )
{
fprintf( stderr, "usage : %s arg1 arg2\n", s );
exit(1);
}
8.7 - Fonction avec un nombre variable d'arguments
Lors de l'appel à une fonction, le compilateur génère une liste
des arguments fournis qu'il empile dans la pile d'exécution
rattachée au processus (pile de type LIFO).
int puissance(int n, int x)
{
int p = 1;
while(n--) p *= x;
return p;
}
void main()
{
int m, k, r;
k = 4; m = 2;
r = puissance(k+3, m);
}
void fonction(int a, ...);
main()
{
int i = 10, j = 11, k = 12;
printf("Avant appel fonction i = %d\n", i);
printf("Avant appel fonction j = %d\n", j);
printf("Avant appel fonction k = %d\n", k);
fonction(i, j, k);
}
void fonction(int a, ...)
{
printf("Valeur de a = %d\n", a);
printf("Récupération de j = %d\n", *(&a + 1));
printf("Récupération de k = %d\n", *(&a + 2));
}
Cette technique de récupération d'arguments dans la pile,
nécessite cependant que le programmeur connaisse les types des
arguments en surnombre et qu'il ait un moyen d'arrêter la recherche.
#include <stdio.h>
#include <varargs.h>
main()
{
float moyenne();
printf("moyenne = %f\n", moyenne(4, 1, 2, 3, 4));
printf("moyenne = %f\n",
moyenne(5, 1, 2, 3, 4, 5));
}
float moyenne(nombre, va_alist)
int nombre;
va_dcl
{
int somme = 0, i;
va_list arg;
va_start(arg);
for(i=0; i < nombre; i++)
somme += va_arg(arg, int);
va_end(arg);
return somme/nombre;
}
#include <stdio.h>
#include <varargs.h>
main()
{
float moyenne();
printf("moyenne = %f\n",
moyenne(4, 1.f, 2.f, 3.f, 4.f));
printf("moyenne = %f\n",
moyenne(5, 1.f, 2.f, 3.f, 4.f, 5.f));
}
float moyenne(nombre, va_alist)
int nombre;
va_dcl
{
float somme = 0.f;
int i;
va_list arg;
va_start(arg);
for(i=0; i < nombre; i++)
somme += va_arg(arg, double); ===> surtout
va_end(arg); pas float!
return somme/nombre;
}
Les arguments en surnombre sont symbolisés par ... dans
le prototype de la fonction.
#include <stdio.h>
#include <stdarg.h>
main()
{
float moyenne(int nombre, ...);
printf("moyenne = %f\n", moyenne(4, 1, 2, 3, 4));
printf("moyenne = %f\n",
moyenne(5, 1, 2, 3, 4, 5));
}
float moyenne(int nombre, ...)
{
int somme = 0, i;
va_list arg;
va_start(arg, nombre);
for(i=0; i < nombre; i++)
somme += va_arg(arg, int);
va_end(arg);
return somme/nombre;
}
Retour début
.
9 - La bibliothèque standard
9.1 - Notion de pointeur générique
La norme a défini le type void * ou pointeur générique
afin de faciliter la manipulation des pointeurs et des objets pointés
indépendamment de leur type.
sont toutes deux correctes si l'un au moins des deux pointeurs
p ou q est de type void *, quel que soit
le type de l'autre pointeur.
int x[5], i, *k;
float *r;
void *p;
void *q;
p = &x[0]; /* correct */
*p = ... /* interdit */
q = p + 1; /* interdit */
r = p; /* correct */
p = r; /* correct */
p[1] = ...; /* interdit */
void echange (void *p, void *q)
{
void *r;
r = *(void **)p;
*(void **)p = *(void **)q;
*(void **)q = r;
}
main()
{
int *i1, *i2;
float *f1, *f2;
double *d1, *d2;
...
echange(&i1, &i2);
echange(&f1, &f2);
echange(&d1, &d2);
}
9.2 - Entrées-sorties de haut niveau
Les entrées-sorties de haut niveau intègrent deux mécanismes
distincts :
Toute opération d'entrée-sortie se fera par l'intermédiaire
d'un flot (stream) qui est une structure de données
faisant référence à :
Trois flots sont prédéfinis au lancement d'un processus :
#define _NIOBRW 20
extern FILE _iob[_NIOBRW];
#define stdin (&_iob[0])
#define stdout (&_iob[1])
#define stderr (&_iob[2])
FILE *fopen(const char *file, const char *type);
int fclose(const FILE *flot);
La fonction fopen retourne un pointeur sur le 1er élément
libre du vecteur _iob s'il en existe, sinon sur une zone
de type FILE allouée dynamiquement.
Accès
Paramètre
Position
Comportement
si le fichier
existesi le fichier
n'existe pas
lecture r début . erreur
écriture w
a
début
fin mis à zéro création
création
lecture
et
écriture
r+
w+
a+
début
début
fin
mis à zéro
erreur
création
création
#include <stdio.h>
main()
{
FILE * flot;
if( (flot = fopen( "donnees", "r" )) == NULL )
{
fprintf( stderr, "Erreur à l'ouverture\n" );
exit(1);
}
...
...
fclose( flot );
}
int getc(FILE *Stream)
int fgetc(FILE *Stream)
int putc(int c, FILE *Stream)
int fputc(int c, FILE *Stream)
Il existe deux pseudo-fonctions supplémentaires :
#include <stdio.h>
main()
{
char c; <=== Attention Erreur !
----------------
while( (c = getchar()) != EOF )
putchar(c);
}
#include <stdio.h>
main()
{
int c;
while( (c = getchar()) != EOF )
putchar(c);
}
#include <stdio.h>
main()
{
int c;
c = getchar();
while( ! ferror(stdin) &&
! feof(stdin) )
{
putchar(c);
c = getchar();
}
}
int getw(FILE *flot)
int putw(int c, FILE *flot)
#include <stdio.h>
#define DIM 100
main()
{
FILE *flot;
int tab[DIM];
int i;
if( (flot = fopen( "resultat", "w" )) == NULL )
{
perror("fopen");
exit(1);
}
for( i=0; i < DIM; i++ )
{
tab[i] = i*i;
putw( tab[i], flot );
}
fclose( flot );
}
char *gets(char *string)
int puts(char *string)
char *fgets(char *string, int nombre, FILE *flot)
int fputs(char *string, FILE *flot)
Le caractère \0 est ensuite
ajouté en fin de chaîne.
Dans le deuxième cas le retour chariot est stocké
dans la chaîne.
Les fonctions gets, fgets renvoient la chaîne lue ou
le pointeur NULL si fin de fichier.
Les fonctions puts, fputs renvoient le nombre de caractères
écrits ou EOF si erreur.
#include <stdio.h>
main()
{
char *mus1 = "Wolfgang Amadeus Mozart\n";
char *mus2 = "Ludwig van Beethoven\n";
char buffer[BUFSIZ+1];
FILE *f;
if( (f = fopen( "musiciens", "w" )) == NULL )
{
perror( "fopen" );
exit(1);
}
fputs( mus1, f ); fputs( mus2, f );
fclose(f);
if( (f = fopen( "musiciens", "r" )) == NULL )
{
perror( "fopen" );
exit(2);
}
while( fgets( buffer, sizeof(buffer), f ) )
fputs( buffer, stdout );
fclose(f);
puts( "\nExecution terminée." );
}
size_t fread(void *p, size_t t, size_t n, FILE *f)
size_t fwrite(void *p, size_t t, size_t n, FILE *f)
Ces fonctions retournent le nombre de blocs traités. Utiliser
les pseudo-fonctions feof et ferror
pour tester la fin de fichier et une erreur d'entrée-sortie.
#include <stdio.h>
#define NbElt(t) ( sizeof t / sizeof t[0] )
main() {
typedef struct { int n; float t[10]; char c;
} Donnee;
Donnee s1 = { 1, { 1., 2., 3.}, 'a'};
Donnee s2[] = { {4, {10., 32., 3.}, 'z'},
{5, { 2., 11., 2., 4.}, 'h'} };
FILE *f, *f_sauve; Donnee s;
if( (f = fopen( "donnee", "w" )) == NULL )
perror("fopen"), exit(1);
fwrite( &s1, sizeof(Donnee), 1, f );
fwrite( s2, sizeof(Donnee), NbElt(s2), f );
fclose(f);
if( (f = fopen( "donnee", "r" )) == NULL ||
(f_sauve = fopen( "sauvegarde", "w" )) == NULL )
perror("fopen"), exit(2);
fread( &s, sizeof(Donnee), 1, f );
while( ! feof(f) )
{
fwrite( &s, sizeof(Donnee), 1, f_sauve );
fread( &s, sizeof(Donnee), 1, f );
}
fclose(f); fclose(f_sauve);
}
int fseek(FILE *f, long decalage, int position);
long ftell(FILE *f);
#include <stdio.h>
main( int argc, char **argv )
{
FILE *f;
void usage( char *s );
if( argc != 2 )
usage( argv[0] );
if( (f = fopen( argv[1], "r" )) == NULL )
{
perror( "fopen" );
exit(2);
}
fseek( f, 0L, SEEK_END );
printf( "Taille(octets) : %d\n", ftell(f) );
fclose(f);
}
void usage(char *s)
{
fprintf( stderr, "usage : %s fichier\n", s );
exit(1);
}
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
main( int argc, char **argv )
{
void conversion( char *nom, char *mode );
FILE *f;
char nom[100];
char *mus[] = { "Frédéric Chopin\n",
"Maurice Ravel\n", };
int n;
if ( argc != 2 ) exit( 1 );
if ( ( f = fopen( argv[1], "r+" ) ) == NULL )
{
if ( errno != ENOENT ||
( f = fopen( argv[1], "w+" ) ) == NULL )
{
perror( "fopen" );
exit( 2 );
}
}
fgets( nom, 100, f );
while ( !feof( f ) )
{
fseek( f, -1L*strlen(nom), SEEK_CUR );
conversion( nom, "majuscule" );
fputs( nom, f );
fseek( f, 0L, SEEK_CUR );
fgets( nom, 100, f );
}
for( n=0; n < sizeof mus / sizeof mus[0]; n++ )
fputs( mus[n], f );
fclose( f );
}
void conversion( char *nom, char *mode )
{
char *ptr;
int (*conv)(int);
if ( !strcmp( mode, "majuscule" ) )
conv = toupper;
else if ( !strcmp( mode, "minuscule" ) )
conv = tolower;
for( ptr=nom; *ptr++=conv(*ptr); )
;
return;
}
int scanf(const char *format [, ...])
int fscanf(FILE *f, const char *format [, ...])
int sscanf(const char *buffer,
const char *format [, ...])
int printf(const char *format [, ...])
int fprintf(FILE *f, const char *format [, ...])
int sprintf(char *buffer,
const char *format [, ...])
Après les conversions effectuées, cette chaîne est reproduite :
Les spécifications de conversions portent successivement
sur les arguments passés à la suite du paramètre format.
Dans ce cas, la précision indique le
nombre maximun de chiffres significatifs.
printf("|%d|\n", 1234); |1234|
printf("|%-d|\n", 1234); |1234|
printf("|%+d|\n", 1234); |+1234|
printf("|% d|\n", 1234); | 1234|
printf("|%10d|\n", 1234); | 1234|
printf("|%10.6d|\n", 1234); | 001234|
printf("|%10.2d|\n", 1234); | 1234|
printf("|%.6d|\n", 1234); |001234|
printf("|%06d|\n", 1234); |001234|
printf("|%.2d|\n", 1234); |1234|
printf("|%*.6d|\n", 10, 1234); | 001234|
printf("|%*.*d|\n", 10, 6, 1234); | 001234|
printf("|%x|\n", 0x56ab); |56ab|
printf("|%#x|\n", 0x56ab); |0x56ab|
printf("|%X|\n", 0x56ab); |56AB|
printf("|%#X|\n", 0x56ab); |0X56AB|
printf("|%f|\n",1.234567890123456789e5);
|123456.789012|
printf("|%.4f|\n",1.234567890123456789e5);
|123456.7890|
printf("|%.15f|\n",1.234567890123456789e5);
|123456.789012345670000|
printf("|%15.4f|\n",1.234567890123456789e5);
| 123456.7890|
printf("|%e|\n",1.234567890123456789e5);
|1.234568e+05|
printf("|%.4e|\n",1.234567890123456789e5);
|1.2346e+05|
printf("|%.18e|\n",1.234567890123456789e5);
|1.234567890123456700e+05|
printf("|%18.4e|\n",1.234567890123456789e5);
| 1.2346e+05|
printf("|%.4g|\n",1.234567890123456789e-5);
|1.235e-05|
printf("|%.4g|\n",1.234567890123456789e+5);
|1.235e+05|
printf("|%.4g|\n",1.234567890123456789e-3);
|0.001235|
printf("|%.8g|\n",1.234567890123456789e5);
|123456.79|
#include <stdio.h>
main()
{
char *chaine = "Wolfgang Amadeus Mozart";
printf("|%s|\n", chaine); ===> 1)
printf("|%.16s|\n", chaine); ===> 2)
printf("|%-23.16s|\n", chaine); ===> 3)
printf("|%23.16s|\n", chaine); ===> 4)
}
1) |Wolfgang Amadeus Mozart|
2) |Wolfgang Amadeus|
3) |Wolfgang Amadeus |
4) | Wolfgang Amadeus|
Les données en entrée sont découpées en champs.
Le caractère \0 est ensuite
ajouté en fin de chaîne.
On peut faire précéder les spécifications d, i,
o, x, u par la lettre h ou
l pour référencer un short * ou un long *.
#include <stdio.h>
main()
{
int i;
float x, y;
char buffer[BUFSIZ];
char *p = "12/11/94";
int jour, mois, annee;
scanf( "%d%f%f%*c", &i, &x, &y );
printf( "i = %d, x = %f, y = %f\n", i, x, y );
scanf( "%[^\n]%*c", buffer );
while( ! feof(stdin) )
{
fprintf( stderr, "%s\n", buffer );
scanf( "%[^\n]%*c", buffer );
}
sscanf( p, "%d/%d/%d", &jour, &mois, &annee );
printf( "jour : %d\n", jour );
printf( "mois : %d\n", mois );
printf( "annee : %d\n", annee );
}
#include <stdio.h>
main()
{
char mois[10], buffer[BUFSIZ];
int quantite;
double prix;
FILE *f;
if( (f = fopen( "donnees", "r" )) == NULL )
perror( "fopen" ), exit(1);
fgets( buffer, sizeof(buffer), f );
while( ! feof(f) )
{
sscanf( buffer, "%s%d%lf",
mois, &quantite, &prix );
printf( "mois : %s, qte : %d, prix : %f\n",
mois, quantite, prix );
fgets( buffer, sizeof(buffer), f );
}
fseek( f, 0L, SEEK_SET );
fscanf( f, "%s%d%lf", mois, &quantite, &prix );
while( ! feof(f) )
{
printf( "mois : %s, qte : %d, prix : %f\n",
mois, quantite, prix );
fscanf( f, "%s%d%lf", mois, &quantite, &prix );
}
fclose(f);
}
FILE *freopen(char *fichier, char *mode, FILE *flot);
int fflush(FILE *flot);
#include <stdio.h>
main( int argc, char **argv )
{
void usage( char *s );
if( argc != 2 )
usage( argv[0] );
if( freopen( argv[1], "w", stdout ) == NULL )
{
perror( "freopen" );
exit(2);
}
printf( "Ce message est redirigé ");
printf( "dans le fichier ");
printf( "dont le nom est ");
printf( "passé en argument.\n ");
}
void usage(char *s)
{
fprintf(stderr, "usage : %s fichier\n", s);
exit(1);
}
9.3 - Manipulation de caractères
Le fichier en-tête ctype.h contient des déclarations
de fonctions permettant de tester les caractères. Elles admettent
un argument de type entier et retourne un entier :
Les caractères imprimables sont compris entre 0x20 et
0x7e, les caractères de contrôle sont compris entre
0 et 0x1f ainsi que 0x7f.
#include <stdio.h>
#include <ctype.h>
main()
{
int c;
int NbMaj = 0;
int NbMin = 0;
int NbNum = 0;
int NbAutres = 0;
while( (c=getchar()) != EOF )
if( isupper(c) )
NbMaj++;
else if( islower(c) )
NbMin++;
else if( isdigit(c) )
NbNum++;
else
NbAutres++;
printf( "NbMaj : %d\n", NbMaj );
printf( "NbMin : %d\n", NbMin );
printf( "NbNum : %d\n", NbNum );
printf( "NbAutres : %d\n", NbAutres );
}
9.4 - Fonctions de conversions
Le fichier en-tête stdlib.h contient des déclarations de
fonctions permettant la conversion de données de type chaîne
de caractères en données numériques :
Si la valeur retournée est trop grande, la fonction
retourne les pseudo-constantes LONG_MAX
ou LONG_MIN suivant le signe du résultat,
est équivalente à strtol mis à part que le résultat
est de type unsigned long, et que la valeur de retour,
en cas d'erreur, est la pseudo-constante
ULONG_MAX définie dans le fichier en-tête
limits.h.
#include <stdio.h>
#include <stdlib.h>
main()
{
char *s = " 37.657a54";
char *cerr;
printf( "%f\n", strtod( s, &cerr ) ); ===> 37.657
if( *cerr != '\0' )
fprintf( stderr,
"Caractère erroné : %c\n", *cerr );
s = "11001110101110";
printf( "%ld\n", strtol( s, NULL, 2 ) ); ===> 13230
s = "0x7fff";
printf( "%ld\n", strtol( s, NULL, 0 ) ); ===> 32767
s = "0777";
printf( "%ld\n", strtol( s, NULL, 0 ) ); ===> 511
s = "777";
printf( "%ld\n", strtol( s, NULL, 0 ) ); ===> 777
}
9.5 - Manipulation de chaînes de caractères
Le fichier en-tête string.h contient des déclarations
de fonctions permettant la manipulation de chaînes de caractères :
Le type size_t est un alias du type
unsigned long.
#include <stdio.h>
#include <string.h>
main( int argc, char **argv )
{
char *parm1, *parm2, buffer[BUFSIZ];
if( argc != 3 ) usage( argv[0] );
parm1 = strdup( argv[1] );
parm2 = strdup( argv[2] );
strcat( strcpy( buffer, "Résultat de la "
"concaténation : " ),
parm1 );
strcat( strcat( buffer, parm2 ), "\n" );
printf( "%s", buffer );
sprintf( buffer, "%s%s%s\n", "Résultat de la "
"concaténation : ",
parm1, parm2 );
printf( "%s", buffer );
free( parm1 ); free( parm2 );
}
void usage( char *s )
{
fprintf( stderr, "usage : %s ch1 ch2.\n", s );
exit(1);
}
#include <stdio.h>
#include <string.h>
main( int argc, char **argv )
{
void usage( char *s );
char *s = "/usr/include/string.h", *p;
int NbSlash = 0;
if( argc != 3 ) usage( argv[0] );
if( ! strcmp( argv[1], argv[2] ) )
printf( "Les 2 arguments sont identiques.\n" );
else if( strcmp( argv[1], argv[2] ) > 0 )
printf( "arg1 > arg2\n" );
else printf( "arg1 < arg2\n" );
for( p = s-1; p = strchr( ++p, '/' ); NbSlash++ )
;
printf( "La chaîne s contient %d\n", NbSlash );
printf( "slashs sur %d caractères.\n", strlen(s) );
}
void usage( char *s )
{
fprintf( stderr, "usage : %s ch1 ch2.\n", s );
exit(1);
}
si c n'y figure pas,
#include <stdio.h>
#include <string.h>
main()
{
char buffer[100];
char tab[] = "Voici\0une chaîne qui"
"\0\0contient\0des"
"\0caractères \"null\".";
char *p, *ptr;
int taille = sizeof tab / sizeof tab[0];
int n;
memset( buffer, ' ', 100 );
memcpy( buffer, tab, taille );
n = --taille;
for( p=ptr=tab; p=memchr( ptr, '\0', n ); )
{
*p = ' ';
n -= p - ptr + 1;
ptr = ++p;
}
printf( "%s\n", buffer );
printf( "%.*s\n", taille, tab );
}
9.6 - Allocation dynamique de mémoire
Les fonctions permettant de faire de l'allocation dynamique
de mémoire sont :
Leurs déclarations se trouvent dans le fichier en-tête
stdlib.h.
void *malloc(size_t nb_octets)
void *calloc(size_t nb_elements, size_t taille_elt)
void *realloc(void *pointeur, size_t nb_octets)
void free(void *pointeur)
#include <stdio.h>
#include <stdlib.h>
typedef struct { int nb; float *ptr; } TAB_REEL;
#define TAILLE 100
main()
{
TAB_REEL *p; int i;
p = (TAB_REEL *)calloc( TAILLE, sizeof(TAB_REEL) );
if( ! p )
{
fprintf( stderr, "Erreur à l'allocation\n\n" );
exit(1);
}
for( i=0; i < TAILLE; i++ )
{
p[i].nb = TAILLE;
p[i].ptr = (float *)malloc( p[i].nb*
sizeof(float) );
p[i].ptr[i] = 3.14159f;
}
for( i=0; i < TAILLE; i++ ) free( p[i].ptr );
free( p );
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
main()
{
char **ptr = (char **)NULL;
char buffer[BUFSIZ];
int nb = 0, i;
for( ;; )
{
printf( "Entrer une chaîne : " );
scanf( "%s", buffer );
if( ! strcmp( buffer, "fin" ) ) break;
ptr = (char **)realloc( ptr,
++nb*sizeof(char *) );
ptr[nb-1] = (char *)malloc(
(strlen(buffer)+1)*sizeof(char) );
strcpy( ptr[nb-1], buffer );
}
for( i=0; i < nb; i++ )
{
printf( "%s\n", ptr[i] ); free( ptr[i] );
}
free( ptr );
}
9.7 - Date et heure courantes
Le fichier en-tête time.h contient des déclarations de types
et de fonctions permettant de manipuler la date et l'heure :
Les types time_t et clock_t sont, respectivement, des alias de long et int.
La structure struct tm, explicitant la date et l'heure, contient les champs suivant (de type int) :
Exemple
#include <stdio.h>
#include <time.h>
main()
{
time_t t;
struct tm *tm;
time( &t );
puts( ctime( &t ) ); /* Thu Feb 27 11:26:36 1997 */
tm = localtime( &t );
puts( asctime( tm ) ); /* Thu Feb 27 11:26:36 1997 */
tm->tm_year = 94;
tm->tm_mday = 16;
tm->tm_mon = 10;
t = mktime( tm );
puts( ctime( &t ) ); /* Wed Nov 16 11:26:36 1994 */
t -= 20*86400;
puts( ctime( &t ) ); /* Thu Oct 27 11:26:36 1994 */
return 0;
}
Syntaxe
char *getenv(char *nom)
Exemple
#include <stdio.h>
#include <stdlib.h>
#define RACINE "RACINE"
#define DEFAUT_RACINE "."
main()
{
char *racine;
if( (racine = getenv( RACINE )) == NULL )
racine = DEFAUT_RACINE;
printf( "Répertoire de travail: \"%s\"\n", racine );
return 0;
}
Sauvegarder le contexte d'un processus, consiste à sauvegarder la valeur des registres à cet instant (notamment la valeur du compteur ordinal ou registre d'instruction qui contient l'adresse de la prochaine instruction machine à exécuter).
Les fonctions setjmp et longjmp permettent, respectivement, de sauvegarder et restaurer le contexte d'un processus. Leurs déclarations se trouvent dans le fichier en-tête setjmp.h.
Syntaxe
int setjmp(jmp_buf cntx)
void longjmp(jmp_buf cntx, int valeur)
La fonction setjmp permet de sauvegarder le contexte
dans le vecteur cntx et retourne la valeur entière
0.
La restauration de ce contexte est effectuée par la fonction longjmp à laquelle on passe en argument le contexte sauvegardé. L'appel de cette fonction provoque alors une reprise de l'exécution de la fonction setjmp, qui a servi à sauvegarder le contexte, laquelle retourne cette fois-ci l'entier transmis, comme 2e argument, à la fonction longjmp.
Exemple
#include <stdio.h>
#include <setjmp.h>
jmp_buf cntx;
main()
{
int appel_boucle();
printf( "Terminaison avec \"%c\"\n",
appel_boucle() );
return 0;
}
int appel_boucle( void )
{
void boucle( void );
int retour = setjmp( cntx );
printf( "setjmp retourne %d\n", retour );
if( retour == 0 ) boucle();
return retour;
}
Exemple (suite)
void boucle( void )
{
for( ;; )
{
char getcmd( char * );
char c = getcmd( "-> " );
switch( c )
{
case 'q':
longjmp( cntx, c );
default:
printf( "Traitement de %c\n", c );
break;
}
}
}
char getcmd( char *s )
{
char c = (printf( "%s", s ), getchar());
while( getchar() != '\n' )
;
return c;
}
Certains compilateurs provoquent de plus l'arrêt du programme avec création d'un fichier «~image mémoire~» (core). Ce mécanisme peut être désactivé en compilant le programme avec l'option -DNDEBUG.
Exemple
#include <string.h>
#include <assert.h>
#define DIM 50
main()
{
char tab[DIM];
void f( char *p, int n );
memset( tab, ' ', DIM );
f( tab, DIM+1 );
}
void f( char *p, int n )
{
char *ptr = p;
int i;
for( i=0; i < n; i++ )
{
assert( ptr - p < DIM );
*ptr++ = '$';
}
}
La fonction perror, dont la déclaration figure dans le fichier en-tête stdio.h, permet d'émettre le message correspondant à la valeur positionnée dans la variable errno.
De plus, la fonction strerror, déclarée dans le fichier
en-tête string.h, retourne un pointeur sur ce message.
Syntaxe
void perror(const char *s)
char *strerror(int erreur)
La fonction perror émet, sur le flot stderr, le message d'erreur précédé de la chaîne passée en argument ainsi que du caractère :.
La fonction strerror retourne un pointeur sur le message d'erreur dont le numéro est passé en argument.
Exemple
#include <stdio.h>
#include <errno.h>
#include <string.h>
main()
{
FILE *f;
if( (f = fopen( "ExistePas", "r" )) == NULL )
{
perror( "fopen" );
puts( strerror( errno ) );
exit(1);
}
...
return 0;
}
La compilation d'un programme faisant appel à ces fonctions doit être effectuée avec l'option -lm afin que l'éditeur de liens puissent résoudre les références externes correspondantes.
Sous UNIX SYSTEM V, la fonction matherr
permet de gérer une erreur qui s'est produite lors de l'utilisation
d'une fonction mathématique.
Le programmeur peut écrire sa propre fonction matherr,
laquelle doit respecter la syntaxe suivante :
int matherr(struct exception *exp)
Le type struct exception est défini dans le fichier
en-tête math.h comme :
struct exception
{
int type;
char *name;
double arg1, arg2, retval;
};
Exemple
#include <stdio.h>
#include <math.h>
main()
{
double x, y;
scanf( "%lf", &x );
y = log( x );
printf( "%f\n", y );
return 0;
}
int matherr( struct exception *p )
{
puts( "erreur détectée" );
printf( " type : %d\n", p->type );
printf( " name : %s\n", p->name );
printf( " arg1 : %f\n", p->arg1 );
printf( " arg2 : %f\n", p->arg2 );
printf( " valeur retournée : %f\n", p->retval );
p->retval = -1.;
return 0;
}
Syntaxe
void *bsearch(const void *key, const void *base,
size_t NbElt, size_t TailleElt,
int (*cmp)(const void *, const void *))
Cette fonction recherche dans le vecteur trié base,
contenant NbElt éléments de taille
TailleElt, l'élément pointé par key.
La fonction cmp fournit le critère de recherche. Elle
est appelée avec 2 arguments, le 1er est un pointeur sur l'élément
à rechercher et le 2e un pointeur sur un élément du vecteur.
Cette fonction doit retourner un entier négatif, nul ou positif suivant
que son 1er argument est inférieur, égal ou supérieur à son
2e argument (en terme de rang dans le vecteur).
Exemple
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
main( int argc, char **argv )
{
int cmp( const void *key, const void *elt );
int tab[] = {10, 8, 7, 4, 2, 1, 0};
int *ptr;
int NbElt = sizeof tab / sizeof tab[0];
int key = (int)strtol( argv[1], NULL, 0 );
ptr = bsearch( &key, tab, NbElt,
sizeof(int), cmp );
if( ptr )
printf( "Rg de l'élt rech. : %d\n", ptr-tab+1 );
}
int cmp( const void *key, const void *elt )
{
if( *(int *)key < *(int *)elt )
return 1;
else if( *(int *)key > *(int *)elt )
return -1;
else return 0;
}
La fonction qsort, dont la déclaration se trouve dans
le fichier en-tête stdlib.h, permet de trier un vecteur.
Cette fonction est une réalisation de l'algorithme de tri rapide
(«~quick-sort~») dû à Hoare (1962).
Syntaxe
void qsort(const void *base,
size_t NbElt, size_t TailleElt,
int (*cmp)(const void *, const void *))
Cette fonction trie le vecteur base contenant
NbElt éléments de taille TailleElt.
La fonction cmp, qui sert de critère de tri,
admet 2 arguments pointant sur 2 éléments du vecteur à comparer.
Elle doit renvoyer un entier obéissant aux mêmes règles que pour
bsearch.
Exemple
#include <stdio.h>
#include <stdlib.h>
main()
{
int cmp( const void *elt1, const void *elt2 );
double tab[] = {3., 10., 6., 101.,
7., 11., 6., 14.};
int NbElt = sizeof tab / sizeof tab[0];
int i;
qsort( tab, NbElt, sizeof(double), cmp );
printf( "Vecteur tab trié : \n" );
for( i=0; i < NbElt; i++ )
printf( "%f\n", tab[i] );
}
int cmp( const void *elt1, const void *elt2 )
{
if( *(double *)elt1 < *(double *)elt2 )
return 1;
else if( *(double *)elt1 > *(double *)elt2 )
return -1;
else return 0;
}
Il existe une table des descripteurs de fichiers rattachée à chaque processus. La première entrée libre dans cette table est affectée lors de la création d'un descripteur de fichier.
Le nombre d'entrées de cette table, correspondant au nombre maximum de fichiers que peut ouvrir simultanément un processus, est donné par la pseudo-constante NOFILE définie dans le fichier en-tête «~sys/param.h~».
Syntaxe
int open(const char *fichier, int mode, mode_t acces)
int creat(const char *fichier, mode_t acces)
Le type mode_t est un alias du type
unsigned long, défini dans le fichier en-tête
«~sys/types.h~».
L'argument fichier indique le fichier à ouvrir.
L'argument mode indique le mode d'ouverture du fichier que l'on spécifie à l'aide de pseudo-constantes définies dans le fichier en-tête «~fcntl.h~» :
Ces pseudo-constantes peuvent être combinées à l'aide de l'opérateur booléen |.
L'appel creat est équivalent à open avec
O_WRONLY | O_CREAT | O_TRUNC
comme mode d'accès.
L'appel à creat, ainsi qu'à open dans le cas où le mode O_CREAT a été indiqué, s'effectue avec un autre argument décrivant les accès UNIX du fichier à créer qui se combinent avec ceux définis au niveau de la commande umask du SHELL.
Ces fonctions retournent le descripteur de fichier ou -1 en cas d'erreur.
La fonction close permet de libérer le descripteur de fichier passé en argument.
Exemple
#include <stdio.h>
#include <fcntl.h>
main()
{
int fd1, fd2;
if( (fd1 = open( "Fichier1",
O_WRONLY|O_CREAT|O_TRUNC,
0644 )) == -1 )
{
perror( "open" );
exit(1);
}
if( (fd2 = creat( "Fichier2", 0644 )) == -1 )
{
perror( "creat" );
exit(2);
}
...
close( fd1 );
close( fd2 );
return 0;
}
Syntaxe
int read(int fd, char *buffer, int NbOctets)
int write(int fd, char *buffer, int NbOctets)
Ces fonctions lisent ou écrivent, sur le descripteur de fichier
fd, NbOctets à partir de l'adresse
buffer.
Elles retournent le nombre d'octets effectivement transmis.
Une valeur de retour de read égale à 0 signifie fin de fichier.
Une valeur de retour égale à -1 correspond à la détection d'une erreur d'entrée-sortie.
Exemple
#include <stdio.h>
#include <fcntl.h>
main()
{
int fde, fds;
char buffer[BUFSIZ];
int nlus;
if( (fde = open( "entree", O_RDONLY )) == -1 )
{
perror( "open" );
exit(1);
}
if( (fds = creat( "sortie", 0644 )) == -1 )
{
perror( "creat" );
exit(2);
}
while( nlus = read( fde, buffer, sizeof(buffer) ) )
write( fds, buffer, nlus );
close( fde );
close( fds );
return 0;
}
Les fonctions tell et lseek permettent
de récupérer et de positionner le pointeur de position courante.
Elles sont déclarées dans le fichier en-tête unistd.h.
Syntaxe
off_t lseek(int fd, off_t decalage, int origine)
int tell(int fd)
Le type off_t est un alias du type long défini
dans le fichier en-tête «~sys/types.h~».
Exemple
#include <unistd.h>
/*
* fonction lisant n octets à partir
* de la position courante.
*/
int lire( int fd, long pos, char *buffer, int n )
{
if( lseek( fd, pos, 0 ) > = 0 )
return read( fd, buffer, n );
else return -1;
}
Il est possible de changer de mode de traitement d'un fichier c'est-à-dire passer du mode flot («~stream~») au mode descripteur («~bas niveau~») ou vice-versa.
Pour ce faire, il existe :
Syntaxe
int fileno(FILE *f)
FILE *fdopen(const int fd, const char *type)
Le mode d'ouverture indiqué au niveau de l'argument type
de la fonction fdopen, doit être compatible avec
celui spécifié à l'appel de la fonction open
qui a servi à créer le descripteur.
Ces deux fonctions sont déclarées dans le fichier en-tête stdio.h.
Exemple
#include <stdio.h>
main()
{ int fd;
FILE *fe, *fs;
char buffer[BUFSIZ];
if( (fd = creat( "copie", 0644 )) == -1 )
perror( "creat" ), exit(1);
write( fd, "abcdefg", 7 );
if( (fe = fopen( "entree", "r" )) == NULL )
perror( "fopen" ), exit(2);
printf( "Descripteur du fichier \"entree\" : "
"%d\n", fileno(fe) );
if( (fs = fdopen( fd, "w" )) == NULL )
perror( "fdopen" ), exit(3);
fgets( buffer, sizeof buffer, fe );
while( ! feof(fe) )
{
fputs( buffer, fs );
fgets( buffer, sizeof buffer, fe );
}
fclose( fe ); fclose( fs );
return 0;
}
| Caractère | déc. | hexa. | octal | Caractère | déc. | hexa. | octal |
| C-@ (NUL) | 0 | 0x00 | 000 | espace | 32 | 0x20 | 040 |
| C-a (SOH) | 1 | 0x01 | 001 | ! | 33 | 0x21 | 041 |
| C-b (STX) | 2 | 0x02 | 002 | " | 34 | 0x22 | 042 |
| C-c (ETX) | 3 | 0x03 | 003 | # | 35 | 0x23 | 043 |
| C-d (EOT) | 4 | 0x04 | 004 | $ | 36 | 0x24 | 044 |
| C-e (ENQ) | 5 | 0x05 | 005 | % | 37 | 0x25 | 045 |
| C-f (ACK) | 6 | 0x06 | 006 | & | 38 | 0x26 | 046 |
| C-g (BEL) | 7 | 0x07 | 007 | ' | 39 | 0x27 | 047 |
| C-h (BS) | 8 | 0x08 | 010 | ( | 40 | 0x28 | 050 |
| C-i (HT) | 9 | 0x09 | 011 | ) | 41 | 0x29 | 051 |
| C-j (LF) | 10 | 0x0a | 012 | * | 42 | 0x2a | 052 |
| C-k (VT) | 11 | 0x0b | 013 | + | 43 | 0x2b | 053 |
| C-l (FF) | 12 | 0x0c | 014 | , | 44 | 0x2c | 054 |
| C-m (CR) | 13 | 0x0d | 015 | - | 45 | 0x2d | 055 |
| C-n (SO) | 14 | 0x0e | 016 | . | 46 | 0x2e | 056 |
| C-o (SI) | 15 | 0x0f | 017 | / | 47 | 0x2f | 057 |
| C-p (DLE) | 16 | 0x10 | 020 | 0 | 48 | 0x30 | 060 |
| C-q (DC1) | 17 | 0x11 | 021 | 1 | 49 | 0x31 | 061 |
| C-r (DC2) | 18 | 0x12 | 022 | 2 | 50 | 0x32 | 062 |
| C-s (DC3) | 19 | 0x13 | 023 | 3 | 51 | 0x33 | 063 |
| C-t (DC4) | 20 | 0x14 | 024 | 4 | 52 | 0x34 | 064 |
| C-u (NAK) | 21 | 0x15 | 025 | 5 | 53 | 0x35 | 065 |
| C-v (SYN) | 22 | 0x16 | 026 | 6 | 54 | 0x36 | 066 |
| C-w (ETB) | 23 | 0x17 | 027 | 7 | 55 | 0x37 | 067 |
| C-x (CAN) | 24 | 0x18 | 030 | 8 | 56 | 0x38 | 070 |
| C-y (EM) | 25 | 0x19 | 031 | 9 | 57 | 0x39 | 071 |
| C-z (SUB) | 26 | 0x1a | 032 | : | 58 | 0x3a | 072 |
| C-[ (ESC) | 27 | 0x1b | 033 | ; | 59 | 0x3b | 073 |
| C-\ (FS) | 28 | 0x1c | 034 | < | 60 | 0x3c | 074 |
| C-] (GS) | 29 | 0x1d | 035 | = | 61 | 0x3d | 075 |
| C-$ (RS) | 30 | 0x1e | 036 | > | 62 | 0x3e | 076 |
| C-_ (US) | 31 | 0x1f | 037 | ? | 63 | 0x3f | 077 |
| Caractère | déc. | hexa. | octal | Caractère | déc. | hexa. | octal |
| @ | 64 | 0x40 | 100 | ` | 96 | 0x60 | 140 |
| A | 65 | 0x41 | 101 | a | 97 | 0x61 | 141 |
| B | 66 | 0x42 | 102 | b | 98 | 0x62 | 142 |
| C | 67 | 0x43 | 103 | c | 99 | 0x63 | 143 |
| D | 68 | 0x44 | 104 | d | 100 | 0x64 | 144 |
| E | 69 | 0x45 | 105 | e | 101 | 0x65 | 145 |
| F | 70 | 0x46 | 106 | f | 102 | 0x66 | 146 |
| G | 71 | 0x47 | 107 | g | 103 | 0x67 | 147 |
| H | 72 | 0x48 | 110 | h | 104 | 0x68 | 150 |
| I | 73 | 0x49 | 111 | i | 105 | 0x69 | 151 |
| J | 74 | 0x4a | 112 | j | 106 | 0x6a | 152 |
| K | 75 | 0x4b | 113 | k | 107 | 0x6b | 153 |
| L | 76 | 0x4c | 114 | l | 108 | 0x6c | 154 |
| M | 77 | 0x4d | 115 | m | 109 | 0x6d | 155 |
| N | 78 | 0x4e | 116 | n | 110 | 0x6e | 156 |
| O | 79 | 0x4f | 117 | o | 111 | 0x6f | 157 |
| P | 80 | 0x50 | 120 | p | 112 | 0x70 | 160 |
| Q | 81 | 0x51 | 121 | q | 113 | 0x71 | 161 |
| R | 82 | 0x52 | 122 | r | 114 | 0x72 | 162 |
| S | 83 | 0x53 | 123 | s | 115 | 0x73 | 163 |
| T | 84 | 0x54 | 124 | t | 116 | 0x74 | 164 |
| U | 85 | 0x55 | 125 | u | 117 | 0x75 | 165 |
| V | 86 | 0x56 | 126 | v | 118 | 0x76 | 166 |
| W | 87 | 0x57 | 127 | w | 119 | 0x77 | 167 |
| X | 88 | 0x58 | 130 | x | 120 | 0x78 | 170 |
| Y | 89 | 0x59 | 131 | y | 121 | 0x79 | 171 |
| Z | 90 | 0x5a | 132 | z | 122 | 0x7a | 172 |
| [ | 91 | 0x5b | 133 | { | 123 | 0x7b | 173 |
| \ | 92 | 0x5c | 134 | | | 124 | 0x7c | 174 |
| ] | 93 | 0x5d | 135 | } | 125 | 0x7d | 175 |
| ^ | 94 | 0x5e | 136 | ~ | 126 | 0x7e | 176 |
| _ | 95 | 0x5f | 137 | C-? | 127 | 0x7f | 177 |
| Catégorie d'opérateurs | Opérateurs | Assoc. |
| fonction, tableau,
membre de structure, pointeur sur un membre de structure | () [] . -> | G=>D |
| opérateurs unaires | - ++ -- ! ~
* & sizeof (type) | D=>G |
| multiplication, division,
modulo | * / % | G=>D |
| addition, soustraction | + - | G=>D |
| opérateurs binaires
de décalage | << >> | G=>D |
| opérateurs relationnels | < <= > >= | G=>D |
| opérateurs de comparaison | == != | G=>D |
| et binaire | & | G=>D |
| ou exclusif binaire | ^ | G=>D |
| ou binaire | | | G=>D |
| et logique | && | G=>D |
| ou logique | || | G=>D |
| opérateur conditionnel | ?: | D=>G |
| opérateurs d'affectation | = += -= *= /= %=
&= ^= |= <<= >>= | D=>G |
| opérateur virgule | , | G=>D |
Exercice 1
Soit un programme contenant les déclarations suivantes :
int i = 8;
int j = 5;
float x = 0.005f;
float y = -0.01f;
char c = 'c';
char d = 'd';
Déterminer la valeur de chacune des expressions suivantes :
Corrigé ou retour début chapitre .
Exercice 2
Soit un programme contenant les déclarations suivantes :
char *argv[] = {
"Wolfgang Amadeus Mozart",
"Ludwig van Beethoven",
"Hector Berlioz",
"Nicolo Paganini" };
char **p = argv;
Déterminer la valeur des expressions des 2 séries suivantes :
| 1 | (*p++)[1] | 1 | (*p++)[1] |
| 2 | *p++[1] | 2 | *p[1]++ |
| 3 | (*++p)[4] | 3 | (*++p)[4] |
| 4 | *++*p | 4 | *++*p |
Corrigé ou retour début chapitre .
Exercice 3
Analyser les expressions contenues dans le programme suivant :
#include <stdio.h>
main()
{
int a;
int b;
int c;
a = 16;
b = 2;
c = 10;
c += a > 0 && a <= 15 ? ++a : a/b;
/*
* Que dire de l'expression suivante ? :
* -----------------------------------
*/
a > 30 ? b = 11 : c = 100;
}
Corrigé ou retour début chapitre .
Exercice 4
Calculer parmi les entiers de 1 à 100 :
Corrigé ou retour début chapitre .
Exercice 5
Écrire un programme permettant d'effectuer le produit de 2 matrices A et B. Leurs profils seront définis à l'aide de constantes symboliques. La matrice résultat C sera imprimée ligne par ligne.
Corrigé ou retour début chapitre .
Exercice 6
Écrire un programme permettant de déterminer les nombres premiers dans l'intervalle [1,n] à l'aide du crible d'Ératosthène. Il consiste à former une table avec tous les entiers naturels compris entre 2 et n et à rayer (mise à zéro), les uns après les autres, les entiers qui ne sont pas premiers de la manière suivante : dès que l'on trouve un entier qui n'a pas encore été rayé, il est déclaré premier, et on raye tous les multiples de celui-ci. À la fin du procédé, les nombres non barrés sont des nombres premiers. On tiendra compte du fait qu'un nombre donné peut déjà avoir été éliminé en tant que multiple de nombres précédents déjà testés. Par ailleurs, on sait que l'on peut réduire la recherche aux nombres de 2 à sqrt(n) (si un entier non premier est strictement supérieur à sqrt(n) alors il a au moins un diviseur inférieur à sqrt(n) et aura donc déjà été rayé).
Corrigé ou retour début chapitre .
Exercice 7
Remplir un tableau de 12 lignes et 12 colonnes à l'aide des
caractères '1', '2' et
'3' tel que :
1
1 2
1 2 3
1 2 3 1
1 2 3 1 2
1 2 3 1 2 3
1 2 3 1 2 3 1
1 2 3 1 2 3 1 2
1 2 3 1 2 3 1 2 3
1 2 3 1 2 3 1 2 3 1
1 2 3 1 2 3 1 2 3 1 2
1 2 3 1 2 3 1 2 3 1 2 3
Corrigé ou retour début chapitre .
Exercice 8
Écrire un programme permettant de trier les vecteurs lignes d'une matrice de nombres en ordre croissant. On s'appuiera sur l'algorithme appelé «~tri à bulle~» qui consiste à comparer 2 éléments consécutifs et les intervertir si nécessaire. Si après avoir terminé l'exploration du vecteur au moins une interversion a été effectuée, on renouvelle l'exploration, sinon le tri est terminé. Chaque ligne à trier sera transmise à une fonction qui effectuera le tri.
Corrigé ou retour début chapitre .
Exercice 9
Le but de cet exercice est de transformer une matrice de réels que l'on se définira. Cette transformation consiste à modifier chaque élément à l'aide d'une fonction paramétrable de la forme y = f(x). On définira plusieurs fonctions de ce type. La valeur d'un entier défini sous la forme d'une constante symbolique indiquera la fonction à transmettre en argument de la procédure chargée d'effectuer la transformation.
Corrigé ou retour début chapitre .
Exercice 10
Écrire une fonction à laquelle on transmet des couples (entier, réel) en nombre variable, et qui retourne les sommes des entiers et des réels.
Corrigé ou retour début chapitre .
Exercice 11
Écrire un programme qui analyse les paramètres qui lui sont passés. Ce programme devra être appelé de la manière suivante :
exo11 -a chaine1|-b chaine2|-c chaine3 [-d -e -f] fichier
Corrigé ou retour début chapitre .
Exercice 12
Écrire un programme qui lit des mots sur l'entrée standard et les affiche après les avoir converti en louchebem («~langage des bouchers~»).
Cette conversion consiste à :
Corrigé ou retour début chapitre .
Exercice 13
Écrire une fonction myatof qui convertit la chaîne passée en argument, en un réel de type «~double~».
On pourra comparer le résultat obtenu avec celui de la fonction «~atof~» de la bibliothèque standard.
Corrigé ou retour début chapitre .
Exercice 14
Écrire un programme qui lit des chaînes de caractères sur l'entrée standard.
À la rencontre de la chaîne «~la~», il affichera la liste des chaînes déjà saisies.
À la rencontre de la chaîne «~li~», il affichera cette liste dans l'ordre inverse.
Corrigé ou retour début chapitre .
Exercice 15
Écrire un programme dont le but est de créer, à partir du fichier «~musiciens~», deux fichiers :
Corrigé ou retour début chapitre .
Exercice 16
Ce programme devra, à partir des fichiers créés par le programme de l'exercice 15, afficher :
Corrigé ou retour début chapitre .
Exercice 17
Écrire un programme qui affichera l'enregistrement du fichier indexé des musiciens, créé par le programme de l'exercice 15, dont le rang est passé en argument. (Prévoir les cas d'erreurs).
Corrigé ou retour début chapitre .
Exercice 18
Écrire une fonction qui retourne les différentes positions d'une chaîne de caractères dans un fichier texte ou binaire.
Le nom du fichier, ainsi que la chaîne seront transmis en argument au programme.
On pourra le tester avec les arguments suivants :
Corrigé ou retour début chapitre .
Exercice 19
Écriture d'un programme intéractif de gestion d'une liste chaînée.
Ce programme affichera le menu suivant :
1 - AJOUTS d'éléments dans une liste chaînée.
2 - AFFICHAGE de la liste chaînée.
3 - TRI de la liste chaînée.
4 - SUPPRESSION d'éléments dans la liste.
5 - VIDER la liste.
6 - ARRÊT du programme.
et effectuera le traitement correspondant au choix effectué.
Corrigé de l'exercice 1
int i = 8;
int j = 5;
float x = 0.005f;
float y = -0.01f;
char c = 'c';
char d = 'd';
| (3*i - 2*j)\%(2*d - c) | = | 14 |
| 2*((i/5) + (4*(j-3))\%(i + j - 2)) | = | 18 |
| i <= j | = | 0 |
| j != 6 | = | 1 |
| c == 99 | = | 1 |
| 5*(i + j) > 'c' | = | 0 |
| (i > 0) && (j < 5) | = | 0 |
| (i > 0) || (j < 5) | = | 1 |
| (x > y) && (i > 0) || (j < 5) | = | 1 |
| (x > y) && (i > 0) && (j < 5) | = | 0 |
Corrigé de l'exercice 2
char *argv[] = {
"Wolfgang Amadeus Mozart",
"Ludwig van Beethoven",
"Hector Berlioz",
"Nicolo Paganini" };
char **p = argv;
| (*p++)[1] | = | 'o' | (*p++)[1] | = | 'o' |
| *p++[1] | = | 'H' | *p[1]++ | = | 'H' |
| (*++p)[4] | = | 'l' | (*++p)[4] | = | 'r' |
| *++*p | = | 'i' | *++*p | = | 'c' |
Corrigé de l'exercice 3
#include <stdio.h>
§MEVBCBfint main()
{
int a;
int b;
int c;
a = 16;
b = 2;
c = 10;
/*
* 1) on commence par évaluer l'expression
* a > 0 && a <= 15, laquelle constitue
* le 1er opérande de l'opérateur ?:.
* Celle-ci est fausse car les expressions
* a > 0 et a <= 15 sont vraie et
* fausse respectivement,
* 2) on évalue donc le 3eme opérande de l'opérateur
* ?:, c'est-à-dire l'expression a/b,
* 3) et enfin on effectue l'affectation.
*/
c += a > 0 && a <= 15 ? ++a : a/b;
printf( "c : %d\n", c ); ===> 18
/*
* Que dire de l'expression suivante? :
* ----------------------------------
*/
a > 30 ? b = 11 : c = 100;
/*
* Cette expression provoque une erreur
* à la compilation car le troisième
* opérande de l'opérateur ?: est c
* et non pas c = 100. De ce fait,
* l'expression a > 30 ? b = 11 : c
* est d'abord évaluée. Sa valeur est ensuite utilisée
* comme opérande de gauche de la dernière affectation.
* D'où l'erreur, car cette valeur n'est pas une g-valeur.
*
* On devrait écrire :
*/
a > 30 ? b = 11 : (c = 100);
return 0;
}
Corrigé de l'exercice 4
#include <stdio.h>
§MEVBCBfint main()
{
int pairs, carres_impairs, cubes;
int i;
pairs = carres_impairs = cubes = 0;
/* Boucle de calcul.*/
for (i=1; i<=100; i++)
{
cubes += i*i*i;
/* "i" est-il pair ou impair?*/
i%2 ? carres_impairs += i*i : (pairs += i);
}
/*
* Impression des résultats.
*/
printf( "Somme des entiers pairs entre 1 et 100 : "
"%d\n", pairs );
printf( "Somme des carrés des entiers impairs entre"
" 1 et 100 : %d\n", carres_impairs );
printf( "Somme des cubes des 100 premiers "
"entiers : %d\n", cubes );
printf( "\n\nFin EXO4.\n" );
return 0;
}
Corrigé de l'exercice 5
Corrigé de l'exercice 6
Corrigé de l'exercice 7
Corrigé de l'exercice 8
Corrigé de l'exercice 9
Corrigé de l'exercice 10
Corrigé de l'exercice 11 : première solution
Corrigé de l'exercice 11 : deuxième solution
Corrigé de l'exercice 12
Corrigé de l'exercice 13
Corrigé de l'exercice 14
Corrigé de l'exercice 15
Corrigé de l'exercice 16
Corrigé de l'exercice 17
Corrigé de l'exercice 18
Corrigé de l'exercice 19
Fichier exo19.h
Fichier exo19_gestion_liste.h
Fichier exo19.c
Fichier exo19_gestion_liste.c
#include <stdio.h>
§MEVBCBfint main()
{
const int n = 10;
const int m = 5;
const int p = 3;
double a[][5] =
{
{ 0.00, 0.38, 0.42, 0.91, 0.25 },
{ 0.13, 0.52, 0.69, 0.76, 0.98 },
{ 0.76, 0.83, 0.59, 0.26, 0.72 },
{ 0.46, 0.03, 0.93, 0.05, 0.75 },
{ 0.53, 0.05, 0.85, 0.74, 0.65 },
{ 0.22, 0.53, 0.53, 0.33, 0.07 },
{ 0.05, 0.67, 0.09, 0.63, 0.63 },
{ 0.68, 0.01, 0.65, 0.76, 0.88 },
{ 0.68, 0.38, 0.42, 0.99, 0.27 },
{ 0.93, 0.07, 0.70 ,0.37, 0.44 }
};
double b[][3] =
{
{ 0.76, 0.16, 0.9047 },
{ 0.47, 0.48, 0.5045 },
{ 0.23, 0.89, 0.5163 },
{ 0.27, 0.90, 0.3190 },
{ 0.35, 0.06, 0.9866 }
};
double c[10][3];
int i,j,k;
/* Produit matriciel C = A*B */
for( i=0; i
#include <stdio.h>
#include <math.h>
§MEVBCBfint main()
{
const int n = 1000;
int tab_nombres[n];
int imax;
int i, j;
/*
* Remplissage du tableau "tab_nombres"
* à l'aide des nombres de 1 à 1000.
*/
for( i=1; i
/*
* Impression des nombres non exclus
* qui sont les nombres premiers cherchés.
* Impression de 10 nombres par ligne.
*/
printf( "Les nombres premiers entre 1 et "
"%d sont :\n\n", n );
for( i=1; i
#include <stdio.h>
§MEVBCBfint main()
{
int i, j;
char c, tab[12][12];
/* Boucle sur les colonnes.*/
for( j=0; j<12; )
/*
* On remplit par groupe de 3 colonnes avec
* les caractères successifs '1', '2' et '3'.
*/
for( c='1'; c<='3'; c++, j++ )
for( i=j; i<12; i++ )
tab[i][j] = c;
/*
* Impression du tableau obtenu.
*/
for( i=0; i<12; i++ )
{
for( j=0; j<=i; j++ )
printf( " %c", tab[i][j] );
printf( "\n" );
}
printf( "\n\nFin EXO7.\n" );
return 0;
}
#include <stdio.h>
#define NbElts(t) ( (sizeof(t)) / (sizeof(t[0])) )
#define NCOLS 4
typedef enum { False, True } Boolean;
§MEVBCBfint main()
{
void tri_vec( double *t );
double mat[][NCOLS] =
{
{ 1.56, 0.89, 10.234, 2.78 },
{ 9.789, 2.67, 39.78, 22.34 },
{ 99.99, 324.678, 4.56, 8.567 }
};
int i, j;
/* Tri de chaque vecteur ligne.*/
for( i=0; i
/*
* Fonction effectuant le tri d'un vecteur
* par la méthode du tri à "bulles".
*/
§MEVBCBfvoid tri_vec( double *t )
{
Boolean tri_termine;
int i;
for( ;; )
{
tri_termine = True;
for( i=0; i
#include <stdio.h>
#include <math.h>
#define NbElts(t) ( (sizeof(t)) / (sizeof(t[0])) )
#define NCOLS 4
/* Fonctions de transformations.*/
double identite( double x ) { return x; }
double carre ( double x ) { return x*x; }
double cubes ( double x ) { return x*x*x; }
§MEVBCBfint main()
{
void transform( double (*)[NCOLS],
int nb_lignes,
double (*f)( double ) );
const int choix = 4;
double mat[][NCOLS] =
{
{ 1.56, 0.89, 10.234, 2.78 },
{ 9.789, 2.67, 39.78, 22.34 },
{ 99.99, 324.678, 4.56, 8.567 }
};
int i, j;
switch( choix )
{
case 1:
transform( mat, NbElts(mat), identite );
break;
case 2:
transform( mat, NbElts(mat), carre );
break;
case 3:
transform( mat, NbElts(mat), cubes );
break;
case 4:
transform( mat, NbElts(mat), log );
break;
}
/* Impression de la matrice transformée.*/
for( i=0; i
/* Fonction effectuant la transformation.*/
§MEVBCBfvoid transform( double (*p)[NCOLS],
§MEVBCBfint nb_lignes,
§MEVBCBfdouble (*f)( double ) )
{
int i, j;
for( i=0; i
#include <stdio.h>
#include <stdarg.h>
typedef struct somme
{
int entiers;
double reels;
} Somme;
§MEVBCBfint main()
{
Somme sigma( int nb_couples, ... );
Somme s;
s = sigma( 4, 2, 3., 3, 10., 3, 5., 11, 132. );
printf( " Somme des entiers : %d\n", s.entiers );
printf( " Somme des réels : %f\n", s.reels );
printf( "\t\t------------------\n" );
s = sigma( 5, 2, 3., 3, 10., 3, 5., 11, 132., 121, 165. );
printf( " Somme des entiers : %d\n", s.entiers );
printf( " Somme des réels : %f\n", s.reels );
printf( "\n\nFin EXO10.\n" );
return 0;
}
§MEVBCBfSomme sigma( int nb_couples, ... )
{
Somme s;
int i;
va_list arg;
/*
* Initialisation de "arg" avec l'adresse
* de l'argument qui suit "nb_couples".
* ("arg" pointe sur l'entier du 1er couple).
*/
va_start( arg, nb_couples );
s.entiers = s.reels = 0;
/*
* Boucle de récupération des valeurs.
*/
for( i=0; i
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void traitement_option( const unsigned char option );
void usage( char *s );
const unsigned char option_a = 1;
const unsigned char option_b = 2;
const unsigned char option_c = 4;
const unsigned char option_d = 8;
const unsigned char option_e = 16;
const unsigned char option_f = 32;
unsigned char mask_options = 0;
char *valeur_option = NULL, *fichier = NULL;
char *module;
void bilan_options( void );
§MEVBCBfint main( int argc, char **argv )
{
/* Sauvegarde du nom de l'exécutable. */
module = strdup( *argv );
/* Boucle sur les arguments. */
while( *++argv != NULL )
{
/*
* Est-ce que l'argument commence
* par le caractère '-' ?
*/
if( (*argv)[0] == '-' )
{
/* On analyse les caractères qui suivent. */
for( (*argv)++; **argv; (*argv)++ )
{
switch( **argv )
{
case 'a':
traitement_option( option_a );
break;
case 'b':
traitement_option( option_b );
break;
case 'c':
traitement_option( option_c );
break;
case 'd':
traitement_option( option_d );
break;
case 'e':
traitement_option( option_e );
break;
case 'f':
traitement_option( option_f );
break;
default : usage( module );
}
if ( **argv == 'a' || **argv == 'b' || **argv == 'c' )
{
/*
* La valeur de l'option 'a', 'b' ou 'c' peut
* suivre immédiatement, ou bien être séparée
* de l'option par des blancs.
*/
if( *++*argv != '\0' )
valeur_option = strdup( *argv );
/* Cas où aucune valeur ne suit. */
else if( (*++argv)[0] == '-' )
usage( module );
else
valeur_option = strdup( *argv );
break;
}
}
}
/*
* L'argument ne commence pas
* par le caractère '-'.
*/
else if( fichier != NULL )
usage( module );
else
fichier = strdup( *argv );
}
bilan_options();
printf( "\n\nFin EXO11.\n" );
return 0;
}
§MEVBCBfvoid bilan_options( void )
{
/*
* L'option 'a', 'b', ou 'c' suivie d'une
* chaîne, ainsi qu'un nom de fichier
* doivent être spécifiés.
*/
if( valeur_option == NULL || fichier == NULL )
usage( module );
/*
* Si aucune des options 'd', 'e', 'f'
* n'a été spécifiée, on les considère toutes.
*/
if( ! (mask_options & option_d) &&
! (mask_options & option_e) &&
! (mask_options & option_f) )
mask_options |= option_d + option_e + option_f;
if( mask_options & option_a )
printf( "Option \"a\" fournie avec comme valeur : "
"%s\n", valeur_option );
if( mask_options & option_b )
printf( "Option \"b\" fournie avec comme valeur : "
"%s\n", valeur_option );
if( mask_options & option_c )
printf( "Option \"c\" fournie avec comme valeur : "
"%s\n", valeur_option );
printf( "Option \"d\" %s.\n",
mask_options & option_d ? "active" : "inactive" );
printf( "Option \"e\" %s.\n",
mask_options & option_e ? "active" : "inactive" );
printf( "Option \"f\" %s.\n",
mask_options & option_f ? "active" : "inactive" );
printf( "fichier indiqué : %s\n", fichier );
return;
}
§MEVBCBfvoid traitement_option( const unsigned char option )
{
/*
* Une seule des options "-a", "-b", "-c"
* doit avoir été spécifiée.
*/
if ( option == option_a ||
option == option_b ||
option == option_c )
if ( valeur_option != NULL )
usage( module );
/*
* On interdit qu'une option
* soit indiquée 2 fois.
*/
if ( mask_options & option )
usage( module );
else
mask_options |= option;
return;
}
§MEVBCBfvoid usage( char *s )
{
printf( "usage : %s -a chaine | -b chaine"
" | -c chaine [-d -e -f] fichier\n", s );
exit(1);
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef enum { False, True } Boolean;
typedef struct options
{
char nom[3];
Boolean option_fournie;
Boolean option_a_valeur;
char *valeur;
} Option;
typedef enum { option_a, option_b, option_c,
option_d, option_e, option_f,
nb_options, option_invalide } option_t;
Option options[] = {
{ "-a", False, True, NULL },
{ "-b", False, True, NULL },
{ "-c", False, True, NULL },
{ "-d", False, False, NULL },
{ "-e", False, False, NULL },
{ "-f", False, False, NULL }
};
option_t type_option( char *option );
void bilan_options( void );
void usage(char *s);
char *module, *fichier = NULL;
§MEVBCBfint main( int argc, char **argv )
{
/* Sauvegarde du nom de l'exécutable. */
module = strdup( *argv );
/* Boucle sur les arguments. */
while( *++argv != NULL )
{
/*
* Est-ce que l'argument commence
* par le caractère '-' ?
*/
if( (*argv)[0] == '-' )
{
option_t opt;
opt = type_option( *argv );
if( opt == option_invalide ) usage( module );
if( options[opt].option_a_valeur )
{
*argv += strlen( options[opt].nom );
if( argv[0][0] != '\0' )
options[opt].valeur = strdup( *argv );
/* Cas où aucune valeur ne suit. */
else if( (++argv)[0][0] == '-' )
usage( module );
else
options[opt].valeur = strdup( *argv );
}
}
else if( fichier != NULL )
usage( module );
else
fichier = strdup( *argv );
}
bilan_options();
printf("\n\nFin EXO11.\n");
return 0;
}
§MEVBCBfoption_t type_option( char *option )
{
option_t rang;
for( rang=0; rang
§MEVBCBfvoid bilan_options( void )
{
option_t rang;
/*
* Une seule des options "-a", "-b", "-c"
* doit avoir été spécifiée ainsi qu'un
* nom de fichier.
*/
if( options[option_a].option_fournie ^
options[option_b].option_fournie ^
options[option_c].option_fournie &&
fichier != NULL )
{
if ( options[option_a].option_fournie &&
options[option_b].option_fournie &&
options[option_c].option_fournie ) usage( module );
/*
* Si aucune des options 'd', 'e', 'f'
* n'a été spécifiée, on les considère toutes.
*/
if( ! options[option_d].option_fournie &&
! options[option_e].option_fournie &&
! options[option_f].option_fournie )
options[option_d].option_fournie =
options[option_e].option_fournie =
options[option_f].option_fournie = True;
for( rang=0; rang
#include <stdio.h>
§MEVBCBfint main()
{
char buffer[BUFSIZ];
char *p;
/*
* Boucle de lecture sur l'entrée standard
* avec la chaîne "--> " comme prompt.
*/
fputs( "--> ", stdout );
gets( buffer );
while( ! feof(stdin) )
{
/* On se positionne à la fin du mot lu. */
for( p=buffer; *p; p++ );
/* Conversion du mot en "louchebem". */
p[0] = *buffer;
*buffer = 'l';
p[1] = 'e'; p[2] = 'm'; p[3] = '\0';
puts( buffer );
fputs( "--> ", stdout );
gets( buffer );
}
printf( "\n\nFin EXO12.\n" );
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
§MEVBCBfint main( int argc, char **argv )
{
void usage ( char *s );
double myatof( char *s );
/* Y a-t-il un argument ? */
if( argc != 2 ) usage( argv[0] );
/*
* On imprime les résultats des fonctions
* "atof" et "myatof" pour comparaison.
*/
printf( "%f\n", atof ( argv[1] ) );
printf( "%f\n", myatof( argv[1] ) );
printf("\n\nFin EXO13.\n");
return 0;
}
§MEVBCBfdouble myatof( char *s )
{
long nombre, signe;
double exposant;
exposant = 1.;
nombre = 0;
/*
* Saut des éventuels caractères
* espace, tabulation et "newline"
* situés en tête.
*/
for( ; isspace( *s ); s++ )
;
/* Gestion du signe. */
signe = *s == '-' ? -1 : 1;
*s == '-' || *s == '+' ? s++ : s;
/* Gestion de la partie entière. */
for( ; isdigit( *s ); s++ )
nombre = nombre*10 + *s - '0';
if( *s++ != '.' )
return signe*nombre*exposant;
/* Gestion de la partie décimale. */
for( ; isdigit( *s ); s++ )
{
nombre = nombre*10 + *s - '0';
exposant /= 10.;
}
return signe*nombre*exposant;
}
§MEVBCBfvoid usage( char *s )
{
fprintf( stderr, "usage: %s nombre.\n", s );
exit( 1 );
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Définitions de nouveaux types. */
typedef enum sens {arriere, avant} Sens;
typedef struct cellule
{
char *chaine;
struct cellule *ptr_precedent;
struct cellule *ptr_suivant;
} CEL;
void liste ( CEL *p, Sens sens );
void libere( CEL *p );
CEL *debut = NULL;
§MEVBCBfint main()
{
CEL *ptr_courant = NULL;
char chaine[40];
/*
* Boucle de lecture sur l'entrée standard
* avec "--> " comme prompt.
*/
fputs( "--> ", stdout );
gets( chaine );
while( ! feof( stdin ) )
{
/*
* Si "la" est la chaîne entrée,
* on liste les chaînes déjà saisies.
*/
if( ! strcmp( chaine, "la" ) )
liste( debut, avant );
/*
* Si "li" est la chaîne entrée,
* on liste les chaînes déjà saisies
* dans l'ordre inverse.
*/
else if( ! strcmp( chaine, "li" ) )
liste( ptr_courant, arriere );
else
{
/* C'est la 1ère chaîne. */
if( debut == NULL )
{
debut = malloc( sizeof(CEL) );
debut->ptr_precedent = NULL;
ptr_courant = debut;
}
else
{
/* C'est une chaîne différente de la 1ère. */
ptr_courant->ptr_suivant = malloc( sizeof(CEL) );
ptr_courant->ptr_suivant->ptr_precedent = ptr_courant;
ptr_courant = ptr_courant->ptr_suivant;
}
/* On valorise le nouvel élément de la liste. */
ptr_courant->chaine = strdup( chaine );
ptr_courant->ptr_suivant = NULL;
}
fputs( "--> ", stdout );
gets( chaine );
}
/* On libère la liste. */
if( debut != NULL )
libere( debut );
printf( "\n\nFin EXO14.\n" );
return 0;
}
/* Fonction récursive d'affichage de la liste. */
§MEVBCBfvoid liste( CEL *p, Sens sens )
{
if( debut == NULL )
{
printf( "Désolé! la liste est vide.\n\n" );
return;
}
if ( p != NULL )
{
printf( "\t%s\n", p->chaine );
liste( sens == avant ?
p->ptr_suivant : p->ptr_precedent, sens );
}
return;
}
/*
* Fonction libérant la mémoire
* occupée par la liste.
*/
§MEVBCBfvoid libere( CEL *p )
{
if ( p->ptr_suivant != NULL )
libere( p->ptr_suivant );
free( p->chaine );
free( p );
return;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
§MEVBCBfint main()
{
typedef struct index
{
unsigned int debut;
unsigned int longueur;
} INDEX;
FILE *mus, *ind, *indmus;
char buffer[BUFSIZ];
INDEX index;
/* Ouverture du fichier texte "musiciens". */
if( (mus = fopen( "musiciens", "r" )) == NULL )
{
perror( "fopen" );
exit(1);
}
/*
* Ouverture en écriture du fichier
* indexé des musiciens,
*/
if( (ind = fopen( "index", "w" )) == NULL )
{
perror( "fopen" );
exit(2);
}
/* Ouverture en écriture du fichier d'index. */
if( (indmus = fopen( "indmus", "w" )) == NULL )
{
perror( "fopen" );
exit(3);
}
index.debut = ftell( mus );
/* Boucle de lecture du fichier des musiciens. */
fgets( buffer, sizeof buffer, mus );
while( ! feof(mus) )
{
static int n = 0; /* nombre de caractères "newline". */
/* On supprime le caractère "newline". */
buffer[strlen( buffer )-1] = '\0'; n++;
fputs( buffer, indmus );
index.longueur = strlen( buffer );
fwrite( &index, sizeof index, 1, ind );
/*
* Mise à jour de la position pour
* l'itération suivante.
*/
index.debut = ftell( mus ) - n;
fgets( buffer, sizeof buffer, mus );
}
/* Fermeture des fichiers. */
fclose( ind ); fclose( indmus ); fclose( mus );
printf( "\n\nFin EXO15.\n" );
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Définition de types. */
typedef struct date
{
unsigned int date_naiss;
unsigned int date_mort;
} DATE;
typedef struct musicien
{
char nom[30];
char prenom[20];
DATE date;
} MUSICIEN;
typedef struct index
{
unsigned int debut;
unsigned int longueur;
} INDEX;
§MEVBCBfint main()
{
MUSICIEN mort_le_plus_jeune( MUSICIEN *mus, int n );
void imprime ( MUSICIEN *mus, int n );
int cmp_alpha ( const void *mus1, const void *mus2 );
int cmp_chrono( const void *mus1, const void *mus2 );
FILE *index, *mus;
INDEX ind;
MUSICIEN *p_mus;
MUSICIEN mus_mort_le_plus_jeune;
char *buffer;
int NbMus;
/* Ouverture du fichier index. */
if( (index = fopen( "index", "r" )) == NULL )
{
perror( "fopen" );
exit(1);
}
/* Ouverture du fichier indexé des musiciens. */
if( (mus = fopen( "indmus", "r" )) == NULL )
{
perror( "fopen" );
exit(2);
}
NbMus = 0;
p_mus = NULL;
/*
* Boucle de lecture du fichier indexé des musiciens,
* par l'intermédiaire du fichier d'index.
*/
fread( &ind, sizeof(INDEX), 1, index );
while( ! feof(index) )
{
buffer = malloc( ind.longueur+1 );
fgets( buffer, ind.longueur+1, mus );
p_mus = realloc( p_mus, ++NbMus*sizeof(MUSICIEN) );
sscanf( buffer, "%s%s%d%d",
p_mus[NbMus-1].prenom,
p_mus[NbMus-1].nom,
&p_mus[NbMus-1].date.date_naiss,
&p_mus[NbMus-1].date.date_mort );
free( buffer );
fread( &ind, sizeof(INDEX), 1, index );
}
/* Fermeture des fichiers. */
fclose( index ); fclose( mus );
/* Affichage de la liste des musiciens. */
printf( "\n\n\t\tListe des musiciens :\n" );
printf( "\t\t-------------------\n\n" );
imprime( p_mus, NbMus );
/*
* Tri puis affichage de la liste des musiciens
* triés par ordre alphabétique.
*/
qsort( p_mus, NbMus, sizeof(MUSICIEN), cmp_alpha );
printf( "\n\n\tListe des musiciens"
" par ordre alphabétique :\n" );
printf( "\t-----------------------"
"-------------------\n\n" );
imprime( p_mus, NbMus );
/*
* Tri puis affichage de la liste des musiciens
* triés par ordre chronologique.
*/
qsort( p_mus, NbMus, sizeof(MUSICIEN), cmp_chrono );
printf( "\n\n\tListe des musiciens"
" par ordre chronologique :\n" );
printf( "\t-----------------------"
"---------------------\n\n" );
imprime( p_mus, NbMus );
/* Recherche du musicien mort le plus jeune. */
mus_mort_le_plus_jeune = mort_le_plus_jeune( p_mus, NbMus );
/*
* Affichage du musicien mort le plus jeune, ainsi
* que sa durée de vie.
*/
printf( "\n\n\tLe musicien mort le plus jeune est :"
"\n\t----------------------------------\n\n" );
printf( "\t%s %s qui est mort a %d ans.\n\n",
mus_mort_le_plus_jeune.prenom,
mus_mort_le_plus_jeune.nom,
mus_mort_le_plus_jeune.date.date_mort -
mus_mort_le_plus_jeune.date.date_naiss );
printf( "\n\nFin EXO16.\n" );
return 0;
}
/*
* Fonction appelée par "qsort" pour trier les
* musiciens par ordre alphabétique.
*/
§MEVBCBfint cmp_alpha( const void *mus1, const void *mus2 )
{
if( strcmp( ((MUSICIEN *)mus1)->nom,
((MUSICIEN *)mus2)->nom ) > 0 )
return 1;
if( strcmp( ((MUSICIEN *)mus1)->nom,
((MUSICIEN *)mus2)->nom) < 0 )
return -1;
return 0;
}
/*
* Fonction appelée par "qsort" pour trier les
* musiciens par ordre chronologique.
*/
§MEVBCBfint cmp_chrono( const void *mus1, const void *mus2 )
{
if( ((MUSICIEN *)mus1)->date.date_naiss >
((MUSICIEN *)mus2)->date.date_naiss )
return 1;
if( ((MUSICIEN *)mus1)->date.date_naiss <
((MUSICIEN *)mus2)->date.date_naiss )
return -1;
return 0;
}
/* Fonction d'affichage. */
§MEVBCBfvoid imprime( MUSICIEN *mus, int n )
{
int i;
for( i=0; i
#include <stdio.h>
#include <stdlib.h>
typedef struct index
{
unsigned int debut;
unsigned int longueur;
} INDEX;
§MEVBCBfint main( int argc, char **argv )
{
void erreur(int rc);
void usage (char *s);
FILE *index, *mus;
int rang_mus;
INDEX ind;
char *buffer;
/* Le rang a-t-il été spécifié ? */
if( argc != 2 ) usage( argv[0] );
/* Conversion de l'argument en entier. */
rang_mus = (int)strtol( argv[1], NULL, 0 );
if( rang_mus <= 0 )
erreur( 1 );
/* Ouverture du fichier indexé des musiciens. */
if( (index = fopen( "index", "r" )) == NULL )
perror( "fopen" ), exit(2);
/* Ouverture du fichier d'index. */
if( (mus = fopen( "indmus", "r" )) == NULL )
perror( "fopen" ), exit(3);
/*
* Positionnement dans le fichier d'index.
* Ne pas trop compter sur la valeur retournée
* par la fonction "fseek". Un mauvais positionnement
* provoquera une erreur lors de la lecture suivante.
*/
fseek( index, (rang_mus-1)*sizeof(INDEX), SEEK_SET );
/*
* Lecture de l'index contenant le positionnement et
* la longueur de l'enregistrement, dans le fichier
* indexé des musiciens, correspondant au rang spécifié.
*/
if( fread( &ind, sizeof ind, 1, index ) != 1 )
erreur( 4 );
/*
* Positionnement puis lecture
* de l'enregistrement désiré.
*/
fseek( mus, ind.debut, SEEK_SET );
buffer = malloc( ind.longueur+1 );
fgets( buffer, ind.longueur+1, mus );
/* Affichage du musicien sélectionné. */
printf( "\n\tmusicien de rang %d ==> %s\n\n",
rang_mus, buffer );
free( buffer );
/* Fermeture des fichiers. */
fclose( index ); fclose( mus );
printf("\n\nFin EXO17.\n");
return 0;
}
§MEVBCBfvoid erreur( int rc )
{
fprintf( stderr, "rang invalide.\n" );
exit( rc );
}
§MEVBCBfvoid usage( char *s )
{
fprintf( stderr, "usage : %s rang\n", s );
exit( 6 );
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
int *positions;
int nb_positions;
size_t taille_bloc;
§MEVBCBfstatic void init( size_t longueur )
{
positions = NULL;
nb_positions = 0;
taille_bloc = BUFSIZ - longueur + 1;
return;
}
§MEVBCBfint main( int argc, char **argv )
{
void usage(char *s);
void strrech( char *buffer, int nblus,
char *ChaineAchercher, size_t longueur );
int fd;
char buffer[BUFSIZ];
char *ChaineAchercher;
size_t longueur;
int nblus;
/*
* Le fichier et la chaîne à chercher
* ont-ils été passés en argument ?
*/
if ( argc != 3 )
usage( argv[0] );
ChaineAchercher = argv[2];
longueur = strlen( ChaineAchercher );
/*
* initialisation des variables globales.
*/
init( longueur );
/* Ouverture du fichier passé en argument. */
if( (fd = open( argv[1], O_RDONLY )) == -1 )
{
perror( "open" );
exit( 1 );
}
/* Boucle de lecture du fichier. */
while( nblus = read( fd, buffer, sizeof buffer ) )
{
/*
* Récupération des positions de la chaîne
* dans le buffer courant.
*/
strrech( buffer, nblus, ChaineAchercher, longueur );
/*
* Si BUFSIZ caractères ont été lus, on
* recule de (longueur-1) caractères
* dans le fichier, pour être sûr de n'oublier
* aucune position de la chaîne.
*/
if( nblus == BUFSIZ )
lseek( fd, -(long)(longueur - 1), SEEK_CUR );
}
close( fd );
/* Impression des positions trouvées. */
if ( nb_positions == 0 )
printf( "La chaîne \"%s\" n'a pas été trouvée\n"
"dans le fichier \"%s\".\n",
ChaineAchercher, argv[1] );
else
{
int pos;
printf( "Dans le fichier \"%s\", la chaîne \"%s\"\n"
"a été trouvée aux positions :\n\n",
argv[1], ChaineAchercher );
for( pos=0; pos
/*
* Fonction de récupération des positions
* de la chaîne dans le buffer courant.
*/
§MEVBCBfvoid strrech( char *s, int nblus,
§MEVBCBfchar *ChaineAchercher, size_t longueur )
{
char *buffer, *ptr;
static int n = 0, nombre_octets_lus = 0;
int i;
/*
* On prend garde de remplacer les éventuels caractères
* "nuls" par des blancs pour pouvoir utiliser
* la fonction "strstr".
*/
buffer = malloc( nblus+1 );
memcpy( buffer, s, nblus );
for( i=0; i
/* Boucle de recherche de la chaîne. */
for( ptr=buffer;
ptr=strstr( ptr, ChaineAchercher );
ptr+=longueur )
{
/*
* extension du vecteur positions.
*/
positions = (int *)realloc( positions, ++n*sizeof(int) );
assert( positions != NULL );
/*
* position de la chaîne trouvée par
* rapport au début du bloc lu.
*/
positions[n-1] = ptr - buffer + 1;
/*
* position de la chaîne trouvée par
* rapport au début du fichier.
*/
positions[n-1] += nombre_octets_lus;
}
free( buffer );
/*
* Nombre d'octets déjà lus dans le fichier.
*/
nombre_octets_lus += taille_bloc;
nb_positions = n;
return;
}
§MEVBCBfvoid usage(char *s)
{
fprintf( stderr, "usage: %s fichier"
" ChaineAchercher.\n", s );
exit(1);
}
#define taille(t) sizeof(t) / sizeof(t[0])
typedef enum bool {False, True} Boolean;
void ajouts ( void );
void liste ( void );
void tri ( void );
void suppression( void );
void vider ( void );
void arret ( void );
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "exo19.h"
#include "exo19_gestion_liste.h"
struct menu
{
char *texte;
void (*action)( void );
};
§MEVBCBfint main()
{
/* Définition du menu. */
struct menu menu[] =
{
{" 1 - AJOUTS d'éléments dans une liste chaînée.\n",
ajouts},
{" 2 - AFFICHAGE de la liste chaînée.\n",
liste},
{" 3 - TRI de la liste chaînée.\n",
tri},
{" 4 - SUPPRESSION d'éléments dans la liste.\n",
suppression},
{" 5 - VIDER la liste.\n",
vider},
{" 6 - ARRÊT du programme.\n",
arret}
};
int SelectionMenu( struct menu menu[], int NbChoix );
/* Boucle infinie sur les choix effectués. */
for (;;)
menu[SelectionMenu( menu, taille(menu) )].action();
}
/* Fonction renvoyant le choix effectué. */
§MEVBCBfint SelectionMenu( struct menu menu[], int NbChoix )
{
int choix, m;
char entree[10];
char *endp;
do
{
printf( "\n\nListe des choix :\n" );
for( m=0; m
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "exo19.h"
#include "exo19_gestion_liste.h"
#define LISTE_VIDE "La liste est vide.\n"
static const char * const prompt_ajout =
"Élément à ajouter[CtrlD pour terminer] --> ";
static const char * const prompt_suppression =
"Élément à supprimer[CtrlD pour terminer] --> ";
static const char *prompt;
typedef struct cellule
{
char *capitale;
struct cellule *ptr_precedent;
struct cellule *ptr_suivant;
} CEL;
static CEL *debut = NULL;
static CEL *curseur = NULL;
static Boolean liste_vide( void );
static void ajout_cellule( char *chaine );
static void suppression_cellule( void );
static Boolean recherche_cellule( char *chaine );
static char *lire_chaine( void );
static void affichage_liste( CEL *p );
§MEVBCBfstatic Boolean liste_vide( void )
{
return debut == NULL ? True : False;
}
§MEVBCBfstatic void ajout_cellule( char *chaine )
{
CEL *p;
/*
* Allocation, valorisation,
* insertion du nouvel élément.
*/
p = malloc( sizeof(CEL) );
p->capitale = chaine;
if ( liste_vide() )
p->ptr_suivant = p->ptr_precedent = NULL;
else
{
if ( curseur != debut )
curseur->ptr_precedent->ptr_suivant = p;
p->ptr_precedent = curseur->ptr_precedent;
curseur->ptr_precedent = p;
p->ptr_suivant = curseur;
}
curseur = p;
if( curseur->ptr_precedent == NULL )
debut = curseur;
return;
}
§MEVBCBfstatic void suppression_cellule( void )
{
if( curseur == debut )
{
/*
* L'élément à supprimer est le 1er de la liste.
*/
debut = curseur->ptr_suivant;
if( ! liste_vide() )
debut->ptr_precedent = NULL;
}
else
{
/*
* L'élément à supprimer n'est pas le 1er de la liste.
*/
curseur->ptr_precedent->ptr_suivant =
curseur->ptr_suivant;
if( curseur->ptr_suivant != NULL )
/*
* L'élément à supprimer n'est
* pas le dernier de la liste.
*/
curseur->ptr_suivant->ptr_precedent =
curseur->ptr_precedent;
}
{
CEL *p = curseur;
free( p->capitale ); free( p );
if ( curseur->ptr_suivant != NULL )
curseur = curseur->ptr_suivant;
else
curseur = debut;
}
return;
}
§MEVBCBfstatic Boolean recherche_cellule( char *chaine )
{
CEL *p;
for( p=debut; p; p=p->ptr_suivant )
if ( ! strcmp( p->capitale, chaine ) )
break;
if( p != NULL )
{
curseur = p;
return True;
}
return False;
}
§MEVBCBfstatic char *lire_chaine( void )
{
char buffer[BUFSIZ];
/*
* Lecture de l'élément à ajouter.
*/
fputs( prompt, stdout );
gets( buffer );
/*
* Si Control-D, annuler le bit indicateur
* de fin de fichier, pour les prochaines saisies.
*/
if( feof( stdin ) )
{
clearerr( stdin );
return NULL;
}
return strdup( buffer );
}
/*
* Fonction rattachée au choix 1.
* (AJOUTS d'éléments dans la liste chaînée).
*/
§MEVBCBfvoid ajouts( void )
{
char *chaine;
/*
* Boucle de lecture des chaînes.
*/
prompt = prompt_ajout;
while( (chaine = lire_chaine()) != NULL )
ajout_cellule( chaine );
return;
}
/*
* Fonction rattachée au choix 3.
* (TRI de la liste chaînée).
*/
§MEVBCBfvoid tri( void )
{
Boolean tri_terminee;
CEL *ptr;
/*
* La liste doit exister.
*/
if ( liste_vide() )
fprintf( stderr, LISTE_VIDE );
else
{
/*
* Boucle de tri.
*/
do
{
tri_terminee = True;
for( ptr=debut; ptr->ptr_suivant;
ptr = ptr->ptr_suivant )
if( strcmp( ptr->capitale,
ptr->ptr_suivant->capitale ) > 0 )
{
/*
* On effectue une interversion.
*/
curseur = ptr;
ajout_cellule(
strdup( curseur->ptr_suivant->capitale ) );
curseur = ptr->ptr_suivant;
suppression_cellule();
tri_terminee = False;
if ( ptr->ptr_suivant == NULL )
break;
}
} while( ! tri_terminee );
}
return;
}
/*
* Fonction rattachée au choix 4.
* (SUPPRESSION d'éléments dans la liste).
*/
§MEVBCBfvoid suppression( void )
{
char *chaine;
/*
* Boucle de lecture des chaînes.
*/
prompt = prompt_suppression;
while( ! liste_vide() && (chaine = lire_chaine()) != NULL )
{
if( ! recherche_cellule( chaine ) )
{
fprintf( stderr, "L'élément \"%s\" est"
" inexistant!\n\n", chaine );
continue;
}
suppression_cellule();
printf( "L'élément \"%s\" a été supprimé"
" de la liste.\n\n", chaine );
}
/*
* La liste est-elle vide ?
*/
if ( liste_vide() ) fprintf( stderr, LISTE_VIDE );
return;
}
/*
* Fonction rattachée au choix 5.
* (VIDER la liste ).
*/
§MEVBCBfvoid vider( void )
{
if ( liste_vide() )
fprintf( stderr, LISTE_VIDE );
else
{
curseur = debut;
while ( ! liste_vide() )
suppression_cellule();
}
return;
}
/*
* Fonction rattachée au choix 6.
* (ARRET du programme).
*/
§MEVBCBfvoid arret( void )
{
/*
* Si la liste n'est pas vide, on libère
* la mémoire qu'elle occupe.
*/
if( ! liste_vide() ) vider();
printf( "\n\nFin EXO19.\n" );
exit( 0 );
}
Fin de ce manuel
© CNRS - IDRIS, 08/03/2010