Вопрос

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

Это было полезно?

Решение

Перегрузка обычно означает, что у вас есть две или более функции в одной и той же области действия, имеющие одно и то же имя. Функция, которая лучше соответствует аргументам, когда вызов сделан, выигрывает и вызывается. Важно отметить, что в отличие от вызова виртуальной функции, вызываемая функция выбирается во время компиляции. Все зависит от статического типа аргумента. Если у вас есть перегрузка для 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 производного класса. В этом смысл виртуальных функций. Без них будет вызвана функция печати базы, и функция в производном классе не переопределит ее.

Другие советы

Это добавит ясности к мыслям. введите описание изображения здесь

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

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

    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);
    
  • В некоторых случаях стоит утверждать, что функция с другим именем является лучшим выбором, чем перегруженная функция. В случае конструкторов, перегрузка является единственным выбором.

    <Ч>

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

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

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

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

      
        

    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», это означает, что вы должны знать, что для этого требуется только удвоение.

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

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

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

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

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

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top