IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Question basique sur les pointeurs


Sujet :

C++

  1. #1
    Membre régulier
    Profil pro
    Inscrit en
    Décembre 2007
    Messages
    348
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2007
    Messages : 348
    Points : 103
    Points
    103
    Par défaut Question basique sur les pointeurs
    Bonjour à tous,

    Question très basique que je souhaite poser sur les pointeurs en C++:

    Je comprends très bien que cette fonction n'échange pas les valeurs des deux int:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void Echange (int a, int b)
    {
        int c;
        c=a;
        a=b;
        b=c;
    }
    Car d'après ce que j'ai compris (corrigez-moi si je me trompe), les paramètres sont des copies des variables.

    En revanche, je reste convaincu que celle-ci devait marcher:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void EchangeParPointeurMarchePas (int* a, int* b)
    {
        int* c;
        c=a;
        a=b;
        b=c;
    }
    Alors que celle-ci marche:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void EchangeParPointeur (int* a, int* b)
    {
        int c;
        c=*a;
        *a=*b;
        *b=c;
    }
    Selon moi, la deuxième méthode devrait marcher puisque je donne à la méthode deux pointeurs sur int. Je donne dans la méthode à chacun des deux pointeurs l'adresse de l'autre.

    Pourquoi EchangeParPointeur(&a,&b) marche et pas EchangeParPointeurMarchePas(&a,&b)?

    Merci d'avance d'éclairer ma lanterne.

  2. #2
    Invité
    Invité(e)
    Par défaut
    Bonjour,

    Comme tu l'as si bien dit :
    les paramètres sont des copies des variables.
    et ce, quelque soit le type de ces paramètres. Et cela inclus bien évidement les pointeurs

  3. #3
    Membre régulier
    Profil pro
    Inscrit en
    Décembre 2007
    Messages
    348
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2007
    Messages : 348
    Points : 103
    Points
    103
    Par défaut
    Je t'avoue que je m'attendais mot pour mot à cette réponse, mais ça me laisse dans le brouillard...

    Edit: Je crois que je commence à comprendre... dans celle qui marche on intervient directement sur le contenu des cases mémoires, qui elles ne sont pas copiées (ce qui est impossible puisqu'elles sont "physiques"), peu importe que le chemin qui guide vers elles soit dupliqué.

    Alors que moi j'essaie de "croiser*" des pointeurs, mais que ce sont des copies de ces pointeurs qui sont "croisées".

    J'espère que je me fais comprendre, si c'est le cas, c'est ça que tu essayais de me dire?

    *croiser: faire en sorte que le pointeur 1 prenne l'adresse du pointeur 2 et vice versa.

  4. #4
    Membre habitué
    Profil pro
    Inscrit en
    Juin 2013
    Messages
    294
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2013
    Messages : 294
    Points : 128
    Points
    128
    Par défaut
    Bonjour,

    Dans la fonction void EchangeParPointeurMarchePas (int* a, int* b),

    Les adresses de a et b ont bien été échangées (confère le fichier test.cpp).

    Seulement les modifications ne sont pas prises en compte au niveau global.

    D'ou l'explication de la copie des pointeurs juste pour le cadre de la fonction.
    Et ensuite la destructions de ces copies dès la fin de la fonction.

    Il me semble quand fait un pointeur occupe une case mémoire pour stocker l'adresse. C'est cette dernière qui est détruite.

    Cordialement.
    Fichiers attachés Fichiers attachés

  5. #5
    Membre régulier
    Profil pro
    Inscrit en
    Décembre 2007
    Messages
    348
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2007
    Messages : 348
    Points : 103
    Points
    103
    Par défaut
    Ca me paraît plus clair. Merci!

  6. #6
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    En fait, pour fixer correctement les choses, tu dois "simplement" comprendre ce qu'est réellement un pointeur.

    Un pointeur n'est jamais qu'une valeur numérique entière (généralement non signée) "comme les autres", à ceci près que la valeur représentée correspond à une adresse mémoire.

    A partir de là, tout s'enchaine et s'emboîte parfaitement

    Lorsque tu déclares un pointeur sous la forme de
    tu ne fais que déclarer une variable comme une autre à ceci près que la valeur qu'elle contient représente l'adresse mémoire à laquelle tu es sensé trouvé un élément du type concerné (un int, en l'occurrence )

    Si tu rajoutes à cela que le passage de paramètre se fait par copie (comprends : par création d'une nouvelle variable qui prend la même valeur que celle passée en paramètre lors de l'appel de la fonction), tout devient clair

    Mettons que l'on parte d'un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    int main(){
        int i = 12;
        int j = 24;
        int * ptrI= &i; // ptrI contient l'adresse à laquelle on trouve la variable i
        int * ptrJ= &j; // ptrJ contient l'adresse à laquelle on trouve la variable j
        std::cout<< "Adresse de i : "<<ptrI<<" et sa valeur "<<i <<std::endl
                 << "Adresse de j : "<<ptrJ<<" et sa valeur "<<j <<std::endl;
    /* note que j'aurais tout aussi bien pu écrire un code proche de
        std::cout<< "Adresse de i : "<<&i<<" et sa valeur "<<i <<std::endl
                 << "Adresse de j : "<<&j<<" et sa valeur "<<j <<std::endl;
        ou meme proche de
        std::cout<< "Adresse de i : "<<ptrI<<" et sa valeur "<<*ptrI <<std::endl
                 << "Adresse de j : "<<ptrJ<<" et sa valeur "<<*ptrJ <<std::endl;
        L'affichage aurait été strictement identique :D
     */
        return 0;
    }
    Il occasionnera un affichage proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Adresse de i : 0x12345678 et sa valeur : 12
    Adresse de j : 0xFEDCBA98 et sa valeur : 24
    Les valeurs réellement affichées risquent de changer énormément, mais je te propose de garder celles-ci comme "valeur de référence" pour la suite

    Note que ce code déclare 4 variables totalement distinctes (que l'on trouve donc quatre adresses mémoire différentes):
    • i qui est de type int
    • j qui est de type int
    • ptrI qui est de type "pointeur sur int" (qui contient l'adresse à laquelle on est sensé trouver un élémet de type int)
    • ptrJ qui est de type "pointeur sur int"(qui contient l'adresse à laquelle on est sensé trouver un élémet de type int)
    Les valeurs de ces variables sont clairement définies :
    • i vaut (selon l'exemple) 12
    • j vaut (selon l'exemple) 24
    • ptrI vaut (selon l'exemple) 0x12345678
    • ptrJ vaut (selon l'exemple) 0xFEDCBA98
    (note que l'on pourrait même s'amuser à récupérer l'adresse de ptrI et de ptrJ pour bien te montrer qu'il s'agit de variable bien distinctes )

    Commençons par nous intéresser à ce qui est fait dans la fonction
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void EchangeParPointeur (int* a, int* b)
    {
        int c;
        c=*a;
        *a=*b;
        *b=c;
    }
    Lorsque l'on va appeler cette fonction sous la forme de EchangeParPointeur (ptrI, ptrJ);, nous créerons les variables a et b qui sont de type "pointeur sur int", qui seront propre à cette fonction, et dont les valeurs seront celles des variables transmises en paramètre.

    a aura donc la valeur 0x12345678 et b la valeur 0xFEDCBA98.

    Cela n'aura pas énormément d'importance ici, mais il est important de comprendre que a, b, ptrI et ptrJ sont au final quatre variables totalement distinctes : elles vivent toutes les quatre leur vie sans interférer d'aucune manière avec les autres!

    Bon, d'accord, il se fait que la valeur de a correspond à celle de ptrI et que la valeur de b correspond à la valeur de ptrJ, mais ce n'est qu'une "coïncidence voulue".

    Mais surtout CE N'EST PAS PARCE QUE L'ON CHANGERA LA VALEUR DE a QUE L'ON CHANGERA LA VALEUR DE ptrI (resp: CE N'EST PAS PARCE QUE L'ON CHANGERA LA VALEUR DE b QUE L'ON CHANGERA LA VALEUR DE ptrJ)


    Ce code fonctionne parce que voici à peut près ce qui se passe:
    1. int c; déclare c comme une variable de type int
    2. c=*a; signifie " affecte à c la valeur qui se trouve à l'adresse représentée par a (autrement dit la valeur qui se trouve à l'adresse 0x12345678)". Autrement dit, affecte à c la valeur 12 (vu que c'est cette valeur qui se trouve à l'adresse 0x12345678
    3. *a=*b;singifie " affecte la valeur qui se trouve à l'adresse représentée par b (autrement dit 0xFEDCBA98) à ce qui se trouve à l'adresse représentée par a (autrement dit 0x12345678". Autrement dit: affecte la valeur 24 (vu que c'est ce qui se trouve à l'adresse 0xFEDCBA98) à l'adresse 0x12345678
    4. *b=c;signifie "affecte la valeur de c à l'adresse représentée par b (autrement dit 0xFEDCBA98)", ou, si tu préfères, affecte la valeur 12 à la case mémoire 0xFEDCBA98
    Une fois que l'on sort de la fonction et que l'on retourne dans main, i et j n'ont pas bougé : i se trouve toujours à l'adresse 0x12345678 et j se trouve toujours à l'adresse 0xFEDCBA98.

    Mais, comme on a changé la valeur de ces deux cases mémoire, on remarque que i vaut maintenant 24 et j vaut maintenant 12.

    Regardons maintenant ce qui se passe du coté de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void EchangeParPointeurMarchePas (int* a, int* b)
    {
        int* c;
        c=a;
        a=b;
        b=c;
    }
    Une même cause ayant les mêmes effets, je peux me citer:
    Citation Envoyé par moi-même
    Lorsque l'on va appeler cette fonction sous la forme de EchangeParPointeur (ptrI, ptrJ); EchangeParPointeurMarchePas (ptrI, ptrJ);, nous créerons les variables a et b qui sont de type "pointeur sur int", qui seront propre à cette fonction, et dont les valeurs seront celles des variables transmises en paramètre.

    a aura donc la valeur 0x12345678 et b la valeur 0xFEDCBA98.

    Cela n'aura pas énormément d'importance ici, mais il est important de comprendre que a, b, ptrI et ptrJ sont au final quatre variables totalement distinctes : elles vivent toutes les quatre leur vie sans interférer d'aucune manière avec les autres!

    Bon, d'accord, il se fait que la valeur de a correspond à celle de ptrI et que la valeur de b correspond à la valeur de ptrJ, mais ce n'est qu'une "coïncidence voulue".

    Mais surtout CE N'EST PAS PARCE QUE L'ON CHANGERA LA VALEUR DE a QUE L'ON CHANGERA LA VALEUR DE ptrI (resp: QUE L'ON CHANGERA LA VALEUR DE b QUE L'ON CHANGERA LA VALEUR DE ptrJ)
    J'ai du corriger quelques passages de ma citation non seulement pour l'adapter au nom de la fonction mais aussi pour supprimer la remarque qui disait que ca n'avait pas d'importance parce qu'il se fait que, pour le coup, l'information prend toute son importance .

    En effet, si EchangeParPointeurMarchePas ne fonctionne pas, c'est uniquement parce que tu agis sur les variable a, b et sur la petite dernière c qui est déclarée dans la fonction, mais que cela n'a strictement aucun impact sur les valeur de ptrI et de ptrJ.

    A la fin de la fonction:
    • la variable a vaut 0xFEDCBA98, qui était à l'origine la valeur de b,
    • la variable b vaut 0x12345678, qui était à l'origine la valeur de a
    et c'est très bien. Mais... les variable ptrI et ptrJ de la fonction main n'ont absolument pas été modifiées dans la foulée.

    PtrI continue donc à valoir 0x12345678 et à pointer vers l'adresse à laquelle se trouve i (qui vaut toujours 12) et ptrJ continue à valoir 0xFEDCBA98 et à pointer vers l'adresse à laquelle se trouve j (qui vaut toujours 24).

    Pour que cela fonctionne, il faudrait transmettre non pas ptrI et ptrJ eux-mêmes mais bien l'adresse auxquelles ils se trouve, sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void echangePointeurDePointeur(int **a, int **b){
        int *c;
        c = *a; //c contient maintenant l'adresse 0x12345678
        *a= *b; //a pointe maintenant vers 0xFEDCBA98 (ce qui signifie que ptrI contient maintenant cette adresse :D)
        *b = c;  //b pointe maintenant vers 0x12345678 (ce qui signifie que ptrI contient maintenant cette adresse :D)
    }
    et que l'on pourrait appeler sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    echangePointeurDePointeur(&ptrI, & ptrJ);
    A la sortie de cette fonction:
    • i n'aurait pas bougé (il se trouverais toujours à l'adresse 0x12345678) et vaudrait toujours 12
    • j n'aurait pas bougé non plus (il se trouverais toujours à l'adresse 0xFEDCBA98 ) et vaudrait toujours 24
    Par contre,
    • la valeur de ptrI correspondrait à l'adresse à laquelle se trouve j et
    • la valeur de ptrJ correspondrait à l'adrese à laquelle se trouve i.

    Et je te laisses deviner ce qui se passera si tu fais afficher "cote à cote" la valeur de i et "ce qui est pointé par ptrI" (respectivement la valeur de j et "ce qui est pointé par ptrJ"
    CEPENDANTj'espère qu'il est visible

    Cette longue réponse avait pour but de répondre à ton interrogation, mais je te conseillerais plus que vivement d'oublier tout cela

    Cette technique est très bonne en C, mais si tu me sors un code pareil dans mon équipe, tu passes par la fenêtre avant meme d'avoir pu dire "quiddich"... Et sans balais volant, j'y veillerai

    Les pointeurs existent bel et bien en C++, mais ils sont la source d'innombrables problèmes dont l'origine est parfois particulièrement difficile à déterminer.

    C++ propose un concept beaucoup plus sécurisant que les pointeurs : les références.

    Ce sont, pour faire simple (et tu n'as pas vraiment à t'inquiéter dans l'immédiat de la manière dont c'est traduit au niveau du processeur ) de véritables alias de la variable référencée.

    En transmettant une référence à une fonction, tu évites la copie de cette variable, et tu t'assures que tout ce qui arrivera à la référence dans la fonction appelée sera répercuté vers la variable d'origine dans la fonction appelante.

    Et, ma foi, si tu veux juste éviter la copie mais que la variable d'origine ne doit pas être modifiée, tu peux déclarer ta référence comme étant constante.

    A ce moment là, tu tomberas de manière systématique sur quelque chose de beaucoup plus têtu que toi pour faire respecter le fait que la variable ne peut pas être modifiée : j'ai nommé le compilateur

    Si je n'avais que deux conseils à te donner, ce seraient:
    1. Laisse bien gentiment la notion de pointeur de coté pour l'instant. Tu en auras besoin plus tard, mais il sera très largement temps de t'intéresser à eux à ce moment là. D'ici là, tu auras évolué dans ta compréhension du langage et tu sauras à quoi tu t'exposes
    2. Utilises les références (éventuellement constantes) chaque fois que tu le peux et n'utilise les pointeurs que lorsque tu n'as vraiment pas le choix.


    Dans le cas présent, tu as le choix d'utiliser des références. C'est donc celui que tu devrais faire
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  7. #7
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Cette longue réponse avait pour but de répondre à ton interrogation, mais je te conseillerais plus que vivement d'oublier tout cela

    Cette technique est très bonne en C, mais si tu me sors un code pareil dans mon équipe, tu passes par la fenêtre avant meme d'avoir pu dire "quiddich"... Et sans balais volant, j'y veillerai

    Les pointeurs existent bel et bien en C++, mais ils sont la source d'innombrables problèmes dont l'origine est parfois particulièrement difficile à déterminer
    C'est un peu violent tout de même. Oui l'usage des pointeurs est délicat et mène à des erreurs lorsqu'on débute. Mais lorsqu'on progresse, on y sera nécessairement confronté, parce que les références ne règlent pas tout et les shared_ptr non plus.Du reste, dès lors qu'on travaille en condition "réelles", on est confronté à du code (ne serais-ce que des bibliothèques tierces) qui en utilisent dans leur API, et à ce moment là il ne fait pas avoir peur des pointeurs car on pas le choix.

    Oui les pointeurs sont sources d'erreur lorsqu'on s'en sert mal. Comme c'est le cas de plein d'autres choses en C++ ! Je trouve que c'est une mauvaise idée d'effrayer les débutants en disant "les pointeurs c'est mal". Il vaut mieux éduquer et dire "les pointeurs peuvent être délicat, il faut apprendre avant de s'en servir".

    Je plusse quand même ta réponse Koala pour l'explication technique
    Find me on github

  8. #8
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Attention, je n'ai pas dit "les pointeurs, c'est mal".

    J'ai dit que "les pointeurs comme tu les utilies, c'est mal", nuance

    et j'ai aussi dit:
    Citation Envoyé par moi-même
    Laisse bien gentiment la notion de pointeur de coté pour l'instant. Tu en auras besoin plus tard, mais il sera très largement temps de t'intéresser à eux à ce moment là. D'ici là, tu auras évolué dans ta compréhension du langage et tu sauras à quoi tu t'exposes
    Alors, oui, les pointeurs sont indispensables dans certaines situations bien précises et il faut donc comprendre ce que c'est.

    Mais nous retombons dans l'éternel débat de l'apprentissage "hold scool" ou de l'apprentissage "moderne" du C++, et le problème qu'il expose démontre une approche hold scool de l'apprentissage.

    Je m'intégre dans la politique générale de la rubrique en essayant de le pousser en douceur et avec le brin d'humour qui me caractérise en direction de l'approche moderne
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Question basique sur les réseaux
    Par abc.xyz dans le forum Architecture
    Réponses: 1
    Dernier message: 22/02/2015, 11h13
  2. Question basique sur les erreurs
    Par JeanNoel53 dans le forum NetBeans
    Réponses: 11
    Dernier message: 02/12/2012, 19h09
  3. Réponses: 7
    Dernier message: 02/01/2008, 14h32
  4. Réponses: 4
    Dernier message: 16/11/2006, 02h10
  5. Question basique sur les tableaux
    Par valanagrid dans le forum C++
    Réponses: 8
    Dernier message: 08/11/2006, 15h47

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo