0, 010, 0x0, 0U,
0L, '0', 0.0, 0e0,
"0", const, enumchar, short, int, long,
float, double, signed,
unsignedvoid main, conseil d'achat de livreLes littéraux constants, comme les variables, ont tous un type de base. Des notations particulières permettent de préciser le type d'un littéral.
#define A 'A' /* entier (le code du caractère A) */ #define APOSTROPHE '\'' /* entier (le code du caractère ') */ #define A_LA_LIGNE '\n' /* entier (le code du caractère \n) */ #define UN 1 /* entier */ #define DIX 10 /* entier */ #define DIXo 012 /* entier octal */ #define DIXh 0xA /* entier hexadécimal */ #define MOINS_UN (-1) /* entier */ #define UN_NON_SIGNE 1U /* entier non signé */ #define GRAND_NON_SIGNE 65535U /* entier non signé */ #define UN_LONG 1L /* entier long */ #define PETIT_LONG (-2147483647L-1)/* entier long */ #define UN_LONG_NON_SIGNE 1UL /* entier long non signé */ #define PI 3.1415927 /* flottant double précision */ #define c 3e8 /* flottant double précision */ #define PI_FLOAT 3.1415927F /* flottant simple précision */ #define PI_LONG_DOUBLE 3.1415927L /* flottant long double précision */ #define CHAINE_VIDE "" /* chaîne de caractères */ #define HELLO_WORLD "hello, world" /* chaîne de caractères */
En C les littéraux caractères (comme 'A') sont de type
int ! En C++ ils sont de type
char. Mais dans les deux cas ce sont des nombres entiers qui
valent le code du caractère entre guillemets.
Certains caractères peuvent ou doivent être représenté dans un source C sous la forme d'une séquence d'échappement :
|
|
'\r' est utilisé
sur MS-DOS avant le caractère newline '\n'.
On parle alors de «CRLF».-ansi.'\0'.Si on a besoin d'une collection de constantes entières de même type, on peut utiliser une énumération :
enum couleurs { noir, blanc, rouge=3, bleu, vert };
int go(enum couleurs feu) { return feu == vert; }
Les identificateurs de l'énumération sont des constantes de type
int. Par défaut elles sont numérotées à partir de 0. Ici on a
noir=0, blanc=1, rouge=3,
bleu=4, vert=5.
constconst int un = 1;
int main(void) { return un; }
ANSI C a introduit le mot-clé
const. Il ne sert cependant pas à déclarer de véritables
constantes. Il indique simplement qu'une variable ne sera pas modifiée.
En particulier, une variable const ne peut pas être utilisée
là où une expression constante est attendue (taille de tableau en
ANSI C, label de case).
À la place on utilise des macros ou des énumérés (pour les constantes entières).
En C++ les variables constantes sont devenues utilisables comme de vraies constantes. Elles restent cependant toujours des variables ; elles se trouvent notamment à une adresse en mémoire vive.
La notation (-2147483647L-1) est nécessaire pour obtenir
-2147483648 car en 32 bits on ne peut pas écrire -2147483648L.
En effet, le compilateur verrait l'opérateur unaire - appliqué à
2147483648L, or le plus grand long 32 bits
possible est 2147483647L.
Les types de base sont des types de données directement manipulables par les processeurs.
| nom complet | nom usuel | bits (typique) | bits (minimum) | limites formelles |
|---|---|---|---|---|
char | char | 8 | 8 | 0 à 255 ou -127 à 127 |
signed char | signed char | 8 | 8 | -127 à 127 |
unsigned char | unsigned char | 8 | 8 | 0 à 255 |
signed short int | short | 16 | 16 | -32767 à 32767 |
unsigned short int | unsigned short | 16 | 16 | 0 à 65535 |
signed int | int | 32 | 16 | -32767 à 32767 |
unsigned int | unsigned | 32 | 16 | 0 à 65535 |
signed long int | long | 32 | 32 | -2147483647 à 2147483647 |
unsigned long int | unsigned long | 32 | 32 | 0 à 4294967295 |
float | float | 32 | ¹ | 6 décimales ² |
double | double | 64 | ¹ | 10 décimales ² |
long double | long double | 80 | ¹ | 10 décimales ² |
¹ N'a pas vraiment de sens.
² Et d'autres exigences.
Voici un programme utilisant tous les types de base :
#include <stdio.h>
int main(void)
{
char c = 'é'; /* 11101001 */
signed char sc = -23; /* 11101001 */
unsigned char uc = 233; /* 11101001 */
short si = -12345; /* 1100111111000111 */
unsigned short usi = 53191U; /* 1100111111000111 */
int i = -12345; /* 11111111111111111100111111000111 */
unsigned int ui = 53191U; /* 00000000000000001100111111000111 */
long li = 1073741824L; /* 01000000000000000000000000000000 */
unsigned long uli = 1073741824UL;/* 01000000000000000000000000000000 */
float f = 3.14159265358979323846264;
double d = 3.14159265358979323846264;
long double ld = 3.14159265358979323846264L;
printf("%c %d %u\n", c, sc, uc);
printf("%d %u %d %u %ld %lu\n", si, usi, i, ui, li, uli);
printf("%.24g\n%.24g\n%.24Lg\n", f, d, ld);
return 0;
}
Les conversions entre types de base sont automatiques.
On peut écrire float f = 'a'; ou char c = 2.23;.
Ce n'est pas du meilleur style, mais le compilateur fait simplement les
conversions qui s'imposent. Même dans une conversion perdant des bits ou de
la précision le compilateur n'émet généralement aucun avertissement.
Pour C un char n'est qu'un petit nombre entier.
Écrire char c='A'; ou char c=65; revient au même
si 'A' est codé en ASCII.
Le type int correspond au fameux mot machine, c'est celui
sur lequel les calculs entiers sont les plus efficaces. Les calculs sont
faits au moins dans la précision de int. Autrement dit, si on
additionne par exemple deux short, alors ils sont d'abord promus
(automatiquement convertis) en int avant que l'addition ait lieu
en int et produise un int.
Bien sûr si un type est plus grand qu'un int alors la promotion
se fait vers le type le plus grand.
Les types int et long sont souvent équivalents
(32 bits) sur les processeurs actuels. Sur les anciens processeurs,
int et short étaient souvent équivalents (16 bits).
C99 a ajouté un type long long d'au moins 64 bits.
Le type char est parfois signé, parfois pas, ça dépend des
systèmes. C'est pour cela que signed char n'est pas forcément
équivalent à char.
| Opérateur | Opération |
|---|---|
objet . membrepointeur -> membrepointeur [ expr ] expr ( expr,... ) lvalue ++lvalue --
|
sélection de membre sélection de membre indexation appel de fonction post-incrémentation post-décrémentation |
sizeof exprsizeof ( type ) ++ lvalue-- lvalue~ expr! expr- expr+ expr& lvalue* expr( type ) expr |
taille d'objet taille de type pré-incrémentation pré-décrémentation complément de bits négation moins unaire plus unaire adresse de déréférence conversion de type (cast) |
expr * exprexpr / exprexpr % expr
|
multiplication division modulo |
expr + exprexpr - expr
|
addition soustraction |
expr << exprexpr >> expr
|
décalage de bits à gauche décalage de bits à droite |
expr < exprexpr <= exprexpr > exprexpr >= expr
|
plus petit plus petit ou égal plus grand plus grand ou égal |
expr == exprexpr != expr
|
égal différent |
expr & expr | et de bits |
expr ^ expr | ou exclusif de bits |
expr | expr | ou de bits |
expr && expr | et logique |
expr || expr | ou logique |
lvalue = exprlvalue *= exprlvalue /= exprlvalue %= exprlvalue += exprlvalue -= exprlvalue <<= exprlvalue >>= exprlvalue &= exprlvalue |= exprlvalue ^= expr
|
assignation multiplication et assignation division et assignation modulo et assignation addition et assignation soustraction et assignation décalage à gauche et assignation décalage à droite et assignation et de bits et assignation ou de bits et assignation ou exclusif de bits et assignation |
expr ? expr : expr | expresion conditionelle |
expr , expr | virgule de séquence |
Ce tableau inspiré du livre The C++ programming language de Stroustrup.
expr signifie toute expression au résultat d'un type acceptable. lvalue signifie toute expression retournant un objet modifiable d'un type acceptable.
Les opérateurs unaires, d'assignation et l'expression conditionelle
s'associent de droite à gauche, les autres de gauche à droite. Cela
signifie que a=b=c équivaut à a=(b=c) alors que
a+b+c équivaut à (a+b)+c.
Les opérateurs sur des bits et le modulo ne s'appliquent que sur des opérandes de type entier.
| Opérateurs | Commentaires |
|---|---|
pointeur -> membre |
L'expression |
pointeur [ expr ] |
Par définition if (i["hello"]) 3[t] = 123; |
lvalue ++lvalue --
|
La précédence supérieure de ces opérateurs à l'opérateur de
déréférencement while (*s++ = *t++); |
lvalue ++lvalue --++ lvalue-- lvalue
|
Il faut être prudent avec ces opérateurs car modifier deux fois la même
lvalue sans point de séquencement intermédiaire cause un
résultat indéfini, comme dans |
sizeof expr |
Il est très rare de voir cet opérateur utilisé sans parenthèses, même si avec une expression elles sont optionnelles. |
sizeof ( type ) |
Par définition, |
~ expr |
|
expr / exprlvalue /= expr
|
Entre nombres entiers |
expr % exprexpr %= expr
|
Le modulo par zéro plante le programme. Autrement le modulo est le reste
de la division et on a toujours |
expr >> exprexpr << expr
|
Le décalage à droite d'un nombre négatif donne un résultat dépendant de l'implémentation (non portable). Un décalage à droite ou gauche négatif ou de trop de bits donne un résultat indéfini. |
expr & exprexpr | exprexpr ^ expr
|
Attention, la précédence moindre que les opérateurs de comparaison requièrent généralement l'usage de parenthèses : if ((val1 | FLAG) != (val2 | FLAG)) Cela est dû au fait qu'au tout début de C, les opérateurs
|
expr & expr |
Utile pour tester un bit : |
expr | expr |
Utile pour tester plusieurs bits :
|
expr && exprexpr || expr
|
Ces opérateurs introduisent un point de séquencement, ce qui permet d'écrire : if (i-- && t[i]) La seconde opérande n'est évaluée que si nécessaire, ce qui permet d'écrire : if (y != 0 && x / y > 100) |
lvalue &= expr |
Utile pour mettre un bit à zéro : |
lvalue |= expr |
Utile pour mettre un bit à un : |
lvalue ^= expr |
Utile pour inverser un bit : |
expr ? expr : expr |
Teste la première expression avec tous les effets de bord (point de séquencement). Exécute la seconde ou troisième expression selon que le résultat de la première est vrai ou faux. #define MAX(A,B) ((A) > (B) ? (A) : (B)) |
expr , expr |
Très rarement utile, sauf dans un contexte nécessitant une seule expression : for (i = 0; i < 10; i++, j++) Lorsque l'opérateur f(a, (t = 3, t + 2), c) |
void main()
On tombe souvent sur des programmes déclarant main comme
fonction void. Un certain nombre de compilateurs (sur Windows)
le supportent. Toutefois, void main n'existe dans
aucun standard.
t[i] = i++
Modifier deux fois la même variable sans point de séquencement entre deux a
un effet indéfini. L'opérateur = n'introduit pas de point
de séquencement.
printf(argv[3])
L'erreur est de donner à printf une chaîne de formatage
contenant un nombre inconnu de %. Ce problème (de sécurité !)
se pose bien sûr avec toutes les fonctions prenant une chaîne de formatage.
Avec printf on le règle ainsi :
printf("%s", argv[3]).
char* gets(char*)
Cette fonction standard lit une ligne de caractère et la sauve dans un tableau. Elle est inutilisable de manière sure car une longue ligne risque toujours de déborder du tableau.
int p = malloc(4);
int adr = (int)&toto;
Un int n'occupe pas forcément 4 bytes ;
sizeof(int) donne la bonne taille.
Les préjugés sur la taille des types et le non usage des types appropriés
(size_t, ptrdiff_t, unsigned long)
causent des problèmes de portage, notamment sur les systèmes 64 bits.
t[argv[1][i]]
Utiliser un char comme indice de tableau fait d'un caractère
8 bits un indice négatif sur une architecture où les caractères sont signés,
d'où des accès à côté du tableau.
exit(-1)
La valeur 1 indique conventionnellement l'échec.
Ou EXIT_FAILURE si on veut être absolument portable.
-1 est hors des limites données par la documentation de la libc
(de 0 à 255).
void transformer(char*, char*);
Le const dans le prototype de la fonction standard
char* strcpy(char*, const char*) indique clairement quelle est
la chaîne source non modifiée et la chaîne destination modifiée, c'est
agréable. C'est aussi un des rares supports du compilateur pour éviter les
bugs de pointeur. L'usage de const dans les prototypes de
fonctions recevant des pointeurs est donc important.
if (ptr != NULL) free(ptr);
Ce n'est pas un bug, mais le programmeur ne lit probablement pas les
manuels des fonctions standards, ce qui n'inspire pas confiance et cause
des caractères inutiles. (man 3 free :
If ptr is NULL, no operation is performed.)
© 2002 Marc Mongenet, pour le Groupe des Utilisateurs Linux du Léman (GULL).