Du C au C++ De la programmation procédurale à l'objet

Les structures

Auteur

...

- Titre : Du C au C++ De la programmation procédurale à l'objet

- Auteur : ...

- Parution : 17 octobre 2014

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2014 .... Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Structure

- I-A. Qu'est-ce qu'une structure ?

- I-B. Disposer d'une structure dans un programme

- I-B-1. Définir un type de structure

- I-B-2. Où placer sa définition de structure

- I-B-3. Déclarer ses variables structure

- I-B-4. Utiliser un typedef

- I-C. Utiliser une structure

- I-C-1. Accès aux éléments avec l'opérateur point

- I-C-2. Priorité de l'opérateur point

- I-C-3. Une structure comme champ dans une structure

- I-C-4. Initialiser une structure à la déclaration

- I-C-5. Copier une structure

- I-D. Mise en pratique : définir, déclarer, initialiser des structures

- II. Structures et fonctions

- II-A. Retourner une structure

- II-B. Structures en paramètre de fonction

- II-C. Mise en pratique : structures et fonctions

- II-D. Expérimentation

- II-D-1. Fourmi 1 : une fourmi mobile à l'écran

- II-D-2. Voiture 2 : structure circuit, structure voiture

I - Structure

I-A - Qu'est-ce qu'une structure ?

La structure en programmation est un type de données qui permet de regrouper en une seule unité plusieurs variables éventuellement de types différents. C'est UNE variable qui est un ensemble de variables. C'est très utile pour composer des objets complexes qui réunissent différentes facettes. Par exemple si l'on veut faire un carnet de rendez-vous, chaque rendez-vous suppose des informations du genre :

- un libellé ;

- un lieu ;

- une date ;

- un horaire de début ;

- un horaire de fin ;

- une catégorie (bureau, perso, loisir, etc.) ;

- etc.

Toutes ces informations peuvent être regroupées au sein d'une structure et plusieurs structures permettront d'avoir plusieurs rendez-vous. L'intérêt ensuite est d'avoir un nombre illimité de rendez-vous. Autre exemple : dans un jeu vidéo en 2D, un ennemi est défini par une position, un déplacement, un type (rampant, grouillant, serpentant, plombant, assommant), un taux d'agressivité, une couleur, une ou plusieurs images, etc.

Pour chacune de ces informations, il s'agit d'abord de choisir un type de variable approprié et ensuite de regrouper toutes ces informations au sein d'une structure « ennemi » afin de pouvoir gérer un ou plusieurs ennemis dans le jeu. Chaque élément de la structure est appelé un « champ ». Notre structure rendez-vous a au moins 8 champs et la structure ennemie en a au moins 8 (position et déplacement horizontaux et verticaux, type…).

I-B - Disposer d'une structure dans un programme

I-B-1 - Définir un type de structure

À la base pour définir une structure, on utilise le mot-clé struct suivi d'une liste de déclarations de variables (sans initialisation) entre accolades et d'un point-virgule :

struct {
float x,y; // attention il n'est pas possible d'initialiser
float dx,dy; // ses variables lors de la définition
int color; // de la structure
char lettre;
};

Tel quel c'est bien un type de structure, mais comme il n'a pas de nom, il n'est pas possible de l'utiliser dans tout le programme. Pour nommer son type de structure, il suffit d'ajouter le nom voulu après le mot-clé struct:

struct entite { // nommer son type de structure
float x,y;
float dx,dy;
int color;
char lettre;
};

Nous venons de définir le type de structure. Attention c'est un nom de type, cela ne correspond pas à une variable, mais à un type de variable.

I-B-2 - Où placer sa définition de structure

En général, la définition d'un type de structure se place en global. A,au début du fichier juste après les includes, si le programme tient sur un seul fichier C. S'il y a plusieurs fichiers C, cette définition sera placée dans un fichier d'en-têxte .h et ce dernier sera inclus dans tous les fichiers C de codes qui ont besoin de cette définition pour fonctionner.

Il est possible de définir un type de structure dans une fonction, mais dans ce cas, ce type ne sera connu que dans le bloc de la fonction et invisible partout ailleurs.

I-B-3 - Déclarer ses variables structure

Après avoir défini un type de structure, pour avoir des variables de ce type, la procédure est identique à celle des autres variables. Il faut indiquer le type, donner un nom pour la variable et clore avec un point-virgule.

Pour avoir des variables du type « struct entite » défini ci-dessus, nous écrirons quelque part dans le programme :

struct entite e1, e2;

e1 et e2 sont deux variables du type « struct entite ».

Remarque :

