Да, я понимаю разницу между ними. Я хочу знать, почему: OVERRIDE метод? Какая польза от этого? В случае перегрузки: единственное преимущество заключается в том, что вы не должны думать в разных именах с функциями?
Переопределение и перегрузка в С++
Ответ 1
Перегрузка обычно означает, что у вас есть две или более функции в одной и той же области с тем же именем. Функция, которая лучше соответствует аргументам при вызове, вызывается и вызывается. Важно отметить, что в отличие от вызова виртуальной функции, функция, которая вызывается, выбирается во время компиляции. Все зависит от статического типа аргумента. Если у вас есть перегрузка для B
и одна для D
, а аргумент - ссылка на B
, но она действительно указывает на объект D
, тогда перегрузка для B
выбрана в С++. Это называется статической отправкой в отличие от динамической отправки. Вы перегружаетесь, если хотите сделать то же самое, что и другая функция с тем же именем, но вы хотите сделать это для другого типа аргумента. Пример:
void print(Foo const& f) {
// print a foo
}
void print(Bar const& bar) {
// print a bar
}
они оба печатают свой аргумент, поэтому они перегружены. Но первый печатает foo, а второй печатает панель. Если у вас есть две функции, которые делают разные вещи, они считают неправильный стиль, когда у них одно и то же имя, потому что это может привести к путанице в том, что произойдет при вызове функций. Еще одна утилита для перегрузки - это когда у вас есть дополнительные параметры для функций, но они просто управляют другими функциями:
void print(Foo & f, PrintAttributes b) {
/* ... */
}
void print(Foo & f, std::string const& header, bool printBold) {
print(f, PrintAttributes(header, printBold));
}
Это может быть удобно для вызывающего, если часто используются опции, которые перегружают.
Переопределение - это нечто совершенно другое. Он не конкурирует с перегрузкой. Это означает, что если у вас есть виртуальная функция в базовом классе, вы можете написать функцию с той же сигнатурой в производном классе. Функция в производном классе переопределяет функцию базы. Пример:
struct base {
virtual void print() { cout << "base!"; }
}
struct derived: base {
virtual void print() { cout << "derived!"; }
}
Теперь, если у вас есть объект и вызывается функция-член print
, функция print производного всегда вызывается, поскольку она переопределяет базовую. Если функция print
не была виртуальной, то функция в производном не перекрывала бы базовую функцию, а просто скрывала бы ее. Переопределение может быть полезно, если у вас есть функция, которая принимает базовый класс, и каждый, который получен из него:
void doit(base &b) {
// and sometimes, we want to print it
b.print();
}
Теперь, хотя во время компиляции только известно, что b является, по крайней мере, базовым, будет вызываться печать производного класса. Это точка виртуальных функций. Без них будет вызываться функция печати базы, а одно из производного класса не будет переопределять ее.
Ответ 2
Это добавит некоторую ясность в мысли.
Ответ 3
Вы перегружаете функции по трем причинам:
-
Предоставить две (или более) функции, которые выполняют похожие, тесно связанные вещи, дифференцированные по типам и/или количеству аргументов, которые она принимает. Продуманный пример:
void Log(std::string msg); // logs a message to standard out void Log(std::string msg, std::ofstream); // logs a message to a file
-
Предоставить два (или более) способа выполнения одного и того же действия. Продуманный пример:
void Plot(Point pt); // plots a point at (pt.x, pt.y) void Plot(int x, int y); // plots a point at (x, y)
-
Предоставлять возможность выполнять эквивалентное действие при двух (или более) разных типах ввода. Продуманный пример:
wchar_t ToUnicode(char c); std::wstring ToUnicode(std::string s);
В некоторых случаях стоит утверждать, что функция другого имени - лучший выбор, чем перегруженная функция. В случае конструкторов перегрузка является единственным выбором.
Переопределение функции совершенно другое и служит совершенно другой цели. Функция переопределения - это то, как полиморфизм работает на С++. Вы переопределяете функцию, чтобы изменить поведение этой функции в производном классе. Таким образом, базовый класс предоставляет интерфейс, а производный класс обеспечивает реализацию.
Ответ 4
Переопределение полезно, когда вы наследуете базовый класс и хотите расширить или изменить его функциональность. Даже когда объект используется как базовый класс, он вызывает вашу переопределенную функцию, а не базовую.
Перегрузка не требуется, но она позволяет сделать жизнь проще или более легко читаемой. Возможно, это может усугубить ситуацию, но когда ее не следует использовать. Например, у вас могут быть две функции, которые выполняют одну и ту же операцию, но действуют на разные вещи. Например, Divide(float, float)
должен отличаться от Divide(int, int)
, но в основном это одна и та же операция. Разве вы не помните одно имя метода "Разделить", чем нужно помнить "DivideFloat", "DivideInt", "DivideIntByFloat" и т.д.?
Ответ 5
Люди уже определили как перегрузку, так и переопределение, поэтому я не буду разбираться.
ASAFE спросил:
единственное преимущество [перегрузки] - вы не думаете в нескольких именах для функций?
1. Вам не нужно думать по нескольким именам
И это уже мощное преимущество, не так ли?
Позвольте сравнить с известными функциями C API и их вымышленными вариантами 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) ;
Это означает две вещи: во-первых, вы должны указать компилятору тип данных, которые он будет подавать в функцию, выбрав нужную функцию. Два, если вы хотите расширить его, вам нужно найти причудливые имена, и пользователь ваших функций должен будет запомнить правильные причудливые имена.
И все, что он хотел, было иметь абсолютную ценность некоторой числовой переменной...
Одно действие означает одно и только одно имя функции.
Обратите внимание, что вы не ограничены, чтобы изменить тип одного параметра. Все может измениться, если это имеет смысл.
2. Для операторов это обязательно
Пусть вид операторов:
// 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)
}
В приведенном выше примере вы хотите избежать использования чего-либо другого, кроме оператора +.
Обратите внимание, что C имеет неявную перегрузку оператора для встроенных типов (включая сложный тип C99):
/* C */
void doSomething(void)
{
char c = 32 ;
short s = 54 ;
c + s ; /* == C++ operator + (char, short) */
c + c ; /* == C++ operator + (char, char) */
}
Таким образом, даже в не-предметных языках эта вещь перегрузки используется.
3. Для объектов обязательно
Давайте рассмотрим использование базовых методов объекта: его конструкторы:
class MyString
{
public :
MyString(char character) ;
MyString(int number) ;
MyString(const char * c_style_string) ;
MyString(const MyString * mySring) ;
// etc.
} ;
Некоторые могут рассматривать эту функцию как перегрузку функций, но на самом деле она больше похожа на перегрузку оператора:
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" ;
}
Вывод: перегрузка - это круто
В C, когда вы указываете имя функции, параметры неявно являются частью подписи при вызове. Если у вас есть "double fabs (double d)", то в то время как подпись fabs для компилятора - это неупорядоченные "fabs", это означает, что вы должны знать, что он принимает только удвоения.
В C++ имя функции не означает, что ее подпись принудительно. Его подпись при вызове - это его имя и его параметры. Таким образом, если вы напишете abs (-24), компилятор будет знать, что перегрузка абс, который он должен вызвать, и вы, когда пишете его, считаете его более естественным: вы хотите абсолютное значение -24.
Во всяком случае, любой, кто закодирован на любом языке с операторами, уже использует перегрузку, будь то C или базовые числовые операторы, конкатенация строки Java, делегаты С# и т.д. Почему? потому что это более естественно.
И примеры, показанные выше, являются лишь верхушкой айсберга: при использовании шаблонов перегрузка становится очень полезной, но это уже другая история.
Ответ 6
Пример учебника - это класс Animal с методом speak(). Подкласс Dog overrides говорит(), чтобы "закопать", в то время как подкласс Cat подменяет speak() на "мяу".
Ответ 7
Одно использование перегрузки предназначено для использования в шаблонах. В шаблонах вы пишете код, который можно использовать для разных типов данных, и вызывайте его разными типами. Если функции, которые принимают разные аргументы, должны быть названы по-разному, код для разных типов данных в общем случае должен быть другим, а шаблоны просто не будут работать.
Пока вы еще не можете писать шаблоны, вы почти наверняка используете некоторые из них. Потоки - это шаблоны, а также векторы. Без перегрузки и, следовательно, без шаблонов вам нужно будет вызывать потоки Unicode, отличные от потоков ASCII, и вам придется использовать массивы и указатели вместо векторов.