C++(十七) 多态

news2024/11/25 23:27:11

一、 多态概念

        多态(polymorphism),通俗来说,就是多种形态。多态分为编译时多态(静态多态)运行时多态(动态多态)。这里我们重点讲运行时多态,同时简单介绍编译时多态。

 1.1动态绑定和静态绑定

  • 静态绑定
    对于不满足多态条件(即指针或引用 + 调用虚函数)的函数调用,是在编译时绑定的,也就是说编译时已经确定了将要调用的函数地址。这种方式称为静态绑定

  • 动态绑定
    满足多态条件的函数调用(指针或引用 + 调用虚函数),是在运行时绑定的。也就是说,在运行时根据指针或引用实际指向的对象,在该对象的虚函数表中找到并调用相应的函数地址。这种方式称为动态绑定

  • 编译时多态(静态多态) 使用静态绑定,例如函数重载、模板等。
  • 运行时多态(动态多态) 使用动态绑定,通过虚函数实现。

 二、编译时多态(静态多态)

        编译时多态通过函数重载运算符重载模板来实现。它在编译阶段确定,属于静态绑定所以也称静态多态

它的实现方式也就是我们之前学过的:

  • 函数重载:同一个函数名可以有多个不同的参数列表,编译器在编译时根据参数的类型和个数来决定调用哪个函数。
  • 运算符重载:可以为用户自定义的类型(例如类)定义运算符的行为。
  • 模板:允许编写与类型无关的通用代码,在编译时实例化为具体的类型。

举个简单例子:

#include <iostream>
using namespace std;

class Shape {
public:
    // 编译时多态:函数重载
    void draw() {
        cout << "无参draw" << endl;
    }

    void draw(int size) {
        cout << "传参draw 参数: " << size << endl;
    }
};

int main() {
    Shape shape;
    shape.draw();       // 调用无参数的draw
    shape.draw(10);     // 调用有参数的draw
}

这里就不过多赘述,如果对 函数重载、运算符重载、模板 不熟悉的朋友可以翻看前面的篇章:

 C++(一)-CSDN博客     ——函数重载

 C++ (四) 类和对象part 2-CSDN博客       ——运算符重载

C++(七)模板_c++template typename-CSDN博客

C++(十四)模板进阶_c++14 模板-CSDN博客


 三、运行时多态(动态多态)

接下来详细介绍本篇正题:

        运行时多态通过虚函数实现,依赖于继承和动态绑定。在运行时根据实际对象的类型决定调用哪个函数。这种多态性常用于处理派生类对象而无需改变代码结构。

举个例子,假设有一个“买票”的行为(函数):

  • 普通人买票时,是全价买票;
  • 学生买票时,是优惠票(比如5折或7.5折);
  • 军人买票时,则是优先买票。

再比如,同样是动物“叫”的行为(函数):

  • 当传入对象时,发出的是“喵喵~~”的声音;
  • 当传入对象时,发出的是“汪汪~~”的叫声。

 3.1 虚函数

        学习动态多态我们要先学习一个新的概念:虚函数

        关键字:virtual

        实现动态绑定,即在运行时决定应该调用哪个类的函数。通过虚函数,C++实现了运行时多态,允许你在父类中定义通用的接口,并在子类中根据具体需求实现不同的行为。

【注意】:非成员函数不能使用virtual修饰 

文字比较难理解我们对照以上动物叫的例子来实现一段代码:

#include <iostream>
using namespace std;

// 基类
class Animal {
public:
    // 声明为虚函数
    virtual void sound() 
    { 
        cout << "基类中的 sound()" << endl;
    }
};

// 派生类:猫
class Cat : public Animal {
public:
    // 重写虚函数
    void sound()
    {   
        cout << "喵!" << endl;
    }
};

// 派生类:狗
class Dog : public Animal {
public:
    // 重写虚函数
    void sound() 
    { 
        cout << "汪!" << endl;
    }
};

int main() {
    Animal* animal; // 基类指针

    Cat cat;
    Dog dog;

    animal = &cat;
    animal->sound(); // 调用Cat::sound()

    animal = &dog;
    animal->sound(); // 调用Dog::sound()

    return 0;
}

 程序运行结果:

喵!
汪!

 在这个简单例子中,基类Animal中的sound()函数是虚函数(使用了关键字:virtual ),派生类CatDog分别重写了该函数。当使用基类指针animal指向不同的派生类对象时,程序在运行时根据实际对象类型调用相应的派生类的sound()函数。这就是虚函数的动态绑定特性。

        这就是动态多态的最简单用法。


3.2 重写/覆盖 

        上面我们提到一个虚函数的重写/覆盖的概念,那什么叫重写呢?

        当派生类中有一个与基类完全相同的虚函数(即返回值类型、函数名、参数列表与基类虚函数完全一致),则称派生类的虚函数重写了基类的虚函数。

重点:构成虚函数重写的条件(完全一样的虚函数)

  • 返回值类型  与基类虚函数完全一致
  • 函数名  与基类虚函数完全一致
  • 参数列表 与基类虚函数完全一致

        (俗称:三同) 


【注意】:

        在重写基类虚函数时,虽然派生类的虚函数可以不加 virtual 关键字(因为基类的虚函数属性会被继承下来,派生类中的该函数仍然保持虚函数的属性),但这种写法不规范,不建议使用。在实际开发中,应显式使用 virtual 关键字进行声明。然而,在考试中,往往会故意设置这种情况,要求判断其是否构成多态。 

 依然用上面例子(无任何修改):

Cat::sound() 、 Dog::sound() 和 基类Animal 中sound() 构成重写

#include <iostream>
using namespace std;

// 基类
class Animal {
public:
    // 声明为虚函数
    virtual void sound() 
    { 
        cout << "基类中的 sound()" << endl;
    }
};

// 派生类:猫
class Cat : public Animal {
public:
    // 重写虚函数
    void sound()
    {   
        cout << "喵!" << endl;
    }
};

// 派生类:狗
class Dog : public Animal {
public:
    // 重写虚函数
    void sound() 
    { 
        cout << "汪!" << endl;
    }
};

int main() {
    Animal* animal; // 基类指针

    Cat cat;
    Dog dog;

    animal = &cat;
    animal->sound(); // 调用Cat::sound()

    animal = &dog;
    animal->sound(); // 调用Dog::sound()

    return 0;
}

3.3 实现多态还有两个必须重要条件

  • 必须指针或者引用调用虚函数
  • 被调用的函数必须是虚函数

说明: 

  1.  第一必须是基类的指针或引用,基类指针或引用指向的是基类类型,但它们可以指向派生类的对象。这是因为派生类是从基类继承而来的,派生类对象中包含基类的部分,所以基类的指针或引用可以指向它们。这种机制被称为向上转型。如果你直接用派生类的指针或引用,那你就只能操作具体的派生类对象,无法泛化到多个派生类对象。这就失去了灵活性。而通过基类的指针或引用,你可以操作任何派生自该基类的对象,具有很大的扩展性和灵活性。
  2. 第二派生类必须对基类的虚函数重写 / 覆盖,重写了,派生类才能有不同的函数,多态的不同形态效果才能达到。 

我们把上面例子稍作改动,大家体会一下多态语法的灵活性(把sound()独立到一个普通函数中): 

#include <iostream>
using namespace std;

// 基类
class Animal {
public:
    // 声明为虚函数
    virtual void sound() 
    { 
        cout << "基类中的 sound()" << endl;
    }
};

// 派生类:猫
class Cat : public Animal {
public:
    // 重写虚函数
    void sound()
    {   
        cout << "喵!" << endl;
    }
};

// 派生类:狗
class Dog : public Animal {
public:
    // 重写虚函数
    void sound() 
    { 
        cout << "汪!" << endl;
    }
};
//普通函数
void animalSound(Animal* a)
{
    a->sound();
}
int main() {
    Animal animal; // 基类指针

    Cat cat;
    Dog dog;

    animalSound(&animal);
    animalSound(&cat);
    animalSound(&dog);
    return 0;
}

运行结果:

基类中的 sound()
喵!
汪!

 3.4 虚函数重写的一些其他问题

3.4.1 协变(了解)

  • 概念:当派生类重写基类的虚函数时,如果其返回值类型与基类不同,但具有关联性,即基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或引用,这种现象称为协变
  • 实际意义:协变在实际开发中使用较少,意义并不大,因此只需简单了解即可。

3.4.2 析构函数的重写

        接下来我们重点来关注一下析构函数的重写:

  • 虚析构函数的重要性:基类的析构函数被声明为虚函数时,派生类的析构函数无论是否加上 virtual 关键字,都会与基类的析构函数构成重写。这是因为虽然基类和派生类的析构函数名称不同,但编译器对析构函数进行了特殊处理,最终统一将析构函数的名称处理为 destructor
  • 内存泄漏问题:如果基类的析构函数没有声明为虚函数,那么在使用基类指针指向派生类对象并通过 delete 释放时,只会调用基类的析构函数,而不会调用派生类的析构函数。这会导致派生类中的资源无法正确释放,可能引发内存泄漏。

看以下代码:

次代码为错误示范(不可取)

#include <iostream>
using namespace std;

class A {
public:
    ~A() {
        cout << "A的析构函数被调用" << endl;
    }
};

class B : public A {
public:
    B() {
        _member = new int(10);
    }
    ~B() {
        cout << "B的析构函数被调用" << endl;
    }
private:
    int* _member;
};

int main() {
    B* p1 = new B();
    A* p2 = p1;

    delete p2;  // 仅调用 A 的析构函数,不会调用 B 的析构函数
    return 0;
}

 代码中,基类 A 的析构函数不是虚函数,所以当通过基类指针 p2 删除对象时,只会调用基类 A 的析构函数,而不会调用派生类 B 的析构函数。这意味着 B 类中的 _member 指针所指向的内存不会被释放,从而导致内存泄漏

代码运行结果: 

A的析构函数被调用

为了避免这种情况,我们需要将基类 A 的析构函数声明为虚函数。这样,当通过基类指针删除对象时,会正确调用派生类 B 的析构函数,从而释放所有动态分配的内存。

修改后的正确代码如下:

#include <iostream>
using namespace std;

class A {
public:
    // 将析构函数声明为虚函数
    virtual ~A() 
    {  
        cout << "A的析构函数被调用" << endl;
    }
};

class B : public A {
public:
    B() 
    {
        _member = new int(10);
    }

    ~B() 
    {
        delete _member;  // 释放动态分配的内存
        cout << "B的析构函数被调用" << endl;
    }
private:
    int* _member;
};

int main() {
    B* p1 = new B();
    A* p2 = p1;

    delete p2;  // 现在会调用 B 的析构函数
    return 0;
}

 代码运行结果:

B的析构函数被调用
A的析构函数被调用

【总结】

        在设计类的继承结构时,如果基类有可能会通过基类指针来操作派生类对象,基类的析构函数应该声明为虚函数。这样可以确保正确调用派生类的析构函数,避免内存泄漏和资源未释放的问题。

3.5 override和 final关键字

        在C++中,override 和 final 关键字是用于控制类继承和函数重写行为的两个重要特性。它们在C++11中引入,旨在提高代码的安全性和可维护性。

  • 关键字:override
  • 关键字:final

3.5.1 override

   override 关键字用于明确指示派生类中的虚函数是对基类中虚函数的重写。这有助于编译器进行检查,确保函数签名匹配,避免无意中创建新的虚函数。 

        如果在C++中使用 override 关键字,但函数并没有正确地重写基类中的虚函数,编译器会报错。这是因为 override 关键字告诉编译器该函数应该重写基类中的虚函数,如果没有匹配的基类虚函数,编译器会检测到这一点并发出错误信息。

用法也很简单,只要在派生类重写的函数声明末尾加上关键字即可:

#include <iostream>
using namespace std;

class Base {
public:
    virtual void show() 
    {
        cout << "Base::show()" << endl;
    }
};

class Derived : public Base {
public:
    void show() override         // 重写基类的虚函数加上关键字:override
    {  
        cout << "Derived::show()" << endl;
    }
};

int main() {
    Base* b = new Derived();
    b->show();  // 输出 "Derived::show()"
    delete b;
    return 0;
}

【总结】在我们平常的项目代码编写中是建议在重写虚函数时接入override关键字,有利于提高代码易读性和更容易发现错误。

3.5.2 final

 final 关键字用于防止类被继承或虚函数被重写。它可以应用于类或虚函数。

用法1: 

        防止类被继承: 当一个类被声明为 final 时,表示该类不能被继承。

 示例

class Base final {
public:
    virtual void show()
    {
        cout << "Base::show()" << endl;
    }
};

//次段会报错,错误:无法继承被 final 修饰的类
/**
class Derived : public Base {
public:
    void show() override         // 重写基类的虚函数加上关键字:override
    {
        cout << "Derived::show()" << endl;
    }
};
**/

 用法2: 

        防止虚函数被重写: 当一个虚函数被声明为 final 时,表示该函数不能在派生类中被重写。

示例

#include <iostream>
using namespace std;

class Base {
public:
    virtual void final show()
    {
        cout << "Base::show()" << endl;
    }
};

class Derived : public Base {
public:
/**
    void show() override         // 错误:无法重写被 final 修饰的函数
    {
        cout << "Derived::show()" << endl;
    }
**/
};

3.6  重载/重写/隐藏的对比


四、纯虚函数和抽象类

4.1纯虚函数

纯虚函数

  • 定义:在虚函数的后面写上 =0,则这个函数为纯虚函数。
  • 特点:纯虚函数不需要定义实现,只要声明即可。虽然语法上可以实现,但通常没有意义,因为它们要被派生类重写。

用法如下: 

#include <iostream>
using namespace std;

class A{
public:
    virtual void Function() = 0;  // 纯虚函数,只声明不实现
};

class B: public A{
public:
    void Function() override {
        cout << "B::Function()" << endl;
    }
};
class C: public A{
public:
    void Function() override {
        cout << "C::Function()" << endl;
    }
};


int main() {
    // A a;  // 错误:无法实例化抽象类
    B b;
    b.Function();  // 输出 "B::Function()"

    C c;
    c.Function();  // 输出 "C::Function()"
    return 0;
}

 4.2 抽象类

抽象类

  • 定义:包含纯虚函数的类叫做抽象类。
  • 特点(语法规则)
    • 抽象类不能实例化出对象。
    • 如果派生类继承了抽象类但不重写纯虚函数,那么派生类也是抽象类。
    • 纯虚函数某种程度上强制了派生类重写虚函数,因为不重写就无法实例化对象。

 依然看上面代码:

int main() {
    // A a;  // 错误:无法实例化抽象类
    B b;
    b.Function();  // 输出 "B::Function()"

    C c;
    c.Function();  // 输出 "C::Function()"
    return 0;
}

五、虚函数表

(这部分内容比较接近底层,比较复杂,这里做简单分析、大家别晕没看懂也不太影响平常代码编写)

5.1虚函数概念

基本概念
  • 基类对象的虚函数表:存放基类所有虚函数的地址。
  • 虚函数表共享:同类型对象共享同一个虚函数表,不同类型对象有各自独立的虚函数表。
派生类的构成
  • 组成部分:派生类由继承下来的基类部分和派生类自己的成员组成。
  • 虚函数表指针:通常情况下,继承下来的基类部分包含虚函数表指针,派生类自身不会再生成新的虚函数表指针。
  • 独立性:派生类中继承的基类部分的虚函数表指针与基类对象的虚函数表指针不同,就像基类对象的成员和派生类对象中的基类对象成员是独立的一样。
