Petite initiation au langage C et au graphisme SDLaudibert/ens/01-INITIATIONC_SDLx.pdf · Petite...

26
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 Vista, mais si vous préférez le faire sous Linux, ce ne doit pas être plus compliqué. Télécharger Code Blocks sous Windows (Vista) en 5 minutes chrono Allez chez Google, et demandez « télécharger code blocks 8.02 ». Prendre la première proposition qui vient, et demandez télécharger. Nouvelle page : cliquez sur download the binary release. Vous êtes envoyé sur un site où dans la rubrique Windows on vous propose Codeblocks-8.02mingw-setup.exe à télécharger avec Sourceforge ou BerliOS. Choisissez qui vous voulez, et téléchargez. Quelques secondes plus tard, c’est fini. Une icône ronde est apparue sur votre « bureau ». Cliquez dessus, et obéissez aux instructions sans chercher à faire preuve d’originalité. Quelques secondes encore, et le répertoire CodeBlocks se trouve installé à l’intérieur de vos Programmes (Program Files). Dans ce répertoire C:\Program Files\CodeBlocks, vous allez trouver l’exécutable, 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. Dans la fenêtre qui apparaît, cliquez sur Console application.

Transcript of Petite initiation au langage C et au graphisme SDLaudibert/ens/01-INITIATIONC_SDLx.pdf · Petite...

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 Vista, mais si vous préférez le faire sous Linux, ce ne doit

pas être plus compliqué.

Télécharger Code Blocks sous Windows (Vista) en 5 minutes chrono

Allez chez Google, et demandez « télécharger code blocks 8.02 ». Prendre la

première proposition qui vient, et demandez télécharger. Nouvelle page : cliquez sur

download the binary release. Vous êtes envoyé sur un site où dans la rubrique Windows

on vous propose Codeblocks-8.02mingw-setup.exe à télécharger avec Sourceforge ou

BerliOS. Choisissez qui vous voulez, et téléchargez. Quelques secondes plus tard, c’est

fini. Une icône ronde est apparue sur votre « bureau ». Cliquez dessus, et obéissez aux

instructions sans chercher à faire preuve d’originalité. Quelques secondes encore, et le

répertoire CodeBlocks se trouve installé à l’intérieur de vos Programmes (Program

Files). Dans ce répertoire C:\Program Files\CodeBlocks, vous allez trouver

l’exécutable, 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. Dans la fenêtre qui apparaît, cliquez sur Console application.

2

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 édité un petit programme déjà tout prêt.

Commencez par supprimer #include <stdlib.h> qui ne sert à rien. Puis remplacez

« Hello World !\n », par « Bonjour\n » ou ce que vous voulez.

Vous avez votre premier programme en C :

#include <stdio.h>

int main()

{

printf(« Bonjour\n ») ;

return 0 ;

}

Pas besoin d’être un expert pour se douter que ce programme va nous afficher

Bonjour ! Encore convient-il de faire tourner ce programme. Pour cela aller dans la

rubrique Build en haut de l’écran, et demander build puis run. Une fenêtre à fond noir

apparaît avec marqué dessus Bonjour. Quand vous en avez assez, appuyez sur une

touche, et la fenêtre va disparaître. Vous vous retrouvez dans votre IDE Code Blocks.

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. 1 Et en allant visiter ce

répertoire bonjour, vous allez constater qu’il contient 7 fichiers, tout ça pour le prix

d’un seul bonjour.

Maintenant quelques explications sur le programme. Voici comment se présente en

général un programme en C :

1 En fait 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. On peut en déduire qu’on est soi-même stupide, ou bien

que c’est une certaine informatique qui l’est. Evidemment j’ai pensé que c’était plutôt la

deuxième éventualité. En fait si j’ai bien compris, Windows Vista 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. Comme je suppose que je ne suis pas le seul à qui cela puisse arriver, 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

#include <stdio.h>

#include <stdlib.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 {…},2 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.3

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ù

2 Dans ce qui suit, nous omettrons souvent les #include, les déclarations de variables, et

même le main() pour nous concentrer sur l’essentiel: le contenu du programme principal. 3 Pour la petite histoire, signalons qu’il y a une vingtaine d’années le langage Visual C++ de

