Notes et transcriptions du cours “Démarrer avec Go” proposée par University of California, Irvine disponible sur la plateforme coursera.
Nous allons donc parler de la portée d'une variable. En gros, la portée d'une variable correspond aux endroits du code où elle est accessible.
Ainsi, la portée de la variable définit comment une variable est résolue dans le code. Si vous faites référence à une variable x
, comment le programme détermine-t-il de quelle variable il s'agit réellement ?
C'est essentiellement ce qu'est la portée des variables (scope). Ces petits exemples illustrent un exemple de portée.
var x = 4 func f() { fmt.Printf("%d", x) } func g() { fmt.Printf("%d", x) } | func f() { var x = 4 fmt.Printf("%d", x) } func g() { fmt.Printf("%d", x) } |
Si nous regardons le premier bloc de code à gauche, nous avons une variable x
. Cette variable x
est définie en dehors de ces deux fonctions f() et g(). Ces fonctions sont très simples, elles affichent x
.
Lorsque vous appelez ces fonctions, elles doivent donc déterminer où trouver la valeur de x
pour l'afficher. Dans ce premier exemple, elles vont trouver la valeur définie à l'extérieur, là où est écrit var x = 4
, elles vont afficher 4 car les règles de portée le permettent.
Donc, en gros, comme x
est définit en dehors de l'une ou l'autre de ces fonctions, les deux y auront accès. Nous allons définir les règles formelles de portée dans quelques un peu plus tard.
Dans l'extrait de code à droite, vous allez avoir un problème. Il contient à nouveau les fonctions f() et g(), mais cette fois, la variable x
est définie dans la fonction f(), mais pas dans la fonction g(). Donc, la fonction f l'affichera correctement car elle examinera x
et la résoudra puisqu'elle est définie localement. Mais la fonction g() n'aura aucune référence à x
. Elle ne pourra pas la voir et elle générera une erreur lorsque vous essaierez de l'exécuter, car elle ne saura pas ce qu'est x
.
C'est donc le type de problème qui doit être résolu. Vous voulez être en mesure de savoir comment vos variables sont résolues pour éviter ce type de problème.
Nous allons donc parler de la portée des variables dès maintenant : comment le compilateur détermine-t-il comment une référence de variable doit être résolue ? De quel x
parlez-vous ? Ce x
ou bien cet autre x
.
Ainsi, dans Go, la portée des variables se fait à l'aide de blocs. Le bloc est une séquence de déclarations et d'instructions entre crochets {}
. Tout ce qui se trouve entre crochets s'appelle un bloc. C'est ainsi que vous définissez explicitement les blocs.
Notons que ces blocs peuvent être hiérarchiques. Vous pouvez avoir des crochets, puis à l'intérieur de ceux-ci, vous pouvez avoir d'autres crochets et à l'intérieur de ceux-ci, vous pouvez en avoir d'autres etc. Vous pouvez donc avoir cette hiérarchie de blocs. Il s'agit de blocs explicites.
Lorsque vous placez les crochets dans votre propre code, il s'agit de blocs explicites que vous incluez en tant que programmeur. Notons que les définitions de fonctions utilisent des crochets. Nous n'en sommes pas encore aux fonctions, nous en reparlerons plus tard mais pour chaque définition de fonction, vous donnez le nom de la fonction, puis vous avez des crochets ouverts, des crochets fermés. Il existe donc une hiérarchie de ces crochets et une hiérarchie de ces blocs.
Il existe également des blocs implicites dans cette hiérarchie, des blocs définis implicitement sans les crochets. Listons ces blocs :
if
, for
, switch
et select
. Tous ces éléments ont des crochets qui définissent leurs propres blocs. Nous y reviendrons plus en détail ultérieurement.
Ces blocs implicites peuvent être utilisés de façon explicite. Par exemple, pour une instruction if
, vous pouvez utiliser les crochets. Mais comme le bloc univers, le bloc package, le bloc fichier, ce sont tous des blocs implicites et il existe une hiérarchie entre eux.
Il existe une hiérarchie de ces blocs et chaque bloc peut avoir ses propres variables d'environnement associées.
La portée lexicale définit la manière dont les références aux variables sont résolues. Go est un langage à portée lexicale utilisant les blocs.
Ainsi, lorsque nous parlons de délimitation lexicale, nous devons parler de cette relation entre un bloc défini à l'intérieur d'un autre bloc.
On utilise la terminologie :
Comme est définit dans on désigne de portée interne (inner scope), et on désigne portée externe outer scope).
Il s'agit d'une relation transitive. A titre d'exemple, regardez le code ci-dessus.
Nous avons la variable x (en rouge) initialisée à 4. Ensuite, nos avons une fonction f() et une fonction g(). Examinons à présent l'imbrication des blocs. Le code se trouve dans un seul fichier. Donc, tout cela se trouve dans le bloc de fichiers et je l'appelle b1
. En plus du bloc de fichiers, je définis deux fonctions, f() et g(). Chacun de ces blocs fonctionnels possède son propre bloc, donc b2
est le bloc fonctionnel pour f() et b3
est le bloc fonctionnel pour g().
Si je devais regarder comment ces blocs sont liés : b2
et b3
sont tous deux définis dans b1
. Donc, je dis que b1
est supérieur à b2
, et b1
est supérieur à b3
selon ma définition, car b2
et b3
sont définis à l'intérieur de b1
. Mais notez qu'il n'y a aucune relation entre b2
et b3
. Parce qu'ils ne sont pas définis l'un dans l'autre.
Nous devons donc en prendre conscience, car selon les règles de portée, lorsque vous résolvez une variable, vous passez à la portée la plus étendue.
Ainsi, dans l' exemple, la fonction f() se trouve le bloc b1
avec la variable x. Lors de l’exécution de f() la variable x
va être recherchée dans le bloc lui-même (b2
portée locale). Mais comme x
n'existe pas, elle est recherchée dans le bloc de plus haut niveau (ici b1
). Donc, on commence par rechercher x
dans b2
, si elle n'est pas définie, on consulte le bloc b1
: comme la définition de x
existe bien dans b1
, on utilise celle-ci.
Il en va de même pour b3
. Si vous regardez la fonction g(), elle utilise la variable x
, elle accède à la variable x
définie dans b1
. Elle regarde d'abord dans son bloc local (b3
), elle ne la voit pas. Donc, elle regarde à l'intérieur du bloc supérieur b1
et la trouve ici. C' est pourquoi ce code fonctionnera : la variable x
sera résolue correctement.
Ainsi, lorsque vous parlez de portée de variable, une variable est accessible depuis un bloc , si les variables sont déclarées dans un bloc , et le bloc est supérieur/égal à .
Donc, ce sont soit les variables que vous avez déclarées dans , soit elles sont déclarées dans un bloc extérieur et supérieur à .
Les deux fonctions, qui se trouvent également dans le même bloc de fichiers, peuvent toutes deux accéder correctement à la variable x car leurs blocs de fonctions se trouvent dans le bloc de fichier.
Mais dans le bloc de code ci dessous
func f() { var x = 4 fmt.Printf("%d", x) } func g() { fmt.Printf("%d", x) }
la variable x
est définie dans le bloc fonctionnel de f(). Elle n'est pas dans le bloc fonctionnel de g. Ainsi, lorsque g() essaie de trouver x
, la variable x
, n'estp as présente dans son bloc local. Il ne le voit pas non plus dans le bloc supérieur de fichiers, car la définition se trouve maintenant dans le bloc de fonction de la fonction f(). C'est pourquoi cela échoue, x
n'est pas résolu en quoi que ce soit et il y a une erreur.