C++---虚函数(8)

news2024/10/6 20:32:31

多态

虚函数

虚函数就是在类的成员函数声明前加virtual,该成员函数就变成了虚函数。一旦一个类中有虚函数,编译器就会为该类生成虚函数表。

虚函数表中一个元素记录一个虚函数的地址,使用该类构造对象时,对象前4(8)个字节记录虚函数表的首地址。

/02-虚函数和虚函数表/

#include <iostream>

using namespace std;

class Animal{
public:
    //成员函数
    virtual void show()//虚函数
    {
        cout<<"Animal show"<<endl;
    }

    void run()
    {
        cout<<"Animal run"<<endl;
    }

    virtual void eat()
    {
        cout<<"Animal eat"<<endl;
    }

};

class Dog:public Animal{
public:
    //重写虚函数
    virtual void show()//虚函数
    {
        cout<<"Dog show"<<endl;
    }

    virtual void eat()
    {
        cout<<"Dog eat bones"<<endl;
    }

};

class Cat:public Animal{
public:
    //重写虚函数
    virtual void show()//虚函数
    {
        cout<<"Cat show"<<endl;
    }

    virtual void eat()
    {
        cout<<"Cat eat fish"<<endl;
    }

};

typedef void (*pfunc_t)();//为函数指针类型起别名

int main()
{
    
    //没有成员变量的类类型对象所占空间大小是1
    //cout<<sizeof(Animal)<<endl;
    Animal an;
    //long addr = *(long *)&an;//取出对象前8个字节的数据
    //pfunc_t *pt = (pfunc_t *)addr;//转化为虚函数表首地址类型

    pfunc_t *pt = *(pfunc_t **)&an;//取出对象前8个字节的数据 --- 虚函数表的地址
    //调用虚函数
    pt[0]();//an.show();
    pt[1]();//an.eat();

	       
	     return 0;
}

如果父类中有虚函数,子类中可以对虚函数进行重写(overwrite),子类会继承父类的虚函数表,但是重写的虚函数会覆盖父类中对应虚函数在虚函数表中的位置。

/02-虚函数和虚函数表/

#include <iostream>

using namespace std;

class Animal{
public:
    //成员函数
    virtual void show()//虚函数
    {
        cout<<"Animal show"<<endl;
    }

    void run()
    {
        cout<<"Animal run"<<endl;
    }

    virtual void eat()
    {
        cout<<"Animal eat"<<endl;
    }

};

class Dog:public Animal{
public:
    //重写虚函数
    virtual void show()//虚函数
    {
        cout<<"Dog show"<<endl;
    }

    virtual void eat()
    {
        cout<<"Dog eat bones"<<endl;
    }

};



typedef void (*pfunc_t)();//为函数指针类型起别名

int main()
{

    Dog dg;

    pt = *(pfunc_t **)&dg;//取出对象前8个字节的数据 --- 虚函数表的地址
    //调用虚函数
    pt[0]();//an===>show();
    pt[1]();//dg===>eat();
    return 0;
}

如果父类的虚函数在子类中被重写,则可以使用父类类型记录子类对象,此时调用虚函数会去调用子类中虚函数的实现,而不调用父类中的原虚函数,普通的成员函数没有这个特点。

/02-虚函数和虚函数表/

#include <iostream>

using namespace std;

class Animal{
public:
    //成员函数
    virtual void show()//虚函数
    {
        cout<<"Animal show"<<endl;
    }

    void run()
    {
        cout<<"Animal run"<<endl;
    }

    virtual void eat()
    {
        cout<<"Animal eat"<<endl;
    }

};

class Dog:public Animal{
public:
    //重写虚函数
    virtual void show()//虚函数
    {
        cout<<"Dog show"<<endl;
    }

    virtual void eat()
    {
        cout<<"Dog eat bones"<<endl;
    }

};



typedef void (*pfunc_t)();//为函数指针类型起别名

int main()
{
    
    //有虚函数,父类类型记录子类对象
    Animal *pa = new Dog;   //可以会有指向问题,不建议这样使用
    pa->show();          //有虚函数就可以这样写
    pa->run();
    pa->eat();

    delete pa;
    return 0;
}

使用虚函数可以实现用父类类型记录子类对象,可以通过父类型的 指针/引用 访问子类中对应的接口,大大提高编程的灵活性(动态绑定),这种语法就叫多态。(简单的是父类指向子类)

#include <iostream>

using namespace std;

class Animal{
public:
    //虚析构
    virtual ~Animal()
    {
        cout<<"~Animal"<<endl;
    }

    //成员函数
    virtual void show()//虚函数
    {
        cout<<"Animal show"<<endl;
    }

    void run()
    {
        cout<<"Animal run"<<endl;
    }

    virtual void eat()
    {
        cout<<"Animal eat"<<endl;
    }

};

class Dog:public Animal{
public:
    virtual ~Dog()
    {
        cout<<"~Dog"<<endl;
    }

    //重写虚函数
    virtual void show()//虚函数
    {
        cout<<"Dog show"<<endl;
    }

    void run()//名字隐藏
    {
        cout<<"Dog run"<<endl;
    }

    virtual void eat()
    {
        cout<<"Dog eat bones"<<endl;
    }

};

class Cat:public Animal{
public:
    virtual ~Cat()
    {
        cout<<"~Cat"<<endl;
    }

    //重写虚函数
    virtual void show()//虚函数
    {
        cout<<"Cat show"<<endl;
    }

    virtual void eat()
    {
        cout<<"Cat eat fish"<<endl;
    }

};

//传入一个Animal对象 ----- 多态
void animal_gogo(Animal *p)
{
    p->show();
    p->eat();
}

int main()
{
    Animal *pa = new Animal;
    //pa->show();
    //pa->run();
    //pa->eat();
    animal_gogo(pa);   //动态接口绑定 
    delete pa;

    //有虚函数,父类类型记录子类对象
    pa = new Dog;
    //pa->show();
    //pa->run();
    //pa->eat();
    animal_gogo(pa);
    delete pa;

    pa = new Cat;
    //pa->show();
    //pa->run();
    //pa->eat();
    animal_gogo(pa);
    delete pa;

    //Dog dg;   
    //Animal &ra = dg;
    //ra.show();   //这样也是多态
    //ra.Animal::show();

    return 0;
}

几种重名机制的处理

函数重载(overload)

名字隐藏(namehide)

虚函数重写(overwrite)

函数重载:在同一作用域,函数名相同,参数列表不同的函数构成重载关系 名字隐藏:子类中出现与父类中同名的成员,子类中父类的同名成员会被隐藏 虚函数重写:子类中重写父类的虚函数,父类的虚函数在子类的虚函数表中将被覆盖(覆盖是虚函数表)

多态的总结

通过父类 指针/引用 记录子类对象,调用虚函数时体现的是子类中虚函数的实现。

1.继承是多态的基础 2.虚函数是实现多态的关键 3.虚函数重写是实现多态的必要条件

练习:

实现一个类Phone,有成员price和虚函数show,func,继承产生SmartPhone,重写其虚函数,SmartPhone继承产生IPhone,重写其虚函数。

实现一个全局函数void Use_Phone(Phone &p),参数时Phone的引用,在函数中传递不同对象调用该函数。

#include <iostream>

using namespace std;

class Phone {
public:
    //虚析构
    Phone(double p = 0.0) :price(p)
    {
       
    }

    //成员函数
    virtual void show()//虚函数
    {
        cout << "Phone show" << endl;
    }
    virtual void func()//虚函数
    {
        cout << "Phone 打电话" << endl;
    }
    double get_pricr() {
        return this->price;
    }
private:
    double price;
};

class SmartPhone :public Phone {
public:

    SmartPhone(double p = 0.0) :Phone(p)
    {
      
    }

    //重写虚函数
    virtual void show()//虚函数
    {
        cout << "SmartPhone show" << endl;
    }
    virtual void func()//虚函数
    {
        cout << "SmartPhone 打电话,听音乐" << endl;
    }

};

class IPhone :public SmartPhone {
public:

    IPhone(double p = 0.0) :SmartPhone(p)
    {
      
    }

    //重写虚函数
    virtual void show()//虚函数
    {
        cout << "IPhone show" << endl;
    }
    virtual void func()//虚函数
    {
        cout << "IPhone 打电话,听音乐,刷抖音,Wechat" << endl;
    }

};


//传入一个Animal对象 ----- 多态
void usePhone(Phone &p){

    p.show();
    p.func();
}

int main()
{
    Phone nokia(300);
    SmartPhone xiaomi(2000);
    IPhone iphone13(6000);

    usePhone(nokia);
    usePhone(xiaomi);
    usePhone(iphone13);

    return 0;
}

另一种写法

但是这个方法在delate时会调用析构父类的方法,引入虚析构加上关键字

#include <iostream>

using namespace std;

class Phone {
public:



    Phone(double p = 0.0) :price(p)
    {

    }

    //成员函数
    virtual void show()//虚函数
    {
        cout << "Phone show " << this->get_price() << endl;
    }
    virtual void func()//虚函数
    {
        cout << "Phone 打电话" << endl;
    }
    double get_price() {
        return this->price;
    }
private:
    double price;
};

class SmartPhone :public Phone {
public:

    SmartPhone(double p = 0.0) :Phone(p)
    {

    }

  
       
            //重写虚函数
        virtual void show()//虚函数
        {
            cout << "SmartPhone show " << this->get_price() << endl;
        }
        virtual void func()//虚函数
        {
            cout << "SmartPhone 打电话,听音乐" << endl;
        }

};

class IPhone :public SmartPhone {
public:

    IPhone(double p = 0.0) :SmartPhone(p)
    {

    }



    //重写虚函数
    virtual void show()//虚函数
    {
        cout << "IPhone show " << this->get_price() << endl;
    }
    virtual void func()//虚函数
    {
        cout << "IPhone 打电话,听音乐,刷抖音,Wechat" << endl;
    }

};


//传入一个Animal对象 ----- 多态
void usePhone(Phone* p) {

    p->show();
    p->func();
}

int main()
{

    Phone* nokia = new Phone(300);

    usePhone(nokia);   //动态接口绑定 
    delete nokia;


    SmartPhone* xiami = new SmartPhone(1000);

    usePhone(xiami);   //动态接口绑定 
    delete xiami;

    IPhone* iphone13 = new IPhone(6000);

    usePhone(iphone13);   //动态接口绑定 
    delete iphone13;   //这个方法会调用父类的方法


    return 0;
}

虚析构

如果父类类型指向子类对象,当释放对象时,默认调用父类的析构函数而不是子类的析构函数。如果在多态中希望根据对象的具体类型调用其析构函数,需要将析构函数写成虚析构(在析构函数前加virtual)。

类中本身有析构函数,同时也有虚函数时必须将析构函数写成虚析构。

#include <iostream>
/* employer  */

using namespace std;

class Phone {
public:

    //虚析构
    /*
     ~Phone()
    {
        cout<< " ~Phone show " << endl;
    }

    
    */
    virtual ~Phone()
    {
        cout << " ~Phone show " << endl;
    }

    Phone(double p = 0.0) :price(p)
    {

    }

    //成员函数
    virtual void show()//虚函数
    {
        cout << "Phone show " << this->get_price() << endl;
    }
    virtual void func()//虚函数
    {
        cout << "Phone 打电话" << endl;
    }
    double get_price() {
        return this->price;
    }
private:
    double price;
};

class SmartPhone :public Phone {
public:

    SmartPhone(double p = 0.0) :Phone(p)
    {

    }

    /*
    
    SmartPhone() {
        cout << " ~SmartPhone show " << endl;
    }
    
    
    */
    virtual ~SmartPhone() {
        cout << " ~SmartPhone show " << endl;
    }
       
            //重写虚函数
        virtual void show()//虚函数
        {
            cout << "SmartPhone show " << this->get_price() << endl;
        }
        virtual void func()//虚函数
        {
            cout << "SmartPhone 打电话,听音乐" << endl;
        }

};

class IPhone :public SmartPhone {
public:

    IPhone(double p = 0.0) :SmartPhone(p)
    {

    }

    /*
    
    ~IPhone()
    {
        cout << " ~IPhone show " << endl;
    }
    
    */
    virtual ~IPhone()
    {
        cout << " ~IPhone show " << endl;
    }

    //重写虚函数
    virtual void show()//虚函数
    {
        cout << "IPhone show " << this->get_price() << endl;
    }
    virtual void func()//虚函数
    {
        cout << "IPhone 打电话,听音乐,刷抖音,Wechat" << endl;
    }

};


//传入一个Animal对象 ----- 多态
void usePhone(Phone* p) {

    p->show();
    p->func();
}

int main()
{

    Phone* nokia = new Phone(300);

    usePhone(nokia);   //动态接口绑定 
    delete nokia;


    SmartPhone* xiami = new SmartPhone(1000);

    usePhone(xiami);   //动态接口绑定 
    delete xiami;

    IPhone* iphone13 = new IPhone(6000);

    usePhone(iphone13);   //动态接口绑定 
    delete iphone13;   //这个方法会调用父类的方法


    return 0;
}

纯虚函数和抽象类

纯虚函数

用virtual修饰,没有语句体,只有声明和(=0)的成员函数

语法:

class A{ //父类 public: virtual void show()=0;//纯虚函数 }

如果类中有纯虚函数,该类不能实例化对象,这种类叫抽象类。

抽象类的应用

抽象类的作用不是用来构造对象,而是用作父类(基类)继承产生子类

子类在继承抽象父类时,如果没有实现父类中所有的纯虚函数,那么子类仍然是一个抽象类。

如果一个类中所有的成员函数都是纯虚函数,那么该类就叫纯抽象类。(只有实现父类,才能用,不然会报错)

一个项目中的抽象类的设计属于框架设计的一部分。用来写实现

C++中类和实现的分离

C++中使用类来组织代码,类的声明写在头文件,类的声明包括成员变量的声明,成员函数的声明,成员变量的声明无需修改,类中的函数只保留声明语句。

类的函数实现写在配对的源文件中,实现类中的函数时应该指定该函数属于哪个类。

头文件(xxx.h xxx.hpp) class 类名{ 成员变量的声明; 成员函数的声明; 构造函数,析构函数,拷贝构造函数的声明; }; 源文件(xxx.cpp) 成员函数的实现; 构造函数,析构函数,拷贝构造函数的实现;

//函数参数的默认值要写到声明中(头文件) //初始化参数列表写到实现中(源文件)

练习:

将employer.cpp拆分为源文件和头文件的分离形式。

#include <cstring>
#include "employer.hpp"
​
​
Object::Object(const char *s)
{
    cout<<"Object()"<<endl;if(s){
        this->p = new char[strlen(s)+1];
        strcpy(this->p, s);
    }
    else{
        this->p = new char[10];
        memset(this->p,0,10);
    }
}//拷贝构造
Object::Object(const Object &o)
{
    cout<<"Object(const Object &o)"<<endl;
    if(strlen(o.p)){
        this->p = new char[strlen(o.p)+1];
        strcpy(this->p, o.p);
    }
    else{//空串
        this->p = new char[10];
        memset(this->p,0,10);
    }
}
​
Object::~Object()
{
    cout<<"~Object()"<<endl;
    delete[] this->p;
}//获取
const char *Object::get_p()
{
    return this->p;
}
​
​
//普通人类
Person::Person(const char *s,string name):Object(s),p_name(name)
{
    cout<<"Person()"<<endl;
}//获取
string Person::get_pname()
{
    return this->p_name;
}
​
​
//公司类
Comp::Comp(const char *s,string name):Object(s),c_name(name)
{
    cout<<"Comp()"<<endl;
}//获取
string Comp::get_cname()
{
    return this->c_name;
}
​
​
//员工类
Employer::Employer(const char *s,string pname,string cname,double salary):
    Object(s),Person(s,pname),Comp(s,cname),salary(salary)
{
    cout<<"Employer()"<<endl;
}void Employer::show()
{
    cout<<this->get_pname()<<":"<<this->get_cname()<<":"<<
          this->get_p()<<":"<<this->salary<<endl;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/669715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

团队知识库的使用场景有哪些?如何搭建团队知识库?

团队知识库是一种用于共享和管理团队知识和信息的工具。它可以帮助团队成员更好地组织、存储、查找和分享信息&#xff0c;提高工作效率和准确性。以下是团队知识库的使用场景和搭建方法的详细介绍。 一、团队知识库的使用场景 项目管理 团队知识库可以帮助团队成员更好地管…

封神之后,又来超神?南卡OE骨传导开放式耳机有啥新本领

终于到了适合夜跑的温度&#xff0c;我的新跑鞋和专用陪跑运动耳机也都收到了&#xff0c;万事俱备却意外中招“二阳”了。虽然身体情况&#xff0c;短期是不能支持去跑步运动了&#xff0c;但每天戴着新耳机打打王者也还是一样很欢乐的。 反正现在时间很多很闲&#xff0c;我…

pyqt6安装与配置(基于vscode环境)

一、安装pyqt6和pyqt6-tools包 注意&#xff1a;pyqt6-tools目前仅支持python3.9版本&#xff0c;3.9版本后的安装部成功。&#xff08;截止2022.11.20&#xff09; 1.1 安装pyqt6和pyqt6-tools 安装pyqt和pyqt6-tools可以使用conda和pip进行安装 &#xff08;1&#xff09…

基于高通camera-帧率-曝光

前人种树&#xff0c;后人乘凉&#xff1b;创造不易&#xff0c;请勿迁移~ daisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 daisy.skye擅长嵌入式,Qt,Linux,等方面的知识 https://blog.csdn.net/qq_40715266?typelately ———————————————— 版权声明&…

计算机网络(谢希仁-第八版)第四章习题全解

4-01 网络层向上提供的服务有哪两种&#xff1f;试比较其优缺点&#xff1f; 虚电路服务和数据报服务。 虚电路 优点&#xff1a; 1.可以提供可靠的通信服务 2.因为数据是沿着建立的虚电路进行传输的&#xff0c;因此分组的首部不需要携带完整的目的主机 的地址&…

redis 的基本介绍以及 五种 数据类型

一、redis是什么&#xff1f; 一句话&#xff1a;redis 是一个开源的、使用C语言编写的、支持网络交互&#xff0c;基于内存也可持久化的 key-value &#xff08;非关系型&#xff09;数据库 redis作者博客&#xff0c;有兴趣的小伙伴可以去逛一逛&#xff1a;http://github.…

后台登录功能开发 -- 手把手教你做ssm+springboot入门后端项目黑马程序员瑞吉外卖(二)

文章目录 前言三、后台登录功能开发1. 需求分析2. 代码开发3. 功能测试&#xff08;简略&#xff09; 四、后台退出功能开发1. 需求分析2. 代码开发3. 功能测试&#xff08;简略&#xff09; 总结 前言 为了巩固所学的知识&#xff0c;作者尝试着开始发布一些学习笔记类的博客…

Servlet规范maven创建webapp项目

main目录下新建webapp pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.…

C++完成淄博烧烤节管理系统

背景&#xff1a; 这次我们结合今年淄博烧烤做一个餐厅管理系统&#xff0c;具体需求如下&#xff0c;我们选择的是餐饮商家信息管理 问题描述&#xff1a; 淄博烧烤今年大火&#xff0c;“进淄赶烤”是大家最想干的事情&#xff0c;淄博烧烤大火特火的原因&#xff0c;火的…

SSM海南省陵水县旅游网站-计算机毕设 附源码87145

SSM海南省陵水县旅游网站 摘 要 本系统的前端界面主要实现页面的美观和动态效果使之符合广大群众的审美观&#xff0c;后台主要使用的技术主要有Java编程语言&#xff0c;SSM框架&#xff0c;JSP技术、Ajax技术进行开发系统。海南省陵水县旅游网站解决了传统旅游方式中数据分析…

开发者聚焦 | 不容错过的开发者新专栏就要来啦!

掌握前沿技术动态、获得新产品与新应用的使用技巧、在开放专业的社区中与志同道合的朋友交流互动&#xff0c;这些对于开发者来说&#xff0c;都是提高工作效率与自我技能的利器。 为了与开发者互动&#xff0c;并在社区与开发者建立连接和影响&#xff0c;让亚马逊云科技更好…

Toyota Programming Contest 2023#3(AtCoder Beginner Contest 306) 6月17日比赛 第四题

题目地址&#xff1a;D - Poisonous Full-Course 题目大意 时间限制&#xff1a;2秒&#xff0c;空间限制&#xff1a;1024MB&#xff0c;分值&#xff1a;400分 问题描述 小明来到了一家餐厅&#xff0c;一共有道菜&#xff0c;第道菜具有以下属性&#xff1a; 若&#xf…

web前端框架JS学习之JavaScript类型转换

vascript有多种数据类型&#xff0c;如字符串、数字、布尔等&#xff0c;可以通过typeof语句来查看变量的数据类型。数据类型转换就是数据类型之间相互转换&#xff0c;比如把数字转成字符串、把布尔值转成字符串、把字符串转成数字等&#xff0c;这在工作也是经常碰到的。 本…

C++----继承,多重继承(7)

面向对象的三大特征 封装 继承 多态 封装 该隐藏的数据私有化&#xff0c;该公开的数据设计为公有的接口 private public 目的为了更好地分工合作&#xff0c;有助于数据的安全性和使用的方便性&#xff0c;也防止不必要的扩展。 继承(inherite) 作用 实现了代码的复用&a…

浅析餐饮油烟对环境空 气质量的影响与对策

摘 要&#xff1a;为进一步揭示餐饮油烟的空气污染特性&#xff0c;研究了达州市市政中心环境空气自动监测站周边某餐饮业餐饮油烟对该站点周边环境的影响。结果表明:1&#xff09;距离餐饮油烟排气口越近&#xff0c;餐饮油烟对颗粒物的影响越明显&#xff0c;反之亦然。同时&…

编译原理笔记10:语言与文法,正规式转CFG,正规式和CFG,文法、语言与自动机

目录 正规式&#xff0c;和 CFG正规式到 CFG 的转换&#xff1a;正规式和 CFG 的关系为毛不用 CFG 描述词法规则贯穿词法、语法分析始终的思想 上下文有关文法 CSG文法、语言与自动机0型文法&#xff1a;1型文法&#xff1a;2型文法&#xff1a;3型文法&#xff1a;为什么&…

从0到1基于ChatGLM-6B使用LoRA进行参数高效微调

之前尝试了基于LLaMA使用LoRA进行参数高效微调&#xff0c;有被惊艳到。相对于full finetuning&#xff0c;使用LaRA显著提升了训练的速度。 虽然 LLaMA 在英文上具有强大的零样本学习和迁移能力&#xff0c;但是由于在预训练阶段 LLaMA 几乎没有见过中文语料。因此&#xff0…

pdf上传文件过大怎么缩小?pdf在线压缩的简单方法

在日常使用中&#xff0c;我们经常会遇到需要共享或传输pdf文件但是文件过大无法传输的情况&#xff0c;需要使用压缩图的pdf压缩&#xff08;https://www.yasuotu.com/pdfyasuo&#xff09;功能来解决这个问题&#xff0c;利用这款pdf在线压缩工具可以快速将pdf文件压缩&#…

【三维重建】【深度学习】windows10下NeRF代码Pytorch实现

【三维重建】【深度学习】windows10下NeRF代码Pytorch实现 提示:最近开始在【三维重建】方面进行研究,记录相关知识点,分享学习中遇到的问题已经解决的方法。 文章目录 【三维重建】【深度学习】windows10下NeRF代码Pytorch实现前言NeRF模型运行下载源码并安装环境训练NeRF训练…

【AI底层逻辑】——篇章12:统计学与概率论数据“陷阱”

目录 引入 一、“思维方式”是解题关键&#xff01;&#xff01; 1、统计思维的诞生 2、概率的力量 概率与数理统计的区别&#xff1f; 如何验证假设&#xff1f; 经验与现实如何共存——贝叶斯定理&#xff1f; “朴素”的朴素贝叶斯&#xff1f; 二、数据“陷阱” …