C++核心知识(五)—— 继承和派生

news2024/10/2 6:40:38

1. 继承概述

1.1 为什么需要继承

网页类
class IndexPage{
public:
    //网页头部
    void Header(){
        cout << "网页头部!" << endl;
    }
    //网页左侧菜单
    void LeftNavigation(){
        cout << "左侧导航菜单!" << endl;
    }
    //网页主体部分
    void MainBody(){
        cout << "首页网页主题内容!" << endl;
    }
    //网页底部
    void Footer(){
        cout << "网页底部!" << endl;
    }
private:
    string mTitle; //网页标题
};

#if 0
//如果不使用继承,那么定义新闻页类,需要重新写一遍已经有的代码
class NewsPage{
public:
    //网页头部
    void Header(){
        cout << "网页头部!" << endl;
    }
    //网页左侧菜单
    void LeftNavigation(){
        cout << "左侧导航菜单!" << endl;
    }
    //网页主体部分
    void MainBody(){
        cout << "新闻网页主体内容!" << endl;
    }
    //网页底部
    void Footer(){
        cout << "网页底部!" << endl;
    }
private:
    string mTitle; //网页标题
};

void test(){
    NewsPage* newspage = new NewsPage;
    newspage->Header();
    newspage->MainBody();
    newspage->LeftNavigation();
    newspage->Footer();
}
#else
//使用继承,可以复用已有的代码,新闻业除了主体部分不一样,其他都是一样的
class NewsPage : public IndexPage{
public:
    //网页主体部分
    void MainBody(){
        cout << "新闻网页主主体内容!" << endl;
    }
};
void test(){
    NewsPage* newspage = new NewsPage;
    newspage->Header();
    newspage->MainBody();
    newspage->LeftNavigation();
    newspage->Footer();
}
#endif
int main(){

    test();

    return EXIT_SUCCESS;
}

1.2 继承基本概念

C++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。

一个B类继承于A类,或称从类A派生类B。这样的话,类A成为基类(父类), 类B成为派生类(子类)。

派生类中的成员,包含两大部分:

  • 一类是从基类继承过来的,一类是自己增加的成员。

  • 从基类继承过过来的表现其共性,而新增的成员体现了其个性。

1.3 派生类定义

派生类定义格式:

Class 派生类名 :  继承方式 基类名{
         //派生类新增的数据成员和成员函数
}

三种继承方式:

  • public : 公有继承

  • private : 私有继承

  • protected : 保护继承

从继承源上分:

  • 单继承:指每个派生类只直接继承了一个基类的特征

  • 多继承:指多个基类派生出一个派生类的继承关系,多继承的派生类直接继承了不止一个基类的特征

2. 派生类访问控制

派生类继承基类,派生类拥有基类中全部成员变量和成员方法(除了构造和析构之外的成员方法),但是在派生类中,继承的成员并不一定能直接访问,不同的继承方式会导致不同的访问权限。

2.1 派生类的访问权限规则

2.2 访问权限案例

/基类
class A{
public:
    int mA;
protected:
    int mB;
private:
    int mC;
};

//1. 公有(public)继承
class B : public A{
public:
    void PrintB(){
        cout << mA << endl; //可访问基类public属性
        cout << mB << endl; //可访问基类protected属性
        //cout << mC << endl; //不可访问基类private属性
    }
};
class SubB : public B{
    void PrintSubB(){
        cout << mA << endl; //可访问基类public属性
        cout << mB << endl; //可访问基类protected属性
        //cout << mC << endl; //不可访问基类private属性
    }
};
void test01(){

    B b;
    cout << b.mA << endl; //可访问基类public属性
    //cout << b.mB << endl; //不可访问基类protected属性
    //cout << b.mC << endl; //不可访问基类private属性
}

