一、派生类的作用域嵌套在其基类的作用域之内
- 例如:有下面的代码
class Quote {
public:
double isbn() {
return 3.14;
}
};
class Disc_quote :public Quote {};
class Bulk_quote :public Disc_quote {};
int main()
{
Bulk_quote bulk;
cout << bulk.isbn();
return 0;
}
- bulk对象在调用isbn()函数时,会:
- 从Bulk_quote类中查找是否有该函数,发现没有
- 进一步查找Disc_quote类,发现Disc_quote类中也没有该函数
- 进一步查找Quote,发现存在,于是执行Quote中的isbn()函数
二、在编译时进行名字查找
- 一个对象、引用、或指针的静态类型决定了该对象的哪些成员是可见的。即使静态类型与动态类型可能不一致(例如使用基类的指针/引用指向于派生类)
- 举个例子:
class Quote {};
class Disc_quote :public Quote {
public:
std::pair<size_t, double> discount_policy()const {}
};
class Bulk_quote :public Disc_quote {};
int main()
{
Bulk_quote bulk;
Bulk_quote *bulkP = &bulk;
Quote *itemP = &bulk;
bulkP->discount_policy(); //正确,bulkP的类型是Bulk_quote
itemP->discount_policy(); //错误,itemP的类型是Quote
return 0;
}
- 上述代码错误的那一行是因为使用基类的指针指向于派生类,但是通过这个指针调用了属于派生的成员(但是基类中没有定义),该成员对于基类来说是不可见的
三、隐藏
- 当子类定义出的成员变量、方法与父类的重名时,父类的会被隐藏
- 详细介绍可参阅:https://blog.csdn.net/qq_41453285/article/details/92713362
四、虚函数的隐藏
- 通过隐藏的知识点我们知道,虚函数为什么在基类与派生类中的参数列表必须一致了。如果参数列表不一致,那么基类的虚函数在派生类中没有被重写,而是被隐藏了(就无法通过基类的引用或指针调用派生类的虚函数了)
- 注意:此处介绍的是虚函数的隐藏,而不是重写(覆盖)
演示案例
class Base { public: virtual int fcn(); //虚函数 }; class D1 :public Base { public: //因为参数列表不一致,因此Base的虚函数没有被覆盖(重写),而是被隐藏了 //所以下面的fcn函数时D1的一个普通成员函数,不是虚函数 int fcn(int); virtual void f2(); //虚函数 }; class D2 :public D1 { public: int fcn(int); //非虚函数,隐藏了D1::fcn(int) int fcn(); //覆盖了Base的虚函数fcn() void f2(); //覆盖了D1的虚函数f2() };
- 下面都是对虚函数的调用
int main() { Base bobj; D1 d1obj; D2 d2obj; Base *pb1 = &bobj; Base *pb2 = &d1obj; Base *pb3 = &d2obj; pb1->fcn(); //虚调用,运行时调用Base::fcn() pb2->fcn(); //虚调用,运行时调用Base::fcn() pb3->fcn(); //虚调用,运行时调用D2::fcn() D1 *d1p = &d1obj; D2 *d2p = &d2obj; d1p->f2(); //虚调用,运行时调用D1::f2() d2p->f2(); //虚调用,运行时调用D2::f2() return 0; }
- 还有以下对于非虚函数fcn(int)的调用
int main() { D2 d2obj; Base *p1 = &d2obj; D1 *p2 = &d2obj; D2 *p3 = &d2obj; //错误p1->fcn(42); Base没有用fcn(int)函数 p2->fcn(42); //静态绑定,调用D1::fcn(int) p3->fcn(42); //静态绑定,调用D2::fcn(int) return 0; }
五、重载函数与隐藏、重写(覆盖)的关系
- 从上面的隐藏知识点来看,对于派生类的函数,不论是成员函数还是虚函数都可以被重载
- 有时一个基类有很多重载函数,而派生类重写了基类的函数,那么默认情况下,基类中的这些所有的重载函数都不可以使用了。见下面的演示案例:
class A {
public:
void func();
void func(int);
void func(string);
};
class B :public A {
public:
void func(); //此时A中的三个func都被覆盖了,只能使用无参数的func版本
};
int main()
{
B b;
b.func(); //正确
//b.func(10); 错误
//b.func("HelloWorld"); 错误
return 0;
}
使用using声明
- 如果我们希望只覆盖基类中的一部分函数,而其他函数在派生类中还没有被覆盖,一种使用方法就是使用前面文章介绍的using声明。这样的话就无须覆盖基类中的每一个重载版本了
- using声明只需要给出名称,而不需要给出参数列表,因此基类中的所有重载函数在派生类中都可以使用了
- 注意:使用using声明时,当using在派生类的不同的访问模式(public、protected、private)下,那么基类的函数在派生类中就属于该访问模式
- 演示案例:
class A { public: void func(); void func(int); void func(string); }; class B :public A { public: void func(); //使用using声明,此时只有无参数的func被覆盖了,而另外两种func在派生类中没有被覆盖 //using处于public下,因此三个func函数都是public的 using A::func; }; int main() { B b; b.func(); //正确 b.func(10); //正确 b.func("HelloWorld");//正确 return 0; }