Переопределение и перегрузка в C ++ - PullRequest
30 голосов
/ 09 января 2009

Да, я понимаю разницу между ними. То, что я хочу знать, это: почему ПЕРЕГРУЗИТЬ метод? Что хорошего в этом? В случае перегрузки: единственное преимущество в том, что вам не нужно думать о разных именах функций?

Ответы [ 7 ]

67 голосов
/ 09 января 2009

Перегрузка обычно означает, что у вас есть две или более функции в одной и той же области действия, имеющие одно и то же имя. Функция, которая лучше соответствует аргументам, когда вызов сделан, выигрывает и вызывается. Важно отметить, что в отличие от вызова виртуальной функции, вызываемая функция выбирается во время компиляции. Все зависит от статического типа аргумента. Если у вас есть перегрузка для B и одна для D, а аргумент является ссылкой на B, но он действительно указывает на объект D, тогда перегрузка для B выбрана в C ++. Это называется статическая отправка , а не динамическая отправка . Вы перегружаетесь, если хотите сделать то же самое, что и другая функция с таким же именем, но вы хотите сделать это для другого типа аргумента. Пример:

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 не была виртуальной, то функция в производной не переопределяла бы базовую функцию, а просто скрывала бы ее . Переопределение может быть полезно, если у вас есть функция, которая принимает базовый класс, и каждый, который является производным от него:

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

Теперь, хотя во время компиляции компилятор знает только, что b является хотя бы базовым, вызывается print производного класса. В этом смысл виртуальных функций. Без них будет вызвана функция печати базы, и функция в производном классе не переопределит ее.

27 голосов
/ 10 июня 2014

Это добавит ясности к мыслям. enter image description here

18 голосов
/ 09 января 2009

Вы превысили загружаете функции по трем причинам:

  1. Предоставить две (или более) функции, которые выполняют похожие, тесно связанные вещи, дифференцированные по типам и / или количеству аргументов, которые он принимает. Придуманный пример:

    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. Предоставить два (или более) способа выполнения одного и того же действия. Придуманный пример:

    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. Для обеспечения возможности выполнения эквивалентного действия с двумя (или более) различными типами ввода. Придуманный пример:

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

В некоторых случаях стоит утверждать, что функция с другим именем является лучшим выбором, чем перегруженная функция. В случае конструкторов, перегрузка является единственным выбором.


Более верхом функция совершенно иная и служит совершенно другой цели. Переопределение функций - это то, как полиморфизм работает в C ++. Вы переопределяете функцию, чтобы изменить поведение этой функции в производном классе. Таким образом, базовый класс обеспечивает интерфейс, а производный класс обеспечивает реализацию.

4 голосов
/ 09 января 2009

Переопределение полезно, когда вы наследуете от базового класса и хотите расширить или изменить его функциональность. Даже когда объект приводится как базовый класс, он вызывает вашу переопределенную функцию, а не базовую.

Перегрузка не обязательна, но иногда она делает жизнь легче или более читабельной. Можно утверждать, что это может сделать хуже, но это когда это не должно использоваться. Например, вы можете иметь две функции, которые выполняют одну и ту же операцию, но действуют на разные вещи. Например, Divide(float, float) должен отличаться от Divide(int, int), но в основном это одна и та же операция. Разве вы не помните одно имя метода «Divide», чем «DivideFloat», «DivideInt», «DivideIntByFloat» и т. Д.?

3 голосов
/ 10 января 2009

Люди уже определили как перегрузку, так и переопределение, поэтому я не буду уточнять.

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, когда вы даете имя функции, параметры неявно являются частью подписи при вызове. Если у вас есть «двойные fabs (double d)», то, хотя подпись fabs для компилятора - это неокрашенные «fabs», это означает, что you должен знать, что для этого требуется только удвоение.

В C ++ имя функции не означает, что ее подпись является принудительной. Его подпись при вызове - это его имя и параметры. Таким образом, если вы напишите abs (-24), компилятор будет знать, какую перегрузку abs он должен вызвать, и вы, при его написании, найдете это более естественным: вам нужно абсолютное значение -24.

В любом случае, любой, кто хоть немного кодировал на любом языке с операторами, уже использует перегрузку, будь то C или Basic числовые операторы, конкатенация строк Java, делегаты C # и т. Д. Почему? потому что это более естественно .

А приведенные выше примеры являются лишь верхушкой айсберга: при использовании шаблонов перегрузка становится очень и очень полезной, но это уже другая история.

2 голосов
/ 09 января 2009

Пример из учебника - класс Animal с методом speak (). Переопределения подкласса Dog говорят () «лаять», а переопределения подкласса Cat говорят () «мяу».

1 голос
/ 09 января 2009

Одно использование перегрузки для использования в шаблонах. В шаблонах вы пишете код, который можно использовать для разных типов данных, и вызываете его разными типами. Если функции, которые принимают разные аргументы, должны были бы называться по-разному, код для разных типов данных в общем случае должен был бы отличаться, и шаблоны просто не работали бы.

Хотя вы, возможно, еще не пишете шаблоны, вы почти наверняка используете некоторые из них. Потоки - это шаблоны, а также векторы. Без перегрузки и, следовательно, без шаблонов вам нужно будет вызывать потоки Unicode чем-то отличным от потоков ASCII, и вам придется использовать массивы и указатели вместо векторов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...