【C++】——继承详解

目录

1、继承的概念与意义

2、继承的使用

2.1继承的定义及语法

2.2基类与派生类间的转换

2.3继承中的作用域

2.4派生类的默认成员函数

<1>构造函数

<2>拷贝构造函数

<3>赋值重载函数

<4析构函数

<5>总结

3、继承与友元

4、继承与静态变量

5、菱形继承及菱形虚拟继承

6、继承与组合


1、继承的概念与意义

什么是继承?

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称派生类。

继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复用,继承是类设计层次的复用。

通过继承联系在一起的类构成了一种层次关系,在这种关系中有一个基类(base class),其他类则是直接或间接地从基类继承过来的,这些继承来的类可以称为派生类(drived class)。基类通常有着层次关系中所有类共同拥有维护的成员,而每个派生类也有着自己各自特定的成员。

一个简单的例子:一个学习管理系统,那么成员必定有学生,老师等等,这些是身份,归根到底是个人(基类)包含着名字、年龄、地址等基础信息。这些需要共同维护的就是基类的成员。

//共同维护的成员部分->基类
class Person
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 void identity(){cout << "void identity()" << _name << endl;}
protected:string _name = "qsy"; // 姓名 string _address; // 地址 string _tel; // 电话 int _age = 18; // 年龄 
};class Student : public Person
{
public:// 学习 void study(){// ...}
protected:int _stuid; // 学号 
};
class Teacher : public Person
{
public:// 授课 void teaching(){//...}
protected:string title; // 职称 
};
int main()
{Student s;Teacher t;s.identity();t.identity();return 0;
}

可以看到派生类可以访问基类成员

如果没有继承这种结构关系的话 Student和Teacher 都有姓名/地址/ 电话/年龄等成员变量,都有identity身份认证的成员函数,设计到两个类里面就是冗余的。更好地体现了继承是类设计层次的复用。

2、继承的使用

2.1继承的定义及语法

这就是继承的语法格式

继承方式与访问限定符号一样有着三种,不同的继承方式与不同的类成员组合会是不同的情况

总结一下规律:

<1>基类 private 成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

将年龄变为私有验证一下是否继承到了派生类对象

可以看到继承下来了但是不可以访问!!

<2>如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为 protected。可以看出保护成员限定符是因继承才出现的。

如果想要访问 private 成员可以在基类中成员函数访问,这样派生类可以间接访问到 private成员

<3>基类的私有成员在派生类都是不可见。基类的其他成员在派生类的访问方式 == Min(成员在基类的访问限定符,继承方式),public  > protected > private。

Tip:class默认继承方式是 private,struct默认继承方式是public。最好显示写出继承方式

<4>在实际运用中⼀般使用都是 public 继承,几乎很少使用 protetced/private 继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实 际中扩展维护性不强。

2.2基类与派生类间的转换

基类与派生类之间是否有着类型的转换呢?

答案是可以的! public继承中有一个 is-a 概念:每个派生类都是一个特殊的基类对象

• public继承的派生类对象 可以赋值给 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中基类那部分切出来,基类指针或引用指向的是派生类中切出来的基类那部分。

基类对象不能赋值给派生类对象

基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type  Information)的dynamic_cast 来进行识别后进行安全转换。

2.3继承中的作用域

继承体现中也有各自的作用域规则并且引出来一个隐藏概念,隐藏影响的只是编译器查找规则

1. 在继承体系中基类和派生类都有独立的作用域。

2. 派生类和基类中有同名成员,派生类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。 (在派生类成员函数中,可以使用 基类::基类成员显示访问)

3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。(区分重载)

4. 注意在实际中在继承体系里面最好不要定义同名的成员。

// Student的_num 和 Person的_num 构成隐藏关系,可以看出这样代码虽然能跑,但是⾮常容易混淆 
class Person
{
protected:string _name = "小徐"; // 姓名 int _num = 111; // ⾝份证号 
};
class Student : public Person
{
public:void Print(){cout << " 姓名:" << _name << endl;cout << " 身份证号:" << Person::_num << endl;cout << " 学号:" << _num << endl;}
protected:int _num = 999; // 学号 
};
int main()
{Student s1;s1.Print();return 0;
};

访问的是哪个 _num 呢?

可以看到派生类成员隐藏了基类的同名成员,直接访问了派生类的 _num 

同理,函数也有隐藏的现象

A和B类中的 fun 两个函数构成什么关系呢??根据前面的知识可以知道继承体系中函数名相同就构成隐藏关系

2.4派生类的默认成员函数

6个默认成员函数,默认的意思就是指我们不写,编译器会自动生成⼀个,那么在派生类中,这 几个成员函数是如何生成的呢?

四个常见默认成员函数:

<1>构造函数

派生类的构造函数必须调用基类的构造函数初始化基类的那⼀部分成员。

class Person
{
public:Person(const char* name="xxc") //全缺省函数,默认构造:_name(name){cout << "Person()" << endl;}
protected:string _name;//姓名
};class Student :public Person
{
public://不显示实现默认构造,编译器生成的// 1. 内置类型->不确定// 2. 自定义类型->调用自定义类型的显示写的默认构造// 3. 基类成员看作一个整体,要求调用基类的默认构造
protected:int _num;//学号string _addrss;//地址
};int main()
{Student s1;return 0;
}

如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用

写一个 Student的构造函数

还是报错!前面提到 需要把基类成员当成一个对象调用基类的构造函数

如何实现一个不能被继承的类呢?

方法1:基类的构造函数私有,派生类的构成必须调用基类的构造函数,但是基类的构成函数私有化以后,派生类看不见就不能调用了,那么派生类就无法实例化出对象。

方法2:C++11新增了⼀个final关键字,final 修改基类,派生类就不能继承了。

<2>拷贝构造函数

派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。

可以看到,没有资源申请的时候 Student 并不需要自己显示实现拷贝构造,因为编译器默认拷贝构造会调用基类的拷贝构造

那么怎么自己实现拷贝构造呢?(Tip:基类对象是最先声明(内存顺序)的,初始化列表中第一个初始化)

Person(const Person& p): _name(p._name)
{cout << "Person(const Person& p)" << endl;
}Student(const Student& s):Person(s),_num(s._num),_addrss(s._addrss)
{//深拷贝
}

Person(s) 这个 s 是派生类对象的引用为什么可以传给基类呢? 涉及基类与派生类间的转换概念——切片 

如果显示写了拷贝构造但是不显示调用基类的拷贝构造的会,编译器会自动调用默认构造而非调用基类的拷贝构造

补充一下缺省值构成默认构造,运行一下发现调用的就是默认构造而非拷贝构造

<3>赋值重载函数

派生类的operator=必须要调用基类的operator=完成基类的复制。

赋值重载与拷贝构造类似一般编译器默认生成的就已经够用了,如果有资源申请的话才需要显示实现

Student& operator=(const Student& s)
{if (this != &s){operator=(s);//派生类切片基类成员_num = s._num;_addrss = s._addrss;}return *this;
}

栈溢出,无限递归调用,我们不是想要调用基类的赋值函数吗?为什么调用了派生类的呢?

需要注意的是派生类的 operator= 隐藏了基类的operator= ,所以显示调用基类的operator= ,需要指定基类作用域

Student& operator=(const Student& s)
{if (this != &s){//基类和派生类的赋值构成了隐藏关系 需要指定作用域Person::operator=(s);//派生类切片基类成员_num = s._num;_addrss = s._addrss;}return *this;
}

<4析构函数

析构函数可以显示调用,那么可以在派生类显示调用基类的析构函数来清理基类成员

可是为什么调不动呢?这里派生类和基类的析构函数构成了隐藏关系

因为多态中⼀些场景析构函数需要构成重写,重写的条件之一是函数名相同,那么编译器会对析构函数名进行特殊处理,处理成destructor(),所以基类析构函数不加 virtual的情况下,派生类析构函数和基类析构函数构成隐藏关系。

想要调用就标明作用域:

Person::~Person()

但是像上述这样写,会有一个问题,基类的析构会调用两次!!!

其实,派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。所以我们不必在派生类的析构函数中进行调用基类的析构函数,不然就会重复释放同一块空间,导致报错!

可以怎么理解派生类析构自动调用基类的析构呢? 先子后父保证析构顺序!显示调用不一定保证先子后父的析构顺序

<5>总结

派生类和基类的层次关系逻辑基础还是类和对象

派生类的默认成员函数的注意事项:

<1>派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。


<2>派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。


<3>派生类的operator=必须要调用基类的operator=完成基类的复制。


<4>派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员(不需要显示和调用基类析构)。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。


<5>派生类对象初始化先调用基类构造再调派生类构造。派生类对象析构清理先调用派生类析构再调基类的析构。


<6>因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系

3、继承与友元

友元关系不能继承,也就是说基类友元不能访问派生类私有和保护成员。比如爸爸的朋友可以说是你的朋友吗?


class Student;//前置声明
class Person
{
public:friend void Display(const Person& p, const Student& s);//需要前置声明否则报错招不到 Student
protected:string _name; // 姓名 
};
class Student : public Person
{
protected:int _stuNum; // 学号 
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl;
}
int main()
{Person p;return 0;
}

如果访问派生类的私有和保护成员呢?

可以看见是不可访问的 在派生类同样设置一个友元就可以解决这个问题了。

4、继承与静态变量

基类定义了 static 静态成员,则整个继承体系里面只有⼀个这样的成员。无论派生出多少个派生类,都只有⼀个static 成员实例。

验证一下:

class A
{
public: static int _a;int _aa;
};
class B :public A
{
public:int _b;
};
// static int _a = 1;报错int A::_a = 1;//注意定义的方式
int main()
{A  a;B b1;B b2;//这⾥的运行结果可以看到非静态成员_aa的地址是不⼀样的// 说明派生类继承下来了,⽗类派生类对象各有⼀份 cout << &a._aa << endl;cout << &b1._aa << endl;cout << endl;// 这⾥的运行结果可以看到静态成员 _a 的地址是⼀样的 //说明派生类和基类共用同⼀份静态成员 cout << &a._a << endl;cout << &b1._a << endl;cout << &b2._a << endl;cout << endl;//公有情况下 基类派生类都可以访问静态成员变量cout << a._a << endl;cout << b1._a << endl;cout << b2._a << endl;return 0;
}

也就说明他们共用一个_a变量,所以无论派生出多少个子类,都只有一个static成员实例

这个特性可以带来一种思路统计实例化类的数量个数,只需在构造函数中加入一个增加该静态变量的语句即可:

class Person
{
public:Person() { ++_count; }//子类的构造会调用父类构造
protected:string _name; // 姓名
public:static int _count; // 统计人的个数。
};
int Person::_count = 0;class Student : public Person
{
protected:int _stuNum; // 学号
};
int main()
{Student s1;Student s2;Student s3;cout << " 人数 :" << Person::_count << endl;Student::_count = 0;cout << " 人数 :" << Person::_count << endl;return 0;
}

这样我们就可以知道该继承体系中实例化了多少个类了!!!

5、菱形继承及菱形虚拟继承

首先声明一下,由于C++的历史缘故,其一致行走在语言发展的前端,一直在尝试新的内容。在发展过程中,有些内容加入到C++的时候,还没有发现其弊端。而后来发现的时候,为了向上兼容,只能打补丁,所以不开避免的不会有一些弊端,会有复杂的语法和复杂的特性。总要有先驱者走前前面,而C++就是!!!

单继承:⼀个派生类只有⼀个直接基类时称这种继承关系为单继承

多继承:⼀个派生类有两个或以上直接基类时称这种继承关系为多继承多继承对象在内存中的模型是,先继承的基类在前面,后面继承的基类在后面,派生类成员在放到最后面。

菱形继承:菱形继承是多继承的⼀种特殊情况。

菱形继承的问题,从上面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题,在Assistant的对象中Person成员会有两份。

class Person
{
public:string _name; // 姓名 
};
class Student : public Person
{
protected:int _num; //学号 
};
class Teacher : public Person
{
protected:int _id; // 职⼯编号 
};
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程 
};
int main()
{// 编译报错:error C2385: 对“_name”的访问不明确 二义性Assistant a;a._name = "peter";// 需要显⽰指定访问哪个基类的成员可以解决⼆义性问题,但是数据冗余问题⽆法解决 a.Student::_name = "xxx";a.Teacher::_name = "yyy";return 0;
}

那该如何解决数据冗余的问题呢??可以借用虚拟继承!!

虚拟继承(virtual)可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在StudentTeacher的继承Person时使用虚拟继承,即可解决问题。

这是什么原理呢?测试一下!

菱形继承不虚拟继承的情况

#include<iostream>
#include<string>using namespace std;class A
{
public:int _a;
};class B : public A
//class B : virtual public A
{
public:int _b;
};class C : public A
//class C : virtual public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}

调试一下:

通过这个逐语句调试的内存变化,我们可以确定大致的内存情况:

不使用虚拟继承就是这样的内存情况,也好理解为什么同名变量的两份是如何储存的了。
接下来我们来看虚拟继承下的菱形继承是怎么个情况:

内存分布:

a储存在最下面,而B,C部分的原有储存_a的位置现在是什么呢???
其实是个指针,那我们来看看指针指向的空间储存着什么吧:

???怎么对应位置是00 00 00 00为什么是零?往下看看:
分别储存着16进制数字14 0c转换为10进制数字20 12,然后对应B,C原本的指针位置(006FFB6C)加上这个值(偏移量),都会指向到A _a的空间!!!这个00 00 00 00到多态的部分再来进行讲解,知道原地址加上下面的值就是A _a的空间就可以了!!!

这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。
即原本B,C中_a的位置储存这一个指针,指针指向的位置有一个偏移量,原位置的地址加上偏移量就会指向A的空间!!!

那这样进行拷贝切片的时候是怎样的呢?一样是把D中B对象的部分切片,然后通过虚基表的方式来找到_a。但这样也带来了一些代价:(PS:内存中的储存顺序就是声明的顺序,先继承谁,谁就在前面)

多继承指针偏移问题(切片)

p1和p2指向哪里呢???

内存分布中,先继承的放前面!

因为切片的概念p2指向 base2开始但是只能看见 base2 那一部分

6、继承与组合

  • public继承是一种is-a(谁是什么)的关系。也就是说每个派生类对象都是一个基类对象。
  • 组合是一种has-a(谁有什么)的关系。假设B组合了A,每个B对象中都有一个A对象(也就是把A作为B的成员变量)
  • 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse 能看见,不安全,耦合度高)。术语 “白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。


  • 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse 不能能看见,安全,耦合度低),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。

有关继承的经典面试题
<1>C++有多继承,为什么java等语言没有?
历史原因!C++是先驱者(人的直觉认为多继承很合理,我感觉正常人都会想到多继承),并且c++中的多继承处理起来十分复杂,访问基类变量的过程就会很复杂!!!java等后来发展的语言见到c++中多继承的复杂,就干脆放弃了。

<2>什么是菱形继承?多继承的问题是什么?
菱形继承如字面意思(两个父类的父类是同一个类就会发生菱形继承),多继承本身没什么问题,真正的问题是有多继承就可能发生菱形继承。菱形继承就有问题了:变量的二义性和继承冗杂。解决办法很简单就是虚拟继承,但是这样就会大大降低效率。

<3>继承和组合的区别?什么时候用继承?什么时候用组合?
继承:通过扩展已有的类来获得新功能的代码复用方法
组合:新类由现有类的对象合并而成的类的构造方式

如果二者间存在一个“是”的关系,并且一个类要对另外一个类公开所有接口,那么继承是更好的选择
如果二者间存在一个“有”的关系,那么首选组合
!能用组合就用组合!!!能用组合就用组合!!!能用组合就用组合!!!

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

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

相关文章

蓝桥杯—STM32G431RBT6按键的多方式使用(包含软件消抖方法精讲)从原理层面到实际应用(一)

新建工程教程见http://t.csdnimg.cn/JySLg 点亮LED教程见http://t.csdnimg.cn/Urlj5 末尾含所有代码 目录 按键原理图 一、按键使用需要解决的问题 1.抖动 1.什么是抖动 2.抖动类型 3.如何去消除抖动 FIRST.延时函数消抖&#xff08;缺点&#xff1a;浪费CPU资源&#xff…

Python(TensorFlow和PyTorch)及C++注意力网络导图

&#x1f3af;要点 谱图神经网络计算注意力分数对比图神经网络、卷积网络和图注意力网络药物靶标建模学习和预测相互作用腹侧和背侧皮质下结构手写字体字符序列文本识别组织病理学图像分析长短期记忆财务模式预测相关性生物医学图像特征学习和迭代纠正 Python注意力机制 对…

深度学习Day-33:Semi-Supervised GAN理论与实战

&#x1f368; 本文为&#xff1a;[&#x1f517;365天深度学习训练营] 中的学习记录博客 &#x1f356; 原作者&#xff1a;[K同学啊 | 接辅导、项目定制] 一、 基础配置 语言环境&#xff1a;Python3.8编译器选择&#xff1a;Pycharm深度学习环境&#xff1a; torch1.12.1c…

3 种自然语言处理(NLP)技术:RNN、Transformers、BERT

自然语言处理 (NLP) 是人工智能的一个领域&#xff0c;旨在使机器能够理解文本数据。NLP 研究由来已久&#xff0c;但直到最近&#xff0c;随着大数据和更高计算处理能力的引入&#xff0c;它才变得更加突出。 随着 NLP 领域的规模越来越大&#xff0c;许多研究人员都试图提高…

【 html+css 绚丽Loading 】000051 方寸轮回矩

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f…

蓝桥杯-基于STM32G432RBT6的LCD进阶(LCD界面切换以及高亮显示界面)

目录 一、页面切换内容详解 1.逻辑解释 2.代码详解 code.c&#xff08;内含详细讲解&#xff09; code.h main.c 3.效果图片展示 ​编辑 二、页面选项高亮内容详解 1.逻辑解释 2.读入数据 FIRST.第一种高亮类型 code.c&#xff08;内含代码详解&#xff09; code.…

[000-01-008].第05节:OpenFeign特性-重试机制

我的后端学习大纲 SpringCloud学习大纲 1.1.重试机制的默认值&#xff1a; 1.重试机制默认是关闭的&#xff0c;给了默认值 1.2.测试重试机制的默认值&#xff1a; 1.3.开启Retryer功能&#xff1a; 1.修改配置文件YML的配置&#xff1a; 2.新增配置类&#xff1a; packa…

大模型时代:普通人如何获利

随着人工智能技术的飞速发展&#xff0c;我们正步入一个以大模型为驱动力的新时代。这些大型语言模型&#xff0c;如GPT-3和BERT&#xff0c;已经在各个领域展现出惊人的能力&#xff0c;包括文本生成、翻译、问答等。这些技术的进步不仅改变了我们的生活&#xff0c;也为普通人…

【ACM出版】第三届人工智能与智能信息处理国际学术会议(AIIIP 2024,10月25-27)

第三届人工智能与智能信息处理国际学术会议&#xff08;AIIIP 2024&#xff09; 2024 3rd International Conference on Artificial Intelligence and Intelligent Information Processing 中国-天津 | 2024年10月25-27日 | 会议官网&#xff1a;www.aiiip.net 官方信息 会议…

