Les variables et leurs types
Il existe 3 grands types de variables basiques en C :
- les entiers
- les réels
- les caractères
Contrairement à python, chaque variable de C a un type qui ne peut changer. On déclare une variable de cette façon :
type nom = valeur;
danger !
C
vous permet de créer plusieurs variable en une fois int i, j;
mais ne le faites pas.
- une seule déclaration de variable par ligne.
- initialisez cette variable
Prenez l'habitude de toujours initialiser vos variables à la création.
Le C permet de commencer par déclarer une variable par l'instruction type nom;
avant de l'utiliser plus tard, mais c'est presque toujours une mauvaise idée.
Chaque type a une taille donnée (en bytes). La valeur de la variable est stockée :
- dans la pile si elle est définie dans une fonction
- dans la partie data si elle est n'est pas définie dans une fonction.
La durée de vie d'une variable est le bloc dans laquelle elle est définie ou le fichier si elle est définie en-dehors de tout bloc.
Une variable est ainsi l'indice de la pile ou de la partie data à laquelle sa valeur est stockée.
On peut connaître la taille d'une variable grâce à l'opérateur sizeof
.
#include
int main() {
int i = 1;
double d = 3.14;
char c = 'A';
printf("Taille d'un entier : %luB\n", sizeof(i));
printf("Taille d'un réel : %luB\n", sizeof(d));
printf("Taille d'un caractère : %luB\n", sizeof(c));
}
Le code ci-dessus donne le résultat suivant sur ma machine :
Taille d'un entier : 4B
Taille d'un réel : 8B
Taille d'un caractère : 1B
La fonction printf permet d'afficher une chaîne de caractères formatée :
- le premier paramètre est ue chaîne de caractères. Elle contient des paramètres
%x
oùx
correspond au type de la variable à représenter. - les autres paramètres correspondent aux variables qui remplaceront, dans l'ordre, les caractères
%x
Il existe beaucoup de types possibles, ils sont tous représentés ici : text spécifier. On utilise fréquemment les formats :
%zu
: pour les résultats d'un sizeof%i
: pour les entiers (signés)%u
: pour les entiers non signés%f
: pour les réels%c
: pour un caractère%s
: pour les chaines de caractères%p
: pour les pointeurs génériques
Plutôt que d'afficher la taille en mémoire des différentes variables dans le code précédent, affichez leurs valeurs avec le bon type.
solution
solution
#include
int main() {
int i = 1;
double d = 3.14;
char c = 'A';
printf("Un entier : %i\n", i);
printf("Un réel : %f\n", d);
printf("Un caractère : %c\n", c);
}
La fonction printf
et son fonctionnement est un bon moyen pour réaffirmer que la valeur d'une donnée dépend de son type (presque) indépendamment de sa valeur binaire effective. Prenez le code suivant comme exemple où l'on utilise la fonction printf
sur une même variable selon deux types différents :
#include
int main() {
int i = -1;
printf("Entier (signé) : %i\n", i);
printf("Entier non signé : %u\n", i);
printf("flottant : %f\n", i);
}
- le premier affichage on utilise le format
%i
pour représenter un entier signé - le premier affichage on utilise le format
%u
pour représenter un entier non signé (il ne peut que être positif)
Il est crucial de se rappeler qu'une donnée peut se voir de multiple façon selon le type par le prisme duquel on la regarde.
Entiers
Le type int
est l'entier par défaut en C. Il est signé, ce qui signifie qu'il peut être positif ou négatif. Sa taille n'est pas définie et peut donc être variable selon l'architecture. Il est habituellement codé sur 4B pour les machines de bureaux.
La norme c23
impose que le signe soit effectué par complément à deux
Types d'entiers
Ce type entier peut être spécifié pour moduler sa taille :
short int
long int
long long int
Quelle est la taille de ces types sur votre machine ?
solution
solution
short int
: 2Blong int
: 8Blong long int
: 8B
La norme C impose uniquement le fait que ce soit plus grand, pas strictement plus grand
Et on peut rendre le type non signé en ke faisant précéder de unsigned
. Par exemple : unsigned short int
L'intérêt d'utiliser des entiers non signés est double :
- on peut en aller plus loin
- les nombres cyclent du maximum à 0 (ce comportement est non définit pour les entiers signés)
En utilisant le fichier limits.h
, vérifiez que lorsqu'on ajoute 1 à un entier non signé maximum, on obtient bien 0.
solution
solution
#include <stdio.h>
#include <limits.h>
int main() {
unsigned int i = UINT_MAX;
printf(" un entier non signé : %u\n", i);
i += 1;
printf(" un entier non signé : %u\n", i);
}
On est pas obligé d'ajouter int
, ainsi :
long long
est équivalent àlong long int
unsigned
est équivalent àunsigned int
Il existe le signed int
qui est équivalent à int
Utilisez le type int
par défaut lorsque vous avez besoin d'entiers.
Représentation des entiers
TBD : nombre décimal, binaire, hexadécimal (pour les adresses) ou octal (utilisé pour les droits.) insister sur le fait que ce n'est qu'une représentation d'un nombre binaire un long int peut très bien être 2 int à la suite, signé ou non : exemple little/ big endian selon le système.
Réels
Le type réel double
est codé selon le format double précision de la représentation ds nombre en virgule flottante, sur 8B.
- le format en simple précision sur 4B, noté
float
, existe aussi mains n'est plus recommandé pour les calculs. - le format
long double
existe également. Il est codé sur 16B.
Utilisez le type double
par défaut lorsque vous avez besoin de réels.
Char
TBD en C que des nombre + représentation via son type. Donc char est un type entier avec des astuces pour en faire des caractères.
Le type char
est le plus petit adressage possible, sur 1B (sur toutes les implémentations actuelles). Il correspond aussi à la représentation d'un caractère ASCII sur 7b.
L'encodage par défaut actuel, UTF-8, encode également ses caractères sur des bytes, mais les caractères autres que les caractères ASCII sont codées sur plus d'un byte. Remplacez la ligne 7 de l'exemple par :
char c = 'é';
et le code ne compilera plus car é
est codé sur 2 bytes et ne rentrera plus dans un unique char :
Le type char
est l'unité fondamentale des chaines de caractères mais ne correspond pas toujours à un caractère.
Les caractères s'écrivent entre quote '
et sont remplacé par leur code selon l'encodage par défaut (souvent utf8).
printf(" un caractère : %c\n", 65);
printf(" un caractère : %i\n", 'A');
Les double quote ("
) servent aux chaînes de caractères et ne peuvent pas être utilisé pour représenter des caractères
TBD représentation d'un char :
- caractère ASCII : remplacé par un nombre
- si plus, de toute façon pas 1 caractère donc doit être représenté par un tableau. Donner exemple
- entier < 128
- si > 128 : doit être unsigned (c'est dans la spécif C)
TBD exemple char et unsigned char. Sur mac et sur linux.
Cast
Un type peut être converti en un autre. Ceci peut se faire de manière implicite :
int i = 42;
double k = i;
Ou de façon explicite en faisant précéder la variable par le type cible :
int i = 42;
double k = (double)i / 13;
Que vaut $k$ sans et avec le cast explicite
Lorsque vous manipulez des nombres et vous voulez vous assurer que ce sont les opérations réelles qui sont utilisées, même si vous manipulez des entiers, écrivez-les sous la forme de réels. Par exemple :
1.0 / 3
vous assurera d'utiliser la division réelle.
TBD exemple plus tordu entre 1 int valant 128 et 1 (signed/unsigned) char.
Définition de nouveaux types
Le C
ne possède qu'un nombre limité de types qui correspondant à une taille de stockages et pas à leurs utilisations. Il est alors courant de renommer ces types pour rendre leur usage explicite et faciliter la lecture du code.
Par exemple le type de retour de sizeof
est size_t
. Ce type correspond chez moi à un entier long non signé (il est défini dans le fichier stddef.h
).
Ce renommage utilise l'opérateur typedef
:
typedef unsigned long int size_t;
L'opérateur typedef
renomme un type (unsigned long long
) en un autre (size_t
)
Ce type est défini pour pouvoir contenir le nombre maximum d'élément d'un ensemble. Attention, cette quantité est dépendante de la structure. Elle vaut $2^{64}$ pour une architecture x64 mais peut–être plus petite pour un Raspberry PI par exemple.
C utilise cette techniques à de nombreux endroits, et nous l'utiliserons aussi lorsque cela nous permettra de :
- raccourcir les notations (avec les structures par exemple)
- d'être pus clair
Créer un type petit_entier
correspondant au plus petits entiers signé possible.
solution
solution
#include <stdio.h>
int main() {
typedef signed char petit_entier;
petit_entier i = 12;
printf(" un petit entier : %i\n", i);
}
Quel type utiliser quand
Si pas de contrainte de taille :
- entier :
int
- si énumération ou comptage positif
size_t
- réel :
double
Les variables peuvent être Qualifiées. Cette qualification est utilisée par le compilateur pour optimiser le code produit, il n'a pas d'autre utilité en C
.
Le qualifier se place au début de la déclaration, Par exemple :
const double PI = 3.14
Il existe 3 types de qualifier :
const
: la variable est constante. Le compilateur va produire une erreur lorsque le code tentera de la modifier.volatile
: la variable peut être modifiée en dehors du code (lorsque la variable correspond à des données partagées via le réseau par exemple), le compilateur ne doit pas cacher la variable par exemplerestrict
: assure le compilateur que cette donnée ne sera pas accessible par une autre variable. Cela évite le pointer aliasing.
danger !
Opérations
danger !
Méfiez vous de ++i
et i++
.
- ne combinez pas cet incrément à toute autre opération
- si vous êtes obligé de le faire, utilisez toujours
++i
Que fait le code ci-dessous :
int i = 12;
printf(" un int : %i\n", i++);
printf(" un int : %i\n", i);