C++:多态

C++:多态

    • 虚函数
      • 虚函数语法
      • 虚函数重写
        • 协变
        • 接口继承
    • 多态构成
    • 成员函数状态对比
    • 抽象类
    • 多态原理
    • 多继承与多态
    • 虚继承与多态


先看到多态的定义:

C++的多态是指在面向对象程序设计中,允许使用基类的指针或引用来调用派生类的虚函数的特性。这样的调用将根据对象的实际类型来动态绑定到适当的函数实现,实现了不同对象调用相同函数的不同行为

上面的句子确实有点晦涩,但是重点就是最后一句话:不同对象调用相同函数时,函数展现不同的行为。

C++的多态是基于虚函数的,所以在讲解多态前,我们要先了解什么是虚函数。


虚函数

通过使用虚函数,可以实现在程序运行时根据对象的实际类型来确定调用的函数。

看到以下案例:

class person
{
public:
	void func()
	{
		cout << "func被person调用" << endl;
	}
};

class student : public person
{
public:
	void func()
	{
		cout << "func被student调用" << endl;
	}
};

这段代码中,存在着personstudent的父子关系,两者都存在一个func函数,由于函数同名,此时person的函数被隐藏。

接下来我们用不同的引用来调用这个函数:

student s;

person& rp = s;
student& rs = s;

rp.func();
rs.func();

输出结果:

func被person调用
func被student调用

student& rs = s;中,我们把student类的对象s交给了一个student的引用rp维护,此时再使用rs调用func函数,那么这个student类就会调用自己的函数,输出“func被student调用”


person& rp = s;中,我们把student类的对象s交给了一个person的引用rp维护,此时再使用rp调用func函数,那么这个student类就会被当作一个person类来处理,输出“func被person调用”

但是在实际应用应用中,这是一个不合理的行为。我们把一个把student类的对象s交给了一个person的引用,此时这个student类的对象就可以访问person的函数了。

类比:这个行为就好像一个小偷偷走了别人的银行卡,这笔钱就属于这个小偷了。银行在取钱时,不应该通过这个身份证来确定允不允许这个人取钱,而是通过对方是不是这笔钱真正的主人。

以上类比中,身份证就是 引用/指针,而拿着身份证去取别人钱的行为,就是利用别人类型的 指针/引用 去调用别人函数。

既然多态要根据对象是谁,从而展现同一个函数的不同形态,那么当然要先解决 “确认对象是谁” 这个问题,不能让一个对象拿着其他类型的 指针/引用,导致调用了错误的函数。为此C++推出了虚函数,虚函数可以识别到这个对象的真实类型,调用正确的函数。

虚函数是构成多态的必要条件之一,现在我们来讲解虚函数的语法,帮助大家了解如何构成虚函数。

虚函数语法

在类中被virtua关键字修饰的函数,就是虚函数

class person
{
public:
	virtual void func()
	{
		cout << "func被person调用" << endl;
	}
};

以上代码中,func函数就是一个虚函数了。

虚函数重写

当派生类中有一个与基类完全相同的虚函数,则会发生虚函数的重写

完全相同包括:

  • 函数名相同
  • 参数列表相同
  • 返回值相同

示例:

class person
{
public:
	virtual void func()
	{
		cout << "func被person调用" << endl;
	}
};

class student : public person
{
public:
	virtual void func()
	{
		cout << "func被student调用" << endl;
	}
};

上述代码中,personstudentfunc函数构成了重写:

  • 函数名都是func
  • 返回值都是void
  • 参数都是没有参数
  • personfuncvirtual修饰,是一个虚函数
  • studentfuncvirtual修饰,是一个虚函数

即函数完全相同 + 两个函数都是虚函数,此时就构成了虚函数的重写。
虚函数重写后,此时再调用func函数,就已经可以通过对象到底是谁来调用对应的函数了。

同样的代码再执行一次:

student s;

person& rp = s;
student& rs = s;

rp.func();
rs.func();

输出结果:

func被student调用
func被student调用

此时哪怕我们把student的对象交给person的引用来维护,虚函数func依然会根据对象本身来调用函数,两次都输出func被student调用

但是虚函数的重写有两个特例:

协变

协变是指子类可以将父类中的返回类型进行适当的改变,以适应不同的使用场景。

