Intéressé par des cours d'informatique en ligne ?
Visitez mon nouveau site https://www.yesik.it !

Dans un langage impératif, les variables servent à mémoriser des données pendant l'exécution d'un programme.

Dans l'article variables et boucles nous avons introduit ce concept en utilisant des variables locales.

Mais ce n'est pas la seule sorte de variable qui soit à la dispostion du programmeur. En effet, des langages impératifs orientés objets comme le C++ ou PHP proposent plusieurs sortes de variables. En particulier:

Avertissement

Cet article est un article d'introduction. Les experts se rendront compte que je passe sous silence certains détails comme par exemple ceux liés à l'héritage et à l'accès aux variables d'instances d'une classe de base. Ou encore des spécificités de certains langages comme le modificateur friend du C++.

D'un point de vue pratique, cet article vise surtout à permettre à un débutant de savoir choisir quel sorte de variable utiliser.

Durée de vie et portée

Ces 4 types de variables se distinguent par leur portée et leur durée de vie.

La portée d'une variable correspond à la portion du programme l'on peut accéder à la variable. La durée de vie définit quand la variable est créée et quand elle est détruite.

En jouant sur ces deux paramètres, les langages modernes proposent tout une palette de variables plus ou moins partagées par les différentes parties du programme – et donc plus ou moins protégées contre les modifications externes.

Note:

Pour être plus concret, posez-vous la question: dans quel cas est-il le plus facile de garantir le contenu d'une variable? Quand elle ne "sert" que dans 10 lignes groupées dans un seul fichier – ou si au contraire elle peut être utilisée par 100000 lignes réparties dans 100 fichiers?

Variables locales

Dans notre panorama des variables, les variables locales sont celles dont la portée et la durée de vie sont les plus réduites.

Par conséquent, ce sont aussi les variables dont il est le plus facile de contrôler le contenu.

Durée de vie

Une variable locale est créée à chaque fois que s'exécute la méthode (ou fonction) dans laquelle elle est déclarée. La variable est détruite dès la fin de cette méthode (ou fonction). Certains langages (C++, Java par ex.) sont encore plus restrictifs puisqu'une variable locale cesse d'exister dès la fin du bloc (entre accolades) où elle a été déclarée.

Portée

Une variable locale ne peut être utilisée que dans la méthode qui la déclare. Certains langages (C++, Java par ex.) sont encore plus restrictifs puisqu'une variable locale ne peut être utilisée que dans le bloc (entre accolades) où elle a été déclarée.

Exemple

#include <iostream>
 
class Math
{
    public:
    unsigned int    factorielle(unsigned int n)
    {
        int resultat = 1;
        while(n > 0)
        {
            resultat *= n;
            --n;
        }
 
        return resultat;
    }
};
 
int main()
{
    Math    math;
    int     nbr = 3;
 
    std::cout << math.factorielle(nbr);
    std::cout << " est " << nbr << "!" << std::endl;
 
    nbr = 7;
    std::cout << math.factorielle(nbr);
    std::cout << " est " << nbr << "!" << std::endl;
}
<?php
 
class Math {
    public function factorielle($n) {
        $resultat = 1;
        while($n > 0)
            $resultat *= $n--;
 
        return $resultat;
    }
}
 
$math = new Math();
 
$nbr = 3;
print $math->factorielle($nbr);
print " est " . $nbr . "!\n";
 
$nbr = 7;
print $math->factorielle($nbr);
print " est " . $nbr . "!\n";
public class Math {
    public int factorielle(int n) {
        int resultat = 1;
        while(n > 0)
            resultat *= n--;
 
        return resultat;
    }
 
    static public void main(String[] args) {
        Math    math = new Math();
 
        int nbr = 3;
        System.out.print(math.factorielle(nbr));
        System.out.println(" est " + nbr + "!");
 
        nbr = 7;
        System.out.print(math.factorielle(nbr));
        System.out.println(" est " + nbr + "!");
    }
}
Math = function() {
    var that = {};
 
    that.factorielle = function(n) {
        var resultat = 1;
        while(n > 0)
            resultat *= n--;
 
        return resultat;
    }
 
    return that;
};
 
