Forum |  HardWare.fr | News | Articles | PC | S'identifier | S'inscrire | Shop Recherche
1675 connectés 

  FORUM HardWare.fr
  Programmation
  C++

  Utilité des références et des const

 


 Mot :   Pseudo :  
 
Bas de page
Auteur Sujet :

Utilité des références et des const

n°2096084
mikizi
Posté le 18-08-2011 à 18:33:33  profilanswer
 

Bonjour à tous,
 
J'ai posé cette question à plusieurs programmeurs (certes pas spécialisés dans le c++) et je n'ai pas encore eu de réponses convenables. Je dois écrire une fonction allouant un tableau d'entiers passé en paramètre, la taille également passé en paramètre, puis une fonction remplissant un tel tableau, une autre qui en somme les éléments, et enfin désallouer le tableau (4 fonctions à écrire en tout) . J'écris donc :
 
void allocationTab (int* tab, int n){
    tab=new int[n];
}
 
void remplissageTab(int* tab, int n){
    for(int i=0; i<n;i++){
        cin >> tab[i];
    }
}
 
int sommeTab(int* tab, int n){
    int sum=0;
    for (int i=0; i<n; i++){
        sum+=tab[i];
    }
    return sum;
}
 
void desallocationTab(int* tab){
    delete[] tab;
}
 
Je regarde ensuite la correction et je m'aperçois que, bien que mon programme compile correctement, j'ai quelques erreurs à corriger :  
 
Pour la premiere et la derniere fonction (allouer et desallouer) , le type en paramètre n'est pas int*, mais doit etre int*&  
Pour la deuxieme fonction, le pointeur tab doit etre constant (int* const tab)
Pour la troisieme fonction, la variable et le pointeur doivent etre constants (const int* const tab)
Enfin, j'aurais du ajouter dans ma fonction desallocationTab l'instruction tab=0;
 
Quel est l'utilité des ces références passés en paramètre, ainsi que le caractère constant de ma variable ou de mon pointeur? Et l'utilité d'écrire tab=0; ?
Encore mieux, quelqu'un pourrait-il me décrire par un exemple les erreurs qui pourraient arriver lors des appels de fonctions si je les écris sans références ni constantes?
 
Voila merci par avance à ceux qui pourront m'aider!!
 
Mikizi
PS: C'est mon premier message sur forum hardware donc si vous avez des remarques, hésitez pas!

mood
Publicité
Posté le 18-08-2011 à 18:33:33  profilanswer
 

n°2096142
Joel F
Real men use unique_ptr
Posté le 18-08-2011 à 21:18:46  profilanswer
 

et si tu utilisais plutot std::vector au lieu de faire du C-- ?
 
plus prosaiquement, il faut passer ton int* par reference dans 1/ pour pouvoir modifier sa valeur.
la constitude dans 2/ et 3/ sont des infos pour renseigner le contrat de ta fonction rien de plus.
 
et si tu utilisais plutot std::vector au lieu de faire du C-- ?

n°2096150
mikizi
Posté le 18-08-2011 à 21:37:20  profilanswer
 

C-- ça veut dire de la mauvaise programmation en C++? Je fais cela tout simplement parce que je fais les choses dans l'ordre du cours que je suis : d'abord les références et les pointeurs, puis les classes, héritage etc. Par conséquent , je n'ai pas encore vu ce que signifie std::vector.  
 
Qu'est ce que tu veux dire par "renseigner le contrat de ta fonction" ?
 
En tout cas merci pour ta première réponse.
 
Si je

n°2096153
Anonymouse
Posté le 18-08-2011 à 21:51:03  profilanswer
 

J'y connais pas grand chose en C++ mais.
 
Pour la premiere et la derniere fonction (allouer et desallouer) , le type en paramètre n'est pas int*, mais doit etre int*&
 
Pour allouer il faut passer un pointeur de pointeur ou une référence sur un pointeur car en C++ les paramètres sont passés par copie. Dans l'exemple ci-dessous, lorsque l'on rentre dans la fonction allocationTab, le pointeur test est recopié. L'adresse résultant de l'allocation mémoire faite dans la fonction allocationTab est stockée dans une copie du pointeur. Par conséquence, lorsque l'on sort de la fonction test pointe toujours sur NULL.

Code :
  1. int * test = NULL;
  2. allocationTab(test, 10);


Pour la dés-allocation le même problème se pose de manière moins accrue. En effet si on passe un int * on peut faire un free dessus sans problème. Néanmoins on ne peut modifier l'adresse contenue dans le pointeur qui, à la sortie de la fonction contient une adresse invalide. Une bonne pratique consiste à mettre à NULL tout pointeur dont on vient de libérer la mémoire pour éviter d’accéder par erreur à une partie de la mémoire qui est invalide.
 
La seule différence entre un pointeur et une référence vient du fait qu'une référence ne peut être mise à NULL.
 
