zl程序教程

您现在的位置是:首页 >  后端

当前栏目

【C++】多态:虚函数

C++ 函数 多态
2023-09-11 14:15:38 时间

1. 如此继承

#include <iostream>
using namespace std;

class Animal {
public :
    Animal(string name) : name(name) {}
    void run() {
        cout << "I don't know how to run" << endl;
    }
private :
    string name;
};

class Cat : public Animal {
public :
    Cat(string name) : Animal(name) {}
    void run() {
        cout << "I can run with four legs" << endl;
    }
};

int main() {
    Cat a("Tom");
    Animal &b = a;
    Animal *c = &a;
    a.run();
    b.run();
    c->run();

    return 0;
}

运行结果:

I can run with four legs
I don't know how to run
I don't know how to run

代码中,a、b 和 c 都是指向同一个对象,但是调用 run 方法后结果却不同,违反了我们的认知规律。这是为什么呢?因为普通成员方法的特性是跟着类走,也就是说*调用普通成员方法时,要看调用该方法的符号是什么类型的*。

main 函数内:

int main() {
    Cat a("Tom");
    Animal &b = a;
    Animal *c = &a;
    a.run(); //a的类型是Cat,所以调用的是Cat的run方法
    b.run(); //b是Animal类型的引用,所以调用的是Animal的run方法
    c->run();//c是Animal类型的指针,所以调用的是Aniaml的run方法

    return 0;
}

普通成员方法的调用是在编译期就确定了的行为。

在编译期的时候,编译器会将上面三行的 run 方法的调用替换成相应的函数地址:

  • 第5行会被替换成 Cat 类的 run 方法的地址;
  • 第6行会被替换成为 Animal 类中的 run 方法的地址;
  • 第7行会被替换成为 Animal 类中的 run 方法的地址。

2. 虚函数

2.1 虚函数的特性

#include <iostream>
using namespace std;

class Animal {
public :
    Animal(string name) : name(name) {}
    virtual void run() {
        cout << "I don't know how to run" << endl;
    }
private :
    string name;
};

class Cat : public Animal {
public :
    Cat(string name) : Animal(name) {}
    //父类中的方法变成virtual,则子类中同名的方法也就变成了virtual
    void run() {
        cout << "I can run with four legs" << endl;
    }
};

int main() {
    Cat a("Tom");
    Animal &b = a;
    Animal *c = &a;
    a.run();
    b.run();
    c->run();

    return 0;
}

运行结果:

I can run with four legs
I can run with four legs
I can run with four legs

普通成员方法是跟着类走的,而虚函数是跟着对象走的。

理解代码段:

int main() {
    Cat a("Tom");
    Animal &b = a;
    Animal *c = &a;
    a.run(); //通过对象a调用的,而run是虚函数,编译器就会分析对象a具体指向的是什么类型的对象,是Cat类型,于是调用Cat类的run方法
    b.run(); //b是Animal类型的引用,但是指向Cat类对象,所以调用的还是Cat类的run方法
    c->run(); //同上

    return 0;
}

2.2 虚函数的作用

对外展现统一的接口

#include <iostream>
using namespace std;

class Animal {
public :
    Animal(string name) : name(name) {}
    virtual void run() {
        cout << "I don't know how to run" << endl;
    }
private :
    string name;
};

class Cat : public Animal {
public :
    Cat(string name) : Animal(name) {}
    //父类中的方法变成virtual,则子类中同名的方法也就变成了virtual
    void run() {
        cout << "I can run with four legs" << endl;
    }
};

class People : public Animal {
public :
    People(string name) : Animal(name) {}
    void run() {
        cout << "I can run with two leges" << endl;
    }
};

class Bat : public Animal {
public :
    Bat(string name) : Animal(name) {}
    void run() {
        cout << "I can fly" << endl;
    }
};

int main() {
    #define MAX_N 10
    srand(time(0));
    //有10个空间
    Animal **zoo = new Animal*[MAX_N];
    for (int i = 0; i < 10; i++) {
        switch (rand() % 3) {
            case 0: zoo[i] = new Cat("cat"); break;
            case 1: zoo[i] = new People("People"); break;
            case 2: zoo[i] = new Bat("bat"); break;
        }
    }
    for (int i = 0; i < MAX_N; i++) zoo[i]->run();
    return 0;
}

