Le langage C pour gcc (GNU/Linux 64 bits)
LE langage C a été mis au point vers 1970 par Ritchie et Thompson dans le cadre du développement de UNIX. GNU/Linux étant un des nombreux avatars de ce système d’exploitation, ce langage y est très important, surtout qu’il est beaucoup plus rapide que les langages interprétés comme PHP, Python et Javascript puisqu’il est compilé pour être directement exécuté par le processeur, sans passer par un interpréteur. Le compilateur de référence sous GNU/Linux est gcc.
Attention !
- Cette page est écrite pour mon usage personnel, tant mieux si elle peut vous être utile ;
- elle se base sur les spécificités du compilateur gcc (12.2) d’un système GNU/Linux 64 bits ;
- elle est loin d’être complète, notamment les points marqués short, void() et short() ;
- vous restez de toute façon responsables des conséquences de ce que vous faites.
Documentation officielle : www.gnu.org/software/gnu-c-manual/gnu-c-manual.html
1. Organisation du script .c
1.1 Exemple minimal 2. Variables et constantes
2.1 Généralités |
3. Syntaxe
3.1 Séparateurs 4. Structures
4.1 Logique et comparaisons |
A. stdio : Console et fichiersB. stdlib : exit(), malloc() et rand()C. unistd : sleep(), droits...D. math : Arrondis, log, exp, trigoE. complex : Nombres complexesF. ctype : CaractèresG. string : ChaînesH. time : Temps |
1. Organisation du fichier source .c
1.1 Exemple minimal
Considérons cette application minimale très souvent utilisée pour l’exemple :
# include <stdio.h> // exemple canonique void main() { printf("Hello, World!\n") ; /* affiche une phrase */ }
- la première ligne fait appel à une bibliothèque, en l’occurrence la bibliothèque stdio.h, celle des «standard inputs/outputs» (console et fichiers). Cette directive doit être précédée de # (hashtag)
- ce qui est situé après // est un commentaire, il n’interagit pas avec l’application
- main() est la fonction principale de l’application.
- void devant main() signifie que cette fonction ne retourne ici aucune valeur
- les parenthèses () permettent la réception d’éventuels paramètres, inutiles dans ce cas simple. Pour les paramètres passés à la fonction main(), voir fonctions
- ce qui est situé entre /* et */ est également un commentaire :
/* peut ouvrir un commentaire pouvant comporter plusieurs lignes, qui sera fermé par */
- les accolades { et } contiennent le bloc des instructions de la fonction main()
- printf(), fonction issue de la bibliothèque stdio, permet l’affichage d’une chaîne
- \n en fin de la chaîne Hello, world! permet le saut de ligne en fin d’affichage (pour Windows et Atari, il s’agit de \r\n, pour Amiga et OS9, c’est \r)
- les points-virgules ; séparent les différentes instructions (calculs, appels de fonction…) et terminent chaque ligne d’instruction. Ils sont interdits après les parenthèses de réception de paramètres ou d’expression logique et après l’accolade de fin de bloc.
1.2 Compilation
Pour compiler une telle application, il faut sauvegarder le fichier (par exemple sous le nom essai.c et disposer d’un compilateur (le compilateur gcc existe pour tous les systèmes actuels, mais il faudra probablement l’installer). Pour compiler le script en un programme exécutable nommé a.out :
cd chemin_du_fichier_sourceprompt de la console$gcc essai.cprompt de la console$
Il s’exécute tout simplement avec `pwd`/a.out ou ./a.out si le chemin actuel du répertoire (pwd) contient le fichier exécutable, chemin_connu_de_vous/a.out sinon.
Notes :
gcc essai.c -o monprog produit le fichier monprogprompt de la console$gcc produit un fichier avec les droits d’exécution ; si ce n’est pas le cas :prompt de la console$chmod 744 a.out ou chmod 744 monprog sur un système UNIXprompt de la console$- aucun message n’apparaît dans la console si tout se passe bien
- certaines bibliothèques ne sont pas prises en compte s’il manque l’option -lm (voir math.c) :
gcc essai.c -lm -o monprogprompt de la console$
Note : la compilation d’un programme en C avec interface graphique est plus délicate, voyez cette page pour GTK4.
1.3 Directives
Les directives #include et #define (macros) se placent avant la fonction principale main().
#include (bibliothèques)
La directive #include permet d’appeler les headers de bibliothèques nécessaires au fonctionnement du programme. Exemple :
#include <stdio.h> // entrées et sortie (console et fichiers) #include <math.h> // puissances, algorithmes, trigonométrie…
Voir les appendices sur les bibliothèques stdio, stdlib, math, complex qui décrivent les fonctions qu’elle contiennent.
Cette directive permet également d’inclure des fichiers de bibliothèques écrites en C. Il faut alors entourer (le chemin et) le nom du fichier de guillemets "" :
#include "mylibs/semiCplx.c" // ma bibliothèque sur les nombres semi-complexes
#define (macros)
Directive donnée au préprocesseur permettant un remplacement du mot défini par la valeur qui suit dans tout le script.
#define COPYLEFT "GPLv3" #define PI 3.1415926
Il est de coutume d’utiliser des majuscules pour la chaîne à remplacer, mais ce n’est pas une obligation. Il ne s’agit pas d’une affectation (pas de =), et aucun point-virgule ne peut être écrit après. Des parenthèses doivent entourer les expressions composées :
#define PI (22./7)
Avec paramètre :
#define xchg(type, x, y) { type aux =x ; x =y ; y =aux ; }
interprétera toute expression de type xchg(int, a, b) en int dummy =a ; a =b ; b =dummy ;, échangeant les valeurs des deux variables d’un type défini, passé en premier paramètre.
Cela ressemble à une fonction, mais ce n’en est pas une : xchg(float, c, d) sera effectivement remplacé avant compilation par float dummy =c ; c =d ; d =dummy ; à l’intérieur du script. [à vérifier : semble ne pas être ennuyé par des homonymies de variables]
2. Variables et constantes
2.1 Généralités
Une variable est destinée à contenir une valeur, qui pourra être modifiée, d’où le nom.
Noms de variables
Un nom de variable peut contenir des lettres latines sans diacritique (accents, cédille), des chiffres et des soulignés _, sans commencer par un chiffre ni contenir d’espace ni de caractères spéciaux. Majuscules et minuscules définissent des variables différentes : var, Var, VAR, vaR peuvent coexister en représentant des valeurs différentes.
Certains mots sont réservés, car utilisés par la syntaxe C : auto break case char const continue default do double else enum extern float for goto if inline int long register restrict return short signed sizeof static struct switch typedef union unsigned void volatile while
Noms corrects de variable : s, blA, A4, Luna3, _hum, L_3c et même _ tout seul.
Noms incorrects de variable :
- çà : la cédille et l’accent sont des diacritiques, interdits
- 3ponts : utilisation interdite d’un chiffre en première position
- int : mot réservé
- a-v : le trait d’union sera interprété comme signe «moins» de soustraction
- a&b : les caractères spéciaux autres que _ sont interdits
Déclaration et type
On attribue une valeur à une variable (ou on la modifie) à l’aide d’un (seul) signe égal. Cette valeur a un type (nombre, caractère…), qu’il faut déclarer avant toute utilisation, parce que le type a une influence sur la façon de coder la valeur et sur la quantité de mémoire utilisée.
int var ; var =3 ; déclare var comme variable entière, puis lui attribue la valeur 3
int var =3 ; déclare var comme variable entière en lui attribuant en même temps la valeur 3
Il est possible d’affecter la même valeur à plusieurs variables avec :
int x, y, z ; x =y =z =5 ;
…où x vaut ce que z puis y valent, à savoir 5
typedef
typedef int milli ; définit milli comme alias (synonyme) de int, ce qui veut dire que milli mes1 ; équivaut à int mes1 ;, ce qui assure une homogénéité de type et évite la question «quel est le type de ce que je déclare en général dans tel cas ?». Il s’agit d’une aide mnémotechnique.
2.2 Locales, globales, statiques
Variables locales ou globales
Une variable est définie pour le bloc de sa définition et ses sous-blocs : le code qui suit initialise trois variables différentes de même nom, valables à trois niveaux différents.
int x =7 ; if(1) { int x =5 ; for(int x =0 ; x < 4 ; x++) { printf("%d\n", x) ; } printf("%d\n", x) ; } printf("%d\n", x) ;
Attention : l’omission de int dans for(x =0 ; x < 4 ; x++) ramène la valeur de la variable x à 0 et sera à4 au sortir de la boucle (deuxième print()).
Pour résumer, une variable vaut pour tous les blocs inclus sauf si une autre variable de même nom y est initialisée (avec int, float, char, etc.). Une variable initialisée en dehors de toute fonction, y compris main(), est a priori globale.
Variables statiques
Si une variable initialisée en dehors de toute fonction est globale, une variable initialisée dans une fonction est réinitialisée à chaque appel. Pour conserver la valeur acquise par une variable au fil des appels, elle doit être initialisée avec static.
#include <stdio.h> void myfunc() { int var1 =0 ; static int var2 =0 ; printf("var1 : %d -- var2 : %d\n", var1, var2) ; var1++ ; var2++ ; } void main() { for (int i=0 ; i < 3 ; i++) { myfunc() ; } }
…retourne :
var1 : 0 -- var2 : 0 var1 : 0 -- var2 : 1 var1 : 0 -- var2 : 2
Pointeurs
Pour toute variable var, &var contient l’adresse en mémoire où se situe la variable : &var est le pointeur de la variable var. Ce pointeur peut être nécessaire à certaines fonctions pour agir sur plusieurs valeursifier la valeur. printf() utilise %p pour l’expression du pointeur, sous forme hexadécimale :
int var1 =1234567890 ; printf("Adresse : %p\n", &var1) ;
Si l’on connaît l’adresse d’une variable, type *adr permet d’en récupérer la valeur. Attention : il est nécessaire d’en connaître le type, pour savoir le nombre d’octets à considérer.
Pour définir une variable contenant un pointeur, il faut utiliser *, le type devant être égal à celui de la variable pointée. Pour une indirection, c’est-à-dire chercher la valeur indiquée à l’adresse contenue dans le pointeur, on utiliser également * :
#include <stdio.h> #include <string.h> void main() { int var =1234567890 ; int *ptr =&var ; printf("Val. : %d\n", var) ; printf("Adr. : %p\n", &var) ; printf("Adr. : %p\n", ptr) ; printf("Val. : %d\n", *ptr) ; }
Pour expliquer la nécessité de préciser le type de la variable pointée, rappelons les façons dont une valeur (3 en l’occurrence) remplit la mémoire selon que la variable est short, int ou long :
adr adr+1 adr+2 adr+3 adr+4 adr+5 adr+6 adr+7 short 0 3 int 0 0 0 3 long 0 0 0 0 0 0 0 3
2.3 Types entiers
Rappel : ces types et leur codage (1, 2, 4 et 8 octets) sont valables pour le compilateur gcc sur système GNU/Linux 64 bits ; cela peut changer avec d’autres configurations ou d’autres compilateurs. Pour être sûr de la taille en octets d’un type d’entier :
printf("%d\n", sizeof(char)) ; printf("%d\n", sizeof(short)) ; printf("%d\n", sizeof(int)) ; printf("%d\n", sizeof(long)) ;
char
char sert à déclarer un octet qui vaut pour un caractère (il ne s’agit nullement de chaîne).
char e, z ; indique que les variables e et z sont de type «caractère»
char e =127 ; déclare que la variable e (déclarée précédemment) a pour valeur 127
char a ='b' ; déclare le type 'caractère' pour la variable a (à ne faire qu’une fois) et y affecte la valeur 'b'
En WIN-1252 ou ISO-8859-1 (script et console) :
#include <stdio.h> void main() { char car1 ='b', car2 =99 ; printf("1er car =%c, 2nd car= %c\n", car1, car2) ; }
…imprime 1er car =b, 2nd car= c
Certains caractères doivent être codés avec le caractère d’échappement \ :
- \0 pour l’octet 0 de fin de chaîne
- \b pour le retour en arrière
- \t pour la tabulation
- \n pour le saut de ligne
- \' pour le guillemet simple : '\''
- \\ pour l’antislash \
- \" pour le guillemet double " à l’intérieur d’une chaîne
signed char
Valeurs numériques entières codées sur huit bits, de -128 à +127
unsigned char
Valeurs numériques entières positives codées sur huit bits, de 0 à +255
En WIN-1252 ou ISO-8859-1 (source et console) :
#include <stdio.h> void main() { unsigned char car =233 ; printf("%c\n", car) ; }
…imprime é
Attention ! la valeur -1 est codée 11111111 en mémoire, de la même façon que 255 «non signé» :
#include <stdio.h> void main() { unsigned char car =-1 ; printf("%d\n", car) ; }
…retourne 255
(char)var ou (char)43.156 transforme une valeur en type char (sans changer le contenu de la variable). L’exemple suivant tronque la valeur à 43 et affiche donc le caractère + :
printf("%c\n", (char)43.989) ;
Les chaînes sont des tableaux de caractères
Type short
Le type unsigned short code les entiers sur 16 bits, -32 768 à 32 767
printf("%hd", varnum) pour la sortie d’un short signé (%h pour short, %s étant utilisé pour les chaînes ('string') de caractères.
Le type unsigned short code les entiers positifs sur 16 bits, de 0 à 65 535.
printf("%hu", varnum) pour la sortie d’un short non signé
Attention : les nombres de 32 768 à 65 535 d’un entier court non signé sont codés de la même façon que les nombres de -32 768 à -1.
#include <stdio.h> void main() { short ent =-1 ; printf("%hu\n", ent) ; }
…retourne 65535. Ce comportement se retrouve chez les int et les long
(short)var ou (short)43.156 transforme une valeur en type short (sans changer le contenu de la variable). L’exemple suivant renvoie -43 en non signé, soit 65493 :
printf("%h\n", (unsigned short)-43) ;
Type int
Le type int permet le codage des valeurs entières sur 32 bits, de -2 147 483 648 à 2 147 483 647 ; par exemple :
int ent 2000000000 ;
printf("%d", varnum) pour la sortie d’un entier signé.
Le type unsigned int permet le codage des valeurs entières positives sur 32 bits, de 0 à 4 294 967 295 ;par exemple :
unsigned int entpos 4000000000 ;
printf("%u", varnum) pour la sortie d’un entier non signé.
La note concernant les entiers short est valable pour les int.
(int)var ou (int)23 transforme variable ou nombre en type int.
(int)var ou (int)43.156 transforme une valeur en type int (sans changer le contenu de la variable). L’exemple suivant affiche 5.4321e3 en entier (32bits), soit 5432 :
printf("%d\n", (int)5.4321e3) ;
Type long
Le type long permet le codage des valeurs entières sur 64 bits, de -9 223 372 036 854 775 807 à 9 223 372 036 854 775 807
Le type unsigned long permet le codage des valeurs entières positive sur 64 bits, de 0 à 18 446 744 073 709 551 615
La note concernant les entiers short est valable pour les long.
(long)var ou (long)23 transforme variable ou nombre en type long.
(long)var ou (long)43.156 transforme une valeur en type long (sans modifier le contenu de la variable). L’exemple suivant renvoie -5432 :
printf("%ud\n", (long)-5.4321e3) ;
2.4 Nombres à virgule flottante
Les nombres à virgule flottante permet d’approcher les nombres rationnels et réels. Pour rappel, une division entre deux entiers est entière. Il est possible d’indiquer qu’un nombre n’est pas entier en le terminant par un point : 5. /2.
Type float
Le type float permet de coder des valeurs non entières en 32 bits : un bit pour le signe, huit bits pour l’exposant et 23 pour la mantisse. Avec 24 bits de mantisse (le premier bit d’un binaire est toujours de 1, il est donc implicite), on ne peut être certain que des sept premiers chiffres décimaux.
printf("%f", 22/7.) permet une sortie limitée à six décimales : 3.142857
printf("%.3f\n", 22/7.) un point suivit d’un nombre avant le f précise le nombre de décimales : 3.143
printf("%7.3f\n", 22/7.) prévoit 7 caractères en tout avec le point et les trois décimales :
3.143
printf("%e\n", 6789.1234) retourne un résultat en notation exponentielle : 4.568123e+03, qui peut combiner les variantes précédentes.
Attention à bien utiliser "%f" pour la sortie des nombres à virgule flottante :
#include ≶stdio.h> void main() { float reel =1 ; printf("%f\n", reel) ; printf("%d\n", reel) ; }
Le résultat montre que le codage d’un float réinterprété en tant qu’entier n’a pas de sens :
1.000000 -1001762144
(float)var ou (float)123456789e23 transforme une valeur en type float (sans modifier le contenu de la variable). L’exemple suivant retourne 1.234568e+31 :
printf("%e\n", (float)123456789e23) ;
Type double
Valeurs codée en 64 bits : 1 bit pour le signe, 11 bits pour l’exposant, 52 pour la mantisse, pour une précision d’environ 16 chiffres décimaux.
double dbl =22 /7. ; printf("%.20lf\n", dbl) ;
retourne 3.14285714285714279370, soit 17 chiffres significatifs. "%le" et leurs variantes existent.
À vérifier : Les bibliothèques mathématiques travaillent par défaut en double.
(double)var ou (double)123456789e23 transforme une valeur en type double (sans modifier le contenu de la variable). L’exemple suivant retourne 10000000000000000 :
printf("%.0f\n", (double)10e15) ;
typedef
typedef permet de définir des alias de types :
typedef float F définit F comme alias de float
typeof
typedef (var1) var2 permet d’initialiser var2 du type de var1, ce qui peut se justifier dans une macro qui doit déterminer les types des variables :
#define mx() ({ typeof(a) _a =(a); typeof(b) _b =(b) ; _a > _b ? _ : _b ; })
2.5 Constantes
Les constantes se comportent comme des variables, si ce n’est que leur valeur ne peuvent pas être modifiées.
const type variable valeur définit une constante :
const float PI=3.14159268 ;
Les constantes sont souvent définies avec des majuscules, mais ce n’est pas obligatoire.
enum
enum var{ NOM1, NOM2, NOM3, NOM4 } attribue les valeurs entières successives à des constantes à partir de 0.
enum var{ CINQ=5, SIX, SEPT, HUIT } attribue des valeurs entières successives à partir de 5.
2.6 Tableaux
(Service minimum)
Un tableau est une structure consistant en une suite de données de même type, qui se déclare sous la forme type nomdevariable[longueur] :
int nbr[5] ; // déclare un tableau de cinq entiers
La longueur est le nombre de cellules incluses dans le tableau, chacune marquées de 0 à longueur -1. Les cinq emplacements pour ces cinq entiers sont donc accessibles dans les cellules nbr[0] à nbr[4] :
nbr[0] =43 ;
Il est possible de déclarer des valeurs entre accolades lors de la déclaration du tableau, qui prennent place à partir du décalage 0. [4] = définit directement le cinquième décalage :
int nbr[5] ={ 14, 3, 5, [4] =43 } ; for(int i =0 ; i < 5 ; i++) { printf("%d\n", nbr[i]) ; }
…donne :
14 3 5 0 43
…la quatrième valeur nbr[3], qui n’a pas été définie, vaut 0.
La longueur d’un tableau peut être implicite :
short courts[] ={ 1, 2, 3, [9] =99 } ; for(int i =0 ; i < 10 ; i++) { printf("%d\n", courts[i]) ; } printf("%zu\n", sizeof courts)
Attention :
- un décalage au delà de la fin d’un tableau renvoie une valeur qui n’a pas été initialisée à 0 ; le compilateur ne renvoie ni erreur ni avertissement.
- la taille en mémoire d’un tableau est le nombre d’éléments fois leur grandeur. Pour 5 éléments short (deux octets), ce sera 10 sizeof tab.
- le nombre d’éléments d’un tableau est sa taille en mémoire (point précédent) divisé par sizeof *tab (avec astérisque) :
int tab[] ={ 5, 4, 3, 2, 1 } ; for(int i =0 ; i < sizeof tab /sizeof *tab ; i++) { printf("tab[%d] : %d\n", i, tab[i])) ; }
Il est également possible d’accéder aux éléments d’un tableau par un pointeur
int tab[] ={ 5, 3, 4, 1, 2 } ; for(int i =0 ; i < sizeof tab /sizeof *tab ; i++) { printf("tab[%d] : %d\n", i, *(tab +i)) ; }
2.7 Chaînes de caractères
Une chaîne de caractères est en C un tableau de caractères dont le dernier est l’octet 0, représenté par '\0'
Une façon simple de définir une chaîne est :
char ch[] ="L'hiver sera froid" ;
ou
char *ch ="L'hiver sera froid" ;
qui comptera 18 caractères plus \0 de fin de chaîne.
Pour réserver de l’espace pour une chaîne de n caractères :
char chaine[18] ;
…où chaine est une constante permettant l’inclusion de n caractères + le caractère de fin de chaîne.
Il semble impossible de réserver une longueur de chaîne avec une variable, il faut passer par une allocation de mémoire avec malloc() de la bibliothèque <stdlib.h>. C’est nécessaire lorsqu’on veut réserver exactement ce qu’il faut pour copier une chaîne dans une autre :
#include <stdio.h> #include <stdlib.h> // pour malloc() #include <string.h> // pour strcpy() void main() { char *ch1 ="Avanti populo" ; int lon1 =strlen(ch1) ; char *ch2 =malloc(lon1 +1) ; strcpy(ch2, ch1) ; printf("%s\n", ch2) ; }
scanf() et fgets(), qui permettent la saisie de caractères, ajoute l’octet 0 de fin de chaîne.
2.8 struct
Envisagé.
2.9 union
Non encore envisagé.
3. Syntaxe
3.1 Séparateurs
"" entoure les chaînes de caractères
'' entoure les caractères pour un type char
( ) entoure les paramètres de réception des fonctions, l’expression logique d’une structure conditionnelle ou d’une boucle, le protocole du compteur de for() ; permet de modifier les priorités des opérandes
{ } entoure un bloc pour une fonction, boucle ou structure conditionnelle ; également utilisée pour les valeurs d’un tableau
[ ] entoure l'entier de l'adressage à l'intérieur d'un tableau
; doit suivre chaque instruction, mais pas les blocs { } ni les ( ) de réception de paramètres des fonctions ou des conditions d’entrée des structures
3.2 Opérateurs classiques
La fonction d’affichage printf() est traitée à la section stdio.
= est utilisé pour l’affectation variable =valeur
int var =43 ;
+ est l’addition. v =v +2 peut se résumer en v +=2 et v =v +1 en v++
- est la soustration. v =v -2 peut se résumer en v -=2 et v =v -1 en v--
Note : v++ s’appelle une incrémentation. Dans certains cas, il peut être intéressant de pré-incrémenter ou post-incrémenter. Dans le bloc qui suit :
int i =0 ; printf("%d\n", ++i) ; i =0 ; printf("%d\n", i++) ;
La première sortie sera de 1 parce que l’incrémentation a lieu avant l’affichage, la seconde de 0 parce que l’incrémentation a lieu après l’affichage. Il en va de même avec la pré-décrémentation --i et la post-décrémentation i--.
* est la multiplication. v =v *2 peut se résumer en v *=2
Note : * est également utilisé comme préfixe pour l'adressage.
/ est la division. v =v /2 peut se résumer en v /=2.
Note : une division de deux entiers donne le résultat de la division entière. Il est possible de rendre la division «réelle» avec un point qui suit le diviseur entier :
int i =5 ; printf("%d\n", i /2) ; printf("%f\n", i /2) ; printf("%f\n", i /2.) ; printf("%d\n", i /2.) ;
donnera
2 0.000000 2.500000 -1938914656
Dans le premier et troisième cas, le paramètre d’affichage correspond à la nature de la valeur, respectivement un entier 32 bit et un «réel». Dans les autres cas, le langage interprète les données comme il peut, sans s’assurer de l’adéquation des données.
Note : les autres opérations, qui concernent la trigonométrie, les puissances et logarithmes ainsi que les arrondis nécessitent la bibliothèque math ; les nombres complexes sont gérés par la bibliothèque complex.
3.3 Adressage
* préfixe une variable dont le contenu est une adresse un point en mémoire
& adresse (préfixe de variable)
Note : & sert également pour la comparaison de nombres bit à bit (voir point suivant).
4. Structures
4.1 Logique et comparaisons
Les tests ci-dessous renvoient 0 si le test est faux, et 1 si le test est vrai. Il sont très utilisés en début de condition et de boucles.
== est utilisé pour le test d’égalité
!= est utilisé pour le test de différence
Rappel : = seul est utilisé pour l’affectation variable =valeur.
< teste si le premier terme est strictement plus petit
<= teste si le premier terme est plus petit ou égal
> teste si le premier terme est strictement plus grand
>= teste si le premier terme est plus grand ou égal
prop1 && prop2 «et» logique : vrai si les deux propositions sont vraies. Exemple :
- 3 < 5 && 4 < 7 renvoie 1 (vrai)
- 3 < 5 && 4 > 7 renvoie 0 (faux)
prop1 || prop2 «ou» logique : vrai si au moins une des deux propositions est vraie. Exemple :
- 3 < 5 || 4 > 7 renvoie 1 (vrai)
- 3 > 5 || 4 > 7 renvoie 0 (faux)
! est l’inversion logique : !(3<5) donne 0 (faux, l’inverse du vrai).
Les symboles & et | comparent les nombres bit à bit :
9 & 10 renvoie 8 : seuls les bits 1 communs subsistent
9 | 10 renvoie 11 : il suffit d’un bit 1 à chaque rang
& 9 : %1001 | 9 : %1001 10 : %1010 10 : %1010 8 : %1000 11 : %1011
4.2 Condition if - else
if(condition) { instruction(s) } permet l’exécution d’instructions que si la condition est vraie.
if(condition) { instruction(s) } else { instruction(s) } permet d’exécuter les instruction du bloc après else uniquement si la condition est fausse.
int a =5 ; if(a < 7) { printf("a est plus petit que 7") ; } else { printf("a n’est pas plus petit que 7") ; }
Les instructions des blocs doivent être suivies du point-virgule ; mais pas la ou les condition(s) entre parenthèses. Il est possible d’enchaîner plusieurs structures if - then :
a =5 ; if (a < 5) { printf("%c est plus petit que 5\n", a) ; } else if (a > 5) { printf("%c est plus petit que 5\n", a) ; } else // Sinon… { printf ("%c égale 5") ; }
Opérateur conditionnel ternaire
Il s’agit d’une structure if - else simplifiée permettant une affectation sous condition :
short i = 3 < 5 ? 43 : 87 ; résume :
short i ; if(3 < 5) { i =43 ; } else { i =87 ; }
4.3 Structure switch - case
La structure switch / case est un test d’égalité d’une variable avec plusieurs caractères ou valeurs numériques qui suivent le mot-clé case et déclenchent les instructions correspondantes.
Les cas prévoient des nombres ou des caractères, mais pas des conditions (comme <, >, ==...). Il ne s’agit dont pas vraiment d’une suite de if.
La structure ci-dessous teste l’égalité de la variable var avec les valeurs numériques 1, 2 et 3.
switch(var) { case 1 : instruction(s) ; break ; case 2 : instruction(s) ; break ; case 3 : instruction(s) ; break ; default : instruction(s) ; }
- break doit terminer chaque cas. En cas de récursion, commencer par les cas le plus probables permet d’optimiser une application.
- default (facultatif) exécute du code si aucun des case n’est rencontré. Il s’agit de la dernière ligne de la structure.
Si un cas rencontré n’est pas terminé par break, les suivants sont également exécutés, jusqu’à un break. Essayez avec a, b, c et d :
#include <stdio.h> void main () { printf("Taper un caractère :\n") ; char var =getchar() ; switch(var) { case 'a' : printf("A minuscule\n") ; break ; case 'b' : printf("B minuscule\n") ; case 'c' : printf("C minuscule\n") ; break ; default : printf("ni A, ni B ni C\n") ; } }
4.4 Boucle while
Boucle qui se maintient tant que la condition est vraie.
int i =0 ; while(i < 10) { printf("%d est plus petit que 10", i) ; i++ ; }
Pour que la boucle ne soit pas infinie, il est nécessaire de prévoir une condition de sortie ou de faire évoluer le compteur avec i++ ou tout autre pas.
Souvent en suite d’une condition rencontrée, deux mots réservés peuvent modifier le parcours de la boucle :
- continue retourne au début de la boucle
- break sort de la boucle
4.5 Boucle do - while
La condition est évaluée à la fin et doit être suivie d’un point-virgule ;
int i =0 ; do { printf("%d est plus petit que 10") ; i++ ; } while (i < 10) ;
Souvent en suite d’une condition rencontrée, deux mots réservés peuvent modifier le parcours de la boucle :
- continue retourne en fin de boucle
- break sort de la boucle
4.6 Boucle for
La boucle for utilise une récursion à partir d’un compteur qui fixe un départ pour une variable, une condition à respecter et un pas de progression en fin d’itération. La forme la plus commune est :
for(int i =depart ; i < fin ; i++) { instructions ; }
La mise en œuvre sera la table de multiplication par 7 (la plus difficile) :
for(int i =0 ; i < 10 ; i++) { printf("%d x 7 =%d", i, i *7) ; }
Le plus souvent à la suite d’une structure conditionnelle, deux mots réservés peuvent modifier le parcours de la boucle :
- continue retourne directement au début de la boucle
- break sort de la boucle
On peut évidemment prendre une autre valeur de départ, une autre condition et un autre pas, éventuellement négatif.
for (int v =35 ; v > 7 ; v -=5) { printf("%d\n", v) ; }
Il est possible de faire intervenir plusieurs variables dans la définition de la boucle :
for (int i =5, j =1 ; i > j ; i++, j +=2) { printf("i: %d -- j: %d\n", i, j) ; }
- i est initialisé à 5, j à 1
- la boucle continue tant que i est strictement supérieur à j
- à la fin de chaque itération, i est incrémenté d’1, j de 2
4.7 Fonctions
Une fonction est une structure nommée définie une fois et qui peut-être appelée de tout endroit d’un programme. Une fonction simple serait :
long carre(int c) { return c *c ; } printf("%ld\n", carre(88)) ;
- long indique que la valeur retournée par la fonction sera un entier de huit octets
- carre est le nom de la fonction
- (int c) initialise la variable de réception, qui ne vaut alors que pour le bloc, même si une autre variable c existe ailleurs dans le programme.
- return c *c ; retourne le résultat de c multiplié par lui-même
- il suffit donc d’un long res =carre(9) ; pour initialiser la variable res à 81
La fonction doit être définie avant tout appel ; si ce n’est pas le cas, il faut déclarer un prototype qui s’écrivait initialement de la sorte :
long carre(int) ;
Cela ne semble pas fonctionner aussi simplement avec gcc. En attendant, placez vos fonctions avant l’appel, par exemple dans une bibliothèque à inclure.
Paramètres
C’est ainsi que l’on nomme l’ensemble des valeurs passées à une fonction. Chaque variable recevant une valeur (x, y et z) doivent être typées.
float fois_plus(int x, int y, float z) { return (x +y) *z ; } printf("%.5f\n", fois_plus(3, 5, 1.1)) ;
Récursivité
Une fonction peut s’appeler elle-même, mais il faut prévoir une condition de sortie et veiller à ce qu’elle soit rencontrée. La plus célèbre est la factorielle, qui s’appelle elle-même en multipliant un nombre avec son précédent jusqu’à arriver à 1
#include <stdio.h> int facto(int n) { if (n == 1) { return 1 ; } else { return n *facto(n -1) ; } } void main() { printf("%d\n", facto(5)) ; }
main() fonction principale
main() est une fonction presque comme les autres, elle peut recevoir des paramètres de l’application appelante.
void main(int argc, char *argv[]) permet de récupérer les arguments fournis par la console. argc contient la longueur du tableau argv, ce dernier contenant au moins une chaîne de caractères. Voici une boucle pour afficher les paramètres passés lors de l’appel en console du programme et les transmettre au programme appelant :
#include <stdio.h> int main(int argc, char *argv[]) { for(int i =0 ; i < argc ; i++) { printf("%s\n", argv[i]) ; } return 0 ; }
Pour la commande ./monapp -v quiet saisie sur une console, la première chaîne (argv[0]) contient le nom de l’application ./monapp, les deux suivantes -v et quiet, ce qui fait que argc vaut 3 (il vaut toujours au moins 1, à savoir le nom de l’application). Note : argc et argv sont les noms de variable canoniques, cela pourrait être n_arg et tab_arg, voire narg et targ.
int main() indique que la fonction renvoie un entier : elle doit alors se terminer avec return 0 ; convention pour annoncer au logiciel appelant que le programme s’est bien déroulé. Il est possible de prévoir d’autres nombres à retourner selon les situations, à mentionner dans la documentations sur l’application. Il est sinon possible d’écrire void main() sans return 0 ;
A. stdio - Sorties et entrées
Les fonctions de cette section figurent dans la bibliothèque stdio, dont l’appel doit se faire en début de script avec :
# include <stdio.h>
Console
Sorties console
putchar()
putchar(car) envoie un caractère sur la sortie console uniquement
puts()
puts(car) envoie une chaîne de caractères sur la sortie console uniquement
putc() et fputc()
int c =putc(car, stdout) ; envoient un caractère à la console ; cela pourrait être dans un fichier si on remplace stdout par un identifiant de fichier
printf()
printf("Bonjour, monde!\n") ; affiche une chaîne sur la console. Le retour-chariot, symbolisé par \n, doit être précisé le cas échéant. printf() permet le formatage des nombres et des chaînes qu'il affiche :
printf("%s\n", "chaîne") ; affiche une chaîne, avec saut de ligne
printf("%hd\n", -1) ; affiche un entier 16 bits (h pour short) signé
printf("%hu\n", 65535) ; pour un 16 bits non signé (unsigned)
printf("%d\n", -1) ; affiche un entier 32 bits (int) signé
printf("%u\n", 65535) ; pour un 32 bits non signé (unsigned)
printf("%ld\n", -1) ; affiche un entier 64 bits (l pour long) signé
printf("%lu\n", 18446744073709551615) ; pour un 64 bits non signé (unsigned)
Notes :
- %d, %u, %hd, %hu, %ld et %lu affichent en base décimale
- %b, %hb et %lb convertissent en binaire, avec les chiffres 0 et 1
- %o, %ho et %lo convertissent en octal (base 8), avec les chiffres de 0 à 7
- %x, %hx et %lx convertissent en hexadécimal (base 16 avec les «chiffres» supplémentaires a à f, valant de 10 à 15) ; %X pour ces lettres en majuscules
- pour aligner des nombres, réserver une longueur de chaîne pour l’affichage, le surplus étant comblé par des espaces ou 0 à gauche :
- printf("%5d\n", 3) affiche 3
- printf("%05o\n", 343) affiche00527
- %p renvoient une adresse en hexadécimal minuscules
- il est possible d’afficher un nombre signé en non signé et réciproquement :
- printf("%hd", 65535) retourne -1
- printf("%hu", -1) retourne 65535
Attention : printf("%lb", -1) et printf("%lx", -1) ne renvoient curieusement que des 32 bits (version 12.2.0, x86_64-linux-gnu, Debian)
float r =5 /3. ; printf("%f", r) ; retourne 1,666667
float r =5 /3. ; printf("%.3f", r) ; retourne 1,667 arrondissant à la troisième décimale
float r =5 /3. ; printf("%.20f", 16.8425) ; retourne 1.66666662693023681641 permettant de consater que la précision d’un float ne dépasse guère sept chiffres significatifs
printf("%7.3f\n", 22/7.) un point suivi d’un nombre avant le f précise le nombre de décimales : 3.143
printf("%e\n", 6789.1234) retourne un résultat en notation exponentielle : 4.568123e+03, qui peut combiner les variantes précédentes.
double r =5 /3. ; printf("%.20lf", r) ; retourne 1.66666666666666674068, soit une précision d’environs 16 chiffres significatifs
%f et %lf arrondissent, mais la précision n’est pas très grande :
printf("%.4f\n", 26.82605) ; // retourne 26.8260 printf("%.4f\n", 26.82615) ; // retourne 26.8261 printf("%.4f\n", 26.82625) ; // retourne 26.8263 printf("%.4f\n", 26.82635) ; // retourne 26.8264
sprintf()
chaine =sprintf(formatage, nombre) écrit dans une chaîne, aux mêmes formats que printf() ci-dessus, ce qui permet la transformation d’une expression numérique en chaîne. Pour la transformation de chaînes numériques en nombres, voir ato~la bibliothèque stdlib.
Entrées console
getchar()
int c =getchar() ; lit un caractère sur la console ; c doit être initialisé comme entier parce qu’il peut valoir -1 pour une erreur, qui ne peut être égal à 255 non signé qui est un caractère
getc() et fgetc()
int c =getc(stdin) ; et int c =fgetc(stdin) ; lisent un caractère à partir de la console, ou d’un fichier si stdinest remplacé par un identificateur de fichier
fgets()
fgets() permet de contrôler le nombre de caractères en entrée et remplace donc gets(), abandonné car vulnérable aux attaques par dépassement de capacité (overflow)
char chaine[20] ; fgets(chaine, sizeof chaine, stdin) ; printf("%s\n", chaine) ;
scanf() lit des données, en utilisant le formatage de et en réservant préalablement les variables réceptrices. Un retour-chariot termine la saisie, mais il est possible de saisir les deux nombres sur la même ligne en les séparant d’une tabulation ou espace. scanf() renvoie un entier représentant le nombre de variables saisies :
int age, n ; float taille ; n =scanf("%d %f", &age, &taille) ; printf("Age: %d ; Taille: %.3f (%d vars)\n", age, taille, n) ;
Les chaînes fonctionnent de la même manière, sauf que chaine étant déjà un pointeur, il ne faut pas le précéder de &) :
char chaine[20] ; scanf("%s", &chaine) ; printf("%s\n", chaine) ;
Deux inconvénients :
- si le nombre de caractères saisit dépasse le nombre d’octets réservés, il y aura une erreur de segmentation
- la chaîne enregistrée par la variable se limitera à ce qui est saisi en deçà d’une tabulation ou espace
Il est possible d’y remédier en insérant un nombre dans l’expression %s (%10s) et de remplacer le s par l’expression [^\n] (%[^\n]). On peut combiner les deux :
char chaine[20] ; scanf("%10[^\n]", chaine) ; printf("%s\n", chaine) ;
Fichiers
Redirections
Note : l’appel à une application avec > sorties.txt ou 1 > sorties.txt redirige les sorties consoles (printf) de l’application vers ce fichier. La redirection 2 > erreurs.txt crée un fichier d’erreurs.
./monprog 1 > sorties.txt ./monprog 2 > erreurs.txt
La redirection >> accumul.txt ajoute les sorties de l’application au fichier > accumul.txt, le créant au besoin.
./monprog >> accumul.txt
La redirection < entree.txt capture le fichier > entree.txt.
./monprog < fichier.txt
Macros
FOPEN_MAX nombre maximal de fichiers ouverts simultanément, probablement 16.
EOF !vérifier
Modes d’ouverture de fichiers
fopen()
fopen("nom", mode) ouvre un fichier selon un nom et un mode. Il existe deux sortes de fichiers :
- texte : les caractères imprimables et quelques caractères de contrôle : tabulation, fin de ligne, etc.
- binaire : les 256 octets sont acceptés
Les modes d’ouverture sont :
- "r" ouverture en lecture d’un fichier texte existant
- "r+" ouverture en lecture et écriture d’un fichier texte existant, la position du pointeur est à 0
- "w" création d’un fichier en écriture, en écrasant un éventuel fichier de même nom
- "w+" création d’un fichier texte en écriture et lecture, en écrasant un éventuel fichier de même nom
- "a" création ou ouverture d’un fichier texte en mode «ajout», position du pointeur à la fin du fichier
- "a+" création ou ouverture d’un fichier texte en lecture (pointeur en 0) ou en mode «ajout» (pointeur à la fin du fichier)
Pour les fichiers binaires, utiliser "rb", "rb+", "wb", "wb+", "ab" et "ab+"
Il est nécessaire de déclarer un identifiant de fichier, qui recevra un entier à l’ouverture. C’est lui qui permettra d’agir sur le contenu du fichier et d’ensuite le fermer. S’il est nul, c’est par exemple que :
- l’application ne bénéficie pas des droits nécessaires à la lecture du fichier, à moins que ce dernier n’existe simplement pas
- le fichier ouvert en écriture ou ajout a été enregistré préalablement en mode lecture seule, ou est situé dans un répertoire où l’application n’a pas les droits d’écriture
FILE * IdFich ; IdFich = fopen("fichier.txt", "a") ;
stdin est l’identificateur de l’entrée console, stdout celui de la sortie console. Il est donc possible de rediriger le flux venant d’un fichier avec stdin et vers un fichier pour une sortie console avec stdout.
Note : voir Entrées pour l’utilisation de putc() et putc(), et Sorties pour l’utilisation de getc(), fgetc() et fgets(). Rappel :
- int c =putc(car, IdFich) ; et int c =fputc(car, stdout) ; envoient un caractère dans un fichier si on remplace stdout par un identifiant de fichier
- int c =getc(IdFich) ; et int c =fgetc(IdFich) ; lisent un caractère dans un fichier
- fgets(tampon, sizeof tampon, IdFich) permet de charger un nombre précis de caractères en entrée :
char chaine[20] ; fgets(chaine, sizeof chaine, stdin) ; printf("%s\n", chaine) ;
fprintf()
fprintf(IdFich, chaine) écrit dans un fichier ouvert en écriture ou ajout
fclose() et fflush()
fclose(IdFich) ferme proprement le fichier après avoir effectivement transcrit les données qui étaient dans la mémoire-tampon. Retourne une valeur non nulle si cela n’a pas été fait.
fflush(IdFich) permet d’écrire le contenu de la mémoire-tampon sans fermer le fichier tout de suite.
if (fflush(IdFich)) { fprintf(stderr, "Ne peut transférer la mémoire-tampon " "dans le fichier\n") ; }
Exemple d’utilisation d’une séquence fopen() / fprintf() / fclose() en mode «ajout»
#include <stdio.h> #include <stdlib.h> // contient 'exit(0)' void main() { FILE *IdFich ; IdFich = fopen("fichier.txt", "a") ; printf("%u\n", IdFich) ; if (IdFich ==NULL) { printf("Pas moyen d'ouvrir le fichier 'fichier.txt'\n") ; exit(0) ; } fprintf(IdFich, "Ajout dans le fichier\n") ; fclose(IdFich) ; }
Erreurs
ferror(FichId) contient la dernière erreur concernant un identifiant de fichier
perror() affiche sur stderr une texte associé à la dernière erreur selon errno
clearerr(FichId) remet à 0 l’indicateur d’erreur pour un identifiant de fichier
Exemple de traitement d’erreur :
#include <errno.h> #include <stdio.h> #include <stdlib.h> void main() { FILE * FichId =fopen("fichier.txt", "r") ; int ret =fgetc(FichId) ; printf("%d %d %d\n", ret, ferror(FichId), errno) ; perror("") ; ret =fclose(FichId) ; }
Ici tout se passe bien, le fichier.txt existe et n’est pas vide
- ret contient 65, le premier caractère de fichier.txt)
- ferror(FichId) contient 0, absence d’erreur
- errno contient le numéro d’erreur : 0
perror() renvoie donc Success.
Remplacer fgetc(FichId) par fputc('w', FichId) déclencherait un traitement d’erreur, puisque le fichier est ouvert en lecture, et non en écriture :
- ret contiendait -1, retour de fputc() qui ne peut être exécuté
- ferror(FichId) contiendrait 1, présence d’erreur
- errno contiendrait le numéro d’erreur 9
Préciser perror("Verdict") afficherait (:) :
Verdict : Bad file descriptor
Déplacement dans un fichier
ftell(FichId) ; retourne la position courante dans un fichier. En mode binaire, c’est en octets, en mode texte, cela dépend de l’encodage (moins de caractères que d’octets en UTF-8 ou écritures extrême-orientale).
fseek(FichId, deplac, lieu) ; déplace la position dans le fichier
- deplac est un déplacement en octets pour les fichiers binaires
- lieu
- SEEK_SET ou 0 à partir du début du fichier, le déplacement ne peut être que positif
- SEEK_CUR ou 1 à la position courante, déplacement positif ou négatif, dans la mesure du possible
- SEEK_END ou 2 à partir de la fin du fichier, le déplacement ne peut être que négative
feof(FichId) est vrai si le pointeur de fichier est positionné à EOF (fin de fichier).
Avec la variable pos initialisée avec fpos_t pos ;
fgetpos(FichId, &pos) ; retourne la position courante dans
fsetpos(FichId, &pos) ; fixe la position courante avec pos
B. stdlib - Mémoire et hasard
#include <stdlib> en début de fichier
abs() retourne la valeur absolue
atoi(), atol() et atoll() retourne le nombre entier écrit dans une chaîne ("43" vers 43). Attention : toute chaîne non conforme renvoie 0, qui peut également être le résultat de chaîne "0".
strtol, strtoll, strtold, strtoul, lstrtod, strtof fait la même chose, mais retournant une chaîne à partir du premier caractère erroné dans l’énoncé du nombre :
#include <stdio.h> #include <string.h> #include <stdlib.h> void main () { char str[15] ="203.3.5" ; // "203,4" ; "20.3f" char erreur[15] = {0}, *c = erreur ; float nf= strtof(str, &c) ; printf("%f\n", nf) ; if (strlen(c) > 0) { printf("`%s` pose problème\n", c) ; } }
Pour l’opération inverse, transformer un nombre en chaîne, voir sprintf() de la bibliothèque stdio.
B.1 exit()
exit() ; termine un programme
abort() ; termine un programme de façon plus brutale
B.2 Mémoire
À préciser.
malloc - allocation de mémoire
La fonction type adresse =(type *) malloc(type n) avec un type entier réserve n octets de mémoire et renvoie l’adresse du premier octet de la plage réservée (NULL en cas de mémoire indisponible). adresse est une variable
Pour 100 int, malloc(sizeof(int) *100)
calloc - allocation de mémoire
calloc(n, sizeof(type)) ; alloue n élément d’un certain type. Pour réserver un bloc mémoire pour 100 float :
int i ; int * pointer = (int *) calloc(100, sizeof(float));
realloc() réallocation de mémoireb
void *realloc(void *ptr, type nv) change la taille de l’objet pointé par ptr, qui prend l’adresse du nouvel espace mémoire alloué ou NULL en cas d’échec.
free liberation de mémoire
free(p) libère une plage de mémoire réservée
B.3 Générateur aléatoire
rand nombre pseudo-aléatoire
int rand() retourne un entier pseudo aléatoire compris entre 0 et RAND_MAX.
void srand() intitialise ou réinitialise la graine aléatoire pour la fonction rand()
C. unistd : sleep
sleep(sec) attend le nombre de secondes indiqués.
#include <stdio.h> #include <unistd.h> void main() { printf("Début\n") ; sleep(3) ; printf("Fin\n") ; }
usleep(sec) attend le nombre de micro-secondes indiqués.
Gestion des répertoires : rmdir(), chdir(), fchdir(), getcwd()... fichiers : chown(), fchown(), lchown()... et processus, logins...
Sur GNU/Linux ou autres UNIX, voir le fichier de headers /usr/include/unistd.h où les fonctions sont brièvement décrites.
D. math : trigo, log, arrondis
Les fonctions de cette section figurent dans la bibliothèque math, dont l’appel doit se faire en début de script avec :
# include <math.h>
Les fonctions de cette bibliothèque traitent les nombres au format double. Il suffit de les suffixer de f ou l pour obtenir les fonctions qui traitent les nombres au format float ou long double.
Il est recommandé d’ajouter l’option -lm pour la compilation :
gcc essai.c -lm -o monprog
Macros
HUGE_VAL est la plus grande valeur pour un double, ainsi que HUGE_VALF et HUGE_VALL
INFINITY valeur infinie
NAN non valeur (Not A Number)
M_E constante e : 2.718282…
M_PI constante π : 3.141593…
Arrondis
round(x) ; retourne un float arrondi à l’unité
floor(x) ; retourne l’arrondi inférieur
ceil(x) ; retourne l’arrondi supérieur
trunc(x) ; retourne la partie entière
modf(x, y) ; retourne parties entière et fractionnaire :
double entier, fract ; entier = modf(9876.5432, &fract) ; printf("%d - %.6f\n", (int)fract, entier) ;
fmin(x, y) ; retourne le minimum de deux valeurs à virgule flottante
fmax(x, y) ; retourne le maximum de deux valeurs à virgule flottante
fdim(x, y) ; retourne la différence positive de deux valeurs à virgule flottante
fmod(x, y) ; retourne la différence positive entre les valeurs absolues
Exponentielles et logarithmes
exp(x) ; retourne la constante e à la puissance x
expm1(x) ; même chose, moins un
exp2(x) ; retourne une puissance de 2
exp2m1(x) ; même chose, moins 1
exp10(x) ; retourne une puissance de 10
exp10m1(x) ; même chose, moins 1
pow(x, y) ; retourne x à la puissance y. Si y n’est pas entier, x ne peut pas être négatif
sqrt(x) ; retourne la racine carrée de x. x ne peut pas être négatif
cbrt(x) retourne la racine cubique d’une valeur
log (x) ; retourne le logarithme naturel (en base e) de x strictement positif
log1p(x) ; équivaut à log(1+x)
log2(x) ; retourne le logarithme en base 2
log10(x) ; retourne le logarithme en base 10 de x strictement positif
frexp(x) ; retourne une fraction et une puissance de deux, facteurs de x :
double fract ; int exp ; fract = frexp(9876.5432, &exp) ; printf("%.6lf, %d\n", fract, exp) ;
ldexp(x, y) ; retourne x * exp2(y)
logb(x) ; retourne n tel que exp2(n) <= x
scalbln(x, n) idem précédent?
Trigonométrie
Les angles sont reçus ou retournés radians.
sin(x) ; retourne le sinus de x
cos(x) ; retourne le cosinus de x
tan(x) ; retourne la tangente x
Le domaine de définition est l’intervalle [-1, 1].
asin (x) ; retourne l’arcsinus de x
acos (x) ; retourne l’arccosinus de x
Sans restriction du domaine de définition :
atan(x) ; retourne l’arctangente de x
atan2(x, y) ; retourne l’angle de la pente y /x
toutes les fonctions trigonométriques sauf atan2 ont une version hyperboliques avec le suffixe h
La bibliothèque math dispose aussi de fonctions pour les complexes, à voir dans la librairie suivante.
E. Nombres complexes
Les fonctions de cette section figurent dans la bibliothèque complex, dont l’appel doit se faire en début de script avec :
# include <complex.h> // lien vers la bibliothèque
La déclaration et l’affectation d’un complexe en double float :
double _Complex res, z =2.3 + 4.5 *I ;
La compilation du fichier doit comporter l’option -lm :
gcc essai.c -lm -o monprog
Les fonctions de cette bibliothèque traitent les nombres au format double. Il suffit de les suffixer de f ou l pour obtenir les fonctions qui traitent les nombres au format float ou long double.
Par défaut, les deux termes d’un complexe seront double, à condition de préciser un point décimal :
_complex z =2.3 + 4.5 *I ;
Macros
z =CMPLX(r, i) fabrique un complexe double à partir de deux réels
z =CMPLXF(r, i) fabrique un complexe simple float à partir de deux réels
z =CMPLXL(r, i) fabrique un complexe long double à partir de deux réels
type complex var =reel + imag*I ; déclare et mobilise deux réels pour en faire un complexe
type imaginary var =reel*I ; déclare un imaginaire pur et y affecte une valeur (ne semble pas fonctionner avec gcc 12.2.0)
Définitions
creal(z) retourne la partie réelle d’un complexe, ou le nombre réel lui-même passé en paramètre
cimag(z) retourne comme réel la partie imaginaire d’un complexe, 0. pour un nombre réel
conj(z) retourne le conjugué d’un complexe, soit creal() -cimag()
cabs(z) et carg() retournent la valeur absolue ou l’argument d’un complexe
Exposants et logarithmes
cexp(z) retourne l’exponentielle, basée sur e, d’un complexe
cpow(z, p) retourne la puissance complexe d’une valeur complexe
csqrt(z) retourne la racine carrée d’un complexe
clog(z) retourne le logarithme népérien (ou naturel) d’un complexe
Trigonométrie
csin(z) retourne le sinus d’un complexe
ccos(z) retourne le cosinus d’un complexe
ctan(z) retourne la tangente d’un complexe
casin(z) retourne l’arcsinus d’un complexe
cacos(z) retourne l’arccosinus d’un complexe
catan(z) retourne l’arctangente d’un complexe
csinh(z) retourne le sinus hyperbolique d’un complexe
ccosh(z) retourne le cosinus hyperbolique d’un complexe
ctanh(z) retourne la tangente hyperbolique d’un complexe
casinh(z) retourne l’arcsinus hyperbolique d’un complexe
cacosh(z) retourne l’arccosinus hyperbolique d’un complexe
catanh(z) retourne l’arctangente hyperbolique d’un complexe
cproj(z) projette un point d’un plan sur la surface d’une sphère de Riemann ; le résultat est le plus souvent z lui-même
F. ctype : caractères
Les fonctions de cette section figurent dans la bibliothèque ctype, dont l’appel doit se faire en début de script avec :
# include <ctype.h> // lien vers la bibliothèque
Transformations
tolower('c') transforme une majuscule en minuscule
toupper('c') transforme une minuscule en majuscule
Tests
isalnum('c') est vrai en cas de caractère alpha-numérique
isalpha('c') est vrai en cas de caractère alphabétique
isdigit('c') est vrai pour les chiffre
isxdigit('c') est vrai en cas de chiffre hexadécimal (chiffres + a-f + A-F)
islower('c') est vrai en cas de lettre minuscule
isupper('c') est vrai en cas de lettre majuscule
isblank('c') est vrai si le caractère est blanc (\t et espace)
ispunct('c') est vrai en cas de ponctuation (affichables moins alphanumériques)
isspace('c') est vrai en cas de caractère d’espacement (\t, \r,\n, \v, \f et espace)
iscntrl('c') est vrai pour un caractère de contrôle (< ascii 32 + 127)
isprint('c') est vrai en cas de caractère est affichable (non contrôle, avec esppace)
isgraph('c') est vrai en cas de représentation graphique (non contrôle sauf espace)
G. string : Chaînes
Rappels :
- une chaîne de caractères est un tableau d’octets (char) terminé par l’octet nul (représenté par \0).
- #include <string.h> appelle cette bibliothèque pour les fonctions qui suivent
size_t =strlen() retourne la longueur d’une chaîne, sans l’octet nul de fin de chaîne
Copies
strcpy() copie une chaîne dans une autre, qui est écrasée : attention si ch1 est plus courte que ch2
char ch1[] = "Chaîne", ch2[] ="0123" ; strcpy(ch1, ch2) ; printf("%s\n", ch1) ;
strncpy(ch1, ch2, n) limite la copie à n caractères. Attention : si la taille de ch2 est plus petit que n, l’octet nul de fin de chaîne n’est pas copié, et la nouvelle chaîne garde la longueur de la première.
char ch1[] = "Chaîne", ch2[] ="0123" ; strcpy(ch1, ch2, 2) ; printf("%s\n", ch1) ;
…donne 01aîne
strdup() réserve de la mémoire et duplique la chaîne de caractères passée en paramètre. Pour libérer la mémoire, utiliser free() de la bibliothèque stdlib
char *chaine ="ornithorynque" ; char *copie =strdup(chaine) ; printf("%s\n", copie) ; free(copie) ;
strndup(chaine, n) duplique n caractères de la chaîne passée en paramètre.
Concaténations
strcat(chaine, ch1) ajoute une chaîne à la suite d’une autre. Pour assembler deux chaînes, opérer à partir d’une troisième d’une longueur équivalente à la somme des deux :
char ch1[] ="Chaîne", ch2[] ="0123", chaine[sizeof ch1 + sizeof ch2 -1] ={ 0 } ; strcat(chaine, ch1) ; strcat(chaine, ch2) ; printf("%s\n", chaine) ;
…donnera
strncat() limite l’ajout à n caractères.
char ch1[] ="Chaîne", ch2[] ="0123", chaine[sizeof ch1 + sizeof ch2 -1] ={ 0 } ; strcat(chaine, ch1) ; strncat(chaine, ch2, 2) ; printf("%s\n", chaine) ;
Comparaisons
strcmp(ch1, ch2) renvoie -1 si ch1 se classe avant ch2, 1 pour le cas inverse, et 0 si les deux chaînes sont identiques
strncmp(ch1, ch2, n) permet de comparer les n premiers caractères de deux chaînes
strcoll() permet de comparer deux chaînes en tenant compte de la localisation (librairie locale.h requise) :
#include <stdio.h> #include <locale.h> #include <string.h> void main() { setlocale(LC_COLLATE, "fr_FR.utf8") ; char *s1 ="bète" ; char *s2 ="bétail" ; printf("%d\n", strcoll(s1, s2)) ; }
Attention :
- ce tri n’est pas naturel : il y a une hiérarchie entre les diacritiques d’une même lettre
- la réponse est négative (pas nécessairement -1), positive (pas nécessairement 1) ou nulle (nécessairement 0)
Recherche
strchr() retourne l’offset de la première occurrence d’un caractère dans une chaîne de caractères
strrchr() recherche la dernière occurrence d’un caractère dans une chaîne de caractères.
strstr() recherche la première occurrence d’une sous-chaîne dans une chaîne de caractères principale.
strpbrk() renvoie la première position d’un caractère commun parmi les caractères d’une autre chaîne
strerror() renvoie la chaîne de caractères associée à un code d’erreur stocké dans la variable entière errno
strtok() permet d’extraire, un à un, tous les éléments syntaxiques (les tokens) d’une chaîne de caractères.
strxfrm() La fonction strxfrm transforme les n premiers caractères de la chaîne source en tenant compte de la localisation en cours et les place dans la chaîne de destination.
Mémoire
memccpy() transfère un bloc de mémoire orig de n octets dans un second cible :
memcpy(cible, orig, n) ;
memchr() Recherche la première occurrence d’une valeur dans un bloc de mémoire.
memcmp() permet de comparer le contenu de deux blocs de mémoire.
memcpy() permet de copier un bloc de mémoire dans un second bloc.
memmove() permet de copier un bloc de mémoire dans un second, mais fonctionne même si les deux blocs se chevauchent.
memset() permet de remplir une zone mémoire, identifiée par son adresse et sa taille, avec une valeur précise.
H. time : temps
Les fonctions de cette section figurent dans la bibliothèque ctype, dont l’appel doit se faire en début de script avec :
# include <time.h> // lien vers la bibliothèque
Données
CLOCKS_PER_SEC retourne le nombre de «ticks» par seconde (par exemple 1 000 000)
clock_t moment = clock() ; la variable moment contient le nombre de ticks consommés par l’application depuis son lancement. Il ne s’agit pas du temps écoulé sur une montre, une application ne consommant pas toujours du temps CPU
L’empreinte temporelle «C» est définie comme suit :
time_t empreinte =time(NULL) ; printf("%ld seconde(s) depuis 1970.01.01 à 0h00:00\n", empreinte) ;
Pour des raisons de portabilité, il faut utiliser difftime() pour être sûr de convertir la différence entre deux empreintes temporelles en secondes, pas toujours exprimées dans cette unité
#include <stdio.h> #include <time.h> void main() { time_t deb = time(NULL) ; for (long i =0 ; i < 1500000000 ; i++) { } time_t fin = time(NULL) ; int sec = (int)difftime(fin, deb) ; printf( "Finished in %ld sec\n", sec) ; }
struct tm est une structure contenant les informations de la date et l’heure d’une empreinte temporelle. L’exemple interroge le temps local connu de la machine il tourne ; le temps commence en 1900.
#include <stdio.h> #include <time.h> void main() { time_t empreinte =time(NULL) ; struct tm *Tps = localtime(&empreinte) ; printf("%04hd.%02hd.%02hd %02hdh%02hd:%02dh\n", Tps->tm_year +1900, Tps->tm_mon+1, Tps->tm_mday, Tps->tm_hour, Tps->tm_min, Tps->tm_sec) ; }
gmt_time() retourne le temps universel UT (le changement de dénomination de GMT à UT s’est fait en 1972, le nom de fonction est resté).
mktime() convertit une structure temporelle en empreinte temporelle.
Conversions en chaînes
asctime() convertit une structure en chaîne de caractères au format Www Mmm dd hh:mm:ss yyyy
ctime() convertit une empreinte en chaîne au format Www Mmm dd hh:mm:ss yyyy
strftime() convertit une structure en une chaîne de caractères, avec pour formatage :
%Y (%G) l’année complète, sur 4 chiffres jusqu’au 31 décembre 9999 à 23:59:59 ou 60
%y (%g) l’année sur deux chiffres, de 00 à 99
%C nombre de siècles entiers parcourus, ce qui précède l’année sur deux chiffres
%m mois en décimal, de 01 à 12
%b (%h) nom abrégé du mois Jan
%B nom entier du mois January
%d jour du mois, de 01 à 31
%e jour du mois, de 1 à 31
%u jour de la semaine (1 à 7) avec lundi =1
%w jour de la semaine (0 à 6) avec le dimanche =0
%j jour de l’année, de 001 à 366
%a nom abrégé du jour de la semaine, Mon
%A nom entier du jour de la semaine, Monday
%U numéro de la semaine, de 00 à 53, avec le premier dimanche commençant la semaine 01
%W (%V?) numéro de la semaine, de 00 à 53, avec le premier lundi commençant la semaine 01
%H heure sur 24h, de 00 à 23
%k heure sur 24h, de 0 à 23 (une espace remplace le 0)
%I heure sur 12 heures, de 01 à 12
%l heure sur 12 heures, de 1 à 12 (une espace remplace le 0)
%p AM ou PM
%P am ou pm
%M minutes, de 00 à 59
%S Second (00-61) 02
%c date et heure sous la forme Mon Jan 01 01:23:45 2024
%x (%D) date sous la forme %m/%d/%y
%F date sous la forme %Y/%m/%d (format ANSI)
%X (%T) heure sous la forme %H:%M:%S
%R heure sous la forme %H:%M
%s nombre de secondes depuis 1970-01-01 00:00:00.
%z décalage UT + heure d’été
%Z nom de zone CDT (par ex. CEST)
%n retour à la ligne
%t tabulation
%% le signe «pourcent»
%r semble annuler la séquence qui suit
Non-utilisés : %f, %i, %o, %q, %v, %E, %J, %K, %L, %N, %O, %Q
Rappel : expérimentation sur gcc (12.2) sur GNU/Linux Debian Bookworm 64 bits.
Procédé :
#include <stdio.h> #include <time.h> void main() { time_t empreinte =time(NULL) ; struct tm *bribes ; bribes =localtime(&empreinte) ; char chaine[30] ; strftime(chaine, 30, "%Y-%m-%d à %Hh%M:%S", bribes) ; printf("%s\n", chaine) ; }