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 comme 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 parcoure 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 majudcule */
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▲
A 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("\nEt 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. Lorsque 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 pseudo-alé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ée 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;
}

