Licence CC 0

Les variables

Dernière mise à jour :

Programmer, c’est avant tout donner des ordres à notre ordinateur afin qu’il réalise ce que l’on souhaite. Ces ordres vont permettre à notre ordinateur de manipuler de l’information sous différentes formes (nombres, textes, vidéos, etc). À ce stade, nous savons que ces ordres, ces instructions sont exécutées par notre processeur. Cependant, nous ne savons toujours pas comment donner des ordres, ni comment manipuler de l’information.

Ce chapitre vous expliquera comment manipuler les types de données les plus simples du langage C, les nombres et les lettres (ou caractères), grâce à ce qu’on appelle des variables. Après celui-ci, vous pourrez ainsi profiter de votre ordinateur comme s’il s’agissait d’une grosse calculatrice. Néanmoins, rassurez-vous, le niveau en maths de ce chapitre sera assez faible : si vous savez compter, vous pourrez le comprendre facilement !

Cela peut paraitre un peu bête et pas très intéressant, mais il faut bien commencer par les bases. Manipuler du texte ou de la vidéo est complexe et nécessite en plus de savoir comment manipuler des nombres. Eh oui ! Comme vous allez le voir, tout est nombre pour notre ordinateur, même le texte et la vidéo.

Qu’est-ce qu’une variable ?

Pour comprendre ce qu’est une variable et comment manipuler celles-ci, il faut commencer par comprendre comment notre ordinateur fait pour stocker des données. En théorie, un ordinateur est capable de stocker tout type d’information. Mais comment est-il possible de réaliser un tel miracle alors qu’il ne s’agit finalement que d’un amas de circuits électriques ?

Codage des informations

Peut-être avez-vous déjà entendu le proverbe suivant : « si le seul outil que vous avez est un marteau, vous verrez tout problème comme un clou » (Abraham Maslow). bien, l’idée est un peu la même pour un ordinateur : ce dernier ne sachant utiliser que des nombres, il voit toute information comme une suite de nombres.

L’astuce consiste à transformer une information en nombre pour que l’ordinateur puisse la traiter, autrement dit la numériser. Différentes techniques sont possibles pour atteindre cet objectif, une des plus simples étant une table de correspondance, par exemple entre un nombre et un caractère.

Caractère Nombre
A 1
B 2
C 3

Binaire

Cependant, comme si cela ne suffisait pas, un ordinateur ne compte pas comme nous : il compte en base deux (l’andouille !).

En base deux ?

La base correspond au nombre de chiffres disponibles pour représenter un nombre. En base 10, nous disposons de dix chiffres : zéro, un, deux, trois, quatre, cinq, six, sept, huit et neuf. En base deux, nous en avons donc… deux : zéro et un. Pour ce qui est de compter, c’est du pareil au même : nous commençons par épuiser les unités : 0, 1 ; puis nous passons aux dizaines : 10, 11 ; puis aux centaines : 100, 101, 110, 111 ; et ainsi de suite. Ci-dessous un petit tableau de correspondance entre la base deux et la base dix.

Base deux Base dix
0 0
1 1
10 2
11 3
100 4
101 5
110 6
111 7
1000 8
1001 9
1010 10

Un chiffre binaire (un zéro ou un un) est appelé un bit en anglais. Il s’agit de la contraction de l’expression « binary digit ». Nous l’emploierons assez souvent dans la suite de ce cours par souci d’économie.

Mais pourquoi utiliser la base deux et non la base dix ?

Parce que les données circulent sous forme de courants électriques. Or, la tension de ceux-ci n’étant pas toujours stable, il est difficile de réaliser un système fiable sachant détecter dix valeurs différentes. Par contre, c’est parfaitement possible avec deux valeurs : il y a du courant ou il n’y en a pas.

La mémoire

Nous savons à présent que notre ordinateur ne sait employer que des nombres représentés en base deux.

Mais comment stocker tout ce fatras de nombres ?

bien, les bits sont stockés dans un composant électronique particulier de l’ordinateur : la mémoire. Enfin, nous disons « la mémoire », mais il y en a en fait plusieurs.