(function() {
    var math = Math();
    var nbr = 3;
    document.write(math.factorielle(nbr));
    document.write(" est " + nbr + "!<br/>");
 
    nbr = 7;
    document.write(math.factorielle(nbr));
    document.write(" est " + nbr + "!<br/>");
})();

Note:

Les codes sources sont donnés en différents langages à titre de comparaison (C++, PHP, Java et JavaScript). Les explications quand à elles se rapportent à la version C++.

Le programme ci-dessus affiche les factorielle de 3 et 7. Il déclare plein de variables locales. Et uniquement des variables locales.

Remarque:

Ce programme utilise aussi deux variables non-locales: les variables pré-définies std::cout et std::endl.

Récursivité

Il est possible qu'une méthode ou une fonction s'appelle elle-même. C'est ce que l'on appelle la récursivité. La récursivité peut être utile afin d'exprimer plus clairement un algorithme. Ainsi, on peut facilement modifier la méthode factorielle pour la rendre plus proche de la définition mathématique de la factorielle:

/* ... */
    unsigned int    factorielle(unsigned int n)
    {
        int resultat = 1;
        if (n > 0)
            resultat = n*factorielle(n-1);
 
        return resultat;
    }
/* ... */

Pendant l'exécution factorielle est susceptible de s'appeler elle-même. Le point important à noter est que chaque exécution de la méthode factorielle possède son propre jeu de variables locales. Ou autrement dit, son propre exemplaire des variables n et resultat. Par conséquent, quand le contenu de la variable resultat est modifié, seule l'exemplaire de la variable resultat de la méthode en cours d'exécution est modifié.

Utilisation

Les variables locales sont les plus nombreuses dans un programme bien conçu. Elles servent pour mémoriser les données temporaires, les résultats intermédiaires, les compteurs de boucle, etc. Une bonne règle générale est de considérer qu'en cas de doute, il est préférable d'utiliser une variable locale.

Variables d'instance

Dans les langages impératifs orientés objets, la variable d'instance est une variable associée à un objet et accessible par les méthodes de cet objet.

Note:

Quand plusieurs objets coexistent en mémoire, chacun possède son propre jeu de variables d'instance.

Durée de vie

Les variables d'instance d'un objet sont crées lorsque cet objet est instancié. Elle sont détruites lorsque l'objet est détruit.

Portée

Une variable d'instance est accessible par toutes les méthodes appliquées sur un objet.

Remarque:

Selon les langages, il est possible de spécifier plus finement la portée des variables d'instance à l'aide de modificateur comme protected, private ou publique.

Exemple

#include <iostream>
#include <string>
 
class Book
{
    public:
    Book(std::string title) : _title(title)
    {
    }
 
    std::string     toString()
    {
        return "Title: " + _title;
    }
 
    private:
    std::string     _title;
};
 
int main()
{
    Book    book1("The Lord of the Ring");
    Book    book2("The Hobbit");
 
    std::cout << book1.toString() << std::endl;
    std::cout << book2.toString() << std::endl;
 
    return 0;
}
<?php
 
class Book {
    public function __construct($title) {
        $this->title = $title;
    }
 
    public function __toString() {
        return "Title: " . $this->title;
    }
 
    private $title;
}
 
$book1 = new Book("The Lord of the Ring");
$book2 = new Book("The Hobbit");
 
print $book1 . "\n";
print $book2 . "\n";
class Book {
    public Book(String title) {
        this.title = title;
    }
 
    public String toString() {
        return "Title: " + title;
    }
 
    private String      title;
}
 
public class BookStore {
    static public void main(String[] args) {
        Book book1 = new Book("The Lord of the Ring");
        Book book2 = new Book("The Hobbit");
 
        System.out.println(book1);
        System.out.println(book2);
    }
}
var Book = function(title) {
    var _title = title;
 
    var that = {};
    that.toString = function() {
        return "Title: " + _title;
    }
 
    return that;
};
 
