文章目录

  • 1.类型相容
  • 2.绑定时间
  • 3.虚函数
    • 3.1后期绑定的实现
  • 4.final,override
  • 5.纯虚函数和抽象类
    • 5.1纯虚函数(同Java中的接口)
    • 5.2抽象类
    • 5.3应用一
    • 5.3应用二:抽象工厂

1.类型相容

  1. 类、类型
  2. 类型相容:
    • 一个类型是另一个类型的子类型(int -> long int)时,类型是相容的
  3. 赋值相容:对于类型相容的变量才有
    • 如果类型相同可以直接赋值
    • 子类型可以赋值给父类型
  4. 问题:a和b都是类,a、b什么类型时,a = b合法(赋值相容)?B是A的子类型的时候
    • A a; B b; class B: public A; a = b;
    • A与B都在栈上,把B内存中的内容拷贝到A内存中,但A与B大小可能不同,派生类的新增内容就要丢掉
      • 对象切片
    • 对象的身份发生变化,B类型对象变为了A类型的对象
    • 属于派生类的属性已不存在
  5. A a = b:调用A拷贝构造函数
    • 属于B的部分不会被拷贝
  6. 使用引用或指针
    • B* pb; A* pa = pb; class B: public A
    • B b; A& a = b; class B: public A
      • 因为是赋值相容的,所以可以指针赋值
      • 这种情况类似Java
      • B的身份没有发生变化
  7. 把派生类对象赋值给基类对象,基类的引用或指针可以引用或指向派生类对象,不严谨的说,可以说让父类指向子类
    • 传参的时候尽量不要拷贝传参(存在对象切片问题),而是使用引用传参。
//测试切片调用
class A {int x, y;
public:void f();
};
class B: public A {int z;
public:void f();void g();
};
//把派生类对象赋值给基类对象
A a;
B b;
a = b;//OK,
b = a;//Error
a.f();//A::f()
//基类的引用或指针可以引用或指向派生类对象
A& r_a = b;//OK
A* p_a = &b;//OK
B& r_b = a;//Error
B* p_b = &a;//Error
//以下两个部分基本是一致的
func1(A& a) {a.f();}
func2(A* pa) {pa->f();}
func1(b);//A::f
func2(&b);//A::f

2.绑定时间

  1. 前期绑定/静态绑定(Early Binding)

    • 编译时刻确定调用哪一个方法
    • 依据对象的静态类型
    • 效率高、灵活性差
    • 静态绑定根据形参决定
  2. 后期绑定/动态绑定(Late Binding)
    • 运行时刻确定
    • 依据对象的实际类型(动态)
    • 灵活性高、效率低
  3. C++默认静态绑定,注重效率
  4. 后期绑定需显式指出:virtual

3.虚函数

  1. 定义:

    • virtual
    • 动态绑定
      • 根据实际引用和指向的对象类型
    • 方法重定义
  2. 如基类中被定义为虚成员函数,则派生类中对其重定义的成员函数均为虚函数(也就是派生类中的对应函数可以不加virtual)
  3. 限制
    • 类的成员函数才可以是虚函数

      • 全局函数不可以是虚函数
    • 静态成员函数不能是虚函数
      • 静态的成员函数属于类,并不属于一个对象,它是在编译时确定的
    • 内联成员函数不能是虚函数
      • 编译时确定
    • 构造函数不能是虚函数
    • 析构函数可以(而且往往)是虚函数

3.1后期绑定的实现

class A {int x, y;
public:virtual f();virtual g();h();
};
class B: public A {int z;
public:f();h();
};
A a; B b;
A* p;
p = &b;
p->f();//B::f()
p->g();//A::g(),B中没有实现
  1. p->f():需要寻找a和b中的f()函数地址

  2. 虚函数表(索引表,vtable):大小可变

    • 虚函数表是在构造函数中完成的
    • 首先构造基类的虚函数表
    • 然后查找派生类中的函数,如果查找了,则会覆盖对应函数来生成虚函数表
  3. 对象内存空间中含有指针指向虚函数表

  4. (**((char *)pb-4)) (pb)

    • 两次解引用
    • 相当于&A::f (pb)
    • f的函数调用(从虚函数表拿数据),pb是this指针
  5. 空间上和时间上都付出了代价

    • 空间:存储虚函数表指针和虚函数表
    • 时间:需要通过虚函数表查找对应函数地址,多调用
class A {public:A() {f();}virtual void f();void g();void h() {f();g();}
};
class B: public A {public:void f();void g();
};
//直到构造函数返回之后,对象方可正常使用
B b;//A::A(),A::f, B::B()
//为什么调用A的f而不是B的?因为A在构造时B还没有构造,此时虚函数表中还是A的f
A* p = &b;
p->f();//B::f,f是动态绑定
p->g();//A::g,g是静态绑定p->h();//A::h,B::f,A::g
//非虚接口,普通函数调用虚函数让普通函数呈现虚函数的特性
class A {public:virtual void f();void g();
};
class B: public A {public:void f() {g();}void g();
};
B b;
A* p = &b;
p->f();//B::f,B::g
//f动态绑定,而g静态绑定是按声明的来
//在编译时就确定调用B的f必然调用B的g
//B的f中相当于B* const this; this->g();

4.final,override

  1. final

    • 禁止该函数从基类继承;禁止该函数的重载
  2. override
    • 指明该函数是重载的基类中的一个函数
struct B {virtual void f1(int) const;virtual void f2();void f3();virtual void f5(int) final;
};
struct D: B {//继承void f1(int) const override;//正确:f1与基类中的f1匹配;const不能去,否则类型不相同void f2(int) override;//错误:B没有形如f2(int)的函数;去掉override可以编译,但会名隐藏void f3() override;//错误:f3不是虚函数 void f4() override;//错误:B没有名为f4的函数 void f5(int);//错误:B已经将f5声明成final
};

5.纯虚函数和抽象类

5.1纯虚函数(同Java中的接口)

  1. 声明时在函数原型后面加上 = 0:virtual int f() = 0;

    • Means not there
  2. 往往只给出函数声明,不给出实现
    • 可以给出实现,在定义函数实现
    • 但是不能通过基类对象来直接调用(查不到)

5.2抽象类

  1. 至少包含一个纯虚函数
  2. 不能用于创建对象:抽象类类似一个接口,提供一个框架
  3. 为派生类提供框架,派生类提供抽象基类的所有成员函数的实现
  4. 有纯虚函数为什么需要抽象类?
    • 不能创建实例
    • 只能通过引用或指针来传参数
    • 所以能防止对象切片问题
class AbstractClass {public:virtual int f() = 0;
};

5.3应用一

Figure *a[100];//Figure基类,不会被创建出来
//virtual display() = 0;
a[0] = new Rectangle();
a[1] = new Ellipse();
a[2] = new Line();
for (int i = 0; i < num_of_figures; i++){a[i]->display();
}
//基于接口的复用,而不是基于实现的复用

5.3应用二:抽象工厂

  1. Step1:提供Windows GUI类库:WinButton
WinButton* pb = new WinButton();
pb->SetStyle();
WinLabel* pl = new WinLabel();
pl->SetText();
  1. Step2:增加对Mac的支持:MacButton,MacLabel
MacButton* pb = new MacButton();
pb->SetStyle();
MacLabel* pl = new MacLabel();
pl->SetText();
  1. 增加对用户跨平台设计的支持
Button* pb = new MacButton();
pb->SetStyle();
Label* pl = new MacLabel();
pl->SetText();//创建工厂
class AbstractFactory {public:virtual Button* CreateButton() = 0;virtual Label* CreateLabel() =0;
};
class MacFactory: public AbstractFactory {//连new操作也抽象
public:MacButton* CreateButton() {return new MacButton;}MacLabel* CreateLabel() {return new MacLabel;}
};
class WinFactory: public AbstractFactory {public:WinButton* CreateButton() {return new WinButton;}WinLabel*   CreateLabel() {return new WinLabel;}
};
class Button;//Abstract Class
class MacButton: public Button {};
class WinButton: public Button {};
class Label;//Abstract Class
class MacLabel: public Label {};
class WinLabel: public Label {};
AbstractFactory* fac;
switch (style) {case MAC:fac = new MacFactory;break;
case WIN:fac = new WinFactory;break;
}
Button* button = fac->CreateButton();
Label* Label = fac->CreateLabel();

NJU:C++虚函数相关推荐

  1. C++ 虚函数与存虚函数

    什么是虚函数: 虚函数 是在基类中使用关键字 virtual 声明的函数,在C++ 语言中虚函数可以继承,当一个成员函数被声明为虚函数之后,其派生类中的同名函数都自动生成为虚函数, 虚函数主要体验C+ ...

  2. 【C++】多态(早期绑定、后期绑定)、抽象类(纯虚函数)、虚析构函数

    我们都知道面向对象编程的三大特征是封装.继承.多态,今天我们就来说一下其中之一的多态. 概念: 多态: 多态字面意思就是多种形态,C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同 ...

  3. C#中虚函数,抽象,接口的简单说明

    虚函数:由virtual声明,它允许在派生类中被重写,要重写方法,必须先声名为virtual public class myclass { public virtual int myint() { 函 ...

  4. 提高C++性能的编程技术笔记:虚函数、返回值优化+测试代码

    虚函数:在以下几个方面,虚函数可能会造成性能损失:构造函数必须初始化vptr(虚函数表):虚函数是通过指针间接调用的,所以必须先得到指向虚函数表的指针,然后再获得正确的函数偏移量:内联是在编译时决定的 ...

  5. 但并不从包含函数声明的接口派生_C++的虚函数和纯虚函数

    虚函数:类成员函数前面添加virtual关键字,则该函数被称为虚函数. 纯虚函数:在虚函数的基础上,在函数末尾加上 = 0. class Animal {public: virtual void Sh ...

  6. c++ 虚函数_到底什么情况下会合成默认构造函数?

    来源:https://www.cnblogs.com/QG-whz/p/4676481.html 作者:good luck 编辑:公众号[编程珠玑] 编辑注:没有构造函数的时候编译器一定会生成默认构造 ...

  7. C++——虚函数(Virtual Member Functions) 【functions语意学】

    单继承下的虚函数 虚函数的实现: 为每个有虚函数的类配一张虚函数表(virtual table),它存储该类类型信息和所有虚函数执行期的地址. 为每个有虚函数的类插入一个指针(vptr),这个指针指向 ...

  8. 一口气搞懂《虚函数和纯虚函数》

    学习C++的多态性,你必然听过虚函数的概念,你必然知道有关她的种种语法,但你未必了解她为什么要那样做,未必了解她种种行为背后的所思所想.深知你不想在流于表面语法上的蜻蜓点水似是而非,今天我们就一起来揭 ...

  9. 虚函数实现的基本原理(转载)

    1.概述 每一个含有虚函数(无论是其本身就含有的,还是从基类继承过来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针.如下图所示 : 其中: B的虚函数表中存放着B:: ...

最新文章

  1. python装饰器实例-Python装饰器简单用法实例小结
  2. FTP搭建网络yum源
  3. window 程序报错 自动重启_好程序员web前端教程之详解JavaScript严格模式
  4. Ajax接触及对跨域的简单理解
  5. Unity3d中使用自带动画系统制作下雨效果(一)
  6. 边缘计算容器化是否有必要?
  7. C/C++指针 引用
  8. Linux查看端口使用情况
  9. [置顶] 提高生产力:开源Java工具包Jodd(Java的”瑞士军刀”)
  10. bzoj 1924 所驼门王的宝藏
  11. 【SpringBoot_ANNOTATIONS】组件注册 07 @Import 给容器快速导入一个组件
  12. 计算机组装与维护精品,国家精品课程——《计算机组装与维护》.pdf
  13. 《简明微积分》(第四版)学习笔记
  14. 利用C Free3.5 本身获得自身注册码
  15. xampp的安装及使用
  16. Intel_Hm55_AHCI驱动
  17. Invisible Backdoor Attack with Sample-Specific Triggers
  18. 那些年我们一起错过赚钱时光 10年机会逐个数
  19. 出租车语音全自动服务器,出租车语音提示器工作原理
  20. 百度网盘打不开的问题的解决

热门文章

  1. LeNet,AlexNet,VGG,NiN,GoogLeNet,ResNet
  2. java xxtea加密_TEA、XTEA、XXTEA加密解密算法
  3. 如何算java程序的运行时间
  4. div盒子最小宽度_min-width最小宽度与max-width最大宽度
  5. 使用HTML5画布和jQuery使小球颜色不断变化
  6. 3D卷积网络论文阅读笔记
  7. 嵌入式课设-基于GPS模块的校园定位程序
  8. AWS介绍(详细好理解)
  9. 键盘按键错乱解决方案
  10. 如何实现与Teamcenter PLM单点登录(SSO)