c++入门学习⑦——继承和多态(超级详细版)

目录

前言

继承

继承是什么?

为什么会存在继承?

语法:

一些基本的定义:

三种继承方式:

对象模型

对于构造和析构的顺序

同名函数的处理方式

总结:

静态成员:

定义:

性质:

共享数据 

编译阶段分配内存

类内声明类外初始化

静态成员函数

静态成员函数与普通成员函数的区别:

静态成员访问:

多继承

菱形继承

菱形继承会遇到的问题:

如何解决?

原理:

多态

多态分类:

静态多态和动态多态区分:

静态多态:(静态绑定)

动态多态:(动态绑定)

动态多态实现:

引入:

那什么是虚函数?

虚函数的重写?

多态实现代码示例:

虚函数的动态绑定机制(面试经常问的)

 重载、重定义、重写的区别

纯虚函数

抽象类:

那什么是接口继承呢?

那正常的继承叫做什么?

多态原理(工具观察):

虚析构

语法:

原理:通过父类指针来释放子类空间

纯虚析构

特点:

语法:

纯虚析构和虚析构的共性与区别:

虚析构和纯虚析构共性:

虚析构和纯虚析构区别:

总结:

总结:


前言

这篇博客介绍相关c++继承以及多态的内容,在c++中属于很重要的一个部分,请认真学习(ง •_•)ง

继承

继承是什么?

简单从字面意思上来说就是继承上一个事物的基本特点

准确定义是:继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法 , 还可以在子类中重新定义 ,以及追加属性和方法。

为什么会存在继承?

假如要构造两个类——斑点猫和白猫,其中包含的行为有类对象的行为习惯等,而这两个同属于猫类,他们有很多相似的地方,如果没有继承,则需要重复性写这一相似的内容,一个两个还好,如果多个类都有相似之处,继承就是比较好的方式了。

语法:

class 子类名:继承方式(public/private/protected)  父类名

一些基本的定义:

基类:被继承的类,又称为“父类”

派生类:继承其他类的类,又称为“子类”

三种继承方式:

公共继承public:继承父类的公共权限,则相应的私有权限和保护权限也继承过去,也是相应的私有和保护权限

保护继承protected:继承父类的保护权限,则父类的公共权限到子类中变为保护权限,而私有权限仍然是私有权限

私有继承private:继承父类的私有权限,父类中所有权限到子类中都是私有权限。

对象模型

这里创造两个类,一个基类,一个派生类,B类继承A类的公共权限

class A

class B

#include<iostream>
using namespace std
class A
{
public:
    int a;
};
class B:public A
{
public:    
    int b;
};

则它们在内存中的分布是:

也就是说,派生类会继承一份基类的成员,然后在旁边创建自己的成员

对于构造和析构的顺序

当创建一个子类对象后,如果要初始化它,则哪一个类先构造,哪一个类后构造,程序结束时,谁先析构?

谁的构造函数先调用,谁的析构函数先调用

代码示例:

#include<iostream>
using namespace std;
 
class A
{
public:
    A()
    {
        cout<<"A的构造函数"<<endl;
    }
    ~A()
    {
        cout<<"A的析构函数"<<endl;
    }
    int m_a;
};
class B:public A
{
public:
    B()
    {
        cout<<"B的构造函数"<<endl;
    }
    ~B()
    {
        cout<<"B的析构函数"<<endl;
    }
    int m_b;
};
test1(){
	B b;
}
int main()
{
    test1();
    system("pause"); 
    return 0;
}

 输出结果为:

由此可见刚才问题的答案是:父类先构造然后子类构造,子类析构再父类析构

这个可以用鸡生蛋来记——鸡比它生的蛋要早出生,先有这只鸡才有这个蛋,然后吃的时候,先吃蛋,再吃鸡

同名函数的处理方式

对于基类和派生类的同名函数如何处理呢?换句话说当调用这个同名函数时,真正会起作用的是哪一个函数呢?

我们来通过代码看一看:

#include<iostream>
using namespace std;
 