//2. 私有(private)继承
class C : private A{
public:
    void PrintC(){
        cout << mA << endl; //可访问基类public属性
        cout << mB << endl; //可访问基类protected属性
        //cout << mC << endl; //不可访问基类private属性
    }
};
class SubC : public C{
    void PrintSubC(){
        //cout << mA << endl; //不可访问基类public属性
        //cout << mB << endl; //不可访问基类protected属性
        //cout << mC << endl; //不可访问基类private属性
    }
};
void test02(){
    C c;
    //cout << c.mA << endl; //不可访问基类public属性
    //cout << c.mB << endl; //不可访问基类protected属性
    //cout << c.mC << endl; //不可访问基类private属性
}
//3. 保护(protected)继承
class D : protected A{
public:
    void PrintD(){
        cout << mA << endl; //可访问基类public属性
        cout << mB << endl; //可访问基类protected属性
        //cout << mC << endl; //不可访问基类private属性
    }
};
class SubD : public D{
    void PrintD(){
        cout << mA << endl; //可访问基类public属性
        cout << mB << endl; //可访问基类protected属性
        //cout << mC << endl; //不可访问基类private属性
    }
};
void test03(){
    D d;
    //cout << d.mA << endl; //不可访问基类public属性
    //cout << d.mB << endl; //不可访问基类protected属性
    //cout << d.mC << endl; //不可访问基类private属性
}

3. 继承中的构造和析构

3.1 继承中的对象模型

在C++编译器的内部可以理解为结构体,子类是由父类成员叠加子类新成员而成:

class Aclass{
public:
    int mA;
    int mB;
};
class Bclass : public Aclass{
public:
    int mC;
};
class Cclass : public Bclass{
public:
    int mD;
};
void test(){
    cout << "A size:" << sizeof(Aclass) << endl;
    cout << "B size:" << sizeof(Bclass) << endl;
    cout << "C size:" << sizeof(Cclass) << endl;
}

查看类继承的内部模型

