Chapter 1

publicité
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
Téléchargement