Les bases du C - 2ème partie

Écrit le 19/04/2004 par BlackSmurf
Dernière mise à jour : 01/02/2006

Introduction

Bien alors dans la 1ère partie nous avons pu voir de manière général le fonctionnement du C et son histoire.
Nous allons un peu récapituler tout cela.[list]* Le C est un langage de haut niveau procédural (il s'execute ligne par ligne).
* Pour terminer chaque instructions (autres que logiques) il faut saisir ";"
* C fait la différence entre les minuscules et majuscules.
* Le point d'entrée de tout programme commence par (définition par défaut) : void main(void)
* Dans Visual C++ par exemple les mot clés sont écris en BLEU. Ces mots clé font partie intégrante du noyau même de C.
* On peut placer des commentaires dans le code afin de s'y réperer plus tard.
* Il existe 4 grand type d'entier (dans l'ordre du plus petit en place mémoire au plus grand) char, short, int et long. Toutefois on peut préciser si ils sont signé ou pas (si ils peuvent prendre des valeurs négative ou pas).
[/list]

La génèse du compilateur

Un compilateur c'est quoi ? Il s'agit d'un ensemble de fichiers regroupant des éxecutables et des librairies. Prenons l'exemple de Visual C++. Ouvrons son dossier d'installation, puis allons dans le dossier "VC98" (dossier ou est installé le compilateur VC++). Vous devriez avoir quelque chose comme l'écran suivant :

http://www.game-lab.com/images/tuts/c_bases2/vcppfolder.jpg

