VI. Bibliothèque standard▲
VI-A. Fonctions à arguments en nombre variable▲
Le langage C permet de créer des fonctions dont le nombre d'arguments peut varier d'un appel à un autre c'est-à-dire n'est pas fixe. Nous avons déjà eu jusqu'ici l'occasion d'utiliser de telles fonctions par exemple printf et scanf. Maintenant nous allons voir comment en créer.
Une fonction qui accepte un nombre variable d'arguments doit au moins posséder un argument fixe, en effet même si la fonction peut recevoir un nombre variable d'arguments, elle doit quand même savoir à chaque appel combien d'arguments ont été passés. Par exemple, les fonctions printf et scanf comptent le nombre de spécificateurs de formats pour connaître le nombre d'arguments passés.
La bibliothèque standard du langage C est fournie avec des macros permettant d'accéder aux arguments « variables » de manière portable. Ces macros sont définies dans le fichier d'entête stdarg.h. Le principe est simple : à l'intérieur de la fonction, on crée une liste d'arguments (de type va_list). On initialise cette liste en pointant sur le dernier argument fixe à l'aide de la macro va_start. Puis on parcourt la liste à l'aide de la macro va_arg. Lorsqu'on en a terminé avec la liste, il faut appeler va_end.
Nous allons écrire une fonction qui calcule la moyenne des nombres passés en arguments. Le premier argument servira à préciser le nombre d'arguments variables passés.
#include <stdio.h>
#include <stdarg.h>
double
moyenne
(
int
N, ...);
int
main
(
)
{
printf
(
"
moyenne = %f
\n
"
, moyenne
(
4
, 10
.0
, 20
.0
, 30
.0
, 40
.0
));
return
0
;
}
double
moyenne
(
int
N, ...)
{
double
moy =
0
.0
;
if
(
N >
0
)
{
va_list args;
int
i;
double
xi, somme =
0
.0
;
va_start
(
args, N);
for
(
i =
0
; i <
N; i++
)
{
xi =
va_arg
(
args, double
);
somme +=
xi;
}
va_end
(
args);
moy =
somme /
N;
}
return
moy;
}
VI-B. Les paramètres régionaux▲
Les paramètres régionaux sont un ensemble de paramètres qui peuvent changer selon la localisation tels que le symbole décimal, le symbole monétaire, le symbole de groupement des chiffres, etc. Les fonctions, macros et types ayant un lien avec ces paramètres sont déclarés/définis dans le fichier locale.h. La fonction setlocale :
char
*
setlocale
(
int
category, const
char
*
locale);
permet de modifier les paramètres régionaux utilisés par le programme. Cette fonction retourne NULL en cas d'erreur. Par défaut, tous les programmes utilisent les paramètres standard du langage C, c'est-à-dire comme si :
setlocale
(
LC_ALL, "
C
"
);
avait été appelé.
Les valeurs utilisables pour l'argument category sont LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME, plus d'autres éventuelles macros définies par l'implémentation. Pour l'argument locale, « C » indique d'utiliser les paramètres standard du C et "" les paramètres de l'environnement d'exécution. L'implémentation peut évidemment autoriser d'autres valeurs. La fonction localeconv :
struct
lconv *
localeconv
(
);
permet de récupérer les valeurs des paramètres courants. Par exemple :
#include <stdio.h>
#include <locale.h>
int
main
(
)
{
struct
lconv *
plconv;
if
(
setlocale
(
LC_MONETARY, ""
) !=
NULL
)
{
plconv =
localeconv
(
);
printf
(
"
Le symbole monetaire est : %s
\n
"
, plconv->
currency_symbol);
}
return
0
;
}
VI-C. « Type » des caractères▲
Dans le fichier ctype.h sont déclarées des fonctions (ou macros) facilitant la manipulation des caractères. Ces fonctions (macros) attendent en argument un int puisqu'elles doivent aussi tester EOF. Leur comportement, à l'exception de isdigit et isxdigit, est dépendant de la valeur du jeu de paramètres LC_CTYPE.
Les fonctions suivantes permettent de tester le « type » d'un caractère et retourne VRAI (c'est-à-dire une valeur non nulle) lorsque le prédicat est vrai.
int
isalpha
(
int
c); /* c est une letrre */
int
isdigit
(
int
c); /* c est un chiffre decimal */
int
isxdigit
(
int
c); /* c est un chiffre hexadecimal */
int
isalnum
(
int
c); /* c est une lettre ou un chiffre decimal */
int
islower
(
int
c); /* c est une lettre minuscule */
int
isupper
(
int
c); /* c est une lettre majuscule */
int
isspace
(
int
c); /* c est un caractere d'espacement */
int
ispunct
(
int
c); /* c est une ponctuation */
int
iscntrl
(
int
c); /* c est un caractere de controle (DEL, ECHAP, ...) */
int
isprint
(
int
c); /* c est un caractere imprimable */
int
isgraph
(
int
c); /* isprint(c) && !isspace(c) */
Les fonctions toupper et tolower permettent de convertir, lorsque cela est possible, une lettre en majuscule respectivement en minuscule. Par exemple toupper('a') retourne 'A'.
VI-D. Les caractères larges▲
À la différence de la plupart des langages de haut niveau, le langage C traite les caractères comme de simples entiers (en fait un caractère est un entier) exactement de la même manière que le fait le processeur lui-même. Le type char n'est pas le seul type utilisé pour manipuler des caractères. Un caractère large (wide character) est un caractère plus large, en termes de taille, qu'un simple char, c'est-à-dire pouvant prendre plus de valeurs que ces derniers.
Une constante littérale de type caractère large s'écrit comme une constante de type char, mais avec un préfixe L, par exemple : L'x'. De même, une constante chaîne constituée de caractères larges s'écrit comme une simple constante chaîne constituée de char, mais avec le préfixe L, par exemple : L« Bonjour ». Le standard définit un caractère large comme étant de type wchar_t, défini dans le fichier stddef.h.
Les fonctions impliquant des caractères ou chaînes de caractères en argument ou en valeur de retour ont donc toujours, à quelques très rares exceptions près, leur équivalent en wchar_t principalement déclarées dans wchar.h et wctype.h. Ainsi on a putchar/putwchar, getchar/getwchar, isalpha/iswalpha, toupper/towupper, strlen/wcslen, strcpy/wcscpy, printf/wprintf, scanf/wscanf, fgets/fgetws, etc. Par exemple :
#include <stdio.h>
#include <wchar.h>
int
main
(
)
{
char
c =
'
a
'
, *
s =
"
Bonjour
"
;
wchar_t wc =
'
a
'
, *
ws =
L"
Bonjour
"
;
printf
(
"
Voici un caractere simple :
"
);
putchar
(
c);
printf
(
"
\n
Et voici un caractere large :
"
);
putwchar
(
wc);
printf
(
"
\n\n
"
);
printf
(
"
Voici une chaine de caracteres simples : %s
\n
"
, s);
wprintf
(
L"
Et voici une chaine de caracteres larges : %s
\n
"
, ws);
return
0
;
}
Un caractère multioctet (multibyte character) est un élément du jeu de caractères du système hôte ou, en termes plus simples, un … caractère ! (Mais non nécessairement représentable par un char). On l'appelle multioctet puisqu'il peut être codé sur un (pourquoi pas ?) ou plusieurs octets. Un caractère multioctet peut donc être représenté par un char ou un tableau de char selon le cas. De même, une chaîne de caractères multioctets peut être représentée à l'aide d'un simple tableau de char. L'interprétation d'une chaîne de caractères multioctets est donc bien évidemment très dépendante du codage utilisé et ne rentre pas dans le cadre de ce tutoriel.
D'autre part, en fait, le type char a plutôt été désigné non seulement pour représenter un « octet », mais aussi n'importe caractère supporté par le langage lui-même (l'ensemble de ces caractères formant ce qu'on appelle le jeu de caractères de base), qui doit absolument tenir sur un octet, et non n'importe quel caractère du jeu de caractères du système hôte, qui est un sur ensemble du jeu de caractères de base, cette tâche ayant été plutôt confiée aux caractères larges qui sont donc tenus, si vous avez bien suivi, de pouvoir représenter n'importe quel caractère multioctet (… à leur manière !), alors qu'on recourrait éventuellement à un tableau si l'on utilisait le type char.
Il existe plusieurs fonctions dédiées à la manipulation des caractères multioctets, déclarées dans stdlib.h. Leur comportement dépend bien évidemment de la valeur du jeu de paramètres LC_CTYPE. En particulier, la fonction mbstowcs (multibyte string to wide char string) :
size_t mbstowcs
(
wchar_t *
pwcs, const
char
*
s, size_t nwcs);
permet de convertir une chaîne de caractères multioctets en une chaîne équivalente de caractères larges. La fonction wcstombs réalise l'inverse. Par exemple :
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define COUNT(t) (sizeof(t) / sizeof(t[0]))
int
main
(
)
{
char
s[] =
"
Hello, world!
"
;
wchar_t wcs[20
];
mbstowcs
(
wcs, s, COUNT
(
wcs));
wprintf
(
L"
%s
\n
"
, wcs);
return
0
;
}
VI-E. Recherche dans une chaîne▲
La fonction strchr▲
char
*
strchr
(
const
char
*
s, int
c);
retourne l'adresse de la première occurrence du caractère c dans la chaîne s ou NULL si aucune occurrence n'a été trouvée. La fonction strrchr (r pour reverse) fait aussi la même chose sauf qu'elle effectue la recherche dans le sens inverse (c'est-à-dire à partir de la fin).
La fonction strpbrk▲
char
*
strpbrk
(
const
char
*
s, const
char
*
cs);
retourne l'adresse de la première occurrence de n'importe caractère de la chaîne cs dans la chaîne s ou NULL si aucune occurrence n'a été trouvée.
La fonction strstr▲
char
*
strstr
(
const
char
*
s, const
char
*
substr);
retourne l'adresse de la première occurrence de la chaîne substr dans la chaîne s ou NULL si aucune occurrence n'a été trouvée.
La fonction strtok▲
char
*
strtok
(
const
char
*
s, const
char
*
separators);
utilisée en boucle, permet d'obtenir toutes les sous chaînes (tokens) de la chaîne s délimitées par n'importe quel des caractères de la chaîne separators. Si s n'est pas NULL, elle est copiée dans une variable locale statique à la fonction. Celle-ci retournera ensuite la première sous chaîne trouvée (en la terminant par '\0'). Si s est NULL et que la fonction a déjà été correctement initialisée, la sous-chaîne suivante est retournée, sinon, le comportement est indéfini. Lorsqu’aucune sous chaîne n'a pu être trouvée, NULL est retournée. Bien entendu, on peut aussi changer la liste des séparateurs à chaque appel de la fonction. Par exemple :
#include <stdio.h>
#include <string.h>
int
main
(
)
{
char
lpBuffer[100
], separators[] =
"
\t
.,!?
\n
"
, *
p;
printf
(
"
Entrez une chaine de caracteres :
"
);
fgets
(
lpBuffer, sizeof
(
lpBuffer), stdin);
printf
(
"
\n
"
);
p =
strtok
(
lpBuffer, separators);
while
(
p !=
NULL
)
{
printf
(
"
%s
\n
"
, p);
p =
strtok
(
NULL
, separators);
}
return
0
;
}
VI-F. Conversion des chaînes▲
Ces fonctions permettent de constituer une valeur numérique (entier ou flottant) à partir d'une chaîne de caractères. Elles sont déclarées dans le fichier stdlib.h. On distingue deux grandes familles de fonctions de conversion à savoir les fonctions atoxx et celle des fonctions strtoxx.
int
atoi
(
const
char
*
s);
long
atol
(
const
char
*
s);
double
atof
(
const
char
*
s);
Par exemple :
#include <stdio.h>
#include <stdlib.h>
int
main
(
)
{
int
mille =
atoi
(
"
1000
"
);
printf
(
"
mille = %d
\n
"
, mille);
return
0
;
}
Mais que ce passerait-il si on passait par exemple « vive le vent » au lieu de « 1000 » à la fonction ? Et bien, c'est justement là le problème avec les fonctions atoxx : elles ne sont pas capables de signaler qu'on leur a passé n'importe quoi. Dans un programme solide, il faut utiliser les fonctions strtoxx puis tester si la conversion s'est bien déroulée. Une conversion s'est bien déroulée si elle s'est arrêtée à la rencontre du caractère de fin de chaîne.
long
strtol
(
const
char
*
s, char
*
*
p_stop, int
base);
unsigned
long
strtoul
(
const
char
*
s, char
*
*
p_stop, int
base);
double
strtod
(
const
char
*
s, char
*
*
p_stop);
Un petit exemple :
#include <stdio.h>
#include <stdlib.h>
int
main
(
)
{
char
s[] =
"
FF
"
;
char
*
p_stop;
int
n, base =
16
;
n =
(
int
)strtol
(
s, &
p_stop, base);
if
(*
p_stop ==
'
\0
'
) /* OK */
printf
(
"
%s (base %d) = %d (base 10).
\n
"
, s, base, n);
else
printf
(
"
Erreur : %c n'est pas un chiffre.
\n
"
, *
p_stop);
return
0
;
}
Pour créer des applications robustes, on n'effectue jamais de saisie à l'aide de scanf (et moins encore à l'aide de gets). On saisit tout d'abord une chaîne (à l'aide de fgets) puis éventuellement la convertir dans le type qu'on veut avoir.
VI-G. Fonctions de manipulation de chaîne « généralisées »▲
L'intérêt des fonctions memxxx (memcpy, memcmp, etc.) est de permettre d'utiliser n'importe quel pointeur sur un objet dans une fonction assez voisine des fonctions orientées chaînes telles que strcpy, strcmp, etc. Elles sont également déclarées dans le fichier string.h.
void
*
memcpy
(
void
*
p_dest, const
void
*
p_src, size_t n_bytes_to_be_copied);
void
*
memmove
(
void
*
p_dest, const
void
*
p_src, size_t n_bytes_to_be_moved);
void
*
memcmp
(
void
*
p_dest, const
void
*
p_src, size_t n_bytes_to_be_compared);
void
*
memchr
(
const
void
*
p_buffer, int
c, size_t n_buffer_length);
void
*
memset
(
void
*
p_buffer, int
c, size_t n_bytes_to_be_set);
La différence entre memcpy et memmove vient du fait que cette dernière peut être utilisée même si les deux buffers (src et dest) se chevauchent. Elle est généralement utilisée pour « glisser » (ou « déplacer ») des données d'où son nom. Par exemple, le programme suivant copie chaque octet du tableau t2 vers le tableau t1 (t1 doit donc être au moins aussi grand que t2).
#include <stdio.h>
#include <string.h>
int
main
(
)
{
int
t1[10
], t2[] =
{
0
, 10
, 20
, 30
, 40
}
;
int
i;
memcpy
(
t1, t2, sizeof
(
t2));
for
(
i =
0
; i <
5
; i++
)
printf
(
"
t1[%d] = %d
\n
"
, i, t1[i]);
return
0
;
}
VI-H. La date et l'heure▲
VI-H-1. Généralités▲
Les fonctions, macros et types de données standard permettant de travailler avec la date et l'heure sont déclarées/définies dans le fichier time.h.
VI-H-2. Connaître la date et l'heure courantes▲
La fonction permettant de connaître la date et l'heure calendaires (un « timestamp »), est time :
time_t time
(
time_t *
p_time);
En cas d'erreur, (time_t)(-1) est retourné.
On peut donc récupérer la date et l'heure soit en stockant la valeur de retour de time dans une variable de type time_t, soit en passant en argument l'adresse d'une variable du même type. On peut même encore compliquer …
Il est également important d'avoir à l'esprit que ce n'est qu'une simple et petite fonction, pas un sorcier ni un devin, et qu'elle ne peut donc retourner que ce que le système lui a fournit et rien de plus.
D'autre part, une valeur de type time_t c'est quand même difficile à décoder (en fait pas si difficile que ça, mais bref …) pour un humain. L'idéal pour nous c'est d'avoir une structure avec des champs comme heure, minute, seconde, etc. Heureusement pour nous, cette structure existe. Il s'agit de la structure struct tm. Oui, mais comme vous le savez bien, il y a d'un côté le temps local (en anglais Local Time Zone ou tout simplement local time) qui dépend de la localisation et de l'autre le Temps Universel (ou UTC pour Coordinated Universal Time). La structure struct tm est définie dans time.h comme suit :
struct
tm {
int
tm_sec;
int
tm_min;
int
tm_hour; /* 0 - 23 */
int
tm_mday; /* 1 - 31 */
int
tm_mon; /* 0 - 11 (0 = janvier)*/
int
tm_year; /* annees ecoulees depuis 1900 */
int
tm_wday; /* 0 - 6 (0 = dimanche) */
int
tm_yday; /* 0 - 365 (0 = 1er janvier) */
int
tm_isdst; /* DST (Daylight-Saving Time) flag */
}
;
Le DST flag indique si les informations contenues dans la structure tiennent compte de l'heure d'été ou non. Sa valeur est 1 pour oui, 0 pour non et un nombre négatif si l'information n'est pas disponible.
La fonction localtime permet de convertir le temps calendaire (time_t) en temps local. La fonction gmtime (de Greenwich Meridian Time, l'ancienne appellation du Temps Universel) permet quant à elle de convertir le temps calendaire en Temps universel. Ces deux fonctions ont le même prototype.
struct
tm *
localtime
(
time_t *
p_time);
struct
tm *
gmtime
(
time_t *
p_time);
En cas d'erreur, NULL est retourné.
Voici un exemple de programme qui affiche la date et l'heure courantes :
#include <stdio.h>
#include <time.h>
int
main
(
)
{
time_t t;
struct
tm *
ti;
const
char
*
days[] =
{
"
dimanche
"
, "
lundi
"
, "
mardi
"
, "
mercredi
"
, "
jeudi
"
, "
vendredi
"
, "
samedi
"
}
;
const
char
*
months[] =
{
"
janvier
"
, "
fevrier
"
, "
mars
"
, "
avril
"
, "
mai
"
, "
juin
"
, "
juillet
"
, "
aout
"
, "
septembre
"
, "
octobre
"
, "
novembre
"
, "
decembre
"
}
;
t =
time
(
NULL
);
ti =
localtime
(&
t);
printf
(
"
On est le %s %d %s %d
\n
"
, days[ti->
tm_wday], ti->
tm_mday, months[ti->
tm_mon], ti->
tm_year +
1900
);
printf
(
"
Et il est %02d:%02d:%02d
\n
"
, ti->
tm_hour, ti->
tm_min, ti->
tm_sec);
return
0
;
}
On a également la fonction asctime qui permet de convertir facilement un temps représenté par une structure (struct tm) en une chaîne de caractères et la fonction ctime qui reçoit quant à elle un argument de type time_t. Dommage que le format utilisé soit anglo-saxon.
char
*
asctime
(
const
struct
tm *
pTime);
char
*
ctime
(
const
time_t *
p_time);
Par exemple :
#include <stdio.h>
#include <time.h>
int
main
(
)
{
time_t t =
time
(
NULL
);
printf
(
"
Date is : %s
\n
"
, ctime
(&
t));
return
0
;
}
Il y a également la fonction strftime assez complexe pour être détaillée ici.
VI-H-3. Les conversions date <-> timestamp▲
Nous avons vu tout à l'heure les fonctions localtime et gmtime qui permettent de connaître les informations de date et heure à partir d'un temps calendaire. Il existe une fonction mktime qui réalise l'inverse, c'est-à-dire qui calcule le temps calendaire en fonction d'informations de date et heure locales. Lors d'un appel à mktime, les champs tm_wday et tm_yday sont ignorés. En effet, ils peuvent être calculés à partir des valeurs des autres champs.
Voici un exemple de programme qui demande à l'utilisateur d'entrer une date et qui affiche le jour de la semaine correspondant à cette date.
#include <stdio.h>
#include <time.h>
#include <string.h>
int
main
(
)
{
struct
tm ti;
printf
(
"
Entrez une date au format jj/mm/aaaa :
"
);
memset
(&
ti, 0
, sizeof
(
ti));
if
(
scanf
(
"
%d/%d/%d
"
, &
ti.tm_mday, &
ti.tm_mon, &
ti.tm_year) !=
3
)
printf
(
"
Mauvais format.
\n
"
);
else
{
time_t t;
const
char
*
days_tab[] =
{
"
dimanche
"
, "
lundi
"
, "
mardi
"
, "
mercredi
"
, "
jeudi
"
, "
vendredi
"
, "
samedi
"
}
;
ti.tm_mon -=
1
;
ti.tm_year -=
1900
;
t =
mktime
(&
ti);
memcpy
(&
ti, localtime
(&
t), sizeof
(
struct
tm));
printf
(
"
C'est un %s.
\n
"
, days_tab[ti.tm_wday]);
}
return
0
;
}
VI-H-4. Connaître le temps processeur d'un programme▲
Une autre fonction bien pratique de time.h est la fonction clock qui retourne le temps processeur utilisé par le programme depuis le début de son exécution (le point marquant le « début de l'exécution » d'un programme est défini par l'implémentation et non par le C), exprimé en tops, un top étant égal à 1/CLOCKS_PER_SEC seconde (il s'agit là d'une division de réels). Le prototype de cette fonction est :
clock_t clock
(
void
);
clock_t étant évidemment un type arithmétique représentant le temps d'utilisation du processeur d'un programme depuis le début de son exécution. Une des multiples applications de cette fonction (mais qui n'est pas la meilleure …) est de ne quitter une boucle (qui ne fait rien de spécial …) que lorsqu'un certain temps s'est écoulé, cela permet de suspendre le programme pendant un intervalle de temps donné. Par exemple :
#include <stdio.h>
#include <time.h>
void
wait
(
int
ms);
int
main
(
)
{
printf
(
"
Suspension du programme pendant 5s.
\n
"
);
wait
(
5000
);
printf
(
"
Termine.
\n
"
);
return
0
;
}
void
wait
(
int
ms)
{
clock_t start =
clock
(
);
while
((
clock
(
) -
start) *
1000
<
ms *
CLOCKS_PER_SEC) ;
}
VI-I. Les nombres pseudoaléatoires▲
La bibliothèque standard du langage C est fournie avec des fonctions permettant de générer des nombres de façon pseudo aléatoire. Le générateur de ces nombres doit tout d'abord être initialisé avec une graine qui doit elle-même être plus ou moins aléatoire. Si la graine est constante, la même série de nombres va être générée chaque fois que le programme sera exécuté, car le générateur sera alors toujours initialisé avec la même valeur. En ce qui nous concerne, pas la peine d'aller chercher sur Mars, le temps est une valeur qui change « tout le temps » ! Elle peut donc convenir dans la plupart des cas pour initialiser le générateur.
Les fonctions liées aux nombres aléatoires, déclarées dans stdlib.h sont :
void
srand
(
unsigned
int
seed);
int
rand
(
void
);
La fonction srand permet d'initialiser le générateur. La fonction rand retourne à chaque appel un nombre pseudo aléatoire compris entre 0 et RAND_MAX (qui doit être au moins égal à 32767 quel que soit l'implémentation). Par exemple :
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int
main
(
)
{
int
i;
srand
((
unsigned
)time
(
NULL
));
printf
(
"
Voici dix nombres tires au hasard compris entre 0 et 99.
\n
"
);
for
(
i =
0
; i <
10
; i++
)
printf
(
"
%d
\t
"
, rand
(
) %
100
);
return
0
;
}