Question

Oui, je comprends la différence entre eux. Ce que je veux savoir, c'est: pourquoi annuler une méthode? Quel est l'intérêt de le faire? En cas de surcharge: le seul avantage est que vous n’avez pas à penser différemment aux fonctions.

Était-ce utile?

La solution

La

surcharge signifie généralement que vous avez deux fonctions ou plus dans la même étendue portant le même nom. La fonction qui correspond le mieux aux arguments lorsqu’un appel est passé gagne et est appelée. Il est important de noter que, contrairement à l'appel d'une fonction virtuelle, la fonction appelée est sélectionnée au moment de la compilation. Tout dépend du type statique de l'argument. Si vous avez une surcharge pour B et une pour D , et que l'argument est une référence à B , mais qu'il pointe réellement vers un D , la surcharge pour B est choisie en C ++. Cela s'appelle distribution statique par opposition à distribution dynamique . Vous surchargez si vous voulez faire la même chose qu'une autre fonction ayant le même nom, mais vous voulez le faire pour un autre type d'argument. Exemple:

void print(Foo const& f) {
    // print a foo
}

void print(Bar const& bar) {
    // print a bar
}

ils affichent tous deux leur argument, ils sont donc surchargés. Mais le premier imprime un foo et le second imprime une barre. Si vous avez deux fonctions qui font des choses différentes, le style est mauvais quand elles portent le même nom, car cela peut entraîner une confusion quant à ce qui va se passer lors de l’appel des fonctions. Une autre possibilité d'utilisation de la surcharge est lorsque vous avez des paramètres supplémentaires pour les fonctions, mais ils ne font que transférer le contrôle à d'autres fonctions:

void print(Foo & f, PrintAttributes b) { 
    /* ... */ 
}

void print(Foo & f, std::string const& header, bool printBold) {
    print(f, PrintAttributes(header, printBold));
}

Cela peut être pratique pour l’appelant, si les options que prennent les surcharges sont souvent utilisées.

Remplacer est quelque chose de complètement différent. Il ne concurrence pas la surcharge. Cela signifie que si vous avez une fonction virtuelle dans une classe de base, vous pouvez écrire une fonction avec la même signature dans la classe dérivée. La fonction de la classe dérivée remplace la fonction de la base. Échantillon:

struct base {
    virtual void print() { cout << "base!"; }
}

struct derived: base {
    virtual void print() { cout << "derived!"; }
}

Maintenant, si vous avez un objet et appelez la fonction membre print , la fonction d'impression du dérivé est toujours appelée, car elle remplace celle de la base. Si la fonction print n'était pas virtuelle, la fonction dans la fonction dérivée ne remplacerait pas la fonction de base, mais la ferait simplement la masquer . Remplacer peut être utile si vous avez une fonction qui accepte une classe de base et toutes les classes qui en dérivent:

void doit(base &b) {
    // and sometimes, we want to print it
    b.print();
}

Bien que, au moment de la compilation, le compilateur sache uniquement que b est au moins bas, l'impression de la classe dérivée sera appelée. C'est le but des fonctions virtuelles. Sans eux, la fonction d'impression de la base serait appelée et celle de la classe dérivée ne la remplacerait pas.

Autres conseils

Cela clarifiera les idées. entrer la description de l'image ici

Vous avez chargé charger pour trois raisons:

  1. Fournir deux fonctions (ou plus) effectuant des tâches similaires et étroitement liées, différenciées par les types et / ou le nombre d'arguments acceptés. Exemple élaboré:

    void Log(std::string msg); // logs a message to standard out
    void Log(std::string msg, std::ofstream); // logs a message to a file
    
  2. Fournir deux méthodes (ou plus) pour effectuer la même action. Exemple élaboré:

    void Plot(Point pt); // plots a point at (pt.x, pt.y)
    void Plot(int x, int y); // plots a point at (x, y)
    
  3. Pour permettre d’effectuer une action équivalente avec deux types d’entrée différents (ou plus). Exemple élaboré:

    wchar_t      ToUnicode(char c);
    std::wstring ToUnicode(std::string s);
    

Dans certains , il peut être intéressant de dire qu'une fonction portant un nom différent constitue un meilleur choix qu'une fonction surchargée. Dans le cas des constructeurs, la surcharge est le seul choix possible.

Sur l'équitation , une fonction est totalement différente et remplit un objectif totalement différent. La fonction prioritaire est comment le polymorphisme fonctionne en C ++. Vous substituez une fonction pour modifier le comportement de cette fonction dans une classe dérivée. De cette manière, une classe de base fournit une interface et la classe dérivée une implémentation.

La substitution est utile lorsque vous héritez d'une classe de base et souhaitez étendre ou modifier ses fonctionnalités. Même lorsque l'objet est converti en classe de base, il appelle votre fonction remplacée, et non la base.

La surcharge n’est pas nécessaire, mais elle facilite parfois la vie ou la rend plus lisible. On peut soutenir que cela peut aggraver la situation, mais c'est dans ce cas qu'il ne devrait pas être utilisé. Par exemple, vous pouvez avoir deux fonctions qui effectuent la même opération, mais agissent sur différents types de tâches. Par exemple, Divide (float, float) doit être différent de Divide (int, int) , mais il s’agit essentiellement de la même opération. Ne préférez-vous pas vous rappeler un nom de méthode, "Divide", plutôt que de devoir vous rappeler "DivideFloat", "DivideInt", "DivideIntByFloat", etc.?