在讲解协变之前,先来看一个简单的例子。

class person{
public:
    virtual void eat() {
        cout << "person is eating" << endl;
    }
};

class student : public person{
public:
    void eat() override {
        cout << "student is eating" << endl;
    }
};

在上述代码中,我们定义了一个基类 person 和派生类 student 。基类中有一个虚函数 eat(),派生类中对这个函数进行了重写。

现在,我们可以创建一个 person 的指针,指向一个 student 类型的对象,并调用 eat() 函数:

person* p = new student();
p->eat();  // 输出:person is eating

这是因为虚函数允许基类指针指向派生类对象,调用虚函数时会根据指针所指向的对象类型来动态调用合适的函数。

接下来,我们来讨论协变。假设我们在 person 类中添加一个新的虚函数 sleep()

class person{
public:
    virtual void eat() {
        cout << "person is eating" << endl;
    }
    
    virtual person* sleep() {
        cout << "person is sleeping" << endl;
        return this;
    }
};

现在,我们在 student 类中对 sleep() 函数进行重写:

class student: public person{
public:
    void eat() override {
        cout << "student is eating" << endl;
    }
    
    student* sleep() override {
        cout << "student is sleeping" << endl;
        return this;
    }
};

注意到在 student 类中对 sleep() 的返回类型进行了改变,从 person* 修改为 student*。这就是协变的体现,子类可以将父类中的返回类型进行适当的改变。

然后,我们可以创建一个 person 的指针,指向一个 student 类型的对象,并调用 sleep() 函数:

person* p = new student();
student* c = p->sleep();  // 输出:student is sleeping

尽管 sleep() 函数的返回类型在基类中是 person*,在派生类中是 student*,但由于协变的特性,我们仍然可以将 person* 类型的指针赋值给 student* 类型的指针变量,而不会发生编译错误。

那么如果可以随意改变基类与派生类之间的返回值,那不就违背了一开始的返回值必须相同吗?
其实协变是有条件的:

在进行协变时,子类中的返回类型必须要是父类中返回类型的派生类。

也就是说,如果我们想要返回值不同,也是有要求的,返回值之间必须构成父子关系,才能进行协变。

接口继承

在C++中,接口继承是指一个类继承另一个类的接口部分,即只继承虚函数而不继承函数体部分。这样做的目的是为了在派生类中重写虚函数,以实现特定的功能。

我们看到以下代码:

class person
{
public:
	virtual void func(int a = 5)
	{
		cout << "被person调用 a = " << a << endl;
	}
};

class student : public person
{
public:
	virtual void func(int a = 10)
	{
		cout << "被student调用 a = " << a << endl;
	}
};

以上代码中,两个虚函数func构成重写,但是person中的func,参数a的默认值为5;student中的func,参数a的默认值为10。这不影响的参数列表相同,参数列表是指参数的类型要相同,与默认值无关

执行以下代码:

student s;

person& rp = s;
student& rs = s;

rp.func();
rs.func();

输出结果:

被student调用 a = 5
被student调用 a = 10

奇怪的事情发生了:我们确实使用student对象调用了函数func,所以两次调用都显示了被student调用,说明调用了student中的函数。但是为什么通过person&调用的函数,a的值是5?

这就涉及到了接口继承。

两个函数构成虚函数时,并且通过 基类的引用/指针 调用函数,此时根据多态,会调用到派生类对应的函数,同时会发生接口继承。

如下:
在这里插入图片描述
上面的virtual void func(int a = 5)会被继承给派生类,把下面的virtual void func(int a = 10)替换掉,所以最后虽然我们最后通过多态调用到了正确的函数,但是由于接口继承,我们的接口依然是基类的,所以a = 5

但是如果我们直接通过,student&来调用student的函数,此时就是自己调用自己的函数,没有发生多态,所以没有发生接口继承,最后a = 10

所以发生接口继承的要求就是:

  1. 两个虚函数构成重写
  2. 通过基类的 指针 / 引用 来调用

其实这也是多态的要求,我们等下会详细讲解。

也因为这个接口继承规则,我们派生类中的virtual关键字可以省略!

class person
{
public:
	virtual void func()
	{
		cout << "被person调用" << endl;
	}
};

class student : public person
{
public:
	void func()
	{
		cout << "被student调用" << endl;
	}
};

上述代码中,两个函数func构成重写,此时基类的接口virtual void func()会继承给派生类,导致void func()被替换为virtual void func(),最后变成虚函数。

此处共讲解了两个知识点:

  1. 接口继承
  2. 派生类中的virtual关键字可以省略(虚函数重写特例)

两者之间是因果关系。

最后进行一次虚函数重写语法总结:

基本语法:
当派生类中有一个与基类完全相同的虚函数,则会发生虚函数的重写

  • 函数名相同
  • 参数列表相同
  • 返回值相同

特例:
1.当返回值在构成协变的情况下,可以不同(返回值是父子关系)
2. 派生类的virtual可以不写(因为继承了基类接口的virtual)


多态构成

讲完了虚函数,其实多态就已经讲完了一大半了,想要构成多态,条件是:

  1. 必须通过基类的指针或者引用调用虚函数
  2. 基类必须存在相应的虚函数,子类必须对虚函数重写

多态的结果:

多态会根据 指针/引用 指向的对象的类型来调用对应的函数,而不是根据 指针/引用 本身的类型

讲完如何构成多态,接下来我们对比一下C++的类中,成员函数有哪些特殊形态:


成员函数状态对比

在C++的类中,我们的成员函数之间可以构成:重载,重写,重定义(隐藏)。接下来我们对比三者:
函数重载:

功能:当函数传入不同类型的参数时,执行不同的效果
要求:

  1. 重载的函数要在同一个作用域
  2. 函数名相同
  3. 参数列表不同

函数重写:

功能:派生类的虚函数将基类的虚函数重写,以达成多态
要求:

  1. 两个函数分别处于基类与派生类
  2. 函数名相同
  3. 参数列表相同
  4. 返回值相同
  5. 两个函数都是虚函数

函数重定义:

功能:派生类的同名函数屏蔽了基类的同名函数的直接访问
要求:

  1. 两个函数分别处于基类与派生类
  2. 函数名相同
  3. 当函数名相同,只要不构成重写,那就是重定义

抽象类

C++中的抽象类是一种特殊的类,它不能被实例化,只能用作其他类的基类。抽象类的目的是为了定义通用的接口,并强制派生类实现这些接口中的方法。

要创建一个抽象类,需要在类的定义中至少有一个纯虚函数(没有函数体的虚函数)。
当一个虚函数没有函数体,以 = 0 结尾,这个函数就是一个纯虚函数。
如下:

class person
{
public:
	virtual void func() = 0;
};

此时func就是一个纯虚函数。

当一个类有纯虚函数,那么这个类就是一个抽象类。

抽象类不能实例化出对象,其派生类也不能实例化出对象。除非派生类对这个纯虚函数进行重写,派生类才可以实例化出对象

抽象类存在的意义,就是强制派生类进行重写函数


多态原理

那么C++是如何实现多态的呢?
这就和虚函数重写的底层有关了。

虚函数重写,是基于虚函数表的。虚函数表是一个用于存储虚函数指针的数组,其用于存储一个类中所有的虚函数指针,简称虚表

对于一般的类,如果没有虚函数,那么它的函数是不会存储在对象中的。但是虚函数不一样,为了保证可以在对象中确定这个对象对应的函数,我们要想办法在对象中标识出这个对象的虚函数。

于是含有虚函数的类,会多出一个指针,这个指针指向虚函数表,而虚函数表内部存储了这个类所有虚函数的地址。而这个指针叫虚函数表指针,简称虚表指针
在这里插入图片描述
每个类都有自己独立的虚表,所以派生类和基类的虚表是独立的

我们看看派生类的虚表是如何生成的:

  1. 先将基类的虚表拷贝一份到派生类的虚表中
  2. 如果派生类重写了虚函数,那么用重写的虚函数覆盖掉原先的虚函数
  3. 如果派生类自己还有额外的虚函数,依次添加到虚表的末尾

第二条至关重要,这是虚函数重写的底层原理:派生类重写了虚函数后,将虚表中相应的虚函数地址替换为重写后的地址。

当我们调用虚函数时,其会通过对象虚函数表指针找到虚函数表,再通过虚函数表定位函数。

