Skip to content

Texte : instructions des langages impératifs

Un langage est dit impératif s'il utilise le paradigme de programmation impérative où les opérations sont des séquences d'instructions exécutées par l'ordinateur pour modifier l'état du programme.

Le noyau des langages impératifs

La plupart des langages impératifs comportent cinq constructions de base (voir [Les principes des langages de programmation][Gilles Dowek, Les principes des langages de programmation, Ellipses, 2011].

  • l'assignation ou affectation
  • la déclaration de variable
  • la séquence
  • le test
  • la boucle

Notons que dans une assignation (par exemple en Python) variable = expression l'expression est appelée la source (ou dans le jargon anglophone « R-value » pour « Right »), dont l'évaluation produira la valeur sauvegardée dans la variable (« L-value » pour « Left »).

Rappelons qu'en Python, les variables ne sont pas déclarées ; c'est l'affectation qui définit sa valeur et son type.

Notons également que dans les premiers langages de "haut niveau" tel que FORTRAN et COBOL, l'instruction de saut inconditionnel, souvent nommée « goto », était présente. La goto indique par son opérande l'instruction suivante à exécuter dans le programme. Celle-ci est identifiée par une étiquette, un « label » placé devant l'instruction visée, appelée « instruction étiquetée ». Cette instruction est peu recommandée, pour des raisons de "bonne pratique de programmation" ; elle n'existe pas en Python.

Les entrées / sorties

Les premiers langages de programmation définis, ainsi que la plupart des langages impératifs courants, possèdent des instructions d'entrée-sortie qui font partie du cœur du langage (FORTAN, , COBOL, BASIC, Python, Java, etc).

Paradoxalement, ALGOL60 ne définit aucune instruction d'entrée-sortie, manifestant ainsi son objectif purement algorithmique : on décrit toujours un algorithme sous forme d'une ou plusieurs routines ou fonctions, les « entrées-sorties » étant alors leurs paramètres. Cette position radicale est certainement une des causes du peu de succès du langage en dehors de milieux académiques, puisqu'il est impossible de concevoir de vrais programmes sans I/O, des extensions locales, donc non portables, ont dues être systématiquement utilisées.

Si nous ajoutons la possibilité d'utiliser et de définir des fonctions et éventuellement des modules, nous parlons de paradigme de programmation procédurale ou plus simplement de langages procéduraux.

Programme complet en programmation procédurale

Un programme complet — c'est-à-dire compilable ou interprétable sans erreur et produisant effectivement un code ayant un effet — comporte plusieurs éléments complémentaires aux instructions élémentaires traduisant directement l'algorithme lui-même. Ainsi, le texte d'un programme comporte généralement plusieurs parties successives : 1. Un contexte : l'identité du programme (son nom), des éléments informatifs (auteur, date d'écriture, machine cible...), objet du programme, dépendances éventuelles, etc. Du point de vue syntaxique, il s'agit souvent de commentaires, mais ils sont indispensables. 2. Les importations : les importations de différents paquetages ou bibliothèques, standard ou ad hoc, ainsi que les autres fichiers sources à intégrer. 3. Les entités globales : il s'agit de données et déclarations « globales » partagées par le programme et ses différents sous-programmes (routines, etc.). 4. Le code du programme proprement dit. Celui-ci comporte les déclarations, définitions et initialisations des éléments « locaux » propres au programme et les instructions. 5. Les codes des sous-programmes éventuels.

Selon le langage et le contenu d'un programme particulier, l'ordre de ces différentes parties peut être légèrement mêlé ou permuté et ce texte peut être éclaté en plusieurs fichiers différents. Si l'on compare différents langages, la plupart des langages algorithmiques, qu'on appelle aussi « ALGOL-like », se ressemblent fort par la forme et l'effet de leurs instructions, mais ils peuvent sembler très différents si l'on examine leurs parties contextuelles et déclaratives. Pourtant, celles-ci restent très semblables d'un programme à l'autre écrit dans le même langage. Voilà pourquoi il est habituel de reprendre un même canevas standard que l'on établit en fonction de son contexte de travail, ses goûts et ses besoins. Nous allons en présenter un exemple ci-dessous.

Exemple de programme dans différents langages

Nous allons choisir arbitrairement comme programme type un programme lisant une suite de nombres naturels (entiers positifs) et affichant le nombre d'éléments nécessaires pour que leur somme dépasse un certain seuil. Celui-ci est un paramètre intégré au programme, une constante fixée arbitrairement à 1024.

Comme nous le verrons, la partie algorithmique reste très semblable. Mais les différences sont plus importantes pour les entrées-sorties (nom des fonctions, traitement des erreurs...) et les commentaires, ainsi que pour « l'emballage », c'est-à-dire le canevas d'un programme complet.

Programme en Python

#!/usr/bin/env python3.7
# -*- coding: utf-8 -*-
# Yves Roggeman - 2018/12 - Compare.py
# This program counts how much data are needed to reach a given level
#

import sys
level = 1024
sum=0
nb=0
while sum < level:
    try:
        data = int(input())
    if data < 0:
        raise Exception
    except Exception:
        print("Data error")
        sys.exit(-1)
    sum += data
    nb += 1
print(nb)

Il n'est pas possible de définir une constante en Python ; c'est au programmeur d'être attentif à ne pas modifier une telle valeur. Les erreurs de lecture sont capturées par le mécanisme de gestion des exceptions.

Programme en Pascal

{ Free Pascal Compiler version 3.0.4 - "fpc -Miso -Ciort -Tlinux"
  Yves Roggeman - 2018/12 - Compare.pas
  This program counts how much data are needed to reach a given level
}
program compare;
    const level = 1024;
    var data, sum, nb: integer;
begin
    sum := 0; nb := 0;
    repeat
        read(data);
        if data < 0 then
            begin writeln('Data error'); halt(-1) end;
        sum := sum + data; nb := succ(nb)
    until sum >= level;
    writeln(nb)
end.

La structure d'un programme « program déclaratives begin instructions end. » y est semblable à celle d'une fonction (mot-clé «function» ou «procedure»). Le «;» y sépare les instructions, il ne marque pas leur fin ; il n'y en a donc pas en fin de bloc.

Programme en C

En C, « tout est fonction » : le programme principal est lui-même une fonction appelée (nécessairement) « main » dont la valeur entière est renvoyée au système. Selon la convention Unix intriquée à celle de C, une valeur nulle indique une fin normale, un succès d'exécution (si l'on préfère, on peut utiliser à cet effet les constantes « EXIT_SUCCESS » ou « EXIT_FAILURE » définies dans ).

En C, comme en Pascal et dans la plupart de langages ALGOL-like, on doit déclarer toutes ses entités, à l'exception en C des fonctions. La bonne pratique veut qu'on les déclare le plus localement possible, dans le bloc où elles sont utilisées : c'est la règle de localité des variables qui doit être absolument respectée en C et dans tous ses descendants. Ceci peut également se faire en dehors de toute fonction, ce qu'on appelle le niveau espace de nommage ; ces entités (constantes, variables...) sont alors « globales », accessibles dans toute fonction au sein du même fichier source (ce qui correspond à une « unité de traduction »). Dans l'exemple ci-dessous, les directives « #include » ont pour effet de copier le fichier source donné en paramètre ; les entités qui y sont définies sont donc globales.

/* GNU C version 8.2.0 - "gcc -std=c18"
  * Yves Roggeman - 2018/12 - Compare.c
  * This program counts how much data are needed to reach a given level
  */
#include <stdlib.h>
#include <stdio.h>

int main (void) {
    const unsigned level = 1024;
    unsigned sum = 0, nb = 0;
    do {
        int data, err = scanf("%d", &data);
    if (err <= 0 || data < 0)
            {fputs("Data error\n", stderr); abort();}
    sum += data; ++nb;
    } while (sum < level);
    printf("%d\n", nb);
    return 0;
}

Programme en C++

Les ressemblances entre C et C++ sont nombreuses ; un programme en C peut d'ailleurs être compilé comme un programme en C++ si l'on choisit bien ses options de compilation ! On remarque évidemment l'usage des flux (stream) pour les I/O et des noms des en-têtes à inclure (ainsi que l'opérateur de résolution de portée « :: » pour désigner l'espace de nommage « std » utilisé par ces en-têtes).

/* GNU C++ version 8.2.0 - "g++ -std=c++17"
 * Yves Roggeman - 2018/12 - Compare.cpp
 * This program counts how much data are needed to reach a given level
 */
#include <cstdlib> // abort()...
#include <iostream> // cin, cout...

int main () {
    const unsigned level = 1024;
    unsigned sum = 0, nb = 0;
    do {
        int data; std::cin >> data;
    if (!std::cin || data < 0)
            {std::cerr << "Data error" << std::endl; std::abort();}
    sum += data; ++nb;
    } while (sum < level);
    std::cout << nb << std::endl;
    return 0;
}

Programme en Java

En Java, « tout est classe et méthode » ; par conséquent, le programme est (nécessairement) une méthode statique « main » — donc « presque » une fonction ordinaire — d'une classe ayant le nom du fichier (et donnant son nom au programme). Java démontre ainsi sa filiation directe à C.

Mais, contrairement à ce langage, Java ne possède pas de type «unsigned» et «main» ne renvoie aucune valeur, puisqu'elle n'est pas directement connectée au système, mais à une machine virtuelle : la JVM. Pour interagir avec le système, on utilise donc les membres de la classe « System » définie dans « java.lang » et toujours accessible.

/* OpenJDK version 11.0.1 - "javac -source 11"
 * Yves Roggeman - 2018/12 - Compare.java
 * This program counts how much data are needed to reach a given level
 */
import java.util.Scanner; // nextInt...
public class Compare {
    public static void main (String[] args) {
        final int level = 1024;
    int sum = 0, nb = 0;
    do{
        int data = 0;
    try {
            data = (new Scanner(System.in)).nextInt();
            if (data < 0) throw new Exception();
    } catch(Exception err)
            {System.out.println("Data error"); System.exit(-1);}
        sum += data; ++nb;
     } while (sum < level);
     System.out.println(nb);
    }
}

Programme en ALGOL68

Pour mieux comprendre les éléments communs des différents langages illustrés ci-dessus, voici leur ancêtre «théorique»: ALGOL68. Dans ce langage, archétype de tout langage algorithmique, un programme est un simple bloc « begin ... end ».

COMMENT Algol 68 Genie 2.8 - "a68g --strict"
    Yves Roggeman - 2018/12 - Compare.a68
    This program counts how much data are needed to reach a given level
COMMENT

BEGIN
    INT level = 1024;
    INT sum := 0, nb := 0;
    WHILE
        on value error(stand in, (REF FILE file)BOOL: err);
        on file end(stand in, (REF FILE file)BOOL: err);
        INT data; read(data);
        IF data < 0 THEN GOTO err FI;
        sum +:= data;
        nb +:= 1;
        sum < level
    DO SKIP OD;
    write(nb)
    EXIT
err:
    print("Data error")
END
On remarque, entre autres, la différence entre l'identité « = » (permettant donc de définir une constante symbolique, par exemple) et l'assignation « := » modifiant une L-value.

La construction de toutes les instructions composées permet de mettre toute une séquence dont la valeur est donnée par sa dernière expression. Ainsi le « WHILE...DO...OD » traduit bien ici une boucle à post-condition, les instructions précédant l'expression booléenne servant de condition.

Les erreurs de lecture sont traitées par les fonctions prédéfinies qui renvoient au bloc commun associé à l'étiquette « err ».

Programme en FORTRAN

Voici maintenant l'ancêtre des langages de programmation. Bien sûr, FORTRAN a beaucoup évolué et aujourd'hui, il est très semblable aux langages modernes : il est algorithmique, structuré et orienté objet, entre autres. Mais nous présentons ici un programme conforme au langage FORTRAN77 et en format fixe (instruction écrite entre les colonnes 7 et 72, une seule par ligne...) et en lettres capitales, sauf commentaires. Nous avons toutefois introduit une indentation pour marquer la structure, mais aucun programmeur ne le faisait à l'époque.

C GNU Fortran version 8.2.0 - "gfortran -std=f95 -ffixed-form"
C Yves Roggeman - 2018/12 - Compare.for
C This program counts how much data are needed to reach a given level
C
      PROGRAM COMPARE
        IMPLICIT NONE
        INTEGER LEVEL, TOT, NB, DAT
        PARAMETER (LEVEL = 1024)
        TOT=0
        NB=0
        DO WHILE (TOT .LT. LEVEL)
          READ(*, *, ERR=999, END=999) DAT
          IF (DAT .LT. 0) GOTO 999
          TOT = TOT + DAT
          NB = NB + 1
        END DO
        PRINT*, NB
      STOP
999     PRINT*, 'Data error'
        STOP 1
      END

Programme en COBOL

Enfin, nous n'aurions pas pu omettre le concurrent contemporain de FORTRAN : le COBOL. Ce langage est, dans ses premières années, essentiellement adapté au traitement de fichiers, laissant à FORTRAN les programmes calculatoires, dits « scientifiques ». Comme lui, COBOL a beaucoup évolué (c'est également devenu un langage orienté objet), mais nous illustrons ici une version en COBOL « traditionnel », c'est-à-dire antérieur à 1985 et en format fixe (instructions écrites entre les colonnes 8 ou 12 et 72) et écrit en lettres capitales.