Microsoft coûtait à peu près le prix de Word 2007 aujourd’hui, et qu’il était vendu avec une

brochure explicative de 250 pages, où le seul programme qui était expliqué était l’affichage de

Hello world, sous une forme sophistiquée quand même. Résultats des courses : ventes

calamiteuses. On retrouve bientôt la disquette Visual C++ dans des livres à 200 F. On arrive

aujourd’hui avec une version gratuite sur Internet.

4

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>

#include <conio.h> /* c’est là que se trouve la fonction getch() */

main() { printf(« Bonjour ») ; getch() ;}

La fonction getch() attend sagement que vous appuyiez sur une touche avant

d’envoyer le message de fin.4

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\nEt adios »). 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 entiers longs (long), ce qui

revient aujourd’hui exactement au même que les entiers courts !, ou en flottants (float)

quand on a un nombre à virgule, étant entendu que la virgule est représentée par un

point !

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) ; }

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

4 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 getch(), qui attendra que vous appuyiez sur une touche

pour terminer l’exécution et fermer l’écran où se trouvait Bonjour.

5

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. 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.

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 :

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 {}. 5 Profitons-en.

• On veut maintenant avoir la somme des @ premiers nombres entiers, de 1 à @.

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.

#include <stdio.h>

int main()

{int i, cumul, N ;

N=10 ; /* c’est un exemple */ cumul=0 ;

for(i=1 ;i <=N ; i++) cumul + = i ; (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

5 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.

6

libre : si l’on ne dit rien, cumul va quand même contenir quelque chose, pour nous

n’importe quoi.

• Calcul de factorielle @, soit @ ! = @(@-1)(@-2)…3.2.1, @ étant un nombre

entier donné.6 Après la condition initiale cumul=1, on a la boucle de cumul :

#include <stdio.h>

#define N 10 /* on va obtenir factorielle 10 */

int main()

{ int cumul,i ;

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. 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 = =. Ainsi, en langage C, l’assignation s’écrit avec

6 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 @ :

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).

7

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.

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 à 2

10 = 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 compte 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.

8

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 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,1un. 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

On trouve que c’est en 2057 que la population dépasse pour la première fois 100

millions. Vérifiez-le.

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 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

9

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 RA@D_MAX=32767. Pour

obtenir un nombre au hasard compris entre 0 et 9, il suffit de faire rand()%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 pour arriver à un nombre compris entre 0 et 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> /* 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 ;

if (chiffre%2 ==0) nombrepairs ++ ; else nombreimpairs ++ ;

}

printf(« Nombres des pairs :%d et des impairs : %d\n », nombrepairs, nombreimpairs) ;

return 0 ;

}

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(@ULL)) 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 ;

}

10

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 @ nombres entiers à mettre, on déclare le

tableau a[@] dans lequel les nombres vont être placés. Chaque case est numérotée de 0 à

@-1. Insistons : quand un tableau a pour longueur @, sa dernière case est numérotée @-1.

Un programme consiste alors à parcourir le tableau, avec une boucle for(i=0; i<@ ; 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] ;

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 :

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 @ nombres pairs successifs à partir de 2.

Si @=5, on veut connaître les valeurs de 22,4

2,6

2,8

2,10

2. 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]) ;

Remarquons qu’en faisant cela le tableau doit être au préalable déclaré sur une

longueur @+1, en faisant int a[@+1]. Les carrés qui nous concernent sont dans les cases

de 1 à @, la case 0 ne nous intéresse pas.

Maintenant on veut connaître les sommes partielles de ces carrés, soit 22, 2

2+4

2,

22+4

2+6

2, 2

2+4

2+6

2 +8

2, 2

2+4

2+6

2+8

2+10

2. On va les enregistrer dans un tableau int

sommepartielle[@+1] :

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 vraiment

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 @ carrés. Par exemple, pour @=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

11

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>

#include <conio.h>

#define N 5 /* ce #define provoque le remplacement de N par 5 dans tout le programme */

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) ;

}

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 vraies-fausses, 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 – 2x

2 + 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 la 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 ;}

return 0 ;

}

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().

