質問

はい、それらの違いを理解しています。私が知りたいのは、なぜメソッドをオーバーライドするのですか?それを行うことの良い点は何ですか? オーバーロードの場合:唯一の利点は、関数とは別の名前で考える必要がないことです。

役に立ちましたか?

解決

通常、

オーバーロードとは、同じスコープ内に同じ名前を持つ2つ以上の関数があることを意味します。呼び出しが行われたときに引数によりよく一致する関数が勝ち、呼び出されます。仮想関数を呼び出すのとは対照的に、重要なのは、呼び出される関数がコンパイル時に選択されることです。それはすべて、引数の静的型に依存します。 B のオーバーロードと D のオーバーロードがあり、引数が B への参照であるが、実際には B > D オブジェクトの場合、C ++では B のオーバーロードが選択されます。これは、動的ディスパッチではなく、静的ディスパッチと呼ばれます。同じ名前を持つ別の関数と同じことを行いたいが、別の引数型に対してはそれを行いたい場合はオーバーロードします。例:

void print(Foo const& f) {
    // print a foo
}

void print(Bar const& bar) {
    // print a bar
}

両方とも引数を出力するため、オーバーロードされます。しかし、最初はfooを出力し、2番目はバーを出力します。異なることを行う2つの関数がある場合、それらが同じ名前を持っているとスタイルが悪いと見なされます。関数を呼び出すときに実際に何が起こるかについて混乱を招く可能性があるためです。オーバーロードのもう1つのユースケースは、関数の追加パラメーターがあるが、制御を他の関数に転送する場合です。

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 メンバー関数を呼び出すと、派生の印刷関数が常に呼び出されます。これは、ベースの1つをオーバーライドするためです。関数 print が仮想ではない場合、派生の関数はベース関数をオーバーライドせず、単に hide します。オーバーライドは、基本クラスとそれから派生したすべてのクラスを受け入れる関数がある場合に役立ちます。

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

今、コンパイル時にコンパイラはbが少なくともbaseであることしか認識していない場合でも、派生クラスの出力が呼び出されます。それが仮想機能のポイントです。それらがなければ、ベースの印刷関数が呼び出され、派生クラスの印刷関数はそれをオーバーライドしません。

他のヒント

これにより、考えがより明確になります。ここに画像の説明を入力

次の3つの理由で機能をオーバーロードします:

  1. 同様の密接に関連することを実行する2つ(またはそれ以上)の関数を提供し、受け入れる関数のタイプや引数の数で区別します。考案された例:

    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. 同じアクションを実行する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. 2つ(またはそれ以上)の異なる入力タイプが与えられた場合、同等のアクションを実行する機能を提供します。考案された例:

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

一部の場合、異なる名前の関数がオーバーロードされた関数よりも良い選択であると主張する価値があります。コンストラクターの場合、オーバーロードが唯一の選択肢です。


関数の乗馬 はまったく異なり、まったく異なる目的に役立ちます。関数のオーバーライドは、C ++でのポリモーフィズムの動作です。関数をオーバーライドして、派生クラスでその関数の動作を変更します。このようにして、基本クラスはインターフェースを提供し、派生クラスは実装を提供します。

オーバーライドは、基本クラスから継承し、その機能を拡張または変更する場合に役立ちます。オブジェクトが基本クラスとしてキャストされる場合でも、基本ではなくオーバーライドされた関数を呼び出します。

オーバーロードは必要ありませんが、場合によっては作業が楽になり、読みやすくなります。おそらくそれは悪化させる可能性がありますが、それは使用すべきではないときです。たとえば、同じ操作を実行するが、異なる種類の操作を行う2つの関数を使用できます。たとえば、 Divide(float、float) Divide(int、int)とは異なるはずですが、基本的には同じ操作です。 「DivideFloat」、「DivideInt」、「DivideIntByFloat」などを覚えておく必要があるのではなく、「Divide」というメソッド名を覚えてみませんか?

人々はすでにオーバーロードとオーバーライドの両方を定義しているので、詳しく説明しません。

  
    

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つのことを意味します。1つは、適切な関数を選択して、関数に渡すデータの型をコンパイラーに伝える必要があります。 2つ目は、拡張したい場合は派手な名前を見つける必要があり、関数のユーザーは正しい派手な名前を覚えておく必要があります。

そして、彼/彼女が望んだのは、ある数値変数の絶対値を持つことだけでした...

1つのアクションとは、1つの関数名のみを意味します。

1つのパラメーターのタイプを変更することに限定されないことに注意してください。意味があれば、何でも変更できます。

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では、関数の名前を指定すると、パラメーターは呼び出し時に暗黙的に署名の一部になります。 &quot; double fabs(double d)&quot;がある場合、コンパイラのfabsのシグネチャは装飾されていない&quot; fabs&quot;ですが、 you はdoubleのみを必要とすることを知っている必要があることを意味します。

C ++では、関数の名前はその署名が強制されることを意味しません。呼び出し時の署名は、名前とパラメーターです。したがって、abs(-24)を記述した場合、コンパイラはそれが呼び出す必要のあるabsのオーバーロードを認識し、それを記述するときに、より自然になります。絶対値-24が必要です。

とにかく、演算子を使用して任意の言語である程度コーディングした人は、Cまたは基本的な数値演算子、Java文字列連結、C#デリゲートなど、すでにオーバーロードを使用しています。 より自然だから

上記の例は氷山の一角に過ぎません。テンプレートを使用する場合、オーバーロードは非常に便利になりますが、これは別の話です。

教科書の例は、メソッドspeak()を持つクラスAnimalです。 Dogサブクラスはspeak()を&quot; bark&quot;にオーバーライドします。 Catサブクラスはspeak()を&quot; meow&quot;にオーバーライドします。

オーバーロードの使用法の1つは、テンプレートでの使用です。テンプレートでは、異なるデータ型で使用できるコードを記述し、異なる型で呼び出します。異なる引数を取る関数に異なる名前を付ける必要がある場合、一般に異なるデータ型のコードは異なる必要があり、テンプレートは機能しません。

まだテンプレートを書いていないかもしれませんが、ほとんど確実にテンプレートのいくつかを使用しています。ストリームはテンプレートであり、ベクターでもあります。オーバーロードなしで、したがってテンプレートなしで、UnicodeストリームをASCIIストリームとは異なるものを呼び出す必要があり、ベクターの代わりに配列とポインターを使用する必要があります。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top