首页 > 其他分享 >四. 多态性和虚函数

四. 多态性和虚函数

时间:2023-12-04 17:24:14浏览次数:31  
标签:函数 多态性 基类 纯虚 析构 派生类 抽象类

文章参考:

《C++面向对象程序设计》✍千处细节、万字总结(建议收藏)_白鳯的博客-CSDN博客

1. 多态性概述

所谓多态性,就是不同对象接收不同消息时,产生不同的动作。这样就可以用相同的接口访问不同的函数,从而实现一个接口,多种方法

从实现方式上看,多态性分为两种:

  • 编译时多态:
    • 在C++中,编译时多态与连编(把函数名和函数定义连接在一起)有关。静态连编时,系统用实参与形参进行匹配,对于同名的重载函数便根据参数上的差异进行区分,然后进行连编,从而实现了多态性。
    • 优缺点:在程序编译时就知道调用函数的全部信息。因此,这种连编类型的函数调用速度快、效率高,但缺乏灵活性
    • 实现方式:主要通过函数重载运算符重载实现。
  • 运行时多态:
    • C++中通过动态连编实现。动态连编在程序运行时生效,即当程序调用到某一函数名时,才去寻找和连接其程序代码。
    • 优缺点:降低了程序的运行效率,但增强了程序的灵活性。
    • 实现方式:主要通过虚函数实现。

2. 虚函数

定义:

虚函数的声明是在基类中进行的,通过virtual关键字将基类的成员函数声明为虚函数。语法如下:

virtual 返回值 函数名 (形参表){
    函数体;
}

当基类中的某个成员函数被定义为虚函数时,就可以在派生类中对该虚函数进行重新定义,并使用基类调用派生类的实现。

EG:

  • 代码:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Person{
    private:
        string flower;
    public:
        Person(string flower = "鲜花"): flower(flower){}
        string getFlower(){
            return this->flower;
        }
        virtual void show(){
            cout<< "人类喜欢:"<< this->flower<< endl;
        }
    };
    
    class Man: public Person{
    public:
        Man(string flower = "油菜花"): Person(flower){}
        // 派生类中的virtual可加可不加
        virtual void show(){
            cout<< "男人喜欢:"<< this->getFlower()<< endl;
        }
    };
    
    class Woman: public Person{
    public:
        Woman(string flower = "西兰花"): Person(flower){}
        void show(){
            cout<< "女人喜欢:"<< this->getFlower()<< endl;
        }
    };
    
    int main(void){
        Person *p = nullptr;
        Person per;
        Man m;
        Woman w;
        per.show();
        p = &m;
        p->show();
        p = &w;
        p->show();
        return 0;
    }
    
  • 输出:

    人类喜欢:鲜花
    男人喜欢:油菜花
    女人喜欢: 西兰花
    

注意:

  • 在派生类对基类中声明的虚函数进行重新定义时,关键字virtual可以写也可以不写。如不写,这时系统就会遵循以下的规则来判断一个成员函数是不是虚函数:该函数与基类的虚函数是否有相同的名称、参数个数以及对应的参数类型、返回类型或者满足赋值兼容的指针、引用型的返回类型。
  • 虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为虚函数调用要靠特定的对象来决定该激活哪个函数。
  • 内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的。即使虚函数在类的内部定义,编译时仍将其看做非内联的。
  • 构造函数不能是虚函数,但是析构函数可以是虚函数,而且通常说明为虚函数。

3. 虚析构函数

问题:

假设现在有一个派生类B,其基类为A,我们建立一个A类型的指针p,再new一个B类型的对象,并令p指向B的地址。那么当使用delete 释放指针p所指向的内存时,系统只会执行基类的析构函数,不执行派生类的析构函数

这是因为当撤销指针p所指向的空间时,采用了静态连编的方式,只执行了基类A的析构函数,而不会执行基类B的析构函数。

解决方法:如果希望程序执行动态连编时,先调用派生类的析构函数,再调用基类的析构函数,可以将基类的析构函数声明为虚析构函数。格式为:

virtual ~类名(){
    函数内容;
}

EG:

  • 代码:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Base{
    public:
    	virtual ~Base() {
    		cout << "调用基类Base的析构函数..." << endl;
    	}
    };
    
    class Derived: public Base{
    public:
    	~Derived() {
    		cout << "调用派生类Derived的析构函数..." << endl;
    	}
    };
    
    int main() {
    	Base *p;
    	p = new Derived;
    	delete p;
    	return 0;
    }
    
  • 输出:

    调用基类Base的析构函数...
    调用派生类Derived的析构函数...
    

注意:

  • 虽然基类和派生类的析构函数名不一致,但如果将基类的析构函数声明为析构函数,那么其派生类的析构函数也都会变成虚函数
  • 只有在上述情况下,才会出现只调用基类,而不调用派生类析构函数的情况。如果使用的栈空间,或者直接用派生类类型的指针,就不会出现这种问题。

4. 纯虚函数

纯虚函数是在声明虚函数时被“初始化为0的函数”,声明纯虚函数的一般形式如下:

virtual 函数类型 函数名(参数表) = 0;

声明为纯虚函数后,基类中就不再给出程序的实现部分。纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要重新定义。

5. 抽象类

如果一个类至少有一个纯虚函数,那么就称该类为抽象类。对于抽象类的使用有以下几点规定:

  • 因为抽象类中包含一个没有定义功能的纯虚函数。因此,抽象类只能作为其他类的基类使用,不能家里抽象类对象。
  • 不允许从具体类(不包含纯虚函数的类)中派生出抽象类。
  • 抽象类不能用作函数的返回类型、参数类型或是显式转换的类型
  • 可以声明指向抽象类的指针或引用,此指针可以指向它的派生类,进而实现多态性。
  • 如果派生类中没有定义纯虚函数的实现,而派生类中只是继承基类的纯虚函数,则这个派生类仍然是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。

6. 实例

问题:

利用C++的多态性,计算三角形、矩形的面积。

代码:

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

// 定义一个抽象类
class Figure{
// 使用protected,便于派生类直接访问
protected:
    double x;
    double y;
public:
    Figure(double m, double n): x(m), y(n){}
    // 使用需析构函数,避免调用析构函数时只调用基类的
    virtual ~Figure(){
        cout << "Figure is destroyed..."<< endl;
    }
    // 纯虚构函数
    virtual double get_area() = 0;
};

class Triangle: public Figure{
public:
    Triangle(double b, double h): Figure(b, h){}
    ~Triangle(){
        cout<< "Triangle is destroyed..."<< endl;
    }
    double get_area(){
        return x*y/2;
    }
};

class Square: public Figure{
public:
    Square(double b, double h): Figure(b, h){}
    ~Square(){
        cout<< "Square is destroyed..."<< endl;
    }
    double get_area(){
        return x*y;
    }
};

int main(void){
    Figure *p = nullptr;
    p = new Triangle(5, 10);
    cout<< p->get_area()<< endl;
    delete p;
    p = new Square(5, 10);
    cout<< p->get_area()<< endl;
    delete p;
    return 0;
}

输出:

25
Figure is destroyed...
Triangle is destroyed...
50
Figure is destroyed...
Square is destroyed...

标签:函数,多态性,基类,纯虚,析构,派生类,抽象类
From: https://www.cnblogs.com/beasts777/p/17875455.html

相关文章

  • 六. 函数模板和类模板
    文章参考:《C++面向对象程序设计》✍千处细节、万字总结(建议收藏)_白鳯的博客-CSDN博客1.引入在编写函数和类时,有时会出现这样的情况,具体实现方式完全一致,但因此参数类型、返回值类型、数据类型等因素的不同,导致不得不写多个函数或者类(因为C++是强类型语言,无法隐式转换,且有些......
  • C++ 内联函数 inline
    宏定义实现和普通函数实现:-宏定义是直接在实现的时候进行代码替换,可能产生结果异常问题。-普通函数实现:调用函数进出函数体的时候时间开销可能过大。1#include<iostream>2usingnamespacestd;3//宏实现4#defineGETMAX(a,b)((a)>(b)?(a):(b))......
  • Java 函数式接口
    在Java中,函数式接口(functionalinterface)是指仅包含一个抽象方法的接口。这种类型的接口通常用于表示简单的函数签名,并可与lambda表达式或方法引用结合使用,从而提供一种更简洁、更灵活的编程方式。Java8引入了函数式接口的概念,以及一个新的注解@FunctionalInterface。这个注解......
  • vue3 setup 父组件向子组件传递参数、方法|子组件向父组件传递数据,函数
    https://blog.csdn.net/qq_27517377/article/details/123163381https://blog.csdn.net/qq_27517377/article/details/123166367vue3setup父组件向子组件传递参数参数<template><el-rowclass="mb-4"> <el-buttontype="danger">props.vue传......
  • 无涯教程-Erlang - atan函数
    该方法返回指定值的反正切。atan-语法atan(X)X - 为反正切函数指定一个值。atan-返回值返回值是一个浮点值,表示反正切值。-module(helloLearnfk).-import(math,[atan/1]).-export([start/0]).start()->Atan=atan(0.7071),io:fwrite("~p~n",[Atan]......
  • Python函数介绍
    1.函数1.1函数概述函数定义和优势不同形状正方形打印#2个foriinrange(0,2):forjinrange(0,2):print("*",end="")print()#3个foriinrange(0,3):forjinrange(0,3):print("*",end="")pr......
  • MySQL部分函数
    单行函数数值函数基本函数函数用法ABS(x)返回x的绝对值SIGN(X)返回X的符号。正数返回1,负数返回-1,0返回0PI()返回圆周率的值CEIL(x),CEILING(x)返回大于或等于某个值的最小整数FLOOR(x)返回小于或等于某个值的最大整数LEAST(e1,e2,e3…)返回......
  • 无涯教程-Erlang - sin函数
    此方法返回指定值的正弦值。sin-语法sin(X)X - 为正弦函数指定一个值。sin-返回值返回值是代表正弦值的浮点值。-module(helloLearnfk).-import(math,[sin/1]).-export([start/0]).start()->Sin=sin(45),io:fwrite("~p~n",[Sin]).当我们运行上......
  • 无涯教程-Erlang - 嵌套if函数
    有时,需要相互之间嵌入多个if语句,这在其他编程语言中也是可能的。在Erlang中,这也是可能的。Nestedif-示例-module(helloLearnfk).-export([start/0]).start()->A=4,B=6,ifA<B->ifA>5->io:fw......
  • 关于函数宏offset_of 和 container_of的学习
    #defineoffset_of(type,member)((unsignedint)&((type*)0)->member)#definecontainer_of(ptr,type,member)((type*)((char*)(ptr)-offset_of(type,member)))offset_of(type,member)用途:用于获取获取结构体某一个成员在该结构体中的位置参数1:type,表示......