纯虚函数与抽象类

news2024/11/16 17:43:36

纯虚函数与抽象类

  • 虚析构函数
  • 状态转换的引入
    • C中的状态转换
    • C++中的状态转换
    • 职责链模式
  • 纯虚函数和虚基类
    • 使用规则
    • 实例
    • 接口继承和实现继承

虚析构函数

在上一次博客中写到了这么一段代码:

class object {
private: int value;
   public:
	   object(int x = 0) : value(x) {}
	   ~object() {}
	   virtual void print(int x) //2
	   {   cout << "object::print:" <<x<<endl; }
};
class Base :public object {
	int num;
public:
	Base(int x = 0) :object(x + 10), num(x) { }
	~Base() {}
	void print(int x) { cout << "Base::print:" << x << endl; }
};
int main() {
	object* op = new Base(10);
	op->print(1);
	delete op;
	return 0;
}

我们发现这段代码的问题出在析构的时候只会析构obj对象,不会析构Base对象,这是什么原因呢?因为在编译过程中op是obj类型的指针,所以就将op和obj类型进行了绑定,析构的时候同样也就只析构了obj类型,造成了内存泄漏。但是怎么解决这个问题呢,我们可以给obj类的析构函数加上virtual关键字,将其变成一个虚函数,这样析构的时候就是动态联编,析构时查看虚表看其指向的是哪个对象的析构函数,然后进行析构。当你的类的成员函数中存在虚函数,就需要将其析构函数设置成虚函数。
我们依旧是上面一段代码,执行下面主函数

int main() {
    // object* op[10] = { new Base,new Base, new Base, new Base, new Base, new Base, new Base, new Base, new Base, new Base };
    object* op[10];
    for (int i = 0; i < 10; i++) {
        op[i] = new Base;
    }
    op[4]->print(1);//动态联编
    (*(op[4])).print(1);//动态联编
    for (int i = 0; i < 10; i++) {
        delete op[i];
    }
    //delete []op;
    return 0;
}

本来我的思路是这样的,直接创建10个obj类型的指针,将其指向10个动态创建的Base对象,然后使用指针偏移量来查找虚函数并调用,并且析构。如果两个派生类没有属性,只有重新修改的虚函数,那么两个类型的大小相同,也就可以使用指针偏移量来解决这个问题,释放的时候也可以直接使用delete []op来析构。为什么可以这样析构呢,原因是在动态创建这个数组的时候会多申请四个字节来存放创建对象的个数,所以在析构的时候我们不需要写入创建对象的个数。但是呢我在用偏移量查找虚函数的时候出现了错误。因为两个类型的大小不一样,而偏移量是根据指针的类型进行偏移的,+1一次会按照其类型大小进行+1,所以呢当指针+1之后,偏移量+8,而偏移量+8针对于派生类没有偏移够一个对象的大小,也就是没有到下一个对象的首地址,为此呢我使用了指针数组,使得每一个指针指向一个Base对象,但是析构的时候便要循环析构指针。

状态转换的引入

C中的状态转换

我在做题的时候出现了这么一道题,给你一个字符串判断其中单词的个数。
我们写单词的时候会有这种情况(I’am)(hello-world)这样也算一个单词,同时解决这种问题。
我们便可以使用状态转换来解决这个问题,如下
我们举例一个字符串“I am a 45 student ”这样一个字符串,我们刚开始进入字符串,第一个I是字母,便进入了第一个单词,当遇到空格便在单词外,进入a便在单词里,依次遍历下去,进入单词里一次单词数+1,这就是状态转换,状态转换图:在这里插入图片描述
我们这里直接给出代码,代码过于简单,大家可以自行理解