Pour la deuxieme fonction, le pointeur tab doit etre constant (int* const tab)
Pour la troisieme fonction, la variable et le pointeur doivent etre constants (const int* const tab)

 
Le mot clé const peut s'appliquer soit au pointeur, soit à la mémoire pointée. Il signifie que l'on peut accéder uniquement en lecture a la variable ainsi protégée.
 
Exemple

Code :
  1. int y = 10, x = 50;
  2. int const * cVar = &y;
  3. int * const cPt = &y;
  4. // === Pointeur protégé ===
  5. *cPt = 50; // Je modifie y : Autorisé;
  6. cPt = &x;   // J'essaye de faire pointer mon pteur sur x: interdit
  7. // === Mémoire protégée ===
  8. *cVar = 50; // Je cherche à modifier y : Interdit;
  9. cVar = &x;   // Je fais pointer mon pteur sur x: autorisé
  10. // === Mémoire et Pointeur protégé ===
  11. int const * const cPtVar = &y;
  12. *cPtVar = 50; // Je cherche à modifier y : Interdit;
  13. cPtVar = &x;   // J'essaye de faire pointer mon pteur sur x: interdit


 
Pour en revenir à ta question. Le fait de mettre en const le pointeur tab dans la 2eme fonction a peu d'importance. Ca sert juste à éviter que par erreur de frappe ou d'étourderie tu déréférence le pointeur au sein de la fonction mais ca n'impacte que le créateur de la fonction.  
 
De même pour la 3eme fonction le fait de protéger le pointeur tab a peu d'importance. Par contre le fait de protéger la mémoire pointée par tab est important. Tu offres ainsi un contrat à tout utilisateur de ta fonction établissant le fait que tu ne modifies pas ce qui est passé en paramètre dans ta fonction. Il faut toujours déclarer comme const les variables passées par référence ou pointeur que tu ne modifies pas dans une méthode/fonction. Ainsi si quelqu'un récupère un pointeur pointant sur une zone const il pourra quand même utiliser ta fonction.


Message édité par Anonymouse le 18-08-2011 à 21:53:29
n°2096160
Joel F
Real men use unique_ptr
Posté le 18-08-2011 à 22:05:57  profilanswer
 

mikizi a écrit :

C-- ça veut dire de la mauvaise programmation en C++? Je fais cela tout simplement parce que je fais les choses dans l'ordre du cours que je suis : d'abord les références et les pointeurs, puis les classes, héritage etc. Par conséquent , je n'ai pas encore vu ce que signifie std::vector.  
 
Qu'est ce que tu veux dire par "renseigner le contrat de ta fonction" ?
 
En tout cas merci pour ta première réponse.
 
Si je


 
c'est quoi ce cours nul ? Quelle fac que j'y envois pas mes gamins ?

n°2096168
gilou
Modérateur
Modzilla
Posté le 18-08-2011 à 22:18:34  profilanswer
 

mikizi a écrit :

C-- ça veut dire de la mauvaise programmation en C++? Je fais cela tout simplement parce que je fais les choses dans l'ordre du cours que je suis : d'abord les références et les pointeurs, puis les classes, héritage etc. Par conséquent , je n'ai pas encore vu ce que signifie std::vector.

C'est pourtant une des premières choses que tu devrais apprendre, si c'était un vrai cours de C++, et pas un cours de C a la sauce objet pour faire illusion.
Payes toi le Koenig et Moo, Accelerated C++, si tu veux un exemple de bouquin qui t'apprenne correctement le C++. std::vector est une des premières notions qui y est exposée (au chapitre 3, après std::string) tandis que les arrays n'apparaissent que 100 pages plus loin (au chapitre 10).
 
Bon sinon, une réponse rapide pour l'utilité des références:

Code :
  1. void allocationTab (int* tab, int n){
  2.     tab=new int[n];
  3. }


En C++, tu regardes quels paramètres ont leur valeur (volontairement) modifiée par la fonction, la modification de cette valeur étant une des raisons de création de ta fonction. Ces paramètre la, tu les passes par référence (tu fais suivre le type du paramètre par & dans la déclaration et définition de ta fonction). Sans cela, en sortie de la fonction, les valeurs modifiées dans la fonction sont oubliées, et tu vas te retrouver avec la même valeur qu'a l'entrée de la fonction.
Ici c'est simple:
La fonction 1) crée un array d'entiers de taille n et 2) met l'adresse de la première case de l'array dans tab.
Donc des deux paramètres, tab est modifié par la fonction, et c'est volontaire, on veut la valeur modifiée de tab après avoir appelé la fonction, tandis que n est juste une valeur utilisée par la fonction.
On va donc faire:

Code :
  1. void allocationTab (int*& tab, int n){
  2.     tab=new int[n];
  3. }

pour que ça marche.
 
Si tu as compris cela, tu as compris pourquoi on doit faire aussi:

Code :
  1. void desallocationTab(int*& tab){
  2.    ....
  3. }

La suite des explications pour cette fonction la est  la fin de ce post.
 