虚函数的重写
  • 覆盖机制:派生类重写基类的虚函数时,派生类的虚函数表中对应的虚函数地址会被覆盖为派生类重写的虚函数地址。
  • 虚函数表内容:派生类的虚函数表包含基类的虚函数地址、派生类重写的虚函数地址以及派生类自己的虚函数地址。
虚函数表的本质
  • 指针数组:虚函数表本质上是一个存储虚函数指针的指针数组。通常情况下,这个数组的最后一个元素是 0x00000000 标记(C++标准没有规定,各个编译器自行定义,VS系列编译器会在后面放一个 0x00000000 标记,而G++系列编译器不会)。
虚函数的存储位置
  • 代码段:虚函数和普通函数一样,编译后是一段指令,存储在代码段中。虚函数的地址存储在虚函数表中。
虚函数表的存储位置
  • 存储位置:严格来说,C++标准没有规定虚函数表的存储位置。不同编译器可能有不同的实现。例如,在VS编译器中,虚函数表通常存储在代码段(常量区)。
 示例分析
#include <iostream>
using namespace std;

class Base {
public:
    virtual void Func1(){
        cout << "Base::Func1()" << endl;
    }
    virtual void Func2() {
        cout << "Base::Func2()" << endl;
    }
    virtual void Func3() {
        cout << "Base::Func3()" << endl;
    }
    virtual void Func4() {
        cout << "Base::Func4()" << endl;
    }
private:
    int a;
};

class Derived : public Base {
public:

    void Func2() override         
    {
        cout << "Derived::Func2()" << endl;
    }
};

int main() {
    Base b;
    cout << sizeof(b) << endl;

    Derived d;
    cout << sizeof(d);

    return 0;
}

 运行结果:

8
8

运行结果分析

  1. 类 Base 的内存占用

    • Base 类有一个私有成员变量 int a,占用 4 字节(假设 int 类型在你的系统上占用 4 字节)。
    • 由于 Base 类包含虚函数,编译器会为每个 Base 类对象添加一个虚函数表指针(vptr),这个指针指向该类的虚函数表。虚函数表指针的大小通常是指针的大小,在大多数系统上是 4 字节(32位系统)或 8 字节(64位系统)。
  2. 类 Derived 的内存占用

    • Derived 类继承了 Base 类,并重写了 Func2 虚函数。
    • Derived 类对象也包含一个虚函数表指针(vptr),指向 Derived 类的虚函数表。
    • Derived 类对象的大小包括继承自 Base 类的成员变量和虚函数表指针。

下面通过图形来更直观的理解:


 

  5.2虚函数表和虚基表的区别(了解)

虚函数表(vtable)
  • 用途:用于支持多态性和动态绑定。它的主要作用是实现对虚函数的动态分派,即在运行时根据对象的实际类型调用正确的函数。
  • 工作原理
    • 当一个类包含虚函数时,编译器为该类生成虚函数表。
    • 虚函数表是一个指针数组,表中的每个指针指向该类的虚函数实现。
    • 每个类实例都有一个虚表指针(vptr),指向它所属类的虚函数表。
    • 在继承中,派生类可以重写基类的虚函数,派生类的虚函数表会更新,指向派生类的实现。
    • 通过基类指针或引用调用虚函数时,虚表指针用于查找实际对象的虚函数表,以调用正确的函数。

虚基表(vbtable)
  • 用途:用于解决多重继承中虚基类的共享问题,确保在多重继承中,虚基类只被构造一次,并且被派生类共享。
  • 工作原理
    • 当派生类通过虚继承(virtual 关键字)从基类继承时,编译器为派生类生成虚基表。
    • 虚基表中存储了虚基类在内存中的偏移量,确保派生类能够正确访问虚基类的成员。
    • 虚基表帮助派生类管理对虚基类的共享访问,保证虚基类只会被初始化和析构一次。

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

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

相关文章

swagger2.9.2 和 springboot3.3.4版本冲突问腿

