Reference ✱ *********** .. include:: ../BoutonGoogleTrad.rst Référence de variables ====================== Introduction ------------ Pour créer une référence vers une variable existante, il suffit d'utiliser la syntaxe suivante : .. panels:: :column: col-lg-10 p-2 **SYNTAXE** - Création d'une référence Type & NomRef = lvalue; Une **référence** définit un alias vers un élément stocké en mémoire de façon pérenne (lvalue). Nous pouvons définir des références sur des variables mais aussi sur des lvalues plus complexes. Voici un exemple : .. code-block:: cpp #include int main() { int a = 7; int & refA = a; // definition of a reference to variable a std::cout << refA; // ==>> 7 int arr[4] = { 1, 2, 3, 4}; int & refElem = arr[2]; // definition of a reference to arr[2] std::cout << refElem; // ==>> 3 } .. warning:: Lors de la création d'une référence, aucune copie n'est effectuée. La référence et l'élément original désignent une même chose. Utiliser l'une ou l'autre est équivalent. La syntaxe nous interdit de créer une référence sans l'initialiser. Ainsi, on ne peut pas écrire : .. code-block:: cpp int main() { int & ref; // ==> not possible, a reference must always be initialized during definition } Vérification ------------ Veuillez exécuter le code suivant puis analyser les résultats et confirmer que la référence n'effectue pas de copie : .. code-block:: cpp #include struct Vec { int x; int y; }; int main() { // int type int n = 10; int & refN = n; refN++; std::cout << n << " " << refN << std::endl; // struct type Vec obj; obj.x = 10; obj.y = 20; Vec & alias = obj; alias.x += 5; alias.y += 5; std::cout << alias.x; } Référence et passage d'arguments ================================ Un paramètre de fonction qui correspond à une référence ne définit pas une nouvelle variable mais un alias renommant la donnée passée en argument. Par conséquent, toutes les modifications effectuées sur la référence sont en fait effectuées sur la donnée passée en argument. Voici la syntaxe : .. panels:: :column: col-lg-10 p-2 **SYNTAXE** - Passage d'un argument par référence : ... NomFonction(TypeVar & NomRef, ...) { ... } .. note:: L'utilisation de références dans les paramètres de fonction permet de retourner plusieurs informations. L'instruction *return* ne permet elle que de retourner un unique élément ce qui peut parfois être limité. Veuillez exécuter le code suivant puis analyser les résultats et confirmer qu'aucune copie n'est effectuée : .. code-block:: cpp #include struct Vec { int x; int y; }; void F(int & n, Vec & obj) { n += 1; obj.x += 2; obj.y += 3; } int main() { // int type int a = 10; // struct type Vec A; A.x = 10; A.y = 20; std::cout << "Avant l'appel : " << a << " / " << A.x << " " << A.y << std::endl; F(a,A); std::cout << "Après l'appel : " << a << " / " << A.x << " " << A.y << std::endl; } Exercices --------- .. panels:: Fonctions disponibles ^^^^^^^^^^^^^^^^^^^^^ .. code-block:: cpp int F(int t) { t += 2; return t; } void I(int & t) { t += 7; } int Z(int & t) { t = 1; return 5; } int F(int & a, int & b) { return a+b; } --- Exercice 1 .. code-block:: cpp int main() { int a; Z(a); I(a); std::cout << a; } --- Exercice 2 .. code-block:: cpp int main() { int a = 8; a += F(a); I(a); std::cout << a; } --- Exercice 3 .. code-block:: cpp int main() { int a = 8; std::cout << I(a+8); } --- Exercice 4 .. code-block:: cpp int main() { int a = 8; int b = 3; std::cout << F(a,b); } --- Exercice 5 .. code-block:: cpp int main() { int a = 8; int b = 3; std::cout << F(F(b,a)); } .. quiz:: RefTest :title: Premiers pas avec les références. Pour chaque exemple, indiquez l'affichage obtenu ou ERR si le programme émet une erreur : .. csv-table:: :delim: ! Exercice 1 :quiz:`{"type":"FB","answer":"8"}` ! Exercice 4 :quiz:`{"type":"FB","answer":"11"}` Exercice 2 :quiz:`{"type":"FB","answer":"25"}` ! Exercice 5 :quiz:`{"type":"FB","answer":"13"}` Exercice 3 :quiz:`{"type":"FB","answer":"ERR"}` Exercices --------- .. panels:: Fonctions disponibles ^^^^^^^^^^^^^^^^^^^^^ .. code-block:: cpp int F(int & t) { t += 2; return t; } void I(int & t) { t += 7; } int & Z(int & t) { t = 0; return t; } int & F(int & a, int & b) { return a; } --- Exercice 6 .. code-block:: cpp int main() { int a = 8; int b = 3; std::cout << F(F(b,a)); } --- Exercice 7 .. code-block:: cpp int main() { int a = 8; std::cout << Z(a+1); } --- Exercice 8 .. code-block:: cpp int main() { int a = 8; int b = 3; Z(F(a,b))++; std::cout << a; } --- Exercice 9 .. code-block:: cpp int main() { int a = 8; std::cout << F(F(a)); } --- Exercice 10 .. code-block:: cpp int main() { int a = 3; int b = 2; F(Z(a),Z(b))++; std::cout << a; } .. quiz:: RefTest :title: Exercices sur les références - Niveau 2. Pour chaque exemple, indiquez l'affichage obtenu ou ERR si le programme émet une erreur : .. csv-table:: :delim: ! Exercice 6 :quiz:`{"type":"FB","answer":"5"}` ! Exercice 9 :quiz:`{"type":"FB","answer":"ERR"}` Exercice 7 :quiz:`{"type":"FB","answer":"ERR"}` ! Exercice 10 :quiz:`{"type":"FB","answer":"1"}` Exercice 8 :quiz:`{"type":"FB","answer":"1"}` Cas pratiques ============= Voici quelques scénarios spécifiques qu'il faut connaître car ce sont de grands classiques. Performance ----------- Lorsque l'on transmet un élément prenant de la place mémoire, il est préférable de le passer par référence afin d'éviter une recopie des données. Cependant, si vous effectuez un passage par copie, vous n'aurez pas de message d'erreur, mais vous pourrez constater un ralentissement du programme. Prenons l'exemple de la fonction transposition d'une matrice : .. code-block:: cpp Matrix Transpose(Matrix M) { Matrix result; ... return result; } Cette fonction compile sans erreur et fonctionne correctement. Cependant, l'argument : **Matrix M** pose problème. En effet, il s'agit de la définition d'une variable locale *M*. L'objet matrice passé en argument va donc être recopié, valeur par valeur, dans *M* ce qui représente un traitement inutile. Il faut donc préférer la version suivante : .. code-block:: cpp Matrix Transpose(Matrix & M) { Matrix result; ... return result; } Juste un signe & supplémentaire et un gain de performance à la clef. .. panels:: :column: col-lg-10 p-2 **REGLE** : Il est préférable de passer les objets en paramètre par référence plutôt que par valeur, ceci afin d’éviter des copies inutiles. Retour de référence depuis une variable locale ---------------------------------------------- Techniquement, vous pouvez définir une fonction retournant une référence : .. code-block:: cpp int & Test(int a) { int b = a * a + 3; return b; // ERROR: returning reference to a local variable } int main() { int a = 7; int & c = Test(a); c++; // this line triggers a runtime error std::cout << c; } La syntaxe de ce programme est correcte. Le code compile, pourtant son exécution produit une erreur système inconnue. Que se passe-t-il ? * Dans la fonction *Test*, nous créons une variable *b*. Cela est autorisée. * Nous retournons ensuite une référence vers cette variable *b*. * Cette référence sert à initialiser la référence *c* maintenant associée à *b*. * L'exécution de la ligne : *c++;* pourtant correcte, produit une erreur système. Remontons à la source du problème. La référence *c* est faite vers la variable locale *b* de la fonction *Test()*. Hors cette variable est une variable locale dont la durée de vie s'arrête à la fin de la fonction. Ainsi, cette variable est détruite et la référence retournée désigne une variable n'existant plus. Lorsque l'on essaye d'utiliser cette référence, c'est le crash. A noter que le comportement du programme est indéterminé dans ce genre de situation : * Le programme peut crasher avec ou sans message (souvent sans). * Une autre variable peut être modifiée à la place de *b*, produisant un bug incompréhensible plus tard dans le programme. * Une partie de la mémoire contenant du code peut être modifiée ce qui fait que le programme peut se bloquer. .. panels:: :column: col-lg-10 p-2 **REGLE** : On ne doit pas retourner une référence sur une variable locale à une fonction. Une syntaxe d'appel ambiguë --------------------------- Nous présentons dans le code ci-dessous, un passage par copie et un passage par référence : .. code-block:: cpp #include void F1(int n) { n += 1; std::cout << n << std::endl; } void F2(int &n) { n += 1; std::cout << n << std::endl; } int main() { int a = 5; F1(a); // pass by value F2(a); // pass by reference } Le code ne produit pas d'erreur, il semble fonctionner mais là n'est pas le problème. Examinons la définition des deux fonctions disponibles. L'une utilise un passage par copie et l'autre par référence. A ce niveau, tout est clairement explicité grâce à l’utilisation du signe &. Examinons maintenant les appels dans la fonction *main()*, nous avons : *F1(a)* et *F2(a)*. A ce niveau, on ne constate aucune différence de syntaxe. .. panels:: :column: col-lg-10 p-2 **ATTENTION** : En examinant un appel de fonction, il est impossible de déterminer en C++ si les variables sont passées par copie ou par référence. C'est une faiblesse du langage. Exercices --------- .. quiz:: RefTest222 :title: Les cas complexes Pour chaque exemple, indiquez l'affirmation est vraie ou fausse : .. csv-table:: :delim: ! :quiz:`{"type":"TF","answer":"F"}` Si une fonction retourne une référence sur une variable locale, cela la maintient en vie. :quiz:`{"type":"TF","answer":"F"}` L'écriture *F(a)* correspond uniquement à un passage par copie. :quiz:`{"type":"TF","answer":"T"}` Une const référence indique que l'on ne peut modifier l'élément référencé. :quiz:`{"type":"TF","answer":"T"}` Le passage par référence permet de limiter le temps passé à faire des copies. Travail à rendre sur Github Classroom ===================================== Exercice -------- * Créez un fichier nommé *2_reference.cpp* * Codez : * Une fonction *void inc(...)* qui incrémente l'entier passée en paramètre * Une fonction *void makePositive(...)* qui prend un entier et le transforme en nombre positif * Une fonction *void rotate(...)* qui prend trois entiers *a,b,c* et les décale sur la gauche a←b... * Une fonction *void clamp(a,b,c)* qui fait en sorte que a=b si ac * Intégrez les fonctions de test suivantes dans votre code * Validez vos fonctions * Uploadez le fichier sur votre github .. code-block:: cpp #include #include using namespace std; int main() { // ---- inc ---- int x = 0; inc(x); if (x != 1) cout << "[FAIL] inc: got=" << x << " expected=1\n"; x = -10; inc(x); if (x != -9) cout << "[FAIL] inc: got=" << x << " expected=-9\n"; // ---- makePositive ---- x = -3; makePositive(x); if ( x != 3 ) cout << "[FAIL] makePositive: got=" << x << " expected=3\n"; x = 12; makePositive(x); if ( x != 12 ) cout << "[FAIL] makePositive: got=" << x << " expected=12\n"; // ---- rotate ---- int a=1,b=2,c=3; rotate(a,b,c); if (!(a==2 && b==3 && c==1)) cout << "[FAIL] rotate (1,2,3): got=("<