Un programme COBOL est essentiellement composé de déclarations auxquelles trois « divisions » sur quatre sont consacrées.

 IDENTIFICATION DIVISION.
 PROGRAM-ID.     COMPARE.
 AUTHOR.         YVES ROGGEMAN.
 DATE-WRITTEN.   2018/12.
 REMARKS.        THIS PROGRAM COUNTS HOW MUCH DATA ARE NEEDED
                 TO REACH A GIVEN LEVEL.
 ENVIRONMENT DIVISION.
 CONFIGURATION SECTION.
 SOURCE-COMPUTER. UBUNTU.
 OBJECT-COMPUTER. X64-86.
*    OpenCOBOL 2.2.0 - "cobc -std=cobol85 -fixed"
 INPUT-OUTPUT SECTION.
 FILE-CONTROL.
     SELECT INP ASSIGN TO "Compare_Data". 
     SELECT OUT ASSIGN TO DISPLAY.
 DATA DIVISION.
 FILE SECTION.
 FD  INP.
 01  FILLER      PICTURE X(80).
 FD  OUT.
 01  NB-OUT      PICTURE Z(4)9.
 WORKING-STORAGE SECTION.
 77  LEVEL       PICTURE 9999, USAGE IS COMPUTATIONAL
                             , VALUE IS 1024. 
 77  TOT         PICTURE 9(5), USAGE IS COMPUTATIONAL
                             , VALUE IS ZERO.
 77  DAT         PICTURE S9(5), USAGE IS COMPUTATIONAL.
 77  NB          PICTURE 9(5), USAGE IS COMPUTATIONAL
                             , VALUE IS ZERO.
 PROCEDURE DIVISION.
 MAIN.
     OPEN INPUT INP.
     PERFORM LOOP,
         WITH TEST AFTER, UNTIL TOT IS GREATER THAN LEVEL.
     CLOSE INP.
     OPEN OUTPUT OUT.
     WRITE NB-OUT FROM NB.
     CLOSE OUT.
     STOP RUN.
 LOOP.
     READ INP INTO DAT; AT END PERFORM ERR.
     IF DAT IS LESS THAN ZERO THEN PERFORM ERR.
     ADD DAT TO TOT.
     ADD 1 TO NB.
 ERR.
     CLOSE INP.
     DISPLAY "Data error".
     STOP RUN.
C'est loin d'être concis ! Pour aider les programmeurs fatigués, il est permis d'omettre certaines ponctuations («,», «;»...) et certains mots « vides » («IS», «THAN», «AS», «WITH», «AT»...), ainsi que d'utiliser certaines abréviations (« COMP » pour « USAGE IS COMPUTATIONAL », « PIC » pour « PICTURE »...).