(function() {
    var book1 = Book("The Lord of the Ring");
    var book2 = Book("The Hobbit");
 
    document.write(book1 + "<br/>");
    document.write(book2 + "<br/>");
})();

Ce programme crée deux objets (deux instances) de la classe Book puis demande à chacune de s'afficher.

Notez comme chaque objet possède son propre exemplaire de la variable d'instance _title: quand on crée le second livre, le titre du premier n'est pas écrasé. De la même manière, lors de l'affichage (toString), il n'y a aucune confusion possible entre le titre du premier livre et le titre du second: chaque objet possède bien son propre exemplaire de _title.

Utilisation

Les variables d'instances sont nécessaires quand une donnée doit survivre entre plusieurs utilisations du même objet: dans l'exemple ci-dessus, la donnée devait survivre entre la construction de l'objet et son affichage.

Variables de classe

Jusqu'à présent, nous avons parlé des variables locales et des variables d'instance. Toutes deux ont en commun de pouvoir exister simultanément en autant d'exemplaires que nécessaire. Par ailleurs, ces variables se caractérisent par une durée de vie limitée.

Avec les variables de classe, les choses changent radicalement...

Durée de vie

La durée de vie des variables de classe est des plus simples puisqu'elles existent du début à la fin du programme, et chacune en un seul exemplaire.

Remarque:

Pour être plus précis, avec les langages comme Java qui permettent de charger dynamiquement une classe pendant l'exécution du programme, les variables de classes existent à partir du moment où leur classe est chargée.

Portée

Les variables de classes sont accessibles par toutes les méthodes des objets d'une même classe.

Remarque:

Comme pour les variables d'instance, la visibilité des variables de classe peut être ajustée par les modificateurs private, protected ou public.

Exemple

#include <iostream>
#include <sstream>
#include <string>
 
class Book
{
    public:
    Book(std::string title) : _title(title), _id(gNextId++)
    {
    }
 
    std::string     toString()
    {
        std::ostringstream  os;
 
        os << "Title: " << _title << " id: " << _id;
 
        return os.str();
    }
 
    private:
    std::string     _title;
    int             _id;
 
    static int      gNextId;
};
 
int Book::gNextId       = 0;
 
int main()
{
    Book    book1("The Lord of the Ring");
    Book    book2("The Hobbit");
 
    std::cout << book1.toString() << std::endl;
    std::cout << book2.toString() << std::endl;
 
    return 0;
}
<?php
class Book {
    public function __construct($title) {
        $this->title = $title;
        $this->id = self::$nextId++;
    }
 
    public function __toString() {
        return "Title: " . $this->title . " id: " . $this->id;
    }
 
    private $title;
    private $id;
 
    private static $nextId = 0;
}
 
$book1 = new Book("The Lord of the Ring");
$book2 = new Book("The Hobbit");
 
print $book1 . "\n";
print $book2 . "\n";
class Book {
    public Book(String title) {
        this.title = title;
        this.id = nextId++;
    }
 
    public String toString() {
        return "Title: " + title + " id: " + id;
    }
 
    private String      title;
    private int         id;
 
    static private int  nextId = 0;
}
 
public class BookStore {
    static public void main(String[] args) {
        Book book1 = new Book("The Lord of the Ring");
        Book book2 = new Book("The Hobbit");
 
        System.out.println(book1);
        System.out.println(book2);
    }
}
var Book = (function() {
                var gNextId = 0;
 
                return function(title) {
                    var _title = title;
                    var _id = gNextId++;
 
                    var that = {};
                    that.toString = function() {
                        return "Title: " + _title + " id: " + _id;
                    }
 
                    return that;
                }
            }) ();
 
(function() {
    var book1 = Book("The Lord of the Ring");
    var book2 = Book("The Hobbit");
 
    document.write(book1 + "<br/>");
    document.write(book2 + "<br/>");
})();