运行结果:
image-20210311145645426
zoo[i]->run 到底调用的是哪个方法,在编译期是不能确定的,本质是因为虚函数是跟着对象走的,而对象是在程序运行期才产生的,只有产生了对象,虚函数到底调用的是哪个方法才能知道,即虚函数是程序运行期才能确定的

小结:

  • 普通成员方法 跟着 走,在 编译期 就能确定
  • 虚函数 跟着 对象 走,在 运行期 才能确定

2.3 override 关键字

override 关键字起到说明作用,规范化代码的语义。override 关键字只能用来mark虚函数,可以减少程序出现bug。

class Animal {
public :
    Animal(string name) : name(name) {}
    virtual void run() {
        cout << "I don't know how to run" << endl;
    }
private :
    string name;
};

class Cat : public Animal {
public :
    Cat(string name) : Animal(name) {}
    void run() override {
        cout << "I can run with four legs" << endl;
    }
};

2.4 final 关键字

2.4.1 final关键字的作用

final 关键字的作用:禁止子类重写父类中的同名函数。

/*************************************************************************
	> File Name: final.cpp
	> Author: Maureen
	> Mail: Maureen@qq.com
	> Created Time: 日  1/ 9 20:23:10 2022
 ************************************************************************/

#include <iostream>
using namespace std;
class Animal {
public :
    Animal(string name) : name(name) {}
    virtual void run() {
        cout << "I don't know how to run" << endl;
    }
    virtual void fl1() {}
private :
    string name;
};

class Cat : public Animal {
public :
    Cat(string name) : Animal(name) {}
    //父类中的方法变成virtual,则子类中同名的方法也就变成了virtual
    void run() override final {
        cout << "I can run with four legs" << endl;
    }
    void fl1() override {}
};

class Tiger : public Cat {
public :
    void run() {}
};

编译器会报错:

final.cpp:33:10: error: declaration of 'run' overrides a 'final' function
    void run() {}
         ^
final.cpp:25:10: note: overridden virtual function is here
    void run() override final {
         ^
1 error generated.

错误原因:Tiger 类中的 run 方法没有办法覆盖父类 Cat 中的 run 方法,因为 Cat 中的 run 方法已经标记了 final 关键字,所以子类不能重写该方法。

2.4.2 final关键字的应用场景

当希望所有的派生类中包括外部访问该方法的时候表现得一模一样时,就需要将该方法标记为 final

class Animal {
public :
    Animal(string name) : name(name) {}
    virtual void run() {
        cout << "I don't know how to run" << endl;
    }
    virtual void getName() final {}
    virtual void fl1() {}
private :
    string name;
};

那么就能保住 Animal 类的所有子类调用 getName 方法都是调用的 Animal 类的这个方法。

2.5 虚函数为什么能跟着对象走

存在虚函数后,编译器对对象的存储区做了一定的调整:
image-20210311154139944
对象的头 8 个字节存储的是虚函数表的首地址。

代码示例:

#include <iostream>
using namespace std;

class A {
public :
    virtual void say() {
        cout << "Class A say" << endl;
    }
    virtual void run() {
        cout << "Class A run" << endl;
    }
};

class B : public A {
public :
    void say() override {
        cout << "Class B say" << endl;
    }
};

class C : public A {
public :
    void run() override {
        cout << "Class C run" << endl;
    }
};

#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
    cout << "object " << class_name << endl;
    a.say();
    a.run();
    cout << "======" << endl << endl;
    return ;
}

int main() {
    A a;
    B b;
    C c;
    TEST(a);
    TEST(b);
    TEST(c);

    return 0;
}

运行结果:

object a
Class A say
Class A run
======

object b
Class B say
Class A run
======

object c
Class A say
Class C run
======

三个类的虚函数表:
image-20210311154548843
三个对象指向不同的虚函数表:
image-20210311154816400
即是说,相同类型下的不同对象存储的虚函数表是同一张:
image-20210311155011261
a1a2 都是 A 类型对象,它们指向同一张虚函数表。

这就是有了虚函数后的对象存储模型,这就解释了”为什么虚函数可以跟着对象走“。