#define BEGIN    0
#define IN_WORD  1
#define OUT_WORD 2 
int StrToWord(const char* str)
{
    int sum = 0;
    int tag = BEGIN;
    for (const char* p = str; *p != '\0'; ++p)
    {
        switch (tag)
        {
        case BEGIN:
            if (isalpha(*p)) { tag = IN_WORD; }
            else { tag = OUT_WORD; }
            break;
        case IN_WORD:
            if (isalpha(*p)||(*p) == '\'' || (*p) == '-') { }
            else {
                tag = OUT_WORD;
                sum += 1;
            }
            break;
        case OUT_WORD:
            if (isalpha(*p))
            {
                tag = IN_WORD;
            }
            break;
        }
    }
    if (IN_WORD == tag) sum++;
    return sum;
}

C++中的状态转换

在c语言中存在状态转换,是使用一个变量的不同值来设置不同的状态,而C++中自然也存在状态转换,这就使用到了抽象类和多态。
举一个例子,在童话故事中,有青蛙王子这么一说,有一只青蛙,叫声是Ribbet,其被公主亲吻了一下便有概率变成王子,王子说话是Darling,但是也有概率变成一只狗,其叫声是wangwang,这个实例中存在三种状态,青蛙,王子和狗,其表现形式便是不一样的,我们可以写一个程序,一只青蛙,被公主亲了一下,变成了王子或者狗。代码如下:

class Creature
{
    class State
    {
    public:
        virtual string response() = 0; // 纯虚函数   // 
        virtual ~State() {}
    };
    class Forg : public State
    {
    public:
        virtual string response() { return "Ribbet ! "; }
    };
    class Prince : public State
    {
    public:
        virtual string response() { return "Darling ! "; }
    };
    class Dog : public State
    {
    public:
        virtual string response() { return "wang wang ! "; }
    };
private:
    State* pstate;
public:
    Creature() :pstate(new Forg()) {}
    ~Creature() { delete pstate; }
    void greet()
    {
        cout << pstate->response() << endl;
    }
    void kiss()
    {
        delete pstate;
        srand(time(nullptr));
        if (rand() % 2 == 0)
        {
            pstate = new Prince();
        }
        else
        {
            pstate = new Dog();
        }

    }

};
int main()
{
    Creature ca;
    ca.greet();
    ca.kiss();
    ca.greet();
    return 0;
}

职责链模式

职责链模式就是一个请求可以有多个对象进行处理,且处理条件权限不一样。我们举一个例子?有一个小孩,他想吃糖,首先找了他的妈妈,他妈妈没有糖,再一次找了爸爸,爸爸也没有糖,紧接着找了奶奶,奶奶同样的也没有,最后找了爷爷,爷爷最终给了一颗糖。这就很明确的反应了职责链模式。我们同样也可以用代码把这个实例展示出来。

enum Answer { NO, YES };

class GimmeStrategy
{
public:
    virtual Answer canIHave() = 0;
    virtual ~GimmeStrategy() {}
};
class AskMom : public GimmeStrategy
{
public:
    virtual Answer canIHave()
    {
        cout << " Mooom ? Can I have this ? " << endl;
        return NO;
    }
};
class AskDad : public GimmeStrategy
{
public:
    virtual Answer canIHave()
    {
        cout << " Mad? Can I have this ? " << endl;
        return NO;
    }
};

class AskCrandpa : public GimmeStrategy
{
public:
    virtual Answer canIHave()
    {
        cout << " Grandpa ? Can I have this ? " << endl;
        return NO;
    }
};

class AskGrandma : public GimmeStrategy
{
public:
    virtual Answer canIHave()
    {
        cout << " Grandma ? Can I have this ? " << endl;
        return YES;
    }
};

class Gimme : public GimmeStrategy
{
private:
    std::vector<GimmeStrategy*> chian;
public:
    Gimme()
    {
        chian.push_back(new AskMom());
        chian.push_back(new AskDad());
        chian.push_back(new AskCrandpa());
        chian.push_back(new AskGrandma());
    }
    Answer canIHave()
    {
        for (auto p : chian)
        {
            if (p->canIHave() == YES)
            {
                return YES;
            }
            cout << "whiiiiine !" << endl;
        }
        return NO;
    }
    ~Gimme() {
        for (auto &p : chian)
        {
            delete p;
            p = nullptr;
        }
        chian.clear();
    }
};
int main()
{
    Gimme chain;
    chain.canIHave();
    return 0;
}

