当前位置: 首页 > news >正文

【C++】多态详细讲解

        本篇来聊聊C++面向对象的第三大特性-多态。

 1.多态的概念

        多态通俗来说就是多种形态。多态分为编译时多态(静态多态)运⾏时多态(动态多态)

  • 编译时多态:主要就是我们前⾯讲的函数重载函数模板,他们传不同类型的参数就可以调⽤不同的函数,通过参数不同达到多种形态
  • 运⾏时多态:具体点就是去完成某个⾏为(函数),可以传不同的对象就会完成不同的⾏为,就达到多种形态。

 我们重点要说的是运行时多态。运行时多态举个例子:买票的行为,学生买票是学生票,普通人买票是全价票,军人买票又是优先票...这就是买票行为的多种形态。

2.多态的定义及实现

2.1 多态的构成条件

多态是⼀个 继承关系下 的类对象,去调⽤ 同⼀函数 ,产⽣了 不同的⾏为 。⽐如Student继承了
Adult 。Adult 对象买票全价,Student对象优惠买票。

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

  • 必须是基类的指针或者引⽤调⽤虚函数
  • 被调⽤的函数必须是虚函数,并且完成了虚函数重写/覆盖
说明:要实现多态效果,第⼀必须是基类的指针或引⽤,因为只有基类的指针或引⽤才能既指向基类 对象⼜指向派⽣类对象;第⼆派⽣类必须对基类的虚函数完成重写/覆盖,重写或者覆盖了,基类和派 ⽣类之间才能有不同的函数,多态的不同形态效果才能达到。

2.2 虚函数

成员函数前⾯ virtual 修饰,那么这个成员函数被称为虚函数。 (注意⾮成员函数不能加virtual修饰)
class Adult  //基类
{ 
public:virtual void BuyTicket(){cout << "全价票" << endl;}
};

virtual在继承里也遇到过,在继承里出现是为了解决菱形继承问题,跟多态里virtual意义不同。

2.3 虚函数的重写/覆盖

派⽣类中有⼀个跟基类 完全相同 (返回值类型、函数名字、参数列表的类型完全相同) 的虚函数,称派⽣类的虚函数重写了基类的虚函数。
class Student : public Adult //派生类
{
public:virtual void BuyTicket(){cout << "学生票" << endl;}
};
注意:在重写基类虚函数时, 派⽣类的 虚函数在 不加virtual 关键字时,也 可以构成重写 (因为继承 后基类的虚函数被继承下来了在派⽣类依旧保持虚函数属性), 但是该种写法不是很规范 ,不建议这样 使⽤。

2.3.1 多态的实例

基类和派生类还是前面的Adult和Student,现在这里有个Ticket函数。
void Ticket(Adult* pa) //基类的指针
{pa->BuyTicket();
}

前面说过,多态的第一个条件,必须是基类的指针或者引⽤调⽤,这里的pa满足;第二个条件,被调用函数必须是虚函数而且进行了虚函数的重写/覆盖,这里的BuyTicket函数满足条件。

int main()
{Adult a;   //基类对象Student s; //派生类对象Ticket(&a);Ticket(&s);return 0;
}

传不同的对象过去,得到的结果不同。

成功构成了多态。
引用的写法如下。
void Ticket(Adult& pa) //基类的引用
{pa.BuyTicket();
}int main()
{Adult a;   //基类对象Student s; //派生类对象Ticket(a);Ticket(s);return 0;
}

再加一个派生类也是可以的。
class Soldier : public Adult //派生类
{
public:virtual void BuyTicket(){cout << "军人票" << endl;}
};

然后只用Student和Soldier这两个派生类。

int main()
{Student st; //派生类对象Soldier so; //派生类对象Ticket(st);Ticket(so);return 0;
}

2.3.2 对实例进一步解释

1.为什么一定是基类的指针或引用?在继承中我们说过派生类和基类的切片/切割问题,参数类型是基类可以保证实参传基类的对象也可以,传派生类的对象也可以。如果是派生类的指针或引用,基类对象根本传不过去。

2.对于Ticket函数的参数列表,按照我们在继承中的说法,不管传什么过去,调用的都是Adult的成员函数,因为此时pa是由参数类型决定的,与传过来的对象无关,但是,在多态中,pa与自己的类型无关,只与传过来的对象有关,指向谁,调用谁。

3.构成多态的两个条件必须都满足。

如果不满足其中一个条件,比如基类没有虚函数。

class Adult //基类
{
public:void BuyTicket() //没有虚函数{cout << "全价票" << endl;}
};class Student : public Adult //派生类
{
public:virtual void BuyTicket(){cout << "学生票" << endl;}
};void Ticket(Adult* pa) //基类的指针或引用
{pa->BuyTicket();
}int main()
{Adult a;   //基类对象Student s; //派生类对象Ticket(&a);Ticket(&s);return 0;
}

此时不构成多态,pa就走继承那套逻辑,不管传什么都是调用基类的。

如果都是虚函数并且进行了虚函数的重写/覆盖,但是Ticket参数类型不是基类的指针或引用,依旧是不构成多态的。

class Adult //基类
{
public:virtual void BuyTicket(){cout << "全价票" << endl;}
};class Student : public Adult //派生类
{
public:virtual void BuyTicket(){cout << "学生票" << endl;}
};void Ticket(Adult pa) //不是基类的指针或引用
{pa.BuyTicket();
}int main()
{Adult a;   //基类对象Student s; //派生类对象Ticket(a);Ticket(s);return 0;
}

 

所以一定要同时满足构成多态的两个条件。

2.3.3 多态的经典例题

下面程序运行的结果是什么?

class A
{
public:virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }virtual void test() { func(); }
};
class B : public A
{
public:void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};int main()
{B* p = new B;p->test();return 0;
}

答案B -> 1

有两个类,A是基类,B是派生类。A和B里有返回值、函数名、参数类型都相同的函数func,虽然在派生类B里面没有加virtual,在这里依然构成虚函数的重写(原因前面说过)。

p是B类的指针,因为B继承了A,所以B类的指针p可以调用test函数。

在类里,成员函数都有隐含的this指针,这个this指针类型是A* 不是B*,虽然继承到B来了,但这只是一种说法,并不是真的把A里的内容全拷贝一份给B,所以A继承下来成员函数this指针类型依然是A*。

所以,调用func函数的就是基类的指针

而func又构成虚函数的重写,所以这里同时满足了构成多态的两个条件。既然构成了多态,p指向派生类B,所以func应该是B类重写的func。

但是虚函数的重写只会重写函数体部分,重写后的样子如下。

所以结果是 B -> 1 。

2.3.4 协变

派⽣类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引⽤,派⽣类虚函数返回派⽣类对象的指针或者引⽤时,称为协变。协变的实际意义并不⼤,所以我们了解⼀下即可。
class A {};
class B : public A {};
class Person 
{
public:
virtual A* BuyTicket()
{cout << "买票-全价" << endl;return nullptr;
}
};class Student : public Person {
public:
virtual B* BuyTicket()
{cout << "买票-打折" << endl;return nullptr;
}
};void Func(Person* ptr)
{ptr->BuyTicket();
}int main()
{Person ps;Student st;Func(&ps);Func(&st);return 0;
}

返回类型也可以是自己类型的指针或引用。

2.3.5 析构函数的重写

基类的析构函数为虚函数,此时派⽣类析构函数只要定义,⽆论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派⽣类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统⼀处理成destructor,所以基类的析构函数加了vialtual修饰,派⽣类的析构函数就构成重写。
下⾯的代码我们可以看到,如果~A(),不加virtual,那么delete p2时只调⽤的A的析构函数,没有调⽤B的析构函数,就会导致内存泄漏问题,因为~B()中在释放资源。
class A
{
public:virtual ~A(){cout << "~A()" << endl;}
};
class B : public A {
public:~B(){cout << "~B()->delete:" << _p << endl;delete _p;}
protected:int* _p = new int[10];
};
// 只有派⽣类Student的析构函数重写了Person的析构函数,下⾯的delete对象调⽤析构函数,才能
//构成多态,才能保证p1和p2指向的对象正确的调⽤析构函数。
int main()
{A* p1 = new A;A* p2 = new B;delete p1;delete p2;return 0;
}

2.3.6 override 和 final关键字

从上⾯可以看出,C++对虚函数重写的要求⽐较严格,但是有些情况下由于疏忽,⽐如函数名写错参数写错等导致⽆法构成重写,⽽这种错误在编译期间是不会报出的,只有在程序运⾏时没有得到预期结果才来debug会得不偿失,因此C++11提供了override,可以帮助⽤⼾检测是否重写。如果我们不想让派⽣类重写这个虚函数,那么可以⽤final去修饰。
class Car 
{
public:virtual void Dirve(){}
};class Benz :public Car 
{
public:virtual void Drive() override { cout << "Benz" << endl; }
};int main()
{return 0;
}