将派生类的对象交给基类的 指针/引用 维护时,不会发生拷贝,而是进行一次切片,此时指针依然指向原先的对象,访问虚函数时,通过派生类对象的虚表来访问

当指针/引用指向基类对象:访问基类的虚表,调用重写前的虚函数
当指针/引用指向派生类对象:访问派生类的虚表,调用重写后的虚函数

此时不论是通过基类还是派生类的 指针/引用,都会通过对象本身对应的虚表来调用函数,这样就不会被 指针/引用 影响调用错误了

那么为什么将派生类的对象切片为基类对象,不能调用到派生类的函数呢?

当我们将一个派生类的对象切片为基类对象,此时不是直接进行拷贝,基类在拷贝派生类中的基类成员时,不会拷贝派生类的虚表,而是用基类自己的虚表

因此当我们将一个派生类对象切片为基类对象,由于虚表不是派生类的虚表,所以访问到的虚函数是基类的虚函数,无法构成多态。

虚表的特性:

虚表在编译阶段生成
虚表存储在代码段(常量区)中
只有虚函数才进虚表,普通函数不会进入虚表
虚表指针在构造函数的初始化列表中完成的初始化


多继承与多态

现在我们有以下继承关系:

class Base1 {
public:
	virtual void func1()
	{
		cout << "Base1::func1" << endl;
	}

	virtual void func2()
	{
		cout << "Base1::func2" << endl;
	}

private:
	int b1;
};

class Base2 
{
public:
	virtual void func1()
	{
		cout << "Base2::func1" << endl;
	}

	virtual void func2()
	{
		cout << "Base2::func2" << endl;
	}

private:

	int b2;
};

class Derive : public Base1, public Base2
{
public:
	virtual void func1()
	{
		cout << "Derive::func1" << endl;
	}

	virtual void func3()
	{
		cout << "Derive::func3" << endl;
	}

private:
	int d1;
};

Base1:有func1和func2两个虚函数
Base2:有func1和func2两个虚函数
Derive:继承了Base1和Base2,重写了func1,并增加了一个func3虚函数

多继承的情况下,我们的虚表是如何存放虚函数地址的?
我们看看两张虚表:

Base1继承的虚表:

Derive::func1
[0]:00007FF6750714E2

Base1::func2
[1]:00007FF675071343

Derive::func3
[2]:00007FF6750714C9

Base2继承的虚表:

Derive::func1
[0]:00007FF6750714DD

Base2::func2
[1]:00007FF6750710EB

第一个问题
派生类自己的虚函数func3存储在哪一张虚表?

通过上面两张虚表可以看到,派生类的func3出现在了第一张虚表中

结论:

  1. 多继承时,派生类会继承多张虚表,派生类自己增添的函数会放在第一张虚表中

第二个问题
按理来说,派生类对两个基类的func1进行了重写,那么两张虚表都应该被重写,为什么两张虚表中func1的地址不同?

原因:

对于第一张虚表,它会直接调用func1函数。但是对于第二张虚表,它的指针并不在对象的头部,那么如果直接调用func1函数,就会导致this指针指向错误。所以第二张虚表调用函数时,会先跳转到其它地址,修正自己的this指针,让指针指向对象的开头,再去调用func1函数。

结论:

  1. 如果多个基类中存在同名函数,且函数被派生类重写,此时这个虚函数会被存在多张虚表中,不同虚表会根据对应的地址,来修改自己的this指针,找到对象开头的指针,保证调用函数时this指针正确

虚继承与多态

在虚继承中存在 虚基表/虚基表指针。而多态中存在 虚表/虚表指针,这是一对容易混淆的概念,接下来我们辨析一下:
对比

  1. virtual
    - 虚函数中的virtual 与 虚继承的virtual两者只是共用一个关键字,没有太大关系
  1. 指针
    - 指向虚表的指针叫做虚表指针 / 虚函数表指针
    - 指向虚基表的指针叫做虚基表指针
    - 当一个类同时具有虚表指针与虚基表指针,虚表指针放在虚基表指针前面

  1. - 虚函数通过虚表来找到函数地址
    - 虚继承通过虚基表来找到被共享的间接基类
    - 在虚基表中,第一个位置会空出来,存储一个虚基表指针与虚表指针的偏移量

比如以下结构的虚继承:
在这里插入图片描述