12

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 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 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 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[@] 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 @ cartes, on va le couper en deux morceaux de longueur L1 et L2,

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 @ cartes du paquet 0, 1, 2, …, @-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[@].

Ensuite on va numéroter la coupure avec un nombre C compris entre 0 et @-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[@], et que le deuxième va de C+1 à @-1. Dans ces conditions chacun

des deux paquets a au moins une longueur égale à 1.

for(i=0 ; i<N ; i++= a[i]=i ;

C=random(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 ?

13

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 <conio.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"); getch();

}

Que constate-t-on ? On a beau multiplier les coupes, on a toujours à la fin un

décalage cyclique des cartes, par exemple pour @=5, avec au départ 012234, 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 ».

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

14

morceau en premier, puis on 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[@] 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. Enfin, il

existe plusieurs langages C et aussi des systèmes d’exploitation différents. Cela

provoque quelques variations et demande des adaptations (par exemple j’utilise getch()

sous Windows, mais sous Unix on ne l’utilise pas).

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+x

2=R

2 (Pythagore),

d’où h2 = R

2 – x

2. 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

15

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

float H,R,volume,x,dx; /* déclaration des variables 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 π H 2 (R -

��). On peut comparer les résultats

obtenus par le programme précédent et par cette formule 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.

Et maintenant la liberté de dessiner

Rappelez-vous où on en était resté dans notre art de programmer. 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.

Nouvel avatar de la pensée sécuritaire si chère à nos politiciens et à nos philosophes.

Alors remettons la main dans le cambouis, et installons SDL,7 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.

7 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é, il y a aussi Allegro, DirectX, etc. si vous préférez.

16

Téléchargement de SDL sous Windows Vista

Allez sur http//www.libsdl.org et cliquez sur download SDL 1.2. Dans la liste des

propositions, descendez sans complexe jusqu’à la rubrique developpement librairies

(bibliothèques de développement) et cliquez sur SDL-devel-1.2.13-mingw32.tar.gz. Le

fichier compressé apparaît sur votre bureau. Pour le décompresser, procédez à son

« extraction » grâce à un logiciel comme winrar ou 7zip. Dans le fichier SDL obtenu

seuls trois répertoires nous concernent : bin, include, et lib. Vous allez prendre leurs

contenus et les copier dans leurs équivalents respectifs bin, include et lib qui se trouvent

dans votre programme 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 les ennuis que j’ai eu par la suite, vous allez copier 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 vous prenez le

fichier SDL.dll qui est dans le SDL\bin d’origine et vous le balancez dans

c:\windows\system32.

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 @ew from template. 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 C:\Program Files\CodeBlocks\MinGW, 8 et

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 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) 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 nous 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 .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 * ecran ; /* déclaration de la variable ecran */

SDL_Init(SDL_INIT_VIDEO) ;

ecran= SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE) ;

8 C’est là que j’avais eu un problème dans un premier temps, et la raison pour laquelle j’ai

copié une deuxième fois les fichiers .h du include de SDL. Si vous trouvez mieux…

17

SDL_WM_SetCaption(« ECRAN NOIR »,NULL) ; /* juste une fioriture pour voir écrit

ECRA@ @OIR sur la bordure en haut de notre fenêtre écran*/

SDL_Flip(ecran) ;

pause() ;

return 0 ; }

void pause()

{ int continuer=OUI;

SDL_Event event; 9

while(continuer= = OUI)

{ SDL_WaitEvent(&event);

if (event.type==SDL_QUIT) continuer=NON;

}

}

On commence par déclarer la variable ecran 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.10

On met en conditions initiales l’installation du mode graphique, grâce à

SDL_Init(SDL_I@IT_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 ecran, 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.

9 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 …

10 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 !

18

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 comme un entier sur 32 bits

(Uint32), nous appelons la fonction

SDL_MapRGB (ecran->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

(ecran, @ULL, blanc).

Rajoutons cela au programme précédent :

Uint32 blanc=SDL_MapRGB(ecran->format,255,255,255);

SDL_FillRect(ecran,NULL,blanc);

et la fenêtre-écran est devenue blanche.

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, 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,@ULL ,ecran,&position). A la fin il est recommandé

de libérer la place en mémoire prise par le nouveau rectangle, avec

SDL_FreeSurface().Voici le programme final :

#include <SDL/SDL.h>

#define OUI 1

#define NON 0

void pause();

int main (int argc, char ** argv )

{

Uint32 rouge, blanc;

SDL_Surface * ecran , * newrectangle;

SDL_Rect position; SDL_Init(SDL_INIT_VIDEO);

ecran=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);

blanc=SDL_MapRGB(ecran->format,255,255,255);

SDL_FillRect(ecran,NULL,blanc);

19

position.x=400-150;position.y=300-100 ;

rouge=SDL_MapRGB(ecran -> format, 255, 0, 0);

SDL_FillRect(newrectangle,FULL,rouge);

SDL_BlitSurface(newrectangle,FULL,ecran,&position);

SDL_Flip(ecran);

pause(); /* réutiliser la fonction déjà fabriquée dans le programme précédent */

SDL_FreeSurface(newrectangle); 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.

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 3x256= 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 le dessin

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

20

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(ecran->format,255-i,i,0);

else if (i<512) couleur=SDL_MapRGB(ecran->format,0,255-(i-256),i-256);

else couleur=SDL_MapRGB(ecran->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 *ecran, *ligne[768] ;

SDL_Rect position;

int i;

SDL_Init(SDL_INIT_VIDEO);

ecran=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++)

{

position.x=i; position.y=0;

if (i<256) couleur=SDL_MapRGB(ecran->format,255-i,i,0);

else if (i<512) couleur=SDL_MapRGB(ecran->format,0,255-(i-256),i-256);

else couleur=SDL_MapRGB(ecran->format,i-512,0,255-(i-512));

SDL_FillRect(ligne[i],NULL,couleur);

SDL_BlitSurface(ligne[i],NULL,ecran,&position);

}

SDL_Flip(ecran);

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.

21

Dessiner des points

Comment donner 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 ecran->pixels, où ecran vient de la mise en place du

mode vidéo, comme on l’a déjà vu :

ecran = 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

ecran->pixels + 3 x 800 + 200. Il suffit de mettre c dans le contenu de cette case.

void putpixel(int xe, int ye, Uint32 couleur)

{ Uint32 * numerocase ;

numerocase = (Uint32*)(ecran->pixels) + xe + ye * ecran->w ;

*numerocase = couleur; /* la case numérotée par numerocase va contenir couleur */

}

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 :

22

ecran = 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* ecran;

int main (int argc, char ** argv )

{

int i,noir;

srand(time(NULL));

SDL_Init(SDL_INIT_VIDEO);

ecran = SDL_SetVideoMode(800, 600, 32, SDL_SWSURFACE | SDL_DOUBLEBUF);

SDL_WM_SetCaption("BROUILLAGE",NULL);

noir=SDL_MapRGB(ecran->format,0,0,0);

for(i=0;i<1000;i++)

{ brouillage();

SDL_Flip(ecran);

SDL_FillRect(ecran,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;

for(i=0;i<10000;i++)

{ xe=1+rand()%798; ye=1+rand()%598; 11

rouge=rand()%256; vert= rand()%256; bleu=rand()%256;

color=SDL_MapRGB(ecran->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); putpixel(xe,ye+1,color); putpixel(xe,ye-1,color);

}

}

11 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.

Attention, si l’on fait par exemple xe=1+rand()%799, des points vont sortir de l’écran, et le

programme va bloquer.

Exemple 2: Tracé d’une courbe

Maintenant que nous savon

courbes point par point, à partir de leur équation sous la forme

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

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* ecran;

void putpixel(int xe, int ye, int co

void repere(void);

void courbe(void);

int xo=400,yo=300,zoomx=150,zoomy=100;

Uint32 rouge,blanc,noir;

int main (int argc, char* argv[])

{

SDL_Init( SDL_INIT_VIDEO );

ecran = SDL_SetVideoMode(800, 600, 32,SDL_HWSURFACE);

rouge=SDL_MapRGB(ecran

blanc=SDL_MapRGB(ecran

noir =SDL_MapRGB(ecran

SDL_FillRect(ecran,0,blanc);

repere(); courbe();

SDL_Flip(ecran);

pause();

return 0;

}

void putpixel(int xe, int ye, int couleur)

{ *((Uint32 *)ecran->pixels+xe+ye*ecran

}

void repere(void)

{

int i;

for(i=-300;i<300;i++) putpixel(xo+i,yo,rouge);

for(i=-300;i<300;i++) putpixel(xo,yo+i,rouge);

for(i=-10;i<=10;i++) putpixel(xo+zoomx,yo+i,rouge);

for(i=-10;i<=10;i++) putpixel(xo+i,yo

}

void courbe(void)

{

float x,y; int xe,ye;

Exemple 2: Tracé d’une courbe

Maintenant que nous savons colorier des pixels sur l’écran, nous pouvons tracer

courbes point par point, à partir de leur équation sous la forme y = f(x

on se contente de cette méthode de tracé, même si, pour préserver la continuité du

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 à virgule de quelques

unités au plus, à la zone écran où les coordonnées sont des nombres entiers pouvant

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 <SDL/SDL.h>

void putpixel(int xe, int ye, int couleur);

int xo=400,yo=300,zoomx=150,zoomy=100;

Uint32 rouge,blanc,noir;

int main (int argc, char* argv[])

SDL_Init( SDL_INIT_VIDEO );

ecran = SDL_SetVideoMode(800, 600, 32,SDL_HWSURFACE);

rouge=SDL_MapRGB(ecran->format, 255,0,0);

blanc=SDL_MapRGB(ecran->format, 255,255,255);

noir =SDL_MapRGB(ecran->format,0,0,0);

SDL_FillRect(ecran,0,blanc);

courbe();

void putpixel(int xe, int ye, int couleur)

>pixels+xe+ye*ecran->w)=couleur;

300;i<300;i++) putpixel(xo+i,yo,rouge); /* les deux axes */

300;i<300;i++) putpixel(xo,yo+i,rouge);

=10;i++) putpixel(xo+zoomx,yo+i,rouge); /* graduations

10;i<=10;i++) putpixel(xo+i,yo-zoomy,rouge);

23

n, nous pouvons tracer des

x). Pour le moment,

on se contente de cette méthode de tracé, même si, pour préserver la continuité du

prendre un très grand nombre de points. La seule chose à faire est

) où il s’agit de nombres à virgule de quelques

unités au plus, à la zone écran où les coordonnées sont des nombres entiers pouvant

centaines d’unités, d’où la nécessité de procéder à un changement

graduations 1 */

24

for(x=-2.;x<2.;x+=0.001)

{ y=x*x*x-x; /* on a pris y=f(x) avec f(x) = x3 – x */

xe=xo+(int)(zoomx*x); ye=yo-(int)(zoomy*y); /* passage de (x,y) à (xe,ye) */

if (ye>20 && ye<580) putpixel(xe,ye,noir); /* dessin dans les limites verticales */

}

}

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. 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=R

2. 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 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.

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=@O@;

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 lorsque 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 = (ecran->w - codb->w) / 2; position.y = (ecran->h - codb->h) / 2; .

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

25

droite. Pour cela, on s’inspire de notre ancienne fonction pause(), mais dans la boucle

while(continuer== OUI) on ne se contente plus des tests d’arrêt avec SDL_QUIT ou

SDL_KEYDOW@ 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 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 *ecran,*codb;

SDL_Rect position; int continuer=OUI; SDL_Event event;

SDL_Init( SDL_INIT_VIDEO );

ecran= SDL_SetVideoMode(800, 600, 32,SDL_HWSURFACE | SDL_DOUBLEBUF);

codb = SDL_LoadBMP("cb.bmp");

SDL_WM_SetCaption("Mouvements",NULL);

position.x = (ecran->w - codb->w) / 2; position.y = (ecran->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)

{

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, ecran, &position); /* on plaque le logo CodeBlocks*/

SDL_Flip(ecran); /* on balance le tout sur l’écran */

}

SDL_FreeSurface(codb);

return 0;

}

26

et ça bouge!

Pour aller plus loin

Il existe plusieurs « tutoriels » de SDL disponibles sur Internet, notamment sur :

• le Siteduzero.com, avec un prolongement : manipulation des images pixel par pixel

demander siteduzero sdl sur la recherche google)

• 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.