Mais pourquoi plusieurs mémoires et pas une seule ?

Le fait est qu’il est actuellement impossible de créer des mémoires qui soient à la fois rapides et capables de contenir beaucoup de données. Nous ne pouvons donc utiliser une seule grosse mémoire capable de stocker toutes les données dont nous avons besoin. Ce problème s’est posé dès les débuts de l’informatique, comme en témoigne cette citation des années 1940, provenant des concepteurs d’un des tout premiers ordinateurs.

Idéalement, nous désirerions une mémoire d’une capacité indéfiniment large telle que n’importe quelle donnée soit immédiatement accessible. Nous sommes forcés de reconnaître la possibilité de la construction d’une hiérarchie de mémoire, chacune ayant une capacité plus importante que la précédente, mais accessible moins rapidement.

Burks, Goldstine, et Von Neumann

Mais les chercheurs et ingénieurs du début de l’informatique ont trouvé une solution : segmenter la mémoire de l’ordinateur en plusieurs sous-mémoires, de taille et de vitesse différentes, utilisées chacune suivant les besoins. Nous aurons donc des mémoires pouvant contenir peu de données et rapides, à côté de mémoires plus importantes et plus lentes.

Nous vous avons dit que l’ordinateur utilisait plusieurs mémoires. Trois d’entre elles méritent à notre sens votre attention :

  • les registres  ;
  • la mémoire vive (ou RAM en anglais) ;
  • le disque dur.

Les registres sont des mémoires intégrées dans le processeur, utilisées pour stocker des données temporaires. Elles sont très rapides, mais ne peuvent contenir que des données très simples, comme des nombres.

La mémoire vive est une mémoire un peu plus grosse, mais plus lente que les registres. Elle peut contenir pas mal de données et est généralement utilisée pour stocker les programmes en court d’exécution ainsi que les données qu’ils manipulent.

Ces deux mémoires (les registres et la mémoire vive) ont tout de même un léger défaut : elles perdent leur contenu quand elles ne sont plus alimentées… Autant dire que ce n’est pas le meilleur endroit pour stocker un système d’exploitation ou des fichiers personnels. Ceci est le rôle du disque dur, une mémoire avec une capacité très importante, mais très lente qui a toutefois l’avantage d’assurer la persistance des données.

En C, la mémoire la plus manipulée par le programmeur est la mémoire vive. Aussi, nous allons nous y intéresser d’un peu plus près dans ce qui suit.

Bits, multiplets et octets

Dans la mémoire vive, les bits sont regroupés en « paquets » de quantité fixe : des « cases mémoires », aussi appelées multiplets (ou bytes en anglais). À quelques exceptions près, les mémoires utilisent des multiplets de huit bits, aussi appelés octet. Un octet peut stocker 256 informations différentes (vous pouvez faire le calcul vous-même : combien vaut 11111111 en base deux ? :p ). Pour stocker plus d’informations, il sera nécessaire d’utiliser plusieurs octets.

Adresse mémoire

Néanmoins, il est bien beau de stocker des données en mémoire, encore faut-il pouvoir remettre la main dessus.

Dans cette optique, chaque octet de la mémoire vive se voit attribuer un nombre unique, une adresse, qui va permettre de le sélectionner et de l’identifier parmi tous les autres. Imaginez la mémoire vive de l’ordinateur comme une immense armoire, qui contiendrait beaucoup de tiroirs (les cases mémoires) pouvant chacun contenir un octet. Chaque tiroir se voit attribuer un numéro pour le reconnaitre parmi tous les autres. Nous pourrions ainsi demander quel est le contenu du tiroir numéro 27. Pour la mémoire, c’est pareil. Chaque case mémoire a un numéro : son adresse.

Adresse Contenu mémoire
0 11101010
1 01111111
2 00000000
3 01010101
4 10101010
5 00000000

En fait, vous pouvez comparer une adresse à un numéro de téléphone : chacun de vos correspondants a un numéro de téléphone et vous savez que pour appeler telle personne, vous devez composer tel numéro. Les adresses mémoires fonctionnent exactement de la même façon !

