Les structures

Auteur

Frédéric Drouillon

N'hésitez pas à donner vos avis par rapport à ce tutoriel : Commentez

- Titre : Les structures

- Auteur : Frédéric Drouillon

- Parution : 11 décembre 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 Frédéric Drouillon. 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

- III. Conclusion

- IV. Remerciements

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 de pouvoir 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 premièrement 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 six champs et la structure ennemie au moins huit champs (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 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 « struct entite ». 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. 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ête .h et ce fichier d'en-tête sera inclus dans tous les fichiers C de code 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, c'est identique aux 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 trois variables 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 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. 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 à 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 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 et à gauche et le point en bas et à 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 une nouvelle fois l'opérateur point, soit 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 au champ py et le champ lettre prend la valeur 'A'.

Dans le cas de 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 heure, 45 pour minute et 30 pour seconde.

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 la structure pt1 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), hêtre (code 2).

- Définir une structure panneau contenant toutes les informations relatives à un panneau de bois ;

- Dans un programme initialiser deux panneaux à la déclaration et deux panneaux avec des valeurs aléatoires. Afficher 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éfinir une structure produit qui code un produit et dans un programme initialiser deux produits à la déclaration et deux produits avec des valeurs entrées par l'utilisateur. Afficher les valeurs et cloner 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. Donner une structure de données qui permet de coder un flocon. Dans un programme, déclarer quatre flocons. Initialiser un flocon à la déclaration et un autre avec des valeurs aléatoires. Copier chaque flocon et afficher les valeurs des quatre flocons de façon claire.

Exercice 4

Le monde où se trouve le player est parcouru par des ennemis. Donner les caractéristiques essentielles d'un ennemi à imaginer et la structure de données la mieux adaptée. Initialiser un ennemi à la déclaration et un autre avec des valeurs aléatoires. Afficher les valeurs de cet ennemi. Faire 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

valeur 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 elle est 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éfinir une structure panneau contenant toutes les informations relatives à un panneau de bois ;

- Écrire les fonctions de saisie d'un panneau de bois ;

- Écrire une fonction d'affichage d'un panneau de bois ;

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

- Écrire 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éfinir une structure « produit » qui code un produit ;

- Écrire une fonction de saisie des données d'un produit ;

- Écrire une fonction d'affichage des données d'un produit ;

- Écrire 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 « personne » :

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

- Écrire une fonction pour initialiser une structure de type « personne ».

- Écrire 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 : taux d'agressivité, de colère, de convoitise, de faim, de peur.

Définir la structure de données pour coder une entité. Écrire une fonction d'initialisation, une fonction de mise à 0 (reset) et une fonction d'affichage. Tester 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.

Code complet

1. /*************************************************************

2. fourmi 1 : une fourmi mobile à l'écran

3. *************************************************************/

4. #include <stdio.h>

5. #include <stdlib.h>

6. #include <time.h>

7. #include <conio.h>

8. #include <windows.h>

9. // en global définition du type de la structure fourmi

10. typedef struct {

11. float x, y; // la position de la fourmi

12. float dx, dy; // le déplacement

13. int color; // la couleur

14. int lettre; // l'apparence

15. }fourmi;

16. // Limites de la zone de jeu

17. const int TX = 40;

18. const int TY = 20;

19. // les actions

20. fourmi init (void);

21. void affiche (fourmi f, int color);

22. fourmi avance (fourmi f);

23. int top (int*start, int dur);

24. void gotoxy (int x, int y);

25. void textcolor (int color);

26. /*************************************************************

27. ACTION

28. *************************************************************/

29. int main()

30. {

31. int start = 0;

32. fourmi f;

33. srand(time(NULL));

34. f = init();

35. while (!kbhit()){

36. if (top(&start, 75)){

37. affiche(f, 0); // effacer

38. f = avance(f); // changer la position

39. affiche(f, f.color); // réafficher

40. }

41. }

42. return 0;

43. }

44. /*************************************************************

45. INITIALISATION

46. *************************************************************/

47. fourmi init()

48. {

49. fourmi f;

50. f.x = rand() % TX;

51. f.y = rand() % TY;

52. f.dx = ((float)rand() / RAND_MAX) * 4 2;

53. f.dy = ((float)rand() / RAND_MAX) * 4 2;

54. f.lettre = 'A' + rand() % 26;

55. f.color = 1 + rand() % 255;

56. return f;

57. }

58. /*************************************************************

59. AFFICHAGE

60. *************************************************************/

61. void affiche(fourmi f, int color)

62. {

63. gotoxy(f.x, f.y);

64. textcolor(color);

65. putchar(f.lettre);

66. }

67. /*************************************************************

68. MOUVEMENT

69. *************************************************************/

70. fourmi avance(fourmi f)