子类中与父类虚函数同名的函数之所以叫做覆盖,是因为虚函数表中存储的相关函数的地址被修改为子类中相关函数的地址,所谓的覆盖是在虚函数表中的覆盖。

2.5.1 验证 “虚函数表的存在”

验证虚函数表的存在即是验证对象的头8个字节指向的是虚函数表的地址:让 b 对象的地址指向 c 的虚函数表。

#include <iostream>
using namespace std;

class A {
public :
    virtual void say() {
        cout << "Class A say" << endl;
    }
    virtual void run() {
        cout << "Class A run" << endl;
    }
};

class B : public A {
public :
    void say() override {
        cout << "Class B say" << endl;
    }
};

class C : public A {
public :
    void run() override {
        cout << "Class C run" << endl;
    }
};

#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
    cout << "object " << class_name << endl;
    a.say();
    a.run();
    cout << "======" << endl << endl;
    return ;
}

int main() {
    A a;
    B b;
    C c;
    TEST(a);
    TEST(b);
    TEST(c);
    //认为b的存储区中存储着一个个void *的地址
    //将b对象存储的地址修改为c对象中存储的地址(即头8个字节)
    ((void **)(&b))[0] = ((void **)(&c))[0];
    TEST(b);
    return 0;
}

运行结果:

object a
Class A say
Class A run
======

object b
Class B say
Class A run
======

object c
Class A say
Class C run
======

object b #b对象就运行的是C类的虚函数表中的方法
Class A say
Class C run
======

2.5.2 验证 “虚函数表中存储着虚函数”

#include <iostream>
using namespace std;

class A {
public :
    virtual void say() {
        cout << "Class A say" << endl;
    }
    virtual void run() {
        cout << "Class A run" << endl;
    }
};

class B : public A {
public :
    void say() override {
        cout << "Class B say" << endl;
    }
};

class C : public A {
public :
    void run() override {
        cout << "Class C run" << endl;
    }
};

#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
    cout << "object " << class_name << endl;
    a.say();
    a.run();
    cout << "======" << endl << endl;
    return ;
}

//声明函数指针
typedef void (*func)();

int main() {
    A a;
    B b;
    C c;
    TEST(a);
    TEST(b);
    TEST(c);
    //认为b的存储区中存储着一个个void *的地址
    //将b对象存储的地址修改为c对象中存储的地址(即头8个字节)
    ((void **)(&b))[0] = ((void **)(&c))[0];
    TEST(b);

    //调用了b对象指向的虚函数表中的say方法
    //找到b对象指向的虚函数表中的第一个虚函数
    ((func **)(&b))[0][0](); //Class A say

    return 0;
}

运行结果:

object a
Class A say
Class A run
======

object b
Class B say
Class A run
======

object c
Class A say
Class C run
======

object b
Class A say
Class C run
======

Class A say

修改 say 方法,传入一个参数:

#include <iostream>
using namespace std;

class A {
public :
    virtual void say(int x) {
        cout << "Class A say : " << x <<  endl;
    }
    virtual void run() {
        cout << "Class A run" << endl;
    }
};

class B : public A {
public :
    void say(int x) override {
        cout << "Class B say" << endl;
    }
};

class C : public A {
public :
    void run() override {
        cout << "Class C run" << endl;
    }
};

#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
    cout << "object " << class_name << endl;
    a.say(123);
    a.run();
    cout << "======" << endl << endl;
    return ;
}

typedef void (*func)(int);

int main() {
    A a;
    B b;
    C c;
    TEST(a); //正常调用say方法
    TEST(b);
    TEST(c);
    //认为b的存储区中存储着一个个void *的地址
    //将b对象存储的地址修改为c对象中存储的地址(即头8个字节)
    ((void **)(&b))[0] = ((void **)(&c))[0];
    TEST(b);

    //使用函数指针调用say方法
    ((func **)(&b))[0][0](100);

    return 0;
}

运行结果:

object a
Class A say : 123
Class A run
======

object b
Class B say
Class A run
======

object c
Class A say : 123
Class C run
======

object b
Class A say : 123
Class C run
======

Class A say : 73896

可以发现,正常调用say方法的时候,参数x可以被准确地赋值;但是通过函数指针调用的时候,却不能进行正确的赋值。这就涉及到this指针的问题。