Exemple : on demande à notre mémoire de sélectionner la case mémoire d’adresse 1002 et on récupère son contenu (ici, 17).

Plus généralement, toutes les mémoires disposent d’un mécanisme similaire pour retrouver les données. Aussi, vous entendrez souvent le terme de référence qui désigne un moyen (comme une adresse) permettant de localiser une donnée. Il s’agit simplement d’une notion plus générale.

Les variables

Tout cela est bien sympathique, mais manipuler explicitement des références (des adresses si vous préférez) est un vrai calvaire, de même que de s’évertuer à calculer en base deux. Heureusement pour nous, les langages de programmation (et notamment le C), se chargent d’effectuer les conversions pour nous et remplacent les références par des variables.

Une variable correspondra à une portion de mémoire, appelée objet, à laquelle nous donnerons un nom. Ce nom permettra d’identifier notre variable, tout comme une référence permet d’identifier une portion de mémoire parmi toutes les autres. Nous allons ainsi pouvoir nommer les données que nous manipulons, chacun de ces noms étant remplacés lors de la compilation par une référence (le plus souvent une adresse).

Déclarer une variable

Entrons maintenant dans le vif du sujet en apprenant à déclarer nos variables. Tout d’abord, sachez qu’une variable est constituée de deux éléments obligatoires :

  • un type ;
  • un identificateur qui est en gros le « nom » de la variable.

Le type d’une variable permet d’indiquer ce qui y sera stocké, par exemple : un caractère, un nombre entier, un nombre à virgule (ou nombre flottant), etc. Pour préciser le type d’une variable, il est nécessaire d’utiliser un mot-clé spécifique (il y en a donc un pour chaque type).

Une fois que nous avons décidé du nom et du type de notre variable, nous pouvons la créer (on dit aussi la déclarer) comme suit.

1
type identificateur;

En clair, il suffit de placer un mot-clé indiquant le type de la variable et de placer le nom qu’on lui a choisi immédiatement après.

Faites bien attention au point-virgule à la fin !

Les types

Comme dit précédemment, un type permet d’indiquer au compilateur quel genre de données nous souhaitons stocker. Ce type va permettre de préciser :

  • toutes les valeurs que peut prendre la variable ;
  • les opérations qu’il est possible d’effectuer avec (il n’est par exemple pas possible de réaliser une division entière avec un nombre flottant, nous y reviendrons).

Définir le type d’une variable permet donc de préciser son contenu potentiel et ce que nous pouvons faire avec. Le langage C fournit sept1 types de base.

Type Sert à stocker
char un caractère ou un entier
short int un entier
int un entier
long int un entier
float un flottant
double un flottant
long double un flottant

Les types short int, int et long int servent tous à stocker des nombres entiers qui peuvent prendre des valeurs positives, négatives, ou nulles. On dit qu’il s’agit de types signés (car il peut comporter un signe). Pour ces trois types, il existe un type équivalent dit non signé. Un type entier non signé est un type entier qui n’accepte que des valeurs positives ou nulles : il ne peut pas stocker de valeurs négatives. Pour déclarer des variables d’un type non signé, il vous suffit de faire précéder le nom du type entier du mot-clé unsigned.

Le type char peut lui aussi servir à stocker des nombres. Il sert surtout au stockage de caractères, mais ces derniers étant stockés dans l’ordinateur sous forme de nombres, il est possible de stocker des nombres dans un char. Le seul problème, c’est que ce type peut être signé ou non signé de base suivant les compilateurs. Pour éviter les ennuis, spécifiez ce que vous souhaitez lors de la déclaration : non signé (unsigned char) ou signé (signed char).

En cas de manque d’information concernant le type lors d’une déclaration, c’est le type int qui sera utilisé. Ainsi, long et short sont respectivement des raccourcis pour long int et short int. De même, le mot-clé unsigned seul signifie unsigned int.

Capacité d’un type

