【C++】多态 ⑩ ( 不建议将所有函数都声明为 virtual 虚函数 | 多态的理解层次 | 父类指针和子类指针步长 )

2023-11-02 12:26:06 浏览数 (1)

对象可以直接获取到自身封装的 普通函数 , 如果要访问虚函数 , 需要增加一次寻址操作 , 因此 这里建议不需要将有 多态 需求的函数声明为 虚函数 ;

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 . . .

0 人点赞