Les personnes ayant déjà défini la surcharge et la substitution, je ne vais pas en dire plus.

  
    

ASAFE a demandé:

         

le seul avantage [de surcharger] est que vous n'avez pas pensé à plusieurs noms de fonctions?

  

1. Vous n'avez pas à penser en plusieurs noms

Et cela représente déjà un avantage considérable, n'est-ce pas?

Comparons les fonctions connues de l'API C et leurs variantes fictives C ++:

/* C */
double fabs(double d) ;
int abs(int i) ;

// C++ fictional variants
long double abs(long double d) ;
double abs(double d) ;
float abs(float f) ;
long abs(long i) ;
int abs(int i) ;

Cela signifie deux choses: premièrement, vous devez indiquer au compilateur le type de données qu’il transmettra à la fonction en choisissant la bonne fonction. Deuxièmement, si vous souhaitez l'étendre, vous devez trouver des noms sophistiqués et l'utilisateur de vos fonctions devra se rappeler les bons noms.

Et tout ce qu'il / elle voulait, c'était avoir la valeur absolue d'une variable numérique ...

Une action signifie un et un seul nom de fonction.

Notez que vous n'êtes pas limité à changer le type d'un paramètre. Tout peut changer, à condition que cela ait un sens.

2. Pour les opérateurs, il est obligatoire

Voyons voir les opérateurs:

// C++
Integer operator + (const Integer & lhs, const Integer & rhs) ;
Real operator + (const Real & lhs, const Real & rhs) ;
Matrix operator + (const Matrix & lhs, const Matrix & rhs) ;
Complex operator + (const Complex & lhs, const Complex & rhs) ;

void doSomething()
{
   Integer i0 = 5, i1 = 10 ;
   Integer i2 = i0 + i1 ; // i2 == 15

   Real r0 = 5.5, r1 = 10.3 ;
   Real r2 = r0 + r1 ; // r2 = 15.8

   Matrix m0(1, 2, 3, 4), m1(10, 20, 30, 40) ;
   Matrix m2 = m0 + m1 ; // m2 == (11, 22, 33, 44)

   Complex c0(1, 5), c1(10, 50) ;
   Complex c2 = c0 + c1 ; // c2 == (11, 55)
}

Dans l'exemple ci-dessus, vous voulez éviter d'utiliser autre chose que l'opérateur +.

Notez que C a une surcharge implicite des opérateurs pour les types intégrés (y compris le type complexe C99):

/* C */
void doSomething(void)
{
   char c = 32 ;
   short s = 54 ;
   c + s ; /* == C++ operator + (char, short) */
   c + c ; /* == C++ operator + (char, char) */
}

Ainsi, même dans les langages non-objets, cette surcharge est utilisée.

3. Pour les objets, il est obligatoire

Voyons l'utilisation des méthodes de base d'un objet: ses constructeurs:

class MyString
{
   public :
      MyString(char character) ;
      MyString(int number) ;
      MyString(const char * c_style_string) ;
      MyString(const MyString * mySring) ;
      // etc.
} ;

Certains pourraient considérer cela comme une surcharge de fonctions, mais en réalité, cela ressemble davantage à une surcharge d'opérateur:

void doSomething()
{
   MyString a('h') ;                  // a == "h" ;
   MyString b(25) ;                   // b == "25" ;
   MyString c("Hello World") ;        // c == "Hello World" ;
   MyString d(c) ;                    // d == "Hello World" ;
}

Conclusion: la surcharge est cool

En C, lorsque vous donnez le nom de la fonction, les paramètres font implicitement partie de la signature à l'appel. Si vous avez "double fabs (double d)", alors que la signature des fabs pour le compilateur est "fabs" non décorée, cela signifie que vous devez savoir qu'il ne faut que doubler.

En C ++, le nom de la fonction ne signifie pas que sa signature est forcée. Sa signature à l'appel est son nom et ses paramètres. Ainsi, si vous écrivez abs (-24), le compilateur saura quelle surcharge d'abs il doit appeler, et vous le trouverez plus naturel en l'écrivant: vous voulez la valeur absolue de -24.

Quoi qu'il en soit, quiconque a quelque peu codé dans n'importe quelle langue avec des opérateurs utilise déjà la surcharge, que ce soit des opérateurs numériques C ou Basic, une concaténation de chaînes Java, des délégués C #, etc. Pourquoi? parce que c'est plus naturel .

Et les exemples ci-dessus ne sont que la partie visible de l'iceberg: lors de l'utilisation de modèles, la surcharge devient très utile, mais ceci est une autre histoire.

L'exemple de manuel est la classe Animal avec la méthode speak (). La sous-classe Dog remplace speak () en "écorce". tandis que la sous-classe Cat remplace speak () en "miaou".

Une des utilisations de la surcharge concerne les modèles. Dans les modèles, vous écrivez du code qui peut être utilisé sur différents types de données et vous l'appelez avec différents types. Si les fonctions qui prennent des arguments différents doivent être nommées différemment, le code de différents types de données devra en général être différent et les modèles ne fonctionneront tout simplement pas.

Même si vous n’écrivez pas encore de modèles, vous en utilisez certainement certains. Les flux sont des modèles, de même que les vecteurs. Sans surcharge, et donc sans modèles, vous devrez appeler les flux Unicode quelque chose de différent des flux ASCII, et vous devrez utiliser des tableaux et des pointeurs au lieu de vecteurs.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top