class A
{
public:
    void speak()
	{
    	cout<<"A会说话了"<<endl;
	}
    int m_a;
};
class B:public A
{
public:
    void speak()
	{
    	cout<<"B会说话了"<<endl;
	}
    int m_b;
};
test1(){
	B b;
	b.speak();
}
int main()
{
    test1();
    system("pause"); 
    return 0;
}

通过代码发现派生类调用同名函数,则调用派生类的同名函数

得出结论:

如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数

那么如何通过派生类调用基类的同名函数呢?

通过添加作用域      类名::

总结:

  • 当子类与父类拥有同名的成员函数,子类会隐意父类中同名成员函教,加作用域可以访问到父类中同名函数
  • 子类对象可以直接访问到子类中同名成员
  • 子类对象加作用域可以访问到父类同名成员

作用域是👉类名::

静态成员:

定义:

简单来说:普通的成员前加上一个static关键字,就被称为静态成员。

当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

性质:

共享数据 

静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。

编译阶段分配内存

在编译阶段就已经分配好内存了

类内声明类外初始化

静态成员不可以在类内初始化,要在类外初始化,初始化时在成员名加上作用域即可

静态成员函数

在成员函数前加上关键字static,这样就把类的特定对象和该函数独立开

静态函数只要使用类名加范围解析运算符 :: 就可以访问

静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数

静态成员函数与普通成员函数的区别:
  • 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)
  • 普通成员函数有 this 指针,可以访问类中的任意成员

静态成员访问:

  1. 通过类名访问:类名::父类作用域::成员
  2. 通过对象访问
  3. 同名静态函数或者是变量,父类的所有同名函数包括函数重载会被隐藏,除非加上作用域才可以成功

多继承

子类是否只可以继承一个类,可以进行多个类吗?如果可以继承多个类,多继承的语法是什么?

c++允许子类可以继承多个类

语法为:class 子类  :继承权限 父类1 ,继承权限 父类2,继承权限 父类3……

但是注意:在实际开发过程中,不建议使用多继承语法

菱形继承

什么是菱形继承?

如下图:

这样一种几个类相互有一定的继承关系的看起来像菱形一样的,被称为菱形继承

定义:两个派生类继承同一个基类,又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者钻石继承

菱形继承会遇到的问题:

由于类4同时继承类2和类3,而类2和类3都继承了1,那么相当于类4继承了两份类1.

如何解决?

使用虚继承的方式,用关键字virtual,可以使派生类不重复继承

在派生类继承的时候在继承权限前加上这个关键字virtual即可

示例:

原理:

虚继承会产生虚基类指针vbptr

该指针指向虚基表,虚基表记录的是通过指针访问公共祖先的数据的偏移量

多态

多态是c++面向对象三大特征之一

多态按字面的意思就是多种形态,具体解释是指:不同对象去完成某一个行为是而产生的不同状态

当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态

多态性提供接口与具体实现之间的隔离,将what和how这两个板块分离开

多态好处:

  1. 组织结构清晰
  2. 可读性强
  3. 对于前期和后期扩展以及维护性高

多态分类:

分为静态多态动态多态(其实静态多态之前就已经涉及了)

静态多态和动态多态区分:

静态多态的函数地址早绑定-编译阶段确定函数地址
动态多态的函数地址晚绑定-运行阶段确定函数地址

静态多态:(静态绑定)

静态多态:函数重载,重定义,运算符重载属于静态多态

静态多态的函数地址早绑定-编译阶段确定函数地址

动态多态:(动态绑定)

动态多态:派生类和虚函数实现运行时的多态

动态多态的函数地址晚绑定-运行阶段确定函数地址

动态多态满足条件:

1.有继承关系

2.子类重写父类虚函数,函数返回值类型,名称,参数列表完全相同

3.使用->父类指针或者引用执行于类对象

下面仅介绍重点——动态多态

动态多态实现:

如何实现动态多态呢?有什么作用?使用场景又是什么?

多态是当不同继承关系的类对象去调用一个函数而产生的不同的行为

实现动态多态的条件

  • 类对象要调用虚函数,且派生类中必须要包含基类虚函数的重写
  • 通过基类的引用/指针去调用虚函数⭐ 