virtual void say(int x)方法,我们看到的是传入一个参数,正常调用的时候,编译器会将say方法处理为传入两个参数的形式:say(this, int)。这就是底层实现this指针的原理,this指针其实就是成员函数的一个隐藏参数

这就能解释为什么通过函数指针调用的形式不能正确进行赋值,因为少传了一个参数。进行如下修改:

#include <iostream>
using namespace std;

class A {
public :
    virtual void say(int x) {
        cout << "Class A say : " << x <<  endl;
    }
    virtual void run() {
        cout << "Class A run" << endl;
    }
};

class B : public A {
public :
    void say(int x) override {
        cout << "Class B say" << endl;
    }
};

class C : public A {
public :
    void run() override {
        cout << "Class C run" << endl;
    }
};

#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
    cout << "object " << class_name << endl;
    a.say(123);
    a.run();
    cout << "======" << endl << endl;
    return ;
}
//添加指针参数
typedef void (*func)(void *, int);

int main() {
    A a;
    B b;
    C c;
    TEST(a); //正常调用say方法
    TEST(b);
    TEST(c);
    //认为b的存储区中存储着一个个void *的地址
    //将b对象存储的地址修改为c对象中存储的地址(即头8个字节)
    ((void **)(&b))[0] = ((void **)(&c))[0];
    TEST(b);

    //使用函数指针调用say方法,传入当前对象即b的地址
    ((func **)(&b))[0][0](&b, 100);

    return 0;
}

运行结果:

object a
Class A say : 123
Class A run
======

object b
Class B say
Class A run
======

object c
Class A say : 123
Class C run
======

object b
Class A say : 123
Class C run
======

Class A say : 100 #成功给x赋值

2.5.3 正确理解 “处理流程” 和 “数据”

#include <iostream>
using namespace std;

class A {
public :
    A() { x = 200; }
    virtual void say(int x) {
        cout << "this->x = " << this->x << endl;
        cout << "Class A say : " << x << endl;
    }
    virtual void run() {
        cout << "Class A run" << endl;
    }
    int x;
};

class B : public A {
public :
    B() { x = 300; }
    void say(int x) override {
        cout << "Class B say" << endl;
    }
};

class C : public A {
public :
    C() { x = 400; }
    void run() override {
        cout << "Class C run" << endl;
    }
};

#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
    cout << "object " << class_name << endl;
    a.say(123);
    a.run();
    cout << "======" << endl << endl;
    return ;
}

typedef void (*func)(void *, int);

int main() {
    A a;
    B b;
    C c;
    TEST(a); //正常调用say方法
    TEST(b);
    TEST(c);
    //认为b的存储区中存储着一个个void *的地址
    //将b对象存储的地址修改为c对象中存储的地址(即头8个字节)
    ((void **)(&b))[0] = ((void **)(&c))[0];
    TEST(b);

    //使用的是A类中的say方法
    ((func **)(&b))[0][0](&b, 100); //输出300。this指向的是b对象,所以访问的是B类中的x

    return 0;
}

((func **)(&b))[0][0](&b, 100); 代码输出300,虽然调用的是A类的 say 方法,但是此时的 this 指向的是 b 对象,所以输出300。

同理,((func **)(&b))[0][0](&c, 100); 结果为400;((func **)(&b))[0][0](&a, 100); 结果为200。

用这个实验说明:本质上“数据”和“处理流程”是可以分开的。

3. 成员函数指针

问:普通函数指针为什么不能存储成员方法地址?

答:因为成员方法被编译器修饰过,实际上有个默认的隐藏参数 this 指针,而普通的函数指针类型对不上。

普通的函数指针没有 this 的概念,所以就出现了成员函数指针。

#include <iostream>
using namespace std;

class A {
public :
    A() { x = 200; }
    virtual void say(int x) {
        cout << "this->x = " << this->x << endl;
        cout << "Class A say : " << x << endl;
    }
    virtual void run() {
        cout << "Class A run" << endl;
    }
    int x;
};

class B : public A {
public :
    B() { x = 300; }
    void say(int x) override {
        cout << "Class B say" << endl;
    }
};

class C : public A {
public :
    C() { x = 400; }
    void run() override {
        cout << "Class C run" << endl;
    }
};

#define TEST(a) test(a, #a)
void test(A &a, string class_name) {
    cout << "object " << class_name << endl;
    a.say(123);
    a.run();
    cout << "======" << endl << endl;
    return ;
}

typedef void (*func)(void *, int);

int main() {
    A a;
    B b;
    C c;
    TEST(a); //正常调用say方法
    TEST(b);
    TEST(c);
    //认为b的存储区中存储着一个个void *的地址
    //将b对象存储的地址修改为c对象中存储的地址(即头8个字节)
    ((void **)(&b))[0] = ((void **)(&c))[0];
    TEST(b);

    //使用的是A类中的say方法
    ((func **)(&b))[0][0](&b, 100);

    //成员方法指针
    void (A::*p)(int); //p(A*, )
    p = &A::say;
    (a.*p)(12345); //调用了A类的成员方法say

    return 0;
}

运行结果:

object a
this->x = 200
Class A say : 123
Class A run
======

object b
Class B say
Class A run
======

object c
this->x = 400
Class A say : 123
Class C run
======

object b
this->x = 300
Class A say : 123
Class C run
======

this->x = 300
Class A say : 100
this->x = 200
Class A say : 12345

成员函数指针的应用:随机调用 A 类中的 regx 成员方法

#include <iostream>
using namespace std;

class A {
public :
    A() { x = 200; }
    virtual void say(int x) {
        cout << "this->x = " << this->x << endl;
        cout << "Class A say : " << x << endl;
    }
    virtual void run() {
        cout << "Class A run" << endl;
    }
    void reg1() {
        cout << "reg1 function " << endl;
    }

    void reg2() {
        cout << "reg2 function " << endl;
    }
    void reg3() {
        cout << "reg3 function " << endl;
    }
    int x;
};

int main() {
    A a;
    //随机调用A类中的regX函数
    //成员函数指针数组
    void (A::*p[3])();
    p[0] = &A::reg1;
    p[1] = &A::reg2;
    p[2] = &A::reg3;
    for (int i = 0; i < 10; i++) {
        (a.*p[rand() % 3])();
    }

    return 0;
}

运行结果:

reg2 function
reg2 function
reg3 function
reg3 function
reg2 function
reg3 function
reg1 function
reg3 function
reg3 function
reg2 function

4. 成员属性指针

定义一个指向 A 类中整型变量的指针:

#include <iostream>
using namespace std;

class A {
public :
    A() { this->x = 200; this->y = 400; }
    virtual void say(int x) {
        cout << "this->x = " << this->x << endl;
        cout << "Class A say : " << x << endl;
    }
    virtual void run() {
        cout << "Class A run" << endl;
    }
    void reg1() {
        cout << "reg1 function " << endl;
    }

    void reg2() {
        cout << "reg2 function " << endl;
    }
    void reg3() {
        cout << "reg3 function " << endl;
    }

    int x, y;
};
int main() {
    A a;
    
    int A::*q; //成员属性指针
    q = &A::x; //q指向A类的x字段
    cout << (a.*q) << endl; //等价于a.x ,结果为200
    q = &A::y;
    cout << (a.*q) << endl; //400
    
    void (A::*p[3])();//成员函数指针数组,用于随机调用A类中的regX函数
    p[0] = &A::reg1;
    p[1] = &A::reg2;
    p[2] = &A::reg3;
    for (int i = 0; i < 10; i++) {
        (a.*p[rand() % 3])();
    }
    return 0;
}

运行结果:

200
400
reg2 function
reg2 function
reg3 function
reg3 function
reg2 function
reg3 function
reg1 function
reg3 function
reg3 function
reg2 function

无论是成员函数指针还是成员属性指针,都可以看成是字段等价替换功能。

5. 析构函数

#include <iostream>
using namespace std;

class A {
public :
    A() {
        cout << "A constructor" << endl;
    }
    //virtual void say() {
    //    cout << "Class A" << endl;
    //}

    ~A() {
        cout << "A destructor " << endl;
    }
};

class B : public A {
public :
    B() {
        cout << "B constructor" << endl;
    }
    //virtual void say() {
    //    cout << "Class B" << endl;
    //}
    ~B() {
        cout << "B destructor " << endl;
    }
};


class C : public A {
public :
    C() {
        cout << "C constructor" << endl;
    }
    //virtual void say() {
    //    cout << "Class C" << endl;
    //}
    ~C() {
        cout << "C destructor " << endl;
    }
};

int main() {
    srand(time(0));
    A *p;
    switch (rand() % 2) {
        case 0: p = new B(); break;
        case 1: p = new C(); break;
    }
    delete p;
    //cout << dynamic_cast<B *>(p) << endl;
    //cout << dynamic_cast<C *>(p) << endl;
    return 0;
}

运行结果:

A constructor
B constructor
A destructor

可以发现,析构的时候,B类对象并没有被析构,说明在这种情况下,没有对B类对象进行正确的析构。

本质原因还是普通的成员方法是跟着 走的delete p; 会调用 p 指向对象的析构函数,但是现在无论哪个类中的析构函数都是普通成员方法,而目前 p 是 A 类的指针,所以只调用了 A 类的析构函数。

通常情况下,在这种具有继承关系的类的设计,析构函数一定要设计为虚函数

修改析构函数为虚函数:

#include <iostream>
using namespace std;

class A {
public :
    A() {
        cout << "A constructor" << endl;
    }
    //virtual void say() {
    //    cout << "Class A" << endl;
    //}

    virtual ~A() {
        cout << "A destructor " << endl;
    }
};

class B : public A {
public :
    B() {
        cout << "B constructor" << endl;
    }
    //virtual void say() {
    //    cout << "Class B" << endl;
    //}
    virtual ~B() {
        cout << "B destructor " << endl;
    }
};


class C : public A {
public :
    C() {
        cout << "C constructor" << endl;
    }
    //virtual void say() {
    //    cout << "Class C" << endl;
    //}
    virtual ~C() {
        cout << "C destructor " << endl;
    }
};

int main() {
    srand(time(0));
    A *p;
    switch (rand() % 2) {
        case 0: p = new B(); break;
        case 1: p = new C(); break;
    }
    delete p;
    //cout << dynamic_cast<B *>(p) << endl;
    //cout << dynamic_cast<C *>(p) << endl;
    return 0;
}

运行结果:

A constructor
B constructor
B destructor
A destructor

假设现在有如下需求:1、使用 dynamic_cast 的功能;2、设计的类中没有任何虚函数。

基于以上关于析构函数的分析,也解决了类中没有虚函数的时候,还想调用 dynamic_cast 的需求。

根据C++设计原则,只要存在继承关系,析构函数就一定是虚函数,所以只要存在继承关系,大概率就可以使用 dynamic_cast,因为一定存在虚函数表,最起码析构函数是虚函数。

6. 纯虚函数

纯虚函数:当前类不关心该函数的实现到底是什么样的,但是派生类中会实现该函数。

纯虚函数的性质:不实现纯虚函数的子类无法实例化(即创建对象)

#include <iostream>
using namespace std;

class Animal {
public :
    Animal(string name) : name(name) {}
    virtual void run() = 0; //纯虚函数,没有任何实现
    virtual void getName() final {}
    virtual void fl1() {}
private :
    string name;
};

class Cat : public Animal {
public :
    Cat(string name) : Animal(name) {}
    void run() override final {
        cout << "I can run with four legs" << endl;
    }
    void fl1() override {}
};

class People : public Animal {
public :
    People(string name) : Animal(name) {}
    void run() {
        cout << "I can run with two leges" << endl;
    }
};

class Bat : public Animal {
public :
    Bat(string name) : Animal(name) {}
    void run() {
        cout << "I can fly" << endl;
    }
};

class Horse : public Animal {
public :
    Horse() : Animal("Horse") {}

};

int main() {
    return 0;
}

程序中的 Horse 类虽然没有实现父类 Animal 中的纯虚函数 run,但是可以通过编译,原因在于 Horse 类没有进行实例化。

如果在 main 函数中创建 Horse 类的对象:

int main() {
    Horse h;
    return 0;
}

程序就会报错,因为 Horse 类中有一个未实现的纯虚函数。这就是在强制我们实现父类的纯虚函数:

class Animal {
public :
    Animal(string name) : name(name) {}
    virtual void run() = 0; //纯虚函数,没有任何实现
    virtual void getName() final {}
    virtual void fl1() {}
private :
    string name;
};
class Horse : public Animal {
public :
    Horse() : Animal("Horse") {}
    void run() override {
        cout << "Horse, I can run with four leges" << endl;
    }
};

