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 :

Fonction : Même prototype que std::cout


Sujet :

C++

  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Février 2008
    Messages
    57
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 57
    Par défaut Fonction : Même prototype que std::cout
    Bonjour à tous !

    Je souhaite créer une fonction ayant le même prototype que la fonction std::cout.
    Je suis en présence d'un code déjà existant et souhaiterai implémenter un système de "verbose". Pour faire simple, j'espère pouvoir remplacer tous les "std::cout" du code existant par "mafonction".

    Un programme écrit comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    std::cout << "Bonjour" << " Monde !!!" << endl;
    Donnerai...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    mafonction << "Bonjour" << " Monde !!!" << endl;
    Je ne sais pas si c'est possible. Si oui, je ne sais pas comment m'y prendre.
    Quelqu'un peut il me donner une piste sur l'écriture de "mafonction" ?

    Je vous remercie d'avance !

  2. #2
    Membre Expert
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 34
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Par défaut
    std::cout n'est pas une fonction mais un objet. Ce que tu veux faire c'est surcharger l'opérateur <<

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Février 2008
    Messages
    57
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 57
    Par défaut
    D'accord.
    Dans ce cas que dis tu d'un truck comme ca :
    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
    18
     
    ostream& operator << (ostream& flux, const MaClasseVerbose & verbose);
    {
         if( verbose.verboseMessage <= verbose.verboseLevel )
              cout << flux;
     
         return flux;
    }
     
    int main(void)
    {
         //Récupération des parametres et configuration du niveau verbose global
         MaClasseVerbose::setVerboseLevel(1);
     
         //Configuration du niveau de verbose pour le prochain affichage
         MaClasseVerbose::setVerboseMessage(0);
         MaClasseVerbose << "Ceci est un message de niveau 0" << endl;
    }

  4. #4
    Membre Expert Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    Mais dans ce cas, comment faire pour surcharger l'opérateur << et autoriser un nombre indéfini de paramètres et de types ?
    Ce n'est pas une fonction avec un nombre indéfini de paramètre, tu dois la surcharger pour tout les types que tu veux projeter sur un flux.

  5. #5
    Membre averti
    Profil pro
    Inscrit en
    Février 2008
    Messages
    57
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 57
    Par défaut
    Petit croisement de post...

    Voir dernier message svp Merci

  6. #6
    Membre averti
    Profil pro
    Inscrit en
    Février 2008
    Messages
    57
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 57
    Par défaut
    Après avoir fouiner un minimum de documentation, j'en viens à écrire la classe suivante :

    Verbose.h
    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
     
    class Verbose
    {
            private:
                    //Pour "setter" le niveau de verbose du programme
                    static int      _verboseLevel;
                    //Pour "setter" le niveau de verbose de la prochaine chaine à afficher
                    static int      _messageLevel;
     
            public:
                    static void     setVerboseLevel(int verboselevel);
                    static int      getVerboseLevel();
                    static void     setMessageLevel(int messageLevel);
                    static int      getMessageLevel();
                    std::ostream &  operator<<(std::ostream & stream);
    };
    Verbose.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    std::ostream & operator<<(std::ostream & stream, const AudioCoreVerbose & audioCoreVerbose)
    {
            //Si le niveau verbose de la chaine est inférieur à celui du programme
            if(audioCoreVerbose.getMessageLevel() <= audioCoreVerbose.getVerboseLevel())
                    //On affiche la chaine
                    std::cout << stream;
     
            return stream;
    }
    main.cpp
    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
    18
    19
     
    int main(void)
    {
            Verbose::setVerboseLevel(1);
     
            Verbose::setMessageLevel(0);
            //error: expected unqualified-id before ‘<<’ token
            Verbose << "Message niveau 0" << endl;
     
            Verbose::setMessageLevel(1);
            //error: expected unqualified-id before ‘<<’ token
            Verbose << "Message niveau 1" << endl;
     
            Verbose::setMessageLevel(2);
            //error: expected unqualified-id before ‘<<’ token
            Verbose << "Message niveau 2" << endl;
     
            return 0;
    }
    Il me manque quelque chose que je ne comprend pas, j'obtiens l'erreur de compilation suivante :
    error: expected unqualified-id before ‘<<’ token

    Quelqu'un a t-il une idée ?

  7. #7
    Membre Expert Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 048
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 048
    Par défaut
    Ta surcharge demande un audio AudioCoreVerbos à droite et un ostream à gauche. Toi tu lui donne un verbose à gauche et un const char * à droite.
    Il faut que tu mettes ta surcharge d'opérateur en fonction ami.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class Verbose{
    //....
    friend Verbose &  operator<<(Verbose  & verbose, const char* str);
    friend Verbose   &  operator<<(Verbose   & verbose, const AudioCoreVerbose & str);
    };
     
    // Les définir en dehors de la classe comme fonction global ( dans un namespace)

  8. #8
    Membre Expert

    Profil pro
    Inscrit en
    Juin 2006
    Messages
    1 294
    Détails du profil
    Informations personnelles :
    Localisation : Royaume-Uni

    Informations forums :
    Inscription : Juin 2006
    Messages : 1 294
    Par défaut
    Salut,

    Citation Envoyé par Snooker9 Voir le message
    Quelqu'un a t-il une idée ?
    Il faut que tu instancies ta classe, donc :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Verbose() << "Message niveau 0" << endl;
    MAT.

  9. #9
    Membre averti
    Profil pro
    Inscrit en
    Février 2008
    Messages
    57
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 57
    Par défaut
    Tout d'abord merci pour vos réponses.

    Ta surcharge demande un audio AudioCoreVerbos à droite et un ostream à gauche. Toi tu lui donne un verbose à gauche et un const char * à droite.
    Désolé, un malheureux copié collé a laissé trainer un "AudioCoreVerbose". Le remplacer par "Verbose"

    friend Verbose & operator<<(Verbose & verbose, const char* str);
    Si j'écris cette surcharge, je ne pourrai mettre à droite de mon opérateur que des "const char *". Or, je souhaite remplacer tous les "cout" d'un programme par "Verbose", ainsi je ne connais pas le type de la donnée qui se trouvera à droite de l'opérateur "<<".

    Il faut que tu mettes ta surcharge d'opérateur en fonction ami.
    Le "friend" ne sert pas uniquement à l'accès aux attributs privés ?

    Verbose() << "Message niveau 0" << endl;
    Es-tu sure de ta proposition ? En effet, j'obtiens l'erreur de compilation suivante :
    error: no match for ‘operator<<’ in ‘Verbose() << "Message niveau 0"’
    note: candidates are: std::ostream& Verbose::operator<<(std::ostream&)

    Aussi, pourquoi lors d'un appel à "cout", comme ceci :
    cout << "Salut" << endl;
    Il n'y a pas les "()" ?
    cout() << "Salut" << endl;

    Merci d'avance.

  10. #10
    Membre Expert

    Profil pro
    Inscrit en
    Juin 2006
    Messages
    1 294
    Détails du profil
    Informations personnelles :
    Localisation : Royaume-Uni

    Informations forums :
    Inscription : Juin 2006
    Messages : 1 294
    Par défaut
    Citation Envoyé par Snooker9 Voir le message
    Es-tu sure de ta proposition ? En effet, j'obtiens l'erreur de compilation suivante :
    error: no match for ‘operator<<’ in ‘Verbose() << "Message niveau 0"’
    note: candidates are: std::ostream& Verbose::operator<<(std::ostream&)
    Ton opérateur de sérialisation ne peut pas fonctionner ainsi, regarde ce qui est suggéré dans cette entrée de la FAQ.

    Citation Envoyé par Snooker9 Voir le message
    Aussi, pourquoi lors d'un appel à "cout", comme ceci :
    cout << "Salut" << endl;
    Il n'y a pas les "()" ?
    cout() << "Salut" << endl;
    Parce que std::cout est un objet et non une classe.

    MAT.

  11. #11
    Membre averti
    Profil pro
    Inscrit en
    Février 2008
    Messages
    57
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 57
    Par défaut
    Ça avance ça avance...

    J'ai mieux compris le fonctionnement de la surcharge d'opérateur. Ainsi, j'obtiens presque le résultat attendu.

    Verbose.h

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    class Verbose
    {
    //------------------
            public:
                    friend std::ostream &   operator<<(std::ostream & stream, const Verbose & verbose);
    };
    Verbose.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    std::ostream & operator<<(std::ostream & stream, const Verbose & verbose)
    {
            if(verbose.getMessageLevel() <= verbose.getVerboseLevel())
                    return stream;
     
            //Si j'arrive ici, je dois retourner un stream vide afin que rien de soit affiché
            //Je tente ceci pour vider "stream" mais plus jamais rien ne s'affiche alors sur stdout
            //Peut être existe il une autre méthode pour vider "stream" ?
            stream.clear(std::io_base::eofbit);
     
            return stream;
    }
    main.cpp
    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
    18
    19
     
    int main(void)
    {
            Verbose::setVerboseLevel(1);
     
            Verbose::setMessageLevel(0);
            cout << Verbose() << "Message niveau 0" << endl;
     
            Verbose::setMessageLevel(1);
            cout << Verbose() << "Message niveau 1" << endl;
     
            Verbose::setMessageLevel(2);
            cout << Verbose() << "Message niveau 2" << endl;
     
            Verbose::setMessageLevel(0);
            cout << Verbose() << "Message niveau 0" << endl;
     
            return 0;
    }
    Sortie console
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    Message niveau 0
    Message niveau 1
    Il est normal que "Message niveau 2" ne s'affiche pas. En revanche, plus rien dans le programme ne s'affiche... (absence du 2ième message "Message niveau 0"). Je pense que je m'y prend mal pour vider mon "stream" lorsqu'il ne doit pas être affiché. Vous avez une autre idée ?

  12. #12
    Invité
    Invité(e)
    Par défaut
    clear ne vide pas le flux, mais change sont état. Ta fonction le met dans l'état "fin du flux atteint". Pour le qu'il repasse dans un état normal il faut faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    stream.clear(std::io_base::goodbit);

  13. #13
    Membre averti
    Profil pro
    Inscrit en
    Février 2008
    Messages
    57
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 57
    Par défaut
    Aille. C'est un peu embêtant ça.

    Du coup, comment "casser" la chaîne, si le message ne doit pas être affiché ?

  14. #14
    Invité
    Invité(e)
    Par défaut
    Effectivement changer l'état du flux doit être une solution, le problème c'est qu'en cas d'erreur tu ne pourra pas le remarquer. Mais je ne crois pas que le flux d'entrée entre dans l'etat eof("fin du flux atteinte") dans la library standard, donc il y a peu de risque de conflit. Voilà une piste :
    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
    18
     
    std::ostream & operator<<(std::ostream & stream, const Verbose & verbose)
    {
            if(stream.fail())
            {
                   return stream;
            }  
     
            if(verbose.getMessageLevel() <= verbose.getVerboseLevel())
            {
                  stream.clear(std::io_base::goodbit);
            }
            else
            {
                 stream.clear(std::io_base::eofbit);
            }
            return stream;
    }
    Sinon tu peux chercher dans une reference comme par là.

  15. #15
    Membre averti
    Profil pro
    Inscrit en
    Février 2008
    Messages
    57
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 57
    Par défaut
    Pas de résultat.
    Même en repassant le flux à goodbit cela ne fonctionne pas.

  16. #16
    Invité
    Invité(e)
    Par défaut
    En fait lors de la tentative d'écriture, le flux est mis dans l'état failbit. Une autre piste pourrait etre les buffer(memoire tampon). Les flux placent(dans le cas des flux de sortie) dans ou lisent(dans le cas des flux d'entrée) à partir des buffer les caracteres. L'idée est de creer un buffer qui ne fait rien et de l'affecter à cout :
    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
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
     
    class NullBuffer : public std::streambuf
    {
    	NullBuffer * setbuf ( char * s, streamsize n )
    	{
    		return this;
    	}
     
    	streampos seekoff ( streamoff off, ios_base::seekdir way, ios_base::openmode which = ios_base::in | ios_base::out )
    	{
    		return streampos();
    	}
     
    	streampos seekpos ( streampos sp, ios_base::openmode which = ios_base::in | ios_base::out )
    	{
    		return streampos();
    	}
     
    	streamsize xsputn ( const char * s, streamsize n )
    	{
    		return streampos();
    	}
    };
     
    int main()
    {
    	cout << "S'affiche" << std::endl;
     
    	NullBuffer buff;
    	std::streambuf* old = cout.rdbuf(&buff);
     
    	cout << "S'affiche pas" << std::endl;
    	if(cout.rdbuf() == &buff)
    	{
    		cout.rdbuf(old);
    	}
    	cout << "S'affiche" << std::endl;
    }

  17. #17
    Membre averti
    Profil pro
    Inscrit en
    Février 2008
    Messages
    57
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 57
    Par défaut
    Autre chose étrange, j'ai fais hériter ma classe "Verbose" de "ostream" afin que l'affichage soit réalisé directement dans un object de type "Verbose". Mais aucun affichage ne se produit...

    Verbose.h
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    class Verbose : public std::iostream
    {
                    friend std::ostream &   operator<<(std::ostream & stream, const Verbose & verbose);
    };
    Verbose.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    std::ostream & operator<<(std::ostream & stream, const Verbose & verbose)
    {
            std::cout << "Test d'affichage" << std::endl;
     
            if(verbose.getMessageLevel() <= verbose.getVerboseLevel())
                    std::cout << stream;
     
            return stream;
    }
    main.cpp
    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
    18
    19
    20
    21
    22
    23
     
    int main(void)
    {
            std::cout << "Debut du programme Verbose" << endl;
     
            Verbose::setVerboseLevel(1);
     
            Verbose::setMessageLevel(0);
            Verbose() << "Message niveau 0" << endl;
     
            Verbose::setMessageLevel(1);
            Verbose() << "Message niveau 1" << endl;
     
            Verbose::setMessageLevel(2);
            Verbose() << "Message niveau 2" << endl;
     
            Verbose::setMessageLevel(0);
            Verbose() << "Message niveau 0" << endl;
     
            std::cout << "Fin du programme Verbose" << endl;
     
            return 0;
    }
    Sortie console
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    Debut du programme Verbose
    Fin du programme Verbose
    Les lignes de type "std::cout" exécutées dans un object "Verbose" ne produisent aucun affichage...

  18. #18
    Invité
    Invité(e)
    Par défaut
    Hum...Tu n'a pas compris ce qu'était std::cout. C'est un objet global, de type std::ostream. Quelquepart dans un fichier de ta bibliothèque standard tu peux trouver une ligne ressemblant à cela:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    std::ostream cout:
    Lorsque tu l'utilise
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    std::string s("Hello"), s2(", world!");
    std::cout << s << s2;
    Le compilateur "transforme" ce bout de code en :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    ((std::cout << s) << s2);
    Puis en
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    operator <<(operator <<(std::cout,  s), s2);
    Ce qui signifie que l'objet stream est exactement le meme(dans ton cas) que l'objet std::cout. Appeller
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    std::cout << stream;
    Revient à appeller :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    std::cout << std::cout;
    Ce qui n'a aucun sens.

    EDIT : J'avais mal lu, dans ton cas, la fonction appelé doit ressembler à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    //si elle est membre
    Verbose operator<<(Verbose const& v, const char* s);
    EDIT 2 : J'avais pas vu l'héritage. Enlève-le , tu n'obtiendra pas l'effet désiré. Pour pouvoir faire ce que tuveux essaye quelque chose comme ca :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    class Verbose
    {
          template<class T>
          Verbose operator<<(Verbose const& v, T const& t);
    }

  19. #19
    Membre averti
    Profil pro
    Inscrit en
    Février 2008
    Messages
    57
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 57
    Par défaut
    Je te remercie, j'ai essayé ceci :

    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
    18
    19
    20
    21
    22
    23
    24
    25
     
    class Verbose
    {
                    template<class T>
                    friend Verbose        operator<<(const Verbose & verbose, const T & t);
    }
     
    template<class T>
    Verbose operator<<(const Verbose & verbose, const T & t)
    {
            if(verbose.getMessageLevel() <= verbose.getVerboseLevel())
                    std::cout << t;
     
            return verbose;
    }
     
    int main(void)
    {
           Verbose::setVerboseLevel(1);
     
           Verbose::setMessageLevel(0);
     
           //Erreur de compilation ici
           Verbose() << "Message niveau 0" << endl;
    }
    Je retombe sur l'erreur de compilation suivante :
    error: no match for ‘operator<<’ in ‘operator<<(const Verbose&, const T&) [with T = char [17]](((const char (&)[17])"Message niveau 0")) << std::endl’
    note: candidates are: QDataStream& operator<<(QDataStream&, const QChar&)

  20. #20
    Invité
    Invité(e)
    Par défaut
    endl est un manipulateur de flux, c'est à dire une fonction qui modifie le flux. Pour permetrre de l'utiliser avec l'operateur <<, il faut rajouter une surcharge :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    friend Verbose operator<<(const Verbose & verbose, std::ostream& (*t)(std::ostream&));

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. std::cout codeblock même si je mets #include <iostream>
    Par emykev22 dans le forum Débuter
    Réponses: 7
    Dernier message: 15/07/2014, 16h47
  2. Passage d'une fonction en tant que paramètre
    Par bagnolm dans le forum Langage
    Réponses: 3
    Dernier message: 28/11/2006, 15h58
  3. [VS2005][C#] Construire une flèche du même genre que Visio
    Par pocket dans le forum Windows Forms
    Réponses: 11
    Dernier message: 16/03/2006, 09h33
  4. [XHTML] xhtml - Une division de même hauteur que le reste
    Par TommyWeb dans le forum Balisage (X)HTML et validation W3C
    Réponses: 8
    Dernier message: 11/02/2006, 18h31
  5. Comment fermer l'application en même temps que Windows ?
    Par semaj_james dans le forum Langage
    Réponses: 4
    Dernier message: 23/06/2004, 22h17

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