• 【C++入门】(纯)虚函数和多态、抽象类、接口


    1、虚函数和多态

    (1)虚函数定义:在类中用virtual修饰的成员函数;
    (2)纯虚函数:在基类中,虚函数可以有函数实体,也可以没有函数实体,如果没有函数实体则是纯虚函数;含有纯虚函数的类是不能定义对象的;
    (3)多态:用基类的指针或者引用指向派生类,访问派生类中同名覆盖的成员函数;

    2、虚函数的示例代码

    #include 
    
    using namespace std;
    
    //用来描述动物的基类
    class Animal
    {
    public:
    	
    	virtual void speak(void); 		// 虚函数
    	//virtual void speak(void) = 0;	// 纯虚函数
    	//void speak(void);	//普通函数
    	
    };
    
    //用动物基类来构建猫这个类
    class Cat : public Animal
    {
    public:
    	
    	void speak(void);
    };
    
    //用动物基类来构建狗这个类
    class Dog : public Animal
    {
    public:
    	
    	void speak(void);
    };
    
    //虚函数的实体
    void Animal::speak(void)
    {
    	cout << "Animal speak" << endl;
    }
    
    void Cat::speak(void)
    {
    	cout << "miao miao miao" << endl;
    }
    
    void Dog::speak(void)
    {
    	cout << "wang wang wang" << endl;
    }
    
    int main(void)
    {
    	Animal *p;
    	Cat d;			
    	Dog c;			
    	
    	//测试用基类的指针去指向派生类对象时,调用的是基类还是派生类的speak方法
    	p = &c;
    	p->speak();
    	
    	p = &d;
    	p->speak();
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    3、代码执行结果分析

    //基类speak成员定义为普通函数
    [root#]$ ./app 
    Animal speak
    Animal speak
    
    //基类speak成员定义为虚函数
    [root#]$ ./app 
    wang wang wang
    miao miao miao
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (1)基类Animal的speak定义为普通函数:用基类的指针去指向派生类对象时,调用的是基类的speak方法;
    (2)基类Animal的speak定义为虚函数:用基类的指针去指向派生类对象时,调用的是派生类的speak方法;
    总结:当使用类的指针调用成员函数时,普通函数由指针类型决定,而虚函数由指针指向的对象的实际类型决定;

    4、C语言实现上面虚函数的效果(多态)

    4.1、示例代码

    #include 
    
    typedef enum 
    {
    	ANIAMAL_CAT,
    	ANIAMAL_Dog
    }ANIAMAL_TYPE;
    
    typedef struct 
    {
    	ANIAMAL_TYPE type;		//动物的种类
    	int (*speak)(void);		//动物叫的方法
    }Animal;
    
    //猫的叫声
    int cat_speak(void)
    {
    	printf("miao miao miao\n");
    	return 0;
    }
    
    //狗的叫声
    int dog_speak(void)
    {
    	printf("wang wang wang\n");
    	return 0;
    }
    
    int main()
    {	
    	Animal cat;
    	Animal dog;
    
    	cat.type = ANIAMAL_CAT;
    	cat.speak = cat_speak;
    
    	dog.type = ANIAMAL_Dog;
    	dog.speak = dog_speak;
    
    	cat.speak();
    	dog.speak();
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    4.2、代码结构分析

    [root#]$ ./a.out 
    miao miao miao
    wang wang wang
    
    • 1
    • 2
    • 3

    (1)用Animal结构体来抽象的表示动物,定义了动物的一些共有属性,然后再用Animal结构体来表示猫和狗;
    (2)在Animal结构体中定义了speak函数指针,在定义的cat和dog中speak体现出不同的效果,也就是C++中的多态;
    总结:C++中的虚函数,和C语言中的函数指针功能类似,但是用C++的虚函数去实现多态要更简单;

    5、纯虚函数和抽象类

    (1)纯虚函数:基类中虚函数只有声明没有定义函数实体;
    (2)纯虚函数的声明:virtual 函数原型=0;
    (3)抽象类:带有纯虚函数的类成为抽象类;(注意并不要求全部类的全部函数都要是纯虚函数)
    (4)抽象类只能作为基类来构建派生类,不可实例化对象;毕竟纯虚函数没有函数实体,就像C语言里声明了函数指针但是没有指向具体的函数,如果实例化成对象,那声明的函数指针就是野指针;
    (5)抽象类的作用:将一类事物的共性抽离出来组织成一个基类,用来构成派生类;可以结合上面动物(基类)、猫(派生类)、狗(派生类)的例子去理解;
    (6)继承抽象类的子类必须实现基类的纯虚函数,才能去示例化对象;

    6、接口

    (1)接口是特殊的抽象类,类中没有成员变量,全是纯虚函数;
    (2)接口就是用来定义一套接口,就好像通信协议一样,只关心函数的返回值、传参,并不关心函数的具体实现,纯虚函数都有派生类去实现;
    (3)接口就相当于C语言中定义了一个成员全是函数指针的结构体;

    7、虚析构函数

    7.1、虚析构函数定义

    (1)当基类含有1个或多个虚函数时,其析构函数需要声明为virtual虚函数;
    (2)将析构函数定义为虚函数,可以保证派生类析构时能调用正确的析构函数;

    7.2、示例代码

    #include 
    
    using namespace std;
    
    
    // 父类 animal,抽象基类 abstract base type
    class Animal
    {
    public:
    
    	virtual void speak(void) = 0;			// 纯虚方法
    
    	//virtual ~Animal();	//虚析构函数
    	
    	~Animal();	//普通析构函数
    
    };
    
    // 子类
    class Dog : public Animal
    {
    public:
    	
    	void speak(void);
    	
    	~Dog();
    
    };
    
    Animal::~Animal()
    {
    	cout << "~Animal" << endl;
    }
    
    Dog::~Dog()
    {
    	cout << "~Dog" << endl;
    }
    
    void Dog::speak(void)
    {
    	cout << "wang wang wang" << endl;
    }
    
    int main(void)
    {
    #if 0
    	Animal *p = new Dog();		// 对象是Dog类对象,但是分配在堆上面, 调用的是派生类的析构函数——错误
    	p->speak();				 
    	delete p;				
    #else
    	
    	Dog d;					// 对象是Dog类对象,分配在栈上, 调用的是派生类的析构函数————正确
    	Animal *p = &d;
    	p->speak();				
    
    #endif
    	
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    7.3、代码执行结果分析

    //普通析构函数:对象分配在堆上,执行的基类的析构函数(错误)
    [root#]$ ./app 
    wang wang wang
    ~Animal
    
    //普通析构函数:对象分配在栈上,执行的派生类的析构函数(正确)
    [root#]$ ./app 
    wang wang wang
    ~Dog
    ~Animal
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (1)普通析构函数:对象分配在堆上时,会执行基类的析构函数(错误);
    (2)徐析构函数:无论对象分配在栈上还是堆上,都能正确调用派生类的析构函数;
    补充:基类和派生类的析构函数关系参考博客:https://blog.csdn.net/weixin_42031299/article/details/127342145;

    8、构造函数不能是虚函数

    (1)宏观:虚函数的作用在于通过父类的指针或者引用来调用派生类的时候能够正确调用派生类的重写的成员函数;然而构造函数在构建对象的时候就已经自动调用,所以根本不存在上面说的通过父类指针或者引用来调用派生类的构造函数;
    (2)微观:虚函数在实现原理上是依赖vtable虚函数表,而虚函数表的构建是依赖构造函数的初始化,所以构造函数是虚函数实现的前提,不能把构造函数实现成虚函数;
    总结:宏观上不需要将构造函数实现成虚函数,微观上不支持将构造函数实现成虚函数;

    9、虚函数的总结

    (1)虚函数的作用是实现面向对象的多态效果,让成员函数在运行时动态解析和绑定具体的执行函数;
    (2)将成员函数定义为virtual虚函数是有额外开销的,因为要在运行时动态绑定;如果是普通函数,在编译时就可以确定并绑定完成;

  • 相关阅读:
    自动化测试面试常见技术题目
    美团CTF个人决赛WP
    (一)JPA的快速入门
    5、声明式事务
    “拿捏”红黑树
    Pytorch学习笔记 (参考官方教程)
    三井住友保险中国区信息技术部负责人陈婧,将出席“ISIG-RPA超级自动化产业发展峰会”
    Mobx 结合 TypeScript 实现 setState 类型推导
    AI 帮写代码 67 元/月,GitHub Copilot 开启收费模式!
    Android高仿网易云音乐-启动界面实现和动态权限处理
  • 原文地址:https://blog.csdn.net/weixin_42031299/article/details/127579640