Domanda

Sì, capisco la differenza tra loro. Quello che voglio sapere è: perché OVERRIDE un metodo? Qual è il bello nel farlo? In caso di sovraccarico: l'unico vantaggio è che non devi pensare a nomi diversi per le funzioni?

È stato utile?

Soluzione

Sovraccarico generalmente significa che hai due o più funzioni nello stesso ambito con lo stesso nome. La funzione che meglio corrisponde agli argomenti quando viene effettuata una chiamata vince e viene chiamata. È importante notare che, al contrario di chiamare una funzione virtuale, è che la funzione chiamata è selezionata in fase di compilazione. Tutto dipende dal tipo statico dell'argomento. Se hai un sovraccarico per B e uno per D , e l'argomento è un riferimento a B , ma indica davvero un D , quindi il sovraccarico per B è scelto in C ++. Si chiama invio statico anziché invio dinamico . Sovraccarichi se vuoi fare lo stesso di un'altra funzione con lo stesso nome, ma vuoi farlo per un altro tipo di argomento. Esempio:

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

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

entrambi stampano il loro argomento, quindi sono sovraccarichi. Ma il primo stampa un foo e il secondo stampa una barra. Se hai due funzioni che fanno cose diverse, è considerato stile cattivo quando hanno lo stesso nome, perché ciò può creare confusione su ciò che accadrà effettivamente quando si chiamano le funzioni. Un altro caso d'uso per il sovraccarico è quando si hanno parametri aggiuntivi per le funzioni, ma si limitano a inoltrare il controllo ad altre funzioni:

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

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

Questo può essere conveniente per il chiamante, se vengono spesso utilizzate le opzioni utilizzate dai sovraccarichi.

Sostituzione è qualcosa di completamente diverso. Non compete con il sovraccarico. Significa che se si dispone di una funzione virtuale in una classe base, è possibile scrivere una funzione con la stessa firma nella classe derivata. La funzione nella classe derivata sovrascrive la funzione della base. Esempio:

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

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

Ora, se si dispone di un oggetto e si chiama la funzione membro print , viene sempre chiamata la funzione di stampa del derivato, perché sostituisce quella della base. Se la funzione print non fosse virtuale, la funzione nel derivato non sovrascriverebbe la funzione base, ma la limiterebbe a nasconderla . L'override può essere utile se si dispone di una funzione che accetta una classe base e ognuna che ne deriva:

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

Ora, anche se al momento della compilazione il compilatore sa solo che b è almeno base, verrà chiamata la stampa della classe derivata. Questo è il punto delle funzioni virtuali. Senza di essi, la funzione di stampa della base verrebbe chiamata e quella della classe derivata non la sovrascriverebbe.

Altri suggerimenti

Ciò aggiungerà maggiore chiarezza ai pensieri. inserisci qui la descrizione dell'immagine

Le tue funzioni vengono caricate per tre motivi:

  1. Fornire due (o più) funzioni che eseguono cose simili, strettamente correlate, differenziate per i tipi e / o il numero di argomenti che accetta. Esempio inventato:

    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. Fornire due (o più) modi per eseguire la stessa azione. Esempio inventato:

    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. Fornire la possibilità di eseguire un'azione equivalente dati due (o più) tipi di input diversi. Esempio inventato:

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

In alcuni vale la pena sostenere che una funzione con un nome diverso è una scelta migliore di una funzione sovraccarica. Nel caso dei costruttori, il sovraccarico è l'unica scelta.


Oltre andare in bicicletta una funzione è completamente diversa e ha uno scopo completamente diverso. L'override delle funzioni è il modo in cui il polimorfismo funziona in C ++. Si ignora una funzione per modificare il comportamento di quella funzione in una classe derivata. In questo modo, una classe di base fornisce l'interfaccia e la classe derivata fornisce l'implementazione.

L'override è utile quando si eredita da una classe base e si desidera estenderne o modificarne la funzionalità. Anche quando l'oggetto viene castato come classe base, chiama la tua funzione ignorata, non quella base.