Le nom du type de la structure est indépendant de celui de la variable de ce type et il est possible d'avoir une variable du même nom :

struct entite entite; // fonctionne

Par ailleurs, il est possible de combiner une définition de type de structure et des déclarations de variables de ce type de structure. La définition de la structure  pix  ci-dessous peut être suivie d'une suite de déclarations de variables :

struct pix{
int x, y;
int color;
}P1, P2, toto;

La définition de la structure struct pix est suivie des déclarations des variables P1, P2 et toto qui sont de type struct pix.

I-B-4 - Utiliser un typedef

Lorsque l'on utilise des structures en environnement C, il est fastidieux de toujours devoir ajouter le mot-clé  struct . Le  typedef  permet de s'en débarrasser.

La commande  typedef  donne la possibilité de définir ses propres types à partir des types de base. Le principe est simple : déclarer une variable du type voulu, ajouter  typedef  devant… et le nom de la variable devient synonyme du type de cette variable. Par exemple :

typedef int toto;

permet de définir le type « toto » dans un programme ; toto devient synonyme de int et peut être utilisé à la place dans le programme :

#include <stdio.h>
#include <stdlib.h>
typedef int toto; // création du type toto
int main()
{
toto test = 80;
printf("la variable test vaut : %d\n", test);
system("PAUSE");
return 0;
}

L'intérêt de pouvoir rebaptiser ses types dépend de certaines situations, par exemple créer un langage en français. Mais dans un environnement C, typedef est utilisé systématiquement avec les structures afin de faire disparaître le mot-clé struct.

Soit par exemple le type struct ennemi dans un programme :

struct ennemi{
char type; // type de l'ennemi (une lettre)
float nuisance; // potentiel de nuisance
float x,y,px,py; // position et déplacement
int force; // indicateur de force pour les combats
};

En ajoutant le mot-clé typedef devant, on peut définir un synonyme de struct ennemi de la façon suivante :

typedef struct ennemi{
char type;
float nuisance;
float x,y,px,py;
int force;
}t_ennemi;

et de façon plus concise, on peut définir directement son type de structure :

typedef struct{
char type;
float nuisance;
float x,y,px,py;
int force;
}t_ennemi;

t_ennemi devient un nom de type pour la structure dans les deux cas du fait de l'utilisation de typedef et t_ennemi peut être utilisé partout dans le programme :

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
t_ennemi E;
int i;
srand(time(NULL));
E=init_ennemi();
affiche_ennemi(E);
return 0;
}

Le préfixe  t_ est pratique. Il permet de préciser qu'il s'agit d'un type et non d'un nom de variable. Cela n'a rien d'obligatoire.

Déclarer ses variables structure en C++

Dans un environnement C++, c'est-à-dire sur un fichier de code .cpp, la définition d'une structure est la même :

struct entite {
float x,y;
float dx,dy;
int color ;
char lettre;
};

Mais le mot-clé  struct  est inutile lors de la déclaration d'une structure, il y a en quelque sorte l'utilisation implicite d'un « typedef   »typedef. Au lieu d'écrire comme en C :

struct entite e1;

nous pouvons écrire directement :

entite e1;

Jusqu'au chapitre sur le C++, nous restons dans cet ouvrage en C pur et d'une façon générale, nous supprimons le mot-clé  struct  struct à la déclaration d'une structure avec l'utilisation d'un  typedef .

I-C - Utiliser une structure

I-C-1 - Accès aux éléments avec l'opérateur point

Toutes les variables d'une structure, c'est-à-dire tous les champs de la structure, sont accessibles via l'opérateur « . » dit « point » de la façon suivante :

struct pix p;
p.x = 0;
p.y = 50;
p.color = 255;

La première ligne déclare une structure du type struct pix nommée p. Les trois lignes suivantes initialisent chacun des champs de la variable p. Le champ x prend la valeur 0, le champ y prend la valeur 50 et le champ color prend la valeur 255.

I-C-2 - Priorité de l'opérateur point

Priorité maximum, tous les autres opérateurs passent après dans l'évaluation d'une expression (cf. annexe priorité et associativité des opérateurs).

I-C-3 - Une structure comme champ dans une structure

L'imbrication de structures est possible. Une structure peut avoir une autre structure comme champ à condition que son type soit connu au moment de la définition de la structure. Par exemple :

typedef struct rgb{ // valeur de rouge, de vert et de bleu
int r,g,b; // pour avoir une couleur
}rgb;
typedef struct coord{
int x, y; // pour stocker une position en 2D
}coord;
typedef struct rectangle{
coord hg; // coord du coin haut gauche
coord bd; // coord du coin bas droite
rgb color; // couleur du rect
}rectangle;

