C++学习笔记-----继承体系中函数的重载,覆盖和隐藏的区别
2016-12-20 18:24
483 查看
1.重载,在同一个作用域中定义的同名不同参的一些函数为重载。
2.隐藏,若在基类中定义了某一non_virtual函数,在派生类重存在同名函数(不需要参数列表相同),基类的该函数在派生类中是不可见的,如若用派生类的实例化对象调用基类的该函数会编译错误。此种情况即为隐藏,指基类的函数在派生类中隐藏了,编译器找不到这个函数。
3.覆盖,如果基类中的某一函数被声明为虚函数,那么在派生类中如果重新定义了这个虚函数,那么我们说该函数在派生类中被覆盖了。用指向派生类的基类指针访问该函数只能调用派生类的该函数。
本例中,我们定义了基类的实例化对象b,派生类的实例化对象d。
基类的实例化对象调用自身的函数通过编译,我们说在基类中f函数被重载了。
当然,如果非要在上述条件中使用基类被隐藏的函数话,可以使用using关键字来进行标识,具体做法如下:
这样就可以用派生类的实例化对象调用三个f函数了。
上述内容对于虚函数同样适用,但只适用于用实例化对象访问成员函数的情况,
而对于使用多态指针的情况,即本例中定义的pb变量,请看下面的测试:
我们发现,如果调用的不是虚函数,使用指针的情况和实例化对象是相同的,编译器访问的作用域就是该指针的静态类型,本例中pb的静态类型是Base类型,因为Base中没有接收两个参数的f函数,所以不能通过编译。
下面讨论多态指针和虚函数的情况,我们需要改造一下类的定义:
这个例子中在基类中增加了函数名为g的虚函数,分别是不带参数和带一个参数,如前所述,这两个函数在基类中被重载。
现在讨论用指针来访问成员函数的问题,此时pb是一个指向派生类的基类指针:
编译器在处理指针问题的时候,会先判断该指针指向的对象类型,本例中指向的是派生类,所以先去派生类的作用域中寻找最佳匹配的函数(即函数名和参数列表都匹配),因为我们在派生类中定义了不带参数的g函数,所以可以通过编译,输出"Derive"。
在第一种情况下,如果编译器没有找到匹配的函数,会向上进入到其基类的作用域中寻找,本例在基类中找到带有一个参数的g函数,所以通过编译,输出"Base1"。
如果按照前面两条所述,那么这条语句按理说应该会通过编译才对,因为在派生类中找到了这样的函数。
但是这只是表面现象,编译器真的可以找到这样的函数吗。我们知道,在进行多态调用函数的时候是通过虚函数表来寻找匹配函数的。在定义了一个指向派生类的基类指针时,会在分配到的内存最开始处存放所有基类虚函数的地址,然后扫描派生类,如果派生类重新定义了某个虚函数,那么会更新虚函数表,将表中这个虚函数的地址替换成派生类中该虚函数的地址。也就是之前说的覆盖。
然而如果在基类中本来就不存在某一个虚函数,而在派生类中却存在这样的虚函数,那么扫描派生类的时候就不会更新虚函数表,表中就没有该函数的地址,编译器自然是找不到这个函数的。所以无法通过编译。
可以理解为指向派生类的基类指针可以调用派生类的虚函数,但是这些虚函数必须在基类中同样存在。
2.隐藏,若在基类中定义了某一non_virtual函数,在派生类重存在同名函数(不需要参数列表相同),基类的该函数在派生类中是不可见的,如若用派生类的实例化对象调用基类的该函数会编译错误。此种情况即为隐藏,指基类的函数在派生类中隐藏了,编译器找不到这个函数。
3.覆盖,如果基类中的某一函数被声明为虚函数,那么在派生类中如果重新定义了这个虚函数,那么我们说该函数在派生类中被覆盖了。用指向派生类的基类指针访问该函数只能调用派生类的该函数。
class Base { public: Base() { std::cout << "Base Constructor" << std::endl; } virtual ~Base() { std::cout << "Base Destructor" << std::endl; } public: void f() //不带参数的f函数 { cout << 0 << endl; } void f(int a) //带一个参数的f函数 { cout << a << endl; } protected: std::string Bmsg; }; class Derive : public Base { public: Derive() { std::cout << "Derive Constructor" << std::endl; } ~Derive() { std::cout << "Derive Destructor" << std::endl; } public: void f(int a, int b) //带两个参数的f函数 { cout << a + b << endl; } private: std::string Dmsg; }; int main() { Base b; std::cout << std::endl; Derive d; std::cout << std::endl; Base * pb = new Derive; std::cout << std::endl; b.f(); //OK b.f(1); //OK d.f(); //error d.f(1); //error d.f(1, 2); //OK delete pb; pb = NULL; return 0; }
本例中,我们定义了基类的实例化对象b,派生类的实例化对象d。
Base b;
Derive d;
b.f(); //OK b.f(1); //OK
基类的实例化对象调用自身的函数通过编译,我们说在基类中f函数被重载了。
d.f(); //error d.f(1); //error通过派生类的实例化对象调用基类的f函数,因为在派生类中额外定义了一个同名函数f,我们说在派生类中基类的f函数被隐藏了,从而在派生类中这些函数不可见,编译器找不到这些函数,所以无法调用。
d.f(1, 2); //OK通过派生类的对象调用自身的函数通过编译,如前所述,这个函数的定义导致基类的同名函数被隐藏了。
当然,如果非要在上述条件中使用基类被隐藏的函数话,可以使用using关键字来进行标识,具体做法如下:
public: using Base::f;像这样,因为f是public作用域中,所以我们在派生类的public作用域中添加using Base::f;这条语句就相当于我们认为地告诉编译器基类中的f函数是可见的。
d.f(); d.f(1); d.f(1, 2);
这样就可以用派生类的实例化对象调用三个f函数了。
上述内容对于虚函数同样适用,但只适用于用实例化对象访问成员函数的情况,
而对于使用多态指针的情况,即本例中定义的pb变量,请看下面的测试:
pb->f(); //OK pb->f(1); //OK pb->f(1, 2); //error
我们发现,如果调用的不是虚函数,使用指针的情况和实例化对象是相同的,编译器访问的作用域就是该指针的静态类型,本例中pb的静态类型是Base类型,因为Base中没有接收两个参数的f函数,所以不能通过编译。
下面讨论多态指针和虚函数的情况,我们需要改造一下类的定义:
#include <iostream> using namespace std; class Base { public: Base() { std::cout << "Base Constructor" << std::endl; } virtual ~Base() { std::cout << "Base Destructor" << std::endl; } public: void f() { cout << 0 << endl; } void f(int a) { cout << a << endl; } virtual void g() //虚函数,不带参数 { cout << "Base" << endl; } virtual void g(int a) //虚函数,带一个参数 { cout << "Base" << a << endl; } protected: std::string Bmsg; }; class Derive : public Base { public: Derive() { std::cout << "Derive Constructor" << std::endl; } ~Derive() { std::cout << "Derive Destructor" << std::endl; } public: //using Base::f; void f(int a, int b) { cout << a + b << endl; } void g() //派生类中继承虚函数性质,不带参数 { cout << "Derive" << endl; } void g(int a, int b) //同上,带两个参数 { cout << "Derive" << a << " " << b << endl; } private: std::string Dmsg; }; int main() { Base b; std::cout << std::endl; Derive d; std::cout << std::endl; Base * pb = new Derive; std::cout << std::endl; pb->g(); //输出"Derive" pb->g(1); //输出"Base1" pb->g(1, 2); //error delete pb; pb = NULL; return 0; }
这个例子中在基类中增加了函数名为g的虚函数,分别是不带参数和带一个参数,如前所述,这两个函数在基类中被重载。
现在讨论用指针来访问成员函数的问题,此时pb是一个指向派生类的基类指针:
pb->g(); //输出"Derive"
编译器在处理指针问题的时候,会先判断该指针指向的对象类型,本例中指向的是派生类,所以先去派生类的作用域中寻找最佳匹配的函数(即函数名和参数列表都匹配),因为我们在派生类中定义了不带参数的g函数,所以可以通过编译,输出"Derive"。
pb->g(1); //输出"Base1"
在第一种情况下,如果编译器没有找到匹配的函数,会向上进入到其基类的作用域中寻找,本例在基类中找到带有一个参数的g函数,所以通过编译,输出"Base1"。
pb->g(1, 2); //error
如果按照前面两条所述,那么这条语句按理说应该会通过编译才对,因为在派生类中找到了这样的函数。
但是这只是表面现象,编译器真的可以找到这样的函数吗。我们知道,在进行多态调用函数的时候是通过虚函数表来寻找匹配函数的。在定义了一个指向派生类的基类指针时,会在分配到的内存最开始处存放所有基类虚函数的地址,然后扫描派生类,如果派生类重新定义了某个虚函数,那么会更新虚函数表,将表中这个虚函数的地址替换成派生类中该虚函数的地址。也就是之前说的覆盖。
然而如果在基类中本来就不存在某一个虚函数,而在派生类中却存在这样的虚函数,那么扫描派生类的时候就不会更新虚函数表,表中就没有该函数的地址,编译器自然是找不到这个函数的。所以无法通过编译。
可以理解为指向派生类的基类指针可以调用派生类的虚函数,但是这些虚函数必须在基类中同样存在。
相关文章推荐
- C/C++日常学习总结(第六篇)多基派生引起的虚函数访问二义性问题&重载,覆盖,隐藏的区别
- 转---C++学习之多态及重载(overload),覆盖(override),隐藏(hide)的区别
- C++成员函数重载、覆盖和隐藏的区别
- c++成员函数的重载、覆盖、隐藏区别
- C++成员函数重载、覆盖和隐藏的区别
- C/C++知识要点3——类成员函数的重载、覆盖和隐藏的区别
- C++函数的重载、覆盖和隐藏区别
- 虚函数与多态性,虚拟继承,纯虚函数及重载、覆盖、隐藏的区别
- c++成员函数的重载、覆盖、隐藏区别
- C++学习笔记之覆盖、重载、多态的区别
- c++成员函数的重载、覆盖、隐藏区别
- 【C++学习笔记】函数重载和函数覆盖
- C++中成员函数的重载、覆盖和隐藏的区别
- C++成员函数的重载,继承,覆盖和隐藏
- c++成员函数的重载、覆盖、隐藏区别
- C++学习之多态及重载(overload),覆盖(override),隐藏(hide)的区别
- c++成员函数的重载、覆盖、隐藏区别
- c++成员函数的重载、覆盖、隐藏区别
- c++中多态函数以及函数重载,覆盖,遮蔽(隐藏)的区别
- c++成员函数的重载、覆盖、隐藏区别