阅读 230

<<从0到1学C++>> 第7篇 多态性和虚函数

本篇要学习的内容和知识结构概览


多态性

编译时的多态性称为静态联编. 当调用重载函数时, 在编译期就确定下来调用哪个函数.

运行时的多态性称为动态联编. 在运行时才能确定调用哪个函数, 由虚函数来支持.

静态联编中的赋值兼容性及名字支配规律

派生一个类的原因并非总是为了添加新的成员或成员函数, 有时是为了重新定义基类的成员函数.

#define PI 3.14159

class Point {
    double x;
    double y;
    
    public:
    Point(double a, double b) {
        x = a;
        y = b;
    }
    double area() {
        return 0;
    }
};

class Circle: public Point {
    double radius;
    
    public:
    Circle(double a, double b, double r):Point(a, b) {
        radius = r;
    }
    
    double area() {
        return PI * radius * radius;
    }
};

int main() {
    Point a(1.5, 6.7);
    Circle c(1.5, 6.7, 2.5);
    
    cout << a.area() << endl; // 调用对象a的成员函数area()
    cout << c.area() << endl; // 调用对象c的成员函数area()
    
    Point * p = &c; // 对象c的地址为指向Point类型指针赋值
    cout << p -> area() << endl; // 调用Point类的成员函数area()
    
    Point & rc = c; // 对象c初始化Point类型的引用
    cout << rc.area() << endl; // 调用Point类的成员函数area()
}复制代码

在派生类有同名函数的情况下

Point * pPoint; // 声明的基类指针只能指向基类
复制代码

Circle * pCircle // 声明的派生类指针只能指向派生类复制代码

如果派生类没有基类的同名函数, 派生类的指针才根据继承原则调用基类的函数

虚函数

一旦定义了虚函数, 该基类的派生类中的同名函数也自动成为虚函数.

虚函数的定义

用关键字virtual来声明一个虚函数, 虚函数只能是类中的一个成员函数, 不能是静态成员.

像这样

class Point {
    double x;
    double y;
    
    public:
    Point(double a, double b) {
        x = a;
        y = b;
    }
    
    // 用virtual关键字来定义一个虚函数
    virtual double area() {
        return 0;
    }
};

class Circle: public Point {
    double radius;
    
    public:
    Circle(double a, double b, double r):Point(a, b) {
        radius = r;
    }
    
    // 基类中area()函数为虚函数, 派生类中的同名函数也自动成为虚函数
    double area() {
        return PI * radius * radius;
    }
};
复制代码

虚函数实现多态性的条件

关键字virtual告诉编译器调用虚函数进行动态联编.

使用虚函数不一定产生多态性, 也不一定使用动态联编.

在调用中对虚函数使用成员名限定, 可以强制编译器对该函数使用静态联编.

产生运行多态性, 也就是动态联编有3个前提

  • 类之间的继承关系满足赋值兼容性规则
  • 改写了同名虚函数
  • 根据赋值兼容性规则使用指针(或引用)

像这样

class Point {
    double x;
    double y;
    
    public:
    Point(double a, double b) {
        x = a;
        y = b;
    }
    
    // 用virtual关键字来定义一个虚函数
    virtual double area() {
        return 0;
    }
};

class Circle: public Point {
    double radius;
    
    public:
    Circle(double a, double b, double r):Point(a, b) {
        radius = r;
    }
    
    // 基类中area()函数为虚函数, 派生类中的同名函数也自动成为虚函数
    double area() {
        return PI * radius * radius;
    }
};

void display(Point * p) {
    cout << p -> area() << endl;
}

void display(Point & a) {
    cout << a.area() << endl;
}

int main() {
    Point a(1.5, 6.7);
    Circle c(1.5, 6.7, 2.5);
    Point * p = &c; // 对象c的地址为指向Point类型指针赋值
    Point & rc = c; // 对象c初始化Point类型的引用
    
    display(a); // 调用对象a的成员函数area()
    display(p); // 根据运行时的多态性, p指向的c对象, 所以实际调用c对象的成员函数area()
    display(rc); // 根据运行时的多态性, rc是对c对象的引用, 所以实际调用c对象的成员函数area()
    /** 输出结果
     0
     19.6349
     19.6349
     */
}
复制代码

纯虚函数与抽象类

在基类中不给虚函数一个有意义的定义, 可以说明为纯虚函数, 将定义留给派生类去做

像这样

class 类名 {
    public:
    virtual 函数类型 函数名(参数列表) = 0;
};
复制代码

抽象类: 包含有纯虚函数的类称为抽象类. 一个抽象类至少有一个纯虚函数, 一个抽象类只能作为基类来派生新类, 不能说明抽象类的对象.

class Point {
    double x;
    double y;
    
    public:
    Point(double a, double b) {
        x = a;
        y = b;
    }
    
    // 用virtual关键字来定义一个虚函数
    virtual double area() = 0;
};

int main() {
	// Point a(1.5, 6.7); // Point为抽象类, 不能实例化一个对象 error Variable type 'Point' is an abstract class
	Point * p; // Point为抽象类, 可以声明一个指向抽象类对象的指针
}复制代码

注意

空虚函数定义 virtual void area() {}

纯虚函数定义 virtual void area() = 0;

纯虚函数的派生类仍是抽象类. 如果派生类中给出了基类所有纯虚函数的实现, 则该派生类不再是抽象类

类族

如果通过同一个基类派生一系列的类, 则将这些类总称为类族.

像这样

#define PI 3.14159

// 抽象类 有一个纯虚函数 area()
class Shape {
    public:
    virtual double area() = 0;
};

// 正方形 有一个连长数据成员
class Square: public Shape {
    protected:
    double h;
    
    public:
    Square(double a) {
        h = a;
    }
    double area() {
        return h * h;
    }
};

// 圆
class Circle: public Square {
    public:
    Circle(double a):Square(a) {
        
    }
    double area() {
        return h * h * PI;
    }
};

// 三角形
class Triangle: public Square {
    protected:
    double w;
    
    public:
    Triangle(double a, double b):Square(a) {
        w = b;
    }
    double area() {
        return w * h * 0.5;
    }
};

// 矩形
class Rect: public Triangle {
    public:
    Rect(double a, double b):Triangle(a, b) {
        
    }
    double area() {
        return w * h;
    }
};

int main() {
    
    Shape * s[5];
    s[0] = new Square(4);
    s[1] = new Circle(10);
    s[2] = new Rect(3, 6);
    s[3] = new Triangle(3, 6);
    s[4] = new Square(6);
    for (int i = 0; i < 5; i++) {
        // 因为虚函数支持动态联编, 所以在运行时才确定每个元素的类型, 调用各自的成员函数
        cout << "s[" << i << "] = " << s[i] -> area() << endl;
    }
}
复制代码

多重继承与虚函数

多重继承可以被视为多个单一继承的组合.

// 基类
class A {
    public:
    virtual void func() {
        cout << "call A" << endl;
    }
};

// 基类
class B {
    public:
    virtual void func() {
        cout << "call B" << endl;
    }
};

// 多重继承
class C: public A, public B {
    public:
    void func() {
        cout << "call C" << endl;
    }
};

int main() {
    A * pa;
    B * pb;
    C * pc, c;
    
    pa = &c;
    pb = &c;
    pc = &c;
    pa -> func(); // 动态联编, pa指向派生类对象c, 调用对象c的成员函数C::func();
    pb -> func(); // 动态联编, pb指向派生类对象c, 调用对象c的成员函数C::func();
    pc -> func(); // pc既是指向C类对象的指针, 又是指向的C类对象, 调用对象c的成员函数C::func();
}复制代码

总结

C++有两种多态性, 一种是编译时多态性, 也叫静态联编; 另一种是运行时多态性, 也叫动态联编. 这大大提高了我们解决问题的丰富性. 可能也是C++长久不衰的魅力所在吧! 我会继续深入学习C++, 继续挖掘语言的本质!

本系列文章会持续更新! 大家踊跃的留下自己的脚印吧!

👣👣👣👣👣👣👣👣


关注下面的标签,发现更多相似文章
评论