当前位置: 首页 > news >正文

多态(二)

1.多态的原理 

       虚函数表

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};

b对象是8bytes,除了_b成员,还多一个__vfptr放在对象的前面(注意有些 平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代 表virtual,f代表function)。     

    一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数 的地址要被放到虚函数表中,虚函数表也简称虚表

class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};int main()
{Base b;//Derive d;printf("%p\n",&Base::Func1);printf("%p\n", &Base::Func2);printf("%p\n", &Base::Func3);return 0;
}

通过printf可以观察到函数的地址  

对于c++来说 普通全局函数通过函数名就可以取到地址  但是类成员函数 除了要指定类域外 还要& 才能取地址 但是cin >> operator >> 重载中会由关于函数名的一些函数 导致无法正常取地址 所以这里使用 printf来取地址

虚函数是放在虚表当中的 那么普通的全局函数又是放在哪儿的呢?

1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态, 比如:函数重载

2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体 行为,调用具体的函数,也称为动态多态。

通常称普通函数为静态绑定  虚函数为动态绑定

首先先看一段代码

class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};
class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};
void func1(Base*p)
{p->Func1();p->Func3();
}
int main()
{Base b;Derive d;func1(&b);func1(&d);return 0;
}

一个类中所有的虚函数都会存在各自的虚表当中 子类中如果发生了重写  那么其对应部分虚表位置的函数也会由子类中的同名函数发生替换 

多态是看指向的对象是谁  如果是b 那就会进入Base 中去通过虚表指针去找到Base类的虚表 在虚表中找到对应的函数去调用  而如果是d 虽然指针是Base类型 但是确实从Derive类中的Base部分虚表去找对应的函数调用  由于该函数发生重写 已经不再试Base::  而是变成了Derive类域中的同名函数 所以在调用时也是调用的是子类中的同名函数 产生多态效果  (多态的原理)

而这些找的过程都是在运行时发生的 所以叫做动态绑定 

而对于没有重写的普通成员函数 和 全局函数 这些都是在编译时就已经确定的 所以称作静态绑定 

虚函数表本质是一个存虚函数指针的指针数组 

虚表存的是虚函数指针,不是虚函数

虚函数和普通函数一样的,都是存在代码段的,

虚函数指正是存在对象中的

动态多态 :就是类中的多态

静态多态: 函数重载 函数模版

int main()
{int i = 0;double d = 1.1;cout << i;cout << d;return 0;
}

函数重载 一种静态多态

int main()
{int a = 0;int b = 21;double c = 0.2;double d = 1.6;swap(a,b);swap(c,d);return 0;
}

函数模版也属于一种静态多态

多继承中指针偏移问题?下面说法正确的是( )

class Base1 {  public:  int _b1; };
class Base2 {  public:  int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main(){Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}

A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3

这里有一个小技巧 那就是对于子类来说 一定是在地址的下面的  而上面放着的是父类部分 而多继承的话 最先继承的部分的地址会在首位 也就是上面   所以p3和p1指向的地址都是首位 都是相同只不过是两者根据类型大小不同所以取到的内容不同  而p2是通过切片中的指针偏移所以取得是中间的位置的地址   所以选C


http://www.mrgr.cn/news/48959.html

相关文章:

  • 电脑健康检查用什么软件好 电脑健康状况检查工具在哪里
  • [Raspberry Pi]如何在Ubuntu的python venv虛擬環境中,運行YOLOv5 物件辨識功能?
  • CSP-J
  • SBB Local Interface 详解
  • select poll epoll 的区别和联系 以及 应用场景
  • 代码随想录打卡Day57
  • 自动猫砂盆真的有必要吗?买自动猫砂盆不看这四点小心害死猫。
  • Linux内核 -- 文件系统之超级块 super_operations 字段作用与用法
  • 过滤器和拦截器的区别是什么?
  • Jain SLEE 中 Addresses
  • 机器学习中的模型设计与训练流程详解
  • SQL优化 where谓词条件is null优化
  • 秋窗的一周年创作纪念日
  • 【fisco学习记录2】多群组搭建
  • ZYNQ使用XGPIO驱动外设模块(前半部分)
  • vue拖拉拽
  • 文件传输遗漏
  • mysql学习教程,从入门到精通,SQL 约束(Constraints)(41)
  • 【机器学习】逻辑回归|分类问题评估|混淆矩阵|ROC曲线|AUC指标 介绍及案例代码实现
  • leetcode209:长度最小的子数组