La structure rectangle permet de stocker les informations de rectangles colorés. La structure  rgb  concentre les informations de couleurs avec trois champs, un entier pour le rouge, un entier pour le vert et un entier pour le bleu (en RGB - red, green, blue - une couleur est obtenue par un mélange de rouge, de vert et de bleu). La structure coord permet de stocker les coordonnées d'un point dans un espace à deux dimensions. Pour dessiner un rectangle, nous avons besoin de deux points, le point en haut à gauche et le point en bas à droite :

La structure finale pour stocker les informations d'un rectangle comprend une structure rgb pour sa couleur et deux structures coord pour chacun des points haut gauche (hg) et bas droite (bd).

Dans cet exemple, les structures rgb et coord doivent absolument être définies avant la structure rectangle pour y être connues au moment de sa définition. Dans le cas contraire, il y aura une erreur à la compilation.

L'accès aux champs d'une structure se fait avec l'opérateur . (point) et rien ne change si le champ est lui-même une structure. L'opérateur point sera utilisé une nouvelle fois pour accéder aux champs de la structure élément de la façon suivante :

rectangle r1;
r1.hg.x = 0;
r1.hg.y = 0;
r1.bd.x = 100;
r1.bd.y = 50;
r1.color.r = 255;
r1.color.g = r1.color.b = 0;

Le type de l'expression r1.hg par exemple est une structure du type coord et on accède à chacun de ses champs en utilisant soit une nouvelle fois l'opérateur point, soient les expressions e1.hg.x et e1.hg.y et les parenthèses sont inutiles.

I-C-4 - Initialiser une structure à la déclaration

Une structure peut être initialisée à la déclaration en lui affectant une suite de valeurs entre accolades closes par un point-virgule. Chaque valeur est affectée à un champ correspondant dans l'ordre et doit en respecter le type. Soit la structure test :

typedef struct test {
float x, y;
int px, py;
char lettre;
}test;

et une déclaration suivie d'une initialisation dans le programme :

test t1={ 1.5, 2.0, 150,300,'A'};

Pour la variable t1 de type struct test, 1.5 est affecté au champ x, 2.0 est affecté au champ y, 150 est affecté au champ px, 300 est affecté au champ py et le champ lettre prend la valeur 'A'.

Dans le cas des structures imbriquées, il y a le choix entre une seule liste pour les valeurs affectées ou le détail entre accolades pour chaque structure, par exemple :

typedef struct date{
int jour, mois, annee;
}date;
typedef struct time{
int heure, minute, seconde;
}time;
typedef struct moment{
date date;
time time;
}moment;

Dans le programme, un exemple de déclaration suivie d'une initialisation :

moment m1={ 25,12,2006,13,45,30};

Les champs de m1 valent respectivement 25 pour jour, 12 pour mois, 2006 pour année, 13 pour heures, 45 pour minutes et 30 pour secondes.

On peut également détailler le contenu de chaque structure imbriquée de la façon suivante :

moment m1={ {25,12,2006},
{13,45,30}
};

Si le nombre des valeurs de la liste est supérieur au nombre des champs, il y a une erreur de compilation. En revanche, si le nombre des valeurs de la liste est inférieur, les champs restants sont initialisés à 0. Ce test permet de le vérifier :

#include <stdio.h>
#include <stdlib.h>
struct date{
int jour, mois, annee;
};
struct time{
int heure, minute, seconde;
};
struct moment{
struct date date;
struct time time;
};
int main()
{
struct moment m={{1}, {3,4}};
printf("m.date.jour=%d\n",m.date.jour);
printf("m.date.mois=%d\n",m.date.mois);
printf("m.date.annee=%d\n",m.date.annee);
printf("m.time.heure=%d\n",m.time.heure);
printf("m.time.minute=%d\n",m.time.minute);
printf("m.time.seconde=%d\n",m.time.seconde);
system("PAUSE");
return 0;
}

Résultat :

m.date.jour=1
m.date.mois=0
m.date.annee=0
m.time.heure=3
m.time.minute=4
m.time.seconde=0

I-C-5 - Copier une structure

Les copies et les affectations de structure à structure de même type sont autorisées, par exemple :

coord pt1, pt2;
pt1.x=100;
pt1.y=200;

pt2=pt1;

La structure pt1 est initialisée avec les valeurs 100 et 200, ensuite elle est affectée à la structure de même type pt2. Les valeurs de pt1 sont recopiées dans pt2.

I-D - Mise en pratique : définir, déclarer, initialiser des structures