那么这个派生类D的对象结构视图如下:

在这里插入图片描述
有以下两个注意点:

  1. 当一个类同时具有虚表指针与虚基表指针,虚表指针放在虚基表指针前面

在左侧的对象模型中,对于从同一个类继承下来的指针,虚基表指针会在虚表指针前面。

  1. 在虚基表中,第一个位置会空出来,存储一个虚基表指针与虚表指针的偏移量

在右侧的绿色虚基表中,第一个位置存放的不是到达虚基类成员的偏移量,而是到虚表指针到虚基表指针的偏移量。


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

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

相关文章

数据结构-并查集

并查集原理 在一些应用问题中&#xff0c;需要将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个 单元素集合&#xff0c;然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询一 个元素归属于那个集合的运算。适合于描述这类…

阿里云幻兽帕鲁服务器配置4核16G10M带宽够8个人玩吗?玩起来流畅度怎么样?

阿里云幻兽帕鲁服务器配置4核16G10M带宽这个&#xff0c;个人实测下来&#xff0c;五六个人玩是比较流畅的&#xff0c;不过8个人的话&#xff0c;估计会有点卡。如果是8个人的话&#xff0c;我建议选择8核32G那个配置&#xff0c;更加适合一些。 阿里云一键部署幻兽帕鲁详细教…

【lesson57】信号量和生产者消费者模型(环形队列版)

文章目录 信号量概念信号量接口初始化销毁等待发布 基于环形队列的生产者消费者模型编码Common.hLockGuard.hppTask.hppsem.hppRingQueue.hppConProd.cc 信号量概念 POSIX信号量和SystemV信号量作用相同&#xff0c;都是用于同步操作&#xff0c;达到无冲突的访问共享资源目的…

漫漫数学之旅022

文章目录 经典格言数学习题古今评注名人小传- 刘易斯卡罗尔 经典格言 艾丽斯笑着说&#xff1a;“去尝试也毫无用处&#xff0c;一个人无法相信不可能的事情。”——刘易斯卡罗尔&#xff08;Lewis Carroll&#xff09;《艾丽斯梦游仙境&#xff08;Alice in Wonderland&#…

零基础怎么学编程,免费版中文编程工具下载及构件用法教程

零基础怎么学编程&#xff0c;免费版中文编程工具下载及构件用法教程 一、前言 今天给大家分享的中文编程开发语言工具资料如下&#xff1a; 编程入门视频教程链接 http://​ https://edu.csdn.net/course/detail/39036 ​ 编程工具及实例源码文件下载可以点击最下方官网…

基于Python实现Midjourney集成到(个人/公司)平台中

目前Midjourney没有对外开放Api&#xff0c;想体验他们的服务只能在discord中进入他们的频道进行体验或者把他们的机器人拉入自己创建的服务器中&#xff1b;而且现在免费的也用不了了&#xff0c;想使用就得订阅。本教程使用midjourney-api这个开源项目&#xff0c;搭建Midjou…

Linux第55步_根文件系统第2步_测试使用busybox生成的根文件系统

测试使用busybox生成的根文件系统。测试内容较多&#xff0c;很杂。 1、修改“nfs-kernel-server” 1)、打开终端 输入“sudo vi /etc/default/nfs-kernel-server回车”&#xff0c;打开“nfs-kernel-server”文件。 输入密码“123456回车” 见下图&#xff1a; 2)、在最后…

【学网攻】 第(28)节 -- OSPF虚链路

系列文章目录 目录 系列文章目录 文章目录 前言 一、什么是OSPF虚链路&#xff1f; 二、实验 1.引入 实验目标 实验背景 技术原理 实验步骤 实验设备 实验拓扑图 实验配置 扩展 实验拓扑图 实验配置 实验验证 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻…

模型 4S(满意、服务、速度、诚意)理论

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_总纲目录。重在提升认知。以客户为中心。 1 4S(满意、服务、速度、诚意)理论的应用 1.1 4S 理论在制造业中的应用 某汽车制造企业 A 一直致力于提供高品质的汽车产品和优质的服务&#xff0c;以满足客户的需求和期…

2022年12月电子学会青少年软件编程 中小学生Python编程等级考试二级真题解析(选择题)

