Pregunta

Sí, entiendo la diferencia entre ellos. Lo que quiero saber es: ¿por qué ANULAR un método? ¿De qué sirve hacerlo? En caso de sobrecarga: ¿la única ventaja es que no tiene que pensar en nombres diferentes para las funciones?

¿Fue útil?

Solución

Sobrecarga generalmente significa que tiene dos o más funciones en el mismo ámbito que tienen el mismo nombre. La función que mejor coincide con los argumentos cuando se realiza una llamada gana y se llama. Es importante tener en cuenta, en lugar de llamar a una función virtual, es que la función que se llama se selecciona en tiempo de compilación. Todo depende del tipo estático del argumento. Si tiene una sobrecarga para B y una para D , y el argumento es una referencia a B , pero realmente apunta a un D , luego la sobrecarga de B se elige en C ++. Eso se llama despacho estático en lugar de despacho dinámico . Se sobrecarga si desea hacer lo mismo que otra función con el mismo nombre, pero desea hacerlo para otro tipo de argumento. Ejemplo:

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

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

ambos imprimen su argumento, por lo que están sobrecargados. Pero el primero imprime un foo, y el segundo imprime una barra. Si tiene dos funciones que hacen cosas diferentes, se considera un mal estilo cuando tienen el mismo nombre, porque eso puede generar confusión sobre lo que sucederá realmente al llamar a las funciones. Otro caso de uso para la sobrecarga es cuando tiene parámetros adicionales para funciones, pero solo envían el control a otras funciones:

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

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

Eso puede ser conveniente para la persona que llama, si las opciones que toman las sobrecargas suelen usarse.

Reemplazar es algo completamente diferente. No compite con la sobrecarga. Significa que si tiene una función virtual en una clase base, puede escribir una función con la misma firma en la clase derivada. La función en la clase derivada anula la función de la base. Muestra:

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

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

Ahora, si tiene un objeto y llama a la función miembro print , la función print del derivado siempre se llama, porque anula la de la base. Si la función print no fuera virtual, entonces la función derivada no anularía la función base, sino que simplemente la ocultaría . La anulación puede ser útil si tiene una función que acepta una clase base y cada una de ellas derivada de ella:

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

Ahora, aunque en el momento de la compilación el compilador solo sabe que b es al menos base, se llamará la impresión de la clase derivada. Ese es el punto de las funciones virtuales. Sin ellos, se llamaría a la función de impresión de la base, y la de la clase derivada no la anularía.

Otros consejos

Esto agregará más claridad a los pensamientos. ingrese la descripción de la imagen aquí

Usted sobre cargar funciones por tres razones:

  1. Proporcionar dos (o más) funciones que realizan cosas similares, estrechamente relacionadas, diferenciadas por los tipos y / o el número de argumentos que acepta. Ejemplo contribuido:

    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. Para proporcionar dos (o más) formas de realizar la misma acción. Ejemplo contribuido:

    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. Para proporcionar la capacidad de realizar una acción equivalente dados dos (o más) tipos de entrada diferentes. Ejemplo contribuido:

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

En algunos casos vale la pena argumentar que una función con un nombre diferente es una mejor opción que una función sobrecargada. En el caso de los constructores, la sobrecarga es la única opción.


Sobre montar una función es completamente diferente y tiene un propósito completamente diferente. La anulación de funciones es cómo funciona el polimorfismo en C ++. Anula una función para cambiar el comportamiento de esa función en una clase derivada. De esta manera, una clase base proporciona interfaz, y la clase derivada proporciona implementación.

La anulación es útil cuando hereda de una clase base y desea ampliar o modificar su funcionalidad. Incluso cuando el objeto se convierte en la clase base, llama a su función anulada, no a la base.