Passons maintenant à celle ci:

Code :
  1. void remplissageTab(int* tab, int n){
  2.     for(int i=0; i<n;i++){
  3.         cin >> tab[i];
  4.     }
  5. }

Cette fonction marche, et c'est une fonction similaire qu'on utiliserait si on faisait du C (avec autre chose que cin qui n'existe pas en C). Mais on est en C++, et C++, c'est ++ puissant!
Cette fonction, elle remplit les cases de l'array. On ne veut pas changer l'array lui même entre l'entrée et la sortie, mais juste changer les valeurs des cases de l'array.
Déjà, on ne passe pas l'array en paramètre ce qui va permettre d'être sur d'avoir en sortie le même qu'en entrée. Mais comment être sur dans la fonction, je vais pas changer (involontairement) cet array pour un autre, quitte à perdre cette modification en sortant de la fonction? comment être sur qu'on va toujours utiliser notre array et pas un autre?
C++ permet de s'en assurer grâce au mot clé const, qui permet d'être sur que la valeur va être constante, et ne pas changer.
int* const tab va dire au compilateur que tab est constant et ne peut changer. On va alors être sur que tab, dans la fonction, va toujours être notre array de départ, parce que si on veut modifier tab, le compilateur va éructer une bordée d'insultes et refuser de compiler le programme jusqu'au bout.
Donc ça,

Code :
  1. void remplissageTab(int* tab, int n){
  2.     for(int i=0; i<n;i++){
  3.         cin >> tab[i];
  4.     }
  5. }


ça marchait, mais ceci:

Code :
  1. void remplissageTab(int* const tab, int n){
  2.     for(int i=0; i<n;i++){
  3.         cin >> tab[i];
  4.     }
  5. }

va nous garantir que tab va toujours être notre tableau d'entrée au cours de la fonction. C'est ce que JoelF a appelé un contrat de la fonction remplissageTab.
 
Passons à la dernière:

Code :
  1. int sommeTab(int* tab, int n){
  2.     int sum=0;
  3.     for (int i=0; i<n; i++){
  4.         sum+=tab[i];
  5.     }
  6.     return sum;
  7. }


Ici, on veut faire un calcul avec les valeurs des cases de tab. On ne veut ni que tab, ni que les valeurs des cases de tab ne soient modifiées par la fonction.
Déjà, pour que tab ne soit pas modifié, on l'a vu, on va écrire: int* const tab
Pour que les entiers dans les cases de tab ne soient pas modifiés, on va aussi les déclarer constants: const int* const tab
D'ou la forme finale

Code :
  1. int sommeTab(const int* const tab, int n){
  2.     int sum=0;
  3.     for (int i=0; i<n; i++){
  4.         sum+=tab[i];
  5.     }
  6.     return sum;
  7. }


Point final:
quand on fait tab=new int[n]; dans allocationTab, on fait, comme écrit plus haut:
La fonction 1) crée un array d'entiers de taille n et 2) met l'adresse de la première case de l'array dans tab.  
Quand on fait delete[] tab; dans desallocationTab, on ne fait que faire l'inverse du 1): on détruit l'array d'entier de taille n.
Reste a faire l'opération inverse du 2: enlever l'adresse, et mettre une adresse qui ne soit plus celle d'une case contenant un entier (ni de rien du tout). L'adresse de valeur 0 est spécialement la pour cela, aucun objet ne peut avoir pour adresse 0, et on va l'utiliser, en faisant: tab = 0; On est maintenant sur que tab ne pointe plus vers un tableau d'entiers. Et si plus tard, quelqu'un veut savoir si tab pointe vers quelque chose,  en voyant que tab vaut 0, il va savoir que ce n'est pas le cas.
d'ou la version finale:

Code :
  1. void desallocationTab(int*& tab){
  2.     delete[] tab;
  3.     tab = 0;
  4. }


A+,


Message édité par gilou le 19-08-2011 à 19:16:54

---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
n°2096397
mikizi
Posté le 19-08-2011 à 15:47:14  profilanswer
 

Un grand merci à tous ceux qui ont pris la peine de répondre (réponses de qualité!), vous avez un peu éclairé ma lanterne je vais m'exercer!


Aller à :
Ajouter une réponse
  FORUM HardWare.fr
  Programmation
  C++

  Utilité des références et des const

 

Sujets relatifs
[eclipse/php] des références croiséesproblème de conversion unsigned const char* => const char*
[VBA Excel] Références manquantes [Résolu]Comment supprimer un "warning" lié au mot-clef "const"
select : deux références sur une même tableInitialiser des const dans une class, possible?
question sur les référencessyntaxe : operator T&() const;
Cas où les références remplacent mal les pointeurs ?Utilité classe CustomerOrderStatus Enum
Plus de sujets relatifs à : Utilité des références et des const


Copyright © 1997-2022 Hardware.fr SARL (Signaler un contenu illicite / Données personnelles) / Groupe LDLC / Shop HFR