int main() {
    Horse h;
    return 0;
}

纯虚函数的作用:用来规定子类中必须实现的方法。

延伸出一个概念 接口,接口只规定了对外的表现形式,没有规定具体的功能。纯虚函数一般是用来定义接口的。

接口应用:分别用数组和堆实现优先队列

#include <iostream>
#include <vector>
using namespace std;

//实现优先队列
class IQueue {
public :
    virtual void push(int) = 0;
    virtual void pop() = 0;
    virtual bool empty() = 0;
    virtual int top() = 0;
    virtual int size() = 0;
};

//用数组实现优先队列
class vector_queue : public IQueue, public vector<int> {
public :
    void push(int x) override {
        //加不加前缀修饰都可以
        //也可以直接写成:push_back(x)
        this->vector<int>::push_back(x);
        return ;
    }

    void pop() override {
        if (empty()) return ;
        vector<int>::iterator p = this->begin();
        //找到最大值的位置
        for (auto iter = begin(); iter != end(); iter++) {
            if (*iter > *p) p = iter;
        }
        //删除最大值
        erase(p);
        return ;
    }

    bool empty() override {
        return size() == 0;
    }

    int top() override {
        if (empty()) return 0;
        int ans = at(0);
        for (int i = 1; i < size(); i++) {
            ans = max(at(i), ans);
        }
        return ans;
    }

    int size() override {
        return this->vector<int>::size();
    }
};

//用堆实现优先队列
class heap_queue : public IQueue, public vector<int> {
public :
    void push(int x) override {
        push_back(x);
        up_maintain(size());
        return ;
    }

    void pop() override {
        std::swap(at(0), at(size() - 1));
        pop_back();
        down_maintain(1);
        return ;
    }

    bool empty() override {
        return size() == 0;
    }

    int top() override {
        if (empty()) return 0;
        return at(0);
    }

    int size() override {
        return this->vector<int>::size();
    }
private:
    void up_maintain(int ind) {
        while (ind > 1 && at(ind - 1) > at((ind / 2) - 1)) {
            std::swap(at(ind - 1), at((ind / 2) - 1));
            ind /= 2;
        }
        return ;
    }
    void down_maintain(int ind) {
        while (ind * 2 <= size()) {
            int temp = ind;
            if (at(ind * 2 - 1) > at(temp - 1)) temp = ind * 2;
            if (ind * 2 + 1 <= size() && at(ind * 2) > at(temp - 1)) temp = ind * 2 + 1;
            if (temp == ind) break;
            std::swap(at(temp - 1), at(ind - 1));
            ind = temp;
        }
        return ;
    }
};

int main() {
    srand(time(0));
    vector_queue q1;
    heap_queue q2;
    for (int i = 0; i < 10; i++) {
        int val = rand() % 100;
        q1.push(val);
        cout << "push q1 : " << val << endl;
    }
    while (!q1.empty()) {
        cout << q1.top() << " ";
        q1.pop();
    }
    cout << endl;

    for (int i = 0; i < 10; i++) {
        int val = rand() % 100;
        q2.push(val);
        cout << "push q2 : " << val << endl;
    }
    while (!q2.empty()) {
        cout << q2.top() << " ";
        q2.pop();
    }
    cout << endl;
    return 0;
}

运行结果:
image-20210312151441736
可见,接口屏蔽了内部实现细节。

7. 抽象类

拥有纯虚函数的类无法生成对象,将这些类叫做抽象类,通常用来实现接口。

class Animal { //抽象类,无法生成对象
public :
    Animal(const string &name) : __name(name) {}
    virtual void run() = 0;
protected :
    string __name;
};

class Cat : public Animal {
public :
    Cat() : Animal("cat") {}
    void run() override {
        cout << "I can run with 4 legs" << endll
    }
};

8. 小结

virtual 关键字

语义:子类的这个方法可能会跟父类的有所不同

成员方法调用时:

  • virtual 关键字的方法跟着【对象】
  • virtual 关键字的方法跟着 【类】

限制:不能用来修饰【类方法 - static

纯虚函数

语义:子类肯定会有这个方法,而父类只能说「抱歉」

应用场景:定义接口