Oui, je comprends la différence entre eux. Ce que je veux savoir, c'est: pourquoi outrepasser une méthode? À quoi bon le faire? En cas de surcharge: le seul avantage est que vous n'avez pas à penser différemment aux fonctions?
Surcharge signifie généralement que vous avez deux fonctions ou plus dans la même portée portant le même nom. La fonction qui correspond le mieux aux arguments lors d'un appel gagne et est appelée. Il est important de noter, par opposition à l'appel d'une fonction virtuelle, que 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 vraiment vers un objet D
, alors la surcharge pour B
est choisie en C++. Cela s'appelle répartition statique par opposition à répartition dynamique. Vous surchargez si vous voulez faire la même chose qu'une autre fonction portant 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 les 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, c'est considéré comme un mauvais style quand elles ont le même nom, car cela peut entraîner une confusion sur ce qui se passera réellement lors de l'appel des fonctions. Un autre cas d'utilisation pour la surcharge est lorsque vous avez des paramètres supplémentaires pour les fonctions, mais ils transfèrent simplement 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 prises par les surcharges sont souvent utilisées.
Overriding est quelque chose de complètement différent. Il ne fait pas concurrence à 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 dans 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 de la dérivée est toujours appelée, car elle remplace celle de la base. Si la fonction print
n'était pas virtuelle, alors la fonction dans le dérivé ne remplacerait pas la fonction de base, mais simplement hide it. La substitution peut être utile si vous avez une fonction qui accepte une classe de base, et toutes celles qui en dérivent:
void doit(base &b) {
// and sometimes, we want to print it
b.print();
}
Maintenant, même si au moment de la compilation le compilateur sait seulement que b est au moins base, l'impression de la classe dérivée sera appelée. C'est le point 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.
Cela ajoutera un peu plus de clarté aux pensées.
Vous sur chargez fonctions pour trois raisons:
Fournir deux (ou plus) fonctions qui effectuent des choses similaires et étroitement liées, différenciées par les types et/ou le nombre d'arguments qu'il accepte. Exemple artificiel:
void Log(std::string msg); // logs a message to standard out
void Log(std::string msg, std::ofstream); // logs a message to a file
Pour fournir deux (ou plus) façons d'effectuer la même action. Exemple artificiel:
void Plot(Point pt); // plots a point at (pt.x, pt.y)
void Plot(int x, int y); // plots a point at (x, y)
Pour fournir la possibilité d'effectuer une action équivalente pour deux types d'entrée (ou plus) différents. Exemple artificiel:
wchar_t ToUnicode(char c);
std::wstring ToUnicode(std::string s);
Dans certains cas, il vaut la peine de faire valoir qu'une fonction d'un nom différent est un meilleur choix qu'une fonction surchargée. Dans le cas des constructeurs, la surcharge est le seul choix.
Sur équitation une fonction est entièrement différente, et sert un but entièrement différent. La fonction prioritaire est le fonctionnement du polymorphisme en C++. Vous remplacez une fonction pour modifier le comportement de cette fonction dans une classe dérivée. De cette façon, une classe de base fournit une interface et la classe dérivée fournit 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, pas celle de base.
La surcharge n'est pas nécessaire, mais elle rend parfois la vie plus facile ou plus lisible. On peut sans doute aggraver la situation, mais c'est à ce moment-là 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 choses. Par exemple, Divide(float, float)
devrait ê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 souvenir d'un nom de méthode, "Divide", plutôt que de vous souvenir de "DivideFloat", "DivideInt", "DivideIntByFloat", et ainsi de suite?
Les gens ont déjà défini à la fois la surcharge et la surcharge, donc je ne développerai pas.
ASAFE a demandé:
le seul avantage [à surcharger] est que vous n'avez pas pensé à plusieurs fonctions aux fonctions?
Et c'est déjà un gros avantage, non?
Comparons avec les fonctions connues de l'API C et leurs variantes C++ fictives:
/* 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 alimentera dans la fonction en choisissant la bonne fonction. Deuxièmement, si vous voulez l'étendre, vous devrez trouver des noms fantaisistes et l'utilisateur de vos fonctions devra se souvenir des bons noms fantaisistes.
Et tout ce qu'il 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, tant que cela a du sens.
Voyons 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 veux pour éviter d'utiliser autre chose que l'opérateur +.
Notez que C a une surcharge implicite d'opérateur 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) */
}
Donc, même dans les langages non-objets, cette chose de surcharge est utilisée.
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 fait, cela ressemble plus à 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" ;
}
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 si la signature de fabs pour le compilateur est le "fabs" non décoré, cela signifie que vous doit savoir qu'il ne faut que des doubles.
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, en l'écrivant, la trouverez plus naturelle: vous voulez la valeur absolue de -24.
Quoi qu'il en soit, toute personne qui a codé quelque peu dans n'importe quelle langue avec des opérateurs utilise déjà la surcharge, qu'il s'agisse d'opérateurs numériques C ou de base, Java concaténation de chaînes, délégués C #, etc. Pourquoi? parce que c'est plus naturel.
Et les exemples ci-dessus ne sont que la pointe de l'iceberg: lors de l'utilisation de modèles, la surcharge devient très utile, mais c'est une autre histoire.
L'exemple de manuel est la classe Animal avec la méthode speak (). Les dérogations de la sous-classe Dog parlent () de "aboyer" tandis que les dérogations de la sous-classe Cat parlent () de "miaou".
Une utilisation de la surcharge est destinée aux 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 différents arguments devaient être nommées différemment, le code pour différents types de données devrait en général être différent et les modèles ne fonctionneraient tout simplement pas.
Bien que vous n'écriviez peut-être pas encore de modèles, vous en utilisez presque certainement certains. Les flux sont des modèles, tout comme les vecteurs. Sans surcharge, et donc sans modèles, vous devez appeler des flux Unicode quelque chose de différent des flux ASCII, et vous devez utiliser des tableaux et des pointeurs au lieu de vecteurs.