
Le but de ce document est d'apprendre à
programmer le plus vite possible, en découvrant la théorie
petit à petit en même temps que la pratique, ceci dans le
langage soit-disant réservé aux professionnels, et le plus
déconseillé aux débutants.
Il s'adresse aux débutants, mais peut
très bien convenir aussi aux programmeurs désirant apprendre
le C (c'est d'ailleurs mon cas en ce moment !), car les préliminaires
sont réduits à l'essentiel.
Les explications pour non-programmeurs sont en
rouge.
Les termes à retenir sont en gras.
Petite présentation du C
Le C est un langage de programmation compilé,
c'est à dire qu'il est traduit en langage machine par un
compilateur, qui transforme le code source en code exécutable.
D'autres langages (par ex. certains Basic) sont interprétés,
et échappent à la phase de compilation : chaque instruction
du code source est traduite en code exécutable juste avant son exécution.
Les langages compilés sont
- plus rapides (les instructions sont déjà
en langage machine).
- moins souples : avant d'exécuter
le programme, une compilation est nécessaire si le programme a été
modifié.
- dépendants de la machine : la compilation
est spécifique à chaque système (alias plate-forme
matérielle). Pour utiliser le programme sur un autre système,
il faudra reprendre le code source, et le recompiler spécifiquement
pour ce nouveau système.
Les langages interprétés sont
- plus souples (pas besoin de compilation)
- souvent indépendants de la machine
(le code source identique sera traduit différemment pour toutes
les machines pour lesquelles un interpréteur de ce langage
a été prévu).
- plus lents (l'interprétation en temps
réel consomme de la puissance)
Le C est compilé donc rapide, mais assez portable (on parle de portabilité multi-plates-formes), ceci dans sa version normalisée ANSI (bien sûr après recompilation).
Il est très structuré et très exigeant au niveau syntaxique, c'est un langage d'assez bas niveau.
On parle de langage de bas-niveau pour
un langage dont les instruction sont proches des instructions du processeur,
par exemple additionner deux entiers, et de langage de haut-niveau
pour un langage dont les instructions sont totalement détachées
de celles du processeur, par exemple afficher une fenêtre de dialogue.
Une instruction de haut-niveau peut représenter des millions d'instructions
de bas-niveau.
Les codes sources en langage de haut-niveau
sont donc beaucoup plus courts qu'en bas-niveau mais sont souvent plus
lents, car en programmant en bas-niveau, on ne retient que les instructions
strictement nécessaires, ce qui est impossible en haut-niveau (on
ne peut rentrer dans le détail des opérations effectuées).
Mise en pratique
Ouvrez un éditeur de textes pour taper
votre premier programme en C (tapez ce qui est en bleu)
Exemple A
Bonjour le monde !
A-1. Directives de précompilation
Hormis quelques instructions de base, les instructions
du langage C sont contenues dans des bibliothèques. Pour éviter
de surcharger le code executable, on n'y retient que les bibliothèques
utilisées. Il faut donc préciser quelles bibliothèques
vous comptez utiliser (c'est une directive passée au pré-compilateur,
dans le code source lui-même).
Ici on va inclure (include)
la bibliothèque d'entrées/sorties standard (il s'agit
de tout ce qui permet à l'ordinateur de communiquer avec l'extérieur,
comme l'écran, le clavier, l'imprimante, les lecteurs de disquettes,
CD, disques durs). Celle-ci s'appelle en principe stdio.h
.
#include <stdio.h>A-2. Corps du programme
On va maintenant commencer le programme lui-même, par main (principal en anglais, désigne la partie principale du programme par opposition aux sous-programmes).
void main (void)
{
Pourquoi void ?Une fonction est une instruction renvoyant une valeur souvent à partir d'un ou plusieurs paramètres qu'on lui fournit.
exemple : valeur=fonction(parametre1, parametre2, ...);Ici, on ne se soucie pas des paramètres ni de la valeur de renvoi, donc on les évite par void (éviter en anglais).
Pourquoi une accolade ouvrante { sur la ligne suivante ? Pour indiquer le début du programme indiqué parmain, la fin sera signalé par une accolade fermante } .A-3. Contenu du programme
Quelque soit le langage, le premier programme à écrire pour l'apprendre consiste à écrire "Bonjour le monde !" à l'écran : cela permet de vérifier si le compilateur ou l'interpréteur fonctionne, et de savoir comment faire afficher le résultat d'un programme par la suite.
printf ("Bonjour le monde !\n");
Pourquoi printf ? Print signifie imprimer en anglais, f signifie formater.Imprimer ne signifie ici ni imprimer sur l'imprimante, ni écrire à l'écran, mais envoyer vers la sortie standard, qui est généralement fixée sur l'écran, mais qui peut être détournée vers l'imprimante par exemple.
Formater consiste à spécifier (par des codes passés à la commande printf) de quelle manière il faut afficher ce qu'on lui donne en paramètre.
Ici, on utilise \n , ( \ , backslash annonçant un code, et n(sans doute pour le n de new line) faisant un saut à la ligne suivante).Pourquoi un point-virgule en fin de ligne ? Pour signaler la fin de l'instruction et le début de la suivante. Historiquement, les instructions étaient séparées soit par un point-virgule, soit par un retour à la ligne (par exemple en BASIC). Seul la première solution a été conservée.
Attention, on ne met de point-virgule qu'après des instructions, pas après les repères faisant partie de l'articulation des instructions entre elles, comme main, if then else, for, while, until (que vous verrez plus tard).
}
Explication de cette accolade : plus haut, concernant main.Listing complet de l'exemple A :
#include <stdio.h>
void main (void)
{
printf ("Bonjour le monde !\n");}
A-4. Compilation et exécution
Après avoir enregistré votre code source, par exemple sous le nom premier.c, compilez-le. Je ne peux pas vous en dire plus sur ce point sauf si vous êtes sous Unix/Linux et disposez comme moi de GCC.
Dans ce cas, tapez la ligne de commande suivante : gcc premier.c
Maintenant, lancez votre programme exécutable, fabriqué par le compilateur. Dans mon cas, celui-ci toujours a.out (sauf si on demande un autre nom au compilateur).
Toujours si vous êtes dans mon cas, tapez ./a.out (./ pour le chercher dans le répertoire courant au lieu de /usr/bin)
Normalement, ca marche.
Exemple B
Bonjour Toto !
B-1. Comment fournir des informations au programme
Ce nouveau programme ne dira plus "Bonjour le
monde !", mais "Bonjour votre prénom", après vous
l'avoir demandé. Pour cela, il faudra stocker votre prénom
dans une variable (une case de la mémoire de l'ordinateur, dotée
d'un nom). Ensuite on affichera le contenu de cette variable.
#include <stdio.h>Listing complet de l'exemple B :
void main (void)
{char prenom[50]="";}
char est la commande pour déclarer une variable de type chaine de caractères. prenom est le nom de la variable, et [50] signifie que sa longueur maximale est de 50 caractères.printf("Quel est votre prénom ?\n");
scanf("%s",prenom);
scanfest la commande permettant de lire des chaînes de caractère sur l'entrée standard, ici le clavier, jusqu'à ce que la touche <Entrée> soit pressée. f signifie là encore formatage, c'est à dire qu'on annonce à la commande scanf la forme des donées qu'elle recevra. % annonce un code, et s (pour string, chaine en anglais) signifie que la variable suivante (donc prenom) est une chaine de caractères.printf ("Bonjour %s !\n",prenom);
Un nouveau code pour la commande printf :%s (le même code que pour scanf) signifie qu'il faudra afficher ici une chaine, qui est fournie comme paramètre plus loin. Ici, la variable prenom.
#include <stdio.h>
void main (void)
{
char prenom[50]="";}
printf("Quel est votre prénom ?\n");
scanf("%s",prenom);
printf ("Bonjour %s !\n",prenom);
Comme avant, compilez et exécutez.
Bonjour Anne, Myriam, Cyril, Pierre !
C-1. Comment faire des opérations répétitives
Supposons un ordinateur particulièrement
amical, qui voudrait dire bonjour à de nombreuses personnes, et
ce avec leur prénom.
Avec 5 personnes, cela donnerait ça (n'essayez
pas cet exemple...) :
Listing complet de l'exemple C :
#include <stdio.h>
void main (void)
{char prenom[50]="";}
printf("Quel est votre prénom ?\n");
scanf("%s",prenom);
printf ("Bonjour %s !\n",prenom);
printf("Quel est votre prénom ?\n");
scanf("%s",prenom);
printf ("Bonjour %s !\n",prenom);
printf("Quel est votre prénom ?\n");
scanf("%s",prenom);
printf ("Bonjour %s !\n",prenom);
printf("Quel est votre prénom ?\n");
scanf("%s",prenom);
printf ("Bonjour %s !\n",prenom);
printf("Quel est votre prénom ?\n");
scanf("%s",prenom);
printf ("Bonjour %s !\n",prenom);Mais avec 300000 personnes ???
La solution : faire une boucle, répétée 300000 fois. Dans l'exemple, on se contentera de 10 fois...
#include <stdio.h>
void main (void)
{char prenom[50]="";}
int i;
int déclare un entier comme variable (int pour integer, entier en anglais). Cet entier va servir de compteur d'incrémentation pour la boucle : au nième tour, i vaudra n.for (i=1;i<=10;i++)
C'est du chinois ? Alors lisez attentivement TOUT ce qui suit.
for est l'instruction permettant de répéter une boucle (entre accolades après le for, POUR des valeurs définies du compteur d'incrémentation, ici i. D'où l'anglais for.
Initialement, en Basic par exemple, la syntaxe était pour i de 1 à n (for i=1 to n). On fixait donc la valeur de début et de fin, et i s'incrémentait de 1 à chaque tour. En rajoutant step 2, on pouvait l'incrémenter par 2, ou par n'importe quel entier même négatif.
Le C a davantage de possibilités sur ce plan :
le premier paramètre de for assigne la valeur de départ à i, le second précise la condition nécessaire pour réexécuter la boucle (ici, i inférieur ou égal à 10, c'est comme si on indiquait traditionnellement la dernière valeur de i, mais on pourrait théoriquement raffiner davantage).
Enfin le dernier paramètre indique l'opération d'incrémentage à effectuer. Ici, c'est comme si on faisait i=i+1 (c'est à dire donner à i la valeur de i+1). Mais dans ce cas le processeur aurait d'abord donne à i la valeur de i, puis y aurait rajouté 1. On passe donc directement à la seconde étape (c'est 2 fois plus rapide), en notant i+=1. Mais l'informaticien-type étant paresseux, il existe un raccourci pour la valeur 1 (i++) ou la valeur -1 (i--).
{printf("Quel est votre prénom ?\n");}
scanf("%s",prenom);
printf ("Bonjour %s !\n",prenom);
#include <stdio.h>
void main (void)
{
char prenom[50]="";}
int i;
for (i=1;i<=10;i++)
{printf("Quel est votre prénom ?\n");}
scanf("%s",prenom);
printf ("Bonjour %s !\n",prenom);
Après avoir vu comment importer et afficher des données,
nous allons apprendre à les retraiter . Mais pour traiter un grand
nombre de données, il est bien utile de les stocker, afin d'éviter
de les saisir à chaque essai du programme de retraitement. Nous
verrons donc d'abord le stockage des données.
Exemple D
Stockage de données
D-1. Les différents types de fichiers
Les données seront stockées sous
forme de fichiers sous le disque. Mais il existe 2 sortes de fichiers :
les fichiers séquentiels et les fichiers à accès
direct.
- Les fichiers séquentiels se lisent et
s'écrivent normalement donnée par donnée, du début
à la fin du fichier.
- Les fichiers à accès directs
procurent un accès direct à la donnée recherchée,
sans avoir à lire les précédentes.
Nous ne traiterons ici que les fichiers séquentiels car ils sont plus simples (et car je n'ai pas encore étudié les fichiers à accès direct en C !!!)
D-2. L'exemple
Ce programme va écrire dans un fichier tous les prénoms
rentrés par l'utilisateur (un éditeur de textes comme NotPad
sous Windows, SimpleText sous Mac OS, VI sous Unix aurait très bien
pu faire l'affaire, mais cela aurait été moins rigolo).
On commence comme d'habitude...
#include <stdio.h>Listing complet de l'exemple D :
void main(void)
{int i;}
char prenom[30];Ca se corse :
FILE *FICHIER1;
Ceci déclare le pointeur FICHIER.FICHIER1=fopen("/home/chr/premierfichier.txt","w");
Nous ouvrons le fichier premierfichier.txt en écriture (fopen pour file open, fichier et ouvrir en anglais, w=write, écrire en anglais).
Le pointeur FICHIER servira désormais à désigner le fichier premierfichier.txt. En effet, celui-ci ne peut être travaillé directement, il faut préalablement l'ouvrir, dans un endroit de la mémoire (parfois appelé buffer, mémoire tampon en français) désigné par ce pointeur. Par convention, les pointeurs de fichiers sont souvent en majuscules, pour plus de lisibilité, car il s'agit souvent de la partie la plus cruciale des programmes, car la plus importante et la plus dangereuse (risque de perte de données).for(i=1;i<=10;i++)
{printf("Rentrez un prénom :\n");scanf("%s",prenom);}
fprintf(FICHIER1,"%s",prenom);
Au lieu d'écrire à l'écran avec printf, on écrit dans un fichier, désigné par son pointeur, ici FICHIER1 avec fprintf (le f devant pour file, fichier en anglais).fclose(FICHIER1);
On fait le ménage, en fermant le fichier désigné par FICHIER1. C'EST OBLIGATOIRE. Tout ce qui précède ne sert à rien si l'on ne ferme pas le fichier.Règles générales quant à l'ouverture et à la fermeture des fichiers : ouvrez-les aussi tard que possible, et fermez les aussi tôt que possible (dès la dernière modification). Car un fichier ouvert est une brèche de sécurité : si une coupure de courant ou un plantage intervient avant la fermeture d'un fichier, toutes les modifications seront perdues.
Vous pouvez vérifier le résultat très simplement en ouvrant le fichier créé avec un éditeur de texte (ou tapez more premierfichier.txtsous Unix).
Vous devriez voir à peu près ceci :AnneCyrilChristopheMatthieuMyriamEléonore...Comme vous pouvez le constater, c'est inexploitable. Il faut donc rajouter un séparateur entre les entrées du fichier, par exemple \n, ou | ou une virgule, ou n'importe quoi d'autre (mais le \n est plus pratique). Faute de quoi il ne sera plus possible de distinguer les différentes entrées du fichier entre elles sauf dans le cas où celles-ci sont de longueur fixe et connue.
Changez donc la lignefprintf(FICHIER1,"%s",prenom);parfprintf(FICHIER1,"%s\n",prenom);
On écrit \n (retour à la ligne) à la suite de chaque prénom, pendant l'inscription dans le fichier.
Attention, il arrive souvent que les entrées contiennent déjà le caractère de séparation. Dans ce cas, le fichier en contiendra 2 à la suite, c'est à dire une entrée vide. Source importante de bugs...
#include <stdio.h>
void main(void)
{
int i;}
char prenom[30];
FILE *FICHIER1;
FICHIER1=fopen("/home/chr/premierfichier.txt","w");
for(i=1;i<=10;i++)
{printf("Rentrez un prénom :\n");scanf("%s",prenom);}
fprintf(FICHIER1,"%s\n",prenom);
fclose(FICHIER1);
Exemple E
Lecture et retraitement de données
Nous allons maintenant écrire un programme
de traitement automatisé des données stockées ci-dessous.
Imaginons que tous ces prénoms aient été tapés
par Gaston Lagaffe, et aient pour but un publipostage (lettre identique
sauf l'identitié du destinataire, envoyée à de nombreux
exemplaires) auprès de 10000 de ses fidèles lecteurs, pour
leur annoncer la prochaine BD. Malheureusement, les prénoms ont
été rentrés en plusieurs fois, tantôt en minuscules,
tantôt en majuscules, tantôt la première lettre seule
en majuscules.
Désirant obtenir un résultat de
qualité (au lieu de "Bonjour mATTHieu, j'ai le plaisir de vous annoncer
la sortie de la nouvelle BD de Gaston Lagaffe"), mais ne voulant à
aucun prix saisir à nouveau ces prénoms, Gaston vient vous
voir.
Il faudra donc écrire un programme qui
lit toutes les données du fichier, passe la première lettre
de chaque prénom en majuscule et les suivantes en minuscule, puis
les écrit dans un nouveau fichier. Nous ouvrirons parallèlement
les deux fichiers, et chaque donnée sera écrite aussitôt
lue et modifiée. C'est un procédé peu courant, mais
plus simple que le procédé habituel qui sera étudié
dans l'exemple suivant.
#include <stdio.h>Listing complet de l'exemple E#include <string.h>
Cette bibliothèque contient des fonctions de manipulation de chaine de caractères. Juste ce qu'il nous faut ici.void main(void)
{int i,prem,l;}
char prenom[30],prem[2];
prem servira à retenir le caractère en cours de conversion. Mais il faut penser au caractère de fin de chaine, donc longueur 2 !!!
FILE *LISTE1,*LISTE2;LISTE1=fopen("prenoms.txt","r");
Ouverture en lecture seulement : rpour read, lire en anglais. Il s'agit du fichier source.LISTE2=fopen("jolisprenoms.txt","w");
Ouverture en écriture seulement. Il s'agit du fichier destination.Au lieu d'utiliser la boucle for, qui ne marche que pour un nombre connu d'enregistrements, nous allons utiliser la boucle while, car Gaston ne se rappelle plus très bien du nombre de prénoms du fichier. Cette instruction exécute la boucle suivante tant que la condition entre parenthèses qui lui est donnée est vraie.
Ici, nous allons lire dans le fichier jusqu'à la fin de celui-ci. La fonction feof(*FICHIER) renvoie VRAI si la lecture du fichier désigné par le pointeur *FICHIER est terminée. Il faudra donc utiliser la condition inverse, celle qui renvoie FAUX quand la lecture du fichier est terminée et VRAI autrement.. Pour inverser une fonction booléenne, il suffit en C de la précéder d'un point d'exclamation.
C'est de l'algèbre booléenne, du nom du célèbre mathématicien Boole qui a codifié les calculs portant sur des valeurs vraies ou fausses. C'est la base du fonctionnement de tous les ordinateurs actuels, ceux-ci ne manipulant que des 0 ou des 1. (0=faux et 1=vrai).
while (!feof(LISTE1))
{fscanf(LISTE1,"%s\n",prenom);Attention à la syntaxe : la condition globale doit ^etre entre parenthèses comme chaque condition élémentaire. Toujours pas de point virgule.
Comme scanf, mais on lit dans un fichier au lieu de l'entrée standard (le clavier en principe). D'ou comme toujours le f de file.
Il faut préciser en plus dans quel fichier on veut lire, en indiquant son pointeur. On indique également dans la chaine de formatage que la chaine à lire se finit par \n. Si l'on avait utilisé un autre séparateur dans l'écriture du fichier, on l'aurait indiqué ici.printf("Ancien prénom : %s",prenom);
On affiche à l'écran le prénom initial, pour contrôler le résultat.
Avant de passer à la suite, consultez la table ASCII , qui indique les codes numériques correspondant aux caractères. Vous verrez que les majuscules et les minuscules sont décalées de 32 dans la table, y compris les caractères accentués. Pour mettre une majuscule en minuscule, il suffit de rajouter 32 à son code, et inversement. Or le C permet de manipuler un caractère aussi bien en tant que caractère qu'en tant que code. On peut donc additionner ou soustraire 32 à une lettre !!!prem=prenom[0];
Uniquement pour simplifier les écritures. Attention, il s'agit d'un entier représentant un caractère par son code, pas d'un caractère.Pour déterminer si la première lettre est en minuscule, nous allons utiliser une condition ou expression conditionnelle. En examinant la table ASCII, vous avez peut-etre remarqué que les minuscules sont regroupées des codes 97 à 122 (non accentuées) et 224 à 239 (accentuées). Donc si prem, le premier caractère de prenom, est compris dans ces valeurs, il s'agit d'une minuscule.
Faisons le test, sachant que && signifie ET et || signifie OU. En français, cela donne : SI (prem>=97 et prem<=122) ou (prem>=224 et prem<=239). En anglais, IF... Un éventuel SINON devient ELSE, un SINON SI devient ELSE IF
if ((prem>=97 && prem<=122)||(prem>=224 && prem<=253)){}prenom[0]-=32;}
La première lettre de prenom est une minuscule, donc on lui soustrait 32 pour en faire une majuscule.Nous allons maintenant règler le sort des lettres suivantes :
Pour chaque lettre du prenom, nous allons répéter l'opération effectuée sur la première lettre, mais à l'envers : si c'est une majuscule, nous en ferons une minuscule. Pour connaitre le nombre de tours de la boucle, nous allons utiliser la fonction strlen(), qui donne la longueur d'une chaine (cette fonction appartient à la bibliothèque string.h). On y enlève 1 car le premier caractère est déjà traité.
l=strlen(prenom)-1;
for (i=1;i<=l;i++)
Attention, cette boucle ne démarre pas au premier caractère, mais au second, le premier étant en fait le caractère à la position 0.
{prem=prenom[i];}
if (prem>=65 && prem<=90)||(prem>=192 && prem<=221)
Les valeurs sont changées par rapport à ci-dessus pour correspondre aux majuscules.
{prenom[i]+=32;}
Cette fois-ci, on RAJOUTE 32, pour en faire une minuscule.
Le prénom est maintenant correct, on va l'écrire dans le fichier destination.
fprintf(LISTE2,"%s\n",prenom);L'écran affichera un compte-rendu de l'opération :
printf(" -> Nouveau prénom : %s\n",prenom);Le dernier prénom a été lu, modifié, et écrit, il faut maintenant penser à fermer LES DEUX fichiers.
fclose(LISTE1);
fclose(LISTE2);Vérifiez le contenu du fichier créé avec un éditeur de texte.
#include <stdio.h>
#include <string.h>
void main(void)
{
int i,prem,l;}
char prenom[30];
FILE *LISTE1,*LISTE2;
LISTE1=fopen("prenoms.txt","r");
LISTE2=fopen("jolisprenoms.txt","w");
while (!feof(LISTE1))
{fscanf(LISTE1,"%s\n",prenom);}
printf("Ancien prénom : %s",prenom);
prem=prenom[0];
if ((prem>=97 && prem<=122)||(prem>=224 && prem<=253))
{prenom[0]-=32;}
l=strlen(prenom)-1;
for (i=1;i<=l;i++)
{prem=prenom[i];}
if ((prem>=65 && prem<=90)||(prem>=192 && prem<=221))
{prenom[i]+=32;}
fprintf(LISTE2,"%s\n",prenom);
printf(" -> Nouveau prénom : %s\n",prenom);
fclose(LISTE1);
fclose(LISTE2);
Copyright Lixium SARL 2007