找到VS2013开发人员命令提示程序(一般在:C:\Program Files(x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts),打开,然后复制你工程路径,命令:cd 路径,进入你工程文件夹中(如果工程不在C盘在E盘的话,要再E:下),然后命令:

cl /d1 reportSingleClassLayout类名文件名全称

如:cl /d1reportSingleClassLayoutSon test.cpp

3.2 对象构造和析构的调用原则

3.2.1 继承中的构造和析构

  • 子类对象在创建时会首先调用父类的构造函数

  • 父类构造函数执行完毕后,才会调用子类的构造函数

  • 当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显示调用父类构造函数

  • 析构函数调用顺序和构造函数相反

class A{
public:
    A(){
        cout << "A类构造函数!" << endl;
    }
    ~A(){
        cout << "A类析构函数!" << endl;
    }
};

class B : public A{
public:
    B(){
        cout << "B类构造函数!" << endl;
    }
    ~B(){
        cout << "B类析构函数!" << endl;
    }
};

class C : public B{
public:
    C(){
        cout << "C类构造函数!" << endl;
    }
    ~C(){
        cout << "C类析构函数!" << endl;
    }
};

void test(){
    C c;
}

3.2.2 继承与组合混搭的构造和析构

class D{
public:
    D(){
        cout << "D类构造函数!" << endl;
    }
    ~D(){
        cout << "D类析构函数!" << endl;
    }
};
class A{
public:
    A(){
        cout << "A类构造函数!" << endl;
    }
    ~A(){
        cout << "A类析构函数!" << endl;
    }
};
class B : public A{
public:
    B(){
        cout << "B类构造函数!" << endl;
    }
    ~B(){
        cout << "B类析构函数!" << endl;
    }
};
class C : public B{
public:
    C(){
        cout << "C类构造函数!" << endl;
    }
    ~C(){
        cout << "C类析构函数!" << endl;
    }
public:
    D c;
};
void test(){
    C c;
}

4. 继承中同名成员的处理方法

  • 当子类成员和父类成员同名时,子类依然从父类继承同名成员

  • 如果子类有成员和父类同名,子类访问其成员默认访问子类的成员(本作用域,就近原则)

  • 在子类通过作用域::进行同名成员区分(在派生类中使用基类的同名成员,显示使用类名限定符)

class Base{
public:
    Base():mParam(0){}
    void Print(){ cout << mParam << endl; }
public:
    int mParam;
};

class Derived : public Base{
public:
    Derived():mParam(10){}
    void Print(){
        //在派生类中使用和基类的同名成员,显示使用类名限定符
        cout << Base::mParam << endl;
        cout << mParam << endl;
    }
    //返回基类重名成员
    int& getBaseParam(){ return  Base::mParam; }
public:
    int mParam;
};

int main(){

    Derived derived;
    //派生类和基类成员属性重名,子类访问成员默认是子类成员
    cout << derived.mParam << endl; //10
    derived.Print();
    //类外如何获得基类重名成员属性
    derived.getBaseParam() = 100;
    cout << "Base:mParam:" << derived.getBaseParam() << endl;

    return EXIT_SUCCESS;
}

注意: 如果重新定义了基类中的重载函数,将会发生什么?

class Base{
public:
    //重载函数
    void func1(){
        cout << "Base::void func1()" << endl;
    };
    void func1(int param){
        cout << "Base::void func1(int param)" << endl;
    }
    //非重载函数
    void myfunc(){
        cout << "Base::void myfunc()" << endl;
    }
};

class Derived1 : public Base{};
class Derived2 : public Base{
public:
    void myfunc(){
        //基类myfunc被隐藏,可通过类作用域运算符指定调用基类myfunc函数
        //Base::myfunc();
        cout << "Derived2::void myfunc()" << endl;
    }
};
class Derived3 : public Base{
public:
    //改变成员函数的参数列表
    void func1(int param1, int param2){
        //Base::func1(10);  //类的内部可通过类作用域运算符访问基类重载版本的函数
        cout << "Derived3::void func1(int param1,int param2)" << endl;
    };
};
class Derived4 : public Base{
public:
    //改变成员函数的返回值
    int func1(int param){
        Base::func1(10);
        cout << "Derived4::int func1(int param)" << endl;
        return 0;
    }
};

//和基类非重载函数重名
void test01(){
    Derived1 derived1;
    derived1.myfunc();
    //和基类函数重名
    Derived2 derived2;
    derived2.myfunc();
}

//和基类重载函数重名
void test02(){

    Derived3 derived3;
    //derived3.func1();  //基类重载版本的函数fun1被全部隐藏,子类外部不可访问
    //derived3.func1(10);
    derived3.func1(10,20);

    Derived4 derived4;
    //derived4.func1(); //基类重载版本的函数fun1被全部隐藏,子类外部不可访问
    derived4.func1(10);
}

//结论:任何时候重新定义基类中的任何一个函数,子类中这种函数的任何版本都
//会被隐藏(非覆盖,可通过类作用域运算符调用)

任何时候重新定义基类中的一个重载函数,在新类中所有的其他版本将被自动隐藏.

5. 非自动继承的函数

不是所有的函数都能自动从基类继承到派生类中。构造函数和析构函数用来处理对象的创建和析构操作,构造和析构函数只知道对它们的特定层次的对象做什么,也就是说构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建。

另外operator=也不能被继承,因为它完成类似构造函数的行为。也就是说尽管我们知道如何由=右边的对象如何初始化=左边的对象的所有成员,但是这个并不意味着对其派生类依然有效。

在继承的过程中,如果没有创建这些函数,编译器会自动生成它们。

6. 继承中的静态成员特性

静态成员函数和非静态成员函数的共同点:

  1. 他们都可以被继承到派生类中。

  1. 如果重新定义一个静态成员函数,所有在基类中的其他重载函数会被隐藏。

  1. 如果我们改变基类中一个函数的特征,所有使用该函数名的基类版本都会被隐藏。

静态成员函数不能是虚函数(virtual function)。

class Base{
public:
    static int getNum(){ return sNum; }
    static int getNum(int param){
        return sNum + param;
    }
public:
    static int sNum;
};
int Base::sNum = 10;

class Derived : public Base{
public:
    static int sNum; //基类静态成员属性将被隐藏
#if 0
    //重定义一个函数,基类中重载的函数被隐藏
    static int getNum(int param1, int param2){
        return sNum + param1 + param2;
    }
#else
    //改变基类函数的某个特征,返回值或者参数个数,将会隐藏基类重载的函数
    static void getNum(int param1, int param2){
        cout <<  sNum + param1 + param2 << endl;
    }
#endif
};
int Derived::sNum = 20;

7. 多继承

7.1 多继承概念

我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由于多继承是非常受争议的,从多个类继承可能会导致函数、变量等同名导致较多的歧义。

class Base1{
public:
    void func1(){ cout << "Base1::func1" << endl; }
};
class Base2{
public:
    void func1(){ cout << "Base2::func1" << endl; }
    void func2(){ cout << "Base2::func2" << endl; }
};
//派生类继承Base1、Base2
class Derived : public Base1, public Base2{};
int main(){

    Derived derived;
    //func1是从Base1继承来的还是从Base2继承来的?
    //derived.func1(); 
    derived.func2();

    //解决歧义:显示指定调用那个基类的func1
    derived.Base1::func1(); 
    derived.Base2::func1();

    return EXIT_SUCCESS;
}

多继承会带来一些二义性的问题, 如果两个基类中有同名的函数或者变量,那么通过派生类对象去访问这个函数或变量时就不能明确到底调用从基类1继承的版本还是从基类2继承的版本?

解决方法就是显示指定调用那个基类的版本

7.2 菱形继承和虚继承

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

这种继承所带来的问题:

  1. 羊继承了动物的数据和函数,鸵同样继承了动物的数据和函数,当草泥马调用函数或者数据时,就会产生二义性。

  1. 草泥马继承自动物的函数和数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

class BigBase{
public:
    BigBase(){ mParam = 0; }
    void func(){ cout << "BigBase::func" << endl; }
public:
    int mParam;
};

class Base1 : public BigBase{};
class Base2 : public BigBase{};
class Derived : public Base1, public Base2{};

int main(){

    Derived derived;
    //1. 对“func”的访问不明确
    //derived.func();
    //cout << derived.mParam << endl;
    cout << "derived.Base1::mParam:" << derived.Base1::mParam << endl;
    cout << "derived.Base2::mParam:" << derived.Base2::mParam << endl;

    //2. 重复继承
    cout << "Derived size:" << sizeof(Derived) << endl; //8

    return EXIT_SUCCESS;
}

上述问题如何解决?对于调用二义性,那么可通过指定调用那个基类的方式来解决,那么重复继承怎么解决?

对于这种菱形继承所带来的两个问题,c++为我们提供了一种方式,采用虚基类。那么我们采用虚基类方式将代码修改如下:

class BigBase{
public:
    BigBase(){ mParam = 0; }
    void func(){ cout << "BigBase::func" << endl; }
public:
    int mParam;
};

class Base1 : virtual public BigBase{};
class Base2 : virtual public BigBase{};
class Derived : public Base1, public Base2{};

int main(){

    Derived derived;
    //二义性问题解决
    derived.func();
    cout << derived.mParam << endl;
    //输出结果:12
    cout << "Derived size:" << sizeof(Derived) << endl;

    return EXIT_SUCCESS;
}

以上程序Base1 ,Base2采用虚继承方式继承BigBase,那么BigBase被称为虚基类。

通过虚继承解决了菱形继承所带来的二义性问题。

但是虚基类是如何解决二义性的呢?并且derived大小为12字节,这是怎么回事?

7.3 虚继承实现原理

class BigBase{
public:
    BigBase(){ mParam = 0; }
    void func(){ cout << "BigBase::func" << endl; }
public: int mParam;
};
#if 0 //虚继承
class Base1 : virtual public BigBase{};
class Base2 : virtual public BigBase{};
#else //普通继承
class Base1 :  public BigBase{};
class Base2 :  public BigBase{};
#endif
class Derived : public Base1, public Base2{};

普通继承

虚继承

BigBase:

Base1:

Base2:

Derived:

通过对象布局图,我们发现普通继承和虚继承的对象内存图是不一样的。我们也可以猜测到编译器肯定对我们编写的程序做了一些手脚。

  • BigBase 菱形最顶层的类,内存布局图没有发生改变。

  • Base1和Base2通过虚继承的方式派生自BigBase,这两个对象的布局图中可以看出编译器为我们的对象中增加了一个vbptr (virtual base pointer),vbptr指向了一张表,这张表保存了当前的虚指针相对于虚基类的首地址的偏移量。

  • Derived派生于Base1和Base2,继承了两个基类的vbptr指针,并调整了vbptr与虚基类的首地址的偏移量。

由此可知编译器帮我们做了一些幕后工作,使得这种菱形问题在继承时候能只继承一份数据,并且也解决了二义性的问题。现在模型就变成了Base1和 Base2 、Derived三个类对象共享了一份BigBase数据。

当使用虚继承时,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的)。即使共享虚基类,但是必须要有一个类来完成基类的初始化(因为所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成初始化呢?C++标准中选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),但是虚基类的初始化是由最后的子类完成,其他的初始化语句都不会调用

