NJU:C++虚函数

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();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值