《一》重载
重载的规则:
- 函数名称必须相同。
- 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
- 函数的返回类型可以相同也可以不相同。
- 仅仅返回类型不同不足以成为函数的重载。
- 相同的范围(在同一个类中);
- virtual 关键字可有可无。
为什么需要函数重载?
- 试想如果没有函数重载机制,如在C中,你必须要这样去做:为这个print函数取不同的名字,如print_int、print_string。这里还只是两个的情况,如果是很多个的话,就需要为实现同一个功能的函数取很多个名字,如加入打印long型、char*、各种类型的数组等等。这样做很不友好!
- 类的构造函数跟类名相同,也就是说:构造函数都同名。如果没有函数重载机制,要想实例化不同的对象,那是相当的麻烦!
- 操作符重载,本质上就是函数重载,它大大丰富了已有操作符的含义,方便使用,如+可用于连接字符串等!
重载函数的匹配原则:
- 严格匹配,找到后就调用。
- 没有找到严格匹配的函数,但是可以通过隐式转化,找到适合的重载函数。
函数重载的原理:
编译器对重载函数的命名规则为:作用域+返回类型+函数名+参数列表
比如:
void print(int i) --> _ZN4test5printEi
void print(char c) --> _ZN4test5printEc
《二》隐藏
隐藏的定义:隐藏是指派生类的函数屏蔽了与其同名的基类函数。(看似无实际有)
隐藏规则的底层原因其实是C++的名字解析过程。
在继承机制下,派生类的类域被嵌套在基类的类域中。派生类的名字解析过程如下:
- 首先在派生类类域中查找该名字。
- 如果第一步中没有成功查找到该名字,即在派生类的类域中无法对该名字进行解析,则编译器在外围基类类域对查找该名字的定义。
隐藏的规则:
- 不同的作用域并且有继承关系
-
不论基类函数是否为virtual,只要有派生类函数与其同名但不同参,该基类函数在派生类中将被隐藏。
-
基类函数非virtual,有派生类函数与此基类函数同名同参,此时该基类函数在派生类中将被隐藏。
如何访问被隐藏的成员:
- 用using关键字,自定义命名空间一节提到using可将一个作用域中的名字引入另一个作用域中;它的另一个用法是”using Base::fun”,这样派生类中如果定义同名但不同参的函数,基类函数将不会被隐藏,两个函数并存在派生类域中形成新的重载,
class Base
{
public:
void fun1(int a){ cout<<"Base fun1(int a)"<endl; }
};
class Drv:publicBase
{
public:
using Base::fun1; //这句使基类中名为fun1的系列函数不再被株连(只可能被隐藏同名同参的一个)
void fun1(char *x){cout<<"Drv fun1(char *x) !"<<endl; }
};
void main(){
Drv dr;
dr.fun(1);
}
运行输出: Base fun1(int a)
- 用域限定符::来定位继承自Base却被派生类隐藏的成员,如上例将dr.fun1(1);改为dr.Base::fun1(1);即可,这种方法能调用所有被隐藏的基类成员变量和函数。
《三》覆盖 (覆盖发生在虚表中,看似无实际也无)
覆盖的定义:
- 不同的范围(分别位于派生类与基类);
- 函数名字相同;
- 参数相同;
- 基类函数必须有virtual 关键字
class Base
{
public:
Base(int a) :ma(a){}
~Base(){}
virtual void Show()
{
cout << "Base::Show()" << endl;
}
virtual void Show(int flag)
{
cout << "Base::Show(int)" << endl;
}
public:
int ma;
};
class Derive :public Base
{
public:
Derive(int b = 0) :mb(b), Base(b){}
~Derive(){}
void Show()
{
cout << "Derive::Show()" << endl;
}
public:
int mb;
};
int main()
{
Derive* pd = new Derive(10);
Base* pbase2 = pd;
pbase2->Show();
return 0;
}
运行结果:
此时在虚表中发生了覆盖,派生类中的show()函数覆盖了基类中的show()函数
此时派生类的内存布局: