C++入门--函数覆盖
- 函数重载
- 函数名相同
- 参数列表(个数/类型/顺序)不同
- 相同作用域
- 函数重载不考虑返回值的不同
- 函数隐藏
- 作用域不同
- 函数名相同
- 参数和返回值不考虑
- 函数覆盖(虚函数)
- 作用域不同(父子类之间的继承关系)
- 函数名,参数列表(参数个数/顺序/类型),返回值,调用约定必须相同
- 有virtual关键字
看一个例子:
1 #include <stdlib.h> 2 3 #include <iostream> 4 #include <string> 5 using namespace std; 6 7 class CPerson { 8 public: 9 void speak() { cout << "speak" << endl; } 10 }; 11 12 class CChinese : public CPerson { 13 public: 14 virtual void speak() { cout << "speak Chinese" << endl; } 15 }; 16 17 int main(int argc, char const* argv[]) { 18 19 CPerson per; 20 int nPersonSize = sizeof(CPerson); 21 cout << nPersonSize << endl; 22 23 return 0; 24 }
CPerson类仅有一个非虚函数的speak方法时,获取CPerson的大小
nPersonSize = 1; //1字节为一个占位符
将CPerson的speak声明为virtual虚函数
virtual void speak() { cout << "speak" << endl; }
此时CPerson的大小:
nPersonSize = 4; //虽然没有成员变量,但是有virtual关键字,有4字节的虚表指针
二、虚表中虚函数的顺序:
1.子类继承了所有父类虚函数(公有)
2.父类的虚函数顺序决定了子类虚函数的顺序
3.子类重写了父类的某虚函数,则会在子类自己的虚表中覆盖对应位置的函数
4.子类未重写某虚函数,则直接继承父类的该虚函数
5.子类自己的虚函数会出现在前面父类所有虚函数后面
三、虚函数的直接调用与间接调用
1、直接调用
根据函数名称,直接调用该函数(编译器在编译时候就确定了其跳转地址)
1)普通函数的调用
2)对象的普通成员函数的调用
3)对象的虚函数的调用
2、间接调用(虚调用,通过查虚表来调用)
虚函数,通过查找对象的虚表下标来调用函数的方法(在运行时期确定调用谁)
1)通过对象的指针调用虚函数
CChinese* pChs = &chs;
pChs->foo();
2)通过对象的引用调用虚函数
CChinese& pChs = &chs;
pChs->foo();
3)以上两情况下,若明确了类域范围,则为直接调用
pChs->CChinese::foo();
四、函数覆盖隐藏重载
看以下示例,判断main输出结果
#include <stdlib.h> #include <iostream> #include <string> using namespace std; class Base { public: virtual void Handle1(float x) { cout << "Base::Handle1(float)" << endl; } void Handle2(float x) { cout << "Base::Handle2(float)" << endl; } void Handle3(float x) { cout << "Base::Handle3(float)" << endl; } }; class Derived : public Base { public: virtual void Handle1(float x) { cout << "Derived::Handle1(float)" << endl; } void Handle2(int x) { cout << "Derived::Handle2(int)" << endl; } void Handle3(float x) { cout << "Derived::Handle3(float)" << endl; } void Handle3(double x) { cout << "Derived::Handle3(double)" << endl; } }; int main(int argc, char const *argv[]) { Derived DervObj; //定义了一个子类的对象 //把子类对象传递给了父类,得到父类指针,子类指针强转父类指针是安全的 Base *pBase = &DervObj; Derived *pDerv = &DervObj; //得到子类指针 pBase->Handle1(3.14f); pDerv->Handle1(3.14f); pBase->Handle2(3.14f); pDerv->Handle2(3.14f); pBase->Handle3(3.14f); pDerv->Handle3(3.14f); pDerv->Handle3(3.14); return 0; }
分析以下:
- 子类的1与父类的handle1,作用域不同(父子继承关系) ,函数名参数返回值都相同,且为虚函数,故为函数覆盖
- 看父子的Handle2、3,作用域不同,函数名相同,参数不管,为函数隐藏
- 看子类的Handle3,作用域相同,函数名相同,参数不同,为函数重载
// 间接调用:查表(查找子类的虚表),子类对象有函数覆盖,调用子类的Handle1 pBase->Handle1(3.14f); //间接调用:查表(查找子类的虚表),子类对象有函数覆盖,调用子类的Handle1 pDerv->Handle1(3.14f);