Tous les types stockant des nombres ont des bornes, c’est-à-dire une limite aux valeurs qu’ils peuvent stocker. En effet, le nombre de multiplets occupés par une variable est limité suivant son type. En conséquence, il n’est pas possible de mettre tous les nombres possibles dans une variable de type int, float, ou double. Il y aura toujours une valeur minimale et une valeur maximale. Ces limites sont les suivantes.

Type Minimum Maximum
signed char -127 127
unsigned char 0 255
short -32 767 32 767
unsigned short 0 65 535
int -32 767 32 767
unsigned int 0 65 535
long -2 147 483 647 2 147 483 647
unsigned long 0 4 294 967 295
float -1 × 1037 1 × 1037
double -1 × 1037 1 × 1037
long double -1 × 1037 1 × 1037

Si vous regardez bien ce tableau, vous remarquez que certains types ont des bornes identiques. En vérité, les valeurs présentées ci-dessus sont les minimums garantis par la norme2 et il est fort probable qu’en réalité, vous puissiez stocker des valeurs plus élevées que ceux-ci. Cependant, dans une optique de portabilité, vous devez considérer ces valeurs comme les minimums et les maximums de ces types, peu importe la capacité réelle de ces derniers sur votre machine.

Taille d’un type

Peut-être vous êtes vous demandés pourquoi il existe autant de types différents. La réponse est toute simple : la taille des mémoires était très limitée à l’époque où le langage C a été créé. En effet, le PDP-11 sur lequel le C a été conçu ne possédait que 24Ko de mémoire (pour comparaison, une calculatrice TI-Nspire possède 100Mo de mémoire, soit environ 4000 fois plus). Il fallait donc l’économiser au maximum en choisissant le type le plus petit possible. Cette taille dépend des machines, mais de manière générale, vous pouvez retenir les deux suites d’inégalités suivantes : charshortintlong et floatdoublelong double.

Aujourd’hui ce n’est plus un problème, il n’est pas nécessaire de se casser la tête sur quel type choisir (excepté si vous voulez programmer pour de petits appareils où la mémoire est plus petite). En pratique, nous utiliserons surtout char pour les caractères, int ou long pour les entiers et double pour les flottants.

Les identificateurs

Maintenant que nous avons vu les types, parlons des identificateurs. Comme dit précédemment, un identificateur est un nom donné à une variable pour la différencier de toutes les autres. Et ce nom, c’est au programmeur de le choisir. Cependant, il y a quelques limitations à ce choix.

  • seuls les 26 lettres de l’alphabet latin (majuscules ou minuscules), le trait de soulignement « _ » (underscore en anglais) et les chiffres sont acceptés. Pas d’accents, pas de ponctuation ni d’espaces ;
  • un identificateur ne peut pas commencer par un chiffre ;
  • les mots-clés ne peuvent pas servir à identifier une variable ; il s’agit de :
1
2
3
4
5
6
7
8
auto       double     int        struct
break      else       long       switch
case       enum       register   typedef
char       extern     return     union
const      float      short      unsigned
continue   for        signed     void
default    goto       sizeof     volatile
do         if         static     while
  • deux variables ne peuvent avoir le même identificateur (le même nom). Il y a parfois quelques exceptions, mais cela n’est pas pour tout de suite ;
  • les identificateurs peuvent être aussi longs que l’on désire, toutefois le compilateur ne tiendra compte que des 31 premiers caractères.

Voici quelques exemples pour bien comprendre.

Identificateur correct Identificateur incorrect Raison
variable Nom de variable Espaces interdits
nombre_de_vie 1nombre_de_vie Commence par un chiffre
test test! Caractère « ! » interdit
un_dernier_pour_la_route1 continue Mot-clé réservé par le langage

À noter que le C fait la différence entre les majuscules et les minuscules (on dit qu’il respecte la casse). Ainsi les trois identificateurs suivants sont différents.

1
2
3
variable
Variable
VaRiAbLe

Déclaration et initialisation

Maintenant que nous connaissons toutes les bases, entrainons-nous à déclarer quelques variables.

1
2
3
4
5
6
7
8
9
int main(void)
{
    double taille;
    unsigned int age;
    char caractere;
    short petite_valeur;

    return 0;
}

