Bien que le grand public soit friand de nouvelles technologies (tablettes, smartphones, PC, ..), la programmation a la réputation d'être difficile et n'est pas appréciée comme hobby.
Pourtant ce n'était pas le cas dans les années 1980, l'âge d'or de la micro-informatique, où beaucoup de gens bricolaient des petits programmes en "Basic".
Ce qui a changé depuis, c'est que les langages et les environnements de programmation sont devenus beaucoup plus complexes et inaccessibles au grand public. En effet, une nouvelle génération d'informaticiens a inventé des nouveaux concepts tels que : "la programmation orientée-objet", "les classes", "le polymorphisme", "les types dérivés", "les exceptions", "les expressions lambda", etc.
Ces concepts apportent-ils vraiment quelque chose ? Et bien au risque de vous surprendre, je vous dirais qu'ils n'apportent pas grand-chose, et qu'on s'en passe très bien. En fait, on n'en a pas besoin du tout, même pour des très grands programmes professionnels.
Dans ce tutorial vous allez apprendre à programmer de façon simple et amusante. Mais ne vous méprenez pas : le Safe-C n'est pas un jouet. Bien que nos premiers programmes soient simples, le langage est extrêmement puissant et permet d'écrire de manière très efficace tous les genres de programmes.
Avant de commencer, il vous faut apprendre à domestiquer les boites de commandes. Vous les avez peut-être déjà vues, ce sont ces boites noires d'aspect rébarbatif que les "pros" utilisent pour envoyer des commandes à l'ordinateur.
Pour ouvrir une boite de commande, cliquer sur "Démarrer" puis sur "Exécuter".
Tapez ensuite la commande cmd et cliquez sur OK.
Vous devriez voir une boite de commande s'ouvrir comme celle-ci:
Comme vous passerez beaucoup de temps avec ces boites, cela vaut la peine de passer du temps à la configurer pour la rendre pratique. Vous pouvez changer de font et de taille (par exemple l'allonger verticalement pour avoir plus de place) en cliquant sur le coin supérieur gauche, puis sur Propriétés,
ce qui va faire apparaitre le menu suivant :
Observez sur l'image la hauteur de la mémoire tampon qui est mise à 300, ce qui va vous faire apparaitre une barre de défilement à droite et vous permettre d'avoir plus de place.
Voici quelques commandes que vous pouvez essayer : tapez DIR puis appuyez sur la touche Entrée(<-)
Une liste des dossiers de l'utilisateur Samuro va s'afficher.
La commande DIR est l'ancêtre de l'explorateur Windows, elle permet d'obtenir une liste de tous les fichiers qui se trouvent dans un dossier. Ici le dossier par défaut est C:\Users\Samuro.
Pour changer de dossier, on a la commande CD (change directory), par exemple tapez CD \WINDOWS (n'oubliez pas le \ !) pour vous positionner dans le dossier WINDOWS, ensuite tapez DIR. Vous allez voir les nombreux fichiers de Windows.
Pour défiler page par page, essayez la commande DIR /P (attention le / est dans l'autre sens)
Pour effacer l'écran, essayez la commande CLS.
Pour vous positionner à la racine du disque C (donc dans aucun dossier), tapez CD \
Maintenant on va créer un nouveau dossier, par exemple on va l'appelez SAFE-C. Tapez simplement MD SAFE-C (MD signifie Make Directory).
On va se positionner dans le dossier SAFE-C en tapant CD \SAFE-C
Et finalement on va afficher la liste de tout ce qui se trouve dans ce dossier avec DIR :
Vous voyez que le dossier est vide.
Si vous démarrez l'explorateur windows, vous pouvez voir le dossier que vous avez créé :
Nous mentionnerons encore la commande HELP qui vous donne des infos sur toutes les commandes existantes. Par exemple HELP DIR vous donne des infos sur toutes les options de la commande DIR.
Pour compiler vos programmes, vous avez besoin du compilateur Safe-C que vous pouvez télécharger ici.
Créez un dossier sur votre disque C (par exemple C:\Safe-C), ensuite dézippez et copiez tous les fichiers dedans.
Vous devriez voir les fichiers suivants :
Ouvrez une boite de commande (voir chapitre précédent) et positionnez-vous
dans le répertoire du compilateur.
tapez la commande :
notepad hello.c
Notepad va vous poser la question : "voulez-vous créer un nouveau fichier ?" Répondez OUI.
Tapez ensuite votre premier programme :
// hello.c from std use console; void main() { printf ("Hello, World !\n"); }
Sauvez et fermez Notepad.
Vérifiez que votre fichier hello.c se trouve bien dans le dossier :
Tapez ensuite dans la boite de commande :
mk hello
ce qui va compiler votre texte source hello.c en programme hello.exe
Il ne vous reste plus qu'à lancer votre programme en tapant :
hello
et vous devriez voir s'afficher "Hello, World!" à l'écran.
Bravo, vous venez de créer votre premier programme !
Si vous avez une erreur Windows au lancement du programme, il est probable que Windows Defender a effacé votre programme suite à une détection erronée de virus. Il faut alors ajouter le répertoire du compilateur dans les dossiers exclus de Windows Defender.
Plus tard si vous distribuez votre programme sur internet, et que vous voulez éviter des alertes chez vos utilisateurs,
vous devez avertir l'équipe Windows Defender un jour à l'avance pour qu'ils mettent votre programme sur liste blanche.
Cela se fait automatiquement en envoyant votre programme sur le site https://www.microsoft.com/en-us/wdsi/filesubmission
// hello.c from std use console; void main() { printf ("Hello, World !\n"); }
Regardons maintenant ce premier programme en détails.
// hello.c
La première ligne commence par le signe // qui indique un commentaire. Elle est donc ignorée par le compilateur.
from std use console;
La deuxième ligne dit au compilateur d'ouvrir la librairie standard std (std.lib) et d'en charger le composant 'console'. Si vous allez voir le contenu du composant 'console' sur la page principale de ce site, vous y trouverez la déclaration de la fonction printf() que nous pourrons donc utiliser ci-après.
void main()
La troisième ligne indique au compilateur que c'est là que commence le programme. Tout programme Safe-C consiste en un ou plusieurs fonctions. main() est une telle fonction, et en fait tous les programmes ont une fonction main() parce que c'est là que démarre le programme. En général la fonction main() va appeler d'autres fonctions.
{ printf ("Hello, World !\n"); }
Les accolades { } entourent les instructions que la fonction va exécuter. Ici on n'a qu'une seule instruction, printf() qui sert à afficher un texte sur la boite de commande. Chaque instruction se termine par un point-virgule (;)
Pour appeler une fonction, on donne simplement son nom (ici printf) et on donne d'éventuels arguments entre parenthèses (ici "Hello, World !\n"). S'il n'y a pas d'arguments, on ajoute simplement une paire de parenthèses vides ().
Notons au passage que le signe \n ne s'affiche pas, c'est un symbole qui indique un passage à la ligne suivante. Donc si vous placez un deuxième printf juste après, il affichera son texte sur la ligne en dessous et pas à droite.
En fait on aurait très bien pu écrire le printf en trois fois avec le même effet :
printf ("Hello, "); printf ("World !"); printf ("\n");
0 -17 20 -6 40 4 60 15 80 26 100 37 120 48 140 60 160 71 180 82 200 93 220 104 240 115 260 126 280 137 300 148Ce programme introduit plusieurs nouvelles idées : les variables et les boucles.
// table Fahrenheit-Celsius from std use console; void main() { int fahr, celsius; int lower, upper, step; lower = 0; // lower limit of temperature scale upper = 300; // upper limit step = 20; // step size fahr = lower; while (fahr <= upper) { celsius = 5 * (fahr-32) / 9; printf ("%5d %5d\n", fahr, celsius); fahr = fahr + step; } }
En Safe-C, il faut déclarer la liste des variables avant de les utiliser :
int fahr, celsius; int lower, upper, step;
"int" signifie que ce sont des variables entières qui contiennent un nombre sans virgule entre -2147483648 et +2147483647.
Le programme commence par 3 assignations qui donnent des valeurs initiales aux variables :
lower = 0; // lower limit of temperature scale upper = 300; // upper limit step = 20; // step size
L'instruction "while" sert à boucler : tant que la condition est vraie, on répète les instructions entre accolades. A noter que s'il n'y avait qu'une seule instruction, alors les accolades ne seraient pas nécessaires.
fahr = lower; while (fahr <= upper) { celsius = 5 * (fahr-32) / 9; printf ("%5d %5d\n", fahr, celsius); fahr = fahr + step; }
Le gros du travail est fait dans l'instruction de calcul qui va évaluer "5 * (fahr-32) / 9" et placer le résultat dans la variable de gauche. Le signe * indique une multiplication, le signe / indique une division. Il est à noter que puisque ce sont des variables 'int', donc sans virgule, les résultats sont tronqués, la partie fractionnaire est supprimée.
celsius = 5 * (fahr-32) / 9;
La fonction printf permet d'imprimer une ligne formatée. Ici le format %5d lui indique d'imprimer une variable décimale (donc int) en 5 positions.
printf ("%5d %5d\n", fahr, celsius);
La dernière instruction va augmenter 'fahr' de la valeur 'step'. Plus précisémment, elle va calculer la valeur de droite 'fahr + step' et placer le résultat dans la variable de gauche "fahr". Cette dernière reçoit donc une nouvelle valeur avant que la boucle ne se répète.
fahr = fahr + step;
Vous avez remarqué que les températures sont approximatives puisqu'on travaille avec des 'int'. Voici le même programme avec des variables "float" :
// table Fahrenheit-Celsius from std use console; void main() { float fahr, celsius; float lower, upper, step; lower = 0.0; // lower limit of temperature scale upper = 300.0; // upper limit step = 20.0; // step size fahr = lower; while (fahr <= upper) { celsius = 5.0 * (fahr-32.0) / 9.0; printf ("%8.3f %8.3f\n", fahr, celsius); fahr = fahr + step; } }
ce qui donne le résultat suivant :
0.000 -17.777 20.000 -6.666 40.000 4.444 60.000 15.555 80.000 26.666 100.000 37.777 120.000 48.888 140.000 60.000 160.000 71.111 180.000 82.222 200.000 93.333 220.000 104.444 240.000 115.555 260.000 126.666 280.000 137.777 300.000 148.888
Vous remarquerez un formatage spécial pour les floats :
printf ("%8.3f %8.3f\n", fahr, celsius);
qui signifie : imprimer avec 8 caractères au total, dont 3 chiffres après la virgule.
La commande printf est très riche et permet les formatages suivants :
"% [Drapeau] [Largeur] [. Précision] Type" Type sortie ---- ------ % '%%' s'écrira '%' d entier signé nombre décimal (-61) u entier non-signé ou enum nombre non signé (12) x entier ou enum nombre hexadécimal (7fa) e float, double format scientifique (3.9265e+2) f float, double virgule flottante (392.65) c char ou string tous les char, ou max 'précision' char. C wchar ou wstring tous les wchar, ou max 'précision' wchar. s string chaîne s'arrête à nul, ou "précision" char S wstring chaîne s'arrête à Lnul, ou "précision" wchar Drapeaux -------- - Pour tous : justifier à gauche endéans le champ (par défaut : justifier à droite). + Pour d, e, f : préfixe '+' pour les nombres positifs ou nuls. 0 pour d, e, f, x, u : préfixe des chiffres '0' au lieu d'espaces; (non valable avec le flag '-') Largeur ----- (Nombre) nombre minimum de caractères à imprimer (complété avec des espaces ou des zéros si nécessaire). La valeur n'est pas tronquée, même si le résultat est trop long. * La largeur n'est pas spécifiée dans la chaîne de format, mais dans un paramètre supplémentaire de valeur int précédant le paramètre à formater. Précision --------- . Nombre Un point sans nombre indique une précision de zéro. Pour f: c'est le nombre de chiffres à afficher après le point décimal. La précision par défaut est de 6. Aucun point décimal est n'imprimé pour une précision de zéro. Pour s/S: c'est le nombre maximum de caractères à imprimer. Par défaut on imprime tous les caractères. .* La précision n'est pas spécifiée dans la chaîne de format, mais comme un paramètre supplémentaire de valeur int précédant le paramètre à formater.
Voici maintenant une façon abbréviée d'écrire le même programme :
// table Fahrenheit-Celsius from std use console; void main() { int fahr; for (fahr=0; fahr<=300; fahr+=20) printf ("%5d %5d\n", fahr, 5 * (fahr-32) / 9); }
L'instruction "for" est composée de 3 parties : la première "fahr=0" n'est exécutée qu'une fois.
La condition "fahr<=300" est testée avant chaque tour de boucle pour voir si on continue à boucler,
et la 3ème partie "fahr+=20" est exécutée après les instructions à répéter, à la fin de chaque boucle.
La version suivante du programme utilise des "constantes" qui sont des variables qui ne changent jamais de valeur. On les écrit généralement en majuscules. Placer une constante en haut du programme est intéressant quand on risque de vouloir changer souvent cette valeur ou bien qu'elle est utilisée à plusieurs endroits dans le programme.
// table Fahrenheit-Celsius from std use console; void main() { const int LOWER = 0; // lower limit of temperature scale const int UPPER = 300; // upper limit const int STEP = 20; // step size int fahr = LOWER; int celsius; while (fahr <= UPPER) { celsius = 5 * (fahr-32) / 9; printf ("%5d %5d\n", fahr, celsius); fahr = fahr + STEP; } }
// add.c from std use console; void main() { int a, b, c; printf ("enter first number : "); scanf (" %d", out a); printf ("enter second number : "); scanf (" %d", out b); c = a + b; printf ("the sum is %d\n", c); }
enter first number : 123 enter second number : 79 the sum is 202
La fonction scanf vous permet d'introduire une valeur au clavier. Elle permet les formatages suivants :
Un blanc correspond à ignorer zéro ou plusieurs caractères d'espace blanc. Un caractère non-blanc, à l'exception du caractère pourcentage (%), doit correspondre à la saisie, ou la fonction échoue. Format : "%[*][Largeur] Type" * Les données sont lues mais ne sont pas stockées (il n'y a aucun paramètre correspondant). Largeur Nombre maximum de caractères à lire. Type Entrée ---- ------- '%' se lira '%%' d tout entier signé nombre décimal optionnellement précédé de + ou - (-61) u tout entier non-signé nombre non signé (12) ou enum x idem à d ou u nombre hexadécimal (7fa) f float ou double nombre à virgule flottante (0,5) (12.4e +3) e idem que f c char ou string remplir arg, ou lire max 'width' chars. C wchar ou wstring remplir arg, ou lire max 'width' wchars. s char ou string idem que c mais s'arrête au premier blanc. S wchar ou wstring idem que C, mais s'arrête au premier blanc.
Le programme suivant vous permet d'introduire 3 valeurs entières et de calculer leur moyenne :
// averager.c from std use console; void main() { int a, b, c; printf ("please input 3 numbers : "); scanf (" %d %d %d", out a, out b, out c); printf ("the average is %d\n", (a+b+c) / 3); }
Le programme suivant vous permet d'introduire des valeurs et de calculer leur moyenne, mais vous remarquerez qu'on n'utilise pas scanf(). Les valeurs sont passées à la fonction main() lors du démarrage du programme.
// averager2.c from std use console, strings; void main (string[] arg) { int i; float f, sum, count; sum = 0.0; count = 0.0; for (i=1; i<arg'length; i++) { sscanf (arg[i], " %f", out f); sum += f; count += 1.0; } printf ("the average is %f\n", sum/count); }
C:\SAFE-C> averager2 56 129 126 the average is 103.666664
Le programme suivant vous permet d'introduire de créer un fichier sur le disque et d'y écrire des lignes de texte.
// writer.c from std use console, files; void main() { FILE file; int rc; const string FILENAME = "myfile.txt"; rc = fcreate (out file, FILENAME, ANSI); if (rc < 0) { printf ("error: cannot create file %s\n", FILENAME); return; } fprintf (ref file, "Hi World !\n"); fprintf (ref file, "This is a test writing into file %s\n", FILENAME); fprintf (ref file, "have a good day.\n"); fclose (ref file); }
Après exécution, le fichier "myfile.txt" sera créé sur le disque. Si vous l'ouvrez avec notepad, vous verrez qu'il contiendra :
Hi World ! This is a test writing into file myfile.txt have a good day.
Le programme suivant vous permet de lire un fichier texte et d'afficher son contenu à l'écran.
// reader.c from std use console, files; void main() { FILE file; int rc; char line[100]; const string FILENAME = "myfile.txt"; rc = fopen (out file, FILENAME); if (rc < 0) { printf ("error: cannot open file %s\n", FILENAME); return; } while (fgets (ref file, out line) == 0) printf ("%s", line); fclose (ref file); }
Si vous l'exécutez, vous verrez l'affichage suivant :
Hi World ! This is a test writing into file myfile.txt have a good day.
Le programme suivant vous permet de lire un fichier texte et de compter le nombre de lettres "a" qu'il contient.
// counter.c from std use console, files, strings; void main() { FILE file; int rc; char line[100]; int count; const string FILENAME = "myfile.txt"; rc = fopen (out file, FILENAME); if (rc < 0) { printf ("error: cannot open file %s\n", FILENAME); return; } count = 0; while (fgets (ref file, out line) == 0) { int len = strlen(line); int i; for (i=0; i<len; i++) { if (line[i] == 'a') count++; } } fclose (ref file); printf ("number of A's : %d\n", count); }
Si vous l'exécutez, vous verrez l'affichage suivant :
number of A's : 4
Il est possible de séparer les partie lecture de fichier et comptage de lettre en utilisant une fonction.
// counter2.c from std use console, files, strings; int number_of_a (string line) { int len = strlen(line); int count = 0; int i; for (i=0; i<len; i++) { if (line[i] == 'a') count++; } return count; } void main() { FILE file; int rc; char line[100]; int count; const string FILENAME = "myfile.txt"; rc = fopen (out file, FILENAME); if (rc < 0) { printf ("error: cannot open file %s\n", FILENAME); return; } count = 0; while (fgets (ref file, out line) == 0) count += number_of_a (line); fclose (ref file); printf ("number of A's : %d\n", count); }
Il est possible de placer la fonction dans un fichier séparé. Il faut pour cela créer 3 fichiers :
// acounter.h int number_of_a (string line);
// acounter.c from std use strings; public int number_of_a (string line) { int len = strlen(line); int count = 0; int i; for (i=0; i<len; i++) { if (line[i] == 'a') count++; } return count; }
// mycounter.c from std use console, files; use acounter; void main() { FILE file; int rc; char line[100]; int count; const string FILENAME = "myfile.txt"; rc = fopen (out file, FILENAME); if (rc < 0) { printf ("error: cannot open file %s\n", FILENAME); return; } count = 0; while (fgets (ref file, out line) == 0) count += number_of_a (line); fclose (ref file); printf ("number of A's : %d\n", count); }
Il faut compiler uniquement le ficher contenant la fonction main, le compilateur va suivre les clauses "use" et trouver les autres fichiers automatiquement.
Pour afficher la date et l'heure, essayez le programme suivant :
// date1.c from std use calendar, console; void main() { DATE_TIME now; get_datetime (out now); printf ("Nous sommes le %02d/%02d/%04d ", now.day, now.month, now.year); printf ("et il est %02d:%02d:%02d.\n", now.hour, now.min, now.sec); }
ce qui affiche :
Nous sommes le 03/01/2011 et il est 19:24:43.
DATE_TIME est une structure qui est déclarée dans le composant de librairie calendar comme suit :
struct DATE_TIME { int2 year; // 1901 to 9999 int1 month; // 1 to 12 int1 day; // 1 to 31 int1 hour; // 0 to 23 int1 min; // 0 to 59 int1 sec; // 0 to 59 int2 msec; // 0 to 999 }
Vous voyez qu'elle contient 7 champs (année, mois, jour, heure, min, sec et msec).
La fonction get_datetime() définie dans calendar va remplir la variable 'now', ensuite on va utiliser de nouveau printf() pour afficher les différents champs.
Remarquez le formatage de printf : %02d indique une zone numérique de 2 chiffres, %04d indique une zone numérique de 4 chiffres.
On aurait pu écrire aussi %2d et %4d mais alors le programme aurait affiché ceci :
Nous sommes le 3/ 1/2011 et il est 19:25:53.
Vous voyez la différence ? Le 0 indique à printf de remplir la zone avec des zéros au lieu de blancs.
A votre avis, si on écris %d au lieu de %2d, cela donne quoi ?
A vous d'essayer ...
On va compliquer un peu. Comment fait-on pour afficher les mois de l'année en lettres au lieu de chiffres ? Essayez donc ceci :
// date2.c from std use calendar, console; void main() { const string mois[12] = {"Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aout", "Septembre", "Octobre", "Novembre", "Decembre"}; DATE_TIME now; get_datetime (out now); printf ("Nous sommes le %d %s %d\n", now.day, mois[now.month-1], now.year); }
Voici le résultat :
Nous sommes le 3 Janvier 2011
A votre avis, comment fonctionne mois[now.month-1] ?
Le now.month indique le mois de 1 à 12. On en soustrait 1 parce que les indices du tableau mois[] commencent toujours à 0.
Remarquez aussi qu'on a utilisé %s pour indiquer un "string" au lieu d'une zone numérique comme précédemment.
Pour finir ce chapitre, nous allons afficher un calendrier avec tous les jours du mois.
// date3.c from std use calendar, console; void main() { const string mois[12] = {"Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aout", "Septembre", "Octobre", "Novembre", "Decembre"}; const string jours_semaine[7] = {"Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"}; DATE_TIME now; int jour; get_datetime (out now); for (jour=1; jour<=max_days_in_month (now.month, now.year); jour++) { printf ("%9s : Le %2d %s %d\n", jours_semaine[day_of_week (jour,now.month, now.year)-1], jour, mois[now.month-1], now.year); } }
La fonction max_days_in_month() calcule le nombre maximum de jours d'un mois, et la fonction day_of_week() calcule le jour de la semaine d'une date (1=lundi, 7=dimanche). Toutes ces fonctions sont définies dans le composant 'calendar'.
L'instruction 'for' permet de faire varier la variable 'jour' entre 1 et le nombre de jours maximum du mois en l'incrémentant de un (jour++) à chaque tour de boucle.
Le Safe-c a un riche assortiment de types de données pour stocker les entiers.
Le tableau suivant en montre la liste :
nom du type | synonyme | utilisation mémoire | intervalle |
---|---|---|---|
int1 | tiny | 1 byte | de -128 à +127 |
int2 | short | 2 bytes | de -32_768 à +32_767 |
int4 | int | 4 bytes | de -2_147_483_648 à +2_147_483_647 |
int8 | long | 8 bytes | de -9_223_372_036_854_775_808 à +9_223_372_036_854_775_807 |
nom du type | synonyme | utilisation mémoire | intervalle |
---|---|---|---|
uint1 | byte | 1 byte | de 0 à 255 |
uint2 | ushort | 2 bytes | de 0 à 65_535 |
uint4 | uint | 4 bytes | de 0 à 4_294_967_295 |
Le type 'int' sera utilisé le plus souvent.
Les types plus petits peuvent être utiles :
. quand on doit déclarer beaucoup de variables de ce type, par exemple pour un grand tableau.
Choisir un type plus petit permet alors d'économiser de la place mémoire.
. quand on doit fournir un type d'une certaine taille à une interface extérieure.
Voici un exemple qui utilise tous ces types:
// types.c from std use console; void main() { int1 t; int2 s; int4 i; int8 l; byte b; uint2 n; uint u; t = 1; s = 32000; i = 1_000_000; // 1 million l = 1000L * 1000L * 1000L * 1000L; // mille milliards ! b = 0; n = 60000; u = 4_000_000_000; t = (tiny)(t * 100); // conversion du résultat du calcul vers le type tiny printf ("ceci sont des nombres signes : %d, %d, %d et %d\n", t, s, i, l); printf ("ceci sont des nombres non-signes : %u, %u et %u\n", b, n, u); printf ("le plus petit long est %d\n", long'min); printf ("le plus grand long est %d\n", l'max); }
ce qui affiche :
ceci sont des nombres signes : 100, 32000, 1000000 et 1000000000000 ceci sont des nombres non-signes : 0, 60000 et 4000000000 le plus petit long est -9223372036854775808 le plus grand long est 9223372036854775807
En interne, tous les calculs entiers se font, soit en int, soit en uint, soit en long. Si le résultat est stocké dans un type plus petit, vous devez d'abord le convertir :
int2 a = 1; int2 b = 2; int2 c = (int2)(a + b);
Les litéraux de type 'long' ont un L à la fin. Si l'une des opérandes a le type 'long', alors le résultat aura aussi le type 'long'.
int a = 1; long b = a + 1L;
Dans la fonction printf, les entiers signés sont représentés par %d et les non-signés par %u. On peut spécifier un nombre minimum de caractères (par exemple %10d), préfixer par des zéros au lieu de blancs (%010d), ou justifier à gauche (%-10d).
Les attributs 'min et 'max permettent de spécifier la plus petite ou plus grande valeur possible d'un type.
Pour les calculs mathématiques (par exemple la 3D), on utilise les types 'float' et 'double' :
nom du type | synonyme | utilisation mémoire | nombre le plus minuscule | nombre le plus gigantesque | précision |
---|---|---|---|---|---|
float4 | float | 4 bytes | 1.5E-45 | 3.4E+38 | 7 chiffres |
float8 | double | 8 bytes | 5.0E-324 | 1.7E+308 | 15 à 16 chiffres |
Voici un exemple qui utilise ces types:
// types2.c from std use console; void main() { float f; double d; f = 16.0 * 1.0e+10; d = 1.0e150 / 7.25e34; printf ("ceci sont des nombres a virgule flottante : %f, %f ", f, d); printf ("qu'on peut representer aussi en notation scientifique : %e, %e\n", f, d); }
ce qui affiche :
ceci sont des nombres a virgule flottante : 160000000000.000000, 137931034482758 60000000000000000000000000000000000000000000000000000000000000000000000000000000 000000000000000000000.000000 qu'on peut representer aussi en notation scientifiq ue : 1.600000e+11, 1.379310e+115
Dans la fonction printf, les types à virgule flottante sont représentés par %f en notation normale et par %e en notation scientifique (avec exposant).
On peut spécifier un nombre minimum de caractères (par exemple %10f), préfixer par des zéros au lieu de blancs (%010f), justifier à gauche (%-10f).
On peut aussi spécifier le nombre de décimales souhaitées (%.2f), ou combiner les deux, par exemple si on veut 12 caractères dont 6 décimales on écrira (%12.6f).
A remarquer que printf peut ne pas tenir compte de votre nombre de caractères
(par exemple si vous spécifiez seulement %2f pour afficher un milliard).
Il y a essentiellement 2 types 'énumération' fortement utilisés :
. le type 'bool' qui peut avoir les valeurs 'false' ou 'true' et permet d'effectuer des opérations logiques.
. le type 'char' qui stocke les caractères ascii de tout ce qui s'affiche à l'écran.
Ces types consomment 1 byte. Voici un exemple de leur utilisation :
// types3.c from std use console; void main() { bool b; char c; b = 1230 > 63+6; // vrai b = true; c = '$'; printf ("ceci est un bool : %u et ceci est un char : %c\n", b, c); }
Ce qui affiche :
ceci est un bool : 1 et ceci est un char : $
Dans la fonction printf, le type 'bool' est spécifié comme un nombre non-signé (par %u) et s'affiche par un 0 pour faux et 1 pour vrai; le type char est spécifié par %c.
Signalons encore le type 'wchar' (en 2 bytes) qui permet de stocker tous les caractères internationaux, même chinois,
et est spécifié par %C (C majuscule).
Il est possible de créer votre propre type énumeration. Imaginez que vous voulez écrire un jeu de cartes, vous pouvez définir un type énumération FAMILLE comme dans le programme suivant :
// types4.c from std use console; void main() { enum FAMILLE {PIQUE, AS, CARREAU, TREFLE}; FAMILLE f; f = CARREAU; f++; // augmente f de 1, donc f a maintenant la valeur TREFLE printf ("f a la valeur : %u\n", f); printf ("en clair cela veut dire : %s\n", f'string); printf ("le plus petit est %u\n", f'first); printf ("le plus grand est %s\n", f'last'string); }
Ce qui affiche :
f a la valeur : 3 en clair cela veut dire : TREFLE le plus petit est 0 le plus grand est TREFLE
Dans la fonction printf, ce type énumération peut s'afficher de 2 manières différentes:
- soit avec %u, ce qui affichera son numéro de séquence commençant à 0, donc il affichera 0 pour PIQUE, 1 pour AS, etc ...
- soit la variable peut être convertie en string à l'aide de l'attribut 'string et s'afficher alors avec %s (donc ici TREFLE).
Les attributs 'first et 'last permettent de spécifier la première et la dernière valeur d'un type énumération.
Le programme suivant déclare un tableau de 6 entiers et le remplit de 3 manières différentes avant d'afficher ses éléments :
// tableau.c from std use console; void main() { int tab[6]; int i; tab = {12, 71, 33, 46, 84, 6}; for (i=0; i<tab'length; i++) printf ("tab[%d] = %d\n", i, tab[i]); printf ("\n"); tab[4:2] = {all => 1}; for (i=0; i<tab'length; i++) printf ("tab[%d] = %d\n", i, tab[i]); printf ("\n"); clear tab; for (i=0; i<tab'length; i++) printf ("tab[%d] = %d\n", i, tab[i]); printf ("\n"); }
Il initialise d'abord le tableau en y copiant un aggrégat :
tab = {12, 71, 33, 46, 84, 6};
Ensuite, il remplace ses éléments 4 et 5 par la valeur 1 :
tab[4:2] = {all => 1};
Et finalement, il remet tous ses éléments à zéro :
clear tab;
Ce qui affiche :
tab[0] = 12 tab[1] = 71 tab[2] = 33 tab[3] = 46 tab[4] = 84 tab[5] = 6 tab[0] = 12 tab[1] = 71 tab[2] = 33 tab[3] = 46 tab[4] = 1 tab[5] = 1 tab[0] = 0 tab[1] = 0 tab[2] = 0 tab[3] = 0 tab[4] = 0 tab[5] = 0
Quand on réserve un tableau de 6 éléments, on dispose en fait des indices 0 à 5 car les tableaux commencent toujours à l'indice 0.
Remarquez la présence de l'attribut 'length.
Il est équivalent à la valeur 6 mais a un gros avantage : si vous décidez d'agrandir votre
tableau à 10 éléments il s'adaptera tout seul à la nouvelle taille.
Les tranches permettent de prendre, comme sur un gâteau, un ou plusieurs morceaux de tableau.
Une tranche est indiquée par la notation " tableau [ indice_début : nbre_d'elements ] " où indice_début indique l'indice du début de la tranche et nbre_d'elements indique le nombre d'éléments de la tranche.
L'exemple ci-dessus efface d'abord le tableau, ensuite il copie les valeurs {1, 2, 3} dans les 3 premiers éléments (tranche commençant à 0 de longueur 3).
Ensuite il recopie cette tranche dans les 3 éléments suivants du tableau.
// tableau2.c from std use console; void main() { int tab[6]; int i; clear tab; tab[0:3] = {1, 2, 3}; tab[3:3] = tab[0:3]; for (i=0; i<tab'length; i++) printf ("tab[%d] = %d\n", i, tab[i]); }
Ce qui affiche :
tab[0] = 1 tab[1] = 2 tab[2] = 3 tab[3] = 1 tab[4] = 2 tab[5] = 3
Il est à noter que si vous enleviez l'instruction clear dans l'exemple ci-dessus, vous auriez l'erreur de compilation suivante :
line 14 col 14 : semantic : local variable 'tab' used without having been initialized
Pourquoi ?
Parce qu'une variable n'est considérée initialisée que si elle a reçu une valeur complète de tous ses éléments en une fois.
Or dans l'exemple on remplit seulement les 3 premiers éléments avant de la lire.
Le Safe-c requiert que toutes les variables soient initialisées avant d'être lues,
cela garantit des programmes déterministes qui se comportent de manière identique à chaque exécution.
Voici comment trier un tableau à l'aide du composant de librairie 'sorting':
// tableau3.c from std use console, sorting; int cmp (int a, int b) { if (a < b) return -1; if (a > b) return +1; return 0; } package tri = new HeapSort (ELEMENT => int, compare => cmp); void main() { int tab[6]; int i; tab = {12, 71, 33, 46, 84, 6}; tri.sort (ref tab); for (i=0; i<tab'length; i++) printf ("tab[%d] = %d\n", i, tab[i]); }
Ce qui affiche :
tab[0] = 6 tab[1] = 12 tab[2] = 33 tab[3] = 46 tab[4] = 71 tab[5] = 84
Le composant 'sorting' contient plusieurs packages génériques pour trier des tableaux de n'importe quel type (en fait générique veut dire qu'ils sont indépendant de tout type).
Tout d'abord, il faut expliquer à la fonction de tri comment comparer deux éléments, ici deux 'int'. Pour cela, on déclare une fonction 'cmp' qui compare deux 'int' et qui renvoie -1 si le 1er est plus petit, 0 s'ils sont égaux et +1 si le 1er est plus grand.
Ensuite on "instancie" (ça veut dire on crée) un nouveau package de tri en donnant au package générique le type 'int' et notre fonction de comparaison. Il crée alors une fonction "sort" que nous pouvons utiliser.
Le résultat qui s'affiche est le tableau trié.
// tableau4.c from std use console; void main() { int tab[6]; tab = {1, 2, 3}; // <-- ERREUR }
L'exemple ci-dessus va vous donner une erreur à la compilation.
Lors d'une assignation les longueurs de tableaux doivent concorder.
Copier the aggregat de 3 valeurs dans un tableau de 6 éléments ne va forcément pas fonctionner.
// tableau5.c from std use console; void main() { int tab[6]; int i; for (i=0; i<100; i++) { printf ("remplissage de %d ..\n", i); tab[i] = i; } }
L'exemple ci-dessus essaye de copier une valeur dans les indices 0 à 99 d'un tableau qui pourtant ne compte que 6 éléments. Si vous exécutez ce programme il va d'abord afficher :
remplissage de 0 .. remplissage de 1 .. remplissage de 2 .. remplissage de 3 .. remplissage de 4 .. remplissage de 5 .. remplissage de 6 ..
Windows va envoyer un rapport sur ce crash à Microsoft et interroger la base de donnée pour voir si une solution existe pour ce problème. Evidemment comme Microsoft ne connait pas votre programme vous n'aurez pas de réponse ..
Pour plus d'infos sur les crash, rendez-vous au chapitre suivant ..
// tableau5.c from std use console, exception; void main() { int tab[6]; int i; arm_exception_handler(); for (i=0; i<100; i++) { printf ("remplissage de %d ..\n", i); tab[i] = i; } }
Voici à nouveau l'exemple du chapitre précédent, mais avec deux modifications :
. on a ajouté le composant 'exception' dans la liste "use",
. on a ajouté un appel vers la fonction arm_exception_handler() tout au début du programme.
Cette fonction va "armer" un traitement d'exception spécial en cas de crash :
au lieu d'envoyer un rapport de crash à Microsoft, elle va afficher le dialogue suivant :
On y voit que l'erreur s'est produite dans le fichier 'tableau5.c' à la ligne 15 !
Il ne vous reste donc plus qu'à ouvrir tableau5.c avec notepad, d'appuyer sur les touches
Ctrl+G et de taper le numéro de ligne (ici 15), pour voir où le programme
s'est crashé. Aha, c'est à la ligne "tab[i] = i;" cela n'a donc rien d'étonnant.
Le traitement d'exception du chapitre précédent fonctionne que parce que le compilateur a placé dans votre répertoire un fichier "tableau5.dbg" avec la liste des lignes de code source de votre programme et des adresses processeur correspondantes.
Si vous distribuez votre programme sur internet vous n'allez probablement
donner que "tableau5.exe". En cas de crash, la fenêtre d'erreur sera alors différente :
Comment retrouver la ligne d'erreur ici ? En utilisant le programme "bug.exe" qui est fourni avec le compilateur. A condition de disposer du fichier "tableau5.dbg" dans le répertoire courant, essayez de taper la commande suivante :
bug tableau5.dbg 401075
Le programme vous répondra alors :
ce qui vous permettra aussi de retrouver le fichier et le numéro de ligne de l'erreur.
Si vous avez fermé le message d'erreur un peu trop vite, pas de panique, il n'est pas perdu. En effet, un rapport d'erreur CRASH-REPORT.TXT est placé dans le dossier du programme et il contient les mêmes informations :
crash date : 03/11/2014 00:52:59 application : C:\PROJECTS\SafeCSamples1\tableau5.exe command line : tableau5 compile date : 03/11/2014 00:48:57 ACCESS_VIOLATION at rojects/safecsamples1/tableau5.c:15 401016 77120bb9 77120b8f fffffffe eax=00000006 ebx=0000000a ecx=7506d371 edx=0018faf4 esi=00000014 edi=00000013 ebp=0018ff88 esp=0018ff60
Jusque maintenant nous avons affiché le contenu des variables avec printf. Ce n'est cependant pas pratique si votre programme devient très grand et affiche beaucoup de lignes.
A la place des printf, on préfèrera donc utiliser un fichier de trace qu'on doit d'abord ouvrir au début du programme avec open_trace(), puis dans lequel on écrira des lignes de texte avec la fonction trace() qui s'utilise exactement comme printf() :
// test1.c from std use exception, tracing; void main() { int tab[6]; int i; open_trace ("test1.tra", 64*1024*1024); for (i=0; i<100; i++) { trace ("remplissage de l'élément %d\n", i); tab[i] = i; } }
En cas de crash de votre programme, il vous suffit alors d'ouvrir le fichier de trace avec notepad (ici il s'appelle test1.tra) pour voir jusqu'à où votre programme était arrivé.
Dans cet exemple, le fichier contiendra :
11/01/10 22:03:55 : remplissage de l'élément 0 11/01/10 22:03:55 : remplissage de l'élément 1 11/01/10 22:03:55 : remplissage de l'élément 2 11/01/10 22:03:55 : remplissage de l'élément 3 11/01/10 22:03:55 : remplissage de l'élément 4 11/01/10 22:03:55 : remplissage de l'élément 5 11/01/10 22:03:55 : remplissage de l'élément 6
Remarquez que la date est inversée, ici 11/01/10 indique le 10 janvier 2011.
Quand le fichier de trace devient trop grand (>64 MB), il est automatiquement renommé
en fichier .OLD et un nouveau fichier vide est créé.
Vous ne risquez donc pas de remplir le disque dur.
Vous pouvez spécifier une autre taille maximum dans open_trace().
// str1.c from std use console; void main() { string(4) nom1, nom2; nom1 = "Marc"; nom2 = "Eric"; printf ("Mon nom1 est : %s\n", nom1); printf ("Mon nom2 est : %s\n", nom2); }
L'exemple ci-dessus déclare deux variables string de 4 caractères, ensuite il copie "Marc" et "Eric" dans ces variables et les affiche :
Mon nom1 est : Marc Mon nom2 est : Eric
Un string c'est tout simplement un tableau de caractères. Tenez au lieu de :
string(4) nom1, nom2;
on aurait pu écrire aussi l'une de ces 3 lignes strictement équivalentes :
string nom1(4), nom2(4); char[4] nom1, nom2; char nom1[4], nom2[4];
C'est parce qu'en fait, le type "string" est prédéfini comme :
typedef char[] string;
Remarquez qu'on aurait pu aussi, au lieu d'écrire ceci :
nom1 = "Marc";
écrire cela :
nom1 = {'M', 'a', 'r', 'c'};
Une chose est toutefois gênante, on ne sait recopier dans 'nom1' que des chaines de 4 caractères.
Si vous essayez ceci vous aurez une erreur :
nom1 = "Luc"; // <-- ERREUR
Evidemment vous pouvez recopier Luc dans les 3 premiers caractères et nul dans le 4ème, ce qui va afficher Luc correctement parce que printf() s'arrête au caractère nul :
nom1 = {'L', 'u', 'c', nul}; // OK
ou bien vous pouvez insérer le caractère nul dans la chaine, comme ceci :
nom1 = "Luc\0"; // OK
Mais enfin tout ceci n'est pas pratique.
Comment faire pour manipuler des chaines de caractères plus facilement ?
En fait on utilisera le composant "strings" qui contient quelques fonctions très utiles :
La fonction strcpy() permet de recopier un string dans un autre string en remplissant la partie non-utilisée avec le caractère "nul" de valeur zéro.
La fonction sprintf() s'utilise comme printf(), seulement au lieu d'afficher le résultat sur la console elle le place dans le premier string. Remarquez qu'il faut toujours placer le mot-clé "out" devant la variable qui reçoit un résultat.
// str2.c from std use console, strings; void main() { char nom[64], prenom[64], nom_complet[128]; strcpy (out prenom, "Lucien"); strcpy (out nom, "Demoitier"); sprintf (out nom_complet, "%s %s", prenom, nom); printf ("Mon nom est : %s\n", nom_complet); }
ce qui affiche :
Mon nom est : Lucien Demoitier
La fonction strcat() permet d'ajouter un string à la fin d'un autre string. Remarquez qu'il faut spécifier le mot-clé "ref" devant le premier string parce que cette variable est "modifiée" :
// str3.c from std use console, strings; void main() { char str[1000]; strcpy (out str, "Bonjour "); strcat (ref str, " je "); strcat (ref str, " m'appelle "); strcat (ref str, " Lucien "); strcat (ref str, " Demoitier "); printf ("%s\n", str); }
ce qui affiche :
Bonjour je m'appelle Lucien Demoitier
La fonction sscanf() effectue l'opération inverse de sprintf() : Elle examine un string et en extrait les différents champs qui le composent. Ici le format indique 4 champs (%*s %d %*s %d), on lui demande d'ignorer les champs avec une * et de recopier les deux champs numériques dans a et b.
// str4.c from std use console, strings; void main() { char str[1000]; int a, b; strcpy (out str, "valeurs 12 et 18"); sscanf (str, "%*s %d %*s %d", out a, out b); printf ("%d et %d\n", a, b); }
ce qui affiche :
12 et 18
Dans l'exemple ci-dessous on a déclaré une constante (grâce au mot-clé const). Une constante ressemble à une variable, sauf qu'elle ne peut pas changer de valeur. Si vous utilisez souvent le même string, il est intéressant de ne le déclarer qu'une seule fois comme constante.
// str5.c from std use console; void main() { const string TITLE = "Bonjour chers amis !"; printf ("%s\n", TITLE); }
L'exemple suivant montre qu'on peut imprimer une tranche d'un string. Ici on imprimera "njo" vu qu'on commence à l'indice 2 et qu'on imprime 3 caractères :
// str6.c from std use console, strings; void main() { char str[1000]; strcpy (out str, "Bonjour"); printf ("%s\n", str[2:3]); }
La fonction strlen() permet de demander la longueur d'un string jusqu'au caractère nul de remplissage ou jusqu'à la fin du tableau. Ici le programme va afficher 7 :
// str7.c from std use console, strings; void main() { char str[1000]; strcpy (out str, "Bonjour"); printf ("le string a la longueur : %d\n", strlen(str)); }
Une constante est déclarée par le mot-clé const. Elle permet de donner un nom à une valeur qui ne change pas.
Il est de bon style d'écrire les constantes en majuscules.
Voici quelques exemples de constantes de différents types simples, tableau et structure :
const int MAX = 45 * 100; const char CARET = '^'; const string WELCOME = "Hello " + " people" + "\n"; const char[2] CRLF = (char)13 + (char)10; const int^ NULL = null; struct PRODUCT { char[4] code; float value; } const PRODUCT prod1 = {"ABCD", 1.34}; const PRODUCT prod2 = {code => "ABCD", value => 1.34}; const PRODUCT[] prod_table = {{code => "AAAA", value => 1.31}, {code => "BBBB", value => 1.32}, {code => "CCCC", value => 1.33}, {code => "DDDD", value => 1.34}};
D'habitude on spécifie une taille quand on déclare un tableau dans une structure.
Il y a une exception à cette règle : quand on déclare une constante, on peut spécifier un tableau sans en préciser la taille.
Voici un exemple, la structure INFO ci-dessous est une "jagged structure" :
from std use console; struct INFO { char[] code; // un tableau de char sans longueur est autorisé ici float value; } const INFO info1 = {"bananas", 12.5}; const INFO info2[] = {{"melon", 8.6}, info1, {"apple", 120.4}}; void main() { int i; for (i=0; i<info2'length; i++) printf ("%-10s %6.1f\n", info2[i].code, info2[i].value); }
ce qui donne :
melon 8.6 bananas 12.5 apple 120.4
Il est à noter que la structure INFO de l'exemple ci-dessus ne peut pas être utilisée pour déclarer une variable. On peut l'utiliser uniquement pour déclarer des constantes, des paramètres de fonction de mode IN, ou des références vers celles-ci.
Dans une structure "jagged" on peut aussi déclarer des structures variantes
sans donner de valeur pour le paramètre discriminant.
Nombres entiers --------------- Les nombres entiers peuvent avoir une forme décimale, hexadécimale ou binaire. Exemples: 64 // compatible avec n'importe quel nombre entier signé ou non signé 65_000_000 // avec souligné pour une meilleure lisibilité 0xFF // valeur hexadécimale 0b1111_0000 // valeur binaire 176L // long Le caractère souligné est autorisé dans un nombre entier pour une meilleure lisibilité. La valeur entière la plus négative (-2^63) n'est pas représentable par un litéral entier, l'expression long'min peut être utilisée à la place. Si le littéral a un suffixe L, il est de type 'long'. Nombres à virgule flottante --------------------------- Un nombre à virgule flottante peut avoir le type float ou double. Exemples: 1.2f 3.023_223E+2d 0x80.0p-1 Un suffixe "f" indique un type 'float', un suffixe "d" indique un type 'double'. Si aucun suffixe n'est présent, le type est déterminé d'après le contexte où apparait le nombre. Un nombre hexadécimal (avec préfixe 0x) est utilisé pour une représentation machine précise : la mantisse est alors spécifié dans la base 16 et l'exposant est spécifié dans la base 10. Littéraux caractère ------------------- Les littéraux caractère sont des valeurs constantes de type char (ou de type wchar s'ils ont un préfixe 'L'). Exemples: 'A' 'É' '\N' '\x80' // code 80 hexa L'\x00FF' // code FF hexa (le préfixe 'L' indique un type wchar) Les séquences d'échappement suivantes sont supportées : \' 0x0027 Single quote \" 0x0022 Double quote \\ 0x005C Backslash \0 0x0000 Null \a 0x0007 Alert \b 0x0008 Backspace \f 0x000C Form feed \n 0x000A New line \r 0x000D Carriage return \t 0x0009 Horizontal tab \v 0x000B Vertical tab Littéraux string ---------------- Les littéraux string sont des valeurs constantes de type string (ou de type wstring en cas de préfixe 'L'). Exemples: "A" "Hello\n" "Hello" + " World" L"Hallöchen" L"\x1234" Les littéraux string de plus de 128 caractères génèrent une erreur de compilation. Des littéraux string plus longs peuvent être construits en utilisant l'opérateur de concaténation '+'.
Une variable sert à nommer une case mémoire qui contiendra une valeur d'un certain type.
Voici quelques exemples:
int count, i=0; char[10] c; string(80) buffer; PRODUCT[]^ ptr = new PRODUCT[nb];
Les variables peuvent être déclarées à différents endroits :
Une telle variable est créée lors de l'appel de la fonction, et elle disparait lorsque la fonction se termine. En outre, si la fonction est appelée plusieurs fois récursivement, plusieurs occurences distinctes de la variable vont co-exister.
Une variable globale se déclare, soit dans un fichier .h, soit au début d'un fichier .c (après les clauses use mais avant toute déclaration de corps de fonctions).
Si elle est déclarée dans un ficher .h elle sera accessible à toutes les parties du programme qui importent le composant .h par une clause use.
Une variable globale est créée au démarrage du programme et elle disparait lorsque le programme se termine. Son désavantage, par rapport à une variable locale, est qu'elle occupe de la mémoire pendant toute la durée du programme.
Une variable dynamique est allouée par l'allocateur new et son adresse mémoire sera stockée dans une variable pointeur.
Elle existe tant que sa mémoire n'a pas été libérée avec l'instruction free.
Une variable globale doit être déclarée avant toute déclaration des corps de fonctions, pour des raisons de lisibilité.
Ceci va donc donner une erreur :
void treat() { } int i; // <-- incorrect void main() { }
En effet, l'ordre correct de déclaration est :
int i; // <-- correct void treat() { } void main() { }
Maintenant, si vous souhaitez tout de même déclarer la variable entre deux corps de fonctions, il existe un truc, c'est de déclarer un package qui va englober la déclaration, comme ceci :
void treat() { } package MES_VARIABLES int i; // <-- correct end MES_VARIABLES; void main() { }
Le mot-clé typedef sert à donner un nom à un type.
Exemples:
typedef char[] MYSTRING; // type tableau sans taille typedef char[10] NAME; // type tableau typedef byte[]^ POINTEUR; // type pointeur vers tableau MYSTRING str(100); NAME name; POINTEUR p;
typedef permet aussi de déclarer des types vers fonction, ce qui permet les appels de fonction indirects.
En voici un exemple:
from std use console; typedef void TREAT (int nr); // type pointeur vers fonction void A (int i) { printf ("A : %d\n", i); } void B (int i) { printf ("B : %d\n", i); } void main () { TREAT treat; // variable de type 'pointeur vers fonction' treat = A; // la variable reçoit l'adresse de la fonction A treat (nr => 100); // appel indirect de la fonction A() treat = B; // la variable reçoit l'adresse de la fonction B treat (nr => 200); // appel indirect de la fonction B() }
ce qui affiche :
A : 100 B : 200
Une référence permet de donner un synonyme à une variable ou à un paramètre.
Par exemple, imaginez que vous ayez le programme suivant :
struct STUDENT { int nr; int birth; char grade; bool passed; } STUDENT student[100]; void main() { int i = 5; student[i].nr = 5; student[i].birth = 1967; student[i].grade = 'F'; student[i].passed = (student[i].grade <= 'C'); }
Ce programme peut être amélioré. En effet, il répète 4 fois student[i] ce qui nécessite 4 évaluations de l'indice [i].
Voici une version utilisant une référence qui n'évalue [i] qu'une seule fois:
struct STUDENT { int nr; int birth; char grade; bool passed; } STUDENT student[100]; void main() { int i = 5; { ref STUDENT s = student[i]; s.nr = 5; s.birth = 1967; s.grade = 'F'; s.passed = (s.grade <= 'C'); } }
Attention à ne pas oublier le mot-clé ref sinon vous déclareriez simplement une nouvelle variable. Ici s est bien un synonyme de student[i].
Voici d'autres exemples :
ref char ch = buffer[i]; // reference to array element ref string str = buffer[0:3]; // reference to array slice ref int age = student.age; // reference to age field
Il y a un piège lorsqu'on utilise une référence d'un pointeur. Pendant la durée où la référence est active, celle-ci va bloquer la variable désignée par le pointeur de sorte que vous ne puissiez pas la libérer avec free.
Exemple:
void main() { char[]^ buffer = new char[5]; // réservation de 5 caractères ref char[] table = buffer^; // table est une référence vers buffer^ table = "Hello"; // copie d'une valeur vers le tableau free buffer; // libération du buffer }
Le programme ci-dessus va crasher sur la commande free parce que la référence bloque buffer.
La manière correcte pour éviter ce problème est d'appeler free lorque la référence n'est plus active, en plaçant la référence dans un bloc d'instructions comme ceci :
void main() { char[]^ buffer = new char[5]; // réservation de 5 caractères { ref char[] table = buffer^; // table est une référence vers buffer^ table = "Hello"; // copie d'une valeur vers le tableau } free buffer; // libération du buffer }
Opérateur Symbole nombre de cycles CPU =========== ======= ==================== Ajouter + 1 µsec Soustraire - 1 µsec Multiplier * 4 µsec Diviser / 40 µsec Reste Division % 40 µsec Shift Gauche << 1 µsec Shift Droite >> 1 µsec
En plus d'être utilisés sur des entiers et des float, les opérateurs "+" et "-" peuvent aussi être utilisés pour ajouter ou soustraire un entier d'un type énumération. Exemple:
enum COLOR {BLUE, RED, GREEN}; COLOR col = BLUE + 2; // GREEN printf ("col = %s\n", col'string);
Les opérateurs / (division) et % (reste de la division) provoquent un crash du programme si l'argument de droite égale zéro. A noter que l'opérateur % n'est autorisé que sur des entiers.
Les opérateurs de shift permettent une multiplication ou division très rapide par une puissance de 2. Par exemple au lieu de diviser par 64 avec "a/64", vous pouvez shifter par 6 "a>>6", parce que 2 exposant 6 donne 64. Quand votre programme doit aller très très vite le shift est intéressant surtout pour remplacer la division qui est tout de même environ 40 fois plus lente qu'un shift sur les processeurs actuels.
int a = 2; int b = (a * (a + 1)) >> 1;
Opérateur Symbole =========== ======= Ajouter 1 ++ Soustraire 1 --
L'opérateur d'incrémentation "++" permet d'ajouter un à une variable et en même temps de l'évaluer. L'opérateur peut être placé devant ou derrière la variable, ce qui a un effet différent sur l'évaluation : s'il est placé devant, l'évaluation renvoie la valeur après incrémentation, sinon elle renvoie la valeur avant incrémentation. Exemple:
int i = 0; int j = ++i; // j == 1, i == 1
tandis que :
int i = 0; int j = i++; // j == 0, i == 1
Opérateur Symbole =========== ======= concaténer +
L'opérateur "+" peut concaténer des chaines de caractères constantes de type string ou wstring. Exemple:
const string WELCOME = "Welcome to " + "Safe-C programming\n"; const string WELCOME2 = WELCOME + "We hope you have a nice day\n";
Opérateur Symbole =========== ======= ET logique & OU logique | OU exclusif ^ Inversion ~
Ces opérateurs travaillent bit par bit sur la représentation binaire des valeurs.
Le ET logique renvoie un bit 1 seulement si les deux valeurs ont un bit de valeur 1.
Le OU logique renvoie un bit 1 seulement si au moins une valeur a un bit de valeur 1.
Le OU exclusif renvoie un bit 1 seulement si une valeur a un bit de valeur 1 mais pas l'autre.
L'inversion ne prend qu'un seul argument et change tous les bits 1 à 0, et 0 à 1.
int i = 17 & 3; // 1 17 : 10001 3 : 00011 ------------ 1 00001
Opérateur Symbole =========== ======= plus petit que < plus petit ou égal <= plus grand que > plus grand ou égal >= égal à == pas égal à != pas !
Les opérateurs de comparaison permettent de comparer deux valeurs numériques ou de type énumération. Le résultat est une valeur de type bool, true ou false.
int i = 2; int j = 3; bool b = i <= j; // true
L'opérateur "!" ne prend qu'un seul argument booléen à droite et inverse les valeurs false et true.
bool b = !(i <= j); // false
Les opérateurs == et != peuvent aussi comparer des pointeurs.
if (p != null)
Opérateur Symbole =========== ======= ET && OU || OU exclusif ^
Les opérateurs booléens permettent de tester plusieurs conditions en une fois.
if (a == 2 && b == 3) // les deux doivent être vrais if (a == 2 || b == 3) // au moins l'un des deux doit être vrai if (a == 2 ^ b == 3) // l'un d'eux doit être vrai, mais pas les deux
A noter qu'on peut combiner plusieurs opérateurs booléens identiques :
if (a == 2 && b == 3 && c == 4 && d == 5)
par contre, si les opérateurs sont différents, vous devrez utiliser des parenthèses pour lever les ambiguités.
if ((a == 2 && b == 3) || (c == 4 && d == 5))
Contrairement à tous les autres opérateurs, les opérateurs && et || n'évaluent leur argument de droite que si nécessaire. C'est utile pour éviter les divisions par zéro.
if (den == 0 || num/den > limit) // num/den n'est évalué que si den n'est pas zéro ... if (den != 0 && num/den > limit) // num/den n'est évalué que si den n'est pas zéro ...
L'opérateur "? :" permet de tester une condition et de donner une valeur si elle est vraie et une autre si elle est fausse.
Exemple :
average = (count != 0) ? sum/count : 0;
Cet exemple renvoie dans average la valeur 'sum/count' si count n'est pas zéro, sinon elle renvoie zéro.
Les opérateurs ne sont pas toujours exécutés de gauche à droite. Dans l'exemple suivant, la multiplication est exécutée avant l'addition, ce qui fait que le résultat sera 14, et non pas 20.
int i = 2 + 3 * 4; // == 14
Si vous voulez que l'addition soit exécutée d'abord, vous devez utiliser des parenthèses, comme ceci :
int i = (2 + 3) * 4; // == 20
Les opérateurs ont les priorités d'évaluation suivantes :
priorité des opérateurs ----------------------- Unaire + - ! ~ -- ++ Terme * / % Somme + - Expression simple << >> Relation == != < > <= >= Condition && || & | ^ Test Conditionnel ? :
Le Safe-C n'autorise pas le mélange de types signés (int) et non-signés (uint). Par conséquent, si vous voulez additionner un type signé et un type non-signé vous devrez d'abord convertir l'un des deux.
int i, j; uint u; i = 2; u = 34; j = i + (int)u; // conversion de u vers le type int u = (uint)j; // conversion de j vers le type uint
Si vous utilisez les petits types int1 ou int2 pour stocker le résultat d'un calcul, vous devez d'abord convertir le résultat.
int a, b; int1 i; a = 34; b = 12; i = (int1)(a + b); // conversion vers le type int1
Si vous utilisez le petit type float pour stocker une valeur de type double vous devez d'abord convertir le résultat.
float f; f = (float)sin(angle);
Si vous voulez convertir un type bool en type int, vous pouvez le convertir. Le résultat sera égal à 0 ou 1.
int i; i = (int)(a < b); // résultat 0 or 1
Si vous voulez convertir un type char en type int, vous pouvez le convertir.
char c = 'A'; int i; i = (int)c;
Si vous voulez convertir un type float en type int, vous pouvez le convertir. Attention, le résultat sera tronqué. Vous pouvez utiliser les fonction floor(), round() ou ceil() du composant math pour différents arrondis.
float f = 3.1415; int i; i = (int)f; // troncage (enlever la partie fractionnaire) i = (int)floor(f); // arrondi vers le bas (le plus négatif) i = (int)round(f); // arrondi vers l'entier le plus proche i = (int)ceil(f); // arrondi vers le haut (le plus positif)
Les assignations peuvent être combinées avec un opérateur. Voici quelques exemples et leur équivalent :
int i = 1; i += 2; // i = i + 2; i -= 3; // i = i - 3; i *= 4; // i = i * 4; i &= 1; // i = i & 1; i <<= 1; // i = i << 1;
L'instruction clear permet d'effacer une ou plusieurs variables, donc d'y placer la valeur zéro.
int i, j; float f; clear i, j, f; // i = 0; j = 0; f = 0.0;
L'assignation "=" permet de calculer une valeur (à droite) et de placer le résultat dans la variable (à gauche).
int i, j, k[2]; float f; char[3] tar; i = 2; j = i * 2 + 345; f = (float)j * 3.1415927; k = {i, j}; tar = "ABC";
L'assignation combinée est une forme abbréviée valable pour les opérateurs suivants :
+= -= *= /= %= <<= >>= &= |= ^=
int j = 2; j += 3; // j = j + 3;
i++; // i = i + 1; ++i; // i = i + 1; i--; // i = i - 1; --i; // i = i - 1;
t = ticks(); // pas de paramètres printf ("Hello"); rc = fcreate (out file, "file.txt", ANSI);
Il existe 3 mode de passage des paramètres : IN, REF et OUT.
Le mode IN est celui par défaut quand on ne spécifie rien : il passe la valeur calculée à la fonction.
Le mode REF indique qu'on passe une variable à la fonction pour que celle-ci puisse éventuellement la modifier.
Le mode OUT signifie que la fonction va nous renvoyer un résultat dans cette variable. Contrairement aux deux autres modes, il n'est pas nécessaire d'initialiser la variable avec une valeur avant l'appel.
int valeur1 = 1; char tableau2[10]; int resultat3; clear tableau2; ma_fonction (valeur1, ref tableau2, out resultat3);
A noter que le mot-clé "in" n'existe pas. Quand on souhaite le mode IN on ne spécifie pas de mode.
Pour rendre un appel de fonction plus clair, on peut spécifier le nom des paramètres suivi d'une flèche "=>" :
i = strstr (text => "WiZZ", fragment => "i"); init_draw (out dc => my_dc, buffer => buf, width => 100, height => 200);
Une fonction peut avoir une valeur par défaut pour les derniers paramètres. Ceux-ci sont alors optionnels.
void put_int (int value, int base = 10); put_int (i, base => 16); put_int (i); // assumes base 10
L'instruction "return" permet de terminer une fonction et éventuellement de renvoyer un résultat. On ne peut renvoyer que des valeurs simples, pas des valeurs composées comme des tableaux ou des structures.
int sum (int a, int b) { return a + b; } void proceed (int f) { if (f == 2) return; // ... }
Une instruction de bloc regroupe une série d'instructions. Les instructions if, while et for nécessitent soit une seule instruction, soit un bloc. Au début d'un bloc on peut déclarer des variables qui ne sont accessibles qu'à l'intérieur du bloc.
{ // Instruction de bloc int i; H(); I(); }
Une instruction if permet l'exécution conditionnelle.
if (b) { // ... } if (b) ; // ; est une instruction vide else ;
L'instruction switch permet une exécution conditionnelle à plusieurs branches.
switch (i) { case -1: // quand i vaut -1 f(); break; case 1: // quand i vaut 1 g(); break; default: // branche par défaut si aucune valeur ne correspond break; }
L'instruction 'while' permet la répétition d'instructions tant qu'une condition est vraie.
compteur = 0; while (compteur < 10) compteur++; while (compteur < 20) { printf ("compteur = %d\n", compteur); compteur++; }
L'instruction 'for' est une forme plus compacte de l'instruction 'while'.
Elle est composée de 3 parties séparées par deux points-virgules. La première partie n'est exécutée qu'une fois, la seconde partie est une condition qui est testée avant chaque tour de boucle pour voir si on continue à boucler, et la 3ème partie est exécutée après les instructions à répéter, à la fin de chaque boucle.
Les parties 1 et 3 peuvent êtres vides ou contenir plusieurs instructions séparés par des virgules.
Si la partie 2 est vide les instructions vont boucler sans fin (sauf s'il y a un break ou un return à l'intérieur).
for (compteur=0; compteur<10; compteur++) ; for (i=0; i<arg'length; i++) printf ("arg %d : %s\n", i, arg[i]); for ( ; p!=null; i++,j+=3,p=p^.next) ; for (;;) // boucle infinie ;
L'instruction 'break' est utilisée pour quitter l'instruction 'while', 'for' ou 'switch' la plus interne.
for (i=0; i<10; i++) { if (i == j) break; }
L'instruction 'continue' est utilisée pour passer à la prochaine itération d'une instruction 'while' ou 'for'.
for (i=0; i<10; i++) { if (i == j) // skip j continue; printf ("i = %d\n", i); }
L'instruction 'free' libère l'objet heap désigné par un pointeur.
free p; free null; free f();
Libérer un pointeur null n'a aucun effet.
Une erreur d'exécution se produit si l'objet heap est toujours référencé
par une référence ou un paramètre, ou s'il a déjà été libéré.
L'instruction 'assert' provoque un arrêt du programme si une condition est fausse.
assert TABLE_SIZE%2 == 0; // TABLE_SIZE doit être divisible par deux
Une instruction 'abort' arrête le programme.
switch (reponse) { case 'Y': printf ("oui"); break; case 'N': printf ("non"); break; default: abort; // ne devrait jamais se produire }
Une instruction 'sleep' suspend le programme pour la durée spécifiée, en secondes.
sleep 10; // attendre 10 secondes sleep 1.5; // attendre 1.5 secondes sleep 0; // abandonner la tranche de temps en cours pour ce thread
L'expression doit avoir un type entier ou virgule flottante.
Les durées de plus de 86400 secondes (un jour) ou plus petit que -86400
secondes ne sont pas autorisées. Une durée négative est équivalente à zéro.
Une instruction 'code' insère du code machine directement dans le programme.
Elle n'est autorisée que dans une section unsafe.
#begin unsafe _asm {0xC2 0x08 0x00}; // ret 8 #end unsafe
Une instruction '_unused' sert à supprimer l'avertissement du compilateur qu'une variable ou un paramètre est inutilisé.
_unused par1;
L'instruction "run" permet de lancer un thread qui va s'exécuter en parallèle avec le reste de votre programme. Les paramètres de la fonction thread sont limités à un seul paramètre de type int.
from std use console; void my_thread (int nr) { printf ("this is my_thread %d\n", nr); } void main() { int rc; rc = run my_thread (1); // rc == 0 si le thread a bien démarré. rc = run my_thread (2); // rc == 0 si le thread a bien démarré. rc = run my_thread (3); // rc == 0 si le thread a bien démarré. printf ("this is main\n"); sleep 1.0; }
Ce qui affiche :
this is main this is my_thread 2 this is my_thread 1 this is my_thread 3
Une déclaration de fonction précise les paramètres, valeur de retour et options d'une fonction.
int start_treatment (); int treat (string name, char option); void sum (int a, int b, out c); void operate (ref TABLE table); void print (int value, int width = 1);
Il y a 3 modes de passage de paramètres :
mode direction accès au paramètre ==== ========= ================== in --> lecture seule ref <-> lecture et écriture out <-- écriture d'abord, puis lecture et écriture
Le mode "in" permet de passer une valeur à la fonction. A l'intérieur de la fonction, un paramètre de mode "in" ne peut pas être modifié. A noter que le mot-clé "in" n'existe pas, quand on souhaite spécifier le mode "in" on n'indique aucun mode.
Le mode "ref" passe l'adresse d'une variable à la fonction afin que celle-ci puisse être modifiée.
Le mode "out" permet à la fonction de retourner une valeur. A l'intérieur de la fonction, un paramètre de mode "out" est considéré comme non-initialisé et doit recevoir une valeur avant que la fonction ne se termine.
void my_func (int a, ref int b, out c) { a = 1; // erreur : a ne peut être modifié } // erreur : c doit recevoir une valeur
En interne, les paramètres de mode "in" sont passés par valeur pour des types simples et par adresse pour des types composés. Les paramètres de mode "ref" et "out" sont toujours passés par adresse. Pour des paramètres de type "tableau sans longueur" ou "struct variante sans discriminant", un paramètre additional caché est passé avec la longueur du tableau ou la valeur du discriminant. C'est ce qui permet d'utiliser l'attribut 'length pour connaitre la longueur du tableau à l'intérieur de la fonction.
void my_func2 (string str) { int len = str'length; }
Si un paramètre de mode "in" a une valeur constante par défaut (exemple: 'width' ci-dessus), alors le paramètre correspondant est optionnel lors de l'appel de la fonction :
print (value => 10, width => 2); print (value => 10); // width == 1 par défaut
Le type de retour d'une fonction peut être soit un type simple (donc tableau, struct, union, opaque ou générique ne sont pas autorisés), ou "void" si la fonction n'a pas de paramètres.
string concat (string a, string b); // erreur: valeur de retour doit être un type simple.
Les options suivantes existent :
1) inline --------- inline int treat (char count); indique que la fonction devrait être insérée en ligne au lieu d'être appelée. 2) callback ----------- [callback] int MainWin (HWND hwnd, UINT message, WORD w, LONG l); indique que la fonction sera appelée par le système d'exploitation. 3) extern --------- [extern "KERNEL32.DLL"] int GetStdHandle (int nStdHandle); indique que le corps de la fonction se trouve dans une DLL externe. Les options callback et extern ne sont utilisables quand dans une section unsafe (voir pointeurs unsafe).
Le corps de la fonction définit son fonctionnement interne.
void somme (int a, int b, out int c) { c = a + b; }
Le préfixe "public" est obligatoire pour un corps de fonction si cette fonction a été pré-déclarée dans un fichier .h de même nom que le .c :
// p.h void somme (int a, int b, out int c); // déclaration
// p.c public void somme (int a, int b, out int c) // corps avec le mot clé "public" { c = a + b; }
Pour utiliser la fonction somme vous devrez importer le fichier p.h à l'aide d'une clause "use p;"
Une fonction ayant un type de retour non-void doit se terminer par une instruction return ou abort.
Le point d'entrée dans un programme est une fonction appelée 'main'. Il ne doit y avoir qu'une seule fonction de ce nom.
Elle doit avoir le type de retour void ou int. En cas de type int, la valeur est renvoyée au système d'exploitation.
int main() { return -1; }
main() doit avoir, soit aucun paramètre, soit un seul paramètre de mode 'in' et de type string [ ] qui recevra un tableau de string avec les arguments spécifiés au lancement du programme. Le 1er argument (à l'indice 0) est le nom du programme lui-même.
// prog.c from std use console; void main(string[] par) { int i; for (i=0; i<par'length; i++) printf ("parametre %d : %s\n", i, par[i]); }
ce qui affiche :
C:\SafeC> prog un deux trois parametre 0 : prog parametre 1 : un parametre 2 : deux parametre 3 : trois
Une fonction peut s'appeler elle-même, ce qui permet les appels récursifs. Il faut cependant limiter la profondeur de la récursion parce qu'un nombre trop grand d'appels récursifs va provoquer un crash du programme. Ce nombre dépend du nombre de variables locales déclarées dans la fonction.
// recursif.c from std use console; int factorielle (int i) { if (i == 1) return 1; return i * factorielle (i-1); // la fonction s'appelle elle-même ici } void main() { int n; for (n=1; n<=10; n++) printf ("factorielle(%d) = %d\n", n, factorielle(n)); }
avec le résultat:
factorielle(1) = 1 factorielle(2) = 2 factorielle(3) = 6 factorielle(4) = 24 factorielle(5) = 120 factorielle(6) = 720 factorielle(7) = 5040 factorielle(8) = 40320 factorielle(9) = 362880 factorielle(10) = 3628800
Un grand programme Safe-C est composé de multiples fichiers avec les extensions .h et .c
Les fichiers .h et .c vont toujours par paires, sauf le fichier .c principal pour lequel le .h est optionnel.
Un fichier .h et son .c correspondant forment une "unité de compilation" :
. le fichier .h contiendra les déclarations "publiques" utilisables à l'extérieur de l'unité de compilation
. tandis que le fichier .c qui est la partie "privée" contiendra tous les corps de fonction
et autres déclarations non-visibles à l'extérieur.
Les fichiers .h définissent donc les interfaces entre toutes les parties d'un programme. Voici un exemple de fichier .h :
// plotter.h struct POINT { float x, y; } const POINT ORIGIN = {x => 0.0, y => 0.0}; void move (POINT from, POINT to);
L'interface plotter.h déclare une structure POINT, une constante ORIGIN et une fonction move qui pourront être utilisés par un programme p.c comme ceci :
// p.c use plotter; void main() { move (ORIGIN, {1.0, 2.0}); move ({1.0, 2.0}, {2.0, 4.0}); move ({2.0, 4.0}, ORIGIN); }
La clause "use plotter;" au début du programme permet de charger le fichier plotter.h et de pouvoir utiliser ses déclarations. Remarquez que si plotter.h et .c se trouvent ailleurs sur le disque, il faudrait donner le chemin relatif pour l'atteindre. Par exemple s'ils se trouvent dans le sous-folder "hardware" :
use hardware/plotter;
A quoi ressemble plotter.c, la partie privée ? Et bien elle va contenir toute sortes de fonctions pour faire fonctionner un plotter, mais en tout cas elle devra contenir un corps pour la fonction move(). Vu que la fonction move() est déclarée dans le .h, le mot-clé public est obligatoire dans le .c :
// plotter.c void plot (POINT p) { ... } public void move (POINT from, POINT to) // <-- mot-clé "public" obligatoire { POINT p; p = from; while (p.x < to.x) { plot (p); p.x += 0.1; } // ... }
Dans certains cas, on souhaite déclarer une structure dans le .h mais on ne souhaite pas que les champs de cette structure soient accessibles à l'extérieur. On utilisera alors un type opaque.
Par exemple, le composant de librarie file.h déclare une structure FILE comme ceci :
// files.h struct FILE; // ceci est un type OPAQUE car on ne spécifie pas les champs de la structure int fopen (out FILE file, string filename);
Les utilisateurs de file.h ne pourront pas faire grand-chose avec le type FILE, à part déclarer des variables de ce type, les effacer par un clear et passer ces variables dans les fonctions déclarées dans file.h telles que fopen(). En particulier, ils ne pourront pas prendre une copie de la variable.
Evidemment la structure FILE sera re-déclarée une 2ème fois dans le fichier file.c, mais cette fois-ci avec tous les champs accessibles uniquement dans le .c.
// file.c struct FILE { int h; // windows handle (<=0 means closed file) int error; // error code from last fill_buffer or flush_buffer ENCODING coding; bool is_write; // false=read, true=write bool last_chunk_read; bool look_ahead_encoding_full; wchar look_ahead_encoding; bool look_ahead_crlf_full; wchar look_ahead_crlf; bool unget_full; wchar unget_char; bool put_char_full; wchar put_char; int len; // index of buffer for reading from o.s. (not used for write) int ofs; // read or write index into buffer byte buffer[16*1024]; }
Il y a toujours plusieurs façons de découper un programme en morceaux.
Un bon découpage (un bon design comme disent les professionnels) se caractérise par le fait que les fichiers .h sont assez petits et clairs et que les fichiers .c sont assez gros, car plus les interconnections entre les différentes parties d'un programme sont petites et bien définies, et plus la complexité est enfermée dans les .c et pas distribuée dans tout le programme, plus le programme dans son ensemble sera facile à comprendre.
Vous pouvez facilement diviser le temps de programmation par dix si, avant de commencer, vous rédigez une analyse détaillée de votre programme sur papier, puis rédigez des fichiers .h qui ne changeront plus (cela s'appelle "programming in the large").
Si l'analyse papier et les fichiers .h sont clairs, cela ne posera aucun problème de les donner à une équipe de programmeurs où chacun écrira l'un des .c.
Si lors de cette phase on s'aperçoit qu'il faut changer un .h parce que ça "coince",
qu'il manque quelque chose, ou qu'il y a un cas que vous n'aviez pas prévu,
alors votre analyse était de mauvaise qualité et vous allez perdre du temps.
Votre méthode d'analyse est alors à revoir ... pour votre prochain programme.
L'exemple suivant déclare une structure PRODUCT. Une structure, c'est en fait un type composé de plusieurs champs de types plus simples. L'avantage c'est qu'en déclarant une seule variable de type structure, on déclare tous les champs qui la composent en une fois.
// s1.c from std use console; struct PRODUCT { int nr; char[4] code; float price; } void main() { PRODUCT rice; rice = { nr => 1, code => "rice", price => 12.5}; printf ("My product is : %d, %s, %f\n", rice.nr, rice.code, rice.price); }
ce qui donne :
My product is : 1, rice, 12.500000
L'exemple suivant déclare un tableau de structure 'PRODUCT'. Vous remarquez que la syntaxe pour accéder à un élément du tableau est "[indice]", tandis que la syntaxe pour accéder à un champ de la structure est ".champ"
"product[1].price" combine les deux.
// s2.c from std use console; struct PRODUCT { int nr; char[4] code; float price; } const int MAX_PRODUCTS = 3; PRODUCT product[MAX_PRODUCTS]; void main() { int i; clear product; product[0] = {1, "rice", 12.5}; product[1] = {2, "appl", 8.0}; product[2] = {3, "citr", 4.05}; for (i=0; i<product'length; i++) printf ("product[%d] is : %d, %s, %f\n", i, product[i].nr, product[i].code, product[i].price); printf ("we change the price of appl to 100 !\n"); product[1].price = 100.0; for (i=0; i<product'length; i++) printf ("product[%d] is : %d, %s, %f\n", i, product[i].nr, product[i].code, product[i].price); }
ce qui donne :
product[0] is : 1, rice, 12.500000 product[1] is : 2, appl, 8.000000 product[2] is : 3, citr, 4.050000 we change the price of appl to 100 ! product[0] is : 1, rice, 12.500000 product[1] is : 2, appl, 100.000000 product[2] is : 3, citr, 4.050000
L'exemple ci-dessous déclare une structure GEOMETRY paramétrisable. En effet, vous remarquerez dans sa déclaration un paramètre (KIND kind) ainsi que le mot-clé "switch". Les champs effectifs de cette structure dépendent de la valeur du paramètre KIND qui est donné lors de la création de la variable. Quand une variable de type GEOMETRY est créée, son discriminant (kind) est figé, ses champs sont définis, et sa taille est calculée, et tous les trois ne changeront plus.
On aurait pu écrire ce programme en créant 4 structures différentes (pour circle, triangle, rectangle, point). L'avantage de rassembler ces 4 formes géométriques dans la même structure est qu'on peut passer le type GEOMETRY par exemple à une fonction d'affichage commune (telle que display) ou déclarer des pointeurs vers cette structure commune.
Quand vous utilisez un champ déclaré dans la partie switch, le langage vérifie toujours strictement que le discriminant a une valeur qui fait que le champ existe.
// sp1.c from std use console; enum KIND {CIRCLE, TRIANGLE, RECTANGLE, POINT}; struct GEOMETRY (KIND kind) { float x, y; switch (kind) { case CIRCLE: float radius; case TRIANGLE: float base; float height; case RECTANGLE: float width; float height2; case POINT: null; // utilisez le mot-clé null quand il n'y a aucun champ } } void display (GEOMETRY g) { printf ("type : %s\n", g.kind'string); printf ("position : (%f,%f)\n", g.x, g.y); switch (g.kind) { case CIRCLE: printf ("radius = %f\n", g.radius); break; case TRIANGLE: printf ("base = %f, height = %f\n", g.base, g.height); break; case RECTANGLE: printf ("dimensions = %f x %f\n", g.width, g.height2); break; default: break; } printf ("\n"); } void main() { const GEOMETRY my_circle(CIRCLE) = {x => 1.0, y => 2.0, radius => 0.5}; const GEOMETRY my_point(POINT) = {x => 1.0, y => 2.0}; GEOMETRY^ my_triangle = new GEOMETRY(TRIANGLE) ' {x => 1.0, y => 2.0, base => 1.0, height => 3.0}; display (my_circle); display (my_point); display (my_triangle^); free my_triangle; }
ce qui donne :
type : CIRCLE position : (1.000000,2.000000) radius = 0.500000 type : POINT position : (1.000000,2.000000) type : TRIANGLE position : (1.000000,2.000000) base = 1.000000, height = 3.000000
Sur la page principale du site Safe-C, vous pouvez trouver une liste complète des composants de la libraire standard std.lib avec leur contenu.
Pour ne pas vous noyer, nous allons simplement survoler rapidement les composants les plus courants, afin que vous sachiez au moins qu'ils existent. Si vous ne trouvez ce que vous cherchez, n'hésitez pas à consulter la liste complète sur la page principale de ce site.
Pour utiliser un composant, il suffit de placer une clause 'from std use nom_du_composant;' au début de votre programme. Il faut que le fichier de la librairie standard (std.lib) soit dans le dossier avec le compilateur.
Exemple:
// p.c from std use win; void main() { message_box (title => "Ma message box", message => "Veuillez cliquez sur Ok", button => " Ok "); }
void printf (string format, object[] arg); int scanf (string format, out object[] arg);
int strcpy (out string dest, string src); int strncpy (out string dest, string src, int len); int strcat (ref string dest, string src); int strlen (string s); int strcmp (string a, string b); int stricmp (string a, string b); int strncmp (string a, string b, int maxlen); int strnicmp (string a, string b, int maxlen); int memcmp (byte[] a, byte[] b); int strchr (string s, char c); int strrchr (string s, char c); int strstr (string text, string fragment); int stristr (string text, string fragment); int sprintf (out string buffer, string format, object[] arg); int sscanf (string buffer, string format, out object[] arg); int strcatf (ref string buffer, string format, object[] arg); char tolower (char c); char toupper (char c); bool isalpha (char c); // A-Z a-z bool isdigit (char c); // 0-9 bool isxdigit (char c); // 0-9 A-F a-f int wstrcpy (out wstring dest, wstring src); int wstrncpy (out wstring dest, wstring src, int len); int wstrcat (ref wstring dest, wstring src); int wstrlen (wstring s); int wstrcmp (wstring a, wstring b); int wstricmp (wstring a, wstring b); int wstrncmp (wstring a, wstring b, int maxlen); int wstrnicmp (wstring a, wstring b, int maxlen); int wstrchr (wstring s, wchar c); int wstrrchr (wstring s, wchar c); int wstrstr (wstring text, wstring fragment); int wstristr (wstring text, wstring fragment); int wsprintf (out wstring buffer, wstring format, object[] arg); int wsscanf (wstring buffer, wstring format, out object[] arg); int wstrcatf (ref wstring buffer, wstring format, object[] arg); wchar wtolower (wchar c); wchar wtoupper (wchar c); bool wisalpha (wchar c); // A-Z a-z bool wisdigit (wchar c); // 0-9 bool wisxdigit (wchar c); // 0-9 A-F a-f
struct DATE_TIME; void get_datetime (out DATE_TIME datetime); int max_days_in_month (int month, int year); bool is_valid_date (int day, int month, int year); int day_of_week (int day, int month, int year); int nb_days_since_1901 (int day, int month, int year); void add_days (ref DATE_TIME date, int nb_days); void add_seconds (ref DATE_TIME date, long nb_secs); int get_time_zone ();
// basic functions double fabs (double x); // absolute value double sqrt (double x); // square root, with x >= 0. double round (double x); // rounds to nearest integer double ceil (double x); // rounds up double floor (double x); // rounds down double fmin (double a, double b); // minimum double fmax (double a, double b); // maximum double fmin3 (double a, double b, double c); // minimum of 3 values double fmax3 (double a, double b, double c); // maximum of 3 values // trigonometric functions const double PI = 3.141592653589793238; double pi(); // pi in internal 66-bit precision double sin (double x); double cos (double x); double tan (double x); // same as sin(x)/cos(x), with cos(x) != 0. double asin (double x); // with x in [-1..+1] double acos (double x); // with x in [-1..+1] double atan (double x); double atan2 (double y, double x); // valid for all values of x and y. // exponential, logarithm and power functions double exp (double x); // e exponent x double ln (double x); // log e, with x > 0 double log2 (double x); // log 2, with x > 0 double log10 (double x); // log 10, with x > 0 double pow (double base, double exponent); // base to the power of exponent // hyperbolic functions double sinh (double x); double cosh (double x); double tanh (double x); double asinh (double x); double acosh (double x); double atanh (double x); // infinite test bool isINF (double x);
int abs (int a); // valeur absolue int min (int a, int b); // minimum int max (int a, int b); // maximum int min3 (int a, int b, int c); int max3 (int a, int b, int c); uint umin (uint a, uint b); uint umax (uint a, uint b); uint umin3 (uint a, uint b, uint c); uint umax3 (uint a, uint b, uint c); long labs (long a); long lmin (long a, long b); long lmax (long a, long b); long lmin3 (long a, long b, long c); long lmax3 (long a, long b, long c); int ilog2 (int a); // logarithme base 2, a > 0 uint ulog2 (uint a); // logarithme base 2, a > 0
int rnd (int first, int last); // nombre au hasard entre first et last void get_random_number (out byte[] value);
// voir chapitre 6 : Les crashs et le tracing void arm_exception_handler (bool create_crash_report_file = true, bool display_fatal_error_message_box = true);
// voir chapitre 6 : Les crashs et le tracing void open_trace (string filename, // for example "trace.tra" long max_file_size = 64*1024*1024, // default is 64 MB bool date = true, bool time = true, bool msec = false); void trace (string format, object[] arg); void trace_block (byte[] block); void close_trace ();
// voir chapitre 5 : les tableaux package HeapSort package QuickSort package BubbleSort
//---------------------------------------------------------------------------- // Text Files : functions for reading or writing buffered text files. //---------------------------------------------------------------------------- struct FILE; // the same FILE must not be used by several threads at the same time. enum ENCODING {ANSI, UTF8, UTF16, UTF16BE}; int fopen (out FILE file, string filename); int fcreate (out FILE file, string filename, ENCODING encoding); int fappend (out FILE file, string filename, ENCODING encoding); const int FEOF = +1; // return value that indicates end of file int fgetc (ref FILE file, out char c); int wfgetc (ref FILE file, out wchar c); int fputc (ref FILE file, char c); int wfputc (ref FILE file, wchar c); int fgets (ref FILE file, out string buffer); int wfgets (ref FILE file, out wstring buffer); int fputs (ref FILE file, string buffer); int wfputs (ref FILE file, wstring buffer); int fscanf (ref FILE file, string format, out object[] arg); int wfscanf (ref FILE file, wstring format, out object[] arg); int fprintf (ref FILE file, string format, object[] arg); int wfprintf (ref FILE file, wstring format, object[] arg); bool feof (ref FILE file); int fflush (ref FILE file); int fclose (ref FILE file); int fhandle (ref FILE file); //---------------------------------------------------------------------------- // Binary Files : functions that operate on low-level binary files. //---------------------------------------------------------------------------- int create (string filename, uint access = WRITE, uint share = READ, ORGANIZATION organization = UNDEFINED); int open (string filename, uint access = READ, uint share = READ, ORGANIZATION organization = UNDEFINED); int read (int handle, out byte[] buffer); int write (int handle, byte[] buffer); long lseek (int handle, long offset, SEEK_MODE mode = SEEK_SET); long filesize (int handle); void get_ftime (int handle, out calendar.DATE_TIME time); // get last write time. void set_ftime (int handle, calendar.DATE_TIME time); // set create, last access, last write time. int lock_file (int handle, LOCK lock, long offset, long nb_bytes); int unlock_file (int handle, LOCK lock, long offset, long nb_bytes); int flush (int handle); int close (int handle); int move_file (string source, string target); int copy_file (string source, string target, bool overwrite = true); int delete_file (string filename); void split_pathname ( string pathname, out string device, out string directory, // starts with / or \ if absolute path out string filename); // does not contain any / or \ int expand_pathname ( string current_directory, string source_filename, out string result_filename); void normalize_pathname (ref string pathname); bool name_matches_pattern (string name, string pattern, char replace_one = '?', char replace_many = '*'); int select_filename ( string dialog_title, string mask, bool saving, // false=load, true=save out char filename[MAX_FILENAME_LENGTH], string default_filename = "", uint hwnd = 0); // parent window handle //---------------------------------------------------------------------------- // Directories //---------------------------------------------------------------------------- struct FILE_INFO; int open_directory (out FILE_INFO info, string directory_name); int read_directory (ref FILE_INFO info); int close_directory (ref FILE_INFO info); int create_directory (string directory_name); int remove_directory (string directory_name); void get_current_directory (out char directory_name[MAX_FILENAME_LENGTH]); int set_current_directory (string directory_name); //---------------------------------------------------------------------------- // Disk Volumes //---------------------------------------------------------------------------- long free_disk_space (string directory_name); // any directory on the disk
// Part 1 : image compresssion/decompression enum IMAGE_FILE_FORMAT {FORMAT_BMP, FORMAT_JPEG, FORMAT_GIF, FORMAT_PNG, FORMAT_TIFF}; struct IMAGE; int create_image (out IMAGE image, string filename, uint width, uint height, IMAGE_CREATION_PARAMETERS parameters); int open_image (out IMAGE image, string filename); void get_image_size ( IMAGE image, out uint width, out uint height); void get_image_attributes (IMAGE image, out IMAGE_ATTRIBUTES attr); void set_image_options (IMAGE image, IMAGE_OPTIONS opt); int read_image (IMAGE image, out byte[] buffer); int next_image (IMAGE image); int write_image (IMAGE image, byte[] buffer); void multiply_rgb_by_alpha (ref byte[] buffer); int close_image (ref IMAGE image); // Part 2 : RGBA bitmap manipulations int load_image (out IMAGE_INFO info, string filename); int save_image (IMAGE_INFO info, string filename, /* output file */ IMAGE_CREATION_PARAMETERS param); /* output parameters */ void free_image (ref IMAGE_INFO info); int copy_image (IMAGE_INFO source, out IMAGE_INFO target); int bestfit_size (ref IMAGE_INFO info, ref uint width, /* in out ! */ ref uint height); /* in out ! */ int resize_image (ref IMAGE_INFO info, uint width, uint height, uint border_color = 0xFF000000); // opaque black int resize_image2 ( IMAGE_INFO info, uint width, uint height, uint border_color = 0xFF000000, // opaque black out IMAGE_INFO result); int zoom_image ( IMAGE_INFO source, /* source image */ uint click_x, /* zoom x-center on screen */ uint click_y, /* zoom y-center on screen */ int zoom, /* in percent (100 .. 6_500_000) */ /* use 100 for first call of new image ! */ uint width, /* target screen width */ uint height, /* target screen height */ ref CLIP_INFO source_clip, /* clear for first call, value must be kept between calls */ ref CLIP_INFO target_clip, /* clear for first call, value must be kept between calls */ uint border_color, /* =0 */ out IMAGE_INFO target); /* result image for screen */ int rotate_image (ref IMAGE_INFO info, int angle, uint border_color = 0xFF000000); // opaque black int rotate_image2 ( IMAGE_INFO info, int angle, uint border_color = 0xFF000000, // opaque black out IMAGE_INFO result); int copy_image_rectangle ( IMAGE_INFO source_image, CLIP_INFO source_clip, out IMAGE_INFO target_image); int compute_bestfit_target_clip ( CLIP_INFO source_clip, CLIP_INFO target_clip, out CLIP_INFO bestfit_target_clip); void reverse_clip_coordinates ( uint x, uint y, CLIP_INFO source_clip, CLIP_INFO target_clip, out uint out_x, out uint out_y); int compute_zoom_clip_info ( uint center_x, /* within source */ uint center_y, /* within source */ int zoom_factor, /* in percent (100 .. 6_500_000) */ ref CLIP_INFO source_clip, /* in out */ ref CLIP_INFO target_clip); /* in out */ int stretch_image (IMAGE_INFO source, CLIP_INFO source_clip, IMAGE_INFO target, CLIP_INFO target_clip);
struct DRAW_CONTEXT; void init_draw (out DRAW_CONTEXT dc, byte[]^ buffer, /* -> (4*width*height) bytes */ uint width, uint height); void draw_line (ref DRAW_CONTEXT dc, int x1, int y1, int x2, int y2, uint color, /* = rgb(red,green,blue) */ int thick); /* >= 1 */ void draw_circle (ref DRAW_CONTEXT dc, int x, int y, int radius, uint color, int thick); void draw_solid_circle (ref DRAW_CONTEXT dc, int x, int y, int radius, uint color); int draw_text (ref DRAW_CONTEXT dc, string text, /* string to display */ string font_name, /* ex: "Times New Roman" */ int font_height, /* ex: 12 */ int x, /* lower left corner */ int y, uint color, uint style); /* = 0 */ int draw_wtext (ref DRAW_CONTEXT dc, wstring text, /* string to display */ string font_name, /* ex: "Times New Roman" */ int font_height, /* ex: 12 */ int x, /* lower left corner */ int y, uint color, uint style); /* = 0 */ void draw_image (ref DRAW_CONTEXT dc, int x, /* target (upper-left corner) */ int y, byte[] image, uint width, uint height, uint clip_x, /* =0 for all image */ uint clip_y, /* =0 for all image */ uint clip_size_x, /* =width for all image */ uint clip_size_y, /* =height for all image */ uint flags = 0); void set_draw_origin (ref DRAW_CONTEXT dc, int ox, int oy, int dx, /* direction of x-axis */ int dy); /* direction of y-axis */ void set_draw_clip (ref DRAW_CONTEXT dc, int min_x, int min_y, int max_x, int max_y);
void message_box (string title, string message, string button); void create_screen (int x, int y, int x_size, int y_size, string title); void set_screen_title (string title); void close_screen (); void create_text (int x, int y, int x_size, int y_size, OBJECT_ID id); void text_put (string text, OBJECT_ID id); void create_edit (int x, int y, int x_size, int y_size, int length, OBJECT_ID id); void edit_get (out string buffer, OBJECT_ID id); void create_checkbox (int x, int y, int x_size, int y_size, int box_width, string text, OBJECT_ID id); void checkbox_get (out bool setting, OBJECT_ID id); void create_listbox (int x, int y, int x_size, int y_size, int arrow_box_width, int arrow_box_height, int length, OBJECT_ID id); void listbox_insert (int index, // 0 = append to end string text, OBJECT_ID id); void create_button (int x, int y, int x_size, int y_size, string text, OBJECT_ID id); void get_event (out EVENT event); // ....
Exemple:
// p.c from std use win; void main() { int left, top, right, bottom; bool quit = false; get_screen_borders (out left, out top, out right, out bottom); create_screen (x => 100, y => 100, x_size => left+right+300, y_size => top+bottom+200, title => "Test"); create_button (x => 100, y => 100, x_size => 100, y_size => 30, text => "Click me", id => 10); while (!quit) { EVENT event; get_event (out event); switch (event.typ) { case EVENT_BUTTON_PRESSED: switch (event.id) { case 10: message_box (title => "Ma message box", message => "Merci pour votre click !", button => " Ok "); quit = true; break; default: break; } break; default: break; } } close_screen (); }
L'utilitaire makelib.exe vous permet de créer votre propre librarie. Pour cela, placer vos fichiers .h et .c dans un répertoire (par exemple dans C:\LIB) et tapez makelib mylib C:\LIB\. Un fichier mylib.lib sera alors créé, ajoutez-le dans le dossier des fichiers du compilateur.
Importez-le dans votre programme avec from mylib use votre_composant; où votre_composant correspond au nom de votre fichier .h
Dans des cas rares, il peut arriver que deux composants de libraries différentes portent le même nom. Il est alors possible d'en importer un en le renommant avec une clause as :
from std use math; from mylib use math as math2;
Un package est une enveloppe qui sert à grouper une série de déclarations.
Remarquez les mots-clés package pour déclarer un package et end pour le terminer.
Exemple:
package Stack const int MAX = 1000; void push (int i); int pop (); end Stack;
Dans l'exemple ci-dessus, vu que le package contient des déclarations de fonctions, on doit aussi déclarer un corps de package pour y déclarer les corps des fonctions correspondantes.
Remarquez les mots-clés package body pour déclarer un corps de package.
package body Stack int table[MAX]; int index = 0; public void push (int i) { table[index++] = i; } public int pop () { return table[--index]; } end Stack;
Les déclarations à l'intérieur du package sont accessibles depuis l'extérieur. Par contre les déclarations à l'intérieur du corps de package sont privées et non-accessibles depuis l'extérieur.
On peut déclarer des packages dans des fichiers .h ou .c, les éventuels corps de package sont uniquement autorisés dans les .c.
Notez qu'on peut imbriquer des packages.
Un corps de package est obligatoire si le package contient des déclarations incomplètes comme des types opaques ou des déclarations de fonctions.
Voici un exemple complet pour un package Stacks qui permet d'empiler des valeurs :
from std use console; package Stacks struct STACK; // an opaque type void push (ref STACK s, int i); int pop (ref STACK s); end Stacks; package body Stacks const int MAX = 1000; struct STACK { int table[MAX]; int index; } public void push (ref STACK s, int i) { s.table[s.index++] = i; } public int pop (ref STACK s) { return s.table[--s.index]; } end Stacks; void main() { STACK s; clear s; push (ref s, 12); printf ("%d\n", pop(ref s)); }
Cet exemple peut aussi être placé dans 3 fichiers différents :
// stacks.h package Stacks struct STACK; // an opaque type void push (ref STACK s, int i); int pop (ref STACK s); end Stacks;
// stacks.c package body Stacks const int MAX = 1000; struct STACK { int table[MAX]; int index; } public void push (ref STACK s, int i) { s.table[s.index++] = i; } public int pop (ref STACK s) { return s.table[--s.index]; } end Stacks;
// prog.c from std use console; use stacks; void main() { STACK s; clear s; push (ref s, 12); printf ("%d\n", pop(ref s)); }
Notez qu'on peut placer plusieurs packages dans le même fichier source. Dans ce cas, il peut se produire des ambiguités de noms que l'on peut résoudre en préfixant le nom du package au nom, comme dans cet exemple :
package A const int MAX = 100; end A; package B const int MAX = 200; end B; void main() { int i; i = MAX; // ERREUR: MAX est ambigu vu qu'il est déclaré deux fois i = A.MAX; // OK (on a préfixé le nom du package) i = B.MAX; // OK }
Un package générique permet de faire un copier-coller de lignes de programme. Chaque copie sera légèrement différente car le package générique comporte divers paramètres de type et de fonction que l'on donne lors de la copie.
Voici un exemple d'un package générique BubbleSort qui permet de trier n'importe quel tableau. Le package générique va comporter deux paramètres :
. d'abord un type "ELEMENT" qui pourra être remplacé par n'importe quel type. C'est le type des éléments du tableau à trier.
. ensuite une fonction "compare" qui prend deux ELEMENT et renvoie un int. Cette fonction permet au package de connaitre l'ordre de deux éléments.
A l'intérieur du package "BubbleSort" il n'y a qu'une fonction "sort" qui traite un paramètre tableau de type ELEMENT en utilisant les deux paramètres fournis.
// bubble.h generic <ELEMENT> // generic type ELEMENT int compare (ELEMENT a, ELEMENT b); // return -1 if a<b, 0 if a==b, +1 if a>b package BubbleSort void sort (ref ELEMENT table[]); end BubbleSort;
Voici le corps du package. Vous voyez qu'il utilise ELEMENT et compare.
// bubble.c package body BubbleSort public void sort (ref ELEMENT table[]) { int i, j; ELEMENT temp; for (i=1; i<table'length; i++) { for (j=i; j>0; j--) { if (compare (table[j-1], table[j]) <= 0) break; temp = table[j-1]; table[j-1] = table[j]; table[j] = temp; } } } end BubbleSort;
Comment utiliser le package générique ? Pour faire un copier-coller (qu'on appelle "instanciation" chez les pros), on utilise la syntaxe suivante qui va créer une copie du package BubbleSort en replaçant les deux paramètres. Le package après copie s'appelera "Sort_int" parce qu'il trie les tableaux de "int".
package Sort_int = new BubbleSort (ELEMENT => int, compare => compare_int);
Mais d'où provient la fonction compare_int ? et bien vous devez l'écrire vous-même car le package de tri ne connait pas votre type donc à priori il ne sait pas comment comparer deux éléments.
Voici l'exemple complet d'instanciation :
// p.c use bubble; int compare_int (int a, int b) { if (a < b) return -1; if (a > b) return +1; return 0; } package Sort_int = new BubbleSort (ELEMENT => int, compare => compare_int); void main() { int table[5] = {2, 19, 3, 9, 4}; sort (ref table); // à écrire Sort_int.sort si ambigu }
Vous voyez que les packages génériques sont un peu lourds syntaxiquement. Ils sont cependant indispensables afin de pouvoir écrire des algorithmes généraux de tri et autres manipulations de structures de données avec une vitesse d'exécution et une sécurité d'accès mémoire maximales.
La seule manière de les éviter serait de "copier-coller" des lignes de programme
manuellement en l'adaptant pour chaque type, ce qui n'est pas vraiment souhaitable.
Le composant de librarie standard files permet un accès bas-niveau aux fichiers.
Si vous examinez les fonctions qui permettent de lire et d'écrire un fichier, vous allez constater qu'ils n'acceptent que des paramètres de type "tableau de byte" :
int read (int handle, out byte[] buffer); int write (int handle, byte[] buffer);
Comment faire alors pour écrire un int, un float, une structure dans un fichier ?
Et bien c'est facile car en Safe-C presque tous les types sont convertibles automatiquement en tableaux de bytes. Vous pouvez donc écrire ceci :
from std use files, strings; void main() { int fd; float f; char buffer[80]; bool b; fd = create ("myfile.data"); f = 3.14; write (fd, f); // <-- conversion automatique float vers byte[] strcpy (out buffer, "Hello"); write (fd, buffer); // <-- conversion automatique char[] vers byte[] b = true; write (fd, b); // <-- conversion automatique bool vers byte[] close (fd); }
Quels sont les types qui ne sont pas convertibles en tableaux de byte ?
Les structures, tout d'abord, ne sont convertibles que si elles sont précédées du mot-clé packed ce qui indique qu'elles n'ont pas de trous d'alignement entre leur champs.
Voici comment écrire une structure dans un fichier :
from std use files; void main() { int fd; packed struct REC // <--- mot-clé "packed" obligatoire ! { char name[4]; float f; bool b; } REC rec; fd = create ("myfile.data"); rec = {"ABCD", 1.234, true}; write (fd, rec); // <-- conversion automatique REC vers byte[] close (fd); }
Evidemment quand on préfixe le mot-clé packed a une structure, celle-ci ne peut contenir que des champs simples ou également packed.
En outre, la structure ne peut pas contenir des types pointeurs.
Vous devez retenir que packed et pointeurs, cela ne se mélange pas.
Il n'y a aucun intérêt à écrire des pointeurs sur le disque,
et c'est même dangereux d'en lire parce qu'ils ne sont sans doute
plus valides. Pour cette raison, les libraries standards ne permettent
ni de lire ni d'écrire des valeurs de type pointeur sur le disque, sur le réseau
ou autre entrée-sortie (notez qu'il y a moyen de le faire quand même
avec une section de code unsafe).
L'attribut 'size s'applique à tous les types packed et permet de calculer le nombre de bytes d'un type. Attention que 'size renvoie un uint (entier non-signé) que vous devrez éventuellement convertir en int.
Dans cet exemple on va vérifier que read() lit le nombre de bytes corrects. Il faudrait faire la même vérification lors d'un write() dans les exemples ci-dessus, nous ne l'avons pas fait pour des raisons de concision.
from std use console, files; void main() { int fd; packed struct REC { char name[4]; float f; bool b; } REC rec; fd = open ("myfile.data"); if (read (fd, out rec) != (int)rec'size) // <-- attribut size { printf ("error: cannot read file\n"); close (fd); return; } close (fd); }
L'attribute 'byte permet de convertir n'importe quel type packed en tableau de bytes. Cela permet de copier, par exemple, les 4 bytes d'un float dans les 4 bytes d'un int :
float f = 1.2; int i; i'byte = f'byte;
ou même de copier les 2 derniers bytes d'un float dans un tableau de deux bytes :
float f = 1.2; byte b[2]; b = f'byte[2:2];
En Safe-C, le type object est prédéfini comme tableau de byte. Donc partout où on écrit "byte [ ]" on pourrait écrire "object".
Pour des raisons de clarté on utilise surtout le type 'object' dans les déclarations de fonctions à nombre variable de paramètre. Ainsi, printf est défini comme :
void printf (string format, object[] arg);
Le Safe-C applique la règle suivante : si le dernier paramètre d'une fonction est de type "object [ ]", alors la fonction accepte un nombre variable de paramètres qui doivent tous être packed, donc convertibles en tableaux de byte.
A l'intérieur de la fonction, ces paramètres sont réceptionnés comme un tableau de tableau de bytes.
Exemple:
from std use console; void affichage (int test, object[] par) { int i, j; printf ("\n"); printf ("test %d\n", test); for (i=0; i<par'length; i++) { printf ("parametre %d : ", i+1); for (j=0; j<par[i]'length; j++) printf ("%u ", par[i][j]); printf ("\n"); } } void main() { affichage (1); affichage (2, 1.1275); affichage (3, (double)1.1275); affichage (4, "ABCD", 'G', 1.1275); affichage (5, (tiny)3); affichage (6, 3); affichage (7, 3L); affichage (8, L"Hello"); }
va afficher :
test 1 test 2 parametre 1 : 236 81 144 63 test 3 parametre 1 : 10 215 163 112 61 10 242 63 test 4 parametre 1 : 65 66 67 68 parametre 2 : 71 parametre 3 : 236 81 144 63 test 5 parametre 1 : 3 test 6 parametre 1 : 3 0 0 0 test 7 parametre 1 : 3 0 0 0 0 0 0 0 test 8 parametre 1 : 72 0 101 0 108 0 108 0 111 0
Dans les exemples des chapitres précédents, chaque variable d'un programme porte un nom, chaque variable tableau a une taille constante, et chaque variable existe soit pour toute la durée du programme soit tant que la fonction à l'intérieur de laquelle elle est déclarée est active.
Avec les pointeurs, on peut s'affranchir de ces contraintes et créer des variables anonymes, des tableaux de taille variable, et des variables dont on contrôle précisément la durée de vie.
L'exemple p1.c montre la déclaration d'une variable p qui est un pointeur vers un tableau de int (notation int[ ]^, lire de droite à gauche).
Grâce à l'allocateur 'new', on réserve de la mémoire pour un tableau de int anonyme de "count" éléments, et on sauve dans le pointeur p l'adresse mémoire qui permet d'y accéder.
Pour accéder à notre tableau anonyme, on utilise la notation p^[i]. On part du pointeur "p", le symbole "^" permet de suivre le pointeur et d'accéder au tableau, et finalement "[i]" permet d'accéder à l'élément de tableau souhaité. Le résultat est alors une variable int.
La notation "p^'length" permet de déterminer le nombre d'élements du tableau. C'est simplement l'attribut 'length appliqué au tableau p^.
Quand vous n'avez plus besoin du tableau, il ne faut pas oublier de libérer sa mémoire avec l'instruction free sinon vous finiriez par consommer toute la mémoire disponible.
// p1.c from std use console, math; void main() { int count, i; int[]^ p; printf ("Please enter the number of elements : "); scanf (" %d", out count); p = new int[count]; for (i=0; i<p^'length; i++) p^[i] = (int)(100.0*sin(100.0 * (float)i / 180.0 * PI)); for (i=0; i<p^'length; i++) printf ("p^[%d] = %d\n", i, p^[i]); free p; }
Please enter the number of elements : 10 p^[0] = 0 p^[1] = 98 p^[2] = -34 p^[3] = -86 p^[4] = 64 p^[5] = 64 p^[6] = -86 p^[7] = -34 p^[8] = 98 p^[9] = 0
L'exemple p2.c déclare une structure NODE composée d'un int et d'un pointeur vers NODE lui-même. On peut s'en servir pour créer une chaine de variables NODE qui pointent chacune sur le noeud suivant, le dernier noeud pointant sur null.
La valeur constante "null" est utilisée pour indiquer que l'on ne pointe vers rien du tout.
Remarquez ici qu'avec une seule variable "p", on peut accéder à toute une liste de variables anonymes chainées ensembles par des pointeurs. Cette chaine est parcourue pour afficher tous les noeuds (avec display), ou pour les libérer (avec free_list).
// p2.c from std use console; struct NODE { int n; NODE^ next; } void display (NODE^ ptr) { NODE^ p = ptr; while (p != null) { printf ("%d ", p^.n); p = p^.next; } } void free_list (NODE^ ptr) { NODE^ p = ptr; NODE^ q; while (p != null) { q = p; p = p^.next; free q; } } void main() { NODE^ p; p = new NODE ' {17, null}; p = new NODE ' {12, p}; p = new NODE ' {8, p}; p = new NODE ' {3, p}; display (p); free_list (p); }
Ce qui donne :
3 8 12 17
L'allocateur "new" permet de spécifier une valeur initiale pour la variable anonyme créée en utilisant soit la notation apostrophe et aggrégat si on veut spécifier tous les champs :
NODE^ p; p = new NODE ' {17, null};
ou bien apostrophe et parenthèse pour une valeur de même type que le noeud créé :
NODE n; p = new NODE ' (n);
Si on ne spécifie pas de valeur initiale, la variable anonyme sera mise à zéro :
p = new NODE;
Il peut arriver que l'on souhaite déclarer plusieurs structures dont les pointeurs se référencent mutuellement. Il faut alors pré-déclarer l'une des structures comme un type "incomplet", avant de le redéclarer une 2ème fois plus bas, complètement cette fois-ci.
typedef NODE2; // type incomplet struct NODE1 { int count; NODE1^ up; NODE2^ gauche, droite; // utilise le type incomplet } struct NODE2 // redéclaration complète { int count; NODE1^ gauche, droite; }
Pour pouvoir écrire des librairies standard qui appellent le système d'exploitation, il est parfois nécessaire d'utiliser les anciens pointeurs non-sûrs du C.
Afin de baliser cet usage dangereux, le Safe-C n'autorise l'utilisation des pointeurs unsafe que dans une section unsafe.
Exemple, la fonction read() de files.c :
#begin unsafe [extern "KERNEL32.DLL"] int ReadFile ( int hFile, byte *buffer, uint size, int *BytesRead, byte *lpOverlapped); // returns nb of bytes read (>=0), or a negative error code. public int read (int handle, out byte[] buffer) { int read; if (ReadFile (handle, &buffer, buffer'size, &read, null) != 0) return read; return -GetLastError(); } #end unsafe
Dans une section unsafe (entre #begin unsafe et #end unsafe), vous pouvez utiliser les pointeurs du C avec ses opérateurs (cast), & * -> ++ -- != == < > <= >=.
Vous pouvez déclarer des fonctions qui se trouvent dans des DLL externes ou des fonctions callback que Windows peut appeler.
#begin unsafe [callback] int MainWin (HWND hwnd, UINT message, WORD w, LONG l) { return 0; } #end unsafe
Par ailleurs les pointeurs unsafe sont considérés comme des types packed donc convertibles en tableau de byte.
La constante null indique la valeur pointeur zéro.
Les ressources sont des fichiers ajoutés à un exécutable windows. Ils peuvent contenir par exemple :
Pour créer des icônes, utilisez les outils fournis par Microsoft.
Un petit outil extract.exe est fourni avec ce compilateur afin d'extraire toutes les ressources
d'un .EXE windows. Une fois extraits, vous pouvez les ajouter à votre exécutable.
L'outil extract.exe va créer un fichier correspondant à chaque resource, et il va afficher
le texte de définition des ressources sur la console.
Il vous suffit de créer avec notepad un fichier .RC de même nom que votre exécutable contenant
le texte de définition, et de placer les fichiers dans le répertoire courant, pour que les ressources
soient ajoutées à votre .EXE quand vous compilez votre programme.
A partir de la version Vista de Windows, certains noms de programmes (par exemple ceux contenant le mot 'install') sont automatiquement démarrés en mode administrateur (l'utilisateur doit donner son mot de passe).
Pour éviter cela, on peut inclure un manifeste au programme. Voici le manifeste.
Il vous suffit de créer un fichier .RC et d'y indiquer la ligne suivante :
1 24 manifest.txt
Si vous avez tout bien fait, le programme ne demandera plus de mot de passe au démarrage.
***
Voici un autre manifeste2 qui combine :
1) ne pas démarrer en mode Administrateur, et
2) inclure les contrôles windows standard (listbox, edit, ..).
Voici un modèle pour créer un serveur internet tout simple :
// iserv1.c : internet server from std use strings, tcpip, http, tracing, exception; //----------------------------------------------------------------------- HTTP_SERVER_DATA iserv; HTTP_INITIAL_DATA init; //----------------------------------------------------------------------- void handler (ref HANDLER_DATA p) { handle_http_call (ref p, iserv, ref init); } //----------------------------------------------------------------------- void main() { int rc; arm_exception_handler(); open_trace ("iserv1.tra"); // init internet server strcpy (out iserv.html_directory, "c:/www/"); strcpy (out iserv.start_url, "/home.html"); iserv.tracing = true; // start server trace ("============================================\n"); trace ("info: server starting.\n"); rc = serve_requests (port => 80, request_handler => handler, max_handlers => 512, mode => MODE_DUAL, stop => null, server_is_ready => null, incoming_call => null, trace_calls => true); trace ("info: server stopping. rc=%d\n", rc); trace ("============================================\n"); }
Pour tester ce programme, créez tout d'abord un répertoire C:\WWW et placez-y quelques fichiers html (par exemple mapage.html).
Lancez ensuite le serveur, puis démarrez Internet Explorer et tapez http://127.0.0.1/mapage.html Votre page devrait alors d'afficher.
Remarquez que vous pouvez aussi taper http://[::1]/mapage.html car le serveur supporte IPV6.
Le serveur crée un fichier de trace (iserv1.tra) que vous pouvez ouvrir avec notepad
pour voir les actions du serveur.
Voici comment créer des pages virtuelles, c'est-à-dire des pages qui
n'existent pas sur le disque dur mais que vous créez dynamiquement.
// iserv2.c : internet server from std use strings, tcpip, http, tracing, exception; //----------------------------------------------------------------------- HTTP_SERVER_DATA iserv; HTTP_INITIAL_DATA init; const uint WRITE_TIMEOUT = 30; // write timeout : 30 secs //----------------------------------------------------------------------- bool is_virtual_page (int nr, TCP_CLIENT tcp_client, string url, // virtual url ARG_INFO args) // http arguments { _unused nr; // suppresses warning that nr is unused _unused tcp_client; trace ("is_virtual_page (%s)\n", url); return !get_http_physical_file_exists (args); } //----------------------------------------------------------------------- void handle_virtual_page (int nr, ref TCP_CLIENT tcp_client, string url, // virtual url ARG_INFO args) // http arguments { char str[1000]; _unused nr; _unused args; trace ("handle_virtual_page (%s)\n", url); sprintf (out str, "<HTML><HEAD><TITLE>MY TITLE</TITLE></HEAD>\r\n"); strcat (ref str, "<BODY>"); strcat (ref str, "Bonjour! Cette page est virtuelle !<br>"); strcat (ref str, "</BODY></HTML>"); CS_write (ref tcp_client, str[0:strlen(str)], WRITE_TIMEOUT); } //----------------------------------------------------------------------- void handler (ref HANDLER_DATA p) { handle_http_call (ref p, iserv, ref init); } //----------------------------------------------------------------------- void main() { int rc; arm_exception_handler(); open_trace ("iserv2.tra"); // init internet server strcpy (out iserv.html_directory, "c:/www/"); strcpy (out iserv.start_url, "/home.html"); iserv.is_virtual_page = is_virtual_page; iserv.handle_virtual_page = handle_virtual_page; iserv.tracing = true; // start server trace ("============================================\n"); trace ("info: server starting.\n"); rc = serve_requests (port => 80, request_handler => handler, max_handlers => 512, mode => MODE_DUAL, stop => null, server_is_ready => null, incoming_call => null, trace_calls => true); trace ("info: server stopping. rc=%d\n", rc); trace ("============================================\n"); }
Deux fonctions ont été ajoutées :
le serveur vous donne une url et vous demande s'il doit créer une page virtuelle.
Ici nous vérifions à l'aide de get_http_physical_file_exists()
si la page existe sur le disque dur, et si ce n'est pas le cas la fonction
dit au serveur d'appeler handle_virtual_page() pour créer une page virtuelle.
le serveur vous donne une url et vous demande de créer une page virtuelle et de renvoyer
son contenu à l'aide d'un ou plusieurs CS_write().
Voici un exemple qui montre comment interroger les paramètres d'une requête http,
(par exemple le nom du navigateur) et aussi de recevoir les champs d'un formulaire :
// iserv3.c : internet server from std use calendar, files, strings, tcpip, http, tracing, exception; //----------------------------------------------------------------------- HTTP_SERVER_DATA iserv; HTTP_INITIAL_DATA init; const uint WRITE_TIMEOUT = 30; // write timeout : 30 secs //----------------------------------------------------------------------- bool is_virtual_page (int nr, TCP_CLIENT tcp_client, string url, // virtual url ARG_INFO args) // http arguments { _unused nr; // suppresses warning that nr is unused _unused tcp_client; _unused url; return !get_http_physical_file_exists (args); } //----------------------------------------------------------------------- void entete_html (out string str, string titre) { sprintf (out str, "<HTML><HEAD><TITLE>"); strcatf (ref str, "%s", titre); strcat (ref str, "</TITLE></HEAD>\r\n"); } //----------------------------------------------------------------------- void fin_html (ref string str) { strcat (ref str, "</BODY></HTML>"); } //----------------------------------------------------------------------- void page_home (ref TCP_CLIENT tcp_client, ARG_INFO args) { char str[1000], browser[4096]; entete_html (out str, titre => "home"); strcat (ref str, "<BODY>"); strcat (ref str, "Bonjour !<br><br>"); strcat (ref str, "Bienvenue sur mon nouveau serveur développé en Safe-C "); strcat (ref str, "(le meilleur langage bien sûr)<br><br>"); strcat (ref str, "Ah I can see your Navigateur is : "); get_http_parameter (args, "User-Agent", out browser); strcatf (ref str, "%s", browser); strcat (ref str, "<br><br>"); strcat (ref str, "<a href=page1.html> PAGE 1 </a>"); strcat (ref str, "<br><br>"); strcat (ref str, "<a href=page2.html> PAGE 2 (laisser un message) </a>"); fin_html (ref str); CS_write (ref tcp_client, str[0:strlen(str)], WRITE_TIMEOUT); } //----------------------------------------------------------------------- void page_1 (ref TCP_CLIENT tcp_client) { char str[1000]; entete_html (out str, titre => "page 1"); strcat (ref str, "<BODY>"); strcat (ref str, "Youpi ! Nous sommes sur la page 1 !<br><br>"); strcat (ref str, "<br><br>"); strcat (ref str, "<a href=home.html> HOME (revenir) </a>"); fin_html (ref str); CS_write (ref tcp_client, str[0:strlen(str)], WRITE_TIMEOUT); } //----------------------------------------------------------------------- void page_2 (ref TCP_CLIENT tcp_client) { char str[2000]; entete_html (out str, titre => "page 2"); strcat (ref str, "<BODY>"); strcat (ref str, "Ah, here you will be able to leave me a message"); strcat (ref str, " to say what you think about my server ...<br><br>"); strcat (ref str, "<BODY>"); strcat (ref str, "All messages will be stored in the file courier.txt on the server !<br><br>"); strcat (ref str, "<FORM Method=POST Action=/postez.html>" + "<INPUT Type=text Name=PAR1 Size=200 MaxLength=200 Value=''>" + "<INPUT Type=submit Value=' POSTEZ! '>" + "</FORM>"); fin_html (ref str); CS_write (ref tcp_client, str[0:strlen(str)], WRITE_TIMEOUT); } //----------------------------------------------------------------------- void handle_virtual_page (int nr, ref TCP_CLIENT tcp_client, string url, // virtual url ARG_INFO args) // http arguments { _unused nr; if (strcmp (url, "/home.html") == 0) page_home (ref tcp_client, args); else if (strcmp (url, "/page1.html") == 0) page_1 (ref tcp_client); else if (strcmp (url, "/page2.html") == 0) page_2 (ref tcp_client); } //----------------------------------------------------------------------- void postez (ref TCP_CLIENT tcp_client, ARG_INFO args) { char str[1000]; char par1[240], par2[1000]; entete_html (out str, titre => "postez"); strcat (ref str, "<BODY> Nous avons bien reçu votre commentaire : <br><br>"); get_post_parameter (args, "PAR1", out par1); // write into a file { FILE file; PEER info; char[40] ipstr; DATE_TIME now; TCP_query_remote_address (tcp_client, out info); ip_to_numericstr (info.ip, out ipstr); get_datetime (out now); if (fappend (out file, "courier.txt", UTF8) == 0) { fprintf (ref file, "-------------------------------------------------------\n"); fprintf (ref file, "Comment received from %s:%u on %02d/%02d/%04d :\n", ipstr, info.port, now.day, now.month, now.year); fprintf (ref file, "%s\n", par1); fprintf (ref file, "-------------------------------------------------------\n"); fclose (ref file); } } // convert posted text to avoid hacking expand_html (par1, out par2); strcat (ref str, par2); strcat (ref str, "<br><br>"); strcat (ref str, "<a href=home.html> HOME (revenir) </a>"); fin_html (ref str); CS_write (ref tcp_client, str[0:strlen(str)], WRITE_TIMEOUT); } //----------------------------------------------------------------------- void handle_post_request (int nr, ref TCP_CLIENT tcp_client, string url, // virtual url, ex: "/getlist" ARG_INFO args) // post & http arguments { _unused nr; if (strcmp (url, "/postez.html") == 0) postez (ref tcp_client, args); } //----------------------------------------------------------------------- void handler (ref HANDLER_DATA p) { handle_http_call (ref p, iserv, ref init); } //----------------------------------------------------------------------- void main() { int rc; arm_exception_handler(); open_trace ("iserv.tra"); // init internet server strcpy (out iserv.html_directory, "c:/www/"); strcpy (out iserv.start_url, "/home.html"); iserv.is_virtual_page = is_virtual_page; iserv.handle_virtual_page = handle_virtual_page; iserv.handle_post_request = handle_post_request; iserv.tracing = true; // start server trace ("============================================\n"); trace ("info: server starting.\n"); rc = serve_requests (port => 80, request_handler => handler, max_handlers => 512, mode => MODE_DUAL, stop => null, server_is_ready => null, incoming_call => null, trace_calls => true); trace ("info: server stopping. rc=%d\n", rc); trace ("============================================\n"); }
Le programme suivant liste les fichiers d'un répertoire,
les stocke dans un arbre binaire et les affiche dans l'ordre :
// p.c : list files, store them in a binary tree, and display them in order. from std use exception, strings, bintree, files, console; typedef string^ PSTRING; // pointeur vers string package P = new BALANCED_BINARY_TREE (ELEMENT => PSTRING, USER_INFO => int); int compare (int^ line, PSTRING a, PSTRING b) { _unused line; return stricmp (a^, b^); } int print_element (int^ line, PSTRING p) { printf ("file %d : %s\n", line^++, p^); return 0; } int free_element (int^ line, PSTRING p) { _unused line; free p; return 0; } void main() { P.BINARY_TREE t; int^ pline = new int; int rc; FILE_INFO info; string^ p; arm_exception_handler(); create_btree (out t, pline, compare); rc = open_directory (out info, "c:/temp/*.*"); while (rc == 0) { if (info.type == TYPE_REGULAR_FILE) { p = new string ' (info.name[0 : strlen(info.name)]); rc = insert_btree (ref t, p); assert rc == 0; } rc = read_directory (ref info); } if (rc != NO_MORE_FILES) printf ("problem reading directory\n"); close_directory (ref info); // parcourir l'arbre en affichant tous les elements dans l'ordre pline^ = 1; rc = traverse_btree (ref t, print_element, order => +1); assert rc == 0; // pour effacer l'arbre, il faut d'abord le parcourir une fois pour libérer tous les strings rc = traverse_btree (ref t, free_element, order => +1); assert rc == 0; // ensuite on peut l'effacer close_btree (ref t); }
Le programme suivant affiche une image dans une fenêtre :
// show.c : afficheur d'images from std use exception, files, image, win; //====================================================================================== int load_my_image (string filename, int id) { IMAGE_INFO image; int x_res, y_res; window_get_resolution (out x_res, out y_res, id); if (load_image (out image, filename) < 0) return -1; assert resize_image (ref image, (uint)x_res, (uint)y_res) == 0; window_raster (0, 0, x_res, y_res, image.pixel^, id); free_image (ref image); return 0; } //====================================================================================== void main() { int left, top, right, bottom; bool quit = false; arm_exception_handler(); get_screen_borders (out left, out top, out right, out bottom); create_screen (x => 100, y => 100, x_size => left+right+600, y_size => top+bottom+500, title => "Test"); create_window (x => 2, y => 2, x_size => 400, y_size => 400, id => 100); create_button (x => 410, y => 20, x_size => 100, y_size => 30, text => " Load ", id => 10); create_button (x => 410, y => 60, x_size => 100, y_size => 30, text => " Exit ", id => 20); while (!quit) { EVENT event; get_event (out event); switch (event.typ) { case EVENT_BUTTON_PRESSED: switch (event.id) { case 10: { char filename[MAX_FILENAME_LENGTH]; if (select_filename ( dialog_title => "Load", mask => "images=*.jpg;*.bmp;*.gif\n", saving => false, // false=load, true=save out filename => filename, default_filename => "") == 0) { load_my_image (filename, id => 100); } } break; case 20: quit = true; break; default: break; } break; default: break; } } close_screen (); } //======================================================================================