是的,我确实理解它们之间的区别。我想知道的是:为什么超越一种方法?这样做有什么好处? 在超载的情况下:唯一的好处是你不必用不同的名称来思考函数吗?
答案 0 :(得分:67)
重载通常意味着您在同一作用域中具有两个或多个具有相同名称的函数。在进行调用时更好地匹配参数的函数获胜并被调用。需要注意的是,与调用虚函数相反,重要的是在编译时选择被调用的函数。这一切都取决于参数的静态类型。如果你有B
的重载和D
的重载,并且参数是对B
的引用,但它确实指向D
对象,那么重载为在C ++中选择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
成员函数,则总是会调用derived的print函数,因为它会覆盖其中一个。如果函数print
不是虚拟的,那么派生的函数不会覆盖基函数,而只是隐藏它。如果你有一个接受基类的函数,并且每个函数都是从它派生的,那么覆盖会很有用:
void doit(base &b) {
// and sometimes, we want to print it
b.print();
}
现在,即使在编译时编译器只知道b至少是base,也会调用派生类的print。这就是虚拟功能的关键。如果没有它们,将调用基类的print函数,派生类中的函数不会覆盖它。
答案 1 :(得分:27)
这将为思想增添更多清晰度。
答案 2 :(得分:18)
您通过加载功能有三个原因:
提供执行类似的,密切相关的事物的两个(或更多)函数,由它接受的参数的类型和/或数量来区分。举例:
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 ++中的工作原理。您重写一个函数以更改派生类中该函数的行为。通过这种方式,基类提供了接口,派生类提供了实现。
答案 3 :(得分:4)
当您从基类继承并希望扩展或修改其功能时,覆盖很有用。即使将对象转换为基类,它也会调用被覆盖的函数,而不是基函数。
不需要重载,但它确实有时会让生活更轻松或更具可读性。可以说它可以使情况变得更糟,但那时就不应该使用它。例如,您可以使用两个执行相同操作的函数,但可以处理不同类型的操作。例如Divide(float, float)
应该与Divide(int, int)
不同,但它们基本上是相同的操作。难道你不记得一个方法名称“Divide”,而不是要记住“DivideFloat”,“DivideInt”,“DivideIntByFloat”等等吗?
答案 4 :(得分:3)
人们已经定义了重载和覆盖,所以我不会详细说明。
ASAFE问道:
[重载]的唯一优势是你没有想到几个名称的功能?
这已经是一个强大的优势,不是吗?
让我们与已知的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) ;
这意味着两件事:第一,你必须通过选择正确的函数告诉编译器它将为函数提供的数据类型。第二,如果你想扩展它,你需要找到花哨的名字,你的功能用户必须记住正确的名字。
他/她想要的只是拥有一些数值变量的绝对值......
一个动作表示一个且只有一个功能名称。
请注意,您不仅可以更改一个参数的类型。只要有意义,任何事情都可以改变。
让我们看看运营商:
// 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#代理等等。为什么? 因为它更自然。
上面显示的例子只是冰山一角:当使用模板时,重载变得非常有用,但这是另一个故事。
答案 5 :(得分:2)
教科书示例是使用方法speak()的Animal类。 Dog子类覆盖speak()为“bark”,而Cat子类覆盖speak()为“meow”。
答案 6 :(得分:1)
重载的一个用途是在模板中使用。在模板中,您编写可用于不同数据类型的代码,并使用不同类型调用它。如果采用不同参数的函数必须以不同方式命名,则不同数据类型的代码通常必须不同,模板才不会有效。
虽然你可能还没有编写模板,但你几乎肯定会使用其中的一些模板。流是模板,矢量也是模板。没有重载,因此没有模板,你需要调用与ASCII流不同的Unicode流,你必须使用数组和指针而不是向量。