2022年12月Python编程等级考试二级真题解析 选择题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 1、运行下列程序&#xff0c;最终输出的结果是 info {1:小明,2:小黄,3:小兰} info[4]小红 info[2]小白 print(info) A、{1:小明,2:小白,3:小红,4:小…

高德地图上绘制热力图的方法

百度地图和高德地图的JavaScript API都提供了热力图的绘制方法&#xff0c;都是将热力图作为新的图层&#xff0c;叠加到地图上。但是百度地图的经纬度体系与我们的经纬度存在偏差&#xff0c;高德的与我们相符&#xff0c;应当使用高德地图JavaScript API。 因为是JavaScript…

最长连续手牌 - 华为OD统一考试

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 有这么一款单人卡牌游戏&#xff0c;牌面由颜色和数字组成&#xff0c;颜色为红、黄、蓝、绿中的一种&#xff0c;数字为 0−9 中的一个。游戏开始时玩家从手牌中…

【题解】差分

差分其实就是前缀和的逆运算。 如果数组 A 是数组 B 的前缀和数组&#xff0c;则称 B 是 A 的差分数组。 思路 由题意得&#xff0c;应该求给定数组的差分数组。 差分加速的原理 对 L 到 R 区间内的数加上 c&#xff0c;时间复杂度是O(c) &#xff0c;即O(n) 。 但是如果…

SORA:OpenAI最新文本驱动视频生成大模型技术报告解读

Video generation models as world simulators&#xff1a;作为世界模拟器的视频生成模型 1、概览2、Turning visual data into patches&#xff1a;将视觉数据转换为补丁3、Video compression network&#xff1a;视频压缩网络4、Spacetime Latent Patches&#xff1a;时空潜在…

NetMizer 日志管理系统 多处前台RCE漏洞复现

0x01 产品简介 NetMizer是提供集成应用交付和应用安全解决方案以实现业务智能网络的优秀全球供应商,为全球企业和运营商提供确保关键业务应用的全面可用性、高性能和完善的安全性的解决方案。 0x02 漏洞概述 NetMizer 日志管理系统position.php、hostdelay.php、等接口处存在…

leetcode刷题--贪心算法

七. 贪心算法 文章目录 七. 贪心算法1. 605 种花问题2. 121 买卖股票的最佳时机3. 561 数组拆分4. 455 分发饼干5. 575 分糖果6. 135 分发糖果7. 409 最长回文串8. 621 任务调度器9. 179 最大数10. 56 合并区间11. 57 插入区间13. 452 用最少数量的箭引爆气球14. 435 无重叠区间…

鸿蒙系统优缺点,能否作为开发者选择

凡是都有对立面&#xff0c;就直接说说鸿蒙的优缺点吧。 鸿蒙的缺点&#xff1a; 鸿蒙是从2019年开始做出来的&#xff0c;那时候是套壳Android大家都知晓。从而导致大家不看鸿蒙系统&#xff0c;套壳Android就是多次一举。现在鸿蒙星河版已经是纯血鸿蒙&#xff0c;但是它的…

树莓派5 EEPROM引导加载程序恢复镜像

树莓派5不能正常启动&#xff0c;可以通过电源led灯的闪码来判断错误发生的大致情形。 LED警告闪码 如果树莓派由于某种原因无法启动&#xff0c;或者不得不关闭&#xff0c;在许多情况下&#xff0c;LED会闪烁特定的次数来指示发生了什么。LED会闪烁几次长闪烁&#xff0c;然…

02 c++入门

目录 c关键字命名空间c输入&输出缺省参数函数重载引用内联函数auto关键字(c11)基于范围的for循环(c11)指针空值—nullptr(c11) 0. 本节知识点安排目的 c是在c的基础上&#xff0c;容纳进去了面向对象编程思想&#xff0c;并增加了许多有用的库&#xff0c;以及编程范式等…

【HTML】过年不能放烟花,那就放电子烟花

闲谈 大家回家过年可能都多多少少放过些&#x1f9e8;&#xff0c;但是有些在城市上过年的小伙伴可能就没有机会放鞭炮了。不过没关系&#xff0c;我们懂技术&#xff0c;我们用技术自娱自乐&#xff0c;放电子烟花&#xff0c;总不可能被警长叔叔敲门问候吧。 开干 首先&…