71. {

72. f.x += f.dx;

73. if (f.x < 0){

74. f.x = 0;

75. f.dx = ((float)rand() / RAND_MAX) * 2;

76. }

77. if (f.x >= TX){

78. f.x = TX 1;

79. f.dx = ((float)rand() / RAND_MAX)* -2;

80. }

81. f.y += f.dy;

82. if (f.y < 0){

83. f.y = 0;

84. f.dy = ((float)rand() / RAND_MAX) * 2;

85. }

86. if (f.y >= TY){

87. f.y = TY 1;

88. f.dy = ((float)rand() / RAND_MAX)* -2;

89. }

90. return f;

91. }

92. /*************************************************************

93. OUTILS

94. *************************************************************/

95. int top(int*start, int dur)

96. {

97. int res = 0;

98. if (clock()>*start + dur){

99. *start = clock();

100. res = 1;

101. }

102. return res;

103. }

104. void gotoxy(int x, int y)

105. {

106. COORD c;

107. c.X = x;

108. c.Y = y;

109. SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c);

110. }

111. void textcolor(int color)

112. {

113. SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);

114. }

115. /*************************************************************

116. *************************************************************/

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 ça 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. C'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, chaque voiture 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, de 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.

Code complet

1. #include <stdio.h>

2. #include <stdlib.h>

3. #include <time.h>

4. #include <windows.h>

5. #include <conio.h>

6. // taille de la zone de jeu

7. #define TX 80

8. #define TY 24

9. // Les quatre directions

10. enum{ NORD, EST, SUD, OUEST };

11. /* Pour un circuit :

12. - 6 traits max

13. - un trait = une direction et un nombre de pas

14. - un nombre de traits

15. - un nombre de tours

16. */

17. // pour avoir un circuit :

18. typedef struct {

19. int d1,p1,d2,p2,d3,p3,d4,p4,d5,p5,d6,p6; //6 traits

20. int nbtrait; // nombre de traits dans le circuit

21. int nbtour; // nombre de tours à faire pour une course

22. }circuit;

23. /*Pour une voiture :

24. - une position x,y

25. - une direction

26. - un nombre de pas

27. - un identificateur du trait courant

28. - un compteur de tours

29. - une vitesse

30. - le temps de départ (associé à vitesse)

31. En option, pour différencier toutes les voitures

32. avec couleur et apparence :

33. - une lettre pour l'apparence

34. - une couleur d'effacement

35. - une couleur d'affichage

36. */

37. typedef struct {

38. int x, y, dir, pas, idtrait, tour, vit, tmps;

39. int lettre; // lettre correspondante

40. int color1, color2; // deux couleurs

41. }voiture;

42. // les déclarations des fonctions

43. circuit     create_circuit1    (void);

44. voiture     create_voiture     (int startx, int starty,int c1, int c2,

45. int l);

46. voiture     bouge         (voiture v,circuit c);

47. int     run                 (voiture v1,voiture v2,voiture v3,

48. voiture v4,circuit c);

49. voiture     select_trait     (voiture v,circuit c);

50. voiture     avancer         (voiture v);

51. void         afficher         (voiture v,int color);

52. int         top             (voiture v);

53. void         gotoxy         (int x, int y);

54. void         textcolor         (int color);

55. /********************************************************

56. ACTION

57. ********************************************************/

58. int main()

59. {

60. circuit c;

61. voiture v1, v2, v3, v4;

62. srand(time(NULL));

63. c = create_circuit1();

64. v1 = create_voiture(2, 2, 0, 10, '1');

65. v2 = create_voiture(3, 3, 1, 11, '2');

66. v3 = create_voiture(4, 4, 2, 12, '3');

67. v4 = create_voiture(5, 5, 3, 13, '4');

68. while (run(v1, v2, v3, v4, c)){

69. /* est généralisé et remplacé avec la fonction bouge()

70. if(top(v1)){

71. v1.tmps=clock();

72. if(v1.pas==0)

73. v1=select_trait(v1,c);

74. v1=avancer(v1);

75. }

76. if(top(v2)){

77. v2.tmps=clock();

78. if(v2.pas==0)

79. v2=select_trait(v2,c);

80. }

81. v3.tmps=clock();

82. if(v3.pas==0)

83. v3=select_trait(v3,c);

84. v3=avancer(v3);

85. }

86. if(top(v4)){

87. v4.tmps=clock();

88. if(v4.pas==0)

89. v4=select_trait(v4,c);

90. v4=avancer(v4);

91. }*/

92. // un appel de la fonction bouge() par voiture

93. v1 = bouge(v1, c);

94. v2 = bouge(v2, c);

95. v3 = bouge(v3, c);

96. v4 = bouge(v4, c);

97. }

98. gotoxy(0, TY);

99. system("PAUSE");

100. return 0;

101. }

102. /********************************************************

103. CRÉATION DU CIRCUIT

104. ********************************************************/