Il est possible de déclarer plusieurs variables de même type sur une même ligne, en séparant leurs noms par une virgule.

1
int age, taille, nombre;

Ceci permet de regrouper les déclarations suivant les rapports que les variables ont entre elles.

1
2
3
int annee, mois, jour;
int age, taille;
int x, y, z;

Initialisation

En plus de déclarer une variable, il est possible de l’initialiser, c’est-à-dire de lui attribuer une valeur. La syntaxe est la suivante.

1
type identificateur = valeur;

Ou comme ceci s’il s’agit d’un caractère.

1
char identificateur = 'lettre';

Quelques exemples d’initialisations de variables.

1
2
3
4
unsigned int age = 25;
short petite_valeur = 1;
const long abc = 314159265;
char caractere = 'h';

Notez qu’une déclaration comporte le mot-clé const. Celui-ci permet de préciser qu’une variable ne pourra pas être modifiée par la suite. Ceci peut être utile pour stocker une valeur qui ne changera jamais (comme la constante $\pi$ qui vaut toujours 3,14159265).

Les déclarations doivent toujours se situer en début de bloc, c’est-à-dire juste après une accolade ouvrante.

Initialisation des nombres flottants

En notation simple

Petite précision concernant la manière d’initialiser une variable de type flottant : celles-ci étant faites pour contenir des nombres à virgule, à l’initialisation, il est nécessaire de placer cette « virgule ». Toutefois, cette dernière est représentée par un point.

1
const double pi = 3.14;

Ceci vient du fait que le C est une invention américaine et que les anglophones utilisent le point à la place de la virgule.

Notez qu’il est important de bien placer ce point, même si vous voulez stocker un nombre entier. Par exemple, vous ne devez pas écrire double a = 5 mais double a = 5. (certains préfèrent double a = 5.0, cela revient au même). Si vous ne le faites pas, vous risquez d’avoir quelques problèmes.

En notation scientifique

Par ailleurs, sachez qu’il est également possible de représenter un nombre flottant à l’aide de la notation scientifique, c’est-à-dire sous la forme d’un nombre décimal et d’une puissance de dix. Cela se traduit par un nombre flottant en notation simple (comme ci-dessus) suivi de la lettre « e » ou « E » et d’un exposant entier.

1
double f = 1E-1; /* 1x10^-1 = 0.1 */

Affectation

Nous savons donc déclarer (créer) nos variables et les initialiser (leur donner une valeur à la création). Il ne nous reste plus qu’à voir la dernière manipulation possible : l’affectation. Celle-ci permet de modifier la valeur contenue dans une variable pour la remplacer par une autre valeur.

Il va de soi que cette affectation n’est possible que pour les variables qui ne sont pas déclarées avec le mot-clé const puisque, par définition, de telles variables sont constantes et ne peuvent voir leur contenu modifié.

Pour faire une affectation, il suffit d’opérer ainsi.

1
identificateur = nouvelle_valeur;

Nous voyons que la syntaxe est similaire à celle d’une déclaration avec initialisation. La seule différence, c’est que nous n’avons pas à préciser le type. Celui-ci est en effet fixé une fois pour toute lors de la déclaration de notre variable. Aussi, il n’est pas nécessaire de le préciser à nouveau lors d’une affectation.

1
2
3
age = 30;
taille = 177.5;
petite_valeur = 2;

Notez qu’il n’y a aucune limite au nombre d’affectations, comme le démontre l’exemple ci-dessous.

1
2
3
4
5
6
7
petite_valeur = 2;
petite_valeur = 4;
petite_valeur = 8;
petite_valeur = 16;
petite_valeur = 8;
petite_valeur = 4;
petite_valeur = 2;

À chaque affectation, la variable va prendre une nouvelle valeur. Par contre, ne mettez pas le type quand vous voulez changer la valeur, sinon vous aurez le droit à une belle erreur du type « redefinition of ’nom_de_votre_variable’ » car vous aurez créé deux variables avec le même identificateur !

Le code suivant est donc incorrect.

1
2
int age = 15;
int age = 20;

