Partage
  • Partager sur Facebook
  • Partager sur Twitter

pointeur double passé à une fonction

C++

Sujet résolu
    19 juin 2018 à 18:31:17

    Bonjour OC,

    j'ai une question que j'essaie depuis assez longtemps d'éclarcir :
    celle des doubles pointeurs.
    Il se trouve que sur un exemple récent que j'ai lu, on a une variable de type Personne**  et cette variable s'appelle "passagers".
    Or un moment cette variable "passagers" de type Personne** est transmise à une fonction pour être modifié car un passager est ajouté.
    add(passagers, ....);
    Quel est l'intérêt réel du pointeur double ?
    Pourquoi ne pas faire un add sur un simple tableau de Personne , autrement dit un pointeur simple (pas double) n'aurait-il pas pu suffire ?
    Merci pour vos explications
    • Partager sur Facebook
    • Partager sur Twitter
      19 juin 2018 à 18:34:58

      C'est du C. Et pour modifier un tableau en C, il faut faire comme ça. Cf un cours de C si tu veux apprendre le C.
      • Partager sur Facebook
      • Partager sur Twitter
        19 juin 2018 à 18:56:00

        J'ai déjà cherché dans ce que tu dis, d'où ma question. Le fait de me dire Cf ne m'aide pas car je fais es recherches avant de poster.

        Qu'il s'agisse de C, c'est ok.

        Mais je cherche à comprendre pourquoi utiliser un pointeur double, ne pouvait-on pas se contenter d'un pointeur simple (ce qui déterminerait donc un tableau) que l'on aurait pu ensuite passer par référence pour le modifier.

        Ainsi add aurait pu, selon moi, avoir un prototype du genre :

        add(Personne* & passagers, ...)

        Cela permettrait ainsi la modification du tableau passagers.

        Donc pourquoi passer par un pointeur double ?

        Merci

        -
        Edité par pseudo-simple 19 juin 2018 à 19:06:55

        • Partager sur Facebook
        • Partager sur Twitter
          19 juin 2018 à 19:02:59

          Parce que en C, il n'existe pas de reference. Donc ils utilisent des pointeurs a la place.

          On passe notre temps a dire que le C et le C++ sont des langages différents. On ne peut pas simplement copier-coller le code. (Ca compilera, puisque le C++ conserve la rétro-compatibilité, mais ça ne sera pas du C++ valide en général).

          C'est le sens de mon "Cf". Tu poses des questions de C. Ces questions n'ont pas de sens en C++. C'est aussi simple que ca.

          • Partager sur Facebook
          • Partager sur Twitter
            20 juin 2018 à 9:24:27

            Bonjour OC

            merci gbdivers, c'est compris.

            Une question encore et je passe le sujet en résolu :

            J'ai besoin de savoir si en C++ ou C, est-ce que si on passe un pointeur simple non constant ou un tableau (pour les types primitifs, je connais la réponse) en argument d'une fonction  et que localement à cette fonction , on modifie l'adresse ou l'objet pointé pour le pointeur ou le contenu du tableau, est-ce que en sortie de cette fonction, les modifications sont conservées ?

            En C++, par exemple, avec les références, je sais que les modifications sont conservées pour la plupart des variables. Mais c'est le cas des pointeurs qui m'intéresse (et aussi celui des tableaux qui sont des pointeurs sur le premier élément ).

            En C, les références n'existent même pas, donc la question ne se pose pas.

            Je pose cette question car si les modifications ne sont pas conservées alors cela pourrait , à mes yeux, justifier en C, que l'on ait besoin dans le cas de ma question initiale de ce fil, d'utiliser un pointeur (qui serait en C, une sort "d'équivalent d'une référence sur un pointeur qui autoriserait donc sa modification localement).

            Merci

            • Partager sur Facebook
            • Partager sur Twitter
              20 juin 2018 à 10:54:45

              Pour répondre à ta première question. Pour modifier un tableau dans une fonction pas besoin de passer un pointeur double. un pointeur simple sur le tableau est suffisant.

              Le pointeur double peux être utilisé quand on veux modifier un pointeur passé en paramètre à une fonction, on envoie l'adresse du pointeur à la fonction, il faut donc que le type du paramètre soit un pointeur double. 

              • Partager sur Facebook
              • Partager sur Twitter
                20 juin 2018 à 12:14:10

                YES, man a écrit:

                En C++, par exemple, avec les références, je sais que les modifications sont conservées pour la plupart des variables.

                Non, ce n'est pas correct de dire ça. Une référence en elle même n'est pas modifiable, donc ça n'a pas de sens dire qu'une modification de la référence elle même est conservé en dehors de la fonction.

                Et là, normalement, tu ne comprends plus rien. Parce que tu n'as pas compris la différence entre ce qui "pointe" et ce qui est "pointé".

                On reprend les bases...

                Les références et les pointeurs (et les itérateurs et pleins d'autres concepts dérivés) sont ce qu'on appelle des indirections. Une indirection est un objet (en mémoire) qui redirige vers un autre objet (en mémoire).

                Une "variable" est un nom qu'on donne dans le code a un objet, pour pouvoir l'utiliser. Par exemple :

                int i { 123 };
                int& p = i;

                Dans ce code, "i" est une variable (dans le code) qui correspond a un objet de type "int" en mémoire. "p" est une variable aussi, qui correspond a un objet de type "indirection sur un int" (une référence plus précisément).

                Autre exemple :

                int* p = new int { 123 };

                Dans ce code C, "p" est une variable de type "indirection sur un int" aussi (mais plus précisément un pointeur), qui pointe sur un objet de type "int". Aucune variable ne pointe directement sur cet objet pointé. C'est l'un des très gros risque en C et en C++ : perdre l'indirection sur un objet et donc ne plus pouvoir accéder à cet objet (en particulier pour le supprimer ! Et donc fuite mémoire !). Par exemple :

                void foo() {
                    int* p = new int { 123 };
                }

                Ce code n'est pas valide, ni en C, ni en C++ (fuite mémoire).

                Un autre exemple :

                void foo() {
                    int* p = new int { 123 };
                    delete p;
                }
                

                Ce code est valide en C, mais pas en C++ ! (Fuite mémoire en cas d'exception).

                Le même code, valide en C++ :

                void foo() {
                    auto p = std::make_unique<int>(123);
                }
                

                Voila pour la petite digression sur les pointeurs en C++.

                ------------------------------

                On revient sur les indirections. Dans ce code :

                int i { 123 };
                int& p = i;
                int* q = &i;

                "p" et "q" sont des indirections. Mais une grosse difference entre ces 2 variables est que l'une est une référence, l'autre est un pointeur. Une variable de type pointeur est modifiable, pas une variable de type référence. C'est a dire, on peut écrire :

                int j { 456 };
                q = &j; // q pointe sur j maintenant

                Mais on ne peut pas modifier "p" pour qu'elle pointe sur "j". Écrire :

                p = j;

                Ne fais pas pointer "p" sur "j", mais modifie "i". C'est pour cela qu'on dit qu'une référence n'est pas modifiable ! (Dans le sens où l'on ne modifie pas la variable de type référence, mais l'objet pointé).

                ------------------------------

                On passe maintenant aux fonctions. Quand on écrit, en C :

                void foo(int* p) {       // (I)
                    ...
                }
                
                int* q = new int(123);   // (II)
                foo(q);

                Il faut bien comprendre quels sont les objets et les variables qui sont créés. A la ligne (II), 2 objets sont crées :

                • un objet de type "int" en mémoire, qui n'a aucune variable correspondante.
                • un objet de type "pointeur sur int", qui correspond a la variable "q".

                A la ligne (I), un troisième objet est créé :

                • un objet de type "pointeur sur int", qui correspond à la variable "p", et qui est une copie de la variable "q" !

                C'est a dire que "p" et "q" sont deux variables de type "pointeur sur int" et pointent toutes les 2 sur le même objet en mémoire, mais ce sont bien 2 variables différentes !

                Comme un pointeur est modifiable, on peut faire pointer "p" sur un autre objet. Par exemple :

                void foo(int* p) {       // (I)
                    p = new int(456);    // (III)
                }
                
                int* q = new int(123);   // (II)
                foo(q);

                Dans ce code, à la ligne (III), la variable "p" est modifiée pour pointer sur un nouvel objet. La variable "q" n'est pas modifiée et pointe toujours sur le même objet.

                Pour une référence, on a vu que ce n'est pas modifiable. Donc il n'est pas possible d'écrire le même code (c'est a dire faire pointer "p" sur un autre objet) avec une référence :

                void foo(int& p) {       // (I)
                    p = ...;             // pas possible
                }
                
                int* q = new int(123);   // (II)
                foo(q);

                (Ce code modifie l'objet pointé, pas "p").

                Si on veut modifier la variable "q" en C, il faut passer un pointeur sur la variable "q", pas un pointeur sur l'objet pointé par "q". Donc un pointeur sur un pointeur sur int. D'où la notion de double pointeur en C.

                void foo(int** p) {       // (I)
                    *p = new int(456);    // (III)
                }
                
                int* q = new int(123);   // (II)
                foo(&q);

                Dans ce code, la variable "p" est maintenant un pointeur sur "q". Et modifier l'objet pointé par "p" revient donc à modifier la variable "q".

                Pour les références, on peut faire la même chose :

                void foo(int*& p) {       // (I)
                    p = new int(456);    // (III)
                }
                
                int* q = new int(123);   // (II)
                foo(q);

                mais par principe, on ne mélange pas des pointeurs et de références. C'est très moche visuellement. Au pire, on peut s'amuser à créer des alias de types, mais on évite. Cependant, ca peut arriver, par exemple avec une fonction template :

                template<typename T>
                void foo(T& p) {       
                    p = ...;     // on modifie q ici
                }
                
                int* q = new int(123);
                foo(q);
                

                Mais c'est hors sujet.

                En C++, on va éviter de manipuler directement des pointeurs (à cause du problème signalé plus haut, sur la gestion de la mémoire) et on va préférer manipuler des classes qui s'occupent de gérer correctement les pointeurs (on appelle ca des "capsules RAII"). Et si on veut modifier un tel objet, on le passe par référence. (Donc jamais de référence sur référence, ou de pointeur sur pointeur). Par exemple, le code valide en C++ :

                void foo(std::unique_ptr<int>& p) {  
                    p = std::make_unique<int>(456);
                }
                
                auto q = std::make_unique<int>(123);
                foo(q);

                Ce code est la seule façon de gérer correctement les pointeurs en C++ !

                Note : si c'est un tableau (donc un pointeur en C), le code valide en C++ sera d'utiliser la classe RAII correspondant à un tableau :

                void foo(std::vector<int>& p) {  
                    p = std::vector<int>{ 4, 5, 6 };
                }
                
                std::vector<int> q = { 1, 2, 3 };
                foo(q);

                ------------------------------

                J'espère qu'avec toutes ces explications, tu comprends le problème qui se pose avec les double pointeurs.

                Note : en termes d'apprentissage, ce que je viens de faire est très mauvais ! J'ai passé plus de temps à t'expliquer les différences entre le C et le C++, plutôt que de t'expliquer comment faire les choses correctement en C++. Et apprendre 2 langages en même temps (le C et le C++) est forcement plus compliqué (risque de confusion).

                C'est bien pour cela qu'on passe notre temps à dire qu'il ne faut pas apprendre le C++ comme si c'était du C. Et se focaliser en priorité sur le C++ moderne (c'est à dire les bonnes pratiques actuelles du C++).

                Tant que tu n'auras pas accepté ça, tu auras toujours autant de mal à comprendre et à avancer dans ton apprentissage.

                -
                Edité par gbdivers 20 juin 2018 à 13:13:04

                • Partager sur Facebook
                • Partager sur Twitter
                  20 juin 2018 à 12:15:43

                  Salut,

                  Le plus souvent, un double pointeur n'est ni plus ni moins qu'un tableau à 2 dimensions, puisque les tableaux C se dégradent automatiquement en pointeur.

                  Quand aux pointeurs passé en paramètre:

                  void foo(int* ptr);    // on peut modifier l'adresse et l'objet pointé
                  
                  void foo(int const* ptr);    // on ne peut modifier que l'objet pointé
                                               // (pointeur constant)
                  
                  void foo(int const* const ptr); // on ne peut rien modifier
                                                  // (pointeur constant sur un objet constant)

                  Mais comme te l'a indiqué gbdivers, sauf raisons valables, on évite les pointeurs en C++.

                  • Partager sur Facebook
                  • Partager sur Twitter
                    20 juin 2018 à 12:44:46

                    Arrêtez de dire que les pointeurs sont des tableaux. Un pointeur est une variable sensée contenir une adresse et rien d'autre. Un tableau est une suite contiguë d'objets de type x.
                    • Partager sur Facebook
                    • Partager sur Twitter
                      20 juin 2018 à 12:54:52

                      On va pas chipoter sur les termes. Les tableaus du C de type T[] sont implicitement castable en pointeur T*. La distinction entre les 2 est de l'ordre du détail.
                      • Partager sur Facebook
                      • Partager sur Twitter
                        20 juin 2018 à 23:13:47

                        Merci beaucoup à chacun (Deedoolith, roulouds  et gbdivers ...) et en particulier à gbdivers pour son post très instructif que j'ai relu plusieurs fois. Beaucoup de choses s'éclaircissent grâce à vos messages.

                        Juste une question encore :

                        quel est l'équivalent C du dernier code de gbdivers de façon propre si je veux pouvoir faire des modifications sur un tableau, c'est-à-dire l'équivalent propre en c du code suivant

                        void foo(std::vector<int>& p) { 
                            p = std::vector<int>{ 4, 5, 6 };
                        }
                         
                        std::vector<int> q = { 1, 2, 3 };
                        foo(q);


                        Est-ce que c'est :

                        void foo(int t|]* p) { 
                            p ={ 4, 5, 6 };
                        }
                         
                        int q[] = { 1, 2, 3 };
                        foo(q);


                        alors que si j'ai compris ce que dit gbdivers, le code suivant  en C

                        void foo(int t|]& p, size) { 
                            p ={ 4, 5, 6 };
                        }
                         
                        int q[] = { 1, 2, 3 };
                        foo(q,size);

                        ne modifierait pas le tableau q en sortie de foo.

                        Merci à tous

                        -
                        Edité par pseudo-simple 20 juin 2018 à 23:14:27

                        • Partager sur Facebook
                        • Partager sur Twitter
                          20 juin 2018 à 23:38:40

                          Pas de reference en C. Et []* n'est pas valide non plus. Donc en C, c'est des doubles pointeurs.
                          • Partager sur Facebook
                          • Partager sur Twitter

                          pointeur double passé à une fonction

                          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                          • Editeur
                          • Markdown