Подтвердить что ты не робот

Переопределение и перегрузка в С++

Да, я понимаю разницу между ними. Я хочу знать, почему: OVERRIDE метод? Какая польза от этого? В случае перегрузки: единственное преимущество заключается в том, что вы не должны думать в разных именах с функциями?

4b9b3361

Ответ 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

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

Ответ 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, и вам придется использовать массивы и указатели вместо векторов.