引入:

继承会使子类都含有父类的数据,而每一个子类都对这份数据进行重写,而如果想创建一个函数,使其可以操纵父类所有派生的子类?(即使以后还有派生的子类,也可以操纵)

该怎么做呢?

其实最重要的是函数的参数

参数设定其实需要找到这几个派生类的共性——》它们都有父类的数据

因此如果要实现这样的一个函数,那么就需要其参数为父类的指针或是引用,而要操纵所有子类,这就需要用父类指针来保存子类的空间地址⭐⭐⭐

而用父类指针保存子类地址会造成问题:

代码示例:

#include<iostream>
using namespace std;
class A
{
public:
    void say()
    {
        cout << "A会说话了" << endl;
    }
};

class B : public A
{
public:
    void say()
    {
        cout << "A类里的B会说话了" << endl;
    }
};
class C :public A
{
public:
    void say()
    {
        cout << "A类里的C会说话了" << endl;
    }
};
void test1(A*a)
{
    a->say();//输出的都是 A会说话了
}
int main()
{
    A* a=new B;
    test1(a);
    A* b = new C;
    test1(b);
    return 0;
}

我们发现本身要通过父类的指针来完成子类的重定义函数,但是实际上调用的都是父类的函数,为什么呢?

因为指针指向的地址是由指针指向类型决定的,A*a,这个式子就决定了它要指向父类,而子类中继承了父类的数据,因此a指向子类中的父类,实现的也是父类的函数

如何解决这一个问题呢? 

用虚函数

那什么是虚函数?

被关键字virtual修饰的类成员函数,而且子类中要重写虚函数(可加virtual也可不加)

虚函数的重写?

虚函数的重写(又可以叫做覆盖)👉

派生类中有一个跟基类完全相同的虚函数(这里指它们的返回值类型、函数名字、参数列表完全相同),则称子类的虚函数重写了基类的虚函数,而其中的重写内容可以做适当改变,来实现多态

多态实现代码示例:

#include<iostream>
using namespace std;
class A
{
public:
    virtual void say()
    {
        cout << "A会说话了" << endl;
    }
};

class B : public A
{
public:
    void say()
    {
        cout << "A类里的B会说话了" << endl;
    }
};

void test1(A*a)
{
    a->say();//输出的是A类里的B会说话了
}
int main()
{
    A* a=new B;
    test1(a);
    return 0;
}

这样父类指针就可以调用子类的函数了,解决了父类指针指向子类地址的一个问题 

那为什么变成虚函数后,父类指针可以调用子类中的子类函数而非父类呢?

虚函数的动态绑定机制(面试经常问的)

当一个类中的函数变为虚函数之后,会产生虚函数指针(vfptr),虚函数指向虚函数表(vftable),而如果这个类没有被继承的话——》虚函数表保存的是这个虚函数的入口地址

如果被继承了,子类会把父类中的虚函数指针给继承过来,但是这时候这个虚函数表里的内容就发生变化了,它里面的是子类重写的地址

如图本质上A*a指针还是指向父类地址,但是由于去调用时发现它是一个虚函数指针,而这个指针指向的虚函数表是重写的say函数地址,因此调用的是子类函数。

 重载、重定义、重写的区别

重载:同一作用城,同名函教,参数的顺序,个数,类型不同都可以重载。函数的返回值类型不能作为重载条件(函数重载,运算行重载)
重定义:有继承,子类重定义父亲的同名函数(非虚函数),参数顺序,个数,类型可以不同,子类的同名函数会屏蔽父类的所有同名函数(可以通过作用域解决)
重与(覆盖):有继承,子类重写父类的虚函数。返回值类型,函数名,参数顺序,个数,类型都必须一致。 

而当这样写后,代码只进行子类函数调用,此时的父类虚函数就没有什么用了,那么我们可以不可以找到一种方式去省略掉父类虚函数的内容——可以,用纯虚函数,下面就来介绍什么是纯虚函数?

纯虚函数

在多态中,通常父类中虚函数的实现是毫无意义的主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数

纯虚函数是指在虚函数上加上=0则称为虚函数

例如:

virtual void say ()=0;

而当一个类中由纯虚函数后这个类就成了——抽象类👇 

抽象类:

抽象类是指类中含有纯虚函数的类,抽象类又称为接口类,抽象类不能实例化对象(因为它没有函数体,怎么调用?)

抽象类特点:

无法实例化对象

子类必须重写抽象类中的所有纯虚函数,否则也属于抽象类⭐

其实抽象类主要目的是设计类的接口

也就是说只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,其实这里的纯虚函数存在接口继承

那什么是接口继承呢?

简单来说虚函数的继承——接口继承,派生类并没有继承基类函数,而是基类虚函数的接口,这是一个实现多态的方式,可以达到重写的目的,如果不要求多态实现,函数尽量不要定义为虚函数

那正常的继承叫做什么?

普通函数的继承为——实现继承,派生类它继承了基类函数,继承的是函数的实现,而非接口,派生类可以使用这个接口

多态原理(工具观察):

前面已经将结果多态如何通过虚函数实现的了,现在来使用工具观察多态实现的虚函数表

⭐满足多态以后的函数调用,并不是在编译时确定的,而是运行起来以后到对象中去找的

不满足多态的函数调用是在编译时确认好的。

这里借助vs下的一个工具

输入dir空格后出现目录,然后直接输入以下内容,即可看代码中的类

  • cl(空格)/d1(空格)reportSingleClassLayout(类名)(空格)(文件名)(回车)     

例如在test.cpp文件中,A类的结构:

  • cl /d1 reportSingleClassLayoutA test.cpp

最后就可以看见这个类的内部情况。

⭐回顾:我们要达到多态,有两个条件,一个是虚函数覆盖,一个是基类对象的指针或引用调用虚函数

哎?有人发现没,其实刚才多态实现里的代码有个问题——new出来的空间一直没有释放,堆区空间没释放会发生内存泄漏,其实这里就涉及到另一个知识了——虚析构

这时可能会问,为什么非要用虚析构呢?正常的析构为什么不可以释放掉代码呢?这时假如你写一个代码,就会发现——父类的析构函数正常调用,而子类的析构函数无法调用,也就是我们没办法在子类析构函数中去写代码,释放掉子类的堆区数据。

做法就是在父类的析构前加上一个很熟悉的关键字——virtual

这也就是下面要介绍的虚析构

虚析构

多态使用时,若子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,父类在析构的时候不会调用子类析构,如果子类有堆区属性,则会出现内存泄漏
则需要将父类中的析构函数改为虚析构

语法:

虚析构语法:
virtua1 ~类名()

原理:通过父类指针来释放子类空间

由于刚开始实现多态的时候,子类的空间是由父类指针储存的,因此只能通过父类指针来释放子类空间。

已知的是

构造是:父类——》成员——》子类

析构是:子类——》成员——父类

析构函数本身也是个成员函数,虚析构,产生虚函数指针,指向虚函数表:包含这个类的析构函数 

纯虚析构

特点:

纯虚析构的本质:是析构函数,完成各个类的回收工作。
必须为纯虚析构函数提供一个函数体
而且纯虚析构函数必须在类外实现

含有纯虚析构的类也是抽象类

语法:

virtual ~类名()=0;
在类外:类名::~类名() 

纯虚析构和虚析构的共性与区别:

虚析构和纯虚析构共性:

可以解决父类指针释放子类对象
都需要有具体的函数实现


虚析构和纯虚析构区别:

如果是纯虚析构,该类属于抽象类,且无法实例化对象,需要在类外实现

总结:

  • 1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  • 2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  • 3.拥有纯虚析构函数的类也属于抽象类

总结:

c++的入门核心内容基本介绍完毕,整理的有关c++的文件操作以及内存分区放在下面了,可以按需观看

c++文件操作-CSDN博客

c++内存的四大分区详解-CSDN博客

下一阶段是对于c++的模板,欢迎点赞收藏关注主页专栏o(* ̄▽ ̄*)ブ

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

