Programmation en C
Documents connexes :
Liens :
Je me suis rendu compte qu'il faut presque redescendre au niveau 0. Pour faire afficher un a sur l'écran de l'ordinateur il faut presser la touche a. Parfois, on récupère un A qui n'est même pas sur le clavier! Au secours!!!...
Ne soyons pas si négatif, en IUT, les étudiants font tout de même un lien entre l'affichage et le clavier ! Il arrivent même à faire des majuscules! Et ils manipulent aussi la souris. Génial, non?
Les répertoires servent à ranger, trier les données afin d'y voir plus clair. Ce sont comme des placards, des tiroirs dans un placard... Chez vous, est-ce que tout est dans une seule même caisse? Tout, des fringues à la bouffe? Non! Ca la bouffe tacherait les vêtements et le mouchoir dans la soupe, ce n'est appétissant! Et j'aime bien trier les vêtements propres des sales!
Alors, faites de même avec les données que vous gardez sur votre ordinateur ou sur votre compte à l'IUT ! Commencez par créer un répertoire par matière où vous utilisez votre compte et peut-être par la suite vous y mettrez d'autres sous-répertoires... Vous allez ainsi créer l'arborescence de votre ordinateur ou de votre compte.
Ceci permet de retrouver plus facilement vos billes, de perdre moins de temps avec la recherche du bon document.
Avec un explorateur de fichiers, vous cliquez sur un répertoire pour l'ouvrir et voir ce qu'il y a dedans. S'il y a d'autres répertoires, vous pouvez refaire la manipulation.
En ligne de commande pour aller dans un répertoire, on fait "cd répertoire", pour aller sur le répertoire parent (vers la racine, niveau de base de l'arborscense), on fait "cd ..". Comme ceci on peut enchaîner les commandes : "cd .." + "cd rep1" donne "cd ../rep1" avec Unix ou Linux ou sur un OS merdique comme window$ : "cd ..\rep1".
Exercice : simplifier cd rep1/rep2/../rep3/rep4/rep5/../../rep6.
Remarque : l'explorateur de windows (comme windows en général) vous prend pour un con et(/ou) souhaite que vous le deveniez, soit vous l'êtes (ou voulez le devenir), alors garder la configuration de base. Sinon, demandez au moins d'afficher les extensions des fichiers connus : Outils->Options des dossiers onglet affichage, décochez la case Cachez les extentions des fichiers dont le type est connu.. Il est aussi possible de virer des affichages inutiles comme "afficher mon bureau comme une page web" qui prend de la place our rien. Finalement, on retourbe dans l'onglet affichage, clique sur [appliquer] puis sur [comme le dossier actuel]. Cette fois, vous aurez un environnement qui ressemble plus à un environnement de travail bien que ce soit du windows. Votre images, vous saurez si c'est iamge.png ou image.gif ou... ce qui rendra la programmation bien plus simple.
Avec un explorateur de fichiers, il suffit d'aller dans le répertoire où on veut le créer, faire un click droit, choisir "créer un nouveau" "répertoire" après il apparait dans le navigateur avec le nom sélectionné (un nom bidon) : à vous de rentrer celui que vous voulez!
En ligne de commande, une fois que nous sommes arrivé dans le répertoire où nous voulons créer le nouveau répertoire, on rentre la commande "md nom_du_répertoire" md ou mkdir en fonction de votre environnement.
Exercice : créer dans votre répertoire personnel, un répertoire par matière dans laquelle vous utilisez votre compte plus un personnel/libre.
Pour déplacer des fichiers avec un explorateur, on les sélectionne puis clique (1) dessus avec la souris et laisse le bouton enfoncé en déplaçant la souris vers le répertoire destination. On a souvent alors un choix entre (entre autre) déplacer ou copier. Déplacer change la place des fichiers alors que Copier duplique les informations. Ceci marche aussi avec des répertoires.
En ligne de commande, pour copier ou fait "cp chemin_vers_fichier/fichier chemin_vers_destination/nom_arrivé" (copy à la place de cp et \ à la place de / sous window$/DOS). On peut ne pas mettre un chemin, si c'est le répertoire courant. On peut aussi éviter d'écrire nom_arrivé si on garde le même nom. Par contre on doit laisser un champ pour l'arrivée, si "chemin_vers_destination" et "nom_arrivé" sont inutiles, mettre alors uniquement un ".". Pour déplacer on remplacer cp(/copy) par mv(/move).
Exercice : placer dans les répertoires précédement créés les fichiers de chaque matière et vos fichiers personnels.
Voici la forme d'un fichier en c :
// après ce double caractère, la ligne est un commentaire /* toutes les lignes entre le "divisé étoile" de la ligne si dessus et le "étoile divisé" de la ligne suivante sont des commentaires */ #include <les bibliothèques utiles.h> // ceci rajoute lors de la compilation à cet endroit le code de la bibliothèque #define PI 3.14159 // pour remplacer chaque occurence de PI dans le code // source par 3.14159 lors de la compilation // définition des différentes variables globales // Une variable globale est une variable qui peut être utilisée par tous les programmes // utilisée = lue et modifiée. C'est ce dernier point qui fait qu'il vaut mieux les éviter int var1; // réserve la place pour un entier de nom var1 // Cette variable est globale, c'est à dire q'une modification // dans une routine implique une modification partout char nom_de_la_fonction(int);// on signale qu'on va avoir une fonction nommée nom_de_la_fonction // qui retourne un caractère et qui demande comme paramètre un entier // Cette fonction peut être défini après le corps du programme (main) // ou dans un include. // Pour un gros projet, je conseille de mettre les définitions de // fonction dans un include qui sera inséré à chaque début de fichier void main() // main est la fonction qui sera exécutée au début : la fonction principale { // elle pourra en appeler d'autres. // définition de 3 variables locales. On affecte 48 à la troisième. char c,c1; // 2 caractères int j; // un entier int i=48; // un entier qu'on initialise à 48 //le programme principal c=nom_de_la_fonction(i); // appel de la fonction nom_de_la_fonction // c prend la valeur renvoyée par la fonction nom_de_la_fonction et on passe à la fonction // le paramètre i : d'après la définition, on récupère un caractère et on passe un entier c1=nom_de_la_fonction(i+8); // à nouveau appel de la même fonction avec des paramètres différents // Notre fonction (voir son code) affectera i la première fois à l puis renverra // car que nous allons mettre dans c car ici, car est non défini (locale dans la fonction) / La seconde fois, l vaudra i+8 et le retour sera stocké dans c1. // suite du programme } char nom_de_la_fonction(int l) // voici la fonction nommée nom_de_la_fonction // définition d'une procédure : il faut lui fournir un entier, elle renvoie un caractère // l'entier envoyé est mémorisé dans la variable locale l { // définition d'une variable locale char car; // un caractère // Une autre fonction pourra aussi avoir une variable "car", mais ces variables n'auront rien à // voir. C'est ce qu'on appelle une variable locale. Ceci permet de mieux découper un programme en // sous routine sans avoir d'effets de bord incompréhensibles. Imaginez que la fonction printf qui // sert à afficher des chaînes de caractères utilise une variable i globale et que utilisiez aussi // une variable i globale, alors l'appel de printf modifiera votre variable i... //corps le la routine qui peut ell-même en appeler d'autres car=li%256; // le reste de la division de li par 256 // retour du caractère car : la fonction renvoie un caractère d'après sa définition return car; }
Surtout ne pas oublier les ";" à la fin de chaque instruction et les "{...}" pour encadrer les groupes d'instructions qui forment une fonction, une boucle...
Il est bien beau de faire tourner un programme, mais il est intéressant qu'il fasse quelque chose, qu'on puisse récupérer quelque chose en sortie! La sortie peut être l'affichage, un fichier, une commande d'un élément extérieur (imprimante, robot...). Nous, on utilisera surtout l'écran ou un fichier : les instructions sont semblables (ouf!).
De même il parait important de pouvoir agir sur le programme : lui fournir des entrées différentes en fonction de nos besoins. On utilisera encore principalement le clavier et à la fin un fichier.
Pour afficher les données, nous utiliserons "printf()".
printf("Chaîne de caractère avec des formateurs comme %d,%f,%s",liste des valeurs dans l'ordre à mettre à la place des formateurs). En arrivant sur un printf, le programme affiche sur l'écran ce qu'on lui a demandé. L'utilisation des formateurs (le caractère après le %) est très importante, en voici une liste :
d Convert integer to signed decimal string. u Convert integer to unsigned decimal string. i Convert integer to signed decimal string; the integer may either be in decimal, in octal (with a leading 0) or in hexa- decimal (with a leading 0x). o Convert integer to unsigned octal string. x or X Convert integer to unsigned hexadecimal string, using digits ``0123456789abcdef'' for x and ``0123456789ABCDEF'' for X) c Convert integer to the Unicode character it represents. s No conversion; just insert string. f Convert floating-point number to signed decimal string of the form xx.yyy, where the number of y's is determined by the precision (default: 6). If the precision is 0 then no deci- mal point is output. e or E Convert floating-point number to scientific notation in the form x.yyye±zz, where the number of y's is determined by the precision (default: 6). If the precision is 0 then no deci- mal point is output. If the E form is used then E is printed instead of e. g or G If the exponent is less than -4 or greater than or equal to the precision, then convert floating-point number as for %e or %E. Otherwise convert as for %f. Trailing zeroes and a trailing decimal point are omitted. % No conversion: just insert %.
Pour tester, faîtes :
printf("1: %d, 2: %u, 3:%f, 4: %c, 5: %s",-456,896,123.36,'f',"fin");
Changez de place uniquement les formateurs et admirez les dégats.
Il existe des variantes de prinf : fprintf, sprintf qui écrivent dans des fichiers ou des chaînes de caractères, dans ce cas, on met en premier paramètre le fichier ou la chaîne de caractères.
Pour les données arrivant du clavier, nous utiliserons "getchar()", "scanf()".
int getchar() : renvoie la sortie clavier.
Pour tester, faîtes :
c=gets(); printf("j'ai touché le caractère %c",c);
Commencez avec les touches représentants des caractères, puis essayez les touches de fonctions. Attention aux surprises !
char * gets(char * nom) : on fournit à cette fonction l'adresse du premier caractère de la chaîne. Lorsque le programme arrive à cette instruction, il attend qu'on appuie sur la touche Entrée du clavier et renvoie alors la chaîne de caractères que nous avons rentrés avant.
Pour tester, faîtes :
gets(s); printf("j'ai rentré la chaîne %s",s);
La famille de fonctions de scanf : sscanf, fscanf sert à récupérer des données brutes et à les analyser en fonction de formateurs.
Le fonctionnement est fort semblable à printf. Mais cette fois, on ne passe pas en argument les valeurs des variables, mais les adresses où les dîtes variables sont stockées : pour la donnée nommée "i" on devra mettre "&i", sauf pour une chaîne de caractères qui est en fait un pointeur sur le premier caractère de la chaine. Le & permet de donner l'endroit où est stocké la varible qu'on veut modifier afin que le scanf puisse y mettre ce qu'il a trouvé.
En effet, si on écrit i comme paramètre, la fonction reçoit comme information la valeur de i et &i fournit l'adresse où est stocké i. Comme notre scanf va modifier la valeur, il n'a pas besoin de connaître sa valeur i, mais il a plutôt besoin de savoir où il va mettre la valeur qu'il va récupérer &i.
sscanf est en fait une variante de scanf, il existe aussi fscanf comme variante très utile (voir prinf).
sscanf(chaîne_de_caractères,"Chaîne de caractère avec des formateurs comme %d,%f,%s",liste des adresses des variables dans l'ordre) comme sscanf("11:58:13","%d:%d:%d",&h,&m,&s) pour analyser la chaine de caractères qui contient l'heure sous la forme heure:minute:seconde et mettre l'heure dans l'entier h, les minutes dans l'entier m et les secondes dans l'entier s.
Pour tester, cherchez à comprendre et faîtes :
sscanf("3 tomates à 5.45 Euros le kilo coûtent 3.59 Euros", "%d tomates à %f Euros le kilo coûtent %f Euros", &n,&pk,&pt); printf("%d tomates pèsent %f kilogrammes",n,pt/pk);
Quels sont les formats dde n,pk et pt ?
En C, il faut définir, déclarer les variables avant de les utiliser afin que le programme leur attribue un emplacement dans la mémoire et sache comment les traiter.
Ceci n'est pas le cas dans tous les langages de programmation, par exemple, en BASIC ou en javascript ce n'est pas obligatoire. Cette déclaration, obligatoire en C, tout en étant une contrainte, génère une puissance incomparable à ce langage avec les transtypages. Cette puissance vient aussi du fait qu'on sait ce qu'on manipule, c'est par exemple le cas avec les chaînes de caractères qui ne sont en fait qu'un tableau de caractères donc, si "s" est ma chaîne, "s[4]" est le cinquième caractère de la chaîne (les tableaux commencent à l'indice 0). Si la chaîne s vaut "CouCou", alors s[4] vaut 'o'.
Remarquez, les " (guillemet) pour délimiter la chaine de caractères et le ' (aspostrophe) pour encadrer le caractère.
Le ' pour encadrer un caractère est très important. En effet, c='1' n'est pas équivalent à c=1, mais c=49 ! Un caractère est stocké en méoire par son code ASCII et le code ASCII du caractère 1 est l'entier 49 (31 en hexadécimal). Ceci est une force du C. En effet si on fait c='a';c=c+8;, c passe du code ASCII de valeur 97 à celui de valeur 105 qui est celui de la lettre 'i', 8 lettres plus loin dans l'alphabet.
En déclarant les variables, nous devons donc donner le nom mais aussi la nature de la variable. Nous disposons en C de :
C'est une force du C ! En effet, en C, on sait ce qu'on manipule ! On a la possibilité de faire passer un caractère pour un entier, un entier pour un réel...
Le plus simple est le passage entier <-> caractère. En effet, si a est un entier a='c'; met dans a le code ASCII de 'c' alors que si c est un caractère c=35; attribue à c le caractère dont le code ASCII est 35 (en décimal).
La méthode de transtypage habituelle est (nouveau type)(nom de la variable à transtyper), par exemple (float)(a)/4 donnera 0,25 si a est un entier de valeur 1 alors que sans transtypage (a/4) donnera 0 (la partie entière du résultat de la division). Attention, (float)(a/4) donnera aussi 0, mais un 0 décimal : on a fait la division entière puis on a après (seulement après) fait la consersion en float.
Une astuce (pas très rigoureuse) pour forcer un division réelle de a par 4 aurait été de faire a/4.0.
Il peut être intéressant de faire des tests afin de faire des choix, de valider une entrée, une sortie... Pour ceci le C dispose d'outils assez puissant que nous allons décrire ici.
"==" est l'opérateur qui teste l'égalité entre deux valeurs. Attention, pour les chaînes de caractères, ça ne marche pas car le paramètre que nous avons n'est pas la chaîne, mais un pointeur sur la chaîne, dans ce cas, l'égalité signiferai que nos 2 pointeurs décrivent la même case mémoire.
Attention, ne pas confondre l'affection a=5 et le test d'égalité a==5. a=5 qui est renvoie 5 (non nul) sera toujours exact, alors que a=0 quant à lui sera toujours faux.
Pour tester, faîtes entre autre :
printf("32==31 ? %b\n",32==31); printf("31==31 ? %d\n",31==31); printf("'0'==30 %d\n",'0'==30); printf("'0'==48 %d\n",'0'==48);
C'est l'inverse de "=="; c'est pour tester une différence. Faîtes les mêmes tests que tout à l'heure en remplaçant le == par !=.
Des opérateurs de comparaisons. Faîtes comme précédemment.
Opérateur "et" logique.
Opérateur "ou" logique.
Opérateur non.
Les boucles sont une notion très importante de tout langage de programmation. En effet, il peut être important de pouvoir refaire plusieurs fois la même chose tant qu'une condition n'est pas atteinte : tant que l'utilisateur n'a pas rentré une donnée au format convenable, tant qu'il n'est pas une certaine heure, tant que ma variable n'a pas atteint un certain critère, tant que je n'ai pas fait un certains nombres d'itération... En C, nous disposons pour ceci de plusieurs méthodes.
Attention : pensez bien à vérifier que pendant la boucle la condition du test est bien modifiée et qu'au bout d'un moment au pourra bien sortir de la boucle !
La boucle while (tant que), elle a deux façon de s'écrire :
do { // instructions } while(condition);
ou
while (condition) { // instructions }
La première méthode oblige à faire un passage dans la boucle et est obligatoire si la condition porte sur une chose qui est faîte/testée pendant les instructions de la boucle while. La seconde quant à elle, permet de ne pas faire dutout les instructions de la boucle while si les conditions requises ne sont pas là.
for(instruction de départ;test de fin de boucle;instruction de fin de boucle) { // instructions; }
Nous pouvons profiter de cette instruction pour faire un test sur les formateurs avec la boucle suivante :
for(i=-2500;i<2500;i=i+100) // i=i+100 peut aussi s'écrire i+=100 { printf("Pour i=%d, en %%c => %c, en %%u => %u, en %%f => %f, en %%e => %e, en %%s => %s\n",i,i,i,i,i,i); }
Comparez les résultats du %s avec vos voisins.
Si une seule instruction est à faire dans la boucles (à l'intérieur des {...}), alors les accolades deviennent inutiles.
Si vous mettez directement un ; après le test comme for(initialisation;test;icrémentation); alors la boucle ne fera que tourner sur elle même sans répéter les instructions suivantes : elle fera l'instruction se trouvant avant le ; en l'absence d'accolade ouvrante juste après. Si on n'a qu'une instruction à faire, on peut donc supprimer les acolades et mettre notre instruction suivi du ; juste après le for (ou le while ou le do), comme sur l'exemple suivant :
for (initialisation;test;icrémentation) instruction;
Parfois il y a des choix à faire en fonction de différents critères, pour cela on dispose de différentes fonctions.
if (condition) { instruction1; } else { instruction2; }
Si la condition est vérifiée on fait instruction1. Il est posible (non obligatoire) de mettre ensuite un else qui définit alors instruction2 qui sera fait au cas où la condition ne soit pas vérifiée.
switch a { case 1 : instruction1_1; // venir ici si a vaut vaut 1 instruction1_2; case 2 : instruction2; // venir ici si a vaut vaut 2 break; // sortir du with si on arrive ici case 3 : instruction3; // venir ici si a vaut 3 default : instruction4; // venir ici si on n'est dans aucun des cas définis }
En fonction de la valeur de a, on va a différents points. Si a vaut 1, on va à case 1, 2 à case 2, 3 à case 3 ou à defaut si on est dans aucun des cas défini. Remarquez la présence de l'instruction break, elle indique qu'il faut sortir du switch. Donc, si a vaut 1, on fait instruction1_1,instruction1_2 et instruction2, 2 instruction2, 3 instruction3 et instruction4, aucun des 3 cas défini instruction4.
Un pointeur est une adresse mémoire dans laquelle on a ou on peut mettre des données. C'est un objet puissant, mais dangereux aussi, car il est possible de le modifier et ensuite de le faire pointer vers un endroit non voulu et faire planter le programme ou plus encore. Dompter les pointeurs est très important pour faire de bons programmes.
Quand on définit une variable, on lui réserve un emplacement mémoire. Donc un "int i;", réserve la place d'un entier qu'on va appeler i. Pour avoir l'emplacement d'une variable, on fait précéder son nom d'un et commercial, avec notre variable i, ceci donne : &i.
Il est aussi possible de définir un pointeur. Prenons par exemple un pointeur sur quelquechose qu'on va appeler ptr, nous allons le définir de la façon suivante : "quelquechose * ptr". Le * signifie que nous définissions un pointeur. Le quelquechose peut être n'importe quel type de variable et il peut même déjà être un pointeur sur autrechose, dans ce cas, nous venons de faire :autrechose ** ptr. Cette notion de pointeur sur pointeur sur... génère la notion de tableau à dimension multiple.
Parfois on a juste besoin d'un pointeur. Mais il arrive aussi qu'on veuille générer un tableau. Dans ce cas, on doit réserver de la place pour le tableau et notre pointeur indiquera la place réservée. Pour celà, nous avons les fonctions suivantes.
#includevoid * calloc (size_t nmemb, size_t size); void * malloc (size_t size); void free (void * ptr); void * realloc (void * ptr, size_t size);
tableaux
Passage de paramètres : voir le point suivant.
Afin d'améliorer la lisibilité d'un programme, de ne pas réécrire une série d'instructions plusieurs fois, on peut/doit faire des fonctions. L'avantage de ses fonctions, si elles sont bien pensées, c'est qu'on pourra les reprendre dès qu'on fera un autre programme. Avec le temps et l'expérience, on peut se faire une bibliothèque de fonctions qui simplifieront la conception de nouveaux programmes.
type_de_la_sortie nom_de_la_fonction(paramètres à fournir à la fonction)
Une fonction peut renvoyer une valeur, ou on peut lui donner comme paramètre d'entrée des pointeurs sur des valeurs qu'elle pourra modifier.
C'est une notion très importante de la programmation. Elle consiste à faire appeler une fonction par elle même, mais grace aux variables locales, à chaque appel, les variables sont réinitialisées et on retrouve les valeurs précédentes quand on revient de la fonction.
(1) cliquer : appuyer sur le bouton gauche de la souris. Si on donne une information droit/centre, c'est qu'on parle des autres boutons : le droit et le centre est souvent la molette sur laquelle on doit ouvoir aussi appuyer. Sous Linux quand on a une souris avec deux boutons, le click centre est remplacé par un click 'droit-gauche' simultané.