Vous avez trois dossier spécifique au compilateur Visual C++ 6.0 et auxquels l'on ne va pas s'interesser il s'agit de : "ATL", "CRT" et "MFC".[list]* Le dossier BIN va regrouper tout les exécutables (les programmes) du compilateur, celui qui va compiler, celui qui va faire l'édition des liens et beaucoup d'autres. Son nom BIN est tiré de Binaire.
* Le dossier Include va regrouper tous les fichiers .H (fichiers d'en-têtes) qui vont servir de déclarations des fonctions déjà existantes en C (par exemple math.h pour les fonctions mathématique).
* Le dossier Lib va regrouper tous les fichiers .LIB (fichiers librairies) qui eux servent de fichier de définitions. Contrairement aux fichiers .H les fichiers lib, ne sont pas en texte clair. Ils contiennent toutes les définitions les fonctions elle même.
[/list]Prenons un exemple pour mieux comprendre : la fonction [i]printf()[/i].

Dans le fichier stdio.h on trouvera :

_CRTIMP int __cdecl printf(const char *, ...);

Comme on peut le voir il s'agit juste d'une déclaration de la fonction[i] printf()[/i] on ne voit pas les détails de cette fonction, c'est-à-dire les instructions saisies dedans pour obtenir l'affichage à l'écran.

Mais alors la définition de cette fonction est où ? Dans un fichier .LIB ! Laquelle ? Avec Visual C++ 6.0 je ne sais pas ! Le logique du C veut que pour un fichier .H on est un fichier .LIB équivalent avec un même nom. Cependant comme il s'agit des fichiers prédéfinis par le compilateur, cette logique n'est pas vraiment respecté.

Les variables

Une variable est comme une boîte ou l'on range une certaine valeur. Cette boite possède un certain "format". Comme on a pu le voir dans la 1ère partie. Ces formats sont en fait le type d'une variable. Etant codé de 1 à 4 octects on peut y ranger une valeur plus ou moins importante. Par exemple, une variable de type [i]char non signé [/i](codé sur 8 bits avec aucun bit réservé pour le signe +/-) peut prendre une valeur de 0 à 255. Si l'on souhaite ranger la valeur 257 dans cette variable cela ne marchera pas. La syntaxe pour déclarer une variable est la suivante :

[i]<type_de_la_variable> nom_de_la_variable;[/i]

Cette déclaration est vrai pour toute les formes de déclaration d'une variables que ce soit comme ca en globale, en locale dans une fonction ou encore en tant que paramètre.

En ce qui concerne des types, j'en ai omis un volontairement car beaucoup plus compliqué pour l'explication de son codage en mémoire : il s'agit du nombre réel (avec décimal). Pour ranger des nombres à virgule dans une variable il faut utiliser les types : [i]float[/i], [i]double[/i] ou [i]long double[/i] selon le nombre de décimal que l'on souhaite. [i]double[/i] étant plus précis et [i]long double[/i] encore plus précis. Toutefois c'est très rare que l'on utilise le type [i]double[/i] et [i]long double[/i] (car lent).

Exemple :

float Pi;

Pi = 3.14159

Pour attribuer une valeur à une variable il suffit de faire par exemple :

myVar = 10;

On peut attribuer une valeur par défaut à une variable lors de sa création :

int myVar = 10;

Les opérations concernant les variables sont :[list]+ (addition)
- (soustraction)
/ (division)
% (modulo : reste d'une division)
* (multiplication)
[/list]Exemple :

char myVar = 0;

myVar = myVar * 5 + 6 - 2;
myVar = myVar + 1;

(Les ordres de priorités sont les mêmes qu'en mathématiques.)

Il existe certains "raccourci" aussi pour effectuer plus rapidement des opérations. Vous allez comprendre.

Exemple :

myVar *= 5;   // égal à myVar = myVar * 5;
myVar /= 3;  // égal à myVar = myVar / 5;
myVar -= 10;  // égal à myVar = myVar - 10;
myVar += 1;  // égal à myVar = myVar + 1;
myVar++;  // égal à myVar = myVar + 1;
myVar--;       // egal à myVar = myVar - 1;

On peut toutefois effectuer des opérations binaires sur les nombres à l'aide des commandes suivantes :[list]& (ET logique)
| (OU inclusif)
^ (OU exclusif)
[/list]
(NDW : voir le tutorial sur "Les flags" pour des informations complementaires)

Les tableaux

Les tableaux sont identiques aux variables sauf qu'ils prennent plusieurs "cases" mémoire et permettent de ranger plusieurs valeurs dans une même variables à l'aide d'un INDEX.

Exemple :

char monTablo[10];

Ici on à déclaré un tableau appellé monTablo de 10 cases de type [i]char[/i]. C'est-à-dire que chaque case du tableau prendra 1 octet mémoire.
Pour accèder à une case donné il suffit de lui passer en INDEX (entre crochet) son numéro de case.
ATTENTION ! La numérotation des cases se fait TOUJOURS à partir de 0 !!

Exemple :

monTablo[0] = 1; // ok 1ère case mémoire
monTablo[9] = 2; // ok 10ème case mémoire
monTablo[10] = 3; // erreur car la case numéro 10 n'existe pas !

Les fonctions

Qu'est-ce qu'une fonction ? Une fonction est un ensemble d'instructions regroupé dans un sous programme nommé. La facon la plus compréhensible par tous est sans aucun doute celle de faire l'analogie avec les maths !

En maths on décrit la fonction affine comme ceci : [i]y = f(x) = ax+b[/i].
Décorticons à présent cela. Ici la fonction s'appelle F. Cette fonction est un peu différente d'une fonction simple. En effet il s'agit d'une fonction paramétrable. A quoi le voit-on ? Il s'agit du x entre parenthèses. En passant dans x une valeur quelconque la fonction va effectuer un traitement. Ce traitement est : [i]a * x + b[/i]. a et b ne sont pas défini mais on pourrait très bien l'imaginer. Une fois ce traitement fait que ce passe-t-il ? Et bien F (notre fonction) va renvoyer le résultat de ce traitement, et c'est y qui va la recevoir. Et donc on retombe sur ce que l'on nous a appris : [i]f(x) = ax + b[/i], permet pour une valeur x d'obtenir son image y. Il s'agit des coordonnées x, y dans un plan cartésien.

Alors les maths c'est beau, mais ce n'est pas tout. Cet exemple nous permet de bien voir que la notion de "fonction" ne nous est pas inconnu. Mais sans plus tarder je vais écrire la syntaxe d'une fonction simple en C :

void nom_de_la_fonction(void)
{
// instructions
}

Il s'agit d'une fonction sans paramètres, et cette fonction ne renvoi aucune valeur. On appelle en général ce type de fonction : une procédure. Une procédure et un simple sous programme pour faire des tâches (en lui passant des paramètres ou pas) mais à la différence d'une fonction elle ne renvoie rien.

Alors on peut se demander à quoi ca sert d'écrire une procédure simple comme celle ci (sans paramètres) ? Et bien imaginez que vous souhaitez afficher par exemple un message et qu'ensuite vous souhaitez attendre que l'utilisateur presse une touche. Ceci prendra donc plusieurs lignes d'instructions. Imaginez ensuite que vous souhaitez faire ce processus à plusieurs reprise dans votre programme (dans [i]void main(void)[/i]). Vous allez réecrire toutes les lignes à chaque fois ? Non, le mieux c'est d'écrire une fois ce traitement dans une simple procédure et ensuite de l'appeller à chaque fois que vous en aurez besoin.

Bien maintenant détaillons la syntaxe de la déclaration de cette procédure.
Le premier mot que vous voyez : [i]void[/i], correspond en fait au type de la valeur qui sera renvoyé par notre fonction (cf. [i]y = f(x)[/i]). Comme il s'agit d'une simple procédure (qu'on ne veux rien renvoyer), on utilise le mot clé : [i]void[/i]. Ce mot clé défini le type de valeur VIDE. Si on aurez voulu renvoyer une valeur de type entier codé sur 2 octets, il aurait fallu saisir :

short nom_de_la_fonction(void)

Ensuite entre parenthèse c'est ici que l'on va définir les paramètres que l'on souhaite avoir pour notre fonction. Ici on en veut pas, on a donc mis : [i]void[/i].
La syntaxe concernant les paramètres est la suivante : [i]<type> nom_de_la_variable1, <type> nom_de_la_variable2, ...[/i]

Maintenant pour exemple on peut écrire la fonction CARRE qui va calculer la valeur carré d'un nombre et la renvoyer:

int carre(int valeur)
{
return valeur * valeur; // renvoi une valeur de type int, car int * int = int
} 

Le mot clé return permet de renvoyer un résultat (du même type que la fonction) et de sortir de la fonction dessuite.

Le Préprocesseur

Qu'est-ce que le préprocesseur ? Il s'agit de commandes bien spécifique qui vont s'exécuter avant la compilation du code source. Je dirais même que la compilation se détermine en fonction justement des commandes préprocesseur. Sans entrer pour l'instant dans les détails, je vous explique ici la commande préprocesseur la plus utilisé dans la programmation C :[i] #include[/i].

Comme on l'a vu dans la 1ère partie, [i]#include[/i] va servir de référence sur certaine fonctions déjà écrite en C. Par exemple la fonction [i]printf()[/i] qui écrit un message texte à l'écran à besoin du fichier [i]stdio.h[/i]. Lorsqu'on appelle cette fonction dans notre programme, le compilateur ne va pas savoir d'ou viens cette fonction [i]printf()[/i]. Rappellons que le compilateur ne connait que les mots clé, la syntaxe général du C et les commandes préprocesseur. C'est pourquoi il faut alors préciser dans quelle fichier se trouve ce que l'on appelle la "déclaration" de cette fonction. Pour [i]printf()[/i], celle-ci se trouve dans le fichier [i]STDIO.H[/i]. Nous savons ou ce trouve les fichiers .H et nous savons qu'en regardant dedans on va trouver la déclaration de la fonction [i]printf()[/i].

Donc voila la commande à rajouter tout en haut de notre fichier pour utiliser la fonction [i]printf()[/i]:

#include <stdio.h> // pour printf

Mais la commande [i]#include[/i] ne s'arrête pas la. On peut voir que notre fichier .H se trouve entre < et > et parfois dans notre programme on pourrait très bien écrire la syntaxe suivante : [i]#include "monfichier.h"[/i]. Où est la différence ? La différence est que l'on met le nom du fichier d'en-têtes entre " et " lorsque ce fichier se trouve dans le même dossier du projet que l'on travaille, et on met le nom du fichier d'en-têtes entre < et > lorsque ce fichier se trouve dans le dossier par défaut du compilateur.

Il existe une autre commande préprocesseur souvent utilisé, il s'agit de la commande [i]#define[/i]. Cette commande est ce que l'on appelle une macro en général. Elle permet entre autres de définir certains mots. Ces mots correpondront à une valeur.

Par exemple:

#define MaxMesh 60

void affiche(void)
{
if (MaxMesh == 60) printf("ok");
}

Ici on peut voir la définition d'un mot clé : MaxMesh qui à pour valeur 60. Cette commande est traité avant la compilation du code (car c'est une commande préprocesseur). Que va faire alors le compilateur ? Et bien c'est simple, il va remplacer TOUT les mots MaxMesh du code source par 60.

Donc dans notre exemple ca deviendra :

void affiche(void)
{
if (60 == 60) printf("ok");
}

Ce qui est con dans ce cas la je vous l'accorde mais c'est à titre d'exemple.

NOTA BENE : Les commandes préprocesseurs ne sont pas influencé par la casse (majuscules/minuscules), contrairement au reste du code.

Les conditions

Un programme c'est quoi ? C'est des instructions qui s'execute... c'est des données que l'on manipule, en résumé c'est beaucoup de choses. Tout ceci n'est pas fait sans ordre, mais avec une LOGIQUE. Cette logique c'est à vous de la définir à l'aide de différentes opérations et de CONDITIONS.

La conditions la plus simple est sans aucun doutes le IF (traduction si). Si ceci alors cela... sinon si ceci alors cela... sinon faire ceci... Voila comment se résume le IF. Comment marche le IF ? Le IF fonctionne selon une table de vérité. Si ceci est vrai... alors tu fait ca... sinon tu fait ca... Comment cette table ce fait-elle ? En comparant.

Exemple :

if (myVar == 3)
{
printf("myVar vaut 3");
}
else
{
printf("myVar est différent de 3");
}

Examinons ce code. "if (myVar == 3)" se lie : si myVar est bien égale à 3. La comparaison myVar = 3 renvoi VRAI si c'est vérifié et FAUX si il y a erreur.
Si cette condition est vrai alors le programme va executer ce qui suit ENTRE ACCOLADES. Ces accolades comme pour les fonctions définissent le début et la fin d'un bloc. Le mot clé suivant : ELSE va traiter TOUS LES AUTRES CAS si la condition précédente est fausse.

C utilise une syntaxe particulière en ce qui concerne la comparaison, donc voici l'énumération :[list]var1 == var2 (égal à)
var1 != var2 (différent de)
var1 <= var2 (inférieur ou égal à)
var1 < var2 (inférieur à)
var1 >= var2 (supérieur ou égal à)
var1 > var2 (supérieur à)
[/list]Toutefois les conditions ne dépendent pas forcement que d'un seul paramètre on peut très bien avoir deux variables de tester.

Exemple:

if (var1 == var2 && var2 == var3) 
{
}

On trouve ici un nouveau mot clé. Il s'agit d'opérateur de condition. Ici les deux comparaison doivent être VRAI pour entrer dans le if.
Il existe plusieurs opérateur logique que j'énumère ici :[list]&& (ET logique : l'un doit être VRAI et l'autre aussi)
|| (OU logique : soit l'un doit être VRAI soit l'autre ou même les deux)
^^ (OU exclusif : soit l'un doit être VRAI soit l'autre MAIS PAS LES DEUX)
[/list]Il existe un mot clé spécial aussi pour définir l'inverse d'une condition, il s'agit de !

Par exemple:

// rentre dans le if si var1 == var2 est FAUX
// ce qui équivaut à if (var1 != var2)
if ( !(var1 == var2))
{
}

Toutes ces notions relève de la logique BOOLEENE. Il faut savoir que chaque comparaison possède un contraire exprimable avec ! ou même sans.

IF n'est pas la seule conditions qu'il existe en C. Il en existe d'autres, toutefois elles servent de boucles. Il existes 3 type de boucles:
WHILE (TANT QUE), DO WHILE (FAIRE TANT QUE) et le FOR (POUR UNE VALEUR DONNE REPETEE TANT QUE...)

While est peut être la plus simple. La boucle s'execute TANT que la condition est vérifié.

Exemple :

// ici on donne la valeur 3 à la variable myVar
myVar = 3;

// ici on rentre dans la boucle en faisant un premier test : si myVar = 3 alors on rentre
while ( myVar == 3 )
{
// on execute le code qu'on veut faire...
} // et la on remonte à while en haut et on refais un test si myVar est toujours égal à 3 alors on rerentre

// on arrive ici lorsque myVar est devenu différent de 3.

La boucle DO WHILE est identique à WHILE sauf qu'elle rentre au moins une fois dans la boucle sans faire de test

Exemple :

// ici on donne la valeur 3 à la variable myVar
myVar = 3;

do 
{
}while (myVar == 3); // et on fais un test si myVar est égal à 3 alors on remonte à do et on recommence

// on arrive ici lorsque myVar est devenu différent de 3.

La dernière boucle est la boucle FOR. Cette boucle permet de partir d'un point à un autre. Elle est idéale pour parcourir une tranche d'un tableau.

// on initialise au début i à 0, et boucle tant que i est inférieur à 10
// a chaque boucle i s'incrémente de 1
for (i = 0; i < 10; i++)
{
}

Voila c'est tout pour cette deuxième partie !

To be continued... ;)