C++的重写(覆盖)、重载、重定义(隐藏)、多态
“重写(覆盖)、重载、重定义、多态” 绝对称得上是C++里一个比较经典的问题了,下面我们来层层剖析它。
重写(覆盖)override
重写又名覆盖,常见于子类继承父类的时候,子类重写父类的某些方法。
有如下要求:
- 父类中被重写的方法必须是virtual的
- 子类中进行重写操作的那个方法,访问修饰符和父类的可以不同(即根据自身需要选择public、protected、private), 但是返回值、参数列表(参数的顺序、类型)和父类必须相同。
下面是一个重写的例子:
class B
{
private:
virtual void f(int i, char c)
{
cout<<"BBB"<<endl;
}
};
class A:B
{
public:
void f(int j, char ch)
{
cout<<"AAA"<<endl;
}
};
int main()
{
A a;
a.f(2,'a');
return 0;
}
输出为:AAA
重载 overload
重载是指对同一个名称的函数,根据其参数的不同可以执行不同的操作。
举个栗子,下面的几种函数都属于重载,他们的要求就是参数表(参数个数、参数类型、参数顺序)必须至少要有一点不同!
inf f(){...}
int f(int a){...}
int f(char a){...}
int f(int a, char b){...}
....
注意啦,重载强调的是参数表要不同,而如果只改变返回值的类型的话则不属于重载,编译时会报错。比如如下的代码则不算是重载:
int f(){...}
void f(){...}
这个例子中编译会报:error: 'void A::f()' cannot be overloaded
重载还有一个地方需要注意,就是重载很容易产生二义性。
比如:
int f(int a){...}
int f(double a){...}
当你执行f(2)
的时候,编译器不知道你要执行的是哪一个函数,这个时候的调用办法如下:
f((int) 2); // 调用第一个函数
f((double) 2); // 调用第二个函数
重载还可以用来重载运算符,这里就不作细致讨论了。
重定义(隐藏) redefining
重定义又称隐藏,它也常见于子类继承父类的情况中,注意区分重定义和重写的区别。
重定义是子类中有一个和父类方法名一样的方法,且父类中那个方法不能被virtual
修饰,而且子类和父类中这个方法的参数表可以不同。
class A
{
public:
void f()
{
cout<<"AAA"<<endl;
}
};
class B:public A // 这里必须是public或者protected,否则子类访问不了父类的方法
{
public:
void f(int i)
{
cout<<i<<endl;
}
};
int main()
{
B b;
// b.f(); 编译错误,父类方法已被隐藏
b.A::f(); // 想访问父类方法,只能这样
b.f(2); // 这里是调用B类的方法
return 0;
}
多态 polymorphism
多态是基于重写的,因此基类方法必须加virtual。
我们都知道父类指针可以指向子类对象,而多态就是用于给父类指针调用子类重写后的的方法的。
举个例子就明白了:
class A
{
public:
void f() // 非多态
{
cout<<"A-f()"<<endl;
}
virtual void g() // 多态
{
cout<<"A-g()"<<endl;
}
};
class B:public A
{
public:
void f()
{
cout<<"B-f()"<<endl;
}
void g()
{
cout<<"B-g()"<<endl;
}
};
int main()
{
B b;
A *a = &b;
a->f(); // 没有多态,输出为:A-f()
a->g(); // 有多态,输出为:B-g()
return 0;
}
总结
- 重写要求基类方法前加上virtual,且返回值及参数列表均需和基类相同。
- 重载要求参数列表和原函数不同,仅有返回值不同不算重载,使用时需要注意二义性。
- 重定义要求方法名和父类相同,其余可以不同,且此时要调用父类方法时只能采用
::
来调用 - 多态是基于重写的一种程序调用方法,当基类的指针指向子类对象时可以调用子类的方法。