Redis常用操作及springboot整合redis

1. Redis和Mysql的区别 数据模型&#xff1a;二者都是数据库,但是不同的是mysql是进行存储到磁盘当中,而Redis是进行存储到内存中. 数据模型 : mysql的存储的形式是二维表而Redis是通过key-value键值对的形式进行存储数据. 实际的应用的场景: Redis适合于需要快速读写的场景&…

[Linux]:进程间通信(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;Linux学习 贝蒂的主页&#xff1a;Betty’s blog 1. system V通信 前面我们所探究的通信方式都是基于管道文件的&#xff0c;而…

深入解析代理模式:静态代理、JDK 动态代理和 CGLIB 的全方位对比!

代理模式&#xff08;Proxy Pattern&#xff09;是一种结构型设计模式&#xff0c;它提供了对象的替身&#xff0c;即代理对象来控制对实际对象的访问。通过代理对象&#xff0c;可以在不修改目标对象的情况下&#xff0c;扩展或控制其功能。例如&#xff0c;代理模式可以用于延…

Cortex-A7的GIC(通用中断控制器):边沿触发和电平触发中断的区别

0 资料 ARM Generic Interrupt Controller Architecture version 2.0 Architecture Specification1 边沿触发和电平触发中断的区别 1.1 边沿触发和电平触发中断官方解释 边沿触发&#xff08;Edge-triggered&#xff09; This is an interrupt that is asserted on detectio…

DFS:深搜+回溯+剪枝实战解决OJ问题

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一 排列、子集问题 1.1 全排列I 1.2 子集I 1.3 找出所有子集的异或总和 1.4 全排列II 1.5 字母大小写全排列 1.6 优美的排列 二 组合问题 2.1 电话号码的数字组合 …

物联网架构

1 三层架构 三层架构就像我们拿着一个设备&#xff0c;通过网络直接连接到服务器获取结果&#xff0c;步骤简单。 举个例子&#xff1a;智能家居的温度监控系统 1. 感知层&#xff08;设备与传感器&#xff09; 在智能家居系统中&#xff0c;温度传感器被安装在家里的各个房间…

战斗机检测系统源码分享

战斗机检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Visio…

4.提升客户服务体验:ChatGPT在客服中的应用(4/10)

本文大纲旨在指导撰写一篇全面探讨ChatGPT如何通过优化客户服务流程、提供实际应用案例和用户反馈&#xff0c;以提升客户服务体验的深入博客文章。 引言 在当今竞争激烈的商业环境中&#xff0c;客户服务已成为企业成功的关键因素。优质的客户服务不仅能够增强客户满意度和忠…

第十一章 【后端】商品分类管理微服务(11.1)——创建父工程

第十一章 【后端】商品分类管理微服务 11.1 创建父工程 项目名称:EasyTradeManagerSystem:Easy 表示简单易用,Trade 表示交易,Manager 表示管理,System 表示系统,强调系统在商品交易管理方面的便捷性,简称 etms。 新建工程 yumi-etms yumi-etms 作为所有模块的父工程,…

1.使用 IDEA 过程中的英语积累 - File 菜单(每一次重点积累 5 个单词)

前言 学习可以不局限于传统的书籍和课堂&#xff0c;各种生活的元素也都可以做为我们的学习对象&#xff0c;本文将利用 IDEA 页面上的各种英文元素来做英语的积累&#xff0c;如此做有 3 大利 这些软件在我们工作中是时时刻刻接触的&#xff0c;借此做英语积累再合适不过&…

QT + WebAssembly + Vue环境搭建

Qt6.7.2安装工具 emsdk安装 git clone https://github.com/emscripten-core/emsdk.git cd emsdk emsdk install 3.1.50 emsdk activate 3.1.50 Qt Creator配置emsdk 效果 参考 GitHub - BrockReece/vue-wasm: Vue web assembly loader Emscripten cmake多版本编译-CSDN博客 …