雨云GPU云服务器搭建SD(Stable Diffusion)的教程,搭建自己的AI绘画网站,AIGC

雨云GPU云服务器搭建Stable Diffusion的教程&#xff0c;搭建自己的AI图片生成网站&#xff0c;AIGC Stable Diffusion是什么 Stable Diffusion是一种基于潜在扩散模型&#xff08;Latent Diffusion Models&#xff09;的文本到图像生成模型&#xff0c;由CompVis、Stability…

gma 2.0.6 (2024.02.21) 更新日志

安装 gma 2.0.6 pip install gma2.0.6网盘下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1P0nmZUPMJaPEmYgixoL2QQ?pwd1pc8 提取码&#xff1a;1pc8 注意&#xff1a;此版本没有Linux版&#xff01; 编译gma的Linux虚拟机没有时间修复&#xff0c;本期Linux版继…

普中51单片机学习(AD转换)

AD转换 分辨率 ADC的分辨率是指使输出数字量变化一个相邻数码所需输入模拟电压的变化量。常用二进制的位数表示。例如12位ADC的分辨率就是12位&#xff0c;或者说分辨率为满刻度的1/(2^12)。 一个10V满刻度的12位ADC能分辨输入电压变化最小值是10V1/(2^12 )2.4mV。 量化误差 …

创建者模式(Builder Pattern):构造复杂对象的通用解决方案

文章目录 **一、技术背景与应用场景****为何使用创建者模式&#xff1f;****典型应用场景包括但不限于&#xff1a;** **二、创建者模式定义与结构****三、使用步骤举例**四、优缺点分析总结 一、技术背景与应用场景 创建者模式是一种对象创建型设计模式&#xff0c;它通过将复…

国家建筑装配式内装产业基地在沪成立,副主任单位优积科技协同助推绿色低碳循环发展

上海市室内装饰行业协会装配式内装产业专业委员会成立大会暨“国家建筑装配式内装产业基地”项目启动会于3月21日下午1点在上海光大酒店隆重举行。出席此次活动的包括市装协会长徐国俭&#xff0c;市装协党支部书记兼秘书长丛国梁&#xff0c;市装协装配式内装委主任顾泰昌&…

数字化转型导师坚鹏:数据安全法解读与政府数字化转型

网络安全法、数据安全法、个人信息保护法解读与政府数字化转型 课程背景&#xff1a; 很多机构存在以下问题&#xff1a; 不清楚网络安全法、数据安全法、个人信息保护法立法背景&#xff1f; 不知道如何理解网络安全法、数据安全法、个人信息保护法政策&#xff1f; 不…

2024年 Openai的API相关全部概论汇总(通用版)

2024年 Openai的API相关全部概论汇总&#xff08;通用版&#xff09; 文章目录 2024年 Openai的API相关全部概论汇总&#xff08;通用版&#xff09;一、前言1、python快速开始 二、Openai 平台以及相关项目1、Openai的API管理平台2、ChatGPT项目推荐&#xff08;1&#xff09;…

每日学习总结20240222

每日总结 一旦停下来太久&#xff0c;就很难继续了 ——《一个人的朝圣》 20240222 1. 自定义逻辑 请设计一个函数single_track_logic,传入三个参数&#xff0c;第一个参数是int数组&#xff0c;第二个参数是一个int变量&#xff0c;第三个参数是一个以int为返回值&#xff0c…

每日五道java面试题之spring篇(四)

目录&#xff1a; 第一题 Spring框架的设计目标&#xff0c;设计理念&#xff0c;和核心是什么&#xff1f;第二题. Spring由哪些模块组成&#xff1f;第三题. 详细讲解一下核心容器&#xff08;spring context应用上下文) 模块第四题.Spring框架中有哪些不同类型的事件第五题.…

【C++】类与对象—— 初始化列表 、static 静态成员、

类与对象 1 再谈构造函数1.1 构造函数体赋值1.2 初始化列表语法&#xff1a;建议&#xff1a;初始化顺序&#xff1a;注意&#xff1a; 1.3 explicit关键字 2 static 静态成员2.1 概念2.2 声明成员变量2.3 使用类的静态成员2.4 定义静态成员总结 Thanks♪(&#xff65;ω&#…