其流程图如下:在这里插入图片描述

纯虚函数和虚基类

纯虚函数是指没有具体实现的虚成员函数,其实现依赖于不同的派生类。表现形式->virtual 返回类型 函数名 (参数表)=0 "=0"表示程序员将不定义该虚函数实现,没有函数体,只有函数的声明;函数的声明是为了在虚函数表中保留一个位置,本质上是将指向函数体的指针定义为nullptr。
抽象类:含有纯虚函数的类是抽象类。抽象类是一种特殊的类,他是为了抽象的目的而建立的,它处于继承层次结构的较上层。其不能实例化对象,因为纯虚函数没有实现部分。
抽象类作用:将相关的类型组织在一个继承层次结构中,抽象类为派生类提供一个公共的根,相关的派生类型是从这个根派生而来的。
通过以下代码理解以下抽象类和虚基类的概念

class shape {
public:
    virtual ~shape() {}
    virtual float area() = 0;
    virtual void draw()const {
        cout << "shape draw" << endl;
    }
};
class Circle :public shape {
private:
    static const float pi;
    float radius;
public:
    Circle(float r = 0.0) :radius(r) {}
    float area() { return pi * radius * radius; }
    void draw()const {
        cout << "Draw==>Circle" << endl;
    }
};
const float Circle::pi = 3.14f;
class Square :public shape {
    float length;
public:
    Square(float l = 0.0) :length(l) {}
    float area() { return length*length; }
    void draw()const {
        cout << "Draw==>Square" << endl;
    }
};

int main() {
    shape* p = nullptr;
    Circle c1(10);
    Square c2(20);
    p = &c1;
    p->draw();
    cout << p->area() << endl;
    p = &c2;
    p->draw();
    cout << p->area() << endl;
    return 0;
}

使用规则

  • 抽象类只能用作其他类的基类,奴能创建抽象类的对象。
  • 抽象类不能用作参数类型,函数返回类型或显式类型的转换。
  • 可以定义抽象类的指针和引用,此指针可以指向他的派生类对象,从而实现运行时多态。
    注意:抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承了纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类,可以船舰对象的具体类型。

实例

//应用类型 既不提供派生,也不提供继承
class CDTime {};
//节点类型 提供了继承和多态的基础,但没有纯虚函数
class Shape {
	string name;
public:
	virtual float area() { return 0.0f;}
	string getname()const { return name;}
}
//抽象类型 抽象类型只能作为基类来使用,其纯虚函数的实现由派生类给出。
class Shape {
	string name;
public:
	virtual float area()=0;
	string getname()const { return name;}
}
//接口类,只有纯虚函数,没有属性
class Shape {
public:
	virtual float area()=0;
	virtual string getname()=0;
}
//实现类型 继承了接口或抽象类型 定义了纯虚函数的实现
class Shape {
	string name;
	int radio;
public:
	virtual float area() {return radio*radio;}
	string getname()const { return name;}
}

接口继承和实现继承

公有继承可以分为函数接口的继承和函数实现的继承。
类的设计者:

  • 有时希望派生类只继承成员函数的接口(声明),纯虚函数。
  • 有时则希望可以同时继承函数的接口和实现,但允许派生类改写实现,虚函数。
  • 有时希望同时继承接口和实现,并不允许派生类修改任何东西,非虚函数。

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

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

相关文章

嵌入式音视频开发过程中如何控制码率?

一、码率控制的意义&#xff1a; 在音视频领域&#xff0c;码率控制模式有着举足轻重的地位。那什么是码率控制&#xff1f;码率控制是指通过调节图像的压缩比例&#xff0c;从而决定输出编码码率的过程。 二、H264有多少种码率控制模式&#xff1a; H264码率控制模式分别有&am…