Exercice 1

Une menuiserie industrielle gère un stock de panneaux de bois. Chaque panneau possède une largeur, une longueur et une épaisseur en millimètres ainsi que le type de bois. Il y a trois types de bois : pin (code 0), chêne (code 1) et hêtre (code 2).

- Définissez une structure panneau contenant toutes les informations relatives à un panneau de bois.

- Dans un programme, initialisez deux panneaux à la déclaration et deux panneaux avec des valeurs aléatoires. Affichez les valeurs.

Exercice 2

Un grossiste de composants électroniques vend quatre types de produits :

- des cartes mères (code 1) ;

- des processeurs (code 2) ;

- des barrettes mémoire (code 3) ;

- des cartes graphiques (code 4).

Chaque produit possède une référence qui est un nombre entier, un prix en euros et une quantité disponible. Définissez une structure produit qui code un produit, et dans un programme, initialisez deux produits à la déclaration et deux produits avec des valeurs entrées par l'utilisateur. Affichez les valeurs et clonez le produit le plus cher.

Exercice 3

Soit un programme affichant des flocons de neige qui tombent du haut de la fenêtre de la console. Chaque flocon est une lettre. Donnez une structure de données qui permet de coder un flocon. Dans un programme, déclarez quatre flocons. Initialisez un flocon à la déclaration et un autre avec des valeurs aléatoires. Copiez chaque flocon et affichez les valeurs des quatre flocons de façon claire.

Exercice 4

Le monde où se trouve le player est parcouru par des ennemis. Donnez les caractéristiques essentielles d'un ennemi à imaginer et la structure de données la mieux adaptée. Initialisez un ennemi à la déclaration et un autre avec des valeurs aléatoires. Affichez les valeurs de cet ennemi. Faites un clone.

II - Structures et fonctions

II-A - Retourner une structure

Comme la copie de structure est autorisée, les structures peuvent être prises comme

valeurs de retour. Soit la structure coord :

typedef struct coord{
int x, y;
}coord;

Voici typiquement une fonction qui permet d'initialiser une structure après sa déclaration n'importe où dans le programme :

coord init_point1()
{
coord t0;
t0.x = rand()%1024;
t0.y = rand()%768;
return t0;
}

Dans la fonction :

- une structure du type souhaité est déclarée ;

- chacun de ses champs est initialisé avec des valeurs aléatoires ou calculées ;

- pour finir, la structure résultante est retournée au contexte d'appel.

Bien sûr des valeurs peuvent être transmises en paramètres, cela donne par exemple :

coord init_point2(int x, int y)
{
coord t0;
t0.x = x;
t0.y = y;
return t0;
}

Et dans le programme, la structure retournée par la fonction est affectée à une structure du contexte d'appel :

#include <stdio.h>
#include <stdlib.h>
int main()
{
coord pt1, pt2;
pt1 = init_point1();
pt2 = init_point2(40, 40);
printf("pt1.x=%d, pt1.y=%d\n"
"pt2.x=%d, pt2.y=%d\n",pt1.x,pt1.y,pt2.x, pt2.y);
return 0;
}

L'appel de init_point1() renvoie une structure coord initialisée avec des valeurs aléatoires et affectée à la variable coord pt1. L'appel de init_point2() renvoie une autre structure coord initialisée avec (40,40) et affectée à la variable coord pt2.

II-B - Structures en paramètre de fonction

Une structure est passée à un paramètre de fonction de même type par valeur comme n'importe quel autre type de variable. La structure passée est copiée dans le paramètre de la fonction.

Attention !

Soit par exemple la fonction ci-dessous, elle permet de faire progresser de 1 la position horizontale d'un point. Si la position dépasse 1000, elle retourne à 0 :

void modif(coord pt)
{
pt.x++;
if (pt.x>1000)
pt.x=0;
}

Dans un programme, l'appel est le suivant :

#include <stdio.h>
#include <stdlib.h>
int main()
{
coord p={ 10,20};
printf("p.x=%d, p.y=%d\n", p.x, p.y);
modif(p);
printf("p.x=%d, p.y=%d\n", p.x, p.y);
return 0;
}

Qu'imprime le programme ?

p.x=10, p.y=20
p.x=10, p.y=20

Il n'y a pas de modification de la structure p. La structure en paramètre de la fonction modif() est locale à la fonction. C'est une autre variable. Au moment de l'appel, l'affectation implicite suivante est réalisée :

modif(pt = p);

Mais à l'issue de la fonction, la structure p dans le contexte d'appel n'a pas été touchée. Pour avoir une fonction de modification des valeurs contenues dans une structure, il faut passer la structure à modifier en argument et retourner la structure modifiée, ce qui donne :

