Petite initiation au langage C et au graphisme SDL

Transcription

Petite initiation au langage C et au graphisme SDL
1
Petite initiation au langage C
et au graphisme SDL
Lorsque l’on utilise l’ordinateur pour résoudre des problèmes qui nous sont posés, pas besoin d’être
virtuose en programmation. Un bagage minimal de connaissances suffit, et comme je vais utiliser le
langage C, disons qu’il s’agit plutôt de faire du C - - que du C + +. Pourquoi le langage C ? Parce que
c’est le plus œcuménique. Mais si vous préférez aller plus loin avec d’autres langages que le C,
poursuivez votre chemin et affirmez votre originalité.
Si vous connaissez déjà des bribes de programmation, ce qui suit vous donnera des éléments sur la
syntaxe du langage C, étant entendu que la structure interne d’un programme est la même en C qu’en
Pascal, Basic, Fortran,… Par exemple, ce qui s’écrit { en C s’écrit begin en Pascal. Si vous ne
connaissez quasiment rien à la programmation, le rapide survol du langage C qui suit vous donnera un
premier aperçu des moyens de traitement de problèmes via l’ordinateur. Au fond, tout se résume en
quatre termes : boucles, tests, tableaux, fonctions. Il convient de faire avec !
Et maintenant quel langage C choisir ? Celui que vous voulez. Pour satisfaire aux rumeurs qui
courent sur Internet, j’ai pris Code Blocks, ayant lu que son principal concurrent Dev-C++ n’était plus
développé depuis 2005, mais par la suite j’ai appris l’existence de wxDevC++, alors les rumeurs… En
tout cas la première chose à faire est de télécharger le langage. Je vais vous dire comment installer
Code Blocks sous Windows, et même Windows 10,1 mais si vous préférez le faire sous Linux, cela ne
sera pas plus compliqué.
Télécharger Code Blocks sous Windows en 5 minutes chrono
Allez chez Google, et demandez « télécharger code blocks mingw ». Prendre la première
proposition qui vient, du style Download binary-Code ::Blocks. Vous êtes envoyé sur un site où dans
la rubrique Windows on vous propose Codeblocks-16.01mingw-setup.exe à télécharger avec
Sourceforge ou un autre. Téléchargez et demandez l’exécution du setup.exe. Quelques secondes plus
tard, le répertoire CodeBlocks se trouve installé à l’intérieur de vos Programmes (Program
Files(x86)) mais vous pouvez demander de l’installer ailleurs si vous préférez. A l’intérieur de ce
répertoire C:\Program Files (x86)\CodeBlocks, vous allez trouver l’exécutable qui s’appelle aussi
codeblocks, parfaitement repérable grâce à son carré divisé en quatre petits carrés coloriés (rouge, vert,
jaune, bleu). Cliquez dessus, à moins que vous ne fassiez un raccourci à partir de votre bureau. Et la
page CodeBlocks apparaît plein écran. Dans le jargon informatique, cette page constitue l’IDE
(environnement de développement intégré), le centre de pilotage à partir duquel vous allez écrire votre
programme dans l’éditeur de texte, puis lancer la compilation.
Premier programme : bienvenue chez les débiles
Dans la page Code::Blocks on vous propose, au centre de l’écran : Create a new project ; cliquez
dessus. Ou bien vous êtes déjà dans une page avec une ligne en haut de l’écran qui commence à
gauche par File, et vous cliquez sur File ; en haut de la colonne apparraît New et vous cliquez sur
New ; dans la fenêtre qui s’ouvre choisissez Project et cliquez dessus.
1
Ce document a été actualisé en 2016, à l’époque de Windows 10. Mais cela vaut aussi bien pour les
Windows d’avant.
2
Dans la fenêtre qui apparaît, cliquez sur Console application. Faites next pour aller d’une page à la
suivante. On va vous demander si vous voulez C ou C++. Choisissez C. Puis on vous demande de
donner un nom à votre projet. Tapez par exemple dans Project title : bonjour. Puis next, et on vous
annonce qu’une configuration release est créée. C’est fini.
Vous voyez apparaître, dans la colonne Projets à gauche de l’écran, le carré bonjour en dessous de
Workspace. Ainsi dans votre espace de travail (workspace) se trouve intégré votre projet bonjour,
encore vierge. Cliquez dessus, et vous voyez apparaître Sources. Cliquez sur Sources, et vous voyez
apparaître main.c. Cliquez sur main.c et là est déjà édité un petit programme déjà tout prêt. Demandez
build (un petit rond jaune sur la deuxième ligne de l’écran) puis run (un triangle vert). Une fenêtre à
fond noir apparaît sur l’écran, avec marqué dessus hello, et au-dessous un message en anglais sans
aucun intérêt. Cela prouve que votre codeblocks marche ! 2
Maintenant aménagez ce programme. Commencez par supprimer #include <stdlib.h> qui ne sert à
rien ici. Puis remplacez « Hello World !\n », par « Bonjour\n » ou ce que vous voulez. Rajouter un
getchar() qui provoque une pause, où seul Bonjour reste écrit sur l’écran. Quand cela vous suffit,
c’est-à-dire le plus vite possible, appuyez sur une touche, la fenêtre d’exécution se ferme, le message
en anglais n’aura plus le temps de se manifester, et vous vous retrouvez dans votre IDE Code Blocks.
Vous avez fait votre premier programme en C :
#include <stdio.h>
int main()
{
printf(« Bonjour\n ») ;
getchar() ; return 0 ;
}
Pour sauvegarder ce programme, allez dans la première colonne File en haut à gauche, et
demandez par exemple Save everything. Ensuite vous pouvez tout fermer, et quitter codeblocks (allez
sur votre projet bonjour, puis un clic à droite sur la souris donne un menu où l’on trouve close
project). Votre projet va être pieusement conservé dans le répertoire c:\Program Files\CodeBlocks
sous le nom bonjour.3 Et en allant visiter ce répertoire bonjour, vous allez constater qu’il contient 5
fichiers au moins, tout ça pour le prix d’un seul bonjour. Plus tard si vous voulez refaire marcher ce
programme bonjour, votre IDE Codeblocks étant déjà ouverte, vous faites file puis open, et quand
vous arrivez au nom de votre programme bonjour, vous cliquez et dans la fenêtre obtenue vous cliquez
sur bonjour.cbp. Votre programme réapparaîtra dans la colonne des programmmes.
Maintenant passons aux explications sur la programmation. Voici comment se présente en général
un programme en C :
2
En fait dans les temps anciens j’ai eu un problème avec Windows Vista, avec un refus total de compiler le
moindre programme. Faire un programme stupide de deux lignes pour afficher Bonjour, et en plus qu’il ne
marche pas, c’est plutôt frustrant. En fait si j’ai bien compris, Windows est un centre de haute sécurité. Il refuse
l’exécution de programmes qu’il ne connaît pas. C’est ce qui se passait avec la laconique réponse de Code
Blocks lors de la compilation du programme : permission denied (permission refusée). Pour m’en sortir, j’ai dû
désactiver le contrôle sur les comptes d’utilisateur. Si cela vous arrive aussi, voici comment faire : aller dans le
panneau de configuration, puis dans la rubrique comptes d’utilisateur. Là, aller à ajouter ou supprimer des
comptes d’utilisateur. On tombe sur choisir le compte à modifier. Aller en bas de la page et cliquer sur ouvrir la
page principale comptes d’utilisateur. Puis cliquer sur activer ou désactiver le contrôle des comptes
d’utilisateurs. Et une fois arrivé là, enlever la petite croix qui activait le contrôle.
3
Si vous préférez, nous pouvez créer un dossier dans lequel vous allez mettre vos programmes. Lorsque vous
créez un nouveau programme, vous lui donnez un titre, et en dessous vous indiquez la position de ce dossier dans
folder to create project in.
3
#include <stdio.h>
#include <stdlib.h> et éventuellement d’autre include de fichiers .h
main()
{
…
}
On commence par mettre quelques #include de fichiers .h où se trouvent certaines fonctions du
langage, celles que nous allons utiliser dans le programme, et qui sans cela ne seraient pas reconnues.
Il faut passer par là, se dire que pendant des années on va avoir sous les yeux, et taper des milliers de
fois ce genre d’idioties. Cela permet au moins de s’aiguiser les doigts en pensant à autre chose. Puis
vient le programme principal int main(). Nouvelle plaisanterie : le main est considéré comme une
fonction qui ne prend aucun argument (aucune variable) d’où les parenthèses (), mais qui ramène un
nombre entier, d’où le int devant main. Cela explique la présence du return 0 à la fin du programme.
La fonction main ramène un entier, et cet entier est 0. Vous pouvez mettre si vous préférez return
1000, et le programme principal ramènera 1000 ! D’ailleurs vous le verrez apparaître dans le message
final sur l’écran, après Bonjour. Si cette situation vous exaspère, vous pouvez toujours simplifier, et
écrire le programme suivant :
#include <stdio.h>
main()
{ printf(« Un petit bonjour\n ») ;
}
Cela va marcher, mais vous recevrez un avertissement (warning) pour votre mauvaise conduite.
En fait la seule chose intéressante, c’est de remplir l’intérieur du programme principal, entre les
deux accolades {…},4 où vont se trouver une succession d’instructions, toutes terminées par un
point virgule, comme vous avez déjà pu le constater. C’est là que tout se passe, car c’est dans cette
zone que le problème que l’on veut traiter sera écrit et transmis par nos soins à la machine, et se
trouvera résolu lors de l’exécution du programme, si tout va bien ! Evidemment avec notre affichage
de Bonjour, une chose que le moindre traitement de texte ferait beaucoup mieux et plus simplement,
on n’a pas l’air malin.5
Affichage sur l’écran
Comme on l’a vu, on utilise la fonction printf(). Premier miracle, lorsqu’on exécute ce programme:
l’écran ne reste pas désespérément noir. Bonjour apparaît en haut à gauche de l’écran, suivi d’un
message en anglais de fin d’exécution du programme, où l’on vous dit aussi d’appuyer sur une touche
pour fermer la fenêtre. Si vous ne voulez pas que votre Bonjour soit pollué par ce message, vous
pouvez retarder l’échéance, en faisant :
#include < stdio.h>
4
Dans ce qui suit, nous omettrons souvent d’écrire les #include, les déclarations de variables, et même le
main() pour nous concentrer sur l’essentiel : le contenu du programme principal. D’ailleurs lorsque l’on fait un
nouveau programme, il suffit de faire un copier-coller d’un ancien programme, puis d’enlever le corps principal
de l’ancien programme en écrivant le nouveau à sa place.
5
Pour la petite histoire, signalons qu’il y a une vingtaine d’années et même plus, le langage Visual C++ de
Microsoft coûtait à peu près le prix d’un Word Office aujourd’hui, et qu’il était vendu avec une brochure
explicative de 250 pages, où le seul programme qui y était expliqué était l’affichage de Hello world, sous une
forme sophistiquée quand même. Résultats des courses : ventes calamiteuses. Peu de temps plus tard, on
retrouvait une disquette Visual C++ dans des livres à 200 F. Un peu plus tard, on en arrivait à une version
gratuite sur Internet.
4
main()
{ printf(« Bonjour ») ; getchar() ; return 0 ;}
La fonction getchar() attend sagement que vous appuyiez sur une touche avant d’envoyer le
message de fin.6
Venons-en au symbole \n, que nous avions placé initialement dans notre « Bonjour\n ». Après
l’écriture de Bonjour, il fait descendre d’une ligne. Essayez par exemple printf(« Bonjour\n Et adios »)
et l’écriture se fait sur deux lignes. Si l’on fait plutôt printf(« \n\nBonjour »), Bonjour apparaît aussi
mais deux lignes en-dessous du haut de l’écran. Si l’on préfère printf(«
Bonjour »), Bonjour
apparaît en haut de l’écran, mais décalé vers la droite. C’est dans la bibliothèque stdio.h que se trouve
définie la fonction printf(). Si on ne l’incluait pas au début du programme, on aurait un message
d’erreur et un refus de l’affichage attendu.
Variable et assignation
La notion de variable est ambigüe. Au départ c’est une lettre ou un mot que l’on déclare par
exemple en entiers (int), comme int a; ou en flottants (float) quand on a un nombre à virgule, étant
entendu que la virgule est représentée par un point ! Pour une plus grande précision, on dispose aussi
des double.
Il s’agit en fait d’un nom donné à une case mémoire. Ce que l’on met dedans peut être variable ou
fixe, cela dépend de ce que l’on veut. Si l’on fait a=3 ; cela veut dire que 3 est placé dans la case qui
s’appelle a. Le signe = est un symbole d’assignation ou d’affectation : on affecte à a la valeur 3. Tout
bêtement on met 3 dans a, ou encore a contient 3. Faisons ce programme :
main() {int a ;
a=3 ; printf(« %d », a) ; getchar() ; return 0 ;}
On voit s’afficher 3 sur l’écran. A noter dans printf le symbole %d qui indique que c’est un entier
qui va s’afficher, et c’est celui qui est dans a.
Si l’on veut mettre dans a un nombre à virgule (un nombre dit flottant : float), on fait :
float a ; a=3.14159 ; printf(« %f »,a) ;
et on voit s’afficher 3.141590. Si l’on veut seulement voir s’afficher 3.14 on peut faire
printf(« %1.2f »,a). Le chiffre 2 de 1.2 indique que l’on veut deux chiffres derrière la virgule. Quant au
1 de 1.2, il n’a pas grande importance ici, essayez avec 10.2, et il se produira un décalage à droite de
l’écriture de 3.14 sur l’écran.
On peut aussi changer le contenu de la case a, par exemple :
int a ; a=3 ; a=2*a ; Cette dernière instruction indique que la nouvelle valeur de a est deux fois
l’ancienne. L’affichage de a donnera 6.
Ajouter un, ou incrémentation
Faisons : i=0 ; i++ ; afficher i (pour afficher faites vous-même le printf()). On voit 1 sur l’écran. Le
fait d’écrire i++ ajoute 1 à la valeur qu’avait i. C’est seulement un raccourci, on pourrait aussi bien
faire i = i+1. De même si l’on fait i-- avec le contenu de i qui diminue de 1. Et cela se généralise : avec
i+=2 le contenu de la case i augmente de 2, ou avec i*=2 le contenu de la case i est multiplié par 2, etc.
6
D’autres langages C que Code Blocks ont un défaut supplémentaire : la fenêtre où se trouve bonjour
disparaît aussitôt et on n’a le temps de rien voir. Pour empêcher cela, une solution est de placer un getchar(), qui
attendra que vous appuyiez sur une touche pour terminer l’exécution et fermer l’écran où se trouvait Bonjour.
5
Boucle et itération
La boucle for (i=0 ; i<10 ; i++) {…} indique que la variable i va aller de 0 à 9 en augmentant de 1
chaque fois (à cause du i++, qui peut s’écrire si l’on préfère i=i+1, ce qui indique qu’à chaque étape
le nouvel i est l’ancien i augmenté de 1.
• Imaginons que l’on veuille afficher sur l’écran les 10 premiers nombres entiers, de 0 à 9. On
fait à l’intérieur du main() :
int i ;
for(i=0 ; i<10 ; i++)
printf(« %d »,i) ;
/* ou bien for(i=0 ; i<10 ; i++) {printf(« %d », i) ;}
Comme la boucle for() ne contient ici qu’une instruction, on n’est pas obligé de placer celle-ci à
l’intérieur d’accolades {}.7 Profitons-en.
• On veut maintenant avoir la somme des N premiers nombres entiers, de 1 à N. On utilise pour
cela une variable de cumul, qui vaut 0 au départ. Puis on lance une boule qui à chaque fois ajoute un
nombre entier à cumul. A la fin, cumul contient la somme demandée. Voici le programme complet :
#include <stdio.h>
int main()
{int i, cumul, N ;
N=10 ; /* c’est un exemple */
cumul=0 ;
for(i=1 ;i <=N ; i++) cumul + = 1 ; (ou si l’on veut cumul= cumul+1 ; )
printf(« %d », cumul) ;
return 0 ;
}
Première source d’erreurs, si l’on oublie de mettre cumul à 0 au départ, on sera surpris des résultats
complètement faux que l’on obtiendra, car le langage C est très libre : si l’on ne dit rien, cumul va
quand même contenir quelque chose, pour nous n’importe quoi.
• Calcul de factorielle N, soit N ! = N(N-1)(N-2)…3.2.1, N étant un nombre entier donné.8 Après
la condition initiale cumul=1, on a la boucle de cumul :
#include <stdio.h>
#define N 10 /* on donne la valeur 10 à N, car on veut obtenir factorielle 10 */
int main()
{ int cumul,i ;
7
Si l’on fait : for(i=0 ; i<10 ; i++) printf(« %d »,i) ; printf(« %d »,i) ; on verra s’afficher les nombres
de 0 à 9, car la boucle for porte seulement sur le premier printf, puis on verra s’afficher 10 à cause du deuxième
printf, celui-ci étant en dehors de la boucle. A remarquer qu’après la boucle for avec i<10, i prend la valeur
finale 10.
8
La factorielle devient vite très grande. Par exemple 20 ! est de l’ordre de 2 milliards de milliards, soit
2.1018. C’est le moment de voir la limite du bon usage des entiers. Pour cela reprendre le programme de la
factorielle, en faisant à la fois un calcul en entiers et un calcul en flottants pour des valeurs croissantes de N :
int cumul,i,N ; float cumulflottants ;
for(N=1 ; N<=20 ; N++) { cumul*=i ; cumulflottants * = i ;}
printf(« Factorielle %3.d = %d %25.0f \n », cumul, cumulflottants) ;
On s’apercevra qu’à partir de factorielle 13, les calculs en entiers deviennent complètement faux (il apparaît
même des valeurs négatives). D’où la règle pratique : ne pas dépasser la limite d’un milliard pour les entiers
(déclarés int).
6
cumul=1 ;
for(i=2 ;i<=N ;i++) cumul*=i ;
printf(« Factorielle %d = %d »,N,cumul) ;
return 0 ;
}
On voit apparaître la structure habituelle d’un programme : conditions initiales, puis boucle, puis
affichage des résultats.
La boucle for() est couramment utilisée, quand on sait qu’une variable doit évoluer entre deux
valeurs connues, avec un pas connu lui aussi. On peut aussi l’utiliser sous cette forme apparemment
plus libre :
compteur=0 ; /* à vous de mettre l’enrobage habituel, les include, le main, etc. */
for( ;;)
{ afficher compteur ;
/* pour afficher faites un printf ! */
if (compteur == 10) break ; /* l’égalité s’écrit == , voir plus bas. Voir aussi plus bas le
test si… alors, qui s’écrit if() */
compteur++ ;
}
On voit aussi s’afficher les nombres de 0 à 10. La boucle for(;;) n’a aucune contrainte puisqu’elle
ne porte sur rien (on peut aussi bien mettre while(1)). Elle se poursuivrait à l’infini si l’on ne mettait
pas un break qui arrête la boucle dès que la variable compteur atteint une certaine valeur. On utilise
pour cela un test if (si… en français).
Assignation = et égal ==
Comme on le constate dans le programme précédent, l’égalité dans if ( compteur == 10) s’écrit
avec le double symbole ==.9 Ainsi, en langage C, l’assignation s’écrit avec un = et l’égalité avec ==.
L’argument avancé par les inventeurs du langage en faveur de ce choix a été qu’on utilise beaucoup
plus souvent l’assignation que l’égalité, et que dans ces conditions mieux valait que l’assignation
s’écrive plus simplement et rapidement que l’égalité. En ce sens c’est bien mieux que le langage
Pascal qui écrit l’égalité avec = et l’assignation avec :=. Signalons toutefois qu’en Basic les deux
instructions s’écrivent toutes deux avec un =.
Les trois types de boucles
On a déjà vu la boucle for(). Il existe deux autres types de boucles :
•
•
while(…) {…}
do {…} while(…) ;
A la différence du for() où sous la parenthèse sont en général précisées la zone de variation et
l’évolution des variables au coup par coup, la parenthèse du while() ne contient que le blocage final, il
convient d’ajouter avant le while les conditions initiales des variables concernées, et dans le corps
{…} de la boucle leur type d’évolution.
9
Ne mettre aucun espace entre les deux =, sinon la machine ne va pas comprendre.
7
Exemple : La suite des puissances de 2
int compteur, u ;
compteur=0 ; u=1 ; afficher u ;
while(compteur<10) { compteur++ ; u = 2*u ; afficher u ; }
A chaque pas de la boucle, compteur augmente de 1, à partir de compteur=0 mis auparavant en
conditions initiales, et la variable u est multipliée par 2 à partir de u=1. La suite des valeurs de u
affichées donne les puissances successives de 2, de 20 = 1 à 29 = 512. Remarquons qu’au lieu de faire
u = 2*u on pourrait écrire u *=2.
On peut aussi bien faire :
int compteur, u ;
compteur=0 ; u=1 ; afficher u ;
do {compteur++ ; u= 2*u ; afficher u ; }
while(compteur<10) ;
Mais ici on verra l’affichage des puissances de 20= 1 à 210 = 1024.
Les trois types de boucles sont interchangeables, mais dans certains cas on peut préférer à juste titre
utiliser l’une plutôt qu’une autre, comme on le verra à l’occasion.
Exercice 1
En 2008 une certaine population comptait un million d’individus. Sa croissance annuelle est de
10%. On désire savoir :
1) Quel sera le nombre d’individus en 2060
2) En quelle année elle dépassera les 100 millions d’individus.
D’abord on pose le problème, et cela n’a rien d’informatique. On considère que 2008 est l’année 0
(le point de départ de l’évolution comme si l’on enclenchait un chronomètre), et qu’en cette année 0 la
population vaut u0= 1 où l’unité 1 représente un million d’individus. On a la règle d’évolution
suivante : en l’année n, la population est de un et l’année suivante elle vaut un+1 = un + (10/100) un car
10% signifie 10/100, soit un+1 = 1,1 un. On obtient une suite de nombres définie par la condition initiale
u0 = 1 et la relation de récurrence un+1 = 1,1 un. Maintenant on programme :
1)
On veut connaître u52 (population en 2060), ce qui permet d’utiliser une boucle for() :
float u ; int N, annee ;
u= 1. ; N=52 ;
for(annee=1 ; annee<=N ; annee++) u = 1.1 * u ;
afficher u.
On trouve qu’en 2060 la population atteint 142 millions d’individus.
2) On veut une boucle où l’évolution se fait année par année jusqu’à ce que la population dépasse
la valeur 100, ce qui invite à utiliser plutôt un while :
int annee ; float u ;
u=1. ; annee=0 ;
while( u<=100.) {annee++ ; u=1.1*u ;}
afficher annee
8
On trouve que c’est en 2057 que la population dépasse pour la première fois 100 millions. Vérifiezle.
Boucles imbriquées
Que fait le programme suivant :
for(i=1; i<=5 ; i++) /* la grande boucle */
for(j=0 ; j<i ; j++) /* la petite boucle imbriquée dans la grande */
afficher i
On voit s’afficher sur l’écran 122333444455555. Pour chaque valeur de i dans la grande boucle, la
petite boucle se déroule complètement. Par exemple lorsque i = 3, la petite boucle provoque
l’affichage de 3 trois fois de suite (j allant de 0 à 2).
Test si … alors, sinon …
Ce test s’écrit if (…)
{…}
else {…}
mais selon les problèmes le « sinon » (else) n’est pas obligatoire. Au fond si l’on résume, tout
problème traité sur ordinateur se résume à des boucles et des tests. Prenons un exemple.
Imaginons que l’on fasse 10000 tirages au hasard de nombres entiers tous compris entre 0 et 9, et
que l’on veuille savoir combien parmi eux sont pairs et combien sont impairs. On peut s’attendre à en
avoir en gros moitié-moitié, ce qui permet de tester la validité du générateur de nombres aléatoires de
notre langage C.
On utilise pour cela la fonction rand() qui ramène au hasard un nombre compris entre 0 et
RAND_MAX=32767. Pour obtenir un nombre au hasard compris entre 0 et 9, il suffit de faire
rand()%10, ce qui se lit rand() modulo 10, ce qui signifie que l’on prend le reste de la division du
nombre rand() par 10, ce reste étant compris entre 0 et 9. Ensuite, pour savoir si un nombre n (≥0) est
pair ou impair, il suffit de chercher son reste lorsqu’on le divise par 2. Si le reste est nul, le nombre n
est pair, et s’il vaut 1, le nombre est impair. Pour avoir ce reste dans la division par 2, on écrit n%2,
qui se lit n modulo 2. Par exemple 13%2 = 1, 18%2 = 0. Cela revient à enlever au nombre un certain
nombre de fois 2 jusqu’à arriver à un nombre qui est soit 0 soit 1. Plus généralement n%p ramène un
nombre compris entre 0 et p-1, après avoir enlevé p autant de fois qu’il le faut à n.
Revenons à notre problème, qui se traite ainsi :
#include <stdio.h>
#include <stdlib.h>
/* indispensable pour la fonction rand() */
int main()
{
int i, chiffre,nombrepairs, nombreimpairs ;
nombrepairs=0 ; nombreimpairs=0 ; /* les deux variables de cumul */
for(i=0; i<10000 ; i++)
{ chiffre = rand()%10 ; /* chiffre est un nombre au hasard entre 0 et 9 */
if (chiffre%2 ==0) nombrepairs ++ ;
else nombreimpairs ++ ;
}
printf(« Nombres des pairs :%d et des impairs : %d\n », nombrepairs, nombreimpairs) ;
return 0 ;
}
9
Mais exécutons ce programme plusieurs fois de suite. On constate que l’on obtient toujours les
mêmes résultats. En fait la série des 1000 nombres aléatoires n’a pas changé d’un iota. Pour éviter ce
problème où le hasard fait cruellement défaut, on appelle au début du programme la fonction
srand(time(NULL)) qui relie le générateur de nombres aléatoires à l’heure qu’il est. Quand on fait
plusieurs exécutions du même programme, le temps a changé, et le générateur aléatoire ne démarre pas
avec les mêmes nombres, donnant ainsi des séries de nombres différentes.
#include <stdio.h>
#include <stdlib.h> /* pour la fonction rand() */
#include <time.h> /* pour la fonction srand() */
int main()
{ int i, chiffre,nombrepairs, nombreimpairs ;
nombrepairs=0 ; nombreimpairs=0 ; srand(time (NULL)) ;
for(i=0; i<10000 ; i++)
{ chiffre = rand()%10 ;
if (chiffre%2 ==0) nombrepairs ++ ;
else nombreimpairs ++ ;
}
printf(« Nombres des pairs :%d et des impairs : %d\n », nombrepairs, nombreimpairs) ;
return 0 ;
}
Tableaux
En général, on n’a pas une ou deux variables à traiter dans un programme. On a une collection
d’objets, en grand nombre. On est alors amené à les placer l’un après l’autre dans un tableau de cases
successives. Si l’on a N nombres entiers à mettre, on déclare le tableau a[N] dans lequel les nombres
vont être placés. Chaque case est numérotée de 0 à N-1. Insistons : quand un tableau a pour longueur
N, sa dernière case est numérotée N-1. Un programme consiste alors à parcourir le tableau, avec une
boucle for(i=0; i<N ; i++) en procédant à des transformations ou recherches selon le problème à traiter.
Diverses situations peuvent se présenter :
• Soit le tableau nous est imposé au départ, et c’est à nous de le remplir, par exemple :
int a[10] ; /* déclaration du tableau avec son nom et sa longueur */
a[10]={2, -4, 3, 5, -7, 8, -8, 1, 7, -3} ;
ou si l’on préfère, on fait : a[0]=2; a[1]=-4 ; a[2]=3 ; … a[9]=-3.
Imaginons alors qu’on nous demande le nombre d’éléments négatifs dans ce tableau. On fera le
parcours suivant, dans le programme principal :
compteurnegatifs=0 ;
for(i=0 ; i<N ; i++) if (a[i]<0) compteurnegatifs++ ;
afficher compteurnegatifs
• Ou bien le tableau est rempli automatiquement par le programme. Voici un exemple :
On nous demande d’afficher les carrés des N nombres pairs successifs à partir de 2. Si N=5, on
veut connaître les valeurs de 22,42,62,82,102. On peut enregistrer ces calculs dans un tableau puis on
affiche les résultats :
for(i=1 ; i<=N ; i++) { nbpair=2*i ; carre[i]=nbpair*nbpair ;}
for(i=1 ; i<=N ; i++) printf(« %d « , carre[i]) ;
10
Remarquons qu’en faisant cela le tableau doit être au préalable déclaré sur une longueur N+1, en
faisant int a[N+1]. Les carrés qui nous concernent sont dans les cases de 1 à N, la case 0 ne nous
intéresse pas.
Maintenant on veut connaître les sommes partielles de ces carrés, soit 22, 22+42, 22+42+62, 22+42+62
+8 , 22+42+62+82+102. On va les enregistrer dans un tableau int sommepartielle[N+1] :
2
cumul=0 ;
for(i=1 ; i<=N ; i++)
{ nbpair=2*i ; carre=nbpair*nbpair ; cumul+=carre ; sommepartielle[i]=cumul;}
for(i=1 ; i<=N ; i++) printf(« %d « , sommepartielle[i]) ;
Remarquons que si le problème se réduisait à cet affichage, on n’aurait pas besoin d’utiliser un
tableau. Mais imaginons qu’on nous demande le nombre de termes à prendre dans cette somme de
carrés pour atteindre ou dépasser le quart de la somme totale des N carrés. Par exemple, pour N=5,
avec les sommes partielles de carrés obtenues : 4, 20, 56, 120, 220, il faut prendre les trois premiers
carrés, dont la somme vaut 56, pour dépasser le quart de la somme totale, soit 220/4= 55. Dans ce cas,
l’usage d’un tableau s’avère plus utile. Voici le programme complet :
#include <stdio.h>
#define N 5 /* ce #define provoque le remplacement de N par 5 partout dans tout le programme */
int main()
{ int i, nbpair, carre, sommepartielle[N+1], cumul, quartdutotal ;
cumul=0 ;
for(i=1 ; i<N ; i++)
{=2*i ; carre=nbpair*nbpair ; cumul+=carre ; sommepartielle[i]=cumul;
}
quartdutotal= sommepartielle[N] / 4 ;
i=1 ; while ( sommepartielle[i] < quartdutotal) i++ ;
printf(« Pour N=%d, le nombre de termes pour dépasser le quart du total est %d », N, i) ;
getchar() ; return 0 ;
}
Fonctions
Le langage C est à base de fonctions. En ce sens il est unifié et c’est un avantage par rapport à
d’autres langages qui mêlent les fonctions et les procédures (des blocs d’instructions) comme le Pascal
ou le Basic, ou feu le Logo.
Quant elle est généralisée, la notion de fonction devient très élastique. Il y a les vraies, les vraiesfausses, les fausses… Au sens strict, une fonction prend une variable x et ramène une valeur y, ce qui
s’écrit y = f(x), c’est-à-dire y est fonction de x. Commençons par voir comment se présente une telle
fonction classique en C.
Imaginons que l’on veuille calculer les valeurs du polynôme P(x) = x3 – 2x2 + 4x – 3 pour les
valeurs entières de x comprises entre – 5 et 5. Cela peut se programmer ainsi, même si on peut le faire
plus simplement :
#include <stdio.h>
int polynome(int x) ; /* on déclare cette fonction avant le main() */
main()
{ int x, y ;
for(x=-5 ; x<=5 ; x++)
{ y = polynome(x) ; printf(« x=%d y=%d \n » ,x, y ;
}
getchar() ; return 0 ;
11
}
int polynome(int x)
{ int valeur ;
valeur = x*x*x – 2*x*x + 4*x – 3 ;
return valeur ;
}
Prenons la fonction polynome(). On constate que sa variable x doit être déclarée, ici en entiers, par
int x, et que la valeur entière qu’elle ramène doit être annoncée devant elle, d’où int polynome().
D’autre part, à la fin du corps de la fonction, il est obligé de dire qu’elle ramène une valeur entière,
d’où return valeur. C’est cette valeur retournée qui est mise dans le y du programme principal, puis
affichée. Enfin il est indispensable de déclarer la fonction au début du programme, avant le main().
Mais il existe d’autres types de fonctions, notamment des fonctions qui ne prennent aucune
variable, et ramènent quand même une valeur, comme par exemple notre fameux int main(), ou encore
rand() qui ramène un entier entre 0 et 32767. Il y a encore des fonctions qui prennent des variables
mais ne ramènent rien. On les déclare ainsi, par exemple : void fonction (int u, int v) , une fonction de
deux variables, et où void indique que rien n’est ramené. Il n’y a alors aucun return à mettre à
l’intérieur de la fonction. Que fait une telle fonction ? Elle se contente d’afficher des résultats sur
l’écran ou d’y dessiner des objets, et c’est déjà beaucoup. Mieux encore, il existe des fonctions qui ne
prennent aucune variable et qui ne ramènent rien. Elle sont déclarées sous la forme void
fonction(void). De telles fonctions ne sont autres que des procédures englobant un bloc d’instructions,
elles servent de prête-nom (ou d’étiquette ou de label) pour indiquer ce que l’on veut réaliser.
Variables locales et globales
Une variable locale est déclarée à l’intérieur et au début d’une fonction (cette fonction pouvant
notamment être le programme principal). Elle n’agit alors qu’à l’intérieur de cette fonction. Par
exemple si une variable int i est déclarée dans le main() et que l’on a aussi une variable int i déclarée
dans une autre fonction, cela donne deux variables complètement indépendantes et différentes l’une de
l’autre, malgré leur nom identique.
Par contre, une variable globale est déclarée au début du programme, avant le main() et avant les
autres fonctions éventuelles du programme. Alors elle agit dans toutes les fonctions. Il y a aussi un
autre avantage : lorsque l’on déclare un tableau a[N] en global, il est automatiquement mis à 0 (par
contre s’il est déclaré à l’intérieur du main(), il contient n’importe quoi, et si on veut l’initialiser à 0, il
faut le parcourir en mettant chaque case à 0).
Exercice 2 : coupes d’un paquet de cartes
On a un paquet de N cartes, on va le couper en deux morceaux de longueur L1 et L2 (L1 + L2 =
N), aucun n’étant vide, puis on va le reconstituer en intervertissant les deux morceaux.
1)
On fait une coupe au hasard. Programmer cette opération de coupe.
On va d’abord noter les N cartes du paquet 0, 1, 2, …, N-1. Il s’agit là d’une conversion toute
simple : si la première carte du paquet est l’as de cœur, on décide de le noter 0, et ainsi de suite. Ainsi
simplifié, le paquet est placé dans un tableau a[N]. Ensuite on va numéroter la coupure avec un
nombre C compris entre 0 et N-2, et choisi au hasard. Une coupure numérotée C signifie que le
premier morceau va de l’indice 0 à C dans le tableau a[N], et que le deuxième va de C+1 à N-1. Dans
ces conditions chacun des deux paquets a au moins une longueur égale à 1.
for(i=0 ; i<N ; i++) a[i]=i ;
12
C=rand()%(N-1) ; L1=C+1 ; L2=N – C – 1 ;
for(i=0 ;i<N ; i++) if (i<=C) paquet1[i]=a[i] ; else paquet2[i – L1]=a[i] ;
for(i=0 ; i<N ; i++) if (i<L2) a[i]=paquet2[i] ; else a[i]=paquet1[i – L2] ;
afficher le paquet mélangé a[]
2) Pour mélanger les cartes, on se propose de répéter cette opération de coupe un certain
nombre de fois. Programmer. Que va-t-il se passer ?
Evidemment l’essentiel c’est le programme précédent. Il suffit de le répéter un certain nombre de
fois (dans le programme qui suit nbdecoupes vaut 10). Pour montrer comment on intègre des fonctions
au programme principal main(), on a placé l’opération de coupe dans la fonction couper(), et
l’affichage dans la fonction afficher(). Voici le programme complet :
#include <stdio.h>
#include <stdlib.h> /* pour le générateur rand() de nombres aléatoires */
#include <time.h> /* pour la relance srand() du générateur aléatoire */
#define N 32
#define nbcoupes 10
int a[N], paquet1[N-1], paquet2[N-1], L1, L2; /* variables globales, qui concernent toutes
les fonctions */
void couper(void); /* deux fonctions ne prenant aucune variable et ne ramenant rien */
void afficher(void);
main()
{
int i,coupe;
/* variables locales */
srand(time(NULL));
for(i=0; i<N; i++) a[i]=i;
afficher(); /* le tableau initial */
for(coupe=1; coupe<=nbcoupes; coupe++) couper(); /* les coupes répétées */
afficher(); /* résultat après les coupes */
}
void couper(void)
{ int i, C;
C=rand()%(N-1); L1=C+1; L2=N-C-1;
for(i=0; i<N; i++) if (i<=C) paquet1[i]=a[i]; else paquet2[i-L1]=a[i];
for(i=0; i<N; i++) if (i<L2) a[i]=paquet2[i]; else a[i]= paquet1[i-L2];
}
void afficher(void)
{ int i;
for(i=0;i<N;i++) printf("%d ",a[i]);
printf("\n"); getchar();
}
Que constate-t-on ? On a beau multiplier les coupes, on a toujours à la fin un décalage cyclique des
cartes, par exemple pour N=5, avec au départ 01234, on va par exemple obtenir 23401. Tout se passe
comme si l’on n’avait fait qu’une seule coupe. Ce n’est pas un véritable mélange. La machine s’est
comportée comme un « ordinateur », la machine qui met de l’ordre et qui n’aime pas le désordre. On
n’en veut pas.
3) Modifier l’opération de mélange pour qu’après l’avoir effectuée un certain nombre de fois
le paquet soit vraiment mélangé.
Pour casser le quasi ordre indéfiniment retrouvé de la méthode précédente, et créer un véritable
désordre, on décide par exemple de mettre la dernière carte du deuxième morceau en premier, puis on
13
met le reste du deuxième morceau, suivi du premier morceau. Et l’on répète cela un certain nombre de
fois, par exemple autant de fois qu’il y a de cartes. On obtient bien à la fin une permutation
quelconque des cartes.
Il suffit de reprendre le programme précédent, et après la coupure en deux paquets dans la fonction
couper(), de modifier à la fin la reconstruction du paquet a[N] en faisant :
a[0] = paquet2[L2 – 1] ;
for(i=1 ; i<N ; i++) if (i < L2) a[i] = paquet2[i – 1] ; else a[i] = paquet1[i – L2] ;
Conclusion provisoire
Voilà pour cette petite initiation au langage C. Beaucoup de choses n’ont pas été dites, mais avec
ce bagage minimal on peut traiter de nombreux problèmes sur ordinateur. D’autres choses non dites
sont sous-jacentes à travers les exemples de programmes. Autre limite de cette initiation : nous avons
toujours choisi des exemples à base de nombres. Cela peut sembler restrictif, mais l’ordinateur n’est-il
pas avant tout un computer, c’est-à-dire un computeur, un calculateur ? Et dans de nombreux cas,
quand on travaille avec des lettres, on a intérêt à les convertir en nombres.
A l’avenir, il reste une chose essentielle à réaliser : ajouter une bibliothèque graphique. La
frustration suprême, avec les langages C basiques d’aujourd’hui, c’est de ne pas avoir les moyens de
dessiner un point sur un écran d’ordinateur. Quand tout n’est qu’image !
Exercice 3 : Calcul d’une calotte sphérique
Sur une sphère de rayon R, on prend une calotte sphérique de hauteur H (avec H entre 0 et R),
dont on veut déterminer le volume suivant les valeurs de H.
A gauche, la calotte sphérique de hauteur H,
et à droite son découpage en tranches (3
tranches ici, en fait on en fera des dizaines ou
des centaines)
Découpons la calotte en tranches toutes de même épaisseur dx. Chaque tranche est assimilée à un
cylindre de hauteur dx, et dont la base circulaire a un rayon h, dépendant de l’abscisse x, distance entre
le centre de la sphère et celui de la base circulaire du cylindre. Cette abscisse x
est comprise entre R-H et R, et l’on a h2+x2=R2 (Pythagore), d’où h2 = R2 – x2.
Le volume d’une tranche cylindrique située à l’abscisse x est égal à l’aire de la
base circulaire πh2 multipliée par la hauteur dx. Le volume de la calotte est la
somme des volumes de toutes les tranches, soit ∑ − , où x prend
les valeurs comprises entre R-H et R en augmentant à chaque fois de dx.
D’où le programme, où l’on utilise une variable de cumul volume qui vaut 0
au départ, et qui augmente à chaque étape du volume d’une tranche, ce qui
donne à la fin le volume de la calotte :
#include <stdio.h>
#define pi 3.1416 /* sans define, on peut si l’on préfère utiliser le nombre M_PI intégré au langage C */
14
float H,R,volume,x,dx; /* déclaration des variables globales en flottants (nombres à virgule) */
int main()
{ R=1.; H=0.5; dx=0.0001; /* valeurs prises comme exemple */
volume=0.;
for(x=R-H; x<R; x+=dx)
volume + = pi*(R*R-x*x)*dx;
printf("Volume de la calotte (pour R=%3.1f et H=%3.1f) = %3.3f\n\n",R,H,volume);
return 0;
}
Remarque : Nous venons de réaliser ce qui est la source du calcul intégral. Le volume de la calotte
sphérique est − = −
).
par intégration. Le calcul donne finalement
On peut comparer les résultats obtenus par le programme précédent et par cette formule
π H 2 (R théorique, en ajoutant cette ligne au programme précédent :
volume=pi*H*H*(R-H/3.); printf("Volume théorique = %3.3f\n\n",volume);
Dans les deux calculs, on trouve pour l’exemple choisi un volume égal à 0,655. C’est plutôt
réconfortant.
Initiation à SDL :
et maintenant la liberté de dessiner
Rappelez-vous où on en était resté dans notre art de programmer grâce au langage C CodeBlocks.
On pouvait voir s’afficher des millions de chiffres ou de caractères sur l’écran. Mais interdiction de
dessiner un seul point à volonté, encore moins le moindre cercle ou la moindre image. On peut voir là
un nouvel avatar de la pensée sécuritaire et mercantile si chère à nos politiciens et à nos philosophes.
Rappelons qu’en 1980 le langage Basic contenait même une fonction sprite qui permettait de faire
bouger des objets sur l’écran, certes à petite vitesse. Alors remettons la main dans le cambouis, et
installons SDL,10 qui va nous permettre de faire du graphisme, c’est-à-dire de mettre des images sur
notre écran, ce qui est la moindre de choses.
Téléchargement de SDL sous Windows 10
Demandez sur Google « télécharger SDL ». Dans la liste des possibilités, choisissez Simple
DirectMedia Layer – SDL version1.2.15, http//www.libsdl.org. Dans la liste des propositions, aller :
• dans la rubrique developpement librairies (bibliothèques de développement) et cliquez sur SDLdevel-1.2.15-mingw32.tar.gz. Le fichier compressé apparaît. Pour le décompresser, procédez à son
« extraction » grâce à un logiciel comme winrar ou 7zip. Puis par copier-coller, placez le dossier
obtenu SDL-1.2.15 à l’intérieur du dossier CodeBlocks. Il va ainsi apparaître juste au-dessous du
dossier MinGW. L’essentiel est fait.11
10
Encore un peu de jargon : SDL est une API, une interface de programmation (application programming
interface), c’est-à-dire pour les profanes une bibliothèque de fonctions, graphiques et autres, qui vont se greffer
sur notre machine informatique, comme un pace maker. Ce n’est pas la seule possibilité de bibliothèque
graphique, il y a aussi Allegro, DirectX, etc. si vous préférez.
11
Dans les versions précédentes de SDL et de Windows, j’avais procédé de façon plus complexe. Dans le
fichier SDL où se trouvent notamment les trois répertoires : bin, include, et lib, j’avais pris leurs contenus et les
15
• dans la rubrique runtime librairies (bibliothèques d’exécution), et télécharger SDL-1.2.15win32-x64.zip. Vous aurez ainsi à votre disposition la dll : SDL.dll. Mais est-ce utile de faire cela ?
Premier programme : l’écran noir
Retournons dans notre éditeur Code Blocks, et demandons comme d’habitude : create a new
project. On tombe sur une fenêtre New from template. Ou bien faire File → New → Project. Dans les
deux cas, on tombe sur une fenêtre. A l’intérieur cliquez sur SDL project (et non plus sur Console
application). On va vous demander de donner un titre à votre projet, prenez par exemple ecrannoir. et
juste après on va vous demander où se trouve SDL. Ecrivez alors le chemin qui mène jusqu’à SDL1.2.15, en utilisant par exemple le browse qui se trouve là, et quand vous tomber sur SDL-1.2.15 vous
demandez d’ouvrir et le chemin sera écrit. Continuez jusqu’à finish. Votre projet ecrannoir se trouve
dans votre espace de travail (workspace). Tapez sur Sources, puis sur le main.cpp qui va apparaître, et
qui est déjà rempli par un programme de base. Par acquit de conscience, exécutez ce programme qui
vous est gracieusement offert pour constater que ça marche ! Le logo Code Blocks apparaît sur un
fond d’écran noir. Fermez cette fenêtre (avec la croix X en haut à droite) et jetez un œil sur le
programme main.cpp. On n’y comprend rien et c’est tant mieux. On constate quand même un grand
nombre de sécurités mises en place, juste pour faire peur. On va s’empresser de les supprimer ! Voilà
ce que l’on peut se contenter de mettre dans notre premier programme, pour voir la fenêtre écran
apparaître, ici réduite à un rectangle noir :
#include <SDL/SDL.h> /* dorénavant indispensable, pour disposer des fichiers .h de SDL */
#define OUI 1 /* utilisé dans la fonction pause() */
#define NON 0
void pause(void) ;
int main (int argc , char ** argv) /* écrire le main ainsi désormais */
{
SDL_Surface * screen ; /* déclaration de la variable ecran */
SDL_Init(SDL_INIT_VIDEO) ;
screen= SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE) ;
SDL_WM_SetCaption(« ECRAN NOIR »,NULL) ; /* facultatif : juste une fioriture pour voir écrit
ECRAN NOIR sur la bordure en haut de notre fenêtre écran*/
SDL_Flip(screen) ; /* indispensable pour voir un dessin apparaître */
pause() ; return 0 ;
avaits copiés dans leurs équivalents respectifs bin, include et lib qui se trouvent dans CodeBlocks\MinGW.
Notamment le répertoire SDL (soit SDL\include\SDL) qui est le seul contenu de SDL/include comme vous
pouvez le constater, va se retrouver dans CodeBlocks\MinGW\include, et vous aurez les fichiers .h du SDL
d’origine qui se retrouvent dans CodeBlocks\MinGW\include\SDL. Mais pour éviter certains ennuis que j’ai eu
par la suite, j’ai copié une deuxième fois tous les fichiers .h de SDL\include directement dans
CodeBlocks\MinGW\include (ceux-là mêmes qui étaient déjà dans CodeBlocks\MinGW\include\SDL). Enfin j’ai
pris le fichier SDL.dll qui est dans le SDL\bin d’origine et je l’ai balancé dans c:\windows\system32.
16
}
void pause()
{ int continuer=OUI;
SDL_Event event; 12
while(continuer= = OUI)
{ SDL_WaitEvent(&event);
if (event.type==SDL_QUIT) continuer=NON;
}
}
On commence par déclarer la variable screen (écran) comme étant l’adresse d’une surface, c’est-àdire l’adresse de la première case que cette surface va occuper dans la mémoire de l’ordinateur.13
On met en conditions initiales l’installation du mode graphique, grâce à
SDL_Init(SDL_INIT_VIDEO). Puis on demande la mise en place en mémoire de la fenêtre-écran,
grâce à SDL_SetVideoMode, en indiquant que cette fenêtre va avoir une longueur de 800 pixels, une
largeur de 600 pixels, et où la couleur de chaque pixel va occuper 32 bits. Cette fonction ramène
l’adresse screen, indiquant la place où se trouve la fenêtre écran dans la mémoire, ce qui permettra
d’accéder à celle-ci en cas de besoin. Comme on ne met rien dans cette zone de mémoire, on aura une
fenêtre-écran noire sur notre écran d’ordinateur. Mais pas encore ! Si on ne fait rien d’autre,
visiblement rien ne se passe. Encore faut-il balancer sur l’écran ce qui était jusque là dans la mémoire
vidéo, d’où le SDL_Flip(ecran). Cela ne suffit toujours pas, la fenêtre-écran noire ne faisant qu’une
apparition fugace avant de disparaître. Pour qu’elle reste figée sur l’écran de l’ordinateur, il convient
d’ajouter une pause. D’où la fonction d’attente pause() où une boucle while ne cesse de tourner tant
qu’un évènement extérieur ne se produit pas. Et le seul évènement qui est pris ici en compte est
SDL_QUIT, qui se produira dès que nous cliquerons sur la petite croix en haut à droite de notre fenêtre
écran. Alors seulement la boucle while s’arrête, et la fenêtre écran va finalement disparaître.
Extension 1 : Plaquer une surface rectangulaire rouge sur l’écran
Mettons maintenant un peu de couleur. Dans un
premier temps nous allons faire en sorte que la
fenêtre-écran soit en blanc (au lieu de rester noire).
Pour cela, après avoir déclaré la variable blanc (ou
white) comme un entier sur 32 bits (Uint32), nous
appelons la fonction SDL_MapRGB (screen->format,
255, 255, 255) où les trois 255 indiquent que les trois
couleurs de base, rouge, vert et bleu, sont mises à leur
valeur maximale, cette combinaison donnant alors du
blanc. La fonction ramène le numéro donné à cette
couleur blanche. Puis on demande de remplir le
rectangle de la fenêtre-écran en blanc, grâce à
SDL_FillRect (screen, NULL, blanc).
Rajoutons cela au programme précédent :
12
Au lieu d’appeler cette variable event, vous pouvez l’appeler evenement, mais alors il faudra faire ce
remplacement partout dans la suite, notamment mettre WaitEvent(&evenement), puis if(evenement.type …
13
La programmation SDL utilise de nombreux pointeurs, à savoir des numéros de cases mémoires (des
adresses). D’où l’apparition d’étoiles *, de & et de flèches -> (utiliser les touches – et >). Cela s’expliquera plus
tard. Pour le moment se contenter de recopier !
17
Uint32 blanc=SDL_MapRGB(ecran->format,255,255,255);
SDL_FillRect(screen,NULL,blanc);
et la fenêtre-écran est devenue blanche, après le Flip vers l’écran.
Allons plus loin. Nous voulons maintenant installer un rectangle rouge au centre de la fenêtre-écran
blanche. Comme il s’agit toujours de rectangles, les fonctions précédentes vont encore nous servir.
Mais maintenant il s’agit d’un nouveau rectangle (newrectangle) à l’intérieur de celui de l’écran et il
convient de donner sa position. D’où l’apparition de nouvelles fonctions. Au lieu de
SDL_SetVideoMode, comme on continue de le faire pour la fenêtre écran, on fait
SDL_CreateRGBSurface (attention cette fonction possède huit arguments, les quatre derniers n’ayant
pas d’intérêt pour nous sont mis à 0). Avec position.x et position.y on se donne les coordonnées du
sommet en haut à gauche du nouveau rectangle. Tout cela est indiqué ci-dessous dans la partie mise en
gras du programme. Une fois le nouveau rectangle installé, avec ses dimensions de 200 sur 100 ici, il
convient de l’appliquer dans la position demandée sur la fenêtre-écran, d’où la fonction :
SDL_BlitSurface(newrectangle,NULL ,screen,&position).
Puis on balance le tout sur l’écran, grâce à SDL_Flip(screen) comme toujours. A la fin si l’on veut
libérer la place prise en mémoire par le nouveau rectangle, on peut faire SDL_FreeSurface(). Voici le
programme final :
#include <SDL/SDL.h>
#define OUI 1 /* par souci de pédagogie essentiellement */
#define NON 0
void pause();
int main (int argc, char ** argv )
{
Uint32 rouge, blanc;
SDL_Surface * screen , * newrectangle;
SDL_Rect position;
SDL_Init(SDL_INIT_VIDEO);
screen=SDL_SetVideoMode(800, 600, 32,SDL_HWSURFACE);
newrectangle= SDL_CreateRGBSurface(SDL_HWSURFACE,300,200,32,0,0,0,0);
SDL_WM_SetCaption("RECTANGLE ROUGE SUR FOND BLANC",NULL); /* facultatif */
blanc=SDL_MapRGB(screen->format,255,255,255);
SDL_FillRect(screen,NULL,blanc);
position.x=400-150;position.y=300-100 ;
rouge=SDL_MapRGB(screen -> format, 255, 0, 0);
SDL_FillRect(newrectangle,NULL,rouge);
SDL_BlitSurface(newrectangle,NULL,screen,&position);
SDL_Flip(screen);
pause(); /* réutiliser la fonction déjà fabriquée dans le programme précédent */
SDL_FreeSurface(newrectangle); /* libérer de la place, en fait sans intérêt dans ce programme */
return 0;
}
On peut ainsi plaquer (Blit) autant de rectangles que l’on désire sur la fenêtre-écran qui est déjà un
rectangle. Ce que nous allons faire dans ce qui suit.
18
Extension 2 : Dégradé cyclique de couleurs sur l’écran
On va tracer des lignes verticales sur l’écran, chacune avec sa couleur. Une ligne verticale n’est
autre qu’un rectangle de longueur 1 de largeur 600 (la hauteur de la fenêtre-écran). La longueur
d’écran va être 3 × 256= 768. Pour chaque valeur de i allant de 0 à 767, on va faire :
ligne[i]=SDL_CreateRGBSurface(SDL_HWSURFACE,1,600,32,0,0,0,0);
Et maintenant les couleurs :
Comme indiqué sur les dessins ci-contre, chacune des
trois couleurs R, G, B prend à tour de rôle la
prédominance. Par exemple le rouge R démarre à 255
(rouge vif), les autres couleurs étant à 0, puis il diminue
de un en un jusqu’à attendre la valeur 0 sur la colonne
256. Ensuite le rouge reste bloqué à 0 jusqu’à la colonne
512, puis il remonte de un en un jusqu’à 255 sur la
dernière colonne 767. Les deux autres couleurs suivent la
même évolution mais décalées de 256. On obtient
finalement une palette cyclique de couleurs où les trois
couleurs de base s’enchaînent avec des dégradés
intermédiaires.
D’où cette partie de programme :
for(i=0;i<768;i++)
{ if (i<256) couleur=SDL_MapRGB(screen->format,255-i,i,0);
else if (i<512) couleur=SDL_MapRGB(screen->format,0,255-(i-256),i-256);
else couleur=SDL_MapRGB(screen->format,i-512,0,255-(i-512));
}
Dans cette même boucle, il reste à se donner la position de chaque colonne dont on connaît
maintenant la couleur, puis à remplir la surface correspondante avec cette couleur, et enfin à plaquer
(blit) cette colonne sur la fenêtre-écran. Le programme en découle :
#include <SDL/SDL.h>
#define OUI 1
#define NON 0
void pause();
int main ( )
{ Uint32 couleur ;
SDL_Surface *screen, *ligne[768] ;
SDL_Rect position;
int i;
SDL_Init(SDL_INIT_VIDEO);
screen=SDL_SetVideoMode(768, 600, 32,SDL_HWSURFACE);
for(i=0;i<768;i++)
ligne[i]=SDL_CreateRGBSurface(SDL_HWSURFACE,1,600,32,0,0,0,0);
SDL_WM_SetCaption("DEGRADE SDL",NULL);
for(i=0;i<768;i++)
{
19
position.x=i; position.y=0;
if (i<256) couleur=SDL_MapRGB(screen->format,255-i,i,0);
else if (i<512) couleur=SDL_MapRGB(screen->format,0,255-(i-256),i-256);
else couleur=SDL_MapRGB(screen->format,i-512,0,255-(i-512));
SDL_FillRect(ligne[i],NULL,couleur);
SDL_BlitSurface(ligne[i],NULL,screen,&position);
}
SDL_Flip(screen);
pause(); /* toujours cette même fonction à recopier */
for(i=0;i<768;i++) SDL_FreeSurface(ligne[i]);
return 0;
}
On vient de voir comment notre logiciel SDL permet de tracer des rectangles dans un rectangle
d’écran. On pourrait généraliser cela au coloriage des pixels, un pixel n’étant autre qu’un rectangle de
un sur un. Mais il vaut mieux s’y prendre autrement.
Dessiner des points
Comment attribuer une couleur donnée à un pixel de la fenêtre-écran situé à l’abscisse xe et
l’ordonnée ye (rappelons que l’axe des ye est dirigé vers le bas) ? On va fabriquer pour cela la fonction
putpixel(int xe, int ye, Uint32 couleur).
La fenêtre-écran, telle qu’on la voit, se trouve dans la mémoire de l’ordinateur, avec une certaine
adresse initiale qui est screen->pixels, où screen provient de la mise en place du mode vidéo, comme
on l’a déjà vu :
screen = SDL_SetVideoMode(800, 600, 32, SDL_SWSURFACE);
La mémoire ressemble à une rue très longue, bordée de cases ayant des adresses (seulement des
numéros puisque la rue est unique) qui se suivent. Il s’agit de passer de la zone rectangulaire de l’écran
à cette ligne de cases mémoires. Imaginons que l’on veuille donner la couleur c au point (200, 3). Ce
point va se trouver dans la case numéro screen->pixels + 3 × 800 + 200, la longueur d’écran 800 étant
aussi contenue dans screen->w. Il suffit de mettre c dans le contenu de cette case.
void putpixel(int xe, int ye, Uint32 couleur)
{ Uint32 * numerocase ;
numerocase = (Uint32*)(screen->pixels) + xe + ye * screen->w ;
*numerocase = couleur; /* la case numérotée par numerocase va contenir couleur. L’étoile * initiale indique
que l’on prend le contenu de la case dont le numéro est numerocase */
}
20
Exemple 1 : brouillage d’écran
Nous allons dessiner une nuée de 10 000 points sur la fenêtre-écran, ces points étant placés au
hasard, avec une couleur elle aussi aléatoire. Et nous allons répéter cela un millier de fois de façon à
voir sur l’écran ce nuage de points en perpétuel mouvement, comme une sorte de brouillage tel qu’on
le voit sur un téléviseur quand on n’arrive pas à capter une chaîne.
Pour la première fois nous utilisons une fonctionnalité
déjà toute prête de SDL qui est le double buffer. Pendant
qu’une image est affichée sur l’écran (grâce à Flip()), la
suivante se préparée en arrière-plan en mémoire. D’où
une succession fluide d’images, sans à-coups. On va
avoir ici en succession : affichage de la nuée de points effaçage de l’écran - nouvelle nuée - effaçage - ….
Pour cela il suffit d’annoncer au départ :
screen = SDL_SetVideoMode(800, 600, 32, SDL_SWSURFACE | SDL_DOUBLEBUF);
Le programme s’ensuit:
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <SDL/SDL.h>
#define OUI 1
#define NON 0
void brouillage(void);
void putpixel(int xe, int ye, Uint32 couleur);
void pause(void);
SDL_Surface* screen;
int main (int argc, char ** argv )
{
int i,noir;
srand(time(NULL));
SDL_Init(SDL_INIT_VIDEO);
screen = SDL_SetVideoMode(800, 600, 32, SDL_SWSURFACE | SDL_DOUBLEBUF);
SDL_WM_SetCaption("BROUILLAGE",NULL);
noir=SDL_MapRGB(screen->format,0,0,0);
for(i=0;i<1000;i++)
{ brouillage();
SDL_Flip(screen);
SDL_FillRect(screen,NULL,noir); /* effaçage de l'écran, en le coloriant en noir */
}
pause();
return 0;
}
void putpixel(int xe, int ye, Uint32 couleur)
{ *( (Uint32*)(ecran->pixels)+x+y*ecran->w ) = couleur;
}
void brouillage(void)
{ int i,xe,ye; Uint32 rouge,vert,bleu,color;
21
for(i=0;i<10000;i++)
{ xe=1+rand()%798; ye=1+rand()%598; 14
rouge=rand()%256; vert= rand()%256; bleu=rand()%256;
color=SDL_MapRGB(screen
color=SDL_MapRGB(screen->format,
rouge,vert,bleu);
putpixel(xe,ye,color); putpixel(xe+1,ye,color); /* on dessine en fait 5 pixels pour faire
une petite croix */
putpixel(xe-1,ye,color);
1,ye,color); putpixel(xe,ye+1,color);
putpixel(x
putpixel(xe,ye-1,color);
}
}
Exemple 2: Tracé d’une courbe
Maintenant que nous savons colorier des pixels sur l’écran, nous pouvons tracer des courbes point
par point, à partir de leur équation sous la forme y = f(x).
). Pour le moment, on se contente de cette
méthode de tracé, même si, pour préserver la continuité du dessin, il convient de prendre un très grand
nombre de points. La seule chose à faire est de passer de la zone calcul, celle de y=f(x) où il s’agit de
nombres
bres à virgule de quelques unités au plus, à la zone écran où les coordonnées sont des nombres
entiers pouvant atteindre des centaines d’unités, d’où la nécessité de procéder à un changement
d’échelle, avec la mise en place d’un zoom.
#include <stdlib.h>
#include <SDL/SDL.h>
SDL_Surface* screen;
void putpixel(int xe, int ye, int couleur);
void repere(void);
void courbe(void);
=300,zoomx=150,zoomy=100;
int xorig=400,yorig=300,zoomx=150,zoomy=100;
Uint32 rouge,blanc,noir;
int main (int argc, char* argv[])
{
SDL_Init( SDL_INIT_VIDEO
IT_VIDEO );
screen = SDL_SetVideoMode(800, 600,
32,SDL_HWSURFACE);
rouge=SDL_MapRGB(screen-->format, 255,0,0);
blanc=SDL_MapRGB(screen-->format, 255,255,255);
noir =SDL_MapRGB(screen->
>format,0,0,0);
SDL_FillRect(screen,0,blanc);
,0,blanc);
repere();
courbe();
SDL_Flip(ecran);
pause(); return 0;
}
void putpixel(int xe, int ye, int couleur)
{ *((Uint32 *)ecran->pixels+xe+ye*ecran
>pixels+xe+ye*ecran->w)=couleur;
}
void repere(void)
{
int i;
for(i=-300;i<300;i++)
300;i<300;i++) putpixel(xorig+i,yorig,rouge);
putpixel(xo
/* les deux axes */
14
L’abscisse xe va de 1 à 798. Comme on dessine en fait une petite croix, on aura des points à l’abscisse 0 et
d’autres à 799, ce qui constitue les limites de la fenêtre-écran.
fenêtre écran. Attention, si l’on fait par exemple
xe=1+rand()%799,, des points vont sortir de l’écran, et le programme va bloquer.
22
for(i=-300;i<300;i++) putpixel(xorig,yorig+i,rouge);
for(i=-10;i<=10;i++) putpixel(xorig+zoomx,yorig+i,rouge); /* graduations 1 */
for(i=-10;i<=10;i++) putpixel(xorig+i,yorig-zoomy,rouge);
}
void courbe(void)
{
float x,y; int xe,ye;
for(x=-2.;x<2.;x+=0.001)
{ y=x*x*x-x;
/* on a pris ici comme exemple de fonction y = f(x) avec f(x) = x3 – x */
xe=xorig+(int)(zoomx*x); ye=yorig-(int)(zoomy*y);
/* passage de (x, y) à (xe, ye) */
if (ye>20 && ye<580) putpixel(xe,ye,noir); /* dessin dans les limites verticales de l’écran */
}
}
Au point où nous en sommes arrivés, ce qui est dommage c’est de ne pas savoir tracer directement
des collections de points qui finalement donnent des figures simples comme des droites ou des cercles.
En attendant mieux, on peut toujours faire comme pour la courbe précédente, à savoir prendre les
équations de ces courbes. Rappelons que l’équation d’une droite est de la forme y = ax + b, sauf si la
droite est verticale, et que l’équation d’un cercle est (x - xo)2 + (y - yo)2 = R2. Il est stupéfiant de
constater que ce qui se trouvait immédiatement disponible dans tous les langages classiques de
programmation il y a trente ou quarante ans (droites, cercles, sprites, etc.) n’est pas présent d’office
aujourd’hui, et doit être intégré par nos soins.
Heureusement de nouvelles fonctionnalités sont de nos jours disponibles, qui auparavant étaient
bien plus complexes. Comme par exemple la possibilité de gérer des évènements, ce qui permet
d’interagir sur ce qui se passe sur l’écran depuis l’extérieur, en cliquant sur la souris ou en appuyant
sur des touches. Et surtout les langages C des années 2000 sont bien plus rapides que ceux des années
1980.
La gestion d’évènements
Jusqu’à présent le seul évènement que nous avions géré était dans la fonction pause() , avec
SDL_QUIT. Le fait d’appuyer sur la croix en bordure de la fenêtre-écran, arrêtait l’exécution du
programme, et en attendant cet évènement, la fenêtre restait immuablement présente. Reprenons la
fonction pause() et ajoutons dans sa boucle while, juste après if (event.type==SDL_QUIT)
continuer=NON;
else if (event.type== SDL_KEYDOWN && event.key.keysym.sym==SDLK_ESCAPE)
continuer = NON;
Maintenant le programme s’arrêtera aussi si l’on appuie sur la touche Escape (ou Echap) en haut à
gauche du clavier. Mais on va faire mieux. En appuyant sur des touches, on va faire bouger un objet à
volonté sur l’écran. Quel objet ? Un rectangle bien sûr, puisque SDL s’y prête bien.
Plus précisément nous allons prendre l’image du logo Code Blocks (avec ses quatre carrés accolés)
dans le programme qui nous est offert chaque fois que l’on crée un nouveau projet. Cette image
s’appelle cb.bmp, il suffit de la garder ou de la récupérer lorsque l’on fait notre projet actuel. On la
charge comme on l’a fait pour la fenêtre-écran, mais en utilisant la fonction ad hoc :
codb = SDL_LoadBMP("cb.bmp");
après avoir declaré codb avec SDL_Surface *codb;. Cette surface rectangulaire possède maintenant
une position (position.x et position.y) pour son coin en haut à gauche. On commence par faire en sorte
que cette image cb se trouve au centre de l’écran, avec position.x = (screen->w - codb->w) / 2; position.y
= (screen->h - codb->h) / 2; .
23
La grande nouveauté c’est que l’on va la faire bouger en appuyant sur des touches. Par exemple, en
appuyant sur la touche → du clavier, l’image cb va se déplacer sur la droite. Pour cela, on s’inspire
de notre ancienne fonction pause(), inutile dans le cas présent, mais dans la grande boucle
while(continuer== OUI) qui constitue le corps du programme, on ne se contente plus des tests d’arrêt
avec SDL_QUIT ou SDL_KEYDOWN avec SDLK_ESCAPE. Par exemple pour provoquer le
mouvement à droite de l’image cb on fait :
if (event.type== SDL_KEYDOWN && event.key.keysym.sym==SDLK_RIGHT)
position.x +=2;
Cela implique que l’on mette à l’intérieur de la grande boucle while le placage de l’image cb sur la
fenêtre-écran, d’abord en mémoire puis sur l’écran grâce à Flip(), tout cela étant réalisé par le double
buffer. Le programme qui suit devrait maintenant être limpide.
#include <stdlib.h>
#include <SDL/SDL.h>
#define OUI 1
#define NON 0
int main ( int argc,char *argv[])
{ SDL_Surface *screen,*codb;
SDL_Rect position;
int continuer=OUI;
SDL_Event event;
SDL_Init( SDL_INIT_VIDEO );
screen= SDL_SetVideoMode(800, 600, 32,SDL_HWSURFACE | SDL_DOUBLEBUF);
codb = SDL_LoadBMP("cb.bmp");
SDL_WM_SetCaption("Mouvements",NULL); /* toujours facultatif */
position.x = (screen->w - codb->w) / 2; position.y = (screen->h - codb->h) / 2;
SDL_EnableKeyRepeat(10,10); /* pour éviter le coup par coup et permettre d’avancer
de 10 pixels quand on laisse la touche appuyée */
while (continuer==OUI) /* la boucle qui attend des évènements extérieurs pour agir sur l’écran */
{
SDL_WaitEvent(&event);
if (event.type==SDL_QUIT) continuer=NON;
else if (event.type== SDL_KEYDOWN)
if (event.key.keysym.sym==SDLK_ESCAPE) continuer=NON;
else if(event.key.keysym.sym==SDLK_UP) position.y -=2;
else if(event.key.keysym.sym==SDLK_DOWN) position.y +=2;
else if(event.key.keysym.sym==SDLK_LEFT) position.x -=2;
else if(event.key.keysym.sym==SDLK_RIGHT) position.x +=2;
SDL_BlitSurface(codb, 0, screen, &position);
/* on plaque le logo CodeBlocks*/
SDL_Flip(screen);
/* on balance le tout sur l’écran */
}
SDL_FreeSurface(codb);
return 0;
}
et ça bouge à volonté !
24
Pour aller plus loin ou plus en détail
Il existe plusieurs « tutoriels » de SDL disponibles sur Internet, notamment sur :
• OpenClassrooms, anciennement Siteduzero qui est le meilleur site pour s’initier à toutes
sortes de logiciels.
• Developpez.com
• Gnurou.com
• Enfin vous avez intérêt à consulter la rubrique enseignement programmation graphique
sur le site de mon collègue Fares Belhadj, qui m’a convaincu de m’initier à SDL, et que
je remercie.
Quant à nous, nous continuons l’initiation à SDL sur notre site, dans :
Enseignements / Cours d’algorithmique / 10- Graphisme SDL : points, droites,
cercles.
Enfin toutes les fonctions graphiques essentielles, ainsi que la structure générale
d’un programme C-SDL sont données dans :
Book Programs / FONCTIONS GRAPHIQUES SDL

Documents pareils

TP2 corrigé

TP2 corrigé L'analyse du problème nous conduit à la solution suivante : Un parcours du tableau s'impose, avec une mémorisation du plus grand élément rencontré. En effet, le plus grand élément du tableau peut s...

Plus en détail