La sobrecarga no es necesaria, pero a veces hace la vida más fácil o más legible. Podría decirse que puede empeorarlo, pero es cuando no debe usarse. Por ejemplo, puede tener dos funciones que realizan la misma operación, pero actúan sobre diferentes tipos de cosas. Por ejemplo, Divide (float, float) debe ser diferente de Divide (int, int) , pero son básicamente la misma operación. ¿No preferirías recordar un nombre de método, "Divide", que tener que recordar "DivideFloat", "DivideInt", "DivideIntByFloat", y así sucesivamente?

La gente ya definió tanto la sobrecarga como la anulación, por lo que no daré más detalles.

  
    

ASAFE preguntó:

         

¿la única ventaja [de sobrecargar] es que no ha pensado en varios nombres para funciones?

  

1. No tienes que pensar en varios nombres

Y esto ya es una gran ventaja, ¿no?

Comparemos con las funciones conocidas de C API y sus variantes ficticias de 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) ;

Esto significa dos cosas: una, debe decirle al compilador el tipo de datos que alimentará a la función eligiendo la función correcta. Dos, si desea ampliarlo, deberá encontrar nombres elegantes, y el usuario de sus funciones tendrá que recordar los nombres elegantes correctos.

Y todo lo que quería era tener el valor absoluto de alguna variable numérica ...

Una acción significa uno y solo un nombre de función.

Tenga en cuenta que no está limitado a cambiar el tipo de un parámetro. Cualquier cosa puede cambiar, siempre que tenga sentido.

2. Para los operadores, es obligatorio

Veamos los operadores:

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

En el ejemplo anterior, desea evitar usar otra cosa que no sea el operador +.

Tenga en cuenta que C tiene una sobrecarga de operador implícita para los tipos integrados (incluido el tipo complejo C99):

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

Entonces, incluso en lenguajes que no son objetos, se usa esta sobrecarga.

3. Para los objetos, es obligatorio

Veamos el uso de los métodos básicos de un objeto: sus constructores:

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

Algunos podrían considerar esto como sobrecarga de la función, pero de hecho, es más similar a la sobrecarga del operador:

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

Conclusión: la sobrecarga es genial

En C, cuando da el nombre de la función, los parámetros forman parte implícita de la firma en la llamada. Si tiene '' fabs dobles (doble d) '', mientras que la firma de fabs para el compilador es '' fabs '' sin decorar, significa que usted debe saber que solo se duplican.

En C ++, el nombre de la función no significa que su firma sea forzada. Su firma en la llamada es su nombre y sus parámetros. Por lo tanto, si escribe abs (-24), el compilador sabrá a qué sobrecarga de abs debe llamar, y usted, al escribirlo, lo encontrará más natural: desea el valor absoluto de -24.

De todos modos, cualquiera que haya codificado algo en cualquier lenguaje con operadores ya usa sobrecarga, ya sean operadores numéricos C o básicos, concatenación de cadenas Java, delegados C #, etc. ¿Por qué? porque es más natural .

Y los ejemplos que se muestran arriba son solo la punta del iceberg: cuando se usan plantillas, la sobrecarga se vuelve muy muy útil, pero esta es otra historia.

El ejemplo del libro de texto es la clase Animal con el método speak (). La subclase de perro anula speak () para `` ladrar '' mientras que la subclase Cat reemplaza speak () a '' miau ''.

Un uso de sobrecarga es para usar en plantillas. En las plantillas, escribe código que se puede usar en diferentes tipos de datos y lo llama con diferentes tipos. Si las funciones que toman diferentes argumentos debieran nombrarse de manera diferente, el código para diferentes tipos de datos en general tendría que ser diferente, y las plantillas simplemente no funcionarían.

Si bien es posible que todavía no esté escribiendo plantillas, es casi seguro que esté utilizando algunas de ellas. Las secuencias son plantillas, y también lo son los vectores. Sin sobrecargar y, por lo tanto, sin plantillas, necesitaría llamar a las secuencias Unicode de forma diferente a las secuencias ASCII, y tendría que usar matrices y punteros en lugar de vectores.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top