Premières notions de programmation▲
But de ce chapitre▲
Dans ce cours, j'ai tout d'abord rassemblé les notions qui m'ont semblé utiles à introduire avant de commencer proprement dit le cours de programmation.
En particulier, comme la programmation est conditionnée par la manière dont fonctionne un ordinateur, il m'a semblé logique de commencer par une présentation très sommaire de cette machine : sa structure, son fonctionnement, et du seul langage qu'elle est capable de comprendre : le langage machine.
J'aborde ensuite les principes d'interaction homme-machine via une souris, un clavier et un écran graphique. Cela me permet d'introduire la programmation évènementielle, avant de parler de l'environnement de développement intégré Lazarus, spécialement conçu pour ce type de programmation.
Le cours de programmation proprement dit porte sur les instructions permettant de rentrer des données dans la mémoire d'un ordinateur (la lecture, l'affectation) ou d'afficher des informations à l'écran, ce qui demande au préalable d'introduire les notions de variables, de type et d'expressions.
Un peu d'histoire▲
De la machine de Turing à UML, retrouvez les moments-clés de l'histoire de l'informatique dans cette série de slides : téléchargez le document Projo-Histoire.pdfTéléchargez le document Projo-Histoire.pdf.
L'ordinateur▲
Un ordinateur est une machine utilisant et produisant de l'information. Il reçoit de l'information (de l'utilisateur, par internet…), donne de l'information (informations affichées à l'écran, imprimées…), mémorise de l'information (en mémoire vive, sur disque dur, clé USB, CD, DVD…) et traite de l'information (calcul mathématique, « raisonnement » logique…).
Nous allons donc commencer par parler de l'information et plus exactement de la manière dont elle est représentée dans un ordinateur.
Codage binaire▲
Le bit▲
Toute information traitée par un ordinateur est codée en binaire, c'est-à -dire par une suite de bits valant zéro ou un.
La représentation physique du zéro et du un dépend du type de support utilisé : mémoire vive, disque dur, clé USB, CD, etc. Mais, quel que soit le type de support utilisé, il s'agit toujours d'un point de vue logique, d'une suite de zéros et de uns.
Le transistor▲
Pour prendre un exemple, la représentation physique actuelle d'un bit en mémoire vive est un transistor composé de silicium. La représentation du zéro et du un est basée sur la répartition des charges négative et positive dans le transistor.
Voici par exemple le zéro :
Et voici le un :
De 1970 à 2008, le nombre de transistors sur une puce électronique a augmenté de manière vertigineuse en suivant la loi de Moore : le nombre de transistors par puce double tous les deux ans pour le même prix de fabrication. Les transistors deviennent donc de plus en plus petits.
Comme un signal électrique met moins de temps à parcourir une distance plus courte, cette progression a des répercussions directes sur la vitesse des processeurs (elle double tous les 18 mois). Elle entraîne également une augmentation constante de la capacité des mémoires vives (la RAM des PC double tous les deux ans environ).
En 2008, la taille minimale d'un transistor était de l'ordre de 45 milliardièmes de mètre.
L'octet et autres unités▲
Une suite de huit bits est appelée un octet (ou byte en anglais). C'est l'unité de base permettant de définir la dimension d'un espace de stockage. Il en dérive des unités plus grandes comme :
- le kilooctet (ko = 210 octets = 1Â 024 octets)Â ;
- le mégaoctet (Mo = 1 024 Ko, soit environ un million d'octets) ;
- le gigaoctet (1Â 024 Mo, soit environ un milliard d'octets)Â ;
- le téraoctet (1 024 Go, soit environ mille milliards d'octets).
La mémoire vive des ordinateurs actuels (2013) est de l'ordre de plusieurs gigaoctets alors que la capacité des disques durs se compte en téraoctets.
La manière de coder une information en binaire dépend du type d'information. Les informations numériques, par exemple, ne sont pas codées de la même manière que le texte ou que les images ou le son.
Pour fixer les idées, voyons deux exemples de codage.
Le codage des nombres entiers en base 2▲
Le codage binaire des nombres entiers est basé sur l'écriture des nombres en base 2 (en utilisant uniquement les chiffres 0 et 1).
Le principe est le même que celui de l'écriture d'un nombre entier en base 10. c'est-à -dire l'écriture habituelle d'un nombre utilisant les 10 chiffres de 0 à 9. Rappelons que dans ce système, chaque chiffre est associé à une puissance de 10. Il y a un chiffre pour les unités (100), un chiffre pour les dizaines (101), un pour les centaines (102), un pour les milliers (103), etc. Par exemple, 2013 signifie : 2 × 103 + 0 × 102 + 1 × 101 + 3 × 100.
L'écriture d'un nombre entier en base 2 utilise le même principe, sauf que les puissances de 10 sont remplacées par des puissances de 2. Le nombre 40, s'écrira par exemple : 101000. Car 40 = 32 + 8 ou autrement dit 25 + 23 et donc 40 = 1 × 25 + 0 × 24 + 1 × 23 + 0 × 22 + 0 × 21 + 0 × 20.
Comme 40 n'a que six chiffres en base 2, on peut représenter ce nombre sur un octet comme suit :
Ce codage permet de coder de grands nombres en utilisant très peu de bits :
- jusqu'à 64 000 environ avec 2 octets pour les entiers non signés (32 000 avec signe) ;
- jusqu'à 4 milliards environ avec 4 octets pour les entiers non signés (2 milliards avec signe).
Le codage du texte en ASCII▲
Les informations textuelles (suite de caractères) sont souvent codées en ASCII. Dans ce codage, chaque caractère est représenté par un octet de valeur prédéfinie dans une table de correspondance.
La « valeur » est en fait l'interprétation du codage binaire de l'octet en nombre entier, comme nous l'avons vu au paragraphe précédent.
Prenons par exemple, le caractère « ( ». Dans la table de correspondance ASCII, on voit que sa valeur est 40. Autrement dit, le codage binaire du caractère « ( » est :
c'est-à -dire le même codage que celui du nombre 40 sur un octet !
Mais alors comment un ordinateur peut-il distinguer un nombre d'un caractère ?
Il ne le peut pas !
En effet, la simple connaissance d'une suite de 0 et de 1 ne permet pas de savoir ce que représente cette suite. Tout dépend de la manière dont elle est interprétée.
Si on donne l'octet précédent à un ordinateur, il y verra le nombre 40, si à ce moment-là il s'attend à un nombre et il y verra le caractère « ( », si à ce moment-là , il s'attend à un caractère.
Mémoire vive et adresse▲
Barrette de RAM DDR3 de 4 Go
La mémoire vive (ou RAM pour Random Access Memory) d'un ordinateur peut être vue comme une suite continue d'octets.
Chaque octet possède un numéro. Le numéro du premier est 0, du deuxième 1, etc. Ce numéro s'appelle l'adresse de l'octet.
Une plage mémoire est une suite d'octets consécutifs. Elle peut donc être définie par une plage d'adresses (adresses minimale et maximale) ou bien par l'adresse de début (ou autrement dit, l'adresse minimale) de la zone et du nombre d'octets qu'elle contient.
Voici, par exemple, une plage mémoire de trois octets, démarrant à l'adresse 2 :
Langage machine et programme exécutable▲
Processeur Intel 80486DX2 (taille: 12 × 6,75 mm)
Le processeur d'un ordinateur exécute en permanence des instructions que l'on appelle instructions machine. Il contient (en outre) des mémoires de petite taille (quelques octets), appelées registres, qui servent à transférer des données de la mémoire vers le processeur (ou inversement) en vue d'effectuer des opérations sur ces données.
Une instruction machine est un ordre donné au processeur.
Par exemple :
- copier une plage mémoire d'adresse donnée dans un registre ;
- copier le contenu d'un registre dans une plage mémoire d'adresse donnée ;
- additionner, soustraire, diviser ou multiplier les nombres contenus dans deux registres et enregistrer le résultat dans un troisième registre.
Nous avons vu plus haut que toute information traitée par un ordinateur est codée en binaire. Il en de même des instructions machine. Une instruction machine est codée en binaire sur quelques octets (4 ou 8 dans les machines actuelles). Une partie du codage définit le type d'opération à effectuer et une autre, les objets (les registres, par exemple) sur lesquels cette opération doit agir.
La manière de coder les instructions machine dépend du type de processeur.
On appelle langage machine l'ensemble de conventions définissant le codage binaire des instructions machine d'un type particulier de processeur.
Une suite d'instructions machine constitue un programme ou plus précisément un programme exécutable.
Il est important de noter que le langage machine est le seul langage directement exécutable par un ordinateur. Les langages de programmation (comme C, Java, PHP…) ne le sont pas !
Les programmes exécutables peuvent se trouver en mémoire (on dit qu'ils sont résidents) ou sur le disque, sous la forme de fichiers (reconnaissables sous Windows par leur extension .exe). Mais pour pouvoir être exécutés, ils doivent forcément être chargés en mémoire :
Nous avons dit plus haut qu'un programme exécutable est une suite d'instructions.
En fait, pour être plus exact, il faudrait définir un programme exécutable comme un mélange de zones de code contenant des instructions machine et de zones de données servant à stocker provisoirement les informations traitées (données ou résultats).
Processus▲
La mémoire d'un ordinateur contient plusieurs programmes que le processeur exécute à tour de rôle à une telle vitesse que l'on a l'impression qu'ils sont exécutés en même temps.
Chacun de ces programmes résidents possède donc ses propres zones d'instructions et de données.
L'exécution d'un programme particulier par un ordinateur s'appelle un processus.
Notez qu'un même programme peut donner lieu à plusieurs processus.
Parmi les processus résidents figurent nécessairement les processus système, qui servent (entre autres) à gérer les périphériques (clavier, souris, écran, disque, lecteur de CD, imprimantes, etc.) et les fichiers.
Les entrées-sorties▲
Jusqu'à présent, nous avons essentiellement décrit l'ordinateur du point de vue du processeur et de la mémoire vive. Le processeur exécute en permanence des programmes chargés en mémoire. Ces programmes sont des suites d'instructions machine et de plages mémoire réservées aux données.
Mais si les instructions exécutées par le processeur ne font que transférer des informations entre la mémoire vive et les registres, comment peut-il communiquer avec le clavier ? Avec la souris ? Avec l'écran ?
Comment les plages mémoire réservées aux données peuvent-elles recevoir les données de l'utilisateur ?
Comment peuvent-elles s'afficher à l'écran ?
Pour répondre à ces questions, il nous faut tout d'abord compléter notre vision d'un ordinateur et considérer les éléments externes à l'unité centrale que l'on appelle les périphériques (écran, clavier, souris, disque, lecteur de CD, clé USB, imprimante, etc.).
Les périphériques ont leurs propres circuits spécialisés appelés interfaces d'entrées-sorties. Pour l'écran, par exemple, il s'agit de la carte graphique. Ces circuits peuvent avoir leurs propres registres, leur propre mémoire et même leurs propres processeurs.
Ils communiquent avec le processeur et la mémoire par un ensemble de fils que l'on appelle le bus de données.
En réalité, les instructions exécutées par le processeur ne se limitent pas à la mémoire centrale. Certaines instructions spéciales, appelées instructions d'entrée-sortie, permettent de transférer des données entre les interfaces d'entrée-sortie et les registres du processeur ou directement avec la mémoire vive.
Ceci explique très sommairement comment se fait la communication entre le processeur et les périphériques et indirectement, comment un programme peut envoyer des informations vers un périphérique ou recevoir des informations provenant d'un périphérique.
Mais cela n'explique pas l'interaction avec l'utilisateur. En effet, vous remarquerez que votre ordinateur réagit instantanément (ou quasiment) lorsque vous éjectez un CD, introduisez une clé USB, bougez la souris, appuyez sur une touche du clavier, etc.
Ces actions physiques de l'utilisateur sur l'ordinateur peuvent se produire à n'importe quel moment, alors que le processeur est occupé à exécuter les instructions machine d'un programme quelconque. Or c'est nécessairement le processeur qui doit réagir à ces actions.
Il faut donc qu'il soit capable d'interrompre l'exécution du programme en cours pour réagir immédiatement à n'importe quelle action de l'utilisateur.
Comment cela est-il possible ?
Les interruptions▲
La réponse à cette question se trouve dans le mécanisme d'interruption.
Une interruption (physique) est un signal envoyé au processeur par un dispositif externe à l'unité centrale.
Le clavier va par exemple envoyer un signal d'interruption au processeur chaque fois qu'une touche est appuyée. La souris envoie une interruption chaque fois qu'elle est déplacée ou que l'utilisateur effectue un clic.
Le processeur possède des entrées spécialement prévues pour recevoir ces signaux.
Lorsqu'il reçoit une interruption, il interrompt provisoirement le programme qu'il est en train d'exécuter pour exécuter des instructions du système d'exploitation prévues, pour traiter ce type d'interruption. Il existe par exemple des instructions prévues pour traiter les interruptions clavier, d'autres pour traiter des interruptions souris, etc.
Le système ne gère pas entièrement les interruptions ; elles sont sous-traitées par les pilotes (drivers en anglais) des périphériques. Un pilote est un programme spécialisé dans la gestion d'un périphérique spécifique. Il ne fait pas partie du système d'exploitation, car il est généralement produit par le fabricant du périphérique.
Une fois l'interruption traitée, le processeur reprendra ce qu'il était en train de faire à l'endroit exact où il s'était arrêté.
Pour donner une image, on pourrait comparer ce mécanisme au réflexe humain. Prenons par exemple une personne lisant un livre. Le cerveau de cette personne joue le rôle du processeur. Le programme exécuté est la lecture du livre. Soudain, une abeille pique la personne. La douleur perçue est le signal d'interruption envoyé au cerveau (le processeur). Par réflexe, la personne lâche le livre. C'est une réaction préprogrammée dans l'inconscient (le système d'exploitation). Après avoir soigné la piqûre (traitement de l'interruption), la personne reprend la lecture du livre à l'endroit où elle s'était arrêtée.
Les Interfaces graphiques▲
Historique▲
Les premières interfaces graphiques sont apparues avec l'invention de la souris (1967) et des écrans graphiques, c'est-à -dire capables d'afficher des images.
Avant cela, les écrans d'ordinateur n'affichaient que du texte et ressemblaient plutôt à ceci :
En 1973 apparaît le premier ordinateur avec interface graphique : la station graphique Xerox Alto fabriquée en Californie, à Palo Alto, par la société Xerox (par ailleurs à l'origine de la machine à photocopier).
La station Xerox Alto
En 1983, cet ordinateur a inspiré le premier PC de Apple, le LISA, ancêtre des Macintosh puis en 1985, le premier système Windows (Windows 1.0) de Microsoft.
Le Lisa
C'est ainsi que sont apparus les premiers systèmes d'exploitation munis d'interfaces graphiques, dans lesquelles les applications apparaissent sous forme de fenêtres ou d'icônes sur lesquelles on peut agir grâce à la souris.
Interface du système▲
De manière générale, l'interface graphique d'un programme est la manière dont il se présente visuellement à l'écran lorsqu'il s'exécute.
Dans cette partie, nous décrivons plus particulièrement l'interface graphique du système qui apparaît au démarrage d'un ordinateur. Dans Windows, par exemple, elle est constituée du bureau et d'une barre de tâches.
Le bureau contient des icônes de différents programmes. La barre des tâches donne accès (entre autres) au menu Démarrer, par l'intermédiaire duquel on peut accéder à différents composants du système, configurer l'ordinateur, arrêter l'ordinateur, etc.
Tout ceci constitue l'interface graphique du système d'exploitation.
Le curseur▲
On voit également apparaître le curseur, qui se présente généralement sous la forme d'une petite flèche. Le curseur permet d'indiquer sur quel élément de l'interface graphique on veut agir.
Lorsque l'utilisateur déplace la souris, il provoque une interruption gérée par le système d'exploitation. Le traitement par défaut de cette interruption consiste à déplacer le curseur.
Gestion du clic▲
Comme nous l'avons vu, un clic provoque une interruption, qui est tout d'abord interceptée par le système d'exploitation. Le traitement de cette interruption dépendra de la position du curseur. Nous reviendrons là -dessus plus en détail un peu plus loin.
Lorsque l'on clique sur l'icône d'un programme, par exemple, le système le charge en mémoire et lance son exécution. L'interface graphique de ce programme s'affiche alors à l'écran.
Fenêtre d'une application▲
L'interface graphique d'un programme se présente généralement sous la forme d'un rectangle, appelé fenêtre, dans lequel apparaissent des composants ou contrôles. Les composants sont par exemple des menus, des barres d'outils, des zones de texte…
Voici par exemple une partie de l'interface graphique d'Excel 2003Â :
Les zones de texte sont des zones rectangulaires de l'écran dans lesquelles il est possible de taper du texte sur une ligne. Nous reviendrons plus en détail sur ce composant graphique.
À un moment donné, l'écran contient une (ou plusieurs) fenêtre(s) pour chaque application ouverte. Par exemple, une fenêtre pour Internet Explorer, une fenêtre pour Word, une autre pour Photoshop, etc. Mais parmi toutes ces fenêtres, il y a une seule fenêtre active : c'est la fenêtre qui apparaît au premier plan et qui contient le curseur.
Les évènements▲
Lorsqu'il se produit une interruption souris (déplacement de la souris ou clic) ou clavier (touche appuyée ou relâchée), il y a deux possibilités :
- Il n'y a aucune fenêtre active. Dans ce cas, l'interruption est directement gérée par le système. Sous Windows, c'est par exemple le cas lorsque vous cliquez sur le bureau ;
- Il y a une fenêtre active. Dans ce cas, le système intercepte tout d'abord l'interruption, mais il ne la gère pas forcément entièrement. Une partie de la gestion de l'interruption est sous-traitée à l'application associée à cette fenêtre. Pour cela, il communique toutes les informations utiles à l'application sous la forme d'un évènement. Les parties d'une l'application qui interceptent les évènements et les gèrent sont appelés des gestionnaires d'évènements.
Un évènement est lié à un composant particulier de la fenêtre, que l'on pourrait appeler le composant actif : c'est le composant contenant le curseur au moment de l'évènement.
Une application pourra ainsi savoir sur quel composant l'utilisateur a cliqué ou quel était le composant actif lorsqu'il a appuyé sur telle touche du clavier.
Les zones de texte▲
Les interfaces graphiques sont généralement conçues pour éviter au maximum de taper du texte. Mais il arrive que ce soit inévitable. Par exemple, pour donner le nom d'un fichier, une adresse web, une recherche par mots-clés, etc.
Dans une interface graphique, la saisie de texte se fait par l'intermédiaire des zones de texte, zones rectangulaires dans lesquelles il est possible de « rentrer » du texte.
Pour cela, l'utilisateur doit tout d'abord activer la zone de texte en cliquant dedans. Le curseur change alors de forme. En général, il se transforme en une barre verticale clignotante, ce qui signifie que la zone de texte est active et prête à recevoir du texte.
Lorsqu'une zone de texte est active, les caractères frappés au clavier viennent s'afficher à l'écran à l'intérieur de celle-ci. On a ainsi l'impression que les caractères sont directement transférés du clavier vers l'écran. En réalité, ils sont tout d'abord transférés en mémoire par le système d'exploitation.
En effet, à chaque zone de texte est associée une plage mémoire, appelée buffer, destinée à mémoriser les caractères frappés.
Supposons qu'une zone de texte soit active et que l'utilisateur appuie sur la touche « s ». Le système va alors transférer le caractère « s » dans le buffer associé à cette zone de texte, puis dans un deuxième temps, afficher le contenu du buffer à l'écran à l'intérieur de la zone de texte.
S'il appuie ensuite sur la touche « t », le caractère « t » va être rajouté dans le buffer après le « s », puis le buffer va être réaffiché.
Ce mécanisme est très important, car c'est essentiellement de cette manière que des données sont communiquées à un programme. Nous reviendrons là -dessus lorsque nous parlerons de la lecture des données.
La programmation▲
Les langages de programmation ont été introduits pour éviter l'écriture directe de programmes en langage machine (tâche extrêmement fastidieuse, pour ne pas dire impossible !).
Mais tout d'abord, qu'est-ce qu'un programme ?
Notion de programme▲
Un programme écrit dans un langage de programmation se présente sous la forme d'un texte dans lequel figurent les instructions à exécuter. Dans ce texte, les plages mémoire contenant des données portent un nom. Ce sont les variables du programme.
Voici par exemple une instruction écrite en Pascal :
somme := x + y;
Cette instruction signifie : additionner les nombres contenus dans les variables x et y et stocker le résultat dans la variable somme.
Dans les cours de programmation, un programme est souvent défini comme une suite d'instructions. Cette définition n'est pas tout à fait exacte, car les programmes contiennent également des déclarations. Une déclaration permet (entre autres) de définir la nature des variables sur lesquelles le programme doit travailler.
Voici par exemple une déclaration de variable écrite en Pascal :
var
x, y , somme : integer
;
Ici le programmeur a déclaré que les variables x, y et somme doivent contenir des nombres entiers.
Notion de langage▲
Un langage de programmation est un ensemble de conventions définissant la manière d'écrire un programme sous la forme d'un texte.
Ces conventions comprennent des conventions syntaxiques (comparables à la grammaire d'une langue naturelle) et lexicales (comparables à l'orthographe d'une langue naturelle).
Elles sont évidemment différentes d'un langage à l'autre. Voici par exemple l'instruction d'addition vue plus haut traduite dans différents langages de programmation :
On constate que dans certains langages (comme PHP et LISP), les déclarations de variables ne sont pas obligatoires. Ces langages sont dits non typés.
Tous les langages de programmation possèdent des mots spéciaux, que l'on appelle des mots-clés (dans l'exemple ci-dessus, ces mots figurent en gras). Les mots-clés ont une signification particulière prédéfinie dans le langage de programmation. Ils ne peuvent donc pas être utilisés par le programmeur pour signifier autre chose.
En Pascal, par exemple, le mot-clé Integer signifie nombre entier.
Fichiers sources▲
Un programme écrit dans un langage de programmation se présente sous la forme d'un ou plusieurs fichiers appelés fichiers sources (par opposition aux fichiers exécutables).
Ces fichiers contiennent les programmes tels qu'ils ont été initialement écrits par les développeurs. Ce sont des textes écrits en un langage de programmation particulier, que l'on peut visualiser à l'aide d'un éditeur de texte (WordPad, par exemple).
Mais, comme nous l'avons déjà dit plus haut, le seul langage de programmation directement exécutable par un ordinateur est le langage machine adapté à son processeur.
Les fichiers sources doivent donc être traduits en langage machine avant de pouvoir être exécutés. On appelle cela la compilation. Mais nous verrons plus loin que la compilation n'est pas la seule approche possible : il en existe une autre appelée interprétation.
Compilation▲
Notion de compilateur▲
Certains langages, le C par exemple, ne peuvent être que compilés. Les langages de ce type sont appelés langages compilés. Pour pouvoir les utiliser, vous devez avoir installé sur votre ordinateur un compilateur spécifique à ce langage.
Le compilateur est un programme exécutable qui permet de traduire un programme écrit dans un langage de programmation en langage machine.
Par exemple, pour pouvoir programmer en C, vous devez nécessairement avoir installé un compilateur C sur votre machine.
Après avoir été compilé, le programme peut être exécuté en chargeant sa version exécutable en mémoire  :
Exemple de compilation▲
Pour fixer un peu les idées, reprenons le programme d'addition en Pascal et voyons comment il pourrait être traduit en langage machine.
Les variables déclarées sont tout d'abord associées à des plages mémoires :
Les instructions sont ensuite traduites en instructions machine portant sur les registres et les plages mémoire associées aux variables :
Dans cet exemple, nous n'avons pas donné le code binaire des instructions machine, car il serait sans intérêt. On voit qu'une simple instruction écrite en langage de programmation peut correspondre à de nombreuses instructions machine.
Interprétation▲
Les programmes écrits en langages interprétés (Java, Visual Basic, PHP…) peuvent être exécutés sans avoir été auparavant entièrement traduits en langage machine.
Ils font appel à un interpréteur.
Tout comme un compilateur, un interpréteur est spécifique à un langage donné.
Il existe, par exemple, un interpréteur Java, un interpréteur Visual Basic, un interpréteur PHP…
Contrairement aux programmes compilés, les programmes interprétés ne sont pas directement exécutés par l'ordinateur, mais par l'interpréteur (qui, lui, est exécuté par la machine !).
Pour exécuter un programme interprété, il faut tout d'abord lancer l'interpréteur, puis charger le code source du programme en mémoire. Ce code source peut ensuite être exécuté par l'interpréteur :
L'interpréteur s'exécute donc toujours « en même temps » que le programme interprété.
Notez que certains interpréteurs (comme Visual Basic) peuvent également fonctionner en tant que compilateur. Cela permet d'obtenir une version plus rapide et commercialisable du programme, lorsque la mise au point est terminée. En effet :
- l'exécution d'un programme compilé est beaucoup plus rapide que son interprétation ;
- sous sa forme exécutable, un programme peut être vendu sans divulguer les fichiers sources, ainsi qu'à des utilisateurs ne possédant pas l'interpréteur sur leur machine.
Les EDI▲
Le développement d'application (ou si vous préférez la programmation) se fait aujourd'hui grâce à des logiciels appelés EDI (pour Environnement de Développement Intégré) ou IDE en anglais. Un EDI peut être adapté à un langage de programmation particulier (Delphi, Lazarus pour le langage Pascal, Visual C++ pour le langage C++) ou à plusieurs langages (Eclipse pour Java, PHP, JavaScript).
Un EDI comprend en général un éditeur de texte spécialement adapté au langage, un compilateur et/ou un interpréteur, un débogueur (outil de mise au point) et, surtout, un outil de développement d'interface graphique.
Avant l'existence des EDI, la conception de l'interface graphique d'un programme était extrêmement fastidieuse, car le développeur devait lui-même préciser dans le programme les positions et dimensions de tous les composants de l'interface.
Avec un EDI cela se fait simplement en « dessinant » en quelque sorte l'interface graphique à l'aide de la souris. Évidemment, les instructions permettant d'afficher l'interface existent toujours, mais au lieu d'être données par le programmeur, elles sont générées automatiquement par l'EDI.
La programmation évènementielle▲
Avec l'apparition des interfaces graphiques dans les années 80, la programmation est devenue plus complexe. Les programmes devaient d'une part produire tout le design de l'interface et d'autre part pouvoir réagir aux évènements, c'est-à -dire aux actions de l'utilisateur sur les différents composants de cette interface.
Il est alors apparu une nouvelle manière de programmer, appelée programmation évènementielle, qui consiste à associer des gestionnaires d'évènements aux composants de l'interface.
Un des premiers langages permettant la programmation évènementielle a été le langage Visual Basic de Alan Cooper (première version en 1991) et son environnement de développement intégré (un des premiers également). Le concept a ensuite été repris par Borland avec Delphi.
Alan Cooper - inventeur du Visual Basic
Un des points les plus intéressants des EDI est qu'ils facilitent la programmation évènementielle. Rappelons qu'avec un EDI, l'interface graphique peut être « dessinée » par le programmeur. Il peut ensuite associer un gestionnaire d'évènements (initialement vide) à un composant de l'interface en cliquant sur celui-ci.
Lazarus▲
Lazarus est un environnement de développement intégré associé au langage Pascal Objet. C'est la version open source du logiciel Delphi de Borland. Ce logiciel facilite en particulier la création d'interfaces graphiques et la programmation évènementielle par un mécanisme de génération automatique de code.
Notion de formulaire▲
Sous Lazarus, une fenêtre s'appelle un formulaire.
Voici, par exemple, un formulaire réalisé avec Lazarus :
Ce formulaire comporte trois étiquettes, trois zones de texte et un bouton.
Nous avons vu que les zones de texte permettent à l'utilisateur de saisir des données. En fait, elles servent également à afficher des résultats.
Dans notre exemple, les deux zones de texte étiquetées X et Y servent à saisir les deux nombres à additionner. Ce sont les données du programme. La zone de texte étiquetée X+Y sert à afficher la somme de ces deux nombres. C'est le résultat du programme produit par l'ordinateur.
Les étiquettes servent donc en principe à indiquer à l'utilisateur quelle donnée il doit introduire dans la zone de texte située juste à côté ou bien quel résultat y sera affiché.
Lorsque l'utilisateur clique sur le bouton Additionner, après avoir saisi les deux nombres dans les zones de texte prévues à cet effet, le programme affiche le résultat dans la zone de texte étiquetée X+Y.
Un bouton sert donc à déclencher un traitement d'information particulier (dans notre cas, l'addition de deux nombres).
Génération automatique de code▲
La création d'une nouvelle application engendre automatiquement la création d'un formulaire (initialement vide) associé à un fichier source Pascal appelé unité (Unit en anglais).
Le formulaire de départ
Par défaut, ce formulaire s'appelle Form1 et l'unité Pascal associée, Unit1. Si le développeur ne la renomme pas autrement, elle sera représentée sur disque par le fichier Unit1.pas.
Dès la création du formulaire, l'unité qui lui est associée contient du code Pascal généré automatiquement.
Le voici :
Au fur et à mesure que le programmeur ajoute des composants sur le formulaire, leurs noms apparaissent automatiquement dans le code source. Avec l'exemple précédent du programme d'addition, ce serait Label1, Label2, Label3 pour les étiquettes ; Edit1, Edit2, Edit3 pour les zones de texte ; Button1 pour le bouton.
Voici le code source de l'unité associée au programme d'addition, après que le programmeur a déposé les différents composants sur le formulaire :
Ces noms permettront d'accéder aux composants par programme. Il ne faut pas les confondre avec leurs libellés. Les libellés apparaissent sur l'interface graphique. Les noms quant à eux sont invisibles.
Par exemple, le nom du bouton est Button1, mais son libellé est Additionner.
Pour pouvoir plus facilement identifier les composants dans le programme, le développeur peut leur donner des noms plus significatifs que ceux générés automatiquement par Lazarus. Par exemple, BoutonAdditionner au lieu de Button1 pour le bouton, ZoneTexteX et ZoneTexteY pour les zones de texte contenant les deux nombres à additionner, ZoneTexteSomme pour la zone de texte contenant la somme des deux nombres.
La modification des noms des composants se fait via l'interface graphique de Lazarus. Ils sont alors automatiquement modifiés dans le code.
Voici le code modifié automatiquement après modification des noms :
Programmation évènementielle▲
L'interface graphique de Lazarus est conçue pour faciliter la programmation évènementielle.
Rappelons que la programmation évènementielle consiste à associer des gestionnaires d'évènements à des évènements de l'interface graphique. Un évènement est défini par un composant de l'interface et un type d'interruption. Par exemple, un bouton (composant) et un clic sur ce bouton (interruption). Lorsque cet évènement a effectivement lieu, les instructions contenues dans le gestionnaire d'évènements sont automatiquement exécutées.
Sous Lazarus, un gestionnaire d'évènements s'appelle une procédure évènementielle.
Pour associer une procédure évènementielle à un composant, le développeur n'a besoin de taper aucune ligne de code. Il lui suffit en gros de cliquer sur le composant et éventuellement de sélectionner le type d'interruption. Un double-clic sur un composant sélectionne l'interruption par défaut. La déclaration de la procédure évènementielle est alors automatiquement générée dans le code avec un nom formé à partir du nom du composant et du nom de l'interruption.
Reprenons l'exemple de notre programme d'addition.
Supposons que le développeur souhaite associer une procédure évènementielle au bouton Additionner, qui serait activée lorsque l'utilisateur clique sur ce bouton.
Pour cela, il lui suffit de faire un double-clic sur ce bouton et la déclaration de cette procédure évènementielle sera automatiquement générée dans le code source, avec aucune instruction à l'intérieur.
Le nom de cette procédure est BoutonAdditionnerClick. Il est formé à partir du nom du composant (ici BoutonAdditionner) et du nom de l'interruption (Click = clic de souris).
Le développeur complètera ensuite ce gestionnaire d'évènements en plaçant des instructions à l'intérieur (entre begin et end), qui seront automatiquement exécutées lorsque l'utilisateur cliquera sur ce bouton.
Ordre d'exécution des instructions▲
En général, un programme ne s'exécute pas dans l'ordre des lignes du code source, c'est-à -dire de la première à la dernière ligne du fichier.
Considérons un programme dont l'interface graphique possède trois boutons A, B et C :
Dans le code source, on a d'abord la procédure évènementielle du bouton B, puis celle du bouton A, puis celle du bouton C. Mais l'ordre d'exécution de ces procédures est totalement indépendant de l'ordre dans lequel elles figurent dans le fichier source. Cela dépend évidemment de l'ordre dans lequel l'utilisateur cliquera sur les boutons A, B et C. Une même procédure peut même être exécutée plusieurs fois.
Lorsqu'une procédure évènementielle a fini de s'exécuter, le programme retourne dans un état d'attente d'évènement.
Notice d'utilisation▲
Téléchargez ici la notice d'utilisation de Lazarus.
Les variables▲
La notion de variable▲
La notion de variable existe dans tous les langages de programmation.
Les variables servent à conserver temporairement des données en mémoire.
Une variable peut être vue comme une plage mémoire dans une zone de données d'un programme. Elle porte un nom et possède à un instant donné une valeur.
On peut donc parler de l'adresse d'une variable : c'est l'adresse du début la plage mémoire qui lui est réservée.
Voici par exemple quatre variables nommées Age, Nom, NombreEnfant et Prenom.
La valeur d'une variable varie lors de l'exécution d'un programme (d'où le nom de variable justement !). La variable NombreEnfant pourra par exemple valoir successivement 2, 0, 3, 1, 0, 4…
Par contre, le nom d'une variable ne change pas : il est le même du début à la fin de l'exécution d'un programme.
Les noms de variables en Pascal▲
En Pascal, les noms de variables doivent respecter les règles suivantes :
Règle1 : le premier caractère est obligatoirement une lettre ;
Règle2 : les seuls caractères autorisés sont les lettres (sans accents), les chiffres et « _ » ;
Règle3 : un nom de variable ne peut pas être un mot-clé du langage.
Voici quelques exemples et contre-exemples de nom de variables :
Nom | Validité | Règles non respectées |
Prenom | Oui | Â |
Prénom | Non | 2 |
Nom101 | Oui | Â |
Nom_Eleve | Oui | Â |
Nom Eleve | Non | 2 |
_Age | Non | 1 |
Age-Eleve | Non | 2 |
Integer | Non | 3 |
Attention : la casse n'a pas d'importance, c'est-à -dire que le Pascal ne distingue pas les minuscules des majuscules. Nom_Eleve, nom_Eleve, nom_eleve désignent donc la même variable.
La notion de type▲
Il existe différents types de variables.
Le type d'une variable définit la manière dont elle est représentée en mémoire et les opérations que l'on peut faire dessus.
Il existe, par exemple, des variables de type chaîne de caractères, qui vont servir à stocker du texte en mémoire. La variable Prenom pourrait, par exemple, être de ce type. À un instant donné, sa valeur pourrait être 'Astérix', à un autre moment 'Obélix' ou encore à un autre moment 'Abraracourcix'.
Il existe aussi des variables de type entier. Ce pourrait, par exemple, être les variables Age, ou NombreEnfant.
La représentation en mémoire d'une variable dépend de son de type. En particulier, deux variables de types distincts n'occupent pas en général la même place mémoire :
D'autre part, les opérations que l'on peut faire sur deux variables de types différents ne sont pas les mêmes. Par exemple, on ne pourra pas multiplier deux chaînes de caractères, alors que l'on peut très bien multiplier deux entiers.
Déclaration d'une variable▲
La déclaration d'une variable sert à définir son type et, par conséquent, la manière dont elle sera représentée en mémoire. Elle a pour effet de réserver une plage mémoire pour stocker la valeur de cette variable. Le type de la variable définit la taille de cette plage et la manière de coder ses valeurs.
Toute variable doit (en principe) être déclarée, avant de pouvoir être utilisée. En Pascal, la déclaration est obligatoire. D'autres langages (comme Visual Basic) autorisent une déclaration implicite avec un type par défaut. Malheureusement, cela autorise également des erreurs de programmation…
La manière de déclarer une variable dépend évidemment du langage de programmation, mais on y trouve toujours les mêmes ingrédients : le nom de la variable et son type.
Déclaration d'une variable en Pascal▲
Syntaxe générale▲
Une déclaration de variable en Pascal utilise le mot-clé Var suivi du nom de la variable, du caractère « : », puis du nom du type et d'un point-virgule.
Var
Nom de la variable : nom du type
;
Déclaration d'un entier▲
Prenons par exemple la variable Age.
En Pascal, le type entier s'appelle Integer. La déclaration de la variable Age se ferait donc comme ceci :
Var
Age : Integer
;
Le type double▲
Les nombres entiers ne sont pas les seuls nombres avec lesquels un ordinateur peut calculer. Il y a également les nombres décimaux (comme 0.5 ou 3.14 par exemple). Ces nombres peuvent par exemple (il y a d'autres possibilités) être représentés par le type Double en Pascal.
Déclaration d'une chaîne de caractères▲
Prenons par exemple la variable Prenom, de type chaîne de caractères.
En Pascal, le type chaîne de caractères s'appelle String. La déclaration de la variable Prenom s'écrira donc :
Var
Prenom : String
;
Regroupement des déclarations▲
Remarquez qu'il est inutile de répéter le mot-clé var. De plus, les variables de même type peuvent être regroupées : on sépare les noms des variables par des virgules puis on précise leur type par : nom du type ;
Exemple :
Var
Nom, Prenom : String
; Age, NombreEnfant : Integer
;
Où faut-il déclarer les variables ?▲
Pour qu'une variable soit utilisable à n'importe quel endroit d'une unité Pascal, elle doit être déclarée entre var et implementation, comme dans l'exemple suivant :
var
Form1: TForm1;
// Déclaration des variables globales
x, y , somme : integer
;
implementation
Nous avons déclaré ici trois variables de type entier : x, y et somme.
Les variables déclarées de cette manière sont appelées variables globales. Cela signifie qu'elles peuvent être utilisées dans tout le code du fichier où elles sont déclarées.
Lecture et affichage▲
Données et résultats d'un traitement▲
De nombreux traitements informatiques peuvent se schématiser suivant les trois étapes suivantes :
L'image du sourd-muet qui ne sait pas additionner▲
Pour bien comprendre le sens des termes lecture, traitement et affichage, mettez-vous à la place de l'ordinateur. Votre rôle est d'additionner mentalement deux nombres pour un sourd-muet qui ne sait pas calculer (l'utilisateur).
Comme il ne peut pas parler, il vous écrit les deux nombres à additionner sur un bout de papier, qui joue le rôle de l'écran.
Vous ne pouvez pas additionner les deux nombres si vous ne les connaissez pas. Il faut donc commencer par les lire. C'est la première étape de lecture des données.
À présent, les deux nombres à additionner sont dans votre mémoire. Vous pouvez donc effectuer le traitement, qui consiste à additionner mentalement ces deux nombres. C'est l'étape de traitement des données.
Vous avez le résultat. Mais si vous ne le communiquez pas à l'utilisateur, c'est comme si vous n'aviez rien fait. Comme il est sourd, vous allez écrire le résultat sur le bout de papier. C'est la troisième étape : l'affichage des résultats du traitement.
Pour un ordinateur▲
Pour un ordinateur c'est un peu la même chose :
- Lire les données signifie transférer des informations affichées à l'écran vers la mémoire, ou plus précisément vers une variable-donnée, c'est-à -dire une variable prévue pour stocker une donnée du traitement.
- Traiter les données signifie grosso modo faire des calculs qui dépendent des données pour produire des résultats. En principe, mais ce n'est pas obligatoire, chaque résultat est stocké dans une variable-résultat. La programmation de cette étape sera vue plus loin dans le cours lorsque nous parlerons d'expression et d'affectation.
- Si les résultats ont été sauvegardés dans des variables-résultats, Afficher les résultats signifie afficher la valeur de chacune de ces variables à l'écran. Il est également possible d'afficher les résultats d'un traitement sans les avoir au préalable sauvegardés dans des variables. Mais il serait difficile pour l'instant d'expliquer comment. Nous verrons cela un peu plus loin.
Exemple du programme d'addition▲
Reprenons l'exemple de l'addition de deux nombres.
Lire les données signifierait dans ce cas lire les valeurs des deux nombres à additionner depuis (par exemple) deux zones de texte pour les stocker dans deux variables données X et Y.
Traiter les données signifierait additionner les valeurs des variables X et Y, puis mémoriser le résultat dans une variable Somme.
Enfin, afficher les résultats signifierait afficher la valeur de la variable somme dans la zone de texte prévue à cet effet.
Historique▲
Avant l'apparition des interfaces graphiques, tous les langages de programmation avaient des procédures (cette notion sera abordée dans le chapitre sous-programmes) pour lire et afficher.
En Pascal, par exemple, elles s'appellent readln (pour la lecture) et writeln (pour l'écriture).
La procédure writeln▲
La procédure writeln vous permettait d'afficher un texte à l'écran. Par exemple l'instruction :
writeln ('Hello world !'
);
vous affichait le texte « Hello Word » à l'écran, comme ceci (en supposant que l'écran était vide avant l'exécution de l'instruction) :
Avec la procédure writeln, vous pouviez également afficher la valeur d'une variable à l'écran. Par exemple, en supposant que la variable Somme contienne la valeur 2013, l'instruction :
writeln (Somme);
vous affichait « 2013 » à l'écran, comme ceci :
Chaque writeln affichait donc une nouvelle ligne à la suite des lignes déjà affichées à l'écran.
La procédure readln▲
La procédure readln permettait de lire la valeur d'une variable tapée au clavier (et donc affichée à l'écran).
Supposons par exemple qu'un programme exécute l'instruction suivante :
readln (X);
où X représente un nombre entier.
Dès que l'instruction est exécutée, le programme est momentanément interrompu. Le système d'exploitation prend alors la main et range chaque caractère saisi par l'utilisateur dans un buffer associé au clavier. Dès que l'utilisateur appuie sur la touche Entrée, le contenu du buffer est renvoyé au programme qui stocke alors la valeur correspondante dans la variable X et continue son exécution.
ETBib▲
ETBib est une unité Pascal que j'ai écrite afin d'introduire des procédures équivalentes à readln et writeln fonctionnant avec une interface graphique.
Ces procédures sont les suivantes :
- Lire, LireEntier et LireNombre : pour lire une variable depuis une zone de texte ;
- Afficher, AfficherEntier, et AfficherNombre : pour afficher une variable dans une zone de texte ou une zone de liste ;
- Effacer : pour effacer le contenu d'une zone texte ou d'une zone de liste.
Ces procédures ont été introduites pour des raisons pédagogiques (explications pour le lecteur averti). Donc gardez bien à l'esprit qu'elles ne font pas partie du langage Pascal.
Exemple d'utilisation des procédures de ETBib▲
Pour expliquer le fonctionnement des procédures de ETBib, nous allons prendre un exemple de programme qui permet de lire et d'afficher trois variables :
- Ville, de type String, est destinée à contenir le nom d'une ville. Sa valeur sera lue et affichée dans la zone de texte ZT_Ville ;
- CP, de type integer, pour le code postal de la ville, lue et affichée dans la zone de texte ZT_CodePostal ;
- NH, de type double, pour le nombre d'habitants de la ville exprimé en milliers. Remarquez que ce nombre n'est pas forcément entier : 264,2 représente par exemple 264 200 habitants. La zone de texte correspondante se nomme ZT_Hab.
Voici l'interface graphique du programme :
Lecture des variables▲
La lecture des trois variables est déclenchée par le bouton Lire. Voici la procédure évènementielle associée :
procedure
TForm1.BT_LireClick(Sender: TObject);
begin
Lire (Ville,ZT_Ville);
LireEntier (CP, ZT_CodePostal);
LireNombre (NH,ZT_Hab);
end
;
La procédure de lecture à utiliser dépend du type de la variable : Lire pour lire une variable de type String, LireEntier pour lire une variable de type Integer et LireNombre pour lire une variable de type Double.
Entre parenthèses figurent :
- en première position : le nom de la variable à lire ;
- en deuxième position : le nom de la zone de texte.
Rappelez-vous que chaque zone de texte est associée à un buffer, qui se remplit avec les caractères frappés au clavier.
Lorsque la variable est de type chaîne de caractères, la procédure de lecture ne fait que recopier ces caractères dans la variable.
S'il s'agit d'une variable de type Integer ou Double, il ne suffit pas de recopier le buffer dans la variable. Le contenu du buffer est tout d'abord codé en base 2, puis c'est le résultat de ce codage qui est stocké dans la variable.
Affichage des variables▲
L'affichage des trois variables se fait par le bouton Afficher, dont voici la procédure évènementielle :
procedure
TForm1.BT_AfficherClick(Sender: TObject);
begin
Afficher (Ville, ZT_Ville);
AfficherEntier (CP, ZT_CodePostal);
AfficherNombre (NH, ZT_Hab);
end
;
Comme pour la lecture, il faut choisir la procédure d'affichage en fonction du type de la variable : Afficher pour afficher une variable de type String, AfficherEntier pour une variable de type integer et AfficherNombre pour une variable de type Double.
Dans les parenthèses, on précise le nom de la variable à afficher, puis le nom de la zone de texte dans laquelle on souhaite l'afficher.
La procédure Afficher recopie simplement le contenu de la variable dans le buffer, qui est ensuite affiché dans la zone de texte par le système d'exploitation.
Dans le cas des procédures AfficherNombre et AfficherEntier, un décodage préalable est nécessaire avant de recopier la chaîne de caractères représentant le nombre dans le buffer.
Effacement des zones de texte▲
L'effacement des zones de texte s'effectue avec le bouton Effacer, dont voici la procédure évènementielle :
procedure
TForm1.BT_EffacerClick(Sender: TObject);
begin
Effacer (ZT_Ville); Effacer (ZT_Hab);
Effacer (ZT_CodePostal);
end
;
Il suffit ici de préciser entre parenthèses le nom de la zone de texte à effacer.
Argumentation pédagogique▲
Cette section s'adresse au lecteur averti (professeur ou élève avancé) désirant comprendre la raison pour laquelle j'ai introduit les procédures de lecture et d'affichage.
Reprenons notre exemple. Voici comment lire les trois variables sans utiliser les procédures Lire, LireEntier et LireNombre :
procedure
TForm1.BT_LireClick(Sender: TObject);
begin
Ville := ZT_Ville.Text;
CP := StrToInt(ZT_CodePostal.Text);
NH := StrToFloat (ZT_Hab.Text);
end
;
Ce qui me gêne ici est l'utilisation obligatoire de la programmation objet. On accède à l'attribut Text de la classe TEdit. C'est encore pire lorsqu'il s'agit d'afficher du texte dans une zone de liste, puisqu'il faut alors appliquer la méthode Add à l'attribut items (objet de la classe TStringList) d'un objet de la classe TListBox.
Or, je pense que l'on peut difficilement commencer un cours de programmation avec des notions de programmation objet. À mon sens, la programmation objet ne peut être abordée qu'après avoir bien assimilé les types structurés (puisque les objets sont en quelque sorte des types structurés étendus) et les sous-programmes (puisque les méthodes sont des sous-programmes associés à des classes).
Mis à part la programmation objet, d'autres arguments peuvent être ajoutés en faveur de l'utilisation des procédures de lecture et d'affichage :
- cela rend le code plus lisible ;
- cela oblige les élèves à programmer plus proprement : les données doivent obligatoirement être transférées dans des variables avant d'être utilisées. On ne travaille pas directement sur les zones de texte pour faire des calculs ;
- on voit mieux la différence entre une lecture (ou un affichage) et une simple affectation.
Les expressions▲
Vous connaissez déjà tous les expressions mathématiques dans lesquelles on retrouve des nombres, des opérateurs de calculs (+, -, etc.) et des variables. Par exemple :
(a + b) × (a - 17)
Dans les langages de programmation, on retrouve également cette notion d'expression sous une forme un peu différente et plus générale.
Une expression est une écriture possédant une valeur et un type, dans laquelle figurent (entre autres) des littéraux, des constantes, des opérateurs et des noms de variables.
Vous savez déjà ce que sont les variables. Par contre, nous n'avons pas encore défini les littéraux, les constantes et les opérateurs.
La valeur d'une expression est le résultat du calcul de cette expression, en tenant compte des valeurs des variables qu'elle contient. Son type est le type du résultat.
L'écriture Age+1, est, par exemple, une expression de type entier, car :
- la variable Age est de type entier ;
- 1 est un entier ;
- la somme de deux entiers est un entier.
Sa valeur est celle de la variable Age augmentée de 1.
Une expression peut faire intervenir plusieurs opérations. Par exemple :
(Age + 1) * 2
représente le double de la valeur de la variable Age augmentée de 1.
Voilà différentes valeurs des expressions Age+1 et (Age+1)*2 pour différentes valeurs de la variable Age :
Age | Age+1 | (Age + 1) * 2 |
10 | 11 | 22 |
0 | 1 | 2 |
43 | 44 | 88 |
Les littéraux▲
Un littéral est l'écriture d'une valeur d'un certain type de variable.
L'écriture des littéraux obéit à un certain nombre de conventions qui dépendent du type et du langage de programmation.
Les littéraux numériques▲
Un littéral numérique représente la valeur d'un nombre.
Il existe de nombreuses manières d'écrire un nombre. On peut par exemple avoir :
- une simple suite de chiffres. Cette écriture ne convient que pour les nombres entiers positifs. Exemple : 2012 ;
- pour pouvoir représenter des entiers relatifs, il faut autoriser la présence d'un signe + ou - devant cette suite de chiffres. Exemple : -15, +320 ;
- pour pouvoir représenter des nombres non entiers, on utilise le point. La partie entière du nombre est suivie d'un point puis d'une autre suite de chiffres. Exemples : 3.141559, -5.33333 ;
- pour pouvoir représenter des nombres très grands ou très petits, on autorise également la notation scientifique. Exemple : 1.7E+308.
Les littéraux chaînes de caractères▲
Un littéral de type chaîne de caractères s'écrit entre quotes.
Par exemple : 'Il fait beau', '4012', '(3 x + 1)', 'VisualC++', '@%^šÂ€' sont des littéraux de type chaîne de caractères.
On remarquera que tous les caractères sont autorisés entre les quotes sauf les quotes eux-mêmes ! Pour mettre une quote dans une chaîne de caractères, il faut la faire précéder par une autre quote. Par exemple :
L''
horaire d''
été'
Parmi les chaînes de caractères, il y en a une qui joue un rôle particulier : la chaîne vide.
La chaîne vide est une chaîne qui ne contient aucun caractère. On l'écrit '' (deux quotes).
Les constantes▲
Une constante est un littéral portant un nom. Avant la compilation (ou l'interprétation) du code, ce nom sera remplacé par le littéral dans tout le code.
Une constante est donc en quelque sorte le synonyme d'une valeur. On pourra par exemple introduire une constante PI représentant le nombre 3.14159. Avant la compilation du code (cette étape s'appelle en fait la précompilation), toutes les occurrences du mot PI seront remplacées par 3.14159.
Comme son nom l'indique, la valeur d'une constante ne peut pas varier durant l'exécution d'un programme.
Déclaration d'une constante en Pascal▲
En Pascal, une constante peut se déclarer de la manière suivante :
Const
nom de constante = littéral ;
Le nom d'une constante obéit aux mêmes règles que le nom d'une variable.
Exemple :
Const
PI = 3
.14159
;
CLEF_USB = 'F:'
; DISQUE_DUR = 'C:'
;
Opérateur▲
En informatique, un opérateur est généralement représenté par un ou deux caractères représentant une certaine opération.
Voici, par exemple, quelques opérateurs fréquemment rencontrés dans les langages de programmation : +  -  /  *  ^  = ++ !  %
Dans ce cours, nous nous concentrerons pour l'instant sur les opérateurs suivants :
- l'opérateur d'addition, représentée par le caractère + ;
- l'opérateur de soustraction,représenté par le caractère - ;
- l'opérateur de multiplication, représenté par le caractère * (et non pas × comme en mathématique !) ;
- L'opérateur de division, représenté par le caractère / ;
- l'opérateur de concaténation, représenté par le caractère +.
Vous connaissez déjà les quatre premiers opérateurs, puisqu'il s'agit des opérateurs mathématiques classiques. Mais si vous n'avez jamais fait de programmation, vous ignorez probablement le sens de l'opérateur de concaténation.
La concaténation▲
Concaténer deux chaînes de caractères signifie les mettre bout à bout.
Par exemple, si je concatène la chaîne 'para' avec la chaîne 'pluie', j'obtiens la chaîne 'parapluie'.
La manière de noter l'opérateur de concaténation dépend des langages.
En Pascal, il se note comme l'opérateur d'addition, c'est-à -dire avec un signe +.
L'opérateur de concaténation permet de construire des expressions de type chaîne de caractères.
Par exemple, 'Je suis '+Prenom est une expression de type chaîne de caractères constituée d'un littéral ('Je suis '), de l'opérateur de concaténation + et d'un nom de variable (Prenom).
On peut concaténer autant de chaînes de caractères qu'on le souhaite. Par exemple, dans l'expression 'Je suis '+Prenom+' '+Nom il y a quatre chaînes de caractères.
Voilà différentes valeurs de l'expression
'Je suis '
+ Prenom
pour différentes valeurs de la variable Prenom :
Prenom | 'Je suis '+Prenom |
'Jimi' | 'Je suis Jimi' |
'Albert' | 'Je suis Albert' |
Voilà différentes valeurs de l'expression
'Je suis '
+ Prenom + ' '
+ Nom
pour différentes valeurs des variables Prenom et Nom :
Prenom | Nom | 'Je suis '+Prenom+' '+Nom |
'Jimi' | 'Hendrix' | 'Je suis Jimi Hendrix' |
'Albert' | 'Einstein' | 'Je suis Albert Einstein' |
'Albert' | '' | 'Je suis Albert' |
Validité d'une expression▲
L'écriture d'une expression dans un certain langage de programmation doit nécessairement obéir aux règles propres à ce langage.
Une expression de type T est valide si elle peut être « comprise » par l'interpréteur (ou le compilateur) comme un calcul d'une valeur de type T ou plus généralement comme la construction d'un objet de type T.
Nous donnons ici, un certain nombre de règles non exhaustives permettant d'écrire des expressions valides en Pascal.
Expressions valides de type chaîne de caractères▲
Les règles suivantes permettent de construire des expressions de type chaîne de caractères valides :
Règle1 : un nom de variable est une expression valide si et seulement cette variable est de type chaîne de caractères ;
Règle2 : un littéral (ou une constante représentant ce littéral) est une expression valide si et seulement si il est du type chaîne de caractères ;
Règle3 : A+B est valide si et seulement si A et B sont deux expressions valides de type chaîne de caractères ;
Règle4 : un littéral, une constante ou un nom de variable ne pas être suivi immédiatement d'un littéral, d'une constante ou d'un nom de variable.
Exemples d'application de ces règles (Nom et Prenom sont des chaînes de caractères et Age est un entier) :
Expression | Validité | Règle non respectée |
Prenom | Oui | Â |
Age | Non | 1 |
'Thirion' | Oui | Â |
48 | Non | 2 |
'Eric'+'Thirion' | Oui | Â |
Prenom+Nom | Oui | Â |
Prenom+'Thirion' | Oui | Â |
'Cours de'+Prenom+'Thirion' | Oui | Â |
'Eric'+'Thirion a' +Age+' ans' | Non | 3 |
'Eric'+'Thirion a' + 48 +' ans' | Non | 3 |
Prenom Nom | Non | 4 |
'Eric' 'Thirion' | Non | 4 |
Prenom 'Thirion' | Non | 4 |
Expressions valides de type numérique▲
Les règles suivantes (non exhaustives) permettent de construire des expressions numériques valides :
Règle1 : une variable est une expression numérique valide si et seulement si elle est de type numérique (integer ou double par exemple) ;
Règle2 : un littéral (ou une constante représentant ce littéral) est une expression numérique valide si et seulement si il est de type numérique ;
Règle3 : les écritures A+B, A-B, A*B, A/B sont des expressions numériques valides si et seulement si A et B sont des expressions numériques valides ;
Règle4 : un littéral, une constante ou un nom de variable ne peut pas être suivi immédiatement d'un littéral, d'une constante ou d'un nom de variable ;
Règle5 : (A) est une expression numérique valide si et seulement si A est une expression numérique valide ;
Règle6 : chaque parenthèse ouvrante correspond à une et une seule parenthèse fermante.
Exemples d'application de ces règles (avec Nom de type String, Age de type Integer et Poids de type Double) :
Expression | Validité | Règle non respectée |
Nom | Non | 1 |
Age | Oui | Â |
201 | Oui | Â |
20.14 | Oui | Â |
'20.14' | Non | 2 |
Age * Poids | Oui | Â |
Age + Nom | Non | 3 |
Poids/(Age*2.2) | Oui | Â |
Age Poids | Non | 4 |
Age 49 | Non | 4 |
(Age * Poids) | Oui | Â |
(Age * Nom) | Non | 5 |
(Poids/((Age*(2.2))) | Oui | Â |
Poids/((Age + 1 ) | Non | 6 |
Affichage d'expression▲
Les procédures d'affichage de l'unité ETBib permettent d'afficher la valeur d'une expression. Selon le type de l'expression, on utilisera Afficher, AfficherEntier ou AfficherNombre.
Expression de type chaîne de caractères▲
Pour afficher une expression E de type chaîne de caractères dans une zone de texte ou zone de liste Z, on utilisera l'instruction :
Afficher ( E, Z);
Exemple : la variable Nom contient le nom d'une personne et la variable Adresse son adresse. L'instruction suivante permet d'afficher le nom et l'adresse de la personne sur une seule ligne dans la zone de liste ZL_Client :
Afficher (Nom + '-'
+ Adresse, ZL_Client);
Le nom et l'adresse sont séparés par un tiret.
Expression numérique▲
Pour une expression numérique dont le résultat est toujours un nombre entier, on peut utiliser AfficherEntier ou AfficherNombre. Par contre, si le résultat n'est pas forcément entier, il faudra utiliser AfficherNombre.
Exemple 1 : un hôtel propose des chambres à 47 euros. La variable NJ contient le nombre de jours. L'instruction suivante permet d'afficher le prix du séjour dans la zone de texte ZT_PrixSejour :
AfficherEntier ( NJ * 47
, ZT_PrixSejour);
Dans cet exemple, on peut utiliser la procédure AfficherEntier, car le prix à payer est forcément un nombre entier.
Exemple 2 : supposons à présent que le prix d'une chambre soit variable et pas forcément entier. Ce prix est stocké dans une variable P de type double. Dans ce cas, on ne sait pas si le prix du séjour est un nombre entier. Il faut donc utiliser l'instruction :
AfficherNombre ( NJ * P , ZT_PrixSejour);
Traitement sans variables-résultats▲
Revenons sur le schéma lecture-traitement-affichage. À présent, nous pouvons expliquer comment afficher les résultats d'un traitement sans les sauvegarder dans des variables : il suffit d'écrire l'expression du traitement à l'intérieur de l'instruction d'affichage !
Voilà par exemple comment écrire le programme d'addition sans sauvegarder le résultat dans une variable :
var
Form1: TForm1;
// Déclaration des variables globales
x, y : integer
;
implementation
{ TForm1 }
procedure
TForm1.BoutonAdditionnerClick(Sender: TObject);
begin
// Lecture des données
LireEntier (x,zoneTexteX);
LireEntier (y,ZoneTexteY);
// Traitement et Affichage du résultat
AfficherEntier (x + y, ZoneTexteSomme);
end
;
Le calcul de l'addition se fait à l'intérieur de l'instruction d'affichage.
Dans des cas simples, cette manière de programmer peut être utilisée. Mais de manière générale, essayez autant que possible de séparer le traitement des données et l'affichage. Vous obtiendrez ainsi des programmes plus lisibles et plus faciles à modifier.
L'affectation▲
L'affectation est une instruction qui permet de modifier la valeur d'une variable. Plus précisément :
Affecter une valeur à une variable signifie enregistrer cette valeur dans la plage mémoire allouée à cette variable.
Affecter une valeur à une variable modifie donc l'état de la zone mémoire qui lui est allouée, ou autrement dit son « contenu ».
Reprenons notre exemple avec les quatre variables Age, NombreEnfant, Nom et Prenom. Supposons qu'à un instant donné l'état de la mémoire soit le suivant :
Si on affecte ensuite la valeur 33 à la variable Age, on obtient :
Et la zone mémoire allouée à la variable Age restera inchangée jusqu'à ce qu'une valeur différente lui soit affectée.
Écriture d'une instruction d'affectation en Pascal▲
En Pascal, on représente l'affectation par l'opérateur :=. Le nom de la variable figure à gauche de := et la valeur qu'on veut lui affecter, à droite.
Par exemple, affecter la valeur 33 à la variable Age s'écrira :
Age := 33
;
De manière générale, ce qui figure à droite de := est une expression. Une affectation s'écrit donc sous la forme :
Nom de variable := expression ;
Une affectation est toujours exécutée de droite à gauche. c'est-à -dire que l'expression figurant à droite du signe := est d'abord évaluée et que sa valeur est ensuite enregistrée dans la variable dont le nom figure à gauche du :=.
Exemple de code Pascal avec des affectations▲
Voilà un premier exemple de code Pascal, principalement composé d'affectations :
Var
Age, MonAge : Integer
;
begin
Age := 20
;
MonAge := 47
;
Age := Age+1
;
Age := Age*2
;
MonAge := Age ;
end
;
Ce code commence par la déclaration de deux variables de type entier (Age et MonAge). Après la partie déclaration, on a cinq instructions d'affectation.
Voilà comment se déroule l'exécution de ce code :
Après l'exécution de l'instruction | Age vaut | MonAge vaut |
Age := 20 ; | 20 | ? |
MonAge := 47 ; | 20 | 47 |
Age := Age + 1 ; | 21 | 47 |
Age := 2 * Age ; | 42 | 47 |
MonAge := Age ; | 42 | 42 |
Remarque : la première valeur de la variable MonAge est indéterminée. Tant que l'on n'a pas affecté de valeur à une variable, on ne sait pas ce qu'elle contient.
Affectation récursive▲
Dans une affectation, l'expression affectée à la variable peut elle-même contenir la variable. On dit alors que l'affectation est récursive.
Exemple 1
Ajouter le prix d'un article au prix total, se traduit par l'affectation récursive suivante :
PrixTotal := PrixTotal + Prix ;
Exemple 2
Augmenter une variable de 1, se dit incrémenter. C'est un cas particulier d'affectation récursive très fréquemment utilisé en programmation.
Par exemple, pour incrémenter le nombre d'articles :
NombreArticle := NombreArticle + 1
;
Traitement itératif▲
Les affectations récursives sont en général utilisées dans des traitements itératifs, c'est-à -dire des traitements que l'on répète plusieurs fois. Chaque répétition d'un traitement itératif s'appelle une itération.
Illustrons ceci par un exemple.
Considérons un programme permettant de calculer la durée totale des morceaux d'un album. Voici son interface graphique :
Le bouton Ajouter ajoute le nouveau titre saisi dans la zone de liste et augmente la durée totale de l'album par la durée de ce morceau.
Nous avons ici un exemple de traitement itératif, puisque les mêmes opérations vont être effectuées chaque fois que l'utilisateur ajoute un album. Ou autrement dit, chaque fois qu'il clique sur le bouton Ajouter.
Voici, le code de la procédure évènementielle de ce bouton :
procedure
TForm1.BT_AjouterClick(Sender: TObject);
begin
//--- Lecture des données
Lire (Titre, ZT_Titre);
LireEntier (Duree, ZT_Duree);
//--- Traitement des données
DureeT := DureeT + Duree;
Numero := Numero + 1
;
//--- Affichage
AfficherEntier (Numero, ZL_Num);
Afficher (Titre, ZL_Titre);
AfficherEntier (Duree, ZL_Duree);
AfficherEntier (DureeT, ZT_DureeT);
end
;
Le traitement consiste à  :
- lire le titre du morceau et sa durée ;
- augmenter la durée totale (DureeT) par la durée du morceau ;
- incrémenter le numéro du morceau ;
- afficher le numéro, le titre et la durée du morceau dans les zones de liste prévues à cet effet ;
- afficher la durée totale.
Mais que vaut la durée totale au départ ? Quel est le numéro du premier morceau ?
Cela dépend de la valeur de ces variables avant la première itération, c'est-à -dire au démarrage du programme.
Il faudrait donc pouvoir affecter des valeurs de départ (on dit initialiser) aux variables DureeT et Numero. Sinon, on ne pourra pas connaître leur valeur initiale.
Mais avec ce que nous avons vu jusqu'à présent, nous n'avons aucun moyen de faire exécuter des instructions à l'ordinateur avant le premier clic sur un bouton.
Heureusement, il existe une procédure évènementielle qui est appelée dès le démarrage du programme. Elle s'appelle Form_Create. Pour l'ajouter au code, il suffit de double-cliquer sur le fond du formulaire.
C'est donc dans la procédure Form_Create, qu'il faut initialiser les variables.
Voici la procédure Form_Create de notre exemple :
procedure
TForm1.FormCreate(Sender: TObject);
begin
//--- Initialisation
DureeT := 0
;
Numero := 0
;
end
;
Comme cette procédure est déclenchée dès le démarrage du programme, nous saurons que la durée totale et le numéro seront nuls avant la première itération.
Exemple et conseils▲
L'objectif de la programmation n'est pas seulement de faire des programmes qui marchent.
En réalité, un programme n'est jamais terminé (du moins un programme volumineux tel que vous en rencontrerez en entreprise). Il faut donc pouvoir le modifier facilement, sachant que le développeur qui sera chargé de la mise à jour de votre programme ne sera pas forcément vous-même. Cela a deux conséquences :
- Votre code doit être lisible, c'est-à -dire qu'en le lisant on devine facilement ce qu'il fait. ;
- D'autre part, il doit être facilement modifiable.
Dans cette partie du cours, je vous donne à travers un exemple quelques conseils permettant de rendre un programme plus lisible et plus facile à modifier.
Mises à part la lisibilité et la facilité de modification, il existe d'autres critères importants en programmation que nous n'aborderons pas ici :
- la rapidité d'exécution du code. Cet aspect est le sujet essentiel de l'algorithmique, qui peut être vue comme la science de résoudre un problème par un programme de manière à ce que ce programme s'exécute le plus rapidement possible. Sachez simplement que la manière de programmer peut jouer énormément sur la vitesse d'exécution ;
- la concision du code, c'est-à -dire la résolution d'un problème avec un programme le plus court possible. Certains programmeurs sont capables d'écrire en deux lignes ce que d'autres écrivent en vingt lignes. Cet aspect est à mon avis moins important que les autres dans la mesure où un code concis est souvent peu lisible.
Exemple de programme et conseil▲
Cahier des charges▲
La réalisation d'un projet informatique démarre en général avec un cahier des charges. C'est en gros une description de ce que devrait faire le logiciel.
Voici le cahier des charges de notre exemple :
On souhaiterait écrire un programme qui calcule la surface à peindre dans une pièce rectangulaire possédant des portes et des fenêtres.
Pour simplifier, on supposera que les murs ont 2m50 de hauteur et que toutes les fenêtres ont la même dimension (1.4 × 1.2) et de même pour les portes (0.9 × 2.1).
Déclaration des constantes▲
Introduisons une constante pour chaque donnée fixe du problème :
const
LargeurFenetre = 1
.4
;
HauteurFenetre = 1
.2
;
LargeurPorte = 0
.9
;
HauteurPorte = 2
.1
;
HauteurMur = 2
.5
;
On pourrait se passer de ces déclarations et mettre directement les valeurs 1.4, 1.2, etc. dans les calculs, mais on obtiendrait ainsi un code moins lisible et plus difficile à mettre à jour.
S'il faut corriger la hauteur des portes par exemple, il suffit de changer la valeur de la constante et cette modification sera automatiquement répercutée dans tout le code (dans tous les calculs où la hauteur des portes intervient).
Essayez de procéder de cette manière dès que vous avez des valeurs fixées. Déclarez des constantes au lieu d'écrire les valeurs dans les instructions.
Conception de l'interface graphique▲
La surface à peindre dépendra donc de la taille de la pièce ainsi que du nombre de portes et de fenêtres. Ce seront les données du programme. On en déduit l'interface graphique suivante :
Les zones de texte sont nommées ZT_Largeur (largeur de la pièce), ZT_Longueur (longueur de la pièce), ZT_NF (nombre de fenêtres), ZT_NP (nombre de portes) et ZT_SP (surface à peindre).
Les noms des zones de texte sont tous préfixés par ZT_. Cette manière de procéder permet d'éviter des conflits avec des noms de variables. En programmation, un même nom ne peut désigner qu'une seule chose. Vous ne pouvez pas, par exemple, nommer une zone de texte Largeur et utiliser par ailleurs une variable de même nom. Je vous propose de préfixer les noms des zones de texte par ZT_, ceux des zones de liste par ZL_ et ceux des boutons par BT_. Vous pouvez bien sûr avoir vos propres conventions. L'essentiel est d'en avoir une.
Lecture et déclaration des données▲
Associons une variable à chaque donnée : LargeurPiece, LongueurPiece, NFenetre et NPorte. D'où les déclarations :
Var
LargeurPiece, LongueurPiece : Double
;
NFenetre, NPorte : Integer
;
Je n'ai pas besoin de vous expliquer ici la signification de chaque variable, car les noms choisis permettent de deviner la fonction de chacune d'entre elles. Pour obtenir un code lisible, il est recommandé de procéder de cette manière : utilisez des noms de variables parlants. Cette remarque est évidemment également valable pour les noms de constantes et pour, de manière générale, n'importe quel nom utilisé dans un programme.
Nous pouvons à présent écrire les instructions de lecture de données dans la procédure évènementielle du bouton Calculer :
procedure
TForm1.BT_CalculClick(Sender: TObject);
begin
// Lecture des données
LireNombre (LargeurPiece, ZT_Largeur);
LireNombre (LongueurPiece, ZT_Longueur);
LireEntier (NFenetre, ZT_NF);
LireEntier (NPorte, ZT_NP);
end
;
Traitement, résultats intermédiaires et finals▲
Il nous faut maintenant réfléchir à la manière de calculer la surface à peindre. Pour simplifier le problème, nous allons le découper le traitement en plusieurs étapes :
- Calcul de la surface des murs ;
- Calcul de la surface des fenêtres ;
- Calcul de la surface des portes ;
- Calcul de la surface à peindre.
Le résultat de chaque étape sera sauvegardé dans une variable : SurfaceDesMurs, SurfaceDesFenetres, SurfaceDesPortes, SurfacePeinture.
Essayez également de procéder de cette manière lorsque vous avez un calcul compliqué. Découpez le calcul en plusieurs parties et sauvegardez le résultat de chacun de ces calculs dans une variable. À la fin, écrivez le calcul du résultat final en utilisant les résultats des calculs intermédiaires. Cette manière de programmer simplifie la résolution du problème et rend le programme plus lisible.
Il nous faut d'abord déclarer les nouvelles variables :
Var
LargeurPiece, LongueurPiece : Double
;
NFenetre, NPorte : Integer
;
SurfaceDesMurs, SurfaceDesPortes, SurfaceDesFenetres,
SurfacePeinture : double
;
Vous pouvez également procéder de cette manière. Il est en général difficile de prévoir dès le début toutes les variables que l'on aura besoin d'utiliser dans un programme. Ajoutez vos déclarations de variable au fur et à mesure des besoins.
Écrivons à présent les quatre étapes de calcul.
Calcul de la surface des quatre murs :
SurfaceDesMurs :=
2
* (LargeurPiece+LongueurPiece) * HauteurMur;
Calcul de la surface des fenêtres :
SurfaceDesFenetres :=
NFenetre * LargeurFenetre * HauteurFenetre;
Calcul de la surface des portes :
SurfaceDesPortes :=
NPorte * LargeurPorte * HauteurPorte;
Calcul de la surface à peindre :
SurfacePeinture :=
SurfaceDesMurs - SurfaceDesFenetres - SurfaceDesPortes;
Le code de la procédure en entier▲
Pour conclure, voici le code de la procédure évènementielle du bouton Calculer :
procedure
TForm1.BT_CalculClick(Sender: TObject);
begin
//--- Lecture des données
LireNombre (LargeurPiece, ZT_Largeur);
LireNombre (LongueurPiece, ZT_Longueur);
LireEntier (NFenetre, ZT_NF);
LireEntier (NPorte, ZT_NP);
//--- Traitement des données
SurfaceDesMurs :=
2
* (LargeurPiece+LongueurPiece) * HauteurMur;
SurfaceDesFenetres :=
NFenetre * LargeurFenetre * HauteurFenetre;
SurfaceDesPortes :=
NPorte * LargeurPorte * HauteurPorte;
SurfacePeinture :=
SurfaceDesMurs - SurfaceDesFenetres - SurfaceDesPortes;
// Affichage des résultats
AfficherNombre (SurfacePeinture,ZT_SP);
end
;
Le code de cette procédure est bien séparé en trois parties : lecture des données, traitement des données, puis affichage des résultats. Essayez également de procéder de cette manière. Écrivez votre code en séparant bien la lecture, le traitement et l'affichage.
Synthèse▲
Voici un résumé de tous les conseils vus précédemment.
- Lorsque vous avez des valeurs fixées, utilisez des constantes.
- Préfixez les noms des composants de l'interface graphique (suggestion : ZT_ = zone de texte, ZL_ = zone de liste, BT_ = bouton).
- Utilisez des noms parlants.
- Lorsque vous avez un calcul (ou plus généralement un traitement) compliqué, découpez-le en plusieurs parties et sauvegardez les résultats de chaque partie dans des variables.
- Séparez bien la lecture des données, le traitement des données et l'affichage des résultats.
Exercices▲
Retrouvez différents exercices sur le site de l'auteur :
- recherches complémentaires sur internet ;
- prise en main de Lazarus ;
- programme sans interface graphique ;
- exercices de programmation ;
- exercices complémentaires ;
- exercices théoriques.
Pour obtenir les corrigés, le téléchargement n'est possible que via un login et un mot de passe, que vous pouvez obtenir en envoyant un mail à l'adresse suivante :
en précisant un peu qui vous êtes et les raisons pour lesquelles ce cours vous intéresse.