7.2 Structures des langages de programmation
Objectifs d'apprentissage
À la fin de cette section, vous serez capable de :
- Discuter et comparer les types de données des HLL
- Démontrer l'utilisation des variables
- Examiner les expressions et instructions des HLL
- Décrire l'implémentation du flux de contrôle dans les HLL
- Introduire le concept de fonctions
- Classer les programmes bien structurés
- Expliquer le concept de gestion des exceptions
- Récapituler les fichiers et les entrées-sorties
Les HLL existent pour communiquer à un ordinateur les étapes logiques pour aborder une tâche ou une application donnée, et de nombreux HLL fonctionnent de la même manière. Pour cette raison, une fois que vous maîtrisez un HLL moderne, il devient plus facile d'apprendre des langages supplémentaires puisque vous connaissez maintenant les bonnes questions à poser. Par exemple, un point de départ pourrait être de découvrir comment obtenir une sortie de programme simple qui vous permet de voir comment exécuter un programme et tester les concepts que nous sommes sur le point d'apprendre.
Dans cette section, nous décrirons les concepts structurels des HLL pour nous donner les outils avec lesquels les comparer et les apprendre de manière cohérente. Un bon point de départ pour examiner les constructions des langages de programmation est de démontrer les blocs de construction fondamentaux des HLL. Ceux-ci incluent les types de données (data types) que les langages peuvent manipuler légalement, la manière dont ils stockent ces données, la manière dont ils structurent les expressions et les instructions par lesquelles ils communiquent, et le contrôle du flux de programmation.
Types de données des HLL
Les types de données (data types) d'un langage forment l'ensemble légal des types de données qu'un HLL peut manipuler. Ces types de données peuvent être très simples, ou ils peuvent être plus complexes. Le type de données le plus simple d'un langage est un type de données primitif (primitive data type) (également, type de données de base), par exemple, les entiers et char dans le langage de programmation C. Les données correspondant aux variables (variable) de ces types peuvent généralement être représentées et manipulées directement en utilisant le matériel machine, à la fois en mémoire et via des registres.
Cependant, les langages contiennent généralement aussi des types de données complexes. Un type de données complexe (complex data type) se compose de plusieurs types primitifs qui sont utilisés comme blocs de construction. Un exemple de ceci est le type de données chaîne de caractères (string) qui représente une séquence de caractères. Dans le langage de programmation C, les chaînes de caractères sont des types de données complexes représentés à l'aide de tableaux de caractères. En JavaScript, string est un type de données primitif. La Figure 7.9 présente les différents types de données.
En général, les types de données sont des collections de valeurs d'un domaine donné : le type de données numérique JavaScript couvre le domaine des valeurs de nombres à virgule flottante qui peuvent être représentées sur 64 bits. Il se compose également d'un ensemble légal d'opérations bien définies qui peuvent être effectuées sur les valeurs du domaine couvert par le type de données numérique. Les opérations sur ces nombres en JavaScript sont définies par les opérateurs arithmétiques du langage.
Certains langages séparent les nombres en types entiers (integer) (nombres entiers) et en types à virgule flottante (floating point) (nombres décimaux). Même parmi des langages similaires tels que C++ et Java, il peut y avoir des nombres différents de types de données primitifs.
Types de données primitifs
Ces types de données sont considérés comme primitifs car ils sont très étroitement liés au matériel machine. Cela signifie que le format, ou motif binaire, des valeurs réelles peut être reconnu par les registres et l'unité arithmétique et logique (UAL) de l'ordinateur. Quelques exemples de types de données primitifs sont le nombre, le caractère et le booléen (Boolean) (qui contient les valeurs true ou false), comme visible dans le Tableau 7.2.
| Java | C++ | Taille | Plage de valeurs |
|---|---|---|---|
| short int | short int | 2 octets | –32,768 to +32,767 |
| unsigned short int | 2 octets | 0 to +65,535 | |
| int | int | 4 octets | –2,147,483,648 to +2,147,483,647 |
| unsigned int | 4 octets | 0 to +4,294,967,295 | |
| long int | long int | 4 octets | –2,147,483,648 to +2,147,483,647 |
| unsigned long int | 4 octets | 0 to +4,294,967,295 | |
| long long int | unsigned long long int | 8 octets | –9,223,372,036,854,775,808 to 9,223,372,036,854,775,808 |
| unsigned long long int | 8 octets | 0 to 18,446,744,073,709,551,615 |
Nous avons appris que le typage fort fait référence à la caractéristique d'un HLL dans lequel une variable est restreinte à contenir des valeurs du type avec lequel elle est définie. Le concept de coercition (coercion) fait référence à la capacité d'une variable d'un type de données à être forcée de contenir une valeur d'un type de données différent. En d'autres termes, les règles de coercition sont un assouplissement de la vérification de type. Par exemple, un type de données Java int contient un nombre entier de quatre octets, tandis qu'un short int contient un nombre entier de deux octets. Nous pouvons légalement affecter la valeur du short int à l'int : elle est coercée par l'affectation, ce qui a un sens absolu car la valeur courte peut s'insérer dans la valeur plus longue.
D'autre part, nous ne pouvons pas affecter un type de données Java long de 8 octets à un int Java de 4 octets ; il est trop grand pour tenir, et si nous essayons, nous obtiendrons une erreur de compilation qui ne permettra pas au programme de s'exécuter. Cependant, nous pouvons forcer l'affectation en utilisant un mécanisme appelé transtypage (type cast). C'est un mécanisme dans de nombreux HLL qui nous permet de forcer la valeur plus grande dans l'espace plus petit qui nous est donné par la variable plus petite. Cela peut avoir des effets secondaires, qui doivent être connus du programmeur pour utiliser le mécanisme efficacement. L'effet secondaire de l'exemple Java long vers int est la troncation : quatre des octets sont supprimés.
Types de données complexes
Nous avons appris que certains des types de données d'un langage sont des types primitifs, ce qui signifie que les données de ce type peuvent être directement représentées dans les registres et les emplacements mémoire de la machine. Cependant, les langages contiennent généralement aussi des types de données complexes.
Un type de données complexe est un type composé de plusieurs types primitifs utilisés comme blocs de construction, c'est pourquoi nous les appelons également types composites. Ces multiples types peuvent être du même type, comme dans un type de données complexe connu sous le nom de tableau, ou ils peuvent consister en des collections de types de données différents dans une seule construction, comme une classe C#.
Tableaux
Un tableau est un type composite typique qui est utilisé comme conteneur de données. Une excellente façon de visualiser un tableau est comme une unité d'étagères, une structure connectée où nous pouvons placer des éléments sur chaque emplacement. Une instance d'un tableau dans ce cas pourrait être une bibliothèque destinée à contenir des livres (un livre serait un autre type composite).
Un tableau (array) est une variable nommée qui référence un bloc d'emplacements mémoire contigus, et chaque « étagère » du tableau est un élément, qui occupe exactement autant d'octets contigus qu'il en faut pour accueillir une valeur du type de données stocké. Dans le type de structure de tableau le plus simple, un tableau indexé (indexed array), les étagères sont numérotées avec un index, commençant à zéro, ou l'emplacement mémoire le plus bas. Dans un langage fortement typé (strongly typed), tous les éléments d'un tableau doivent être du même type de données, ce qui signifie que chaque élément aura une longueur uniforme en octets.
La Figure 7.10 illustre un tableau dans n'importe quel nombre de HLL, y compris Java, C et C#. Nous commençons par la déclaration du tableau, qui donne le type de données de chacun de ses éléments, nomme la variable de tableau numbers, indique qu'il s'agit d'un tableau avec les crochets ouvrants et fermants ([]), et lui affecte cinq valeurs avec ce que l'on appelle un initialiseur de tableau (array initializer) (valeurs séparées par des virgules placées entre accolades). Nous pouvons voir que la longueur du tableau est de 5, les index vont de 0 à 4, et chacun des éléments est contigu en mémoire et a une longueur de 4 octets. L'instruction suivante affecte un élément du tableau à une variable :
int myNumber = numbers[5];
Chaînes de caractères
Les chaînes de caractères, qui sont un autre type de données composite, sont des tableaux de caractères dans la plupart des HLL. Dans les langages orientés objet, elles sont un peu plus complexes. Elles sont implémentées dans certains HLL comme un tableau qui contient des caractères individuels comme éléments. Les langages POO implémentent généralement les chaînes de caractères comme des objets avec des fonctions (méthodes) intégrées. Voici un exemple de chaîne de caractères en Java :
String myUniversity = "Union Technical College";
Types de référence et pointeurs
Dans notre étude des variables et des types de données (data types) jusqu'à présent, nous avons examiné le concept d'une variable étant une paire nom-valeur. Avec les types primitifs, l'emplacement mémoire référencé par le nom stocke la valeur réelle de la variable directement à cet endroit.
Dans le cas des types de données complexes, les choses ne sont pas si simples. Prenons l'exemple d'une chaîne de caractères. Nous pouvons stocker un nom dans la chaîne, tel que « Jimmy », pour commencer. Supposons maintenant que nous changions la valeur en « Johnathan » à un moment ultérieur. La mémoire requise pour stocker la valeur a maintenant changé et il est possible qu'elle ne tienne plus dans l'emplacement d'origine. Nous avons appris que le langage C a un type de données primitif connu sous le nom de pointeur (pointer). Il est utilisé pour cette raison.
Les pointeurs sont des variables qui contiennent des adresses mémoire réelles d'ordinateur (références). Ils correspondent à la taille de mot de la machine, qui est généralement de 64 bits. Nous appelons une variable qui contient des adresses mémoire une variable de référence (reference variable). Il n'y a pas de tel concept de pointeur primitif explicite en Java ou C#. Par conséquent, lorsque nous créons un tableau ou une chaîne dans ces langages, la valeur réellement stockée dans la variable est l'adresse mémoire de l'emplacement où l'objet complexe existe. Ainsi, dans le cas de notre exemple de chaîne, si nous changeons le nom, nous pouvons simplement changer la valeur de sa variable pour qu'elle soit une adresse mémoire différente — voir la Figure 7.3.
Variables
Nous avons appris dans 7.1 Fondements des langages de programmation qu'une variable est un conteneur utilisé dans un HLL pour contenir une valeur. En informatique, nous avons de nombreuses instances de ce type de construction, que nous appelons une paire nom-valeur (name-value pair), une construction de type variable qui est nommée et peut contenir des valeurs. Les types de valeurs qu'elles peuvent contenir sont les types de données légaux du langage.
Identifiants
Un nom de variable est appelé un identifiant (identifier). Différents HLL ont des règles différentes concernant la syntaxe légale des identifiants. Par exemple, en C#, les règles sont les suivantes :
- Un identifiant ne peut pas être un mot-clé (keyword), qui est un mot réservé par le langage et qui a une signification spéciale.
- Une lettre, un symbole @ ou un trait de soulignement doit commencer un identifiant, tandis que la partie restante peut être composée de chiffres, du symbole de trait de soulignement et/ou de lettres (différemment de cela, un identifiant en PHP commence par un signe dollar ($)).
- Les identifiants sont sensibles à la casse (case-sensitive), où les lettres majuscules et minuscules sont traitées comme distinctes. Par conséquent, l'identifiant C# myAge est une entité différente de myAGE (Fortran, BASIC et Pascal ne sont pas sensibles à la casse).
Enjeux mondiaux en technologie
Apprendre la programmation : une barrière linguistique pour les non-anglophones apprenant les HLL ?
Avez-vous déjà eu du mal à comprendre quelque chose parce que c'était expliqué dans une langue que vous ne parlez pas ? C'est le défi auquel de nombreux non-anglophones sont confrontés lorsqu'ils apprennent les HLL. La plupart des HLL utilisent des mots-clés anglais qui ont du sens pour le compilateur (compiler) mais pas nécessairement pour quelqu'un qui n'est pas familier avec la langue. Puisque la programmation est devenue une entreprise mondiale, les mots-clés anglais peuvent être une pierre d'achoppement pour les non-anglophones apprenant les HLL. Heureusement, il y a un bon côté ! Bien que les mots-clés soient en anglais, ils constituent un ensemble relativement petit de mots dans un programme. La véritable puissance de la programmation réside dans sa capacité à travailler avec des données et des instructions dans n'importe quelle langue, ce qui est rendu possible grâce à Unicode. Unicode peut représenter la plupart des jeux de caractères internationaux, permettant aux programmeurs d'utiliser des caractères de presque toutes les langues aux côtés des mots-clés anglais.
Mais qu'en est-il de l'avenir ? À mesure que la technologie évolue, les langages de programmation trouveront-ils des moyens de devenir encore plus indépendants des langues naturelles ? Peut-être que les futurs HLL offriront des mots-clés interchangeables ou des approches entièrement nouvelles qui ne dépendront d'aucune langue donnée.
Déclarations de variables
Une variable doit être portée à la connaissance d'un compilateur ou d'un interpréteur avant de pouvoir être utilisée par un programme informatique. Ce processus (process) est la déclaration et/ou la définition de variable. Dans les langages fortement typés, une déclaration de variable (variable declaration) consiste en une instruction qui spécifie le nom de la variable et son type de données. Les langages faiblement typés omettent le type de données lorsque des valeurs sont affectées à la variable, qui peuvent être de types différents à des moments différents.
Les définitions de variables dans différents langages sont les suivantes :
Java: int myAge;
JavaScript: var myAge;
PHP: $myAge = 21;
L'affectation de sa première valeur à une variable est connue sous le nom d'initialisation (initialization), qui peut être effectuée à tout moment après la déclaration, comme dans l'extrait Java suivant :
int myAge;
myAge = 21;
C'est une bonne pratique (best practice) de toujours initialiser les variables lorsqu'elles sont déclarées. C'est ce qu'on appelle la déclaration et l'initialisation. Cela empêche la valeur stockée d'être indéfinie à tout moment, ce qui peut avoir de graves conséquences dans le code dans diverses situations. Par exemple, dans le langage de programmation C, ne pas initialiser un pointeur vers un tableau de caractères dans un programme et copier une chaîne de caractères à l'emplacement mémoire (non initialisé) référencé par ce pointeur plus tard dans le programme fera planter le programme. Voici un autre exemple en Java :
int myAge = 21;
Affectation
Un littéral (literal) est une valeur de l'un des types de données légaux d'un HLL qui peut être écrite directement dans le code. Par exemple, en JavaScript, l'un des types de données est numérique, qui peut être représenté soit par le nombre entier littéral 2, soit par le nombre à virgule flottante 2.0. En C++, un littéral de type char peut être écrit comme la séquence entre guillemets simples a.
Le stockage d'une valeur dans une variable (variable) est effectué en créant une instruction d'affectation (assignment statement) : la valeur affectée peut être un littéral, ou elle peut être la valeur qui a été placée dans une autre variable ou le résultat d'une expression. La valeur d'une variable peut également être remplacée par affectation. Par conséquent, les variables peuvent contenir des valeurs différentes à des moments différents.
Dans une expression PHP qui constitue une affectation comme indiqué, la variable est située à gauche. Notez que l'identifiant commence par le signe dollar ($), conformément aux règles d'identifiant de PHP. Le signe égal (=) est connu comme l'opérateur d'affectation, comme dans la plupart des langages avec une syntaxe de type C (C/C++, Java, C#, Python, JavaScript, PHP).
$myAge = 21;
Dans les langages de programmation, nous appelons la partie gauche d'une instruction d'affectation de variable (la variable) la lvaleur (lvalue). La valeur de droite (le littéral) est appelée la rvaleur (rvalue). L'opérateur d'affectation est un opérateur binaire (binary operator), ce qui signifie qu'il est entouré de deux opérandes. L'opérande est la lvaleur ou la rvaleur de chaque côté de l'opérateur. La rvaleur d'une instruction d'affectation de variable peut être la valeur d'une autre variable comme indiqué ici ou le résultat d'une expression. Un exemple de ceci en Java est le suivant :
myAge = yourAge;
Examinons le concept un peu plus en profondeur. Les variables peuvent être des emplacements mémoire nommés. Nous donnons un nom à une variable pour qu'il soit facile pour les humains de la gérer. C'est une bonne pratique d'utiliser des noms qui sont indicatifs à la fois du but de la variable (à quoi elle servira) et du type de données qu'elle contiendra. Ainsi, la variable myAge dans l'exemple précédent répond aux deux caractéristiques. Une bonne pratique des HLL est d'utiliser une convention convenue pour les noms de variables. Un exemple est camelCase, une convention de nommage qui élimine les espaces et la ponctuation en faveur de la capitalisation de mots spécifiques ; dans ce cas, la première lettre du premier mot est en minuscules et si le nom a plusieurs mots, les mots suivants commencent par une majuscule (par exemple, firstName et lastName). D'autres conventions existent telles que snake case (par exemple, first_name, last_name), kebab case (par exemple, first-name, last-name) et Pascal case (par exemple, FirstName, LastName).
Lorsqu'un programme est compilé, le compilateur alloue un emplacement mémoire pour contenir les valeurs des variables et réserve la quantité de mémoire nécessaire pour contenir cette valeur en fonction du type de données de la variable. Les adresses des emplacements mémoire que le compilateur affecte aux variables sont relocalisables, ce qui signifie que l'éditeur de liens (linker) peut modifier ces adresses lors de la création de la version exécutable du programme, et le chargeur de programme les modifiera également lors de l'exécution du programme pour correspondre aux adresses mémoire réelles dans la mémoire machine. La Figure 7.11 illustre à quoi cela ressemble.
Constantes nommées
Une variable peut contenir des valeurs différentes, dans les limites des restrictions de type de données du langage, à divers moments de l'exécution du programme. Il y a des moments où nous souhaiterions qu'une valeur soit affectée à un emplacement mémoire spécifique et ne pas permettre qu'elle soit modifiée par la suite. La plupart des HLL modernes et des DLL (bibliothèques de liens dynamiques) associées fournissent une construction à cet effet appelée constante nommée (named constant). C'est également une bonne pratique dans la plupart des cultures de langage d'utiliser des majuscules avec des traits de soulignement séparant plusieurs mots. L'instruction suivante en C++ crée une constante nommée :
const int PAY_RATE = 15;
Le mot const est un mot-clé, int est un type de données entier C++ suivi du nom de la constante, égal (=) est l'opérateur d'affectation suivi de la rvaleur, et l'instruction se termine par un terminateur. C'est une bonne pratique en programmation d'utiliser des constantes nommées chaque fois que cela est indiqué et possible. Dans cet exemple, si le taux de rémunération d'un employé devait changer, il n'aurait à être modifié qu'à un seul endroit dans le programme plutôt que d'avoir à localiser ses utilisations dans de nombreuses lignes de code.
Opérateurs
Tout comme en algèbre, un opérateur (operator) dans les HLL effectue divers types d'opérations (processus) sur des valeurs. Différents HLL peuvent prendre en charge les opérateurs différemment. Quelques exemples des types d'opérations par lesquelles les valeurs peuvent être manipulées sont les opérateurs arithmétiques (mathématiques), les opérateurs relationnels (comparaison), les opérateurs logiques et les opérateurs de chaînes (séquences de caractères). Ils effectuent ces opérations au sein d'expressions du langage. Pour rappel, une expression (expression) est une construction dans un langage de programmation qui évalue à une valeur. Les opérateurs peuvent agir sur différents nombres d'opérandes, généralement un (un opérateur unaire (unary operator)), deux (un opérateur binaire) ou trois (un opérateur ternaire (ternary operator)). Les opérateurs fonctionnent selon certaines règles qui dictent l'ordre des opérations, ou précédence. Bien que de nombreux HLL utilisent les mêmes opérateurs, lors de l'apprentissage d'un nouveau langage, il est nécessaire de rechercher ses opérateurs pour identifier les très rares exceptions.
Opérateurs arithmétiques
Les opérateurs arithmétiques effectuent les quatre opérations mathématiques familières sur leurs opérandes : addition (+), soustraction (-), multiplication (*) et division (/). Il existe une autre opération moins familière connue sous le nom d'opérateur modulo (%) (modulo operator (%)), qui évalue au reste après la division. Certains langages emploient également un opérateur d'exponentiation (**) (exponentiation operator (**)), l'opérateur qui élève la valeur d'un opérande à la puissance du second opérande. L'opérateur d'incrémentation (++) (increment operator (++)) et l'opérateur de décrémentation (--) (decrement operator (--)) augmentent ou diminuent la valeur d'une variable de un, respectivement, et sont utilisés dans la grande majorité des HLL. Ces opérateurs arithmétiques Java sont décrits dans le Tableau 7.3.
| Opérateur | Nom | Exemple d'expression | Signification |
|---|---|---|---|
| * | Multiplication | a * b | a multiplié par b |
| / | Division | a / b | a divisé par b |
| % | Reste (modulo) | a % b | reste après avoir divisé a par b |
| + | Addition | a + b | a plus b |
| - | Soustraction | a - b | a moins b |
Opérateurs relationnels
Les opérateurs relationnels comparent leurs opérandes, et les expressions les utilisant évaluent aux valeurs booléennes true ou false. Le Tableau 7.4 liste les symboles d'opérateurs relationnels qui sont utilisés dans la grande majorité des HLL.
| Opérateur | Nom | Exemple d'expression | Résultat |
|---|---|---|---|
| < | Inférieur à | 1 < 2 | Vrai |
| > | Supérieur à | 1 > 2 | Faux |
| <= | Inférieur ou égal à | 1 <= 2 | Vrai |
| >= | Supérieur ou égal à | 1 >= 2 | Faux |
| == | Égal à | 1 == 2 | Faux |
| != | Différent de | 1 != 2 | Vrai |
Opérateurs logiques
Les opérateurs logiques sont utilisés pour connecter deux expressions ou plus. Ils évaluent l'expression entière aux valeurs booléennes true ou false. Le Tableau 7.5 liste les symboles d'opérateurs logiques qui sont utilisés dans la grande majorité des HLL.
| Opérateur | Nom | Exemple d'expression | Résultat |
|---|---|---|---|
| && | ET logique | (1 < 2) && (2 > 1) | Vrai |
| || | OU logique | (1 < 2) || (2 > 1) | Vrai |
| ! | NON logique | !(1 < 2) | Faux |
Les expressions utilisant des opérateurs logiques sont évaluées sur la base d'une table de vérité (truth table), un tableau qui montre quelle serait la valeur résultante étant donné chaque combinaison d'opérandes. Le Tableau 7.6 montre les tables de vérité pour les trois opérations logiques que nous avons étudiées.
| A | B | A && B | A || B | !A |
|---|---|---|---|---|
| Vrai | Vrai | Vrai | Vrai | Faux |
| Vrai | Faux | Faux | Vrai | Faux |
| Faux | Vrai | Faux | Vrai | Vrai |
| Faux | Faux | Faux | Faux | Vrai |
Affectation combinée
L'opérateur d'affectation peut être combiné avec d'autres opérateurs, généralement mathématiques, comme notation raccourcie. C'est ce qu'on appelle l'affectation combinée (combined assignment). Dans cette construction, l'opération est effectuée en premier, suivie de l'affectation. Le Tableau 7.7 montre les expressions d'affectation combinée les plus courantes.
| Opérateur | Exemple | Équivalent à |
|---|---|---|
| += | x += 7 | x = x + 7 |
| -= | y -= 4 | y = y - 4 |
| *= | z *= 2 | z = z * 2 |
| False | a /= b | a = a / b |
| False | c %= 9 | c = c % 9 |
Documentation et commentaires
Tous les langages de programmation permettent à un programmeur de documenter son code pour améliorer la lisibilité du code. C'est une bonne pratique (best practice) en programmation et un candidat à un emploi potentiel pourrait ne pas obtenir un poste sans démontrer des compétences en documentation.
La documentation est réalisée en utilisant une structure appelée commentaire (comment), un conteneur utilisé pour contenir la documentation dans le code. Un commentaire commence par un symbole ou un ensemble de symboles définis du langage, suivi du texte du commentaire lui-même. Les commentaires peuvent être sur une seule ligne ou sur plusieurs lignes selon le symbole utilisé. Les symboles de commentaire sur une seule ligne indiquent que tout ce qui suit sur la ligne est ignoré par le compilateur (compiler) ou l'interpréteur. Les commentaires multilignes ont un symbole ou un ensemble de symboles d'ouverture et de fermeture. Les exemples sont les suivants :
// commentaire sur une seule ligne en C#
int myAge = 21;
int myAge = 21; // un autre commentaire sur une seule ligne en C#
/* ceci est un commentaire multiligne en JavaScript
myAge sera utilisé pour contenir l'âge d'un étudiant
*/
int myAge = 21;
D'autres langages peuvent utiliser une syntaxe différente pour les commentaires ; en Python, le symbole est le signe dièse (#). En BASIC, c'est le mot-clé REM, qui est l'abréviation de « remark ».
Expressions et instructions des HLL
Les commandes dans les HLL sont structurées comme des instructions, et les instructions sont composées d'expressions. Nous avons appris qu'une expression dans un langage de programmation évalue à une valeur. Examinez les instructions suivantes dans un langage de type C :
int a = b + c * d;
S'il est donné que b = 4, c = 6 et d = 2, un simple balayage de l'instruction ferait en sorte que la première instruction d'addition évalue à 10, ce qui serait utilisé pour évaluer l'instruction résultante de 10 * 2, donnant une valeur de 20. Cependant, ce calcul est incorrect car l'évaluation des expressions au sein des instructions suit l'ordre de précédence (precedence), les règles qui déterminent l'ordre dans lequel les opérateurs des instructions sont évalués. Par conséquent, 6 * 2 sera évalué en premier, donnant la valeur 12, puis 4 + 12 sera évalué, donnant un résultat final de 16.
Les parenthèses peuvent être utilisées pour modifier l'ordre de précédence dans les instructions. Les expressions entre parenthèses sont toujours évaluées en premier. Si nous devions réécrire l'exemple :
int a = (b + c) * d;
nous obtiendrions le résultat de 20. Le Tableau 7.8 montre les résultats de diverses expressions en suivant les règles de précédence et en leur appliquant des parenthèses.
| Expression | Valeur | Expression parenthésée | Valeur |
|---|---|---|---|
| 5 + 2 * 4 | 13 | (5 + 2) * 4 | 28 |
| 10 / 5 - 3 | -1 | 10 / (5 - 3) | 5 |
| 8 + 12 * 2 - 4 | 28 | 8 + 12 * (2 - 4) | -16 |
| 4 + 17 % 2 - 1 | 4 | (4 + 17) % 2 - 1 | 0 |
| 6 - 3 * 2 + 7 - 1 | 6 | (6 - 3) * (2 + 7) - 1 | 26 |
Certaines expressions des HLL qui utilisent les opérateurs logiques (Java, C++, C#, JavaScript) n'ont pas besoin d'être complètement évaluées pour que leurs résultats soient connus, un concept appelé court-circuitage (short circuiting). En se référant aux tables de vérité des opérateurs logiques && et ||, nous pouvons voir que dans une opération &&, la seule façon d'obtenir un résultat vrai est si les deux opérandes sont vrais. Par conséquent, si le premier opérande est faux, l'expression n'a pas besoin d'être évaluée davantage — elle est fausse. C'est le court-circuitage. Voici un exemple en JavaScript :
if (x == y || today == "Tuesday") {
// do_something
}
Si l'opérande du côté gauche de l'opérateur logique || évalue à true, alors l'expression est vraie, et l'expression du côté droit de l'opérateur || n'a pas besoin d'être évaluée. Le court-circuitage augmente l'efficacité et les performances.
Flux de contrôle
Comme nous l'avons appris dans Introduction aux structures de données et aux algorithmes, nous devons définir les étapes à suivre pour résoudre un problème ou accomplir une tâche. L'ordre dans lequel, ou si, les instructions d'un programme sont exécutées est appelé flux de contrôle (flow of control). Par défaut, les instructions du programme s'exécutent dans un ordre séquentiel à partir d'un point de départ établi ; cependant, le flux de contrôle peut être modifié. Cela est nécessaire afin d'avoir la capacité de modéliser les situations du monde réel que nos algorithmes représentent.
Exécution séquentielle
L'exécution des instructions dans l'ordre où elles apparaissent, l'exécution séquentielle (sequential execution), est un ordonnancement linéaire des instructions dans lequel une instruction suit directement une autre. C'est le flux de contrôle par défaut ; il est automatique. Un exemple d'exécution séquentielle serait le suivant :
int myAge = 35;
String myName = "Johnathan";
boolean isStudent = true;
Ces instructions s'exécuteront une par une dans l'ordre dans lequel elles sont écrites.
Un bloc de code (code block) est une instruction qui consiste en une ou plusieurs instructions structurées en un groupe séquentiel et délimitées comme telles. Cela est nécessaire pour qu'un groupe entier de commandes puisse être exécuté comme une seule structure séquentielle. Dans les langages de type C, les accolades ouvrantes et fermantes ({ }) sont généralement utilisées pour dénoter le début et la fin d'un bloc de code. Nous pouvons modifier l'exemple précédent en un bloc de code comme suit en Java :
{
int myAge = 18;
String myName = "Johnathan";
boolean isStudent = true;
}
Notez que les variables définies dans les blocs de code n'existent que dans le contexte de ce bloc de code, ce qui permet de gérer la portée des variables dans le code plus précisément. C'est une bonne pratique en programmation d'indenter le code contenu dans les structures afin que le code soit à la fois lisible et maintenable.
Sélection
La prise de décision en programmation est appelée sélection (selection). Une construction de prise de décision nous permet de choisir parmi un ou plusieurs chemins d'exécution dans un programme. Pour voir comment elles fonctionnent, il faut comprendre le concept d'expressions conditionnelles.
Expressions conditionnelles
Parfois appelée expression booléenne, une expression conditionnelle (conditional expression) évalue les valeurs booléennes true ou false. Sur la base d'un résultat booléen, un ordinateur peut décider quel chemin d'exécution prendre. Voici quelques exemples d'expressions conditionnelles :
value1 == value2value1 != value2value1 < value2value1 <= value2
Instructions de sélection
La forme la plus simple d'instruction de sélection est une structure de décision connue sous le nom de branche unidirectionnelle, comme affiché dans la Figure 7.12. La plupart des langages représentent une instruction de sélection qui est une branche unidirectionnelle avec une construction connue sous le nom d'instruction if. D'autres langages peuvent utiliser une syntaxe légèrement différente. En Java, une instruction if relativement simple utilisant un bloc de code ressemble à ceci (il est recommandé d'utiliser des blocs de code pour les instructions de sélection sur une seule ligne afin d'éviter des erreurs ultérieures si d'autres instructions sont ajoutées) :
short temp = 90;
(…)
if (temp >= 95) {
System.out.println("It is hot");
}
Le seul type d'instruction conditionnelle dont un langage a besoin est la simple branche unidirectionnelle et n'importe quel nombre d'entre elles peut être placé dans un ordre séquentiel pour exécuter n'importe quelle tâche de prise de décision. Cependant, une variété de structures de décision ont évolué en différentes variantes qui rendent les programmes plus faciles à coder, plus lisibles et plus maintenables. Sa logique est présentée dans la Figure 7.13.
En Java, cela pourrait être codé avec une branche bidirectionnelle utilisant une instruction if…else :
int myNumber = 4;
if (myNumber % 2 == 0)
{
System.out.println("The number is even.");
}
else
{
System.out.println("The number is odd.");
}
Parfois, les décisions peuvent entraîner trois chemins possibles ou plus. Dans les langages de type C, cette situation pourrait être codée avec une branche multi-directionnelle utilisant une instruction if…else…if :
if (myGrade >= 90)
{
System.out.println("You got an A.");
}
else if (myGrade >= 80)
{
System.out.println("You got a B.");
}
else if (myGrade >= 70)
{
System.out.println("You got a C.");
}
else
{
System.out.println("You need to work harder.");
}
De nombreux langages de programmation ont employé une instruction de sélection supplémentaire pour les moments où les décisions entraînent trois chemins possibles ou plus. C'est ce qu'on appelle une instruction switch ou case, et elle peut être beaucoup plus lisible et maintenable que de nombreuses instructions if enchaînées. Cela ressemble à ce qui suit :
switch (myGrade)
{
case 90: System.out.println("You got an A.");
break;
case 80: System.out.println("You got a B.");
break;
default: System.out.println("You need to work harder.");
break;
}
Itération
Également connu sous le nom de bouclage, l'itération (iteration) consiste à répéter et à ré-exécuter des séquences d'instructions. L'une des grandes forces des ordinateurs est leur capacité à répéter des actions encore et encore. Les structures d'itération nous permettent d'écrire des instructions à répéter une seule fois, la répétition étant surveillée par des structures de flux de contrôle.
Le nombre d'itérations est contrôlé par des expressions conditionnelles, tout comme la sélection. Le type d'instructions conditionnelles utilisées pour contrôler les boucles est catégorisé comme contrôlé par condition ou contrôlé par compteur. Dans le scénario contrôlé par condition (condition-controlled), la boucle continuera à itérer jusqu'à ce qu'une condition soit remplie. Dans une situation contrôlée par compteur (count-controlled), la boucle se répète un nombre spécifique de fois.
Les structures d'itération peuvent également être catégorisées comme testées en tête ou testées en fin. Dans une boucle testée en tête (top-tested loop), une condition est définie à l'entrée du bloc de code. Si la condition est remplie, la boucle s'exécute et continuera à se répéter jusqu'à ce que la condition soit fausse. Notez que si la condition dans une boucle testée en tête n'est jamais vraie, la boucle ne s'exécutera pas du tout (par exemple, « while True: » comme test en tête d'une boucle en Python entraînera l'exécution de la boucle indéfiniment).
Dans la boucle à test en fin (bottom-tested loop), la sentinelle (sentinel), l'expression qui définit la condition à l'entrée ou à la sortie d'une boucle pour une itération continue, est à la sortie après le bloc de code. Une boucle de ce type est garantie de s'exécuter au moins une fois. La sentinelle décide si la boucle s'exécute à nouveau. Il est utile de garantir au moins une répétition d'une boucle.
Comme pour les instructions de sélection, la plupart des langages ont une variété de structures à choisir et, tout comme en sélection, il n'y en a qu'une seule qui est nécessaire pour tout type d'itération : la boucle testée en tête et contrôlée par condition. Sa logique est représentée par la Figure 7.14. Notez qu'il est supposé dans cet exemple que la variable « raining » peut changer suite à un événement externe en réaction, par exemple, à la sortie d'un capteur qui détecte la pluie et émet un rappel à un gestionnaire d'événements (non montré ici) qui change la valeur de la variable « raining » pour qu'elle soit vraie.
Dans la plupart des langages modernes, une boucle testée en tête est connue sous le nom de boucle while. Sa syntaxe en C# est la suivante :
Boolean happy = true;
Boolean rainin = false;
while (happy == true)
{
Console.out("I am happy");
if (raining == true) // valeur de raining contrôlée par le gestionnaire d'événements
{
Console.out("I am frowning");
happy = false;
}
}
La sentinelle dans cette boucle est la variable happy, et si cette condition reste vraie, la boucle itérera.
Ce type de boucle est non déterminatif (non determinative), une boucle pour laquelle nous ne pouvons pas prédire le nombre d'itérations (contrôlée par condition). Une boucle déterminante (determinative) qui est prévisible en ce sens que son nombre d'itérations s'exécutera exactement cinq fois et pourrait être construite en C# comme suit :
int count = 1;
while (count <= 5)
{
Console.out("I am smiling");
count = count + 1;
}
Dans la plupart des langages modernes, une boucle à test en fin est connue sous le nom de boucle do…while, comme mis en évidence dans la Figure 7.15. Sa syntaxe en C# est la suivante :
Boolean happy = true;
Boolean raining = false;
do
{
Console.out("I am smiling");
if (raining == true) // valeur de raining contrôlée par le gestionnaire d'événements
{
Console.out("I am frowning");
happy = false;
}
} while (happy == true)
La sentinelle dans cette boucle est toujours la variable happy ; la boucle itérera au moins une fois, et si cette condition reste vraie, la boucle continuera à itérer.
La sentinelle dans cette boucle est à nouveau la variable happy ; elle itérera au moins une fois et continuera si cette condition reste vraie. Ce type de boucle est non déterminatif car nous ne pouvons pas prédire quand il pleuvra et, par conséquent, combien de fois elle s'exécutera. Nous pourrions la construire de manière à la rendre contrôlée par compteur. Pour les boucles contrôlées par compteur, un autre style est une boucle for (for loop). Cette structure est testée en tête et contrôlée par compteur. Elle est concise, élégante, facile à maintenir et efficace. Un exemple de boucle for en PHP est le suivant :
for ($count = 1; $count <= 5; $count = $count +1) {
print("the count is: "", $count);
}
Les règles sont les suivantes : à l'intérieur des parenthèses, la première expression est exécutée une seule fois à la première entrée de la boucle. La deuxième expression est la sentinelle et elle est testée à toutes les itérations de la boucle. La dernière expression est exécutée à la fin de chaque itération et prépare le terrain pour la prochaine itération.
Fonctions
Les fonctions sont parfois appelées, ou remplacées par, des sous-routines, des sous-programmes, des procédures ou des méthodes. Certains informaticiens les considèrent comme des constructions de flux de contrôle car l'appel d'une fonction pour effectuer une tâche retire l'exécution de la séquence régulière. En général, un appel de fonction (function call) est un code qui invoque la fonction et lui transmet les valeurs qui pourraient être nécessaires pour qu'elle accomplisse sa tâche, tout en étant conçue pour retourner (return) des valeurs à l'endroit où la fonction a été appelée. Cependant, dans certains langages de programmation tels que le C, les fonctions ne peuvent retourner qu'une valeur d'un type donné ou aucune valeur. Dans ce cas, les paramètres de fonction peuvent être passés par référence (c'est-à-dire en passant un pointeur (pointer) comme paramètre) et les valeurs associées à ces paramètres peuvent être modifiées par la fonction.
Une alerte JavaScript est une fonction API conçue pour envoyer un message à une page web et attendre que l'utilisateur l'acquitte via un bouton OK, par exemple. Ce qui suit illustre le code qui invoque une telle fonction d'alerte :
alert("This message is for you, click OK to continue");
Modularité
Cela fait référence à la nécessité de diviser un programme en diverses tâches. Un programme d'employés effectuerait probablement de nombreuses tâches, dont l'une pourrait être de calculer le salaire hebdomadaire. La conception modulaire nous permet de spécifier ces tâches, de les nommer et de les appeler sans le code final. Nous pouvons définir les tâches comme des fonctions et construire leur squelette sans tous les détails. Ensuite, nous pouvons les appeler depuis notre flux de contrôle principal et les faire répondre à l'appel.
Maintenabilité
Si nous devions changer la façon dont nous payons nos employés, peut-être en raison d'un changement de déductions, nous devrions changer ce code à une multitude d'endroits. En utilisant une fonction pour effectuer le travail pour tous les employés, les changements peuvent être limités à un seul endroit dans le code.
Réutilisabilité
Construire des fonctions pour effectuer des tâches signifie que nous pouvons soit réutiliser le code dans d'autres logiciels et/ou appeler le même code depuis d'autres emplacements. Une API (API) qui est livrée avec la plupart des langages est un exemple parfait. Notre code appelant la routine d'impression PHP montre l'efficacité de cela — elle ne fait pas partie du langage lui-même ; elle fait plutôt partie de l'API qui est livrée avec.
Signature de fonction
Pour construire une fonction dans un HLL, il faut définir sa signature de fonction (function signature). La signature définit une fonction et informe le compilateur (compiler) ou l'interpréteur des détails dont il a besoin pour appeler cette fonction à exécuter. Elle définit également ce qu'elle peut retourner au code qui a appelé la fonction. Ce qui suit illustre une signature de fonction en Java (dans ce cas, la fonction peut être une méthode) pour la tâche de payer un employé :
public double payEmp(String empName, double empBasePay, double empHours);
La fonction peut être appelée pour faire son travail avec le code suivant :
myPay = payEmp("John Doe", 15.0,40.0);
Le montant de la rémunération serait calculé par la fonction et la valeur résultante serait retournée au point de l'appel et placée dans la variable.
Le mot public est un mot-clé en Java qui est appelé un modificateur d'accès (access modifier) ; il indique où dans le code cette méthode peut être accédée. Dans ce cas, elle est disponible de n'importe où dans le code du programme.
Le mot-clé double indique que cette méthode retournera une valeur de ce type de données à l'endroit où la méthode a été appelée. Dans ce cas, nous voulons qu'elle retourne la rémunération totale de l'employé. En Java, si nous utilisons le mot-clé vide (void) pour le retour, cela indique au compilateur que nous ne retournerons aucune valeur.
L'identifiant payEmp est le nom que nous avons donné à la méthode. Sa syntaxe suit les mêmes règles et bonnes pratiques (best practice) que celles utilisées pour un identifiant de variable dans ce langage.
Les parenthèses indiquent qu'il s'agit d'une fonction (méthode), la distinguant ainsi d'un identifiant de variable.
Un paramètre formel (formal parameter) représente les valeurs que la fonction doit recevoir pour faire son travail. Les paramètres ne sont pas obligatoires, mais s'ils sont spécifiés, l'appel de la fonction doit leur passer des valeurs de paramètres réels dans la fonction. Ne pas le faire entraînerait une erreur de compilation qui ne permettrait pas au programme de s'exécuter. En fait, ne pas se conformer à l'un des éléments de la signature entraîne une erreur de compilation.
Appel de fonction
Un appel de fonction dans un HLL doit être conforme à la signature de fonction. Un appel Java à la fonction ressemble à ceci :
double thisSalary = payEmp(thisName, thisPayRate, thisHrs);
De gauche à droite, nous déclarons une variable thisSalary et lui affectons la valeur qui sera le retour de la fonction. Nous appelons ensuite la fonction, en passant les valeurs requises pour ses paramètres définis. Le terme argument (argument) est utilisé pour les valeurs d'un appel de fonction qui doivent correspondre aux paramètres en nombre et en type de données.
Passage de paramètres
Les paramètres sont passés en utilisant l'une des deux méthodes. Dans le passage par valeur (pass by value), une copie de la valeur est faite et la copie est passée comme argument au paramètre. Lorsqu'il rencontre le paramètre, la valeur est affectée et les valeurs de la variable originale ne peuvent pas être affectées par les changements qui peuvent survenir à l'intérieur de la fonction.
D'autre part, le passage par référence (pass by reference) est employé en Java pour passer un type de données complexe, ce qui signifie que la valeur stockée à l'intérieur de la variable est un pointeur vers l'adresse mémoire de l'objet complexe réel. La valeur copiée dans le paramètre dans ce cas donne au code l'accès à l'objet lui-même dans la portée de la fonction, de sorte que toute modification apportée à sa valeur sera reflétée dans l'objet réel qui a été passé à la fonction, comme le montre la Figure 7.16.
Codons la fonction entière en Java pour avoir une vue complète de ses composants :
public double payEmp(String empName, double empBasePay, double empHours) {
double empPay = empBasePay * empHours;
System.out.println(empName + " is being paid " + empPay);
return empPay;
}
Dans l'exemple, empBasePay et empHours sont passés par valeur de types de données primitifs. D'autre part, empName est passé par référence car la chaîne est un type de données complexe.
Pile d'appels
La pile d'appels (call stack) (pile d'exécution) est une structure de données qui contrôle l'appel et le retour des fonctions. D'autres fonctions incluent le stockage des variables locales, ce qui dicte la portée (scope), définie comme la visibilité et la durée de vie des variables. Elle contrôle également le passage de paramètres.
La description de la pile d'appels se fait généralement en la comparant à une unité de plateaux de cafétéria. Les plateaux propres sont poussés sur l'unité au fur et à mesure qu'ils arrivent et sont retirés de l'unité selon les besoins. Ce concept est connu sous le nom de dernier entré, premier sorti (LIFO). Ainsi, lorsqu'une sous-routine est appelée, elle est poussée sur la pile d'appels. Sa présence et toutes ses parties sur la pile sont appelées un cadre de pile (stack frame). Lorsqu'elle termine son exécution, elle est retirée. Le flux de contrôle du programme suit l'état actuel de la pile d'appels. Une pile d'appels est décrite dans la Figure 7.17.
Dans cette implémentation, function1 est appelée en premier. Elle est poussée sur la pile au format de cadre de pile et les paramètres avec leurs valeurs sont poussés en premier, suivis par l'adresse de retour afin que le flux de contrôle puisse revenir à l'emplacement de l'appel de fonction après que le cadre de pile a été retiré de la pile. Enfin, les variables locales sont poussées sur le cadre de pile au fur et à mesure qu'elles sont définies. Dans cet exemple, function1 a appelé function2 et a été poussée sur la pile. Lorsqu'elle est retirée de la pile, le contrôle reviendra au cadre de pile de function1.
En regardant function1, nous voyons que les paramètres prennent vie lorsque la fonction est poussée sur la pile et disparaissent lorsque la fonction est retirée de la pile. La même chose est vraie pour toutes les variables locales déclarées dans la fonction. C'est une illustration de la portée d'une variable qui contrôle à la fois la visibilité et la durée de vie d'une variable (c'est-à-dire qu'une variable locale à une fonction n'est visible que dans la portée de cette fonction pendant la durée d'exécution de cette fonction). Cette portée est locale car la visibilité et la durée de vie sont locales à la fonction ; elles n'existent pas et ne peuvent pas être vues d'ailleurs dans le programme.
Récursivité
La récursivité est la séquence dans laquelle une fonction s'appelle elle-même. Si une fonction s'appelle elle-même, alors la prochaine instance de la fonction s'appellera également elle-même. Cela continue indéfiniment jusqu'à ce que la pile d'appels manque de mémoire, à moins qu'il n'y ait un moyen au sein de la fonction d'arrêter le processus (process). Cette condition est connue sous le nom de débordement de pile (stack overflow).
Certains problèmes ou tâches que nous voulons qu'un programme aborde se prêtent à la récursivité, comme avec un lancement de fusée. Si nous voulons programmer l'affichage d'une séquence de compte à rebours pour le lancement, nous pouvons le faire de manière récursive. La signature, connue sous le nom de prototype en C++, pourrait ressembler à ceci :
void countDown(long);
Comme indiqué par le void, la fonction ne retournera rien à son point d'appel. Elle a un paramètre qui représente le point de départ du compte à rebours. Par conséquent, si nous voulons commencer le compte à rebours à 100, l'appel ressemblera à ceci :
countDown(100);
La construction de la fonction récursive pourrait se faire comme suit :
void countDown(long count) {
if (count < 0)
{
return;
}
cout << count << endl;
count = count - 1;
countDown(count);
}
Programmes bien structurés
Un défi majeur dans l'écriture de programmes bien structurés est la complexité des systèmes actuels, surtout lorsqu'ils sont influencés par tant de programmeurs. Un dilemme courant est de savoir comment diviser pour mieux régner la tâche d'écriture de programmes lorsque l'effort repose sur la notion de modularisation (modularization), ou de diviser la tâche importante en unités indépendantes qui peuvent ensuite être appelées. Nous pouvons maintenant étendre ce concept pour inclure des entités qui existent dans des morceaux de code source (source code) complètement séparés.
Les programmes sont construits très efficacement à partir de modules, comme le montre la Figure 7.18. Un module (module) est un composant d'un programme et a une interface publique (public interface). Par exemple, en Java, l'interface inclut seulement la documentation de ce qu'un utilisateur a besoin pour accéder à un objet dérivé d'une classe (class), ainsi que ses attributs et méthodes. L'interface documente exactement comment les composants peuvent être utilisés par d'autres programmeurs : comment ils sont nommés, ce dont, le cas échéant, ils ont besoin pour faire leur travail, et ce qu'ils peuvent ou non retourner. La documentation est nécessaire pour qu'un autre programmeur sache comment les utiliser.
Cela nous permet de pratiquer le masquage de l'information (information hiding) : le masquage des bits et octets d'implémentation que l'utilisateur n'a pas besoin de connaître pour utiliser le module. Un bon exemple est la fonction print de PHP ; son implémentation n'est située dans aucun endroit que nous pouvons voir. Elle provient plutôt d'un module que quelqu'un a programmé et qui est livré avec l'API du langage.
Gestion des exceptions
Aucun code n'est parfait ; par conséquent, tout programme informatique peut générer des erreurs d'exécution. Ce type d'erreur indique généralement qu'une condition exceptionnelle s'est produite. Ces erreurs sont générées par une erreur d'exécution détectée par le matériel ou une condition inhabituelle détectée par le logiciel. Certaines de ces erreurs incluent le dépassement arithmétique, la division par zéro, la fin de fichier en entrée et les conditions définies par l'utilisateur (pas nécessairement des erreurs matérielles ou logicielles).
La plupart des langages modernes divisent les erreurs par catégorie : une erreur d'exécution (runtime error) est une exception suffisamment grave pour qu'elle ne puisse ou ne doive pas être gérée par le logiciel, et une exception est un comportement inhabituel qui peut être récupéré en utilisant le support de gestion des exceptions fourni par le langage de programmation (par exemple, la clause try/catch en Java). La terminologie appropriée est qu'une erreur ou une exception a été levée.
Fichiers et entrées-sorties
Les fonctionnalités de gestion de fichiers et les capacités d'entrée-sortie (I/O) (installations qui permettent à un programme de communiquer avec le monde extérieur) sont fortement dépendantes à la fois du système d'exploitation qui héberge le programme et du HLL. Tous les HLL diffèrent dans la manière dont ceux-ci sont gérés. Voici quelques exemples de différentes instructions de sortie dans divers HLL :
- Java:
System.out.println("Hello"); - C#:
Console.Writeln("Hello"); - C:
printf("Hello"); - C++:
cout << "Hello"; - JavaScript:
alert("Hello"); - PHP:
print("Hello");
Il y a autant de façons de gérer les E/S de fichiers qu'il y a de HLL. C'est un sujet qui nécessite des recherches avec chaque nouveau langage.
Lien vers l'apprentissage
En plus d'être l'un des langages de programmation les plus utilisés au monde, Java est un langage très pédagogique. À ce titre, les Constructions linguistiques de base de Java servent de grande référence pour l'apprentissage de ce nouveau langage.