[Tutoriel] Utiliser la bibliothèque standard du C
Posté le 27/04/2014 19:16
La bibliothèque standard du C est une mine d'or pour les développeurs quels qu'ils soient, et je trouve bête de la voir si rarement utilisée.
Voilà une présentation de ce qu'elle vous permet de faire.
Avant toute utilisation de ces fonctions, n'oubliez pas d'ajouter
#include <stdio.h>
#include <stdlib.h>
au début de votre programme.
Sommaire
->
Les fonctions de formatage
->
Les fonctions de conversion
->
Les fonctions de l'aléatoire
->
Les valeurs absolues
->
Les divisions euclidiennes
->
L'allocation dynamique
->
Fonctionnalités avancées
Les fonctions de formatage
Ces fonctions, vous les connaissez sûrement, si vous avez fait du C sur ordinateur. Les plus simples sont les fonctions printf() et scanf().
Si vous ne les connaissez pas, je vous conseille d'aller jeter un coup d'oeil
ici et
là, car elles sont essentielles pour la suite.
Le problème de ces deux fonctions, c'est qu'elles utilisent les flux d'entrée/sortie standard du C, qui n'existent pas sur la calculatrice. Du coup ces fonctions ont été retirées, et le SDK vous insulte si vous tentez de les utiliser.
Mais qu'importe ! Car la bibliothèque standard dispose d'autres fonctions de lecture/écriture formatée. Les plus utiles d'entre elles vous seront probablement:
sprintf(char *str, const char *format, ...);
sscanf (char *str, const char *format, ...);
Ces deux fonctions
ont exactement le même rôle que printf() et scanf(), mais ne vont pas lire/écrire
via les flux d'entrée/sortie standard, mais
dans des chaînes de caractères.
Il n'y a donc aucune sortie à l'écran ni aucune demande de saisie, tout se passe dans des variables.
sprintf()
Prenons un exemple assez simple. Je veux écrire dans une chaîne de caractères l'adresse d'un fichier, dont l'utilisateur a entré le nom. Pour obtenir l'adresse complète, il suffit de mettre
char ch[20], name[20];
sprintf(ch,"\\\\fls0\\%s",name);
où
name contient le nom du fichier.
La chaîne
ch contient maintenant l'adresse complète du fichier. Il ne reste plus qu'à l'ouvrir !
sprintf() permet non seulement le debuggage de valeurs, mais aussi la fusion des chaînes de caractères, si vous mettez plusieurs
"%s" dans votre format.
sscanf()
Prenons un autre exemple. Je voudrais que l'utilisateur entre un nombre
via une interface simple basée sur
GetKey(). Ce nombre se trouve à la fin de la saisie dans une chaîne de caractères, comme dans l'exemple suivant.
char nombre[8] = "23745";
int x;
Ensuite, il faut que
x prenne la valeur entière de 23745. C'est là que
sscanf() intervient:
sscanf(nombre,"%d",&x);
Et c'est tout !
x prend la valeur que la fonction a lue dans la chaîne de caractères
nombre, et contient maintenant la valeur souhaitée.
vsprintf()
Cette fonction a le même rôle que
sprintf, mais prend en troisième argument une liste d'arguments (
va_list).
Les fonctions de conversion[/big]
Ce sont des fonctions qui se comportent à peu près comme
sscanf(), sauf qu'elles ne vont lire qu'une valeur. Avec
sscanf(), il suffit de mettre plusieurs expression du type
"%d" pour lire plusieurs valeurs dans la chaîne.
Reprenons l'exemple précédent. L'intruction
sscanf() peut être remplacée par quelque chose d'encore plus simple.
x = atoi(nombre);
La fonction
atoi() (pour ASCII To Integer) va lire la chaîne de caractères comme un nombre entier, générer ce nombre et le renvoyer.
Il y a dans la bibliothèque standard cinq fonctions qui ont ce rôle, décliné à chaque fois.
double atof (const char *nptr);
int atoi (const char *nptr);
long atol (const char *nptr);
double strtod (const char *nptr, char **endptr);
long strtol (const char *nptr, char **endptr, int base);
Les trois premières sont les plus utilisées. Elle permettent de lire une chaîne de caractères comme un nombre, respectivement un
float, un
int et un
long.
Les fonctions de l'aléatoire[/big]
Il est inutile d'en parler ici, puisqu'il existe déjà un
excellent tutoriel de Dark Storm pour cela.
Les valeurs absolues[/big]
Là sont deux fonctions pratiques de la bibliothèque standard.
int abs (int i);
long labs (long l);
Les deux renvoient la valeur absolue du paramètre.
abs pour les
int,
labs pour les
long.
Les divisions euclidiennes[/big]
La bibliothèque standard permet même de faire des divisions euclidiennes !
Les structures
div_t et
ldiv_t permettent de gérer ces nombres, la première en utilisant des
int, la deuxième avec des
long.
Les deux fonctions pour diviser sont très simples.
div_t div (int number, int denom);
ldiv_t ldiv (long number, long denom);
Enfin, on peut accéder aux membres de ces structures comme suit.
div_t x;
x = div(34,6);
x.quot; // = 5
x.rem; // = 4
L'allocation dynamique[/big]
Le gros morceau de la bibliothèque standard. Cette notion s'appuie plus que largement sur les pointeurs, que je vous conseille de très bien connaître pour comprendre cette partie.
Lorsqu'au début d'un bloc vous déclarez une variable, la calculatrice réserve la place qu'il lui faut dans la mémoire. Lorsque le bloc se termine, la mémoire est libérée pour un autre usage. L'allocation dynamique permet de gérer ces manipulations de mémoire.
Un peu de vocabulaire
-> Allocation: C'est lorsque la mémoire est réservée
-> Libération: C'est lorsque la mémoire est désallouée, l'espace mémoire est de nouveau libre pour une autre utilisation.
Comment ça fonctionne ?
L'allocation dite dynamique, que vous gérez vous-même, utilise des fonctions de la bilbiothèque standard et
des pointeurs. Uniquement des pointeurs, jamais d'adresses, puisque l'adresse d'une variable pointe sur une variable que vous avez déjà déclarée, donc pour laquelle la mémoire
a déjà été allouée.
Allouer de la mémoire à un pointeur: malloc()
Pour allouer de la mémoire à un pointeur, on utilise la fonction
malloc(). Elle ne prend qu'un paramètre, la taille de la zone mémoire à allouer, en octets.
unsigned char *p;
p = malloc(20);
Le pointeur
p pointe maintenant vers une zone mémoire qui lui a été allouée de 20 octets, si l'opération a réussi. Il faut toujours vérifier que l'allocation a fonctionné.
if(p==NULL)
{
// l'allocation a echoue
}
Le pointeur
NULL est un pointeur standard qui pointe dans le vide. Il ne faut surtout pas essayer d'écrire à l'adresse
NULL, le système vous renverrait sûrement une System Error.
La fonction sizeof()
Mais il reste un problème. Imaginons que je veux allouer de l'espace pour 10
int. Sur certains systèmes un
int fait 2, 4 voire même 8 octets. Impossible donc de savoir quelle taille de mémoire je dois allouer.
Heureusement, il y a
sizeof() ! Cette fonction prend en argument un type de donnée et renvoie, en
int, la taille en octets d'une variable d'un tel type.
Le code pour allouer dynamiquement la mémoire que je voulais est donc:
ptr = malloc(10*sizeof(int));
La fonction
sizeof permet également de connaître la taille d'une structure ou d'une union par exemple, mais
en aucun cas la longueur d'une chaîne de caractères.
Libérer la mémoire avec free()
La mémoire allouée à un pointeur n'est pas libérée pas le système à la fin du bloc, mais la variable pointeur, elle, l'est. On a donc un gros problème: à la sortie du bloc, la mémoire est toujours allouée, et on n'y a plus accès car on ne connaît pas l'adresse de la zone mémoire, le pointeur ayant été détruit.
C'est ce que l'on appelle une fuite de mémoire.
Il faut donc
toujours libérer la mémoire allouée avant de perdre le pointeur.
int ptr;
ptr = malloc(10*sizeof(int));
// ...
free(ptr);
La fonction qui alloue le pointeur peut également le renvoyer (donc sans le libérer), ce qui est très utile lorsque l'on veut qu'une fonction crée et initialise une structure par exemple.
Les fonctions calloc() et realloc()
En bonus, deux autres fonctions d'allocation.
calloc() fonctionne de la même manière que
malloc(). Mais en plus d'allouer la mémoire, elle initialise tous les éléments alloués à 0. Elle prend deux paramètres, le nombre d'éléments à allouer et la taille d'un élément.
int *ptr;
ptr = calloc(10,sizeof(int));
Vous diposez alors d'un tableau dont tous les éléments ont la valeur 0.
realloc(), comme son nom l'indique, permet de réallouer une zone de mémoire à un pointeur.
Elle prend deux paramètres: le pointeur déjà alloué et la nouvelle taille. Dans le cas où la réallocation succède, la fonction est susceptible d'allouer un bloc à un autre endroit de la mémoire, et retourne alors le nouveau pointeur.
En revanche, si la réallocation échoue, l'ancienne zone de mémoire n'est pas libérée, donc
attention aux fuites de mémoire !
Fonctionnalités avancées[/big]
La bibliothèque standard contient également des fonctions très avancées et optimisées. Pour comprendre cette section, je vous conseille de bien connaîtres les pointeurs, notamment sur les fonctions.
La fonction qsort()
Cette superbe fonction trie un tableau à l'aide d'un algorithme de tri rapide. Seulement elle possède une spécificité digne d'intérêt. Puisqu'elle doit pouvoir trier autre chose que des entiers,
c'est vous qui définissez si un élément est inférieur, égal ou supérieur à un autre. Il faut donc que vous définissiez votre propre fonction de comparaison. Allez, on va prendre un exemple, parce que c'est un peu compliqué.
Je veux trier un tableau d'entiers, de sorte que les nombres dont le chiffre des unités est le plus faible soient au début.
La fonction de tri est donc la suivante.
int compare(const void *v1, const void *v2)
{
int i1 = (*(int *)v1) % 10;
int i2 = (*(int *)v2) % 10;
if(i1 < i2) return -1;
if(i1 > i2) return 1;
return 0;
}
Notez que la fonction doit prendre en paramètres deux pointeurs de type
const void *. C'est donc à vous de veiller à les caster dans le type que vous manipulez. Cette opération ne constitue pas une violation de la règle d'aliasing stricte puisque le pointeur original est de type void.
Maintenant que nous avons notre fonction de comparaison, nous pouvons trier notre tableau !
void qsort (void* base, size_t num, size_t size,
int (*compar)(const void*,const void*));
Le prototype peut sembler surprenant.
base est le pointeur sur votre tableau.
num est le nombre d'éléments dans le tableau, et
size est la taille d'un élément, que vous pouvez obtenir avec
sizeof().
Enfin, vient le pointeur sur votre fonction de comparaison.
int entiers[10] = { 21, 87, 83742, 874, 732, 923, 93, 97, 2, 743 };
qsort((void *)entiers,10,sizeof(int),compare);
Et le résultat est le tableau suivant.
21, 83742, 732, 2, 923, 93, 743, 874, 87, 97
La fonction bsearch()
La fonction
bsearch() permet de faire une recherche dans un tableau de manière très rapide.
Avant tout, il faut que votre tableau soit trié, de manière croissante pour que la recherche fonctionne. On va donc pouvoir se servir de
qsort().
void* bsearch (const void* key, const void* base,
size_t num, size_t size,
int (*compar)(const void*,const void*));
Aucune surprise du point de vue du prototype. La clé recherchée est castée en
const void *, donc n'oubliez pas de déclarer une variable.
base est toujours l'adresse du tableau, et
num et
size désignent toujours le nombre d'élément et leur taille. Enfin, toujours cette fonction de comparaison.
Voyons comment utiliser cette fonction.
int entiers[10] = { 21, 87, 83742, 874, 732, 923, 93, 97, 2, 743 };
int key = 874;
void *ptr;
qsort((void *)entiers, 10, sizeof(int), compare);
ptr = bsearch((const void *)&key, (const void *)entiers, 10, sizeof(int), compare);
On trie donc d'abord notre tableau avec
qsort(), puis on en extrait l'adresse du premier élément 874 trouvé. L'adresse est assignée au pointeur
ptr.
Donc, si on affiche
*(int *)ptr (ce qui ne crée pas d'alias), on aura bien sûr 874.
Enfin, dernière astuce.
index = (int *)ptr-entiers;
Et vous voilà avec l'index de l'objet recherché dans votre tableau.
Si vous avez la moindre question, remarque, suggestion à faire, n'hésitez pas à laisser un commentaire !
Citer : Posté le 27/04/2014 20:03 | #
Tu ne parles pas de la fonction realloc(). Est-ce qu'elle fonctionne sous casio ?
Ajouté le 27/04/2014 à 20:04 :
Sinon, super tuto qui illustre parfaitement les possibilités de la bibliothèque Standard !
Coïncidence ? Je ne pense pas.
Citer : Posté le 27/04/2014 20:41 | #
La fonction realloc() existe bien sous CASIO ; j'y ajouterai un chapitre dès que j'en aurai l'occasion.
On verra aussi s'il y a d'autres choses intéressantes dans cette bibliothèque.
Citer : Posté le 27/04/2014 20:48 | #
Je trouve qu'on ne voit pas tellement le fait que les fonctions sprintf et sscanf marchent avec des chaînes et pas des flux, un novice pourrais se tromper et vouloir utiliser sprintf pour afficher du texte et sscanf pour faire une saisie.
Après c'est mon point de vue mais je trouve qu'on ne voit pas assez cette différence.
Citer : Posté le 27/04/2014 20:54 | #
D'ailleurs, ci je me souviens bien, il y a une fonction fprintf() qui gère des flux. Serait-il possible de l'utiliser pour l'écran de la casio ?
Coïncidence ? Je ne pense pas.
Citer : Posté le 27/04/2014 20:56 | #
C'est pour des fichiers donc je ne pense pas.
Citer : Posté le 27/04/2014 20:58 | #
Non, pas de fprintf ni de fscanf.
@Alphacreator: c'est noté, j'insisterai sur les chaînes de caractères.
Ajouté le 27/04/2014 à 21:21 :
Done.
J'ai insisté sur les chaînes de caractères avec sprintf() et sscanf(), et ajouté une section sur realloc() avec celle sur calloc().
Ajouté le 27/04/2014 à 22:22 :
Mise à jour.
J'ai ajouté une section sur les fonctionnalités avancées proposées par qsort() et bsearch().
Ajouté le 27/04/2014 à 22:37 :
Encore autre chose.
Les valeurs absolues et les divisions euclidiennes.
Citer : Posté le 28/04/2014 00:57 | #
Lephenixnoir tu n'as vraiment que ça à faire ?
Toutes les formules de Première S.
Toutes les formules de Terminale S.
Un programme de calculs.
Super Mario 3
warrior
Jump Ball
First Fly
►Jeu gagnant des 48h CPC n°12◄
Mon site de discussion pour ados : http://entre-ados.net/ (a brûlé dans l'incendie d'OVH)
Mon éditeur de cours en ligne et plateforme de partage : http://wordline.xyz (a succombé à la concurrence de Google Drive...)
Citer : Posté le 28/04/2014 08:05 | #
Lephenixnoir tu n'as vraiment que ça à faire ?
Jamais.
Mais c'est bien de changer un peu de temps en temps, alors autant en profiter pour rédiger quelque chose d'utile (donc pas sur le wiki manifestement ).
Et moi qui me désole de voir des manipulations de chaînes de caractères avec des boucles de copie caractère par caractère (au lieu de sprintf()), ou des lectures d'entiers dans des chaînes avec des algorithmes compliqués, là ou atoi() suffit, j'ai pensé que faire profiter tout le monde des merveilles de la bibliothèque standard était une idée digne d'intérêt.
Et puis les fonctions comme qsort() et bsearch() sont incroyablement puissantes, alors pourquoi ne jamais s'en servir ?
Citer : Posté le 28/04/2014 14:21 | #
ptr = calloc(10*sizeof(int),sizeof(int));
tu n'as pas alloué 160 octets (donc 40 int)
si tu veux allouer 10 int(40 octets), je pense qu'il fait faire:
ptr = calloc(10sizeof(int));
après , je peux me tromper
Citer : Posté le 28/04/2014 14:24 | #
Non, regarde bien il y a deux arguments puisque c'est calloc().
Le premier c'est la taille à allouer, 10*sizeof(int), et le deuxième c'est la taille d'un élément.
C'est pour que la fonction puisse les initialiser à 0 qu'elle a besoin de ce second paramètre.
Citer : Posté le 28/04/2014 14:32 | #
mince je me suis trompé, je voulais plutot
ptr = calloc(10,sizeof(int));
d'après le sdz,
Voici son prototype :
Le premier argument est le nombre d'éléments qu'on souhaite pouvoir stocker en mémoire et le deuxième est la taille de ces éléments que l'on obtient avec l'opérateur sizeof().
donc pour 10 elements de type int(un tableau de 10 int),
il faut calloc(10,sizeof(int));
Citer : Posté le 28/04/2014 14:44 | #
Oups ^^'
Tiens, oui.
Je corrige ça dans l'instant.
Merci. C'est vrai que ça faisait des allocations énormes.
Citer : Posté le 28/04/2014 20:39 | #
Ajouté aux tutoriels de qualité
Citer : Posté le 28/04/2014 20:52 | #
Merci
Par contre ce serait bien si tu pouvais mettre un 'd' à "standard". ^^'
Citer : Posté le 28/04/2014 20:53 | #
Exact
Citer : Posté le 28/04/2014 22:19 | #
et donc on peux utiliser sprintf comme les guillemets en basic(retour a la ligne ?)
Je suis de l'autre coté de la manche maintenant. Yay.
Citer : Posté le 28/04/2014 22:33 | #
Comment ça ?
Ajouté le 28/04/2014 à 22:34 :
D'ailleur, lephenix, tu peux mettre des balises [ label=mon_label ] pour mettre un lien interne, et [ target=mon_label ] pour y renvoyer (dans le menu )
Citer : Posté le 29/04/2014 01:47 | #
et donc on peux utiliser sprintf comme les guillemets en basic(retour a la ligne ?)
Ces deux fonctions ont exactement le même rôle que printf() et scanf(), mais ne vont pas lire/écrire via les flux d'entrée/sortie standard, mais dans des chaînes de caractères.
Il n'y a donc aucune sortie à l'écran ni aucune demande de saisie, tout se passe dans des variables.
je pense que Lephenixnoir a été clair, mais au cas où. Non, les guillemets en basic écrivent dans un flux virtuel qui n'est pas disponible en C, les flux sont un peux comme des sortes de listes sans réelles limites qui sont normalisée et partagées par plusieurs fonctions, par exemple un flux de caractères est commun a la console et les fonctions print en C, mais on doit passer par d'autres méthodes en C sur casio.
-en C:
-Un pong.
-Un projet en pause. Je compte le reprendre de temps en temps: Summer Cursed
-mon tuto sur les Str
Mes calto: G25+, G75
Mon minecraft en dévelopement
-Portal2D de JavierXD
-CalCraft de Wime
-GeekBros du groupe GeekBrothers (Eiyeron,Siapran,KevKevVTT,Adbook,LIMachi)
Citer : Posté le 29/04/2014 10:53 | #
Merci Dark Storm, c'est ajouté.
@Gollum, si tu fais un sprintf() vide, rien ne se passera, puisque la fonction n'écrira rien dans ta variable.
Ajouté le 29/04/2014 à 13:10 :
En fait, les flux d'entrée/sortie standard existent avec le C Casio: stdin, stdout et stderr.
Seulement on les manipule habituellement avec les fonctions fprintf()/fscanf(), fgetc()/fgets(), fputc()/fputs() etc... qui ne sont pas supportées.
En revanche, le type FILE lui, l'est.
Peut-être que la documentation donne des indications sur l'utilisation de ces flux ?
Ajouté le 29/04/2014 à 13:15 :
Rien dans la doc de CASIO.
Le dernier fichier est le mode d'emploi de Renesas.
900 pages... je lirai tout ça un jour, on pourrait en apprendre pas mal sur le fonctionnement de nos chères calculatrices.