programmation en C / LabWindows 1 le langage
Transcription
programmation en C / LabWindows 1 le langage
informatique - S1 programmation en C / LabWindows 1 le langage C/LabWindows Un programme informatique est, en toute généralité, une suite d’instructions intermédiaires entre le langage courant (les mots et phrases de tous les jours permettant de décrire un algorithme, une « marche à suivre ») et le langage machine (une suite de 0 et de 1). Des milliers de langages de programmation existent, généralistes (comme le C, le Pascal, . . .) ou spécialisés (par exemple maple pour le calcul formel, php pour la programmation de sites web dynamiques, perl pour les manipulations d’expressions, R pour les statistiques, . . .) LabWindows propose un environnement complet de programmation en langage C, l’un des plus répandus. 2 variables Une variable est une « case mémoire » permettant de stocker une donnée (un entier, un réel, un caractère, . . .). Le langage impose, avant utilisation, de déclarer les variables, c’est-àdire de préciser son nom et le type de données qu’elle pourra contenir. Il est de plus fortement conseillé d’initialiser chaque variable avant usage, c’est-à-dire de lui affecter une valeur initiale. En effet, une variable déclarée mais non initialisée aura une valeur aléatoire, dépendant de l’état de la mémoire de l’ordinateur, ce qui peut créer lors de l’éxécution des dysfonctionnements difficiles à détecter et à corriger. exemple 1 : int i; i=2; déclare une variable entière nommée i puis lui affecte la valeur 2. On peut aussi effectuer ces deux opérations en une seule instruction : int i=2; département Mesures Physiques - IUT1 - Grenoble Le nom de la variable doit commencer par une lettre, et peut ensuite être composé librement de lettres, de chiffres et du tiret de soulignement _. Le C distingue lettres majuscules et minuscules : A et a sont deux variables distinctes. De plus, on ne peut définir en C deux variables de types différents ayant le même nom : les déclarations int a; double a; ne peuvent se suivre. Les principaux types de variables sont : — les entiers, déclarés par int, ce sont des entiers signés codés sur 4 octets soit 32 bits. Ils sont donc compris entre −231 = −2 147 483 648 et 231 − 1 = +2 147 483 647. — les réels simple précision déclarés par float, ce sont des nombres à virgule codés sur 4 octets, donc compris entre -3.4e38 et +3.4e38. exemple : float pi=3.1415927; Compte-tenu de la puissance et de la mémoire des ordinateurs dont vous disposez, utiliser les réels simple précision ne comporte plus grand intérêt par rapport aux réels double-précision. — les réels double-précision, déclarés par double, ce sont des nombres à virgule codés sur au moins 8 octets, pour représenter des nombres compris entre -1.7e308 et +1.7e308 au moins. exemple : exemple : double pi=3.14159265358979; double x=1.265443929243e+222; — les caractères, déclarés par char, permettant de coder l’un des 256 caractères alphanumériques de la table des codes ASCII, codés sur un octet. exemple : char c=’a’; 4 affichage — les chaînes de caractères, déclarées par char nomvariable[n] où n est un entier qui représente le nombre maximal de caractères dans la chaîne. exemple : char c[30]; déclare une chaîne nommée c et pouvant contenir au plus 30 caractères. Il est cependant difficile de lui affecter directement une valeur dans le corps du programme : nous verrons plus loin comment l’utilisateur pourra saisir au clavier une chaîne. 4.1 la fonction printf Pour afficher le contenu d’une variable, on utilise la fonction printf qui réalise un affichage formaté, en permettant de mélanger texte et contenu de variables. exemple : printf("message"); affiche simplement le texte message remarque : une chaîne de caractère est en fait un tableau de caractères, de construction analogue aux tableaux de nombres que l’on décrira plus loin (voir 10). Une chaîne de caractères explicite est écrite, dans un printf, en indiquant les caractères entre ", pour la distinger d’un nom de variable. Si l’on souhaite afficher le contenu d’une variable, il faut préciser son type dans la chaîne de formatage, et ensuite indiquer son nom : exemple : printf("%d",message); affiche le contenu de la variable entière message (sous réserve qu’elle ait été déclarée préalablement bien sûr). 3 constantes 4.2 différents types d’affichage Si un programme utilise plusieurs fois une valeur approchée du nombre π, on peut pour éviter de re-taper cette valeur, définir une variable réelle par l’instruction double pi=3.1415926;. On peut bien entendu mélanger texte et contenu de variables, en donnant une chaîne de caractère composée des caractères à afficher et de symboles spéciaux débutant par % qui indiquent la place et le type des variables. Ainsi : %d indique un entier écrit en base 10 (décimal), %x indique un entier écrit en base 16 (hexadécimal), %f indique un réel (float), %e indique un réel en écriture scientifique (avec un exposant), %lf indique un réel double-précision (long float), %le indique un réel double-précision en écriture scientifique, %c indique un caractère (char), %s indique une chaîne de caractères (string). Mais la valeur de la variable ne changeant pas tout au long de l’exécution du programme, utiliser une variable n’est pas le meilleur choix : on préfèrera utiliser une constante. Il s’agit d’un simple raccourci : un #define PI 3.1415926 placé en tout début de programme, et qui indique au compilateur de remplacer chaque occurrence de PI dans le code-source par la valeur associée 3.1415926. Ainsi, dans le programme compilé, aucune variable inutile n’apparaît, ce qui assure un (petit) gain de place mémoire et de temps d’exécution. Cela ajoute aussi de la lisibilité au code. exemple : main() { int i=2; double x=2.256; Attention à la syntaxe : pas de signe =, pas de ; à la fin de la ligne #define PI 3.1415926, qui n’est pas à proprement parler une instruction mais une directive de compilation appelée macro. 2 char c=’a’; 4.4 caractères spéciaux printf("entier %d, réel %lf, caractère %c",i,x,c); } Outre ces symboles précédés de % qui permettent d’afficher le contenu des variables, on note aussi l’existence de certains "caractères spéciaux", précédés d’un « backslash » : \n qui insère un saut de ligne, \s qui insère une espace, \t qui insère une tabulation. affiche le texte : entier 2, réel 2.256, caractère a. Les caractères \ et % n’apparaissent donc pas à l’écran à l’exécution du programme : si l’on désire réellement les afficher, il faut les doubler : printf("\\") affichera \ et printf("%%") affichera %. 4.3 affichage des réels Les différentes formes d’affichage de réels (%f, %e, %lf, %le) permettent, en option, de préciser à la fois le nombre total de caractères à afficher et le nombre de chiffres après la virgule. Notons enfin la fonction Cls() (spécifique à LabWindows) qui efface le contenu de la fenêtre d’affichage. On indique ces nombres, séparés par un point, juste après le % : exemple : le programme 5 saisie de valeurs au clavier : scanf main() { double pi=3.14159265358979; double x=3009444.34; La fonction scanf est symétrique de la fonction printf : elle permet de saisir au clavier une valeur à ranger dans une variable. exemples : scanf("%d",&i); range dans la variable i un entier saisi au clavier. scanf("%lf",&x); range dans la variable x un réel saisi au clavier. scanf("%c",&c); range dans la variable c un caractère. Cls(); printf("%6.2lf", pi); printf("\n%10.6le", x); Notons une différence de syntaxe importante avec printf : le & devant le nom de la variable, qui indique qu’on s’intéresse ici à l’adresse de la variable, l’endroit où ranger la valeur saisie au clavier, et non pas à son ancienne valeur. affiche successivement : — une valeur approchée de π sur 6 caractères (point de séparation décimale inclus) avec deux chiffres après la virgule. Comme 3.14 n’utilise que 4 caractères, la fonction insère deux espaces avant les chiffres. — la valeur de la variable x en notation scientifique, avec 14 chiffres après la virgule, soit 3.00944434000000e+06, sur 20 caractères en tout. — la valeur de la variable x en notation scientifique, avec 6 chiffres après la virgule, sur 10 caractères en tout, 3.0094e+06 remarque 1 : LabWindows est très peu tolérant avec les mélanges de types, et aura une tendance certaine à planter si l’utilisateur rentre une lettre alors qu’un nombre est attendu, ou un séparateur décimal . si un nombre entier est attendu. remarque 2 : quand on utilise deux instructions scanf successives pour récupérer un nombre, puis un caractère ou une chaîne de caractères, on voit apparaître un petit problème. En effet, quand l’utilisateur rentre un nombre, il tape la suite de chiffres correspondante puis appuie sur la touche « entrée » du clavier. La suite de chiffres est transformée en un nombre, rangé dans la variable indiquée. Mais printf("\n%20.14le", x); } 3 6 opérations mathématiques le caractère \n qui correspond à l’appui sur la touche « entrée » n’était pas un chiffre, il est « gardé en mémoire ». Mais ce caractère convient au second scanf, qui ne laisse donc pas le temps à l’utilisateur de taper sa réponse au clavier. Pour éviter tout problème, on fera précéder toutes les instructions scanf par l’instruction fflush(stdin) qui signifier « vider (flush) l’entréestandard (stdin) », autrement dit vider la mémoire du clavier. Beaucoup d’opérations ou fonctions mathématiques sont connues du C ou de la bibliothèque de fonctions mathématiques du C et de LabWindows. Citons quelques exemples : — les quatres opérations +, -, *, /, — le modulo % qui renvoie le reste de la division entre deux entiers, — la fonction racine carrée sqrt, — les fonctions trigonométriques sin, cos, tan, atan, — les exponentielle et logarithme népérien exp, log, . . . exemple : une séquence-type d’instructions pour saisir une valeur au clavier ressemblera donc à cela : opérandes entières : On fera attention aux résultats de la division entre deux entiers : a/b renvoie le quotient entier de la division, ainsi 5/2 renvoie 2. Si l’on souhaite obtenir un résultat correspondant à la division réelle, il faut forcer l’une des deux opérandes à être un réel, par 5.0/2 ou par (double)5/2, la deuxième syntaxe plus longue ayant l’avantage de fonctionner même si l’opérande est une variable. main() { char reponse; printf("Répondez par o ou n : "); fflush(stdin); scanf("%c",&reponse); } main() { int a=3; A noter une exception de taille à la présence du & : scanf("%s", chaine); range dans la variable chaine une chaîne de caractères. Il n’y a pas ici de & devant le nom de la variable, car le nom d’une variable de type « chaîne de caractères », ou plus généralement le nom de tout tableau de valeurs, correspond déjà à une adresse. Ainsi, scanf("%s", chaine); affecte la chaîne saisie au clavier à la variable chaine. Attention, si la chaîne rentrée au clavier par l’utilisateur comporte des espaces, seule la partie précédant la première espace sera prise en compte. Pour permettre de saisir des chaînes de caractères comportant des espaces, on privilégiera l’instruction gets(chaine) qui n’a que des avantages par rapport à l’instruction scanf. printf("%d",3/2); printf("\n%lf",3.0/2); printf("\n%lf",(double)3/2); printf("\n%lf",(double)a/2); } affiche successivement 1, 1.5, 1.5, 1.5. raccourcis : le C propose quelques raccourcis pour les additions simples : i++ est équivalent à l’instruction i=i+1, i-- est équivalent à l’instruction i=i-1, i+=5 est équivalent à l’instruction i=i+5. 4 7 instructions conditionnelles Cls(); Tout programme informatique dépassant quelques lignes de code doit pouvoir adapter son comportement aux résultats de calculs, aux réponses de l’utilisateur. . .Le langage C dispose de deux types d’instructions pour cela. printf("Rentrez un nombre : "); scanf("%lf",&x); printf("Le nombre %lf est ", x); 7.1 l’instruction if if (x>0){ printf("positif."); } else { printf("négatif ou nul."); } L’instruction de base est le « si » : if : } exemple : if (x==0) { printf("x est nul"); } Le fonctionnement est simple à comprendre : si la condition entre parenthèses est vérifiée, on exécute le bloc d’instruction qui suit (qui peut être une instruction unique ou bien un ensemble d’instructions compris entre accolades). Dans l’exemple ci-dessus, si la valeur de la variable x est nulle, le programme affiche le message x est nul, sinon, rien ne se passe et l’exécution du programme continue à l’instruction suivante. Ici, on peut donc afficher deux messages différents, selon la valeur (et en particulier ici, le signe) de la variable. Notons au passage que l’obligation d’indiquer à l’instruction printf les passages à la ligne est ici un avantage : on peut écrire une phrase unique en combinant deux printf successifs : un affichage commun complété d’une partie variable en fonction du résultat du test. En plus de l’égalité (==), les tests peuvent utiliser les inégalités larges ou strictes (>, <, >=, <=), la non-égalité (!=), les conjonctions « et » (&&) et « ou » (||). exemple 2 : résolution d’une équation du premier degré exemples : if (x!=0) { printf("x est non nul"); } if (x==0 && y>=0){ printf("x nul et y positif");} if (x>0 || y>0) { printf("x ou y -ou les deuxest strictement positif"); } main() { double a, b; printf("Résolution de l’équation ax+b=0"); printf("Rentrez a : "); scanf("%lf",&a); printf("Rentrez b : "); scanf("%lf",&b); 7.2 le test if ...else Un raffinement est un test du type « si . . .alors . . .sinon . . . » : exemple 1 : test de positivité if (a != 0){ printf("la solution est x=%lf", -b/a); } else if (b==0){ printf("tout réel est solution"); } else { printf("pas de solution"); } main() { double x; } 5 7.3 le test switch ...case 8 boucles Pour tester successivement plusieurs valeurs d’une variable, on peut imbriquer les tests if ...else .... Par exemple, dans un menu permettant à l’utilisateur de choisir entre trois actions, on peut stocker le choix 1, 2 ou 3 dans une variable puis utiliser ceci : Il est souvent utile de répéter une opération un grand nombre de fois : par exemple, 10 fois, ou bien tant qu’une condition est vérifiée, en changeant à chaque exécution seulement quelques paramètres. Pour cela on distingue deux grandes familles de boucles : la boucle for et la boucle while. Si, théoriquement, les deux sont équivalentes, leur usage en pratique diffère légèrement : — on utilisera souvent la boucle for pour exécuter un nombre prédéfini de fois un bloc d’instructions dans lequel on aura besoin d’une variable modifiée (incrémentée de 1, le plus souvent) à chaque exécution, — on utilisera souvent la boucle while pour une boucle dont l’exécution est controlée par une condition booléenne. if (c==1){ /* instructions si 1 */ } else { if (c==2) { /* instructions si 2 */ } else { if (c==3) { /* instructions si 3 */ } else { /* instructions autres cas */ } } } en remplaçant bien sûr les commentaires /* instructions */ par les instructions appropriées. Mais cette méthode, si elle fonctionne, donne un code lourd et peu lisible. On préférera l’instruction switch ...case ... : switch(c) { case 1: /* instructions si 1 */ case 2: /* instructions si 2 */ case 3: /* instructions si 3 */ default: /* instructions autres } 8.1 la boucle for C’est la boucle qui correspond à la phrase française « pour i allant de 0 à 9 par pas de 1, . . . ». exemple 1 : for ( i=0 ; i<10 ; i++ ) { printf("Le compteur vaut maintenant %d", i); } break; break; break; cas */; On voit qu’une boucle for comporte trois parties : — le mot-clé for, — entre parenthèses, les conditions d’éxécution de la boucle, ellesmêmes composées de trois éléments séparés par des points-virgules : — l’initialisation i=0 : avant la première exécution de la boucle, on affecte à une variable i préalablement déclarée la valeur 0. — le test d’exécution i<10 : la boucle sera exécutée tant que i gardera une valeur strictement inférieure à 10. Cette condition est donc vérifiée avant chaque exécution de la boucle. — la modification de la variable i++ : après chaque exécution de la boucle, on modifie la valeur de i, ici en l’incrémentant de 1. — entre accolades, les instructions proprement dites : ici, à chaque itération (répétition) de la boucle, on affiche un message comportant la valeur de la variable. Ainsi, le programme regarde (switch) le contenu de la variable, et dans chaque cas (case) exécute les instructions prévues. Le break est nécessaire pour reprendre l’exécution après le switch ...case ..., sans examiner les cas suivants et en particulier le default, qui correspond aux instructions à exécuter quand aucun des cas énumérés n’est apparu. Attention, il est impossible de tester avec une instruction switch ...case autre chose que l’égalité du contenu d’une variable à des constantes : pas d’inégalité, pas de tests composés à l’aide d’opérateurs « et » (&&), « ou » (||), . . . 6 On peut imaginer des variantes : 8.2 la boucle while exemple 2 : Cette structure correspond à la phrase « tant que » : la boucle s’exécute tant qu’une certaine condition est vérifiée. for ( i=10 ; i<20 ; i=i+2 ) { printf("Le compteur vaut maintenant %d", i); } exemple 1 : while ( reponse != ’n’ ) { printf("Voulez-vous continuer (o/n) ?"); fflush(stdin); scanf("%c",&reponse); } exécute 5 fois la boucle avec une variable qui prend successivement les valeurs 10, 12, 14, 16, 18. exemple 3 : for ( i=100 ; i>=0 ; i-- ) { j = i/3; if (3*j==i) { printf("%d est divisible par 3", i); } } Ici, tant que l’utilisateur ne répond pas n, la boucle continue de s’exécuter. L’instruction fflush(stdin) permet de « vider l’entrée standard » (voir plus haut). exécute 101 fois la boucle, la variable prenant ses valeurs par ordre décroissant 100, 99, 98, . . ., 1, 0, mais seules les valeurs divisibles par trois sont affichées. Comment écrire la même chose en modifiant les instructions de contrôle entre parenthèses pour enlever le test if ? x=7; while ( 1 ) { x=x/2; printf("%lf", x); } Il est très simple d’obtenir une boucle s’exécutant indéfiniment : exemple 2 : Que font les boucles suivantes : exemple 4 : for ( i=1 ; i>0 ; i++ ) { printf("Le compteur vaut maintenant %d", i); } La valeur numérique 1 correspond à la valeur de vérité « vrai », cette boucle ne s’arrêtera jamais (ici, en affichant successivement 3.5, 1.75, 0.875, . . .puis une fois atteinte la limite de précision du type double, une infinité de 0). exercice : comment fabriquer à l’aide d’une boucle while l’équivalent d’une boucle for ? et exemple 5 : remarque : Dans le premier exemple ci-dessus, il est nécessaire d’initialiser reponse avant la boucle, pour s’assurer que cette variable ne contient for ( i=100 ; i>=0 ; i=i-2 ) { printf("Le compteur vaut maintenant %d", i); } pas, lors du premier test et avant même la première exécution de la boucle, la valeur n ; si une affectation précédente, ou le hasard lors de la déclaration, ? fait que la variable contient n, la boucle n’est pas exécutée du tout. 7 Il est ici plus simple d’utiliser une structure do ...while où le test n’intervient qu’après la première exécution : double carre(double x) { return x*x; } exemple 1 bis : do { printf("Voulez-vous continuer (o/n) ?"); fflush(stdin); scanf("%c",&reponse); } while ( reponse != ’n’ ) main() { printf("%lf", carre(2.5)); } 9 fonctions Ici donc on définit successivement une fonction carre en indiquant : — qu’elle renvoie une valeur réelle (double), — son nom (carre), — qu’elle attend un paramètre réel, qui sera appelé x, — la seule instruction exécutée : le carré de x est calculé par l’opération x*x et renvoyé (retourné). 9.1 notion de fonction Un programme en C est constitué d’une ou plusieurs fonctions, permettant de distinguer des tâches élémentaires. Ainsi, la fonction principale main présente dans tous les programmes va à son tour appeler d’autres fonctions pour réaliser le travail complexe demandé au programme. Beaucoup de fonctions sont déjà fournies par l’environnement LabWindows (via les lignes #include présentes en début de programme), comme printf pour réaliser des affichages, Cls pour effacer l’écran (la fenêtre d’entrée-sortie plus précisément), etc. Mais il est bien sûr possible d’en définir de nouvelles. . . A l’intérieur de la fonction main, la fonction carre est appelée avec le paramètre 2.5 pendant l’exécution de l’instruction printf, et le programme va donc afficher le résultat renvoyé par la fonction carre, soit 6.25. 9.3 compléments 9.2 définition d’une fonction — comme pour les noms de variables, un nom de fonction commence par une lettre et sera constitué de lettres (majuscules ou minuscules), chiffres et tirets de soulignement. — si plusieurs paramètres sont attendus, ils sont précisés successivement, séparés par des virgules : (double x, double y, int a). On ne peut pas, dans les paramètres d’une fonction, utiliser d’abréviation du type double x,y. — par défaut, si aucun type n’est précisé devant le nom de la fonction, LabWindows estimera qu’il s’agit d’un entier. Pour écrire une fonction qui ne renvoie aucune valeur, il est donc nécessaire de préciser qu’elle est de type vide (void) pour éviter un avertissement : si la fonction n’est pas de type vide, l’instruction return est attendue par le compilateur. Schématiquement, une fonction est un petit programme qui prend en entrée zéro, un ou plusieurs paramètres, exécute une action (affichage, écriture dans un fichier, calculs, etc.) et éventuellement retourne un résultat qui peut être utilisé par la fonction appelante. Pour définir une fonction, on doit indiquer quatre éléments : — le type de la valeur renvoyée ; — le nom de la fonction ; — la liste des paramètres utilisés (nom et type) ; — le corps de la fonction constitué des instructions à exécuter. exemple : un exemple complet simple est la définition d’une fonction carre qui calcule le carré d’un nombre : 8 — si une fonction peut demander plusieurs paramètres, il n’est pas possible simplement de faire en sorte qu’elle renvoie plusieurs valeurs. — outre les paramètres, on peut définir au début de chaque fonction des variables par des instructions du type double x;, exactement comme pour la fonction main. Par défaut, en C, les variables sont locales : elles n’existent qu’à l’intérieur de la fonction où elles sont définies, et ne sont pas connues dans les fonctions appelantes ou appelées. tion) et utilise une variable y pour stocker le résultat, calculé par les deux formules possibles, l’une si x est nul, l’autre si x est non nul. Le résultat calculé est ensuite retourné par l’instruction return y;. Il est à noter que la variable a n’existe qu’à l’intérieur de la fonction main() ; si l’on souhaite utiliser sa valeur, il faut comme ici la passer en paramètre à la fonction sinc. De même, une fois revenu dans la fonction main, après calcul du sinus cardinal, les variables x et y n’existent plus. 10 tableaux exemple : on souhaite définir une fonction sinc qui renvoie le sinus cardinal d’un nombre. Une manière de procéder est la suivante : Pour stocker plusieurs valeurs numériques, on peut bien sûr utiliser plusieurs variables, que l’on peut nommer a, b, c, ..., voire a1, a2, a3, ... Mais si l’on effectue des opérations concernant l’ensemble de ces variables (par exemple, affecter une valeur à chacune, calculer leur somme, etc.) cette solution n’est absolument pas souple car elle oblige à prévoir, dès l’écriture du programme, le nombre précis de valeurs qui seront à traiter, et surtout d’écrire explicitement les expressions de type saisie de valeurs ou somme. C’est encore possible si l’on a une poignée de valeurs, difficilement praticable si le programme doit en traiter quelques dizaines, centaines, milliers, . . . double sinc(double x) { double y; if (x==0) { y=0;} else { y=sin(x)/x; } return y; Le C, comme tous les langages de programmation, propose une structure de données plus adaptée : les tableaux, autrement dit des variables indicées par un nombre entier. On peut déclarer un tableau ainsi : double a[10]; Ainsi, dix variables réelles sont créées, a[0], a[1], ..., a[9]. On peut les manipuler, exactement comme les variables simples habituelles. Bien sûr, les tableaux peuvent aussi être de type int, char, . . .Et le nombre de valeurs n’est pas nécessairement 10. . . } main() { double a; Cls; printf("Rentrez un réel :"); scanf("%lf", &a); printf("\n Le sinus cardinal de %lf est %lf", a, sinc(a)); remarque : attention, si on déclare un tableau de 10 valeurs par l’instruction double a[10]; il n’existe pas de variable a[10] ! } exemple 1 : Un avantage important de cette manière de procéder est de permettre de traiter toutes les valeurs à l’aide d’une boucle. L’instruction : Ainsi, la fonction prend un paramètre x (fourni lors de l’appel de la fonc9 La méthode informatique correspond à ce qui aurait été fait à la main : on détermine un certain nombre d’abscisses régulièrement réparties sur l’intervalle, que l’on place dans un tableau X. Puis on calcule les ordonnées correspondantes, placées dans un tableau Y. Il ne reste alors plus qu’à faire appel à une ou plusieurs instructions qui vont placer les points correspondants dans une fenêtre graphique et les relier entre eux. for (i=0; i<10; i++){ a[i]=0; } affecte ainsi à chaque variable - à chaque case du tableau - la valeur nulle. On évite ainsi de devoir écrire les dix instructions a[0]=0;, . . ., a[9]=0; exemple 2 : une pratique utile, en particulier pour tracer des courbes, est de placer dans un tableau n valeurs régulièrement réparties entre deux réels a et b. On utilisera pour cela l’instruction : for (i=0; i<n; i++) { X[i]=a+ i*(b-a)/(n-1); } exemple 3 : l’utilisation d’une boucle permet aussi de calculer facilement la somme des valeurs d’un tableau : si l’on dispose d’un tableau X avec n valeurs, et d’une variable s, on peut procéder ainsi : 11.1 solution simple Le programme suivant présente une solution simple pour réaliser le tracé graphique de exp sur [-1,1] : s=0; for (i=0; i<n; i++){ s = s + X[i]; } main() { double X[200], Y[200]; int i; A la fin de l’exécution de ce bout de programme, la variable s contient bien la somme des éléments du tableau. exemple 4 : une chaîne de caractères est un tableau de caractères, et il est donc possible de manipuler les caractères un par un ; par exemple for (i=0; i<200; i++) { X[i] = -1 + 2.0*i/199; /* Attention, le 2.0 est obligatoire ici : sinon, 2 et i étant entiers, le calcul de 2*i/199 renverrait une valeur entière (troncature, sans décimale, de la valeur exacte) */ Y[i] = exp(X[i]); } char message[20]; gets(message); for (i=0; i<20; i++) { printf("%c : %d\n", message[i], message[i]); } affiche 20 lignes, listant les 20 caractères de la chaîne suivi de leur code ASCII. XYGraphPopup("premier tracé", X, Y, 200, VAL_DOUBLE, VAL_DOUBLE); } 11 graphiques Etant donnée une fonction - qui peut être l’une des fonctions déjà connues du langage (comme sin, sqrt, exp, . . .) ou bien une fonction écrite pour l’occasion (par exemple sinc)- on souhaite en tracer le graphe sur un intervalle donné. La fonction XYGraphPopup (spécifique à LabWindows) s’occupe de tout. . .Il suffit de lui fournir deux tableaux de valeurs et de lui indiquer le nombre de points à tracer. 10 11.2 solution souple VAL_EMPTY_SQUARE, VAL_SOLID, 1, VAL_RED); GetKey(); La solution précédente a l’avantage d’être très simple à mettre en oeuvre, mais l’inconvénient que l’on ne maîtrise pas du tout l’aspect de l’affichage (couleurs, dimensions, échelles, . . .). } Ici, la fonction NewPanel crée une fenêtre dont on détermine le titre, la position initiale (à 10 pixels du haut de l’écran et 20 du bord gauche) et les dimensions (600 pixels de haut et 800 de large). De plus la fonction renvoie un identifiant (un nombre entier) pour cette fenêtre, que l’on range dans une variable. Puis cette fenêtre est affichée par la fonction DisplayPanel dont le paramètre est justement l’identifiant précedemment attribué à la fenêtre. A l’intérieur de la fenêtre, une zone graphique est affichée : à 10 pixels du haut de la fenêtre, 20 du bord gauche, avec une hauteur de 550 pixels et une largeur de 750, par les instructions NewCtrl et SetCtrlAttribute. Puis l’instruction PlotXY trace la courbe. Mais on voit que le nombre de paramètres est plus important que pour la fonction XYGraphPopup : on peut choisir l’aspect des traits (lignes continue, pointillé, . . .), des points (marqués ou non), la couleur. . . Il est possible de remplacer l’instruction unique XYGraphPopup par une série d’instructions qui vont successivement : — définir une fenêtre, — l’afficher, — définir dans cette fenêtre une (ou plusieurs) zones graphiques, — régler la taille de cette zone graphique — effectuer un tracé dans cette zone graphique. Le bout de code suivant illustre cela : main() { double X[200], Y[200]; int i; int fenetre, zone; 12 fichiers for (i=0; i<200; i++) { X[i] = -1 + 2.0*i/199; Y[i] = exp(X[i]); } 12.1 généralités Les données calculées par un programme peuvent être archivées, ou bien traitées des heures ou des jours plus tard par un autre programme. . .Il sera pour cela nécessaire de savoir les écrire ou les relire dans un fichier. fenetre = NewPanel(0, "fenetre de tracé", 10, 20, 600, 800); DisplayPanel(fenetre); 12.2 écriture L’opération la plus simple est l’écriture dans un fichier. zone = NewCtrl(fenetre, CTRL_GRAPH, "zone de tracé", 10, 20); SetCtrlAttribute(fenetre, zone, ATTR_HEIGHT, 550); SetCtrlAttribute(fenetre, zone, ATTR_WIDTH, 750); exemple 1 : dans cet exemple, on se contente d’écrire un message dans un fichier. main() { FILE *fichier; PlotXY(fenetre, zone, X, Y, 200, VAL_DOUBLE, VAL_DOUBLE, VAL_THIN_LINE, 11 fichier = fopen("Z:\\Info_S2\\essai.txt", "w"); fprintf(fichier, "Test de la fonction fprintf"); fclose(fichier); } Ici les valeurs contenues dans le tableau sont écrites les unes à la suite des autres dans le fichier (avec ce programme, les valeurs de X n’étant pas initialisées, le contenu du fichier sera sans grand intérêt : mélange « aléatoire » de 0 et de valeurs du type 1.2994944e+261. . .). } Les trois étapes du programme sont donc : — l’ouverture par la fonction fopen du fichier dont le chemin d’accés est passé en paramètre. Le chemin d’accés réel est en fait ici Z:\Programmation\essai.txt mais il est nécessaire de doubler les \ car ceux-ci sont des caractères spéciaux. Le "w", pour « write » indique que le fichier est ouvert (créé s’il n’existe pas, et préalablement vidé s’il existe déjà) pour écriture. Un identifiant est attribué et rangé dans une variable fichier (d’un type spécifique FILE). Dorénavant, le programme ne fera plus référence au chemin d’accés du fichier sur le disque, mais uniquement à cet identifiant. — L’écriture dans le fichier, par la fonction fprintf, très analogue à la fonction printf, qui exige comme argument supplémentaire l’identifiant du fichier dans lequel écrire. — Puis, une fois l’écriture terminée, on signale au système que le fichier est traité, par l’instruction fclose. 12.3 lecture, version 1 On peut avoir besoin de relire des données écrites lors d’une séance de mesures quelques jours avant, ou fournies par un autre technicien. La méthode s’inspire naturellement de ce qui précède : main() { FILE *fichier; double X[200]; int i; fichier = fopen("Z:\\Info_S2\\essai.txt", "r"); for (i=0; i<200; i++) { fscanf(fichier, "%lf\n", &X[i]); } fclose(fichier); } Quand, comme souvent, les données à sauvegarder sont des valeurs numériques rangées dans un tableau (typiquement, une série de mesures, ou bien les coordonnées d’un grand nombre de points), on combine le programme précédent avec une boucle : Il s’agit de relire des données, donc le w de « write » est devenu un r pour « read », et le fprintf est devenu un fscanf. Bien entendu, pour fonctionner ce programme nécessite la présence d’un fichier essai.txt au bon emplacement et contenant 200 valeurs numériques rangées à raison d’une par ligne. main() { FILE *fichier; double X[200]; int i; 12.4 lecture, version 2 Le principal défaut du programme précédent est qu’il nécessite de connaître à l’avance le nombre de valeurs présentes dans le fichier. Si tel n’est pas le cas, et que l’on connaît seulement une borne supérieure à ce nombre de données, on peut procéder un peu différemment : la boucle fichier = fopen("Z:\\Info_S2\\essai.txt", "w"); for (i=0; i<200; i++) { fprintf(fichier, "%lf\n", X[i]); } fclose(fichier); 12 for, exécutée autant de fois que le nombre de données, peut être remplacée par une boucle while qui sera exécutée « tant que » le fichier contient encore des données. Une image (noir et blanc) sera représentée par une matrice (un tableau de lignes et colonnes). Chaque case, correspondant à un point de l’image, sera d’un type de données nouveau : des unsigned char, soit des entiers compris entre 0 et 255. La valeur 0 correspond à un pixel noir, la valeur 255 à un pixel blanc, et les différentes opérations sur les images vont consister à modifier ou ranger différemment ces valeurs. main() { FILE *fichier; double X[200]; int i; On précisera en début de programme la taille des images (fixée une fois pour toute dans le programme) : #define LARGEUR 400 #define HAUTEUR 266 fichier = fopen("Z:\\Info_S2\\essai.txt", "r"); puis on incluera les bibliothèques i=0; while (!feof(fichier)) { fscanf(fichier, "%lf\n", &X[i]); i++; } fclose(fichier); #include <ansi_c.h> #include <X:\Info_S2\ImagesMph.h> et enfin on déclarera deux variables pour disposer de deux images (typiquement, un original et une image modifiée) : unsigned char Image[HAUTEUR][LARGEUR]; unsigned char ImageTrans[HAUTEUR][LARGEUR]; } feof est la fonction qui teste si l’on est arrivés à la fin du fichier. Et le ! est la négation : l’instruction signifie donc « tant que l’on n’est pas à la fin du fichier », on lit un nombre supplémentaire dans le fichier, que l’on range dans la variable X[i], et on augmente l’indice i de 1. Ainsi, à la fin de cette boucle, la variable i contient le nombre de données lues, et ces données sont rangées dans le tableau X. Bien entendu, si le nombre de données est strictement supérieur à 200 dans le fichier, le programme ne fonctionnera pas correctement. Mais en revanche il est capable de lire correctement tout fichier contenant entre 0 et 200 nombres réels. Alors la fonction ChargeImage("image.bmp", Image) permet de placer dans la matrice Image l’image contenue dans le fichier image.bmp, et AfficheImage(Image,"mon titre", T, G) affiche une image dans une fenêtre dont le bord supérieur gauche est à T pixels du haut et G pixels de la gauche du coin supérieur gauche de l’écran. Il est donc possible maintenant de créer une image blanche, noire, de passer en négatif une image, de faire des inversions droite-gauche ou haut-bas, d’augmenter la luminosité, le contraste d’une image, de l’accentuer (rendre plus nette), . . . 13 traitement d’images L’utilité des outils précédents peut être illustrée de manière visuelle par quelques algorithmes simples de traitement d’images. exemples : 13 applique une symétrie à l’image, for (i=0; i<HAUTEUR; i++) for (j=0; j<LARGEUR; j++) { ImageTrans[i][j] = 255 - Image[i][j]; } for (i=0; i<HAUTEUR; i++) for (j=0; j<LARGEUR; j++) { ImageTrans[i][j] = Image[i][j] + 10; } crée un négatif de l’image initiale, for (i=0; i<HAUTEUR; i++) for (j=0; j<LARGEUR; j++) { ImageTrans[i][j] = Image[i][LARGEUR-1-j]; } Guillaume Laget - éclaircit l’image (ou presque : quel défaut constate-t-on ? Comment le corriger ?) version du 27-08-2014 11:36 (document mis à jour sur http ://maths.tetras.org/) 14 - réutilisation et reproduction non commerciale de tout ou partie de ce document vivement encouragées informatique - S1 TP 1 - premiers pas en C 1. sinus cardinal et cosinus redressé On considère le programme suivant : #include <ansi_c.h> main() { double x,y; printf("\nRentrez une valeur de x : ") scanf("%lf", x); if (x=0); {y == 1;} else {y=sin(x)/x;} département Mesures Physiques - IUT1 - Grenoble 4. pilotage d’un générateur BF Écrire un programme qui demande à l’utilisateur une fréquence (nombre entier, par exemple 100), une tension (nombre entier, par exemple 10), un offset (nombre entier, par exemple 5) et un type de signal (chaîne valant "SIN", "TRI", "SQ"), puis affiche une commande du type OUTP:LOAD INF;:APPL:SIN 100Hz, 10Vpp, 5V en passant à la ligne. 5. la boucle for On considère le programme suivant : #include <ansi_c.h> main() { int i; for (i=0 ; i<10 ; i++) { printf("%d\n",i); } printf("\nsinus_cardinal(%lf)=%lf, x, y); } (a) Corriger les erreurs de syntaxe. (b) À quoi sert l’instruction scanf ? Le symbole == ? (c) Quelle est l’effet de ce programme ? L’exécuter. 2. équation du second degré Écrire un programme qui permet à l’utilisateur de saisir trois valeurs réelles a, b et c, puis affiche le discriminant et les solutions réelles de l’équation ax2 + bx + c = 0. Si l’équation n’a pas de solution afficher un message pour l’indiquer. 3. affichage d’un réel Écrire un programme qui demande à l’utilisateur un réel sous forme décimale et l’affiche ensuite sous forme scientifique avec 5 chiffres significatifs. } (a) L’exécuter pas-à-pas pour en comprendre le fonctionnement, (b) Le modifier pour afficher, du plus grand au plus petit, les nombres compris entre 16 et -3, (c) Le modifier pour qu’il affiche 10 valeurs régulièrement réparties entre 2.5 et 4.8 6. trigonométrie Écrire les fonctions module et argument qui, partant de deux paramètres réels a et b renvoient respectivement le module et l’argument du nombre complexe correspondant a + ib. 7. conversion entre coordonnées comme paramètres un réel x et un entier positif n, et qui renvoie la valeur de x élevé à la puissance n. Écrire les fonctions Abscisse, Ordonnee, Cote qui renvoient les coordonnées cartésiennes d’un point défini par ses coordonnées latitude, longitude et altitude, selon les formules x = (R + h) cos(l) cos(L) y = (R + h) cos(l) sin(L) z = (R + h) sin(l) 9. la fonction sinus cardinal Écrire une fonction sinc qui calcule les valeurs du « sinus cardinal » sin x sinc(x) = si x 6= 0, sinc(0) = 1. x 10. facultatif : suite de Syracuse Écrire un programme qui demande à l’utilisateur de choisir un entier u0 puis affiche successivement les termes de la suite (un ) définie par un si un est pair, 2 un+1 = 3un + 1 sinon. 2 avec l la latitude, L la longitude, et h l’altitude. R = 6378000m est le rayon de la Terre. Quelle est l’abscisse de la salle 301 du département Mesures Physiques ? 8. puissance entière 1) Écrire un programme qui demande à l’utilisateur un entier strictement positif n et un réel x, puis affiche la valeur de xn . 2) Modifier le programme pour qu’il fonctionne correctement pour un entier n quelconque (positif, négatif ou nul). 3) Transformer ce programme en une fonction puissance qui prend Le tester avec plusieurs valeurs pour u0 . Que remarque-t-on ? 11. facultatif : équation du second degré à solutions complexes Reprendre le programme sur l’équation du second degré pour afficher, si le discriminant est négatif, les solutions complexes. 2 informatique - S1 TP 2 - tableaux, graphiques, fichiers 1. cosinus redressé double alternance (a) Écrire une fonction CosRed2 donnant les valeurs du « cosinus redressé double alternance » : cos(x) si cos(x) > 0, − cos(x) sinon. (b) Remplir un tableau X avec 200 valeurs régulièrement réparties entre −2π et 2π. (c) Remplir un tableau Y avec les valeurs correspondantes du cosinus redressé double alternance. (d) Tracer la courbe représentative du cosinus redressé double alternance. 2. courbe de Lissajous Tracer de même la courbe d’équations paramétriques, pour t ∈ [0, 2π] : x(t) = cos(t) y(t) = sin(4t) en plaçant dans des tableaux X et Y les valeurs d’abscisses et d’ordonnées correspondant à 200 instants régulièrement répartis entre 0 et 2π. 3. détermination d’incertitudes de mesure (a) Tracer sur un même graphique, pour 0 ≤ ϕ ≤ π/2, p p 1 + sin2 (ϕ) 2 2 . y = 1 + π /ϕ et y= ϕ| cos(ϕ)| département Mesures Physiques - IUT1 - Grenoble (b) Déterminer graphiquement les intersections de ces deux courbes à une précision de 10−2 . Quelles sont les valeurs en degré correspondantes ? 4. résolution numérique par dichotomie On rappelle que si une fonction f continue sur [a, b] est strictement monotone, avec f (a) et f (b) de signes opposés, alors elle admet un unique zéro sur l’intervalle. L’algorithme de dichotomie suivant permet de déterminer des valeurs approchées de ce zéro. Il consiste à définir deux suites (xn ) et (yn ) adjacentes, avec (xn ) croissante, (yn ) décroissante, et dont la limite commune est le zéro cherché. Pour cela on pose x0 = a, y0 = b puis, si xn et yn sont calculées, on note m le milieu de l’intervalle [xn , yn ]. Si f (xn ) et f (m) sont de signes opposés, le zéro est entre xn et m : on pose xn+1 = xn , yn+1 = m. Dans le cas contraire, on posera xn+1 = m, yn+1 = yn . Comment tester « informatiquement » si f (xn ) et f (m) sont de même signe ? Implémenter cet algorithme. 5. courbe de valeurs lues dans un fichier Lire dans le fichier courbe.txt 200 valeurs d’abscisse et ordonnée (rangées à raison d’une abscisse et d’une ordonnée, séparées par une espace, sur chaque ligne), les ranger dans des tableaux X et Y, et tracer la courbe correspondante. informatique - S1 TP 3 - calculs de moyennes, écarts-type, variances 1. fonction de lecture : Créer une fonction Lecture1 qui prend comme paramètres un tableau de réels et une chaîne de caractères nom et qui : - range dans le tableau les valeurs lues dans le fichier nom - renvoie le nombre de valeurs lues. 2. moyenne : écrire une fonction Moyenne1 qui prend comme paramètre un tableau X et un entier n et renvoie la moyenne des n premières valeurs du tableau. 3. variance : écrire une fonction Variance1 qui prend comme paramètre un tableau X et un entier n et renvoie la variance des n premières valeurs du tableau. 4. écart-type : écrire une fonction EcartType1 qui prend comme paramètre un tableau X et un entier n et renvoie l’écart-type des n premières valeurs du tableau. 5. calculs statistiques : écrire un fonction main() qui, en utilisant ce qui précède : - fait choisir un fichier à l’utilisateur à l’aide de la fonction FileSelectPopup(), - place les nombres contenus dans ce fichier dans un tableau, - affiche la moyenne, la variance et l’écart-type de la série statistique. 6. à deux variables : écrire de même des fonctions Lecture2, Moyenne2, Variance2 et EcartType2 qui permettent de cal- département Mesures Physiques - IUT1 - Grenoble culer les moyennes, variances et écart-type des n premières valeurs contenues dans un tableau X, pondérées par les coefficients correspondants d’un tableau Y, à partir d’un fichier qui contient sur chaque ligne une note et le coefficient associé. Rappels : — pour une série statistique (xi ), la moyenne est x̄ = x1 + . . . + xp , p la variance est (x1 − x̄)2 + . . . + (xp − x̄)2 , p l’écart-type est la racine carrée de la variance. — pour une série statistique (xi , ni ), la moyenne est x̄ = n1 x1 + . . . + np xp , n1 + . . . + np la variance est n1 (x1 − x̄)2 + . . . + np (xp − x̄)2 , n1 + . . . + np l’écart-type est la racine carrée de la variance. informatique - S1 TP 4 - GPS À partir de la trace enregistrée par un GPS, on récupère le parcours d’un randonneur sous la forme d’un fichier contenant, sur chaque ligne, la latitude l, la longitude L et l’altitude h des points successifs. Dans un premier temps, on considèrera que la Terre est une sphère de rayon R = 6378000m. On a alors les formules de conversion en coordonnées cartésiennes : x = (R + h) cos(l) cos(L) y = (R + h) cos(l) sin(L) z = (R + h) sin(l) 1. Les randonnées considérées durent au maximum 10 heures, et un point est enregistré toutes les 10 secondes. Définir en début de programme une constante NBP plus grande que le nombre maximal de points à considérer. département Mesures Physiques - IUT1 - Grenoble (a) permet à l’utilisateur de choisir un fichier (b) relit le fichier (c) trace, en deux graphiques dans la même fenêtre : le trajet parcouru (représenter la latitude et la longitude), et le profil d’altitude (représenter l’altitude) (d) calcule et place dans 3 tableaux X, Y, Z les coordonnées cartésiennes des points lus dans le fichier (e) calcule la distance totale parcourue lors de la randonnée, le dénivelé positif et le dénivelé négatif 4. Écrire une fonction Distance qui prend comme paramètres six réels X1, Y1, Z1, X2, Y2, Z2, et renvoie la distance entre les deux points de coordonnées respectives (X1, Y1, Z1) et (X2, Y2, Z2). 6. Les coordonnées latitude, longitude et altitude fournies par le GPS font en fait référence non pas à une sphère, mais à un ellipsoïde de demi-grand-axe a = 6378137.00m, de demi-petit-axe b = √ 6356752.31m, d’excentricité e = a2 − b2 /a = 0.08181919, d’apa−b = 1/298.2572. platissement f = a On obtient alors les formules de conversion : a + h) cos(l) cos(L) x = (p 2 sin2 (l) 1 − e a y = (p + h) cos(l) sin(L) 1 − e2 sin2 (l) a(1 − e2 ) p + h) sin(l) z = ( 1 − e2 sin2 (l) 5. Écrire une fonction principale qui, en utilisant les fonctions précédentes : Comparer les valeurs obtenues par ces formules avec celles obtenues pour le modèle sphérique. 2. Écrire une fonction qui permet de relire les valeurs contenues dans le fichier, de les placer dans trois tableaux l, L et h, en renvoyant le nombre de lignes lues. 3. Écrire une fonction Abscisse qui prend comme paramètre trois réels (latitude, longitude, altitude) et renvoie l’abscisse correspondante. Écrire de même la fonction Ordonnee et la fonction Cote. informatique - S1 TP 5 - traitement d’images En début de programme vous placerez le code : #include <ansi_c.h> #define LARGEUR 400 #define HAUTEUR 266 #include <X:\Info_S2\ImagesMph.h> pour bénéficier des deux fonctions : ChargeImage("image.bmp", Image) qui place dans la matrice Image l’image contenue dans le fichier image.bmp, et AfficheImage(Image,"mon titre", T, G) qui affiche l’image représentée par la matrice Image dans une fenêtre dont le bord supérieur gauche est à T pixels du haut et G pixels de la gauche du coin supérieur gauche de l’écran. Puis, pour disposer de deux images (originale et modifiée) : unsigned char Image[HAUTEUR][LARGEUR]; unsigned char ImageTrans[HAUTEUR][LARGEUR]; Une image, monochrome, sera donc représentée par une matrice d’unsigned char, soit des entiers compris entre 0 et 255 : 0 indique un pixel noir, 255 un pixel blanc. Pour vos essais utiliser les images fournies ou vos propres images (au format bitmap (bmp), de dimensions imposées HAUTEUR x LARGEUR ; les couleurs seront transformées en niveaux de gris). département Mesures Physiques - IUT1 - Grenoble 1. Quelques manipulations élémentaires pour commencer : (a) Relire et afficher l’une des images fournies. (b) Créer et afficher une image blanche. (c) Créer et afficher une image avec une bande blanche en haut, une bande centrale grise et une bande noire en bas. 2. Appliquer à une image les transformations élémentaires suivantes, en affichant côte à côte l’image originale et l’image transformée. négatif : transformer le blanc en noir et le noir en blanc. symétrie ; inverser droite et gauche. luminosité : éclaircir/assombrir d’une valeur choisie par l’utilisateur (par exemple, entre -20 et +20). contraste : assombrir les pixels sombres et éclaircir les pixels clairs. 3. De même, implémenter deux filtres plus complexes : accentuation (renforcement de netteté) : remplacer le niveau de chaque pixel par la combinaison de son niveau coefficient 1 + 4r et des niveaux des quatre voisins (gauche, droite, haut, bas) coefficient −r. Tester pour plusieurs valeurs de r, de 0.1 à 2. détection de contours : dessiner les contours de l’image. 4. Histogramme : Afficher l’histogramme d’une image (en abscisse les 256 niveaux de gris, en ordonnée le nombre de pixels de cette couleur).