coord modif(coord pt)
{
pt.x++;
if (pt.x>1000)
pt.x=0;
return pt;
}

avec l'appel :

int main()
{
coord p={10,20};;
printf("p.x=%d, p.y=%d\n", p.x, p.y);
p=modif(p);
printf("p.x=%d, p.y=%d\n", p.x, p.y);
return 0;
}

Cette fois, la copie modifiée dans la fonction est affectée à la structure p locale au

main() et le programme imprime :

p.x=10, p.y=20
p.x=11, p.y=20

II-C - Mise en pratique : structures et fonctions

Exercice 1

Une menuiserie industrielle gère un stock de panneaux de bois. Chaque panneau possède une largeur, une longueur, une épaisseur en millimètres, un volume et un type de bois. Il y a trois types de bois : pin (code 0), chêne (code 1), hêtre (code 2).

- Définissez une structure panneau contenant toutes les informations relatives à un panneau de bois.

- Écrivez les fonctions de saisie d'un panneau de bois.

- Écrivez une fonction d'affichage d'un panneau de bois.

- Écrivez une fonction qui calcule le volume en mètre cube d'un panneau.

- Écrivez une fonction qui affiche le volume d'un panneau de bois.

Exercice 2

Un grossiste de composants électroniques vend quatre types de produits :

- des cartes mères (code 1) ;

- des processeurs (code 2) ;

- des barrettes mémoire (code 3) ;

- des cartes graphiques (code 4).

Chaque produit possède une référence qui est un nombre entier, un prix en euros et une quantité disponible.

- Définissez une structure « produit » qui code un produit.

- Écrivez une fonction de saisie des données d'un produit.

- Écrivez une fonction d'affichage des données d'un produit.

- Écrivez une fonction qui permet à un utilisateur de saisir la commande d'un produit.

L'utilisateur identifie le produit et saisit la quantité demandée. L'ordinateur affiche toutes les données de la commande, y compris le prix.

Exercice 3

Soient les deux types de structure date et « personnepersonne »« personnepersonne ».

typedef struct date{
int jour, mois, annee;
}date;
typedef struct personne{
int identifiant;
struct date date_embauche;
struct date date_poste;
}personne;

- Écrivez une fonction pour initialiser une structure de type personne.

- Écrivez une fonction d'affichage de façon à obtenir soit l'un, soit l'autre des deux affichages ci-dessous :

Identifiant : 1224
date embauche (jj mm aa) : 16 01 08
date poste = date embauche ? (O/N) : O
Identifiant : 1224
date embauche (jj mm aa) : 16 01 08
date poste = date embauche ? (O/N) : N
date poste (jj mm aa) : 01 09 08

Exercice 4

Soit une entité dans un jeu vidéo en mode console. Elle est définie par une position, un déplacement, un type (rampant, grouillant, serpentant, plombant, assommant), une couleur. L'entité a également un nom (une lettre) qui sert pour son apparence et une série de taux : agressivité, colère, convoitise, faim, et peur.

Définissez la structure de données pour coder une entité. Écrivez une fonction d'initialisation, une fonction de mise à 0 (reset) et une fonction d'affichage. Testez dans un programme avec un menu : quitter, afficher, initialiser, reset.

II-D - Expérimentation

L'intérêt des structures est de permettre de regrouper en une seule variable plusieurs variables. Cette propriété donne aux fonctions la possibilité d'innombrables paramètres et valeurs de retour. En recevant une structure en argument la fonction reçoit en une fois un ensemble de valeurs, de même en retournant une structure la fonction retourne en une fois un ensemble de valeurs. Il devient possible de gérer de plus gros ensembles de variables et ça commence à donner de la force aux programmes.

II-D-1 - Fourmi 1 : une fourmi mobile à l'écran

L'objectif est de faire circuler une fourmi à l'écran. Elle ne fait que bouger, rien d'autre.

Elle ne se nourrit pas et ne rencontre aucune autre bestiole.

