Chapitre 9 Les sous-programmes ISBN 0-321-49362-1 Chapitre 9: Sujets • • • • • • • • • • • Introduction Éléments de base des sous-programmes Choix de conception Environnement référentiel local Méthodes de passage des paramètres Passage de sous-programme en paramètre Surcharge des sous-programmes Sous-programmes génériques Choix de conception pour les fonctions Surcharge des opérateurs définie par l'usager Coroutines 1-2 Introduction • Deux types fondamentaux d'abstraction – Abstraction des processus • Présent dès le début du développement de l'informatique • Exemple: trier(liste, longueur) – Abstraction des données • Surtout depuis les années 80 • Données + opérations • Exemples: Pile, dictionnaire, etc. 1-3 Éléments de base des sous-programmes • Chaque sous-programme a un unique point d'entrée • La routine appelante est suspendue pendant l'exécution d'un sous-programme • Le contrôle retourne à la routine appelante lorsque l'exécution du sous-programme est terminée. – Exception en Perl: goto &fct La fonction appelante est enlevée de la pile d'appel. 1-4 Définitions élémentaires • La définition d'un sous-programme décrit l'interface ainsi que les actions effectuées par la sous-routine. • Un appel de sous-programme est une instruction qui demande explicitement d'exécuter un sous-programme. • Une en-tête est la première partie de la définition: cela inclu: – le nom – la sorte de sous-programme (fonction ou procédure) – les paramètres formels – le type de la valeur de retour dans le cas d'une fonction 1-5 Procédures et fonctions • Il existe deux catégories de sousprogrammes: – Procédures: collection d'instructions définissant un calcul paramétrisé. – Fonctions: Structurellement similaires aux procédures mais retournent une valeur • En l'absence d'effet de bord, les fonctions d'un langage de programmation correspondent aux fonctions mathématiques. 1-6 Paramètres formels et effectifs • Un paramètre formel est une variable apparaissant dans la liste des paramètres et utilisée dans le sous-programme. • Un paramètre d'appel (ou effectif ) est une valeur ou adresse utilisée comme paramètre dans l'appel d'un sousprogramme 1-7 Paramètres formels et effectifs (suite) La correspondance entre les paramètres d'appel et les paramètres formels peut se faire de deux façons: • Selon la position des paramètres – Sur et efficace • Par association explicite – Le nom du paramètre formel auquel correspond un paramètre effectif est spécifié lors de l'appel. – Les paramètres peuvent apparaître dans n'importe quel ordre. – Ada, Fortran et Python utilisent cette méthode en plus de la méthode positionnelle. 1-8 Paramètres formels et effectifs (suite) Exemple en Python: >>> def f(a,b): return a-b >>> f(1,6) -4 >>> f(b=1,a=6) 5 >>> 1-9 Valeur de défaut des paramètres formels • Dans certains langages (e.g. C++, Ada et Python) on peut donner une valeur de défaut aux paramètres formels • Python: >>> def f(x=1,y=1,z=1): ... ... print x, y, z >>> f(y=2) 1 2 1 • En C les fonctions (en C# les méthodes) peuvent accepter un nombre variable de paramètres 1-10 Exemple en C #include <stdio.h> #include <stdarg.h> int f(int n, ...){ int i; va_list pa; va_start(pa,n); for (i=0;i<n;i++) printf("%d ", va_arg(pa, int)); printf("\n"); } int main(){ f(1,10); f(2,20,21); } Affiche: 10 20 21 1-11 Choix de conception • Quelles méthodes de passage des paramètres sont disponibles? • Y a-t-il vérification du type des paramètres? • Les variables locales sont-elles statiques ou dynamiques? • Peut-on définir des sous-programmes à l'intérieur d'autres sous-programmes? • Les sous-programmes peuvent-ils être surchargés? • Les sous-programmes peuvent-ils être génériques? 1-12 Environnement local • Les variables locales peuvent être dynamique sur pile: – Avantages • Permet la récursion • Partage de la mémoire – Désavantages • Temps d'initialisation • Adressage indirect • Les sous-programmes n'ont pas de mémoire – cela peut être vu comme un avantage: réduit les effets de bord • Les variables locales peuvent être statiques: – Plus efficace (pas d'indirection) – Pas besoin d'initialiser la pile – Ne supporte pas la récursion • En Fortran 95, on utilise le mot clef Recursive pour indiquer qu'une fonction peut être récursive 1-13 Méthodes de passage des paramètres • Façon avec laquelle les paramètres sont transmis et récupérés lors d'un appel de sous-programme: – Par valeur – Par résultat – Par copie – Par par référence – Par par nom 1-14 Passage par valeur (lecture seulement) • La valeur du paramètre d'appel est utilisée pour initialiser le paramètre formel correspondant – Espace supplémentaire requis – Opération de copie couteuse 1-16 Passage par résultat (écriture seulement) • Aucune valeur n'est transmise au sousprogramme. • Le paramètre formel correspondant est utilisé comme variable locale servant à transmettre au programme appelant la valeur calculée par le sous-programme – Requiert de l'espace supplémentaire ainsi qu'une opération de copie • Problème potentiel: sub(p1, p1): – Lequel des 2 valeurs de retour est copiée dans p1? 1-17 Passage par copie • Combine les deux types de passages précédents • Les paramètres formels ont un espace de stockage local • Désavantages: – Les mêmes que les deux modes précédents 1-18 Passage par référence • Un chemin d'accès est transmis • Avantages: – Efficace: pas de copie et pas d'espace dupliqué) • Désavantages – Accès plus lent (comparé au passage par copie) – Effets de bord potentiel – Création de pseudonymes (alias) 1-19 Passage par nom • Substitution textuelle du paramètre: int f(int x){return x*x*x;} Un appel à f(a+2*b) devient: return (a+2*b)*(a+2*b)*(a+2*b) • Utilisé dans Algol 60 1-20 Exemple: copie vs référence int G=3; /*variable globale*/ void fct(int a, int b){G=b;} void main{ Passage par copie: int Liste[10]; adr_G = &G Liste[G]=5; adr_LG = &liste[G] fct(G, Liste[G]) } a = *adr_G b = *adr_LG Valeur de G au retour de fct: G = b • Par copie: G vaut 3 *adr_G = a • Par référence: G vaut 5 *adr_LG = b 1-21 Choix de conception pour le passage de paramètres • Deux considérations – Efficacité – Transfert unidirectionel ou bidirectionnel • En théorie: – Toujours privilégier le transfert unidirectionnel • En pratique: – Passage par référence plus efficace pour de grosses structures comme les tableaux. 1-22 Implémentation du passage de paramètres • Dans la plupart des langages les paramètres sont passés via la pile • Le passage par référence est le plus simple: seule l'adresse est mise sur la pile. • On doit faire attention à l'implémentation du passage par référence et par copie lorsque les paramètres d'appel sont des constantes 1-23 Passage de paramètres dans certains langages • C – Passage par valeur – Passage par référence effectué en utilisant les pointeurs • C++ – Un type de pointeur spécial appelé référence est utilisé pour effectuer le passage par référence. • Java – Tous les paramètres sont passés par valeur sauf les objets qui eux sont passés par référence • C# – Par défaut: passage par valeur – Passage par référence: on met le mot clef ref avant les paramètre formel et effectif 1-24 Passage de paramètres dans certains langages (suite) • Ada – Trois modes de transmission: in, out et in out; par défaut on utilise in – Les paramètres in out sont passés par copie • Fortran – Similaire à Ada • PHP: similaire à C# • Perl: Le paramètres sont transmis via le tableau @_ 1-25 Vérification du type des paramètres • Très important pour la fiabilité • Aucune vérification en FORTRAN 77 et en C original • Pascal, FORTRAN 90, Java, et Ada: Toujours • Perl, JavaScript, Python, Ruby et PHP: Pas de vérification 1-26 Vérification du type des paramètres • C 72: Pas de vérification double sin() double x; { ... } • C 89: L'usager a le choix entre la version précédente et la suivante (type vérifié): double sin(double x) { ... } • C99 et C++: Type (presque) toujours vérifié 1-27 Exemple en C99 et C++ #include <stdio.h> #include <stdarg.h> int f(int p, ...){ // Le type des autres paramètres // n'est pas vérifié va_list pa; va_start(pa, p); /* Pointe return va_arg(pa, int); } int main(){ float x=3.1216; int n; n=f(1, x); printf("%d\n", n); } 1-28 Tableaux multidimensionnels • Le sous-programme doit connaître la taille des tableaux pour calculer la fonction d'accès. • C'est le cas, en particulier, lorsque le sousprogramme est compilé dans un autre module que celui où est défini le tableau. 1-29 Tableaux multidimensionnels: C et C++ • La taille de toutes les dimensions (sauf la première) doit être fournie: void fct(int mat[][100]) ... • Enlève de la flexibilité • Solution: Utilisation des pointeurs 1-30 Tableaux multidimensionnels: Ada with Text_Io; use text_Io; procedure Main is type Mat_Type is array (Integer range <>, Integer range <>) of Integer; M : Mat_Type(1..2, 1..2):=((1,2),(3,4)); T : Integer; function Somme(Mat : in Mat_Type) return Integer is S : Integer; begin S := 0; for i in Mat'range(1) loop for j in Mat'range(2) loop S := S + Mat(i,j); end loop; end loop; return S; end Somme; begin T := Somme(M); put_line("La somme est " & Integer'Image(T)); end Main; 1-31 Tableaux multidimensionnels: Fortran • Les paramètres formels qui sont des tableaux ont une déclaration après l'entête: Subroutine Sub(M, i, j, Resultat) Integer, Intent(In) :: i, j Real, Dimension(i,j), Intent(In) :: M Real, Intent(Out) :: Result ... End Subroutine Sub 1-32 Tableaux multidimensionnels: Java et C# • Similaire à Ada • Chaque tableau possède un attribut (length en Java, Length en C#) défini lors de la création S = 0; for (int i=0; i<Mat.length); i++) for (int j=0; j<Mat[i].length; j++) S += Mat[i][j]; • Remarque: Mat est un tableau de tableaux. 1-33 Passer un sous-programme en paramètre • Choix de conception: – Le type des paramètres est-il vérifié? – Quel est l'environnement de référence utilisé? 1-34 Passer un sous-programme en paramètre: vérification de type • C et C++: on ne peux passer qu'un pointeur de fonction et non pas la fonction elle même; le type des paramètres est vérifié • FORTRAN 95: Type vérifié • Pascal et Ada ne permettent pas de passer des sous-programmes en paramètre; En Ada on utilise plutôt les sous-programmes génériques 1-35 Passer un sous-programme en paramètre: Environnement • Liaison superficielle (dynamique): L'environnement où est exécuté le sousprogramme • Liaison profonde (statique): L'environnement où est défini le sousprogramme • Liaison ad-hoc (jamais utilisé): L'environnement où le sous-programme est passé en paramètre 1-36 Exemple en JavaScript function sub1(){ var x; function sub2(){ alert(x); }; // ouvre une boite de dialogue function sub3(){ var x; x=3; sub4(sub2); }; Superficielle: x=4 function sub4(subx){ Profonde: x=1 var x; Ad-hoc: x=3 x=4; subx(); }; x=1; sub3(); }; 1-37 Surcharge des sous-programmes • Plusieurs sous-programmes avec le même nom – Chaque version possède un prototype exclusif. • Ada, Java, C++, et C# permettent à l'usager de surcharger ses propres sous-programmes • En Ada, le type de retour d'une fonction est utilisé pour discréminer les fonctions surchargées (donc deux fonctions surchargées peuvent avoir les mêmes paramètres) 1-38 Sous-programmes génériques • Un sous-programme est générique (ou polymorphique) s'il peut être exécuté avec différents types de paramètres. • Christopher Strachey a défini en 1967 deux types de polymorphismes: – Polymorphisme ad hoc : • Dans le cas des sous-programmes surchargés • Nombre fini de situations définies explicitement – Polymorphisme paramétrique: • Dans le cas des sous-programmes dont le type des paramètres est générique. • Le sous-programme peut être utilisé avec un nombre illimité de nouveaux types. 1-39 Exemple en C++ template <class Type> Type max(Type first, Type second) { return first > second ? first : second; } • On peut utiliser la fonction précédente avec n'importe quel type pour lequel l'opérateur > est défini. int a,b,c; float x,y,z; ... c = max(a,b); ... z = max(x,y); 1-40 Exemple en C++ • Un template peut aussi avoir une valeur de défaut template <class Type> struct greater { bool operator()(Type a, Type b){return a>b;} }; template <class Type, class comp=greater<Type> > Type max(Type x, Type y) { comp plusgrand; return plusgrand(x,y) ? x : y; } 1-41 Exemple: Ada • Algorithme de tri generic type Index_Type is (<>); type Element_Type is private; type Vector is array(Index_Type range <>) of Element_Type; with function ">"(Left, Right: Element_Type) return BOOLEAN is <>; procedure Generic_Sort(List: in out Vector); 1-42 Exemple: Ada (suite) procedure Generic_Sort(List: in out Vector) is Temp: Element_Type; begin for Top in List'First..Index_Type'Pred(List'Last) loop for Bottom in Index_Type'Succ(Top)..List'Last loop if List(Top) > List(Bottom) then Temp:=List(Top); List(Top):=List(Bottom); List(Bottom):=Temp; end if; end loop; end loop; end Generic_Sort; 1-43 Exemple: Ada (suite) type Int_Array is array (INTEGER range <>) of INTEGER; procedure Integer_Sort is new Generic_Sort( Index_type=>INTEGER, Element_Type=>INTEGER, Vector=>Int_Array); 1-44 Exemple: Java Principale différence entre Java et C++ (ou Ada): • Les paramètres génériques doivent être des classes • Une seule copie du code est construite: elle opère sur la classe Object •Des restrictions peuvent être mises sur les types pouvant être utilisés comme paramètre 1-45 Exemple: Java public class compare { public static <E extends Comparable> E min(E[] tab){ E pluspetit = tab[0]; for (int i=1; i<tab.length; i++) if (tab[i].compareTo(pluspetit) < 0) pluspetit = tab[i]; return pluspetit;} public static void main(String[] arg){ String[] S={"Ferron", "Ducharme", "Tremblay"}; System.out.println(min(S)); Integer[] I={7, 2, 13, 45, 1, 43}; System.out.println(min(I)); } } 1-46 Opérateurs surchargé par l'usager • Les opérateurs peuvent être surchargés en Ada, C#, Perl, Python et C++ • Cela n'est pas possible en C, Java, JavaScript et PHP. • Exemple en Ada: Function “*”(A,B: in Vecteur): return Integer is Sum: Integer := 0; begin for Index in A’range loop Sum := Sum + A(Index) * B(Index) end loop return sum; end “*”; ... a,b,c : Vecteur; ... c = a * b; 1-48 Définition de nouveaux opérateurs • Certain langages permettent de définir et surcharger de nouveaux opérateurs: – e.g. Algol, Fortran, Lisp, Prolog, Perl et Haskell 1-49 Coroutines • Une coroutine est une généralisation des sousprogrammes • Plusieurs points d'entrées contrôlés par les coroutines elles-mêmes • Il n'y a pas de relation maître-esclave entre les coroutines • Une coroutine est appelée à l'aide d'une instruction telle que resume • Analogue à l'exécution de plusieurs threads sauf qu'ici les coroutines gèrent elles-mêmes l'ordonnancement. 1-50 Coroutines (suite) • Lors du premier resume la coroutine commence au début du code comme un sous-programme normal • Lors des appels subséquents, la coroutine poursuit son exécution au point où elle était rendu avant sa dernière interruption • Une interruption se produit lorsqu'une coroutine appelle une autre coroutine. • Origine: article de Melvin Conway (1963) • Présent dans Simula 67, Modula-2, Python, Lua et quelques autres langages. 1-51 Illustration des coroutines: 2 coroutines sans boucles 1-52 Illustration des coroutines: 2 coroutines avec boucles 1-53 Les générateurs • Mécanisme permettant de construire facilement des itérateurs • Généralisation des fonctions – Plusieurs points d'entrées – Peut retourner une valeur plusieurs fois en cours d'exécution • Les générateurs sont aux fonctions ce que les coroutines sont aux procédures. • Première apparition: CLU (MIT 1975) • Aussi dans Python, C#, et d'autres 1-54 Exemple en Lua co = coroutine.create(function () for i=1,10 do print("co", i) coroutine.yield() end end) coroutine.resume(co) coroutine.resume(co) coroutine.resume(co) ... coroutine.resume(co) coroutine.resume(co) --> co --> co --> co 1 2 3 --> co 10 -- n'affiche rien 1-55 Les générateurs en Python >>> def fib(): a,b = 0,1 while 1: yield b a,b = b, a+b >>> >>> 1 >>> 1 >>> 2 >>> 3 >>> 5 >>> g=fib() g.next() g.next() g.next() g.next() g.next() 1-56 Les itérateurs en Python >>> for i in [0,1,2,3,4,5,6,7,8,9]: if i>100: break print i >>> for i in range(10): if i>100: break print i >>> a = ['Un', 'petit', 'exemple'] >>> for i in range(len(a)): print i, a[i] 1-57 Générateurs et itérateurs en Python >>> for i in fib(): if i>100: break print i 1 1 2 3 5 8 13 21 34 55 89 >>> 1-58 Exemple en Python >>> class Reverse: ... "Itérateur pour parcourir une liste à l'envers" ... def __init__(self, data): ... self.data = data ... self.index = len(data) ... ... ... ... ... def __iter__(self): return self def next(self): if self.index == 0: raise StopIteration ... self.index = self.index - 1 ... return self.data[self.index] ... >>> for c in Reverse('spam'): ... print c 1-59 Sommaire • La définition d'un sous-programme décrit les actions effectuées • Un sous-programme peut être une fonction ou une procédure • Les variables locales peuvent être dynamiques sur pile ou statiques • Il y a trois principaux modèles de passage de paramètres: lecture, écriture et bidirectionnel • Certains langages permettent la surcharge des opérateurs et des sous-programmes • Les sous-programme peuvent être génériques • Les coroutines et les générateurs sont des généralisations des sous-programmes. 1-60