Linux:gcc的基本知识

gcc 是一个将C语言文件变成可执行文件的工具。 在Linux中&#xff0c;如果需要将一个C语言文件变得可以执行&#xff0c;那么除了这个文件本身的内容是C语言编写的内容外&#xff0c;还需要gcc这个编译工具进行编译才行。 gcc 使用的格式方法:gcc 要编译的文件 //在该代码下…

【数据结构与算法初学者指南】【冲击蓝桥篇】String与StringBuilder的区别和用法

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《数据结构与算法&#xff1a;初学者入门指南》&#x1f4d8;&am…

Llama2模型的优化版本:Llama-2-Onnx

Llama2模型的优化版本&#xff1a;Llama-2-Onnx。 Llama-2-Onnx是Llama2模型的优化版本。Llama2模型由一堆解码器层组成。每个解码器层&#xff08;或变换器块&#xff09;由一个自注意层和一个前馈多层感知器构成。与经典的变换器相比&#xff0c;Llama模型在前馈层中使用了不…

Redis进阶篇

Redis线程模型 redis是基于内存运行的高性能k-v数据库&#xff0c;6.x之前是单线程, 对外提供的键值存储服务的主要流程 是单线程&#xff0c;也就是网络 IO 和数据读写是由单个线程来完成&#xff0c;6.x之后引入多线程而键值对读写命 令仍然是单线程处理的&#xff0c;所以 …

[SpringDataMongodb开发游戏服务器实战]

背景&#xff1a; xdb其实足够完美了&#xff0c;现在回想一下&#xff0c;觉得有点复杂&#xff0c;我们不应该绑定语言&#xff0c;最好有自己的架构思路。 七号堡垒作为成功的商业项目&#xff0c;告诉我&#xff1a;其实数据是多读少写的&#xff0c;有修改的时候直接改库也…

离散数学 第八单元 布尔代数

目录 1. 布尔函数 2. duality 二元性 3. 表示布尔函数的布尔表达式 sum-of-products expansions 4. Functional Completeness 5. Logic Gates 逻辑门​​​​​​​ 4. 最小化 K-map卡诺图 Quine-McCluskey法 1. 布尔函数 嗯也就是我要知道布尔代数是啥形式&#xff…

[面试] 什么是死锁? 如何解决死锁?

什么是死锁 死锁&#xff0c;简单来说就是两个或者多个的线程在执行的过程中&#xff0c;争夺同一个共享资源造成的相互等待的现象。如果没有外部干预线程会一直阻塞下去. 导致死锁的原因 互斥条件&#xff0c;共享资源 X 和 Y 只能被一个线程占用; 请求和保持条件&#xf…

VMware虚拟机从一台电脑复制到另一台电脑

1 概述 在一台电脑上利用虚拟机安装了OS系统&#xff0c;特别是如果虚拟机中的系统进行了各种繁琐的配置&#xff0c;因为换电脑或者需要在其他电脑上配置&#xff0c;这个时候就可以将虚拟机中的系统复制拷贝一份到新电脑上&#xff0c;省时省力。 2 操作步骤 2.1 vmx文件 …

数字化转型导师坚鹏:政府数字化运营三步曲之认知、行动、结果

政府数字化运营三步曲之认知、行动、结果 课程背景&#xff1a; 很多政府都在开展数字化运营工作&#xff0c;目前存在以下问题急需解决&#xff1a; 不清楚政府数字化运营包括哪些关键工作&#xff1f; 不清楚政府数字化运营工作的核心方法论&#xff1f; 不清楚政府数…

单词倒排——c语言解法

以下是题目&#xff1a; 这个题中有三个点&#xff0c; 一个是将非字母的字符转换为空格&#xff0c; 第二是如果有两个连续的空格&#xff0c; 那么就可以将这两个连续的空格变成一个空格。 第三个点就是让单词倒排。 那么我们就可以将这三个点分别封装成三个函数。 还有就是…
最新文章