/*************************************************************
fourmi 1 : une fourmi mobile à l'écran
*************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <windows.h>
// en global définition du type de la structure fourmi
typedef struct {
float x, y; // la position de la fourmi
float dx, dy; // le déplacement
int color; // la couleur
int lettre; // l'apparence
}fourmi;
// Limites de la zone de jeu
const int TX = 40;
const int TY = 20;
// les actions
fourmi init (void);
void affiche (fourmi f, int color);
fourmi avance (fourmi f);
int top (int*start, int dur);
void gotoxy (int x, int y);
void textcolor (int color);
/*************************************************************
ACTION
*************************************************************/
int main()
{
int start = 0;
fourmi f;
srand(time(NULL));
f = init();
while (!kbhit()){
if (top(&start, 75)){
affiche(f, 0); // effacer
f = avance(f); // changer la position
affiche(f, f.color); // réafficher
}
}
return 0;
}
/*************************************************************
INITIALISATION
*************************************************************/
fourmi init()
{
fourmi f;
f.x = rand() % TX;
f.y = rand() % TY;
f.dx = ((float)rand() / RAND_MAX) * 4 2;
f.dy = ((float)rand() / RAND_MAX) * 4 2;
f.lettre = 'A' + rand() % 26;
f.color = 1 + rand() % 255;
return f;
}
/*************************************************************
AFFICHAGE
*************************************************************/
void affiche(fourmi f, int color)
{
gotoxy(f.x, f.y);
textcolor(color);
putchar(f.lettre);
}
/*************************************************************
MOUVEMENT
*************************************************************/
fourmi avance(fourmi f)
{
f.x += f.dx;
if (f.x < 0){
f.x = 0;
f.dx = ((float)rand() / RAND_MAX) * 2;
}
if (f.x >= TX){
f.x = TX 1;
f.dx = ((float)rand() / RAND_MAX)* -2;
}
f.y += f.dy;
if (f.y < 0){
f.y = 0;
f.dy = ((float)rand() / RAND_MAX) * 2;
}
if (f.y >= TY){
f.y = TY 1;
f.dy = ((float)rand() / RAND_MAX)* -2;
}
return f;
}
/*************************************************************
OUTILS
*************************************************************/
int top(int*start, int dur)
{
int res = 0;
if (clock()>*start + dur){
*start = clock();
res = 1;
}
return res;
}
void gotoxy(int x, int y)
{
COORD c;
c.X = x;
c.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c);
}
void textcolor(int color)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}
/*************************************************************
*************************************************************/

Précisions sur les paramètres et les retours des fonctions

La fonction init() utilise uniquement le mécanisme de retour : une structure fourmi est initialisée dans le corps de la fonction et juste avant de disparaître de la mémoire, elle est recopiée dans une autre en dehors de la fonction, la fourmi qui est déclarée dans le main et qui demeure en permanence jusqu'à la fin du programme.

La fonction avance() utilise les deux mécanismes, une fourmi en paramètre d'entrée et une fourmi en valeur de retour à la sortie. Au moment de l'appel, une fourmi du contexte d'appel est recopiée dans le paramètre de type fourmi. Dans le corps de la fonction, les instructions sont exécutées sur la copie et donc la fourmi du contexte d'appel n'est pas modifiée. Pour récupérer cette copie qui est modifiée à l'intérieur de la fonction, la fourmi du contexte d'appel doit recopier cette copie modifiée de la fonction lorsqu'elle est retournée. D'où l'expression :

f = avance(f);

La fonction affiche() n'utilise qu'un paramètre fourmi. En effet, la fonction a besoin des informations de la fourmi à afficher, mais elle ne les modifie pas, ainsi la copie dans le paramètre suffit et il n'y a rien à retourner :

affiche(f, f.color)

La fonction top() utilise un pointeur et une technique qui est présentée au chapitre « Les pointeurs », mais le principe ici n'est pas très compliqué. Un pointeur est une variable qui prend pour valeur une adresse mémoire. En paramètre, cela permet de donner l'adresse d'une variable à la fonction, et dans le corps de la fonction de pouvoir écrire à cette adresse mémoire (grâce à l'opérateur étoile avec l'expression *start dans la fonction). Cette utilisation des pointeurs permet de transformer le paramètre d'entrée en une entrée + sortie. Cela est utile lorsque plusieurs retours sont nécessaires comme dans cette fonction. Le retour classique de type int est utilisé pour le résultat de la fonction, ce qui permet de l'appeler directement dans le test :

if( top (&start, 150) ){ }

Lors de l'appel de la fonction, c'est l'adresse de la variable start qui est passée au pointeur de même nom, soit l'expression :

&start

Dans la fonction, le paramètre pointeur int*start est utilisé afin de pouvoir modifier éventuellement la valeur de la variable start du contexte d'appel si le temps demandé est passé.

II-D-2 - Voiture 2 : structure circuit, structure voiture

Grâce aux structures, nous pouvons reprendre le programme voiture 1 présenté précédemment et lui ajouter une course à quatre voitures. Rappelons que dans la première version, pour une voiture, il faut sept variables avec des fonctions dédiées à ces variables déclarées en globale.