DailyMart01:一点小想法,一个新的开始!

大家好呀&#xff0c;我是飘渺&#xff01; 截至目前&#xff0c;我已在公众号和知识星球上发布了多个系列文章&#xff0c;涵盖了SpringBoot老鸟系列、SpringCloud微服务系列、运维监控系列、分库分表系列和Kubernetes云原生系列。尽管每个系列的重点各有不同&#xff0c;它们…

【MYSQL】事务的4大属性,对隔离级别的详细讲解

目录 1.原子性和持久性 1.1.手动提交事务 1.2.自动提交事务 1.3.事务的原理&#xff1a; 2.隔离性 1.读未提交&#xff08;Read Uncommitted&#xff09; 2.读提交&#xff08;Read Committed&#xff09; 3.可重复读 4.串行化 3.一致性 4.理解读提交和可重复读的实现…

iptables

目录 iptables概述 netfilter/iptables 关系&#xff1a; 四表五链 四表&#xff1a; 五链&#xff1a; 数据包到达防火墙时&#xff0c;规则表之间的优先顺序&#xff1a; 规则链之间的匹配顺序&#xff1a; 主机型防火墙&#xff1a; 网络型防火墙&#xff1a; ipta…

Uart,RS232,RS485串口通讯协议学习

目录 定义 UART&#xff08;通常被称为串口,简单意味着使用广泛&#xff0c;具有普适性) RS232 RS232电平转换 RS485 -Recommended Standard (再推荐标准) 485和232的对比 RS485组网 总结 定义 串口是我们都很熟悉的&#xff0c;尤其是需要串口调试的时候,打印信息插…

C语言函数大全-- _w 开头的函数(4)

C语言函数大全 本篇介绍C语言函数大全-- _w 开头的函数 1. _wstrtime 1.1 函数说明 函数声明函数功能wchar_t *_wstrtime(wchar_t *buffer);用于获取当前系统时间并返回一个宽字符字符串表示&#xff0c;格式为 "HH:MM:SS"&#xff08;小时:分钟:秒&#xff09; …

2023 Vue开发者的React入门

Vue 和 React 都是流行的 JavaScript 框架&#xff0c;它们在组件化、数据绑定等方面有很多相似之处 本文默认已有现代前端开发(Vue)背景&#xff0c;关于 组件化、前端路由、状态管理 概念不会过多介绍 0基础建议详细阅读 Thinking in React-官方文档 了解 React 的设计哲学 R…

彻底理解粘性定位 - position: sticky(IT枫斗者)

彻底理解粘性定位 - position: sticky 介绍 粘性定位可以被认为是相对定位(position: relative)和固定定位(position: fixed)的混合。元素在跨越特定阈值前为相对定位&#xff0c;之后为固定定位。例如: .sticky-header { position: sticky; top: 10px; }在 视口滚动到元素…

【JavaWeb】--05.Request和Response、JSP、会话技术

文章目录 Request和Response1.概述2.Request对象2.1 Request继承体系2.2Request获取请求数据2.3 IDEA创建Servlet2.4 请求参数中文乱码问题POST请求解决方案GET请求解决方案 2.5 Request请求转发 3.Response对象3.1 Response设置响应数据功能介绍3.2 Response请求重定向3.3 路径…

【全网首测】5G随身Wi-Fi —— 中兴U50 Pro

说到随身Wi-Fi&#xff0c;大家应该都不陌生。 它是一个专门将移动信号转换成Wi-Fi信号的设备&#xff0c;经常被用于旅行和出差场景&#xff0c;也被人们亲切地称为“上网宝”。 现在&#xff0c;我们已经全面进入了5G时代&#xff0c;随身Wi-Fi也升级迭代&#xff0c;出现了支…

人工智能的界面革命,消费者与企业互动的方式即将发生变化。

本文来源于 digitalnative.substack.com/p/ais-interface-revolution 描述了一种社会现象&#xff1a; 随着真实友谊的减少和虚拟友谊的增加&#xff0c;越来越多的人开始将AI聊天机器人视为自己的朋友&#xff0c;甚至建立了深厚的情感纽带。这可能与当前人们越来越孤独的现实…

面向“伙伴+华为”体系,华为产品力的变与不变

在日前举办的“华为中国合作伙伴大会2023”上&#xff0c;华为面向政企市场提出了建设“伙伴华为”体系的发展方向。可想而知&#xff0c;接下来会有更多伙伴加入这一体系&#xff0c;也会有更多客户可以借由这个体系加速完成自身的数字化转型和智能化升级。而产品与技术&#…

luaplus Windows编译(一)

前言 LuaPlus是Lua的C增强&#xff0c;也就是说&#xff0c;LuaPlus本身就是在Lua的源码上进行增强得来的。用它与C进行合作&#xff0c;是比较好的一个选择。 1:准备 luaplus_all 下载地址&#xff1a;https://github.com/jjensen/luaplus51-all jamplus 下载地址 https://gi…

基于神经网络算法的鱼类迁徙轨迹拟合研究

本试验采用HTI Model 291便携型声学标签接收系统,包括的基本部件有:291便携型声学标签接收器1台,590型水听器4根,最新795型声学标签40枚,490-LP 型标签编程器1台,690系列电缆400m,492微型声学标签探测器1台,115VAC型滤波器1台,TagProgrammer 、MarkTags和AcousticTag专…

30个数据科学工作中最常用的 Python 包

Python 可以说是最容易入门的编程语言&#xff0c;在numpy&#xff0c;scipy等基础包的帮助下&#xff0c;对于数据的处理和机器学习来说Python可以说是目前最好的语言。 在各位大佬和热心贡献者的帮助下Python拥有一个庞大的社区支持技术发展&#xff0c;开发两个各种 Python…

Oracle中数据导出成HTML的操作实践

spool是Oracle中将数据到成文件常用的一种工具&#xff0c;但它的强大&#xff0c;不仅仅是数据的导出&#xff0c;在格式和内容上&#xff0c;还可以自定义&#xff0c;甚至生成像AWR一样的统计报告。 参考《SQL*Plus Users Guide and Reference》中第7章"Generating HTM…

光纤仿真相关求解——光纤芯层和包层电磁场分布求解

要求解光纤中的电磁场分布&#xff0c;就要构建合适的物理模型 将光纤假设为圆柱状的波导&#xff0c;求解满足均匀原型介质波导边界条件的麦克斯韦方程组&#xff0c;即可 z分量的亥姆霍兹方程为&#xff1a; 对应在圆柱坐标系下为&#xff1a; 用分离变量法求解Ez&#xff…

如果你不想工作了,先做这3件事

作者| Mr.K 编辑| Emma 来源| 技术领导力(ID&#xff1a;jishulingdaoli) 英国作家毛姆有句名言&#xff1a;“我从来不会厌倦生活&#xff0c;只是厌倦了那些毫无生气的生活方式。”把这句话稍微修改一下&#xff0c;放在职场也无比适用“我并不厌倦工作,只是厌倦了那些毫无…

第10章_创建和管理表

第10章_创建和管理表 1. 基础知识 1.1 一条数据存储的过程 存储数据是处理数据的第一步。只有正确地把数据存储起来&#xff0c;我们才能进行有效的处理和分析。否则&#xff0c;只能是一团乱麻&#xff0c;无从下手。 那么&#xff0c;怎样才能把用户各种经营相关的、纷繁复…

从零玩转设计模式之建造者模式-jianzaozhemoshi

title: 从零玩转设计模式之建造者模式 date: 2022-12-08 18:15:30.898 updated: 2022-12-23 15:35:58.428 url: https://www.yby6.com/archives/jianzaozhemoshi categories: - 设计模式 tags: - 设计模式 - 建造者模式 什么是建造者模式? 建造者模式是一种软件设计模式&…