Si vous exécutez tous ces codes, vous verrez qu’ils n’affichent toujours rien et c’est normal puisque nous n’avons pas demandé à notre ordinateur d’afficher quoique ce soit. Nous apprendrons comment faire au chapitre suivant.

Il n’y a pas de valeur par défaut en C. Aussi, sans initialisation ou affectation, la valeur d’une variable est indéterminée ! Veillez donc à ce que vos variables aient une valeur connue avant de les utiliser !


  1. Depuis la norme C99, un type entier supplémentaire a été ajouté : le type long long int. Ses bornes garanties par la norme sont comprises entre -9 223 372 036 854 775 807 et 9 223 372 036 854 775 807 s’il est signé et entre 0 et 18 446 744 073 709 551 615 s’il est non signé. 

  2. Programming Language C, X3J11/88-090, § 2.2.4.2, Numerical limits 

Les représentations octales et hexadécimales

Pour terminer ce chapitre, nous vous proposons un petit aparté sur deux représentations particulières : la représentation octale et la représentation hexadécimale.

Nous avons déjà vu la représentation binaire au début de ce chapitre, les représentations octale et hexadécimale obéissent au même schéma : au lieu d’utiliser dix chiffres pour représenter un nombre, nous en utilisons respectivement huit ou seize.

Seize chiffres ?! Mais… Je n’en connais que dix moi ! o_O

La représentation hexadécimale est un peu déroutante de prime abord, celle-ci ajoute six chiffres (en fait, six lettres) : a, b, c, d, e et f. Pour vous aider, voici un tableau présentant les nombres de 1 à 16 en binaire, octal, décimal et hexadécimal.

Binaire Octal Décimal Hexadécimal
00001 1 1 1
00010 2 2 2
00011 3 3 3
00100 4 4 4
00101 5 5 5
00110 6 6 6
00111 7 7 7
01000 10 8 8
01001 11 9 9
01010 12 10 a
01011 13 11 b
01100 14 12 c
01101 15 13 d
01110 16 14 e
01111 17 15 f
10000 20 16 10

Notez que dix dans n’importe quelle base équivaut à cette base.

Quel est l’intérêt de ces deux bases exactement ?

L’avantage des représentations octale et hexadécimale est qu’il est facilement possible de les convertir en binaire contrairement à la représentation décimale. En effet, un chiffre en base huit ou en base seize peut-être facilement traduit, respectivement, en trois ou quatre bits.

Prenons l’exemple du nombre 35 qui donne 43 en octal et 23 en hexadécimal. Nous pouvons nous focaliser sur chaque chiffre un à un pour obtenir la représentation binaire. Ainsi, du côté de la représentation octale, 4 donne 100 et 3 011, ce qui nous donne finalement 00100011. De même, pour la représentation hexadécimale, 2 nous donne 0010 et 3 0011 et nous obtenons 00100011. Il n’est pas possible de faire pareil en décimal.

En résumé, les représentations octale et hexadécimale permettent de représenter un nombre binaire de manière plus concise et plus lisible.

Constantes octales et hexadécimales

Il vous est possible de préciser la base d’une constante entière en utilisant des préfixes. Ces préfixes sont 0 pour les constantes octales et 0x ou 0X pour les constantes hexadécimales.

1
2
3
long a = 65535; /* En décimal */
int b = 0777; /* En octal */
short c = 0xFF; /* En hexadécimal */

Les lettres utilisées pour la représentation hexadécimale peuvent être aussi bien écrites en minuscule qu’en majuscule.

Il n’est pas possible d’utiliser une constante en base deux ?

Malheureusement, non, le langage C ne permet pas d’utiliser de telles constantes.


Voilà, c’est la fin de ce chapitre. Nous avons vu beaucoup de choses, n’hésitez pas à potasser pour bien assimiler tout ça. Les variables sont vraiment la base de la programmation, aussi il nécessaire de bien les comprendre. Rendez-vous au prochain chapitre qui sera très intéressant : vous pourrez par exemple demander l’âge de l’utilisateur pour ensuite l’afficher !