swagger2.9.2 和 springboot3.3.4版本冲突问腿 问题描述&#xff1a;当我们使用 swagger 2.9.2版本的时候&#xff0c;如果恰好我们使用的 springboot 版本是3.x版本&#xff0c;会出现启动报错的问题 解决办法&#xff1a;直接使用swagger 3.x 版本和 springboot 3.x 版本 …

window 安装永洪BI Desktop版本教程

本教程基于永洪BI Desktop 10.2 一、下载软件包 &#xff08;下载需要注册&#xff0c;以便接收License邮件激活码&#xff09;&#xff0c;地址如下&#xff1a;桌面智能数据分析工具_vividime Desktop数据分析软件-永洪科技vividime Desktop是一款轻量级桌面智能数据分析工具…

【探测器】线阵相机中的 TDI 技术

【探测器】线阵相机中的 TDI 技术 1.背景2.TDI相机3.场景应用 1.背景 TDI 即Time Delay Integration时间延迟积分。 TDI相机是线阵相机的一种特殊类型&#xff0c;带有独特的时间延迟积分&#xff08;TDI&#xff09;技术。 换句话说&#xff0c;TDI相机是线阵相机的一个高级版…

HCIP-HarmonyOS Application Developer 习题(七)

&#xff08;判断&#xff09;1、HarmonyOs跨端迁移和多端协同&#xff0c;是使用不同的FA/PA&#xff0c;在不同设备间运行来实现完整的业务。 答案&#xff1a;错误 分析&#xff1a; &#xff08;判断&#xff09;2、HarmonyOs的方舟开发框架包含基于TS扩展的类Web开发范式…

【RPC】—Thrift协议 VS Protobuf

Thrift协议 & VS Protobuf ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记仓库&#x1f449;https://github.com/A-BigTree/tree-learning-notes 个人主页&#x1f449;https://www.abigtree.top ⭐⭐⭐⭐⭐⭐ 文章目录 Thrift协议 & VS Pro…

云原生(四十九) | WordPress源码部署

文章目录 WordPress源码部署 一、WordPress部署步骤 二、创建项目目录 三、上传源码到WordPress 四、配置安全组 五、配置WordPress 六、访问WordPress WordPress源码部署 一、WordPress部署步骤 第一步&#xff1a;创建项目目录 第二步&#xff1a;上传源码到项目目…

ARM(5)内存管理单元MMU

一、虚拟地址和物理地址 首先&#xff0c;计算机系统的内存被组成一个由M个连续的字节大小组成的数组。每字节都会有一个唯一的物理地址。CPU访问内存最简单的方式就是使用物理地址。如下图&#xff1a; 图 1 物理地址,物理寻址 而现在都是采用的都是虚拟寻址的方法。CPU生成一…

51单片机的自动制冷系统【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块温度传感器继电器LED、按键和蜂鸣器等模块构成。适用于车载便携式自动制冷系统、冰箱制冷、温度控制等相似项目。 可实现功能: 1、LCD1602实时显示当前温度 2、温度传感器DS18B20采集温度 3、按键可设置温度的阈…

【开发心得】筑梦上海:项目风云录(6)

目录 会海跳槽 票务开启 漂泊在外的日子 未完待续 会海跳槽 随着时刻表的出炉&#xff0c;意味着大规模的界面开发逐步进入正规。项目组里陆陆续续引进了8个人&#xff0c;最多的时候&#xff0c;同时有10个人在现场。“松工”为我们准备的办公室坐的满满当当&#xff0c;…

攸信动态丨厦门火炬大学堂携手厦门攸信技术,共探盈趣汽车电子数字化转型标杆之路

今日上午&#xff0c;在厦门市工信局指导下&#xff0c;由厦门盈趣汽车电子有限公司、厦门攸信信息技术有限公司携手北京赛昇科技有限公司与厦门火炬大学堂联合举办的“厦门中小企业数字化转型人才培训&#xff08;第14期&#xff09;”活动&#xff0c;在热烈而充实的氛围中圆…