class BigBase{
public:
    BigBase(int x){mParam = x;}
    void func(){cout << "BigBase::func" << endl;}
public:
    int mParam;
};
class Base1 : virtual public BigBase{
public:
    Base1() :BigBase(10){} //不调用BigBase构造
};
class Base2 : virtual public BigBase{
public:
    Base2() :BigBase(10){} //不调用BigBase构造
};

class Derived : public Base1, public Base2{
public:
    Derived() :BigBase(10){} //调用BigBase构造
};
//每一次继承子类中都必须书写初始化语句
int main(){
    Derived derived;
    return EXIT_SUCCESS;
}

注意:虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共祖先的多继承的。

工程开发中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性远多于其带来的便利,多重继承对代码维护性上的影响是灾难性的,在设计方法上,任何多继承都可以用单继承代替。

Jerry Schwarz,输入输出流(iostream)的作者,曾在个别场合表示如何他重新设计iostream的话,很可能从iostream中去除多重继承。

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

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

相关文章

[SQL Statements] 基本的SQL知识 之DDL针对表结构和表空间的基本操作

[SQL Statements] 基本的SQL知识 之DDL针对表结构和表空间的基本操作 什么是数据库的表以及表空间 在MySQL中&#xff0c;一个数据库可以包含多个表&#xff0c;每个表是由若干个列&#xff08;column&#xff09;和行&#xff08;row&#xff09;组成的。表是存储数据的基本…