Il sovraccarico non è necessario, ma a volte rende la vita più facile o più leggibile. Probabilmente può peggiorare le cose, ma è allora che non dovrebbe essere usato. Ad esempio, puoi avere due funzioni che svolgono la stessa operazione, ma agire su diversi tipi di cose. Ad esempio Divide (float, float) dovrebbe essere diverso da Divide (int, int) , ma sostanzialmente sono la stessa operazione. Non preferiresti ricordare un nome di metodo, "Dividi", piuttosto che dover ricordare "DividiFloat", "DividiInt", "DividiIntByFloat" e così via?

Le persone hanno già definito sia il sovraccarico che l'override, quindi non elaborerò.

  
    

ASAFE chiesto:

         

l'unico vantaggio [al sovraccarico] è che non hai pensato a più nomi per funzioni?

  

1. Non devi pensare con diversi nomi

E questo è già un enorme vantaggio, vero?

Confrontiamoci con le note funzioni dell'API C e le loro varianti di C ++ fittizie:

/* 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) ;

Ciò significa due cose: una, devi dire al compilatore il tipo di dati che invierà alla funzione scegliendo la funzione giusta. Due, se vuoi estenderlo, dovrai trovare nomi fantasiosi e l'utente delle tue funzioni dovrà ricordare i nomi fantasiosi giusti.

E tutto ciò che voleva era avere il valore assoluto di qualche variabile numerica ...

Un'azione significa uno e un solo nome di funzione.

Nota che non sei limitato a cambiare il tipo di un parametro. Tutto può cambiare, purché abbia senso.

2. Per gli operatori, è obbligatorio

Vediamo gli operatori:

// 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)
}

Nell'esempio sopra, vuoi evitare di usare nient'altro che l'operatore +.

Si noti che C ha un sovraccarico implicito dell'operatore per i tipi incorporati (incluso il tipo complesso C99):

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

Quindi, anche nei linguaggi non oggetto, viene usata questa cosa di sovraccarico.

3. Per gli oggetti, è obbligatorio

Vediamo l'uso dei metodi di base di un oggetto: i suoi costruttori:

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

Alcuni potrebbero considerare questo come sovraccarico di funzioni, ma in realtà è più simile al sovraccarico dell'operatore:

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" ;
}

Conclusione: il sovraccarico è interessante

In C, quando si assegna il nome della funzione, i parametri fanno implicitamente parte della firma alla chiamata. Se hai "double fabs (double d)", allora mentre la firma di fabs per il compilatore è il "fabs" non decorato, significa che tu devi sapere che ci vogliono solo doppi.

In C ++, il nome della funzione non significa che la sua firma sia forzata. La sua firma alla chiamata è il suo nome e i suoi parametri. Quindi, se scrivi abs (-24), il compilatore saprà quale sovraccarico di abs deve chiamare, e tu, quando lo scrivi, lo trovi più naturale: vuoi il valore assoluto di -24.

Ad ogni modo, chiunque abbia codificato in qualche modo una lingua con gli operatori utilizza già il sovraccarico, siano essi operatori numerici C o Basic, concatenazione di stringhe Java, delegati C #, ecc. Perché? perché è più naturale .

E gli esempi mostrati sopra sono solo la punta dell'iceberg: quando si usano i template, il sovraccarico diventa molto utile, ma questa è un'altra storia.

L'esempio del libro di testo è di classe Animale con metodo speak (). Le sostituzioni della sottoclasse Dog parlano da () a " bark & ??quot; mentre la sottoclasse Cat sostituisce speak () in " meow " ;.

Un uso del sovraccarico è per l'uso nei template. Nei modelli, scrivi il codice che può essere utilizzato su diversi tipi di dati e lo chiami con tipi diversi. Se le funzioni che accettano argomenti diversi dovessero essere denominate diversamente, il codice per diversi tipi di dati dovrebbe in generale essere diverso e i modelli semplicemente non funzionerebbero.

Anche se potresti non scrivere ancora modelli, quasi sicuramente ne stai utilizzando alcuni. I flussi sono modelli, così come i vettori. Senza sovraccarico e quindi senza modelli, dovresti chiamare i flussi Unicode in modo diverso dai flussi ASCII e dovresti usare matrici e puntatori anziché vettori.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top