105. /*

106. Un seul circuit. C'est un carré de 10 sur 10, deux tours

107. prévus. Initialisation des variables circuit en conséquence.

108. Initialisation de la voiture

109. */

110. circuit create_circuit1()

111. {

112. circuit c;

113. // définition du circuit

114. c.d1 = EST; // trait 1

115. c.p1 = 10;

116. c.d2 = SUD;

117. c.p2 = 10;

118. c.d3 = OUEST;

119. c.p3 = 10;

120. c.d4 = NORD;

121. c.p4 = 10;

122. c.nbtrait = 4;

123. c.nbtour = 2;

124. return c;

125. }

126. /********************************************************

127. CRÉATION VOITURE

128. ********************************************************/

129. voiture create_voiture(int startx, int starty, int c1,

130. int c2, int l)

131. {

132. voiture v;

133. v.x = startx;

134. v.y = starty;

135. v.pas = 0;

136. v.idtrait = 0;

137. v.tour = 0;

138. v.tmps = 0;

139. v.vit = 0;

140. v.color1 = c1;

141. v.color2 = c2;

142. v.lettre = l;

143. return v;

144. }

145. /********************************************************

146. BOUGER

147. ********************************************************/

148. /*

149. contrôle de la fin de la course. La course continue tant

150. qu'aucune voiture n'a fini et s'arrête dès qu'une voiture

151. a fini

152. */

153. int run(voiture v1,voiture v2,voiture v3,voiture v4,

154. circuit c)

155. {

156. return (v1.tour<c.nbtour && v2.tour<c.nbtour &&

157. v3.tour<c.nbtour && v4.tour<c.nbtour);

158. }

159. // topage voitures

160. int top(voiture v)

161. {

162. return (clock() > v.tmps + v.vit);

163. }

164. /*

165. */

166. voiture bouge(voiture v, circuit c)

167. {

168. if (top(v)){

169. v.tmps = clock();

170. if (v.pas == 0)

171. v = select_trait(v, c);

172. v = avancer(v);

173. }

174. return v;

175. }

176. /*

177. sélectionner un trait à parcourir

178. */

179. {

180. // contrôler si un tour a été effectué

181. if (v.idtrait == c.nbtrait){

182. v.tour++;

183. v.idtrait = 0;

184. }

185. // changer la vitesse pour chaque trait

186. v.vit = 150 + rand() % 150;

187. // sélection du trait

188. switch (v.idtrait){

189. case 0:

190. v.dir = c.d1;

191. v.pas = c.p1;

192. break;

193. case 1:

194. v.dir = c.d2;

195. v.pas = c.p2;

196. break;

197. case 2:

198. v.dir = c.d3;

199. v.pas = c.p3;

200. break;

201. case 3:

202. v.dir = c.d4;

203. v.pas = c.p4;

204. break;

205. case 4:

206. v.dir = c.d5;

207. v.pas = c.p5;

208. break;

209. case 5:

210. v.dir = c.d6;

211. v.pas = c.p6;

212. break;

213. }

214. // préparer pour le tour suivant

215. v.idtrait++;

216. return v;

217. }

218. /*

219. parcourir le trait courant

220. */

221. voiture avancer(voiture v)

222. {

223. if (v.pas>0){

224. v.pas--; // un pas de moins

225. // effacer

226. afficher(v, v.color1);

227. // avancer selon direction

228. switch (v.dir){

229. case NORD: v.y--; break;

230. case EST: v.x++; break;

231. case SUD: v.y++; break;

232. case OUEST: v.x--; break;

233. }

234. // réafficher

235. afficher(v, v.color2);

236. }

237. return v;

238. }

239. /*

240. afficher une voiture de la couleur color

241. */

242. void afficher(voiture v, int color)

243. {

244. gotoxy(v.x, v.y);

245. textcolor(color);

246. putchar(v.lettre);

247. }

248. /********************************************************

249. OUTILS

250. ********************************************************/

251. // déplacement curseur

252. void gotoxy(int x, int y)

253. {

254. COORD c;

255. c.X = x;

256. c.Y = y;

257. SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c);

258. }

259. // gestion couleur

260. void textcolor(int color)

261. {

262. SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);

263. }

264. /********************************************************

265. ********************************************************/

L'algorithme est toujours le même que celui du programme voiture 1 vu précédemment. Simplement l'utilisation de structure 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 transformer en une fonction qui prend en paramètres 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 tel que 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.

III - Conclusion

Ce tutoriel est extrait du livre Du C au C++ - De la programmation procédurale à l'objet de Frédéric Drouillon.

Commandez Du C au C++ - De la programmation procédurale à l'objet sur Amazon

IV - Remerciements

Je tiens sincèrement à remercier Vincent VIALE pour la mise au gabarit et Claude LELOUP pour la relecture orthographique.