void select_trait (void);
void avancer (int l, int color1, int color2);

Avec quatre voitures, il faut donc 28 variables et des fonctions dédiées pour chaque voiture ou des fonctions généralisées avec un nombre très élevé de paramètres.

En revanche, avec un type de structure voiture, nous avons quatre voitures, chacune d'elle est un ensemble de sept variables. Les mêmes fonctions servent pour chaque voiture. Il suffit de passer la voiture concernée en paramètre, et si des modifications sont opérées, il faut récupérer la voiture modifiée en valeur de retour.

Comme il y a plusieurs voitures, il faut arrêter la course dès qu'une voiture gagne et le test de la boucle principale doit être modifié. De plus, l'initialisation des voitures vaut la peine aussi d'une fonction à part qui permet de donner à chacune des valeurs différentes de façon simple, aisément modifiable dans le code.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#include <conio.h>
// taille de la zone de jeu
#define TX 80
#define TY 24
// Les quatre directions
enum{ NORD, EST, SUD, OUEST };
/* Pour un circuit :
- 6 traits max
- un trait = une direction et un nombre de pas ;
- un nombre de traits ;
- un nombre de tours.
*/
// Pour avoir un circuit :
typedef struct {
int d1,p1,d2,p2,d3,p3,d4,p4,d5,p5,d6,p6; //6 traits
int nbtrait; // nombre de traits dans le circuit
int nbtour; // nombre de tours à faire pour une course
}circuit;
/*Pour une voiture :
- une position x,y ;
- une direction ;
- un nombre de pas ;
- un identificateur du trait courant ;
- un compteur de tours ;
- une vitesse ;
- le temps de départ (associé à vitesse).
En option, pour différencier toutes les voitures
avec couleur et apparence :
- une lettre pour l'apparence ;
- une couleur d'effacement ;
- une couleur d'affichage.
*/
typedef struct {
int x, y, dir, pas, idtrait, tour, vit, tmps;
int lettre; // lettre correspondante
int color1, color2; // deux couleurs
}voiture;
// les déclarations des fonctions
circuit     create_circuit1    (void);
voiture     create_voiture     (int startx, int starty,int c1, int c2,
int l);
voiture     bouge         (voiture v,circuit c);
int     run                 (voiture v1,voiture v2,voiture v3,
voiture v4,circuit c);
voiture     select_trait     (voiture v,circuit c);
voiture     avancer         (voiture v);
void         afficher         (voiture v,int color);
int         top             (voiture v);
void         gotoxy         (int x, int y);
void         textcolor         (int color);
/********************************************************
ACTION
********************************************************/
int main()
{
circuit c;
voiture v1, v2, v3, v4;
srand(time(NULL));
c = create_circuit1();
v1 = create_voiture(2, 2, 0, 10, '1');
v2 = create_voiture(3, 3, 1, 11, '2');
v3 = create_voiture(4, 4, 2, 12, '3');
v4 = create_voiture(5, 5, 3, 13, '4');
while (run(v1, v2, v3, v4, c)){
/* est généralisé et remplacé avec la fonction bouge()
if(top(v1)){
v1.tmps=clock();
if(v1.pas==0)
v1=select_trait(v1,c);
v1=avancer(v1);
}
if(top(v2)){
v2.tmps=clock();
if(v2.pas==0)
v2=select_trait(v2,c);
}
v3.tmps=clock();
if(v3.pas==0)
v3=select_trait(v3,c);
v3=avancer(v3);
}
if(top(v4)){
v4.tmps=clock();
if(v4.pas==0)
v4=select_trait(v4,c);
v4=avancer(v4);
}*/
// un appel de la fonction bouge() par voiture
v1 = bouge(v1, c);
v2 = bouge(v2, c);
v3 = bouge(v3, c);
v4 = bouge(v4, c);
}
gotoxy(0, TY);
system("PAUSE");
return 0;
}
/********************************************************
CRÉATION DU CIRCUIT
********************************************************/
/*
Un seul circuit. C'est un carré de 10 sur 10, deux tours
prévus. Initialisation des variables circuit en conséquence.
Initialisation de la voiture
*/
circuit create_circuit1()
{
circuit c;
// définition du circuit
c.d1 = EST; // trait 1
c.p1 = 10;
c.d2 = SUD;
c.p2 = 10;
c.d3 = OUEST;
c.p3 = 10;
c.d4 = NORD;
c.p4 = 10;
c.nbtrait = 4;
c.nbtour = 2;
return c;
}
/********************************************************
CRÉATION VOITURE
********************************************************/
voiture create_voiture(int startx, int starty, int c1,
int c2, int l)
{
voiture v;
v.x = startx;
v.y = starty;
v.pas = 0;
v.idtrait = 0;
v.tour = 0;
v.tmps = 0;
v.vit = 0;
v.color1 = c1;
v.color2 = c2;
v.lettre = l;
return v;
}
/********************************************************
BOUGER
********************************************************/
/*
Contrôle de la fin de la course. La course continue tant
qu'aucune voiture n'a fini et s'arrête dès qu'une voiture
a fini.
*/
int run(voiture v1,voiture v2,voiture v3,voiture v4,
circuit c)
{
return (v1.tour<c.nbtour && v2.tour<c.nbtour &&
v3.tour<c.nbtour && v4.tour<c.nbtour);
}
// topage voitures
int top(voiture v)
{
return (clock() > v.tmps + v.vit);
}
/*
*/
voiture bouge(voiture v, circuit c)
{
if (top(v)){
v.tmps = clock();
if (v.pas == 0)
v = select_trait(v, c);
v = avancer(v);
}
return v;
}
/*
sélectionner un trait à parcourir
*/
{
// contrôler si un tour a été effectué
if (v.idtrait == c.nbtrait){
v.tour++;
v.idtrait = 0;
}
// changer la vitesse pour chaque trait
v.vit = 150 + rand() % 150;
// sélection du trait
switch (v.idtrait){
case 0:
v.dir = c.d1;
v.pas = c.p1;
break;
case 1:
v.dir = c.d2;
v.pas = c.p2;
break;
case 2:
v.dir = c.d3;
v.pas = c.p3;
break;
case 3:
v.dir = c.d4;
v.pas = c.p4;
break;
case 4:
v.dir = c.d5;
v.pas = c.p5;
break;
case 5:
v.dir = c.d6;
v.pas = c.p6;
break;
}
// préparer pour le tour suivant
v.idtrait++;
return v;
}
/*
parcourir le trait courant
*/
voiture avancer(voiture v)
{
if (v.pas>0){
v.pas--; // un pas de moins
// effacer
afficher(v, v.color1);
// avancer selon direction
switch (v.dir){
case NORD: v.y--; break;
case EST: v.x++; break;
case SUD: v.y++; break;
case OUEST: v.x--; break;
}
// réafficher
afficher(v, v.color2);
}
return v;
}
/*
afficher une voiture de la couleur color
*/
void afficher(voiture v, int color)
{
gotoxy(v.x, v.y);
textcolor(color);
putchar(v.lettre);
}
/********************************************************
OUTILS
********************************************************/
// déplacement curseur
void gotoxy(int x, int y)
{
COORD c;
c.X = x;
c.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c);
}
// gestion couleur
void textcolor(int color)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}
/********************************************************
********************************************************/

