<<从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++, 继续挖掘语言的本质!

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

????????


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是面向对象编程中的一个重要概念,它允许不同的对象对同一个消息做出不同的响应。在C++中,通过函数实现多。在引用中的第一个程序中,定义了一个基类ONE和两个派生类TWO和THREE。基类ONE中定义了一个函数f(),派生类TWO和THREE都对该函数进行了重写。在main函数中,通过指针p调用了基类ONE和派生类THREE中的f()函数。由于f()函数函数,在运行时会根据指针的实际类型来确定调用哪个版本的f()函数。因此,在p指向派生类THREE的对象时,调用的是派生类THREE中的f()函数。这就是多的体现。中的程序二中,定义了一个基类Base和一个派生类SubClass。基类Base中定义了一个函数fn(),派生类SubClass对该函数进行了重写。在main函数中,通过指针p分别调用了基类Base和派生类SubClass中的fn()函数。同样地,由于fn()函数函数,在运行时会根据指针的实际类型来确定调用哪个版本的fn()函数。因此,当p指向派生类SubClass的对象时,调用的是派生类SubClass中的fn()函数。这也是多的一种表现形式。中的程序中,定义了一个类A和一个派生类B。类A中有两个私有的整型变量a和b,并定义了构造函数进行初始化,以及成员函数geta()和getb()来获取a和b的值。派生类B从类A继承并覆盖了geta()函数,使其返回a的两倍。在main函数中,声明了一个类B的对象,并调用了该对象中的geta()函数,将结果输出。这里也涉及到了多,因为通过指针p调用的是派生类B中的geta()函数,而不是基类A中的geta()函数123 #### 引用[.reference_title] - *1* *2* *3* [C++实验8报告多函数](https://blog.csdn.net/qq_44621510/article/details/90724548)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值