本文整理我对 C++ 中成员函数重载(overload)、覆盖(override)和隐藏(hide)这三个概念的理解。它们都以"函数名相同"为前提,容易混淆,下面逐一辨析。
隐藏:参数不同的情况
当子类和父类定义了同名函数,但参数列表不同时,父类中的那个函数在子类作用域里变得不可见,这就是隐藏。来看下面的例子:
class Fruit
{
public:
void priName(int s)
{
cout<<"水果"<<endl;
}
};
class Apple :public Fruit
{
public:
void priName(int a,int l)
{
cout<<"苹果"<<endl;
}
};
int main(int argc, char *argv[])
{
Apple* apple=new Apple;
apple->priName(0);
/*父类中的priName(int) 为不可见 也就是隐藏
该处编译器报错,无法找打匹配的函数,但在 Apple中可用using priName 解决访问不了priName(int)
*/
return 0;
}
由于 Apple 中的 priName(int, int) 隐藏了父类的 priName(int),用 apple->priName(0) 调用时编译器找不到匹配的函数,会报错。如果希望父类的重载版本仍然可见,可以在子类中写 using Fruit::priName; 把父类函数引入当前作用域。
隐藏与覆盖:参数相同的情况
当子类和父类的同名函数参数也完全相同时,行为取决于父类函数是否带 virtual:
class Fruit
{
public:
void priName(int s)
{
cout<<"水果"<<endl;
}
};
class Apple :public Fruit
{
public:
void priName(int a)
{
cout<<"苹果"<<endl;
}
};
int main(int argc, char *argv[])
{
Apple* apple=new Apple;
apple->priName(0);//参数相同,调用子类函数,
////////////*****/////
Fruit* apple1=new Apple;
apple1->priName(0);
/*如果带有virtual 重写了 父类函数 那么调用 子类 即覆盖(override)
如果未重写 那么就是调用父类函数,也就是未覆盖,
如果不带virtual 直接就是隐藏 父类,调用子类的函数
*/
return 0;
}
- 如果父类函数带
virtual,子类中同名同参数的函数就构成覆盖(override)。此时用父类指针或引用调用,会根据对象的实际类型走子类版本。 - 如果父类函数不带
virtual,子类函数依然是隐藏而不是覆盖,用父类指针调用时会走父类自己的版本。
重载(overload)
重载指的是同一个作用域内,函数名相同但参数列表不同的多个函数。它的意义是让同一个函数名承担不同的行为。
关键点在于"同一个作用域":跨类(比如父类和子类)的同名但参数不同的函数,不构成重载,而是上面说过的隐藏。
三个概念的辨析
这几个概念都有一个共同特征——函数名相同,所以容易混淆。下面把它们放在一起对比。
重载(overload)
重载必须发生在同一个作用域内,函数名相同但参数不同。它的作用是让同一个函数名表达不同的行为。跨作用域(例如父类和子类之间)的同名函数是无法构成重载的,这是判断重载的重要特征。
覆盖(override)
覆盖指派生类中的虚函数覆盖了基类中同名且参数相同的虚函数。既然与虚函数绑定,覆盖本质上是一种支持多态的机制:通过基类的指针或引用调用虚函数时,会根据对象的实际类型决定真正执行的版本,这样派生类的成员函数就可以"覆盖"基类的同名成员函数。
需要特别注意,构成覆盖要求同名、同参数,并且基类函数带有 virtual 关键字,分别位于派生类和基类中。这是派生类的一个重要特性。而且,由于覆盖与多态挂钩,只有通过类对象的指针或引用调用时才能体现出来。
一句话总结:覆盖函数一定是虚函数,反之不一定。
隐藏(hide)
隐藏指派生类的成员函数把基类的同名成员函数藏了起来。可以这样理解:调用一个类的成员函数时,编译器会沿着继承链自下而上逐级查找函数定义,一旦找到就停止。如果派生类和基类都有同名函数(先不管参数是否相同),编译器最终选中了派生类中的那个版本,就称派生类的成员函数"隐藏"了基类的成员函数——它阻止了编译器继续向上查找。
结合前面对覆盖的定义可知,隐藏关系需要满足:
- 两个函数必须分别位于派生类和基类中;
- 函数必须同名;
- 参数不同时,本身就已经不可能构成覆盖,此时是否是
virtual函数已经不重要,直接构成隐藏; - 参数相同时要看是否有
virtual:有则是覆盖关系,没有就是隐藏关系。