2023年clang12编译问题与解决的记录

最近编译clang12以及尝试基于clang开发一个C的静态代码分析工具&#xff0c;如下是遇到的环境相关的编译问题与解决方案。在此做个记录&#xff0c;同时供可能会遇到同样问题的同学参考 环境说明 注&#xff1a;如下是最终编译成功的环境 clang分支&#xff1a; branch : a…

HTTP 请求头中的 Remote_Addr,X-Forwarded-For,X-Real-IP | Spring Cloud 13

一、$remote_addr 表示发出请求的客户端主机的 IP 地址&#xff0c;但它的值不是由客户端提供的&#xff0c;而是Nginx与客户端进行TCP连接过程中&#xff0c;获得的客户端的真实地址 IP 地址&#xff0c;REMOTE_ADDR 无法伪造&#xff0c;因为建立 TCP 连接需要三次握手&…

Java学习教程,Java基础教程(从入门到精通)

Java 是一门面向对象编程语言&#xff0c;不仅吸收了 C 语言的各种优点&#xff0c;还摒弃了 C 里难以理解的多继承、指针等概念。Java 不但可以用来开发网站后台、PC 客户端和 Android APP&#xff0c;还在数据分析、网络爬虫、云计算领域大显身手。 从学术的角度讲&#xff…

VR全景云展厅,实现7*24小时的线上宣传能力!

数字化时代&#xff0c;虚拟现实技术的应用越来越广泛&#xff0c;其中VR全景云展厅是一种新兴的展示方式&#xff0c;具有独特的展示优势。随着VR技术的不断发展&#xff0c;越来越多的企业、机构和个人开始使用VR全景云展厅来展示他们的产品和服务。一、展厅营销痛点1、实地到…

内网渗透-基础环境

解决依赖&#xff0c;scope安装 打开要给cmd powershell 打开远程 Set-ExecutionPolicy RemoteSigned -scope CurrentUser; 我试了好多装这东西还是得科学上网&#xff0c;不然不好用 iwr -useb get.scoop.sh | iex 查看下载过的软件 安装sudo 安装git 这里一定要配置bu…

105.第十九章 MySQL数据库 -- MySQL半同步复制、复制过滤器、复制的问题和解决方案(十五)

6.1.6 半同步复制 Mysql的主从复制它的复制机制我们称为所谓的异步复制,这里面提到了一个概念异步,那什么叫异步复制呢,所谓异步复制实际上说白了就是在用户发请求到数据库做一些修改的时候,那我们在前面讲过主从复制,如果我们有一个主节点,另外带若干个从节点,假设有2个…

Typescript 全栈最值得学习的技术栈 TRPC

如果你想成为一个 Typescript 全栈工程师&#xff0c;那么你可能需要关注一下 tRPC 框架。本文总共会接触到以下主要技术栈。Next.jsTRPCPrismaZodAuth.js不是介绍 tRPC 吗&#xff0c;怎么突然出现这么多技术栈。好吧&#xff0c;主要这些技术栈都与 typescript 相关&#xff…

知道一个服务器IP应该怎么进入

首先我是国内&#xff0c;访问国外的网站比如谷歌等&#xff0c;访问特别慢&#xff0c;有时候甚至登录不进去。现在知道了一个台湾或者国外的服务器应该怎么登录进去呢&#xff1f;知道服务器IP之后&#xff0c;你还需要知道服务器的远程端口帐号密码才能登录的。知道上面信息…