gitlab-ci 集成 k3s 部署spring boot 应用

环境 一台ECS gitlab 16.10 一台ECS gitlab-runner docker方式 一台腾讯云服务器 k3s k3s version v1.30.5k3s1 (9b586704) go version go1.22.6 本地: idea 2024 准备开始 gitlab上创建"api"仓库,本地IDEA 创建spring boot web demo项目k8s-gitlab-demo. 确保能…

手把手带你服务端实现支付功能的通用解决方案!(全网最新)

友情提示&#xff1a; 跳转到本人juejin观看体验更佳&#xff08;当然CSDN也很好&#x1f60b;&#xff09; link —> https://juejin.cn/user/679936123997707/posts Thanks!&#x1f339; 前言 前段时间&#xff0c;和朋友们一起搭建的一个网站需要实现支付功能&#xff…

速卖通、Shopee、Lazada自养号测评的五大关键步骤:从环境构建到风控应对

在跨境电商领域&#xff0c;速卖通、Shopee和Lazada等平台上的自养号测评已成为提升销量、优化产品排名的重要手段。自养号测评不仅能够帮助卖家快速积累好评&#xff0c;还能有效提升产品的曝光率和转化率。然而&#xff0c;自养号测评并非易事&#xff0c;需要掌握一系列专业…

软考UML图 -- ( 类图,对象图,用例图,序列图,通信图,状态图,活动图,构件图,部署图)

文章目录 一、UML统一建模语言二、关系三、UML图1. 类图2. 对象图3. 用例图4. 序列图&#xff08;顺序图&#xff09;—— 交互图5. 通信图 —— 交互图6. 状态图7. 活动图8. 构件图&#xff08;组件图&#xff09;9. 部署图10. 总结 一、UML统一建模语言 UML由3个要素构成:UM…

Linux 外设驱动 应用 1 IO口输出

从这里开始外设驱动介绍&#xff0c;这里使用的IMX8的芯片作为驱动介绍 开发流程&#xff1a; 修改设备树&#xff0c;配置 GPIO1_IO07 为 GPIO 输出。使用 sysfs 接口或编写驱动程序控制 GPIO 引脚。编译并测试。 这里假设设备树&#xff0c;已经配置好了。不在论述这个问题…

PDF转JPG神器!这四款软件让你轻松搞定文档转换

尊敬的朋友们&#xff0c;您是否曾在数字办公和娱乐的海洋中&#xff0c;遭遇过因格式问题而一筹莫展的时刻&#xff1f;比如&#xff0c;手头有一份绝美的PDF文件&#xff0c;却想将其转换为JPG图片格式&#xff0c;好让它能在你的社交圈中大放异彩&#xff1b;别急&#xff0…

自然语言处理-语言转换

文章目录 一、语言模型二、统计语言模型1.含义与方法2.存在的问题 三、神经语言模型1.含义与方法2.one-hot编码3.词嵌入-word2vec4.模型的训练过程 四、总结 自然语言处理&#xff08;NLP&#xff09;中的语言转换方法主要涉及将一种形式的语言数据转换为另一种形式&#xff0c…

IDEA创建、导入、删除maven项目

全局配置&#xff1a; 1.File->Close Project 2.Customize->All settings 3. Apply 4.选择JRE版本->Apply 5.选择字节码版本->Apply->OK 全局配置结束 创建maven项目&#xff1a; 1.File->New->Module 2.Build system选择Maven GroupId&#xff1a…

Django学习笔记十三:优秀案例学习

Django CMS 是一个基于 Django 框架的开源内容管理系统&#xff0c;它允许开发者轻松地创建和管理网站内容。Django CMS 提供了一个易于使用的界面来实现动态网站的快速开发&#xff0c;并且具有丰富的内容管理功能和多种插件扩展。以下是 Django CMS 的一些核心特性和如何开始…

智能医疗:Spring Boot医院管理系统开发

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常适…