sencillos - Anular y sobrecargar en C++
sobrecarga de operadores unarios en c++ (7)
Sí, entiendo la diferencia entre ellos. Lo que quiero saber es: ¿por qué ANULAR un método? ¿Cuál es el bien al hacerlo? En caso de sobrecarga: ¿la única ventaja es que no debe pensar en nombres diferentes para las funciones?
El ejemplo de libro de texto es clase Animal con método speak (). La subclase Dog anula speak () para "ladrar", mientras que la subclase Cat anula speak () para "miau".
La anulación es útil cuando heredas de una clase base y deseas extender o modificar su funcionalidad. Incluso cuando el objeto se convierte como 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 empeorar las cosas, pero es entonces cuando no debería 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 básicamente es la misma operación. ¿No preferirías recordar el nombre de un método, "Divide", que tener que recordar "DivideFloat", "DivideInt", "DivideIntByFloat", y así sucesivamente?
Un uso de la sobrecarga es para usar en plantillas. En 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 tienen que ser nombradas 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, casi seguramente utilizará algunas de ellas. Los flujos son plantillas, y también lo son los vectores. Sin sobrecargar, y por lo tanto sin plantillas, necesitarías llamar a las transmisiones Unicode algo diferente de las transmisiones ASCII, y tendrías que usar matrices y punteros en lugar de vectores.
La sobrecarga generalmente significa que tiene dos o más funciones en el mismo ámbito con el mismo nombre. La función que mejor se adapta a los argumentos cuando se realiza una llamada gana y se llama. Es importante tener en cuenta que, a diferencia 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 objeto D
, entonces la sobrecarga para B
se elige en C ++. Eso se llama despacho estático en oposición al despacho dinámico . Sobrecarga si quieres hacer lo mismo que otra función que tiene el mismo nombre, pero quieres hacer eso para otro tipo de argumento. Ejemplo:
void print(Foo const& f) {
// print a foo
}
void print(Bar const& bar) {
// print a bar
}
ambos imprimen sus argumentos, 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 estilo incorrecto cuando tienen el mismo nombre, porque eso puede generar confusión sobre lo que ocurrirá en realidad al llamar a las funciones. Otro caso de uso para la sobrecarga es cuando tiene parámetros adicionales para las funciones, pero simplemente reenvía 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 a menudo se utilizan.
Anular 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 de miembro de print
, siempre se invoca la función de impresión de derivada, ya que anula la de la base. Si la función de print
no fuera virtual, entonces la función en 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 derivada de ella:
void doit(base &b) {
// and sometimes, we want to print it
b.print();
}
Ahora, aunque en tiempo de compilación el compilador solo sabe que b es al menos base, se invocará 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.
Usted sobrecarga las funciones por tres razones:
Proporcionar dos (o más) funciones que realizan cosas similares estrechamente relacionadas, diferenciadas por los tipos y / o la cantidad de argumentos que acepta. Ejemplo de ejemplo:
void Log(std::string msg); // logs a message to standard out void Log(std::string msg, std::ofstream); // logs a message to a file
Proporcionar dos (o más) formas de realizar la misma acción. Ejemplo de ejemplo:
void Plot(Point pt); // plots a point at (pt.x, pt.y) void Plot(int x, int y); // plots a point at (x, y)
Para proporcionar la capacidad de realizar una acción equivalente dado dos (o más) tipos de entrada diferentes. Ejemplo de ejemplo:
wchar_t ToUnicode(char c); std::wstring ToUnicode(std::string s);
En algunos casos, vale la pena argumentar que una función de 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.
Más de 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 una interfaz, y la clase derivada proporciona implementación.
Esto agregará algo más de claridad a los pensamientos.
La gente ya definió tanto la sobrecarga como la anulación, así que no daré más detalles.
ASAFE preguntó:
la única ventaja [a la sobrecarga] es que no ha pensado en varios nombres para las funciones?
1. No tienes que pensar en varios nombres
Y esto ya es una gran ventaja, ¿no?
Comparemos con las funciones C API conocidas 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 extenderlo, necesitará buscar 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. Todo puede cambiar, siempre que tenga sentido.
2. Para los operadores, es mantadory
Veamos de 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 el uso de cualquier cosa que no sea el operador +.
Tenga en cuenta que C tiene una sobrecarga implícita del operador para los tipos incorporados (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 no objeto, se usa esta cosa sobrecargada.
3. Para objetos, es mantadory
Veamos el uso de un objeto de métodos básicos: 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 funciones, 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 das el nombre de la función, los parámetros son implícitamente parte de la firma en la llamada. Si tiene "doble fabs (double d)", entonces mientras la firma de fabs para el compilador es la "fabs" no decorada, significa que debe saber que solo requiere dobles.
En C ++, el nombre de la función no significa que su firma es 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 idioma con operadores ya usa la sobrecarga, ya sea C o operadores numéricos básicos, concatenación de cadenas Java, C # delegados, 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 útil, pero esta es otra historia.