Java 19和IntelliJ IDEA,如何和谐共生?

Java仍然是目前比较流行的编程语言&#xff0c;它更短的发布节奏让开发者每六个月左右就可以试用新的语言或平台功能&#xff0c;IntelliJ IDEA帮助我们更流畅地发现和使用这些新功能。IntelliJ IDEA v2022.3正式版下载(Q技术交流&#xff1a;786598704&#xff09;在本文中&am…

小樽C++ 多章⑧ (叁) 指针与字符串、(肆) 函数与指针

目录 叁、函数与字符串 肆、函数与指针 4.1 指针作为函数参数 4.2 函数返回指针 4.3 函数指针与函数指针数组 4.4 结构体指针 ​​​​​​​​​​​​​​小樽C 多章⑧ (壹) 指针变量https://blog.csdn.net/weixin_44775255/article/details/129031168 小樽C 多章⑧ …

数据可视化的正确逻辑和关键点

由于移动互联网和手机的普及发展&#xff0c;截至2022年6月&#xff0c;我国短视频的用户规模达9.62亿&#xff1b;即时通信用户规模达10.27亿&#xff1b;网络新闻用户规模达7.88亿&#xff1b;网络直播用户规模达7.16亿......这些数据都意味着互联网已经涉及我们的方方面面&a…

Java并发编程与API详解

文章目录前言操作系统——进程和线程进程进程组成进程状态进程控制进程创建进程终止进程阻塞和唤醒进程通信线程线程组成线程状态线程控制线程的实现方式用户线程内核线程混合方式CPU调度调度的层次调度的实现调度器调度的时机、切换与过程进程调度的方式闲逛进程两种线程的调度…

合作伙伴管理软件如何帮助简化您的业务流程?

随着合作伙伴数量的增加&#xff0c;企业需要处理更多的信息和数据&#xff0c;并在更多的项目上进行协调和管理。这会增加企业的复杂性和工作量&#xff0c;使管理变得更加困难。同时&#xff0c;随着合作伙伴项目数量的增加&#xff0c;企业需要与更多的合作伙伴进行协调和沟…

Echarts 水波图实现

开发的项目中需要实现这样一个水波图&#xff0c;例如下图在echarts官网中找了很久没找到&#xff0c;后面是在Echarts社区中找到的&#xff0c;实现了大部分的样式&#xff0c;但是还有一些数据的展示没有实现。水波图的数值展示是默认整数百分比&#xff0c;我的需求是需要保…

【算法数据结构体系篇class14、15】:并查集

一、并查集1)有若干个样本a、b、c、d…类型假设是V2)在并查集中一开始认为每个样本都在单独的集合里3)用户可以在任何时候调用如下两个方法&#xff1a;boolean isSameSet(V x, V y) : 查询样本x和样本y是否属于一个集合void union(Vx, V y) : 把x和y各自所在集合的所有样本合并…

带你玩转modbusTCP通信

modbus TCP Modbus TCP是一种基于TCP/IP协议的Modbus通信协议&#xff0c;它是Modbus协议的一种变体&#xff0c;用于在以太网上进行通信。Modbus TCP协议是一种开放的通信协议&#xff0c;它支持多种编程语言和操作系统&#xff0c;并且可以在不同的硬件和软件平台上进行通信…

从0开始学python -49

Python MySQL - mysql-connector 驱动 -2 插入数据 插入数据使用 “INSERT INTO” 语句&#xff1a; demo_mysql_test.py: 向 sites 表插入一条记录。 import mysql.connectormydb mysql.connector.connect(host"localhost",user"root",passwd"…

液氮恒温器概述

恒温器是直接或间接控制一个或多个热源和冷源来维持所要求的温度的一种装置。 恒温器要实现这种功能&#xff0c;就必须具有一个敏感元件和一个转换器&#xff0c;敏感元件量度出温度的变化&#xff0c;并对转换器产生所需的作用。转换器把来自敏感元件的作用转换成对改变温度…

创建型设计模式(C++)

文章目录1.简单工厂模式&静态工厂模式2.工厂方法模式3.抽象工厂模式4.原型模式5.单例模式a.饿汉式b.懒汉式6.建造者模式&#xff08;生成器模式&#xff09;创建型模式提供了创建对象的机制&#xff0c;旨在提升已有代码的灵活性和可复用性。 部分插图来自&#xff1a; ht…