L'algorithme est toujours le même que celui du programme voiture 1 vu précédemment. L'utilisation de structures permet d'avoir plusieurs voitures facilement. Pour avoir 10 voitures, il suffit de déclarer 10  struct voiture, de les initialiser et dans la boucle, avoir une fois pour chaque voiture v1, v2…v10 le bloc :

if(top(v1)){
v1.tmps=clock();
if(v1.pas==0)
v1=select_trait(v1,c);
v1=avancer(v1);
}

Généralisation d'un bloc d'instructions en une fonction

Ce bloc est répété pour chaque voiture. Pour fonctionner, il a besoin d'une voiture et d'un circuit. Sur les deux paramètres, un seul change, c'est la voiture. Alors il faut le généraliser, c'est-à-dire le transformer en une fonction qui prend en paramètre une voiture et un circuit, et comme le paramètre voiture peut être modifié, il est retourné en sortie. C'est la fonction :

voiture bouge (voiture v, circuit c)
{
if(top(v)){
v.tmps=clock();
if(v.pas==0)
v=select_trait(v,c);
v=avancer(v);
}
return v;
}

Au moment de l'appel, il suffira de préciser de quelle voiture et quel circuit il s'agit.

v1 = bouge(v1, c);
v2 = bouge(v2, c);
v3 = bouge(v3, c);
v4 = bouge(v4, c);

Comme il y a plusieurs voitures, la course prend fin si une voiture a terminé. Le test aurait pu être mis dans la boucle principale. Mais c'est plus propre d'écrire une fonction qui retourne le résultat. Notre objectif maintenant est de faire tourner 100, 1000 ou 10 000 voitures. Clairement, les seules structures ne suffisent plus et il faut leur adjoindre des tableaux, il faut faire des tableaux de structures.