Structures en boucle et entrée/sortie de fichiers
Une tâche inachevé
Il existe une une fonction fopen() renvoyant un pointeur vers le type FILE. Le type FILE est une structure définie dans le fichier stdio.h, contenant les éléments de données requis pour gérer les entrées/sorties de fichier. Cette fonction fopen() est une fonction de bibliothèque. Maintenant, on pourrait penser que, si celui ayant composé le fichier stdio.h s'est donné la peine de configurer la structure FILE, il aurait au moins fait l'effort supplémentaire de rendre fopen() "prêt à l'emploi", en déclarant comme renvoyant un pointeur vers FILE. Pour une raison étrange, la version de stdio.h fournie avec l'ensemble du développeur Atari n'inclut pas la déclaration, nous devons donc le faire nous-mêmes. Si vous avez le compilateur Megamax C, cependant, vous pouvez supprimer cette déclaration du programme ; ils nous ont accordé la courtoisie de terminer le travail.
Un aperçu de GEM
Juste au-delà de la déclaration de fichier pour fopen(), il existe des déclarations pour un certain nombre de tableaux globaux : work_in[], work_out[], contrl[], intin[], ptsin[], intout[] et ptsout[]. Si vous avez examiné une partie du code source C de divers programmes GEM dans le domaine public ou ceux publiés dans des magazines, vous avez remarqué que ces tableaux sont presque toujours présents. En fait, vous les avez probablement également vus utilisés dans les programmes ST BASIC.
Tous les tableaux ci-dessus ont une chose en commun : ils fournissent à GEM un endroit pour entreposer ou récupérer des informations sur le programme. Ces informations peuvent ensuite être facilement manipulées par le programmeur. Le GEM est composé de nombreuses bibliothèques de fonctions, dont chacune est responsable de la gestion d'une certaine partie des activités du système. Ces bibliothèques sont regroupées en deux unités principales, appelées AES (Application Environment Services) et VDI (Virtual Device Interface). Les bibliothèques composant l'AES gèrent des éléments tels que les fenêtres, les boîtes de dialogue, les barres de menus et le traitement des événements. (Un événement est une action de l'utilisateur, comme taper une lettre ou déplacer la souris.) Le VDI contient les sous-programmes pour contrôler les graphiques du Atari ST, ainsi que certaines fonctions de contrôle de la souris et du curseur. Étant donné que GEM est capable de gérer plusieurs programmes à la fois (comme l'utilisation d'un accessoire de bureau avec un traitement de texte), il doit y avoir un moyen de séparer un travail d'un autre. Le GEM s'attaque à cela en attribuant à chaque programme et à son périphérique associé (dans notre cas, l'écran) un poste de travail pouvant ensuite être référencé par un identificateur appelé "handle" ou descripteur de fichier. La première chose que toute application GEM doit faire est d'ouvrir un poste de travail.
Lorsque nous ouvrons un poste de travail, nous devons dire à GEM comment nous voulons que les attributs du système soient initialisés. De quelle couleur doit être le texte ? Et doit-il être ombragé ? Ou peut-être audacieux ? Quel remplissage de style voulons-nous ? Solide ? À carreaux ? Tous ces attributs doivent être placés dans le tableau work_in[] avant d'ouvrir le poste de travail, car c'est là que GEM s'attend à les trouver. Nous n'allons pas nous soucier, pour le moment, de savoir quels éléments du tableau contiennent des informations pour quel attribut. Nous allons juste croire que work_in[10] doit être initialisé à 2, et le reste sera parfaitement heureux initialisé à 1. Après avoir configuré le tableau, nous disons à GEM d'ouvrir le poste de travail avec l'appel v_opnvwk[] :
- v_opnvwk(work_in,&handle,work_out);
Le paramètre work_in est l'adresse de notre tableau work_in[], contenant les informations d'attribut que nous souhaitons transmettre à GEM. Et &handle est l'adresse où GEM doit entreposer le handle, la valeur entière nous permettant de nous référer au poste de travail de ce programme. Dans notre exemple de programme, il s'agit de l'adresse du descripteur de variable, étant défini après les tableaux work_in[] et work_out[] en haut de la liste. Le paramètre work_out est bien sûr l'adresse de notre tableau work_out[]. Lorsque nous ouvrons le poste de travail, GEM charge le tableau work_out[] avec toutes les informations dont un programmeur a besoin sur le poste de travail. Par exemple, work_out[12] contiendra le nombre de styles de hachures disponibles, tandis que work_out[13] contiendra le nombre de couleurs pouvant être affichées en même temps. Nous n'avons pas à nous préoccuper de ces informations maintenant, mais il est important que vous compreniez pourquoi nous avons besoin de ces deux tableaux. Aussi, à la fin de main(), notez l'appel de fonction :
- v_clsvwk(handle);
Ceci ferme le poste de travail à d'autres sorties. Le paramètre handle est le handle du périphérique vous étant transmis par l'appel de la fonction v_opnvwk().
Un aperçu de VDI
Les cinq tableaux restants (contrl[], intin[], ptsin[], ptsout[] et intout[]) sont directement associés au VDI. Les trois premiers sont utilisés pour transmettre des informations aux routines VDI, tandis que les deux derniers permettent au VDI de renvoyer des informations au programme. Ces tableaux sont utilisés par GEM pour ses propres besoins ; vous n'avez rien d'autre à faire que de les déclarer au début de votre programme.
Se déplacer le long
Si vous avez pris le temps d'examiner la liste des programmes pour Megamax C d'Atari ST, vous vous êtes probablement demandé ce qui se passait avec cet appel :
- Cconws("message");
Cette fonction ne fait rien de plus qu'écrire une chaîne de caractères à l'écran. Pourquoi n'ai-je pas simplement utilisé printf() et évité toute cette confusion ? Cela a à Avec Megamax C, la fonction printf() n'affichera que lorsqu'il rencontrera un \n. Cela complique la gestion des prompts, si vous souhaitez que l'entrée de l'utilisateur soit sur la même ligne que le prompt. Le recours à Cconws() résoudra ce problème.
Le curseur VDI
Afin de profiter du curseur, nous devons d'abord faire cet appel :
- v_enter_cur(handle);
Cet appel de fonction nous fait sortir du mode graphique et en mode texte. Dans cette fonction, comme pour tout ce qui suit, le handle est l'identificateur du poste de travail nous ayant été renvoyé par l'appel v_opnvwk(). Nous pouvons positionner le curseur n'importe où sur l'écran en passant les coordonnées X,Y à la fonction vs_curaddress() :
- vs_curaddress(handle,y,x);
Notez que les coordonnées sont transmises dans l'ordre inverse de ce à quoi vous vous attendiez ; c'est-à-dire Y suivi de X. N'oubliez pas non plus que nous sommes maintenant en mode texte. L'emplacement du curseur est basé sur les positions des caractères, et non sur les coordonnées en pixels du raster. En mode texte à résolution moyenne, la taille de l'écran est interprétée comme 80x24, alors qu'en mode graphique, c'est 640x200.
Sortie d'imprimante
Pour l'imprimante, la première chose que nous devons faire est de vérifier si l'imprimante est allumée à l'aide de la fonction suivante :
- status = Cprnout(0);
La ligne ci-dessus accomplit sa tâche en envoyant un caractère nul à l'imprimante. Si l'imprimante expire, un 0 sera retourné par la fonction. Une autre façon de vérifier l'imprimante consiste à utiliser la fonction Cprnos() renvoyant une valeur différente de zéro si l'imprimante est prête à recevoir :
- status = Cprnos();
Une fois que nous savons que l'imprimante est prête à répondre, nous pouvons commencer à envoyer du texte. Il existe plusieurs façons de procéder. La méthode la plus commune est d'utiliser une fonction appelée Cprnout(), envoyant les caractères à l'imprimante un par un. Le format de cet appel de fonction est la suivante :
- status = Cprnout(ch);
Ici, la valeur renvoyée dans status sera -1 si le caractère a été envoyé correctement, ou 0 si, pour une raison quelconque, l'imprimante n'a pas répondu. La variable ch est le caractère que nous voulons imprimer. Comment saurions-nous si l'imprimante est à court de papier ou s'est déconnectée de manière inattendue ? Notez également que nous pouvons envoyer un caractère littéral à l'imprimante, ainsi que le caractère entreposé dans une variable. Dans notre programme, par exemple, nous imprimons un espace comme celui-ci :
- Cprnout(' ');
Comme nous devons imprimer des chaînes de caractères complètes plutôt qu'un seul caractère, nous devez configuré une boucle for pour chacune des chaînes de caractères, en utilisant la variable de boucle comme index dans le tableau de caractères. De cette façon, nous parcourons la chaîne de caractères, en l'envoyant à l'imprimante un caractère à la fois. Enfin, notez que nous terminons chaque chaîne de caractères en imprimant un \n et un \r. Sans saut de ligne ni retour chariot, les chaînes de caractères seront imprimées côte à côte plutôt que l'une au-dessus de l'autre.
Impaire et fins
Nous avons maintenant une dernière tâche à accomplir avant de pouvoir passer à GEM : aborder quelques détails du langage C que nous n'avons pas encore abordés. Le C sous Atari ST propose des opérateurs ternaire :
- z = (x<4) ? x : y;
Cette instruction n'est rien de plus qu'une version abrégée de :
Le ?: est un opérateur conditionnel nécessitant trois opérandes. Le premier opérande (entre parenthèses) est l'expression à tester. Si l'expression est vraie, l'instruction donne l'évaluation du second opérande (entre le ? et :). Si la première expression est fausse, l'instruction donne l'évaluation du troisième opérande (entre le : et ;). Voici un autre exemple allant obtenir la valeur la plus élevée de deux variables :
- highest = (x > y) ? x : y;
Le C de l'Atari ST a également une construction similaire à ON...GOTO de BASIC :
L'instruction switch fonctionne en évaluant d'abord l'expression entre parenthèses, puis en vérifiant les étiquettes suivantes pour voir s'il y en a une correspondant à la valeur de l'expression. Si c'est le cas, l'exécution du programme saute à la ligne correspondante et continue jusqu'à ce qu'elle rencontre l'instruction break.
Mais que se passe-t-il s'il n'y a pas de correspondance ? Et si, dans l'exemple ci-dessus, expression n'est pas 1, 2 ou 3 ? C'est là qu'intervient l'étiquette default. L'exécution du programme sautera à cette ligne si aucune des autres étiquettes ne correspond. Sinon, s'il n'y a pas de valeur par défaut, l'exécution du programme sautera à la ligne suivante après la fin de l'instruction switch (après l'accolade fermante). Que se passe-t-il si nous omettons les instructions break ? Une fois que l'expression suivante switch est évaluée, le programme saute à l'étiquette correspondante et continue jusqu'à ce qu'il rencontre une pause. Le programme ne se soucie pas s'il n'y a pas de pause avant l'étiquette suivante. Il continuera, au-delà des étiquettes suivantes (en les ignorant), et exécutera chaque instruction qu'il trouve - jusqu'à ce qu'il trouve une pause ou atteigne l'accolade fermante. Dans l'exemple ci-dessus, si nous avons omis toutes les instructions break et expression évalué à 2, la sortie ressemblerait à ceci :
expression = 2expression = 3expression < 1 ou > 3De même, si expression était évalué à 3, nous verrions :
expression = 3expression < 1 ou > 3Une nouvelle boucle
Il existe des constructions de boucles while et for. Les deux sont des boucles de condition d'entrée ; c'est-à-dire que la condition de boucle est vérifiée avant chaque itération de la boucle. Il existe une autre construction de boucle, la boucle do while. La construction do while est une boucle de condition de sortie. La boucle conditionnelle est évaluée après chaque itération :
Ce qui précède l'affichage des valeurs de x de 1 à 4. Comparez cela avec :
affichant les valeurs de x de 1 à 3.
break, continue et goto
Nous avons parlé de l'instruction break plus tôt, en conjonction avec switch, mais elle peut également être utilisée pour sortir des boucles for, while et do while. Lorsqu'il est utilisé dans une construction de boucle imbriquée, il ne termine que la boucle dans laquelle il est utilisé. Les boucles les plus externes continueront normalement.
Une autre méthode pour affecter l'exécution de la boucle consiste à continuer. Lorsque continue est rencontré dans une boucle, la boucle ne se termine pas, mais commence à la place l'itération suivante :
Enfin, en C du Atari ST il existe également une instruction goto. Le mot clef goto est suivi de l'étiquette identifiant où l'exécution du programme doit continuer :
- goto print_nom;
- print_nom: printf("Nom: %s", nom);
Il y a peu ou pas d'utilité pour l'instruction goto dans un langage structuré comme C. Il en va de même, mais pas aussi fortement, pour break et continue, sauf lorsque la première est utilisée dans une instruction switch. Il existe presque toujours une manière plus structurée et élégante de contourner l'utilisation de ces instructions. Si vous êtes un programmeur BASIC, il vous faudra un certain temps pour vous habituer à structurer vos programmes de manière à éviter l'utilisation d'un goto.