对象可以直接获取到自身封装的 普通函数 , 如果要访问虚函数 , 需要增加一次寻址操作 , 因此 这里建议不需要将有 多态 需求的函数声明为 虚函数 ;
C 中 指向某类型对象的 指针 的 运算 , 是 根据 指针类型 进行的 , 指针 自增 , 指针的地址值 会增加 指针类型字节大小 ;
指针的 步长 是 根据 指针 指向的 内存空间 的数据类型确定的 ;
子类 继承 父类 , 如果 子类 没有添加任何 成员函数 与 成员方法 , 那么子类指针 与 父类指针 的步长是相同的 ;
一、不建议将所有函数都声明为 virtual 虚函数
C 类中 , 每个 成员函数 都可以声明为 virtual 虚函数 , 但是 这样会降低 运行效率 , 每次访问 成员函数 时 , 都需要通过 vptr 指针获取 虚函数表 中的函数地址 , 显然会极大的降低效率 ;
如果 调用 非虚函数 , 可以直接通过 对象 获取到 非虚函数 的地址 , 不必通过 vptr 指针 从 虚函数表 中获取 函数地址 ;
显然 , 对象可以直接获取到自身封装的 普通函数 , 如果要访问虚函数 , 需要增加一次寻址操作 , 因此 这里建议不需要将有 多态 需求的函数声明为 虚函数 ;
二、多态的理解层次
多态的理解层次 :
- 多态实现效果 : 相同的代码调用 , 有不同的表现形态 ; 父类指针 可 指向子类对象 , 使用父类指针 调用 虚函数 可执行 子类对应的函数 ;
- 多态实现条件 : ① 继承 , ② 虚函数重写 , ③ 父类指针/引用指向子类对象 ;
- 父类指针 可以 指向 父类对象 , 也可以指向 不同的 子类对象 ;
- 通过 父类指针 调用 virtual 虚函数 , 会根据实际的对象类型调用不同的 虚函数 , 而不是死板的调用父类的成员函数 ;
- 多态实现原理 : 虚函数 对应 动态联编 , 非虚函数 对应 静态联编 ;
- 有 虚函数 的类 , 在 编译时 , 会生成 虚函数表 , 对应类中生成一个 vptr 指针指向 虚函数表 ;
- vptr 指针 是 与 对象绑定的 , 调用时 从 对象的 虚函数表 中查找虚函数 ;
- 通过 父类指针 访问虚函数时 , 直接根据 实际对象 的 vptr 指针找该对象的 虚函数表 , 然后调用 虚函数表 中的 虚函数 ;
- 多态意义 : 多态是 设计模式 的基础 , 是 软件框架 的基础 ;
三、父类指针和子类指针步长
指针数据类型 : C 中 指针 是 数据类型 的 一种 , 对 指针 进行 自增 或 自减 – 操作 , 指针的 地址值 是根据 指针类型 改变的 ;
指针运算 : C 中 指向某类型对象的 指针 的 运算 , 是 根据 指针类型 进行的 , 指针 自增 , 指针的地址值 会增加 指针类型字节大小 ;
如 : 指针 Student* p
, 其类型是 自定义的 Student 类型 , 则 p
的计算结果是 p 指针的地址值 加上 sizeof(*p)
对象的字节长度 ;
显然 父类 与 子类 对象 的 字节大小是不同的 , 在进行数组操作 , 或 指针运算时 , 指针 或 数组 的类型 必须一致 , 一定不能使用多态 ;
指针步长自增 是 根据 声明的 类型 进行自增的 , 不是根据 指针实际指向的对象类型的大小进行自增的 ;
指针的 步长 是 根据 指针 指向的 内存空间 的数据类型确定的 ;
子类 继承 父类 , 如果 子类 没有添加任何 成员函数 与 成员方法 , 那么子类指针 与 父类指针 的步长是相同的 ;
代码示例 :
代码语言:javascript复制#include "iostream"
using namespace std;
// 父类
class Parent {
public:
virtual void fun()
{
cout << "执行 父类 Parent 的 virtual void fun(int a) 函数" << endl;
}
int a;
};
// 子类
class Child : public Parent {
public:
virtual void fun()
{
cout << "执行 子类 virtual void fun(int a) 函数" << endl;
}
int b;
};
int main() {
Parent* p = NULL;
Child* c = NULL;
Child array[] = {Child(), Child(), Child()};
p = array;
c = array;
p->fun();
c->fun();
// 步长加 1 , 然后继续调用
// 指针步长自增 是 根据 声明的 类型 进行自增的
// 不根据 实际的类型 自增
p ;
c ;
// 父类指针 自增 后 , 增加的是 父类的步长
// 此时指向的位置 不是 元素的首地址 , 肯定会出错
//p->fun();
// 子类步长加 1 , 指针正常
c->fun();
// 控制台暂停 , 按任意键继续向后执行
system("pause");
return 0;
}
执行结果 : 执行 子类 virtual void fun(int a) 函数 执行 子类 virtual void fun(int a) 函数 执行 子类 virtual void fun(int a) 函数 Press any key to continue . . .