Le programme ci-dessus utilise une variable de classe pour mémoriser le nombre de livres créé (et par conséquent l'ID du prochain livre).

En C++, la variable de classe se distingue d'une variable d'instance dans le code source par la présence du mot-clé static.

Remarque:

Remarquez également qu'en C++ la variable de classe est initialisée à l'extérieur de la classe. En effet, celle-ci existe (et donc doit être initialisée) avant même la création du premier objet. Chaque langage utilise ses propres conventions pour résoudre ce problème.

Utilisation

Les variables de classes sont utiles lorsqu'il est nécessaire de partager une information entre les différentes instances d'une classe.

Cela peut être une information de configuration, ou encore une information qui permet de communiquer entre les intances d'une même classe.

Un petit mot d'avertissement cependant: une variable de classe dont la visibilité est étendue par le mot-clé public n'est rien d'autre qu'une variable globale. Avec toutes les réserves que vous découvrirez ci-dessous...

Variables globales

De toutes les variables dont nous parlons ici, les globales sont celles qui sont les moins protégées contre les utilisations "abusives".

Durée de vie

Une variable globale existe du début à la fin d'un programme. Et en un seul exemplaire.

Portée

Une variable globale peut être utilisée par n'importe quelle méthode de n'importe quel objet de n'importe quelle classe. En fait, elle peut être utilisée n'importe où dans le programme. Il est clair dans ces conditions qu'il est très difficile de contrôler l'utilisation fait d'une telle variable dans un programme un tant soit peu complexe.

Exemple

Pour illustrer la dangerosité des variables globales, nous reprenons ici l'exemple précédent, mais en utilisant une variable globale à la place de la variable de classe:

#include <iostream>
#include <sstream>
#include <string>
 
int gNextId     = 0;
 
class Book
{
    public:
    Book(std::string title) : _title(title), _id(gNextId++)
    {
    }
 
    std::string     toString()
    {
        std::ostringstream  os;
 
        os << "Title: " << _title << " id: " << _id;
 
        return os.str();
    }
 
    private:
    std::string     _title;
    int             _id;
};
 
int main()
{
    Book    book1("The Lord of the Ring");
    Book    book2("The Hobbit");
 
    std::cout << book1.toString() << std::endl;
    std::cout << book2.toString() << std::endl;
 
    return 0;
}

Comme vous le voyez, très peu de choses ont changé. Et le programme fonctionne toujours. D'ailleurs, le code généré par le compilateur est quasiment identique.

Néanmoins, cette version est beaucoup plus fragile! En effet, imaginons une classe auteur qui utilise aussi un mécanisme d'ID:

class Author
{
    public:
    Author(std::string name) : _name(name), _id(gNextId++)
    {
    }
 
/* ... */

Comme vous le constatez, la classe auteur utilise le même compteur gNextId que la classe Book. Est-ce que ce comportement est désiré ou pas? Souhaitable ou non? En tous cas, ce code est susceptible d'introduire des comportements inattendus, voir des bugs, par exemple si l'auteur d'une de ces deux classes décide de "ré-initialiser" le compteur:

/* ... */
        void reloadAuthorsFromDatabase() {
            gNextId = 0;
            while(_db.hasNextAuthor())
                loadNextAuthor();
        }
/* ... */

En outre, au cas où un bug est découvert et qu'il est causé par une valeur erronée dans une variable globale, il devient très difficile de localiser le fragment de code responsable de cette erreur: en effet, la modification intempestive peut avoir eu lieu n'importe où dans le programme. Ca n'est pas si grave que ça si le programme fait 10 lignes. C'est un autre paire de manche s'il fait 100000 lignes...

Ces problèmes ne pourraient pas se produire avec une variable de classe (sous entendu non publique), puisqu'elle ne serait pas partagée entre Book et Author.

Utilisation

Les variables globales ne servent à rien. C'est un point de vue un peu extrême – et certainement un peu faux aussi. Néanmoins, dans la pratique il y a très peu de cas où elle sont réellement nécessaires. En outre, il est toujours possible d'utiliser une variable de classe à la place d'une variable globale. Par conséquent...

Récupérée de « http://www.chicoree.fr/w/Variable »