如果不加override这个代码是检查不出错误的。

class Car
{
public:virtual void Drive() final  //不能被重写{}
};
class Benz :public Car
{
public:virtual void Drive() { cout << "Benz" << endl; }
};
int main()
{return 0;
}

不想成员函数被重写,就加上final。

2.4 重载/重写/隐藏的对⽐

 3.纯虚函数和抽象类

在虚函数的后⾯写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类。
class Car //抽象类
{
public:virtual void Drive() = 0; //纯虚函数
};

纯虚函数不需要定义实现,只需要声明,但并不代表纯虚函数不能定义实现。抽象类不能实例化出对象。如果派⽣类继承后不重写纯虚函数,那么派⽣类也是抽象类

class Benz :public Car
{
public:virtual void Drive() //重写纯虚函数{cout << "Benz" << endl;}
};

此时就没有父类的对象,只有派生类的,但是父类的指针和引用还是有的

int main()
{Car* pBenz = new Benz;pBenz->Drive();return 0;
}

4.多态的原理

4.1 虚函数表指针

下⾯编译为32位程序的运⾏结果是什么?
A. 编译报错   B. 运⾏报错   C. 8   D. 12
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
protected:int _b = 1;char _ch = 'x';
};
int main()
{Base b;cout << sizeof(b) << endl;return 0;
}

答案:D.12

类的大小如何计算,在【C++】类和对象(上):初识类和对象 中的 2.2 对象大小  有详细解释。

按照计算对象大小的规则,这里的结果应该是8,但为什么是12呢?因为除了_b和_ch成员,还多⼀个_vfptr放在对象的前⾯(注意有些平台可能会放到对象的最后⾯,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(简称虚表指针

vf就是virtual function。这个指针在32位程序下大小为4,所以按照内存对齐规则,大小为12.

 ⼀个含有虚函数的类中都⾄少都有⼀个虚函数表指针,因为⼀个类所有虚函数的地址要被放到这个类对象的虚函数表中,虚函数表也简称虚表。这里的表其实就是一个数组,虚函数表也就是一个函数指针数组,虚函数表指针就是指向这个数组的指针。

比如我们现在再加一个虚函数,这个_vfptr就会多一个内容。

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}virtual void Func2(){cout << "Func2()" << endl;}void Func3()  //不是虚函数{cout << "Func3()" << endl;}
protected:int _b = 1;char _ch = 'x';
};

这里只会存放虚函数的指针,不是虚函数不会放在里面,func3就不再这个里面。

 

4.2 多态的原理

以下面这段代码为例。

class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
private:string _name;
};class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-打折" << endl; }
private:string _id;
};class Soldier : public Person {
public:virtual void BuyTicket() { cout << "买票-优先" << endl; }
private:string _codename;
};void Func(Person* ptr)
{ptr->BuyTicket();
}int main()
{Person ps;Student st;Soldier sr;Func(&ps);Func(&st);Func(&sr);return 0;
}

4.2.1 从理论上分析

有ps、st、sr三个对象,就有三个虚函数表,这三个虚表里存放着自己的虚函数。

 多态中Func函数应该是指向谁调用谁。

 从内存角度,这里的ptr“看到的”都是父类,因为传子类过去会切片

 在满足多态的条件下,运行时编译器会找_vfptr这个指针,然后通过这个指针去对应的虚函数表里去找虚函数的地址。不管传的ptr类型是什么,与类型无关,只与指向的对象有关

4.2.2 从汇编角度观察

满足多态条件下的汇编,最终调的是 call   eax   。

 不满足多态,把前面的代码父类中的virtual删除,就不满足了。

class Person  //父类
{
public:void BuyTicket() { cout << "买票-全价" << endl; }
private:string _name;
};

来看一下汇编。

不管传的ptr是什么,与对象无关,调用的都是父类的函数。

 

4.3 静态绑定与动态绑定

  • 对不满⾜多态条件(指针或者引⽤+调⽤虚函数)的函数调⽤是在编译时绑定,也就是编译时确定调⽤函数的地址,叫做静态绑定
  • 满⾜多态条件的函数调⽤是在运⾏时绑定,也就是在运⾏时到指向对象的虚函数表中找到调⽤函数的地址,也就做动态绑定
// ptr是指针+BuyTicket是虚函数满⾜多态条件。
// 这⾥就是动态绑定,编译在运⾏时到ptr指向对象的虚函数表中确定调⽤函数地址ptr->BuyTicket();
00CD3C72  mov         eax,dword ptr [ptr]  
00CD3C75  mov         edx,dword ptr [eax]  
00CD3C77  mov         esi,esp  
00CD3C79  mov         ecx,dword ptr [ptr]  
00CD3C7C  mov         eax,dword ptr [edx]  
00CD3C7E  call        eax
// BuyTicket不是虚函数,不满⾜多态条件。
// 这⾥就是静态绑定,编译器直接确定调⽤函数地址ptr->BuyTicket();
00603C72  mov         ecx,dword ptr [ptr]  
00603C75  call        Person::BuyTicket (06015FFh)  

5.虚函数表更深入的讲解

  • 基类对象的虚函数表中存放基类所有虚函数的地址。同类型的对象共⽤同⼀张虚表,不同类型的对象各⾃有独⽴的虚表,所以基类和派⽣类有各⾃独⽴的虚表
  • 派⽣类由两部分构成,继承下来的基类和⾃⼰的成员,⼀般情况下,继承下来的基类中有虚函数表指针,⾃⼰就不会再⽣成虚函数表指针。但是要注意的这⾥继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同⼀个,就像基类对象的成员和派⽣类对象中的基类对象成员也独⽴的。
  • 派⽣类中 重写的基类的虚函数 ,派⽣类的虚函数表中对应的虚函数就会 被覆盖 成派⽣类重写的虚函数 地址
  • 派⽣类的虚函数表中包含, (1)基类的虚函数地址,(2)派⽣类重写的虚函数地址完成覆盖,(3)派⽣类⾃⼰的虚函数地址 三个部分。
  • 虚函数表本质是⼀个存虚函数指针的 指针数组 ,⼀般情况这个数组最后⾯放了⼀个0x00000000标记。(这个C++并没有进⾏规定,各个编译器⾃⾏定义的)。
  • 虚函数表 存在哪的?这个问题严格说并没有标准答案C++标准并没有规定,vs下是存在代码段(常量区)
  • 虚函数 存在哪的?虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在 代码段 的,只是虚函数的地址⼜存到了虚表中。

 

本次分享见到这里,我们下篇见~

相关文章:

【C++】多态详细讲解

本篇来聊聊C面向对象的第三大特性-多态。 1.多态的概念 多态通俗来说就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态)。 编译时多态&#xff1a;主要就是我们前⾯讲的函数重载和函数模板&#xff0c;他们传不同类型的参数就可以调⽤不同的函数&#xff0c;通…...

防火墙的安全策略

1.VLAN 2属于办公区;VLAN 3属于生产区&#xff0c;创建时间段 [FW]ip address-set BG type object [FW-object-address-set-BG]address 192.168.1.0 mask 25 [FW]ip address-set SC type object [FW-object-address-set-SC]address 192.168.1.129 mask 25 [FW]ip address-se…...

Android 进程间通信

什么是IPC&#xff1f; Android 进程间通信&#xff08;IPC&#xff0c;Inter-Process Communication&#xff09;是Android操作系统中不同进程间交换数据和资源的一种机制。由于Android是多任务操作系统&#xff0c;每个应用通常运行在自己的进程中&#xff0c;以提高安全性和…...

【优先算法】专题——位运算

在讲解位运算之前我们来总结一下常见的位运算 一、常见的位运算 1.基础为运算 << &&#xff1a;有0就是0 >> |&#xff1a;有1就是1 ~ ^&#xff1a;相同为0&#xff0c;相异位1 /无进位相加 2.给一个数 n&#xff0c;确定它的二进制表示…...

深入理解k8s中的容器存储接口(CSI)

CSI出现的原因 K8s原生支持一些存储类型的PV&#xff0c;像iSCSI、NFS等。但这种方式让K8s代码与三方存储厂商代码紧密相连&#xff0c;带来不少麻烦。比如更改存储代码就得更新K8s组件&#xff0c;成本高&#xff1b;存储代码的bug还会影响K8s稳定性&#xff1b;K8s社区维护和…...

ZZNUOJ(C/C++)基础练习1061——1070(详解版)

目录 1061 : 顺序输出各位数字 C语言版 C版 1062 : 最大公约数 C C 1063 : 最大公约与最小公倍 C C 1064 : 加密字符 C C 1065 : 统计数字字符的个数 C C 1066 : 字符分类统计 C C 1067 : 有问题的里程表 C C 1068 : 进制转换 C C C&#xff08;容器stack…...

ES6 变量解构赋值总结

1. 数组的解构赋值 1.1 基本用法 // 基本数组解构 const [a, b, c] [1, 2, 3]; console.log(a); // 1 console.log(b); // 2 console.log(c); // 3// 跳过某些值 const [x, , y] [1, 2, 3]; console.log(x); // 1 console.log(y); // 3// 解构剩余元素 const [first, ...re…...

机理模型与数据模型融合的方式

机理模型与数据模型的融合旨在结合两者的优势&#xff0c;以提供更准确、可靠的预测和决策支持。以下是几种常见的融合方式及其示例&#xff1a; 1. 特征增强&#xff08;Feature Augmentation&#xff09; 描述&#xff1a;将由机理模型计算得到的结果作为额外特征加入到数据…...

高效 MyBatis SQL 写法一

高效 MyBatis SQL 写法一 前言 MyBatis 作为一款优秀的持久层框架&#xff0c;极大地简化了数据库操作。 然而&#xff0c;在实际开发中&#xff0c;XML 配置的编写仍然可能显得繁琐。 本文将分享一些 MyBatis 动态 SQL 的优质写法&#xff0c;帮助开发者提升效率并减少错误…...

vue3中的ref相关的api及用法

在 Vue 3 中&#xff0c;ref 相关的 API 主要用于管理响应式数据。以下是 ref 相关的 API 及其用法&#xff1a; 1. ref ref 用于创建响应式的基本数据类型或对象。 用法示例&#xff1a; <script setup> import { ref } from vue;const count ref(0);const incremen…...

3 卷积神经网络CNN

1 Image Classification (Neuron Version) – 1.1 Observation 1 1.2 Observation 2 如果不同的receptive field需要相同功能的neuron&#xff0c;可以使这些neuron共享参数 1.3 Benefit of Convolutional Layer 2 Image Classification (Filter Version) 不用担心filter大小…...

CSV数据分析智能工具(基于OpenAI API和streamlit)

utils.py&#xff1a; from langchain_openai import ChatOpenAI from langchain_experimental.agents.agent_toolkits import create_csv_agent import jsonPROMPT_TEMPLATE """你是一位数据分析助手&#xff0c;你的回应内容取决于用户的请求内容。1. 对于文…...

解决php8.3无法加载curl扩展

把它的值更改为扩展存在的目录的绝对路径(扩展存在的目录为有php_xxx.dll存在的目录) extension_dir "e:\serv\php83\ext" 然后从php根目录复制 libssh2.dll 和 libcrypto-*.dll 和 libssl-*.dll 到Apache根目录下的bin目录 重启apache服务即可...

拍照对比,X70 PRO与X90 PRO+的细节差异

以下是局部截图&#xff08;上X70P下X90PP&#xff09; 对比1 这里看不出差异。 对比2 X90PP的字明显更清楚。 对比3 中下的字&#xff0c;X90PP显然更清楚。...

《MPRnet》学习笔记

paper&#xff1a;2102.02808 GitHub&#xff1a;swz30/MPRNet: [CVPR 2021] Multi-Stage Progressive Image Restoration. SOTA results for Image deblurring, deraining, and denoising. 目录 摘要 1、介绍 2、相关工作 2.1 单阶段方法 2.2 多阶段方法 2.3 注意力机…...

机器学习-线性回归(参数估计之结构风险最小化)

前面我们已经了解过关于机器学习中的结构风险最小化准则&#xff0c;包括L1 正则化&#xff08;Lasso&#xff09;、L2 正则化&#xff08;Ridge&#xff09;、Elastic Net&#xff0c;现在我们结合线性回归的场景&#xff0c;来了解一下线性回归的结构风险最小化&#xff0c;通…...

C++11详解(二) -- 引用折叠和完美转发

文章目录 2. 右值引用和移动语义2.6 类型分类&#xff08;实践中没什么用&#xff09;2.7 引用折叠2.8 完美转发2.9 引用折叠和完美转发的实例 2. 右值引用和移动语义 2.6 类型分类&#xff08;实践中没什么用&#xff09; C11以后&#xff0c;进一步对类型进行了划分&#x…...

深度学习系列--01.入门

一.深度学习概念 深度学习&#xff08;Deep Learning&#xff09;是机器学习的分支&#xff0c;是指使用多层的神经网络进行机器学习的一种手法抖音百科。它学习样本数据的内在规律和表示层次&#xff0c;最终目标是让机器能够像人一样具有分析学习能力&#xff0c;能够识别文字…...

熵采样在分类任务中的应用

熵采样在分类任务中的应用 在机器学习的分类任务里,数据的标注成本常常制约着模型性能的提升。主动学习中的熵采样策略,为解决这一难题提供了新的思路。本文将带你深入了解熵采样在分类任务中的原理、应用及优势。 一、熵采样的原理(优化版) 熵,源于信息论,是对不确定…...

vite配置之---依赖优化选项

vite optimizeDeps 配置项主要在 开发环境 中对依赖项发挥作用 optimizeDeps.entries vite optimizeDeps.entries 是 Vite 配置中的一个选项&#xff0c;用来指定要优化的入口文件。这在开发环境中尤其有用&#xff0c;因为它告诉 Vite 需要预构建哪些文件&#xff0c;以便加速…...

Shell基础:中括号的使用

在Shell脚本中&#xff0c;中括号&#xff08;[ ... ] 和 [[ ... ]]&#xff09;是一种常见的条件测试结构。它们用于进行文件类型检查、值比较以及逻辑判断。通过了解它们的不同特点和用法&#xff0c;能够帮助你编写更加高效、安全且易读的脚本。本文将详细介绍Shell中单中括…...

oracle ORA-27054报错处理

现象 在oracle执行expdp&#xff0c;rman备份&#xff0c;xtts的时候,由于没有足够的本地空间&#xff0c;只能使用到NFS的文件系统但有时候会出现如下报错 ORA-27054: NFS file system where the file is created or resides is not mounted with correct options根据提示信…...

SpringCloud速通教程

视频地址 文档地址 3. SpringCloud - 快速通关...

MapReduce分区

目录 1. MapReduce分区1.1 哈希分区1.2 自定义分区 2. 成绩分组2.1 Map2.2 Partition2.3 Reduce 3. 代码和结果3.1 pom.xml中依赖配置3.2 工具类util3.3 GroupScores3.4 结果 参考 本文引用的Apache Hadoop源代码基于Apache许可证 2.0&#xff0c;详情请参阅 Apache许可证2.0。…...

python算法和数据结构刷题[3]:哈希表、滑动窗口、双指针、回溯算法、贪心算法

回溯算法 「所有可能的结果」&#xff0c;而不是「结果的个数」&#xff0c;一般情况下&#xff0c;我们就知道需要暴力搜索所有的可行解了&#xff0c;可以用「回溯法」。 回溯算法关键在于:不合适就退回上一步。在回溯算法中&#xff0c;递归用于深入到所有可能的分支&…...

JDK 中 NIO 框架设计与实现:深入剖析及实战样例

一、引言 在 Java 的发展历程中&#xff0c;I/O&#xff08;Input/Output&#xff09;操作一直是构建高效、稳定应用程序的关键环节。传统的 Java I/O 操作基于流&#xff08;Stream&#xff09;的方式&#xff0c;虽然简单易用&#xff0c;但在面对高并发、大规模数据传输等场…...

基于springboot校园点歌系统

基于Spring Boot的校园点歌系统是一种专为校园场景设计的音乐点播平台&#xff0c;它能够丰富学生的校园生活&#xff0c;提升学生的娱乐体验。以下是对该系统的详细介绍&#xff1a; 一、系统背景与意义 在校园环境中&#xff0c;学生们对于音乐有着浓厚的兴趣&#xff0c;传…...

Spring 核心技术解析【纯干货版】- IX:Spring 数据访问模块 Spring-Jdbc 模块精讲

在现代企业级应用中&#xff0c;数据访问层的稳定性和高效性至关重要。为了简化和优化数据库操作&#xff0c;Spring Framework 提供了 Spring-JDBC 模块&#xff0c;旨在通过高度封装的 JDBC 操作&#xff0c;简化开发者的编码负担&#xff0c;减少冗余代码&#xff0c;同时提…...

React开发中箭头函数返回值陷阱的深度解析

React开发中箭头函数返回值陷阱的深度解析 一、箭头函数的隐式返回机制&#xff1a;简洁背后的规则二、块函数体中的显式返回要求&#xff1a;容易被忽视的细节三、真实场景下的案例分析案例1&#xff1a;忘记return导致组件渲染失败案例2&#xff1a;异步操作中的返回值陷阱 四…...

线程同步时定义 std::mutex 为什么要在前面添加 mutable 关键字

在C中&#xff0c;mutable关键字用于修饰类的成员变量&#xff0c;表示即使在一个const对象中&#xff0c;该成员变量也可以被修改。对于mutex这样的同步原语&#xff0c;使用mutable是必要的&#xff0c;原因如下&#xff1a; 1. 为什么需要 mutable&#xff1f; mutex通常用…...

【多线程】线程池核心数到底如何配置?

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. 前置回顾2. 动态线程池2.1 JMX 的介绍2.1.1 MBeans 介绍 2.2 使用 JMX jconsole 实现动态修改线程池2.2.…...

Linux find 命令 | grep 命令 | 查找 / 列出文件或目录路径 | 示例

注&#xff1a;本文为 “Linux find 命令 | grep 命令使用” 相关文章合辑。 未整理去重。 如何在 Linux 中查找文件 作者&#xff1a; Lewis Cowles 译者&#xff1a; LCTT geekpi | 2018-04-28 07:09 使用简单的命令在 Linux 下基于类型、内容等快速查找文件。 如果你是 W…...

爬楼梯(dp)杭电复试

一个楼梯共有 nn 级台阶&#xff0c;每次可以走一级或者两级或者三级&#xff0c;问从第 00 级台阶走到第 nn 级台阶一共有多少种方案。 输入格式 一个整数 NN。 输出格式 一个整数&#xff0c;表示方案总数。 数据范围 1≤N≤201≤N≤20 输入样例&#xff1a; 4输出样…...

JVM执行引擎

一、执行引擎的概述: 执行引擎是]ava虚拟机核心的组成部分之一; “虚拟机”是一个相对于“物理机”的概念&#xff0c;这两种机器都有代码执行能力&#xff0c;其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的&#xff0c;而虚拟机的执行引擎则…...

企业四要素如何用PHP进行调用

一、什么是企业四要素&#xff1f; 企业四要素接口是在企业三要素&#xff08;企业名称、统一社会信用代码、法定代表人姓名&#xff09;的基础上&#xff0c;增加了一个关键要素&#xff0c;通常是企业注册号或企业银行账户信息。这种接口主要用于更全面的企业信息验证&#x…...

基于springboot河南省旅游管理系统

基于Spring Boot的河南省旅游管理系统是一种专为河南省旅游行业设计的信息管理系统&#xff0c;旨在整合和管理河南省的旅游资源信息&#xff0c;为游客提供准确、全面的旅游攻略和服务。以下是对该系统的详细介绍&#xff1a; 一、系统背景与意义 河南省作为中国的中部省份&…...

arm 下 多线程访问同一变量 ,使用原子操作 性能差问题

arm下原子操作性能差的原因 Linux Kernel(armv8-aarch64) 的原子操作的底层实现 - 极术社区 - 连接开发者与智能计算生态 arm 下如何解决 ARMs LSE (for atomics) and MySQL – MySQL On ARM – All you need to know about MySQL (and its variants) on ARM. arm 下lse 和…...

嵌入式工程师必学(143):模拟信号链基础

概述: 我们每天使用的许多电子设备,以及我们赖以生存的电子设备,如果不使用电子工程师设计的实际输入信号,就无法运行。 模拟信号链由四个主要元件组成:传感器、放大器、滤波器和模数转换器 (ADC)。这些传感器用于检测、调节模拟信号并将其转换为适合由微控制器或其他数…...

PyQt6/PySide6 的 QDialog 类

QDialog 是 PyQt6 或 PySide6 库中用于创建对话框的类。对话框是一种特殊的窗口&#xff0c;通常用于与用户进行短期交互&#xff0c;如输入信息、显示消息或选择选项等。QDialog 提供了丰富的功能和灵活性&#xff0c;使得开发者可以轻松地创建各种类型的对话框。下面我将详细…...

【AI日记】25.02.05 自由不是一种工具

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】 AI kaggle 比赛&#xff1a;Backpack Prediction Challenge感想&#xff1a;这次比赛的数据集的一大特点是信号过弱或者噪声过大&#xff0c;也是一大难点&#xff0c;即使kaggle 官方增加了一…...

【原子工具】快速幂 快速乘

题幂算.一切即1 阴阳迭变积微著&#xff0c;叠浪层峦瞬息功 莫道浮生千万事&#xff0c;元知万象一归宗 文章目录 快速幂原始快速幂&#xff08;O(logn)&#xff09;二分递归形式非递归形式 模下意义的快速幂&#xff08;O(logn)&#xff09;二分递归形式非递归形式 快速乘龟速…...

2024年12月 Scratch 图形化(四级)真题解析 中国电子学会全国青少年软件编程等级考试

202412 Scratch 图形化&#xff08;四级&#xff09;真题解析 中国电子学会全国青少年软件编程等级考试 一、选择题(共10题&#xff0c;共30分) 第 1 题 列表存放全班同学的身高&#xff0c;小猫运行下列程序&#xff0c;下列选项说法正确的是&#xff1f;&#xff08; &#…...

【面试宝典】机器学习:深度解析高频面试题与解答策略

目录 &#x1f354; 机器学习中特征的理解 &#x1f354; 机器学习三要素如何理解? &#x1f354; 机器学习中&#xff0c;有哪些特征选择的⼯程⽅法&#xff1f; &#x1f354; 机器学习中的正负样本 &#x1f354; 线性分类器与⾮线性分类器的区别及优劣 &#x1f354…...

使用 ElementUI 和 Spring 实现稳定可靠的文件上传和下载功能

前端(ElementUI) 1. 文件上传 使用 el-upload 组件配置上传接口处理上传成功和失败<template><div><el-uploadclass="upload-demo"action="http://your-server-url/upload":on-success="handleSuccess":on-error="handle…...

Linux驱动---字符设备

目录 一、基础简介 1.1、Linux设备驱动分类 1.2、字符设备驱动概念 二、驱动基本构成 2.1、驱动模块的加载和卸载 2.2、添加LICENNSE以及其他信息 三、字符设备驱动开发步骤 3.1、分配主次设备号 3.1.1 主次设备号 3.1.2静态注册设备号 3.1.3动态注册设备号 3.1.4释…...

FastReport.NET控件篇之交叉表控件

认识交叉表 上面是交叉表的原型&#xff0c;关键的三个单元格。 单元格①&#xff1a;用于扩展行数据&#xff0c;譬如打印学生成绩表时&#xff0c;每个学生一行&#xff0c;那么这个地方就是以学生姓名列进行打印。 单元格②&#xff1a;用于扩展列数据&#xff0c;譬如打印…...

构建高效复杂系统的关键:架构与模块详解

目录 一、复杂系统组成 二、接入系统 (Access System) 三、应用系统 (Application System) 四、基础平台 (Foundation Platform) 五、中间件 (Abundant External Middleware) 六、支撑系统 (Supporting System) 七、总结 参考文章 干货分享&#xff0c;感谢您的阅读&am…...

C++之递归

递归 递归函数是指一个函数直接或间接地调用自身。递归函数通常用于解决可以分解为相似子问题的问题&#xff0c;例如计算阶乘、斐波那契数列、遍历树结构等 基本结构 一个递归函数通常包含两个部分&#xff1a; 基准条件&#xff08;Base Case&#xff09;&#xff1a;这是…...

pushgateway指标聚合问题

一 问题现象 一个job有多个实例推送指标&#xff0c;但是从pushgateway上看这个job的instance字段&#xff0c;只显示一个实例的ip&#xff0c;而不是多个实例。导致在grafana上无法正常根据ip查看监控。 应用的prometheus的配置 management:metrics:tags:application: ${spr…...

mini-lsm通关笔记Week2Day7

项目地址&#xff1a;https://github.com/skyzh/mini-lsm 个人实现地址&#xff1a;https://gitee.com/cnyuyang/mini-lsm 在上一章中&#xff0c;您已经构建了一个完整的基于LSM的存储引擎。在本周末&#xff0c;我们将实现存储引擎的一些简单但重要的优化。欢迎来到Mini-LSM的…...