はい、私はそれらの違いを理解しています。私が知りたいのは、なぜメソッドをオーバーライドするのですか?それを行うことの良い点は何ですか?オーバーロードの場合:唯一の利点は、関数とは異なる名前で考える必要がないことですか?
オーバーロードは、通常、同じスコープ内に同じ名前の2つ以上の関数があることを意味します。呼び出しが行われたときに、引数によりよく一致する関数が勝ち、呼び出されます。仮想関数の呼び出しとは対照的に、重要なのは、呼び出される関数がコンパイル時に選択されることです。それはすべて引数の静的型に依存します。 B
のオーバーロードとD
のオーバーロードがあり、引数がB
への参照であるが、実際にはD
オブジェクトを指している場合、次に、C++でB
のオーバーロードが選択されます。 動的ディスパッチとは対照的に、静的ディスパッチと呼ばれます。同じ名前を持つ別の関数と同じことをしたいが、別の引数タイプに対してはしたい場合はオーバーロードします。例:
void print(Foo const& f) {
// print a foo
}
void print(Bar const& bar) {
// print a bar
}
どちらも引数を出力するため、オーバーロードされます。しかし、最初はfooを出力し、2番目はバーを出力します。異なることを行う2つの関数がある場合、それらが同じ名前を持っているとスタイルが悪いと見なされます。関数を呼び出すときに実際に何が起こるかについて混乱を招く可能性があるためです。オーバーロードの別のユースケースは、関数に追加のパラメーターがあるが、制御を他の関数に転送する場合です。
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 itをオーバーライドします。オーバーライドは、基本クラスとそれから派生したすべてのクラスを受け入れる関数がある場合に役立ちます。
void doit(base &b) {
// and sometimes, we want to print it
b.print();
}
これで、コンパイル時にコンパイラはbが少なくともベースであることしかわからない場合でも、派生クラスの出力が呼び出されます。それが仮想機能のポイントです。それらがなければ、ベースの印刷関数が呼び出され、派生クラスの印刷関数はそれをオーバーライドしません。
これにより、思考がより明確になります。
オーバーload関数は3つの理由で:
同様の密接に関連することを実行する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つ(またはそれ以上)の方法を提供する。考案された例:
void Plot(Point pt); // plots a point at (pt.x, pt.y)
void Plot(int x, int y); // plots a point at (x, y)
2つ(またはそれ以上)の異なる入力タイプが与えられた場合、同等のアクションを実行する機能を提供します。考案された例:
wchar_t ToUnicode(char c);
std::wstring ToUnicode(std::string s);
someの場合、異なる名前の関数がオーバーロードされた関数よりも良い選択であると主張する価値があります。コンストラクターの場合、オーバーロードが唯一の選択肢です。
Over riding関数はまったく異なり、まったく異なる目的を果たします。関数のオーバーライドは、C++でのポリモーフィズムの動作方法です。関数をオーバーライドして、派生クラスでその関数の動作を変更します。このようにして、基本クラスはインターフェースを提供し、派生クラスは実装を提供します。
オーバーライドは、基本クラスから継承し、その機能を拡張または変更する場合に役立ちます。オブジェクトが基本クラスとしてキャストされる場合でも、基本関数ではなく、オーバーライドされた関数を呼び出します。
オーバーロードは必要ありませんが、場合によっては簡単に、または読みやすくすることができます。おそらくそれは悪化させる可能性がありますが、それは使用すべきではないときです。たとえば、同じ操作を実行するが、異なる種類のことを実行する2つの関数を使用できます。たとえば、Divide(float, float)
はDivide(int, int)
とは異なるはずですが、基本的には同じ操作です。 「DivideFloat」、「DivideInt」、「DivideIntByFloat」などを覚える必要があるのではなく、1つのメソッド名「Divide」を覚えていませんか?
人々はすでにオーバーロードとオーバーライドの両方を定義しているので、詳しく説明しません。
[オーバーロード]の唯一の利点は、いくつかの名前で関数を考えていないことですか?
そして、これはすでに大きな利点ですよね?
既知の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つのパラメーターのタイプを変更することに限定されないことに注意してください。理にかなっている限り、何でも変更できます。
演算子を見てみましょう:
// 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) */
}
したがって、非オブジェクト言語でも、このオーバーロードするものが使用されます。
オブジェクトの基本的なメソッドの使用を見てみましょう:そのコンストラクタ:
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)を記述すると、コンパイラはそれが呼び出す必要のあるabsのオーバーロードを認識し、それを記述するときに、より自然になります。絶対値-24が必要です。
とにかく、演算子を使用して任意の言語である程度コーディングした人は、Cまたは基本的な数値演算子、Java文字列連結、C#デリゲートなど)で既にオーバーロードを使用しています。 より自然だから。
また、上記の例は氷山の一角にすぎません。テンプレートを使用する場合、オーバーロードは非常に便利になりますが、これは別の話です。
教科書の例は、メソッドspeak()を持つAnimalクラスです。 Dogサブクラスはspeak()をオーバーライドして "bark"にし、Catサブクラスはspeak()をオーバーライドして "meow"にします。
オーバーロードの使用法の1つは、テンプレートでの使用です。テンプレートでは、異なるデータ型で使用できるコードを記述し、異なる型で呼び出します。異なる引数を取る関数に異なる名前を付ける必要がある場合、一般に異なるデータ型のコードは異なる必要があり、テンプレートは機能しません。
あなたはまだテンプレートを書いていないかもしれませんが、ほとんど確実にそれらのいくつかを使用しています。ストリームはテンプレートであり、ベクターでもあります。オーバーロードなしで、したがってテンプレートなしで、UnicodeストリームをASCIIストリームとは異なるものと呼ぶ必要があり、ベクトルの代わりに配列とポインターを使用する必要があります。