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

【C++学习手札】多态:掌握面向对象编程的动态绑定与继承机制(深入)

                                               🎬慕斯主页修仙—别有洞天

                                              ♈️今日夜电波:世界上的另一个我

                                                                1:02━━━━━━️💟──────── 3:58
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


目录

多态的原理

首先理解虚函数表

正式理解多态的原理

一些拓展

多态对于引用、指针和对象

虚表的拓展

多继承中的虚函数表

先了解如何打印虚函数表

然后理解多继承中的虚函数表

方法一:加上base1的大小

方法二:切片

结论


多态的原理

首先理解虚函数表

class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};

        请问sizeof(Base)的大小为多少?

        答案为:x64下16字节,x86下8字节

        解析如下:

        Base类中包涵着int类型的成员变量占4字节,而由于有虚函数,因此会有一个虚函表的指针vfptr,因此根据内存对齐,得到上述答案。

        这时就会有疑惑了?虚函数表指针和虚函数表是什么呢?

        如下通过监视窗口可以看到vfptr指向了一个数组(也就是虚函数表),而数组中存储着虚函数指针:

        继续分析,我们在上述代码的基础上增加代码:

class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};
class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};
int main()
{Base b;Derive d;return 0;
}

        通过观察和测试,我们发现了以下几点问题:

  1. 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
  2. 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
  3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。
  4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
  5. 总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
  6. 这里还有一个童鞋们很容易混淆的问题:虚函数存在哪的?虚表存在哪的? 答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下是存在代码段的,Linux g++下大家自己去验证?

正式理解多态的原理

        见以下代码:

class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }virtual void vv() { cout << "打折" << endl; }int a = 0;
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }int b = 1;
};
void Func(Person& p)
{p.BuyTicket();
}
int main()
{Person m;Func(m);Student s;Func(s);return 0;
}

        从监视中很明显的看到,子类继承了父类的虚函数表,但是很明显的看到虚函数表中我们的BuyTicket()的虚函数指针地址改变了,而vv()确没有改变,这就很明显了,因为BuyTicket()被重写了,而vv()没有,而重写也有另外一个名字:覆盖当我们重写了虚函数,那么就会覆盖对应虚函数在虚函数表中的指针

        内存方面观察:

        可以看到其中vfptr中存储的地址是发生了改变的,也就是说我们可以根据这个地址找到新的一张虚函数表,在前面我们学习过“切片”的概念,我们知道当以父类的类型去访问子类的类型会发生“切片”使得只访问父类的类型的空间,也就是说我们只访问上图中蓝色框内的内容,再结合上上张图监视中如果子类重写了虚函数则虚函数表中虚函数指针改变。当我们调用对应的虚函数时,就会调用子函数的虚函数而不是父类的虚函数!这就是多态实现的原理!因此,多态中指向父类调用父类,指向子类调用子类!

一些拓展

多态对于引用、指针和对象

        为什么多态只允许引用和指针呢?我们都知道引用的底层实现实际上还是指针,多态的实现就是指向子类对象中切割出来的那一部分!而对象只会拷贝子类对象中父类的那一部分,但是不会拷贝虚函数表指针。为什么呢?因为如果允许虚函数表指针的拷贝会造成二义性,如下:

int main()
{Person m;Student s;m=s;Func(m);Func(s);return 0;
}

        如果对象可以像引用和指针一样,那么当拷贝了虚函数表指针后,你会发现我们实现不了多态中指向父类调用父类,指向子类调用子类的场景。也会造成析构函数调用调错等等的错误。

虚表的拓展

        如果子类与父类中不重写虚函数,子类与父类的续虚函数表一样吗?不一样!他们存在不同的位置!虽然他们的内容是一样的!同类对象的虚函数表一样吗?一样!

        总结,不同的类不会共用虚函数表,只有相同的类才会共用虚函数表!

多继承中的虚函数表

先了解如何打印虚函数表

        我们都知道虚函数表是一个函数指针数组,并且数组最后一位是以nullptr结尾的。因此,我们可以根据该特性打印虚函数表:

typedef void(*VFPTR) ();
void PrintVTable(VFPTR* vTable)
{// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout << " 虚表地址>" << vTable << endl;for (size_t i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}

        例子,打印单继承的虚函数表:

class Base {
public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }
private:int a;
};
class Derive :public Base {
public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }virtual void func4() { cout << "Derive::func4" << endl; }
private:int b;
};typedef void(*VFPTR) ();
void PrintVTable(VFPTR* vTable)
{// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout << " 虚表地址>" << vTable << endl;for (size_t i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}int main()
{Base b;Derive d;VFPTR * vTableb = (VFPTR*)(*((int*)&b));PrintVTable(vTableb);VFPTR* vTabled = (VFPTR*)(*((int*)&d));PrintVTable(vTabled);return 0;
}

        思路:取出b、d对象的头4bytes,就是虚表的指针,前面我们说了虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr。需要注意的是这是在x86的运行环境下的,如果是x64则需强转为long long:

        1.先取b的地址,强转成一个int*的指针

        2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针

        3.再强转成VFPTR*,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组。

        4.虚表指针传递给PrintVTable进行打印虚表

        5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最后面没有放nullptr,导致越界,这是编译器的问题。我们只需要点目录栏的 - 生成 - 清理解决方案,再编译就好了。

然后理解多继承中的虚函数表

        概念:

C++中的多继承是指一个派生类可以同时从多个基类派生,从而继承它们的属性和行为

        多继承是面向对象编程中一个重要的概念,它允许一个类继承多个其他类的成员。这样做有几个目的:

  • 代码重用:多继承可以提高代码的重用性,因为派生类可以访 问所有基类的公有成员和保护成员。
  • 功能组合:通过继承多个类,派生类可以将不同基类的功能组合在一起,形成更复杂的功能。

        然而,多继承也可能带来一些问题,如菱形继承问题,这可能导致二义性。为了解决这个问题,C++引入了虚基类的概念。

        如下为一段多继承的代码,可以看到drive继承了base1和base2:

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;
};

        那么他的虚函数表又是什么样的呢?如下:

        可以看到正如我们猜测的那样,它包含着两张虚函数表!然而,我们在derive中重写了func1()函数,以及额外添加了一个func3()函数,但是并没有在监视中显示,这是因为编译器并没有让你实际的看到,也就是说编译器在骗人,实际上就是在其中的一张表当中,可以理解为监视的一个bug。我们通过上述打印虚函数表可以看到具体的效果(注意此为x64环境下):

typedef void(*VFPTR) ();
void PrintVTable(VFPTR* vTable)
{// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout << " 虚表地址>" << vTable << endl;for (size_t i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}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;
};int main()
{cout << "base1:" << endl;base1 b;PrintVTable((VFPTR*)(*(long long*)&b));cout << "base2:" << endl;base2 c;PrintVTable((VFPTR*)(*(long long*)&c));cout << "derive 表1:" << endl;derive d;PrintVTable((VFPTR*)(*(long long*)&d));//printvft((vfunc*)(*(int*)((char*)&d+sizeof(base1))));cout << "derive 表2:" << endl;base2* ptr = &d;PrintVTable((VFPTR*)(*(long long*)ptr));
}

        得到第一个虚基表的方法很简单,因为第一个虚基表的指针正好处在前8个字节处,只需要向上面一样进行强转即可,如果要找到第二个虚基表则有如下两种方法:

方法一:加上base1的大小
	printvft((vfunc*)(*(int*)((char*)&d+sizeof(base1))));

        也就是加上sizeof(base1)即可,但是!需要注意的是d的类型是Derive,在&d后变为Derive* 的一个指针,+1 跳转的是Derive类型的字节大小!而我们想要的是每次+1跳转1个字节,所以需要强制转换char* !

方法二:切片
	base2* ptr = &d;PrintVTable((VFPTR*)(*(long long*)ptr));

        把d利用切片的原理给到ptr,然后再按照上面强转的原理找到虚基表即可!

结论

        如下为上面代码的运行结果:

        可以看到上面的图示,我们可以得出相应的结论:多继承中重写的虚函数以及新增的虚函数都是在第一个虚基表当中进行修改以及增加的!如果重写的虚函数在其他基类中也有对应的虚函数,那么继承下来的虚基表也需要重写。

        更加详细的图解如下:

​        这里又引申出来一个问题,为什么其derive继承的两个虚基表中func1()的地址不同呢?这里就需要从汇编的角度进行理解了:

        在以上的代码的基础上调试下面这段代码(x86环境下),通过反汇编可得结果如下:

	derive d;base1* p1 = &d;p1->func1();base2* p2 = &d;p2->func1();

        ​从上图的图示可以看到p1只经过了一次jmp就找到了derive中的func1()的地址,而p2则是经过了多次的jmp才找到func1()地址。这是因为:p1的调用的地址恰好与derive* 类型的this指针的地址是重叠的,因此不需要去找这个地址,而p2要经过蓝框中的“8字节的偏移”才能找到this指针(可以看到有ecx标识(ecx是存储this指针的)),才能指向derive对象的开始,才可以调用derive的func1()(毕竟fun1也可能调用成员函数、成员变量等等)。

        总结:这里是为了修正this指针指向derive对象,这里调用的是derive重写的func1()。

 


                      感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

 

相关文章:

【C++学习手札】多态:掌握面向对象编程的动态绑定与继承机制(深入)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;世界上的另一个我 1:02━━━━━━️&#x1f49f;──────── 3:58 &#x1f504; ◀️ ⏸ ▶️ ☰ &am…...

【机构vip教程】Android SDK手机测试环境搭建

Android SDK 的安装和环境变量的配置 前置条件&#xff1a;需已安装 jdk1.8及 以上版本 1、下载Android SDK&#xff0c;解压后即可&#xff08;全英文路径&#xff09;&#xff1b;下载地址&#xff1a;http://tools.android-studio.org/index.php/sdk 2、新建一个环境变量&…...

2024.2.18

使用fgets统计给定文件的行数 #include<stdio.h> #include<string.h> int main(int argc, const char *argv[]) {FILE *fpNULL;if((fpfopen("./test.txt","w"))NULL){perror("open err");return -1;}fputc(h,fp);fputc(\n,fp);fput…...

Haproxy实验

环境: servera(Haproxy):192.168.233.132 serverb(web1):192.168.233.144 serverc(web2):192.168.233.140 serverd(客户端):192.168.233.141 servera(Haproxy): yum install haproxy -y vim /etc/haproxy/haproxy.cfg(配置文件) # 设置日志&#…...

CSRNET图像修复,DNN

CSRNET图像修复 CSRNET图像修复&#xff0c;只需要OPENCV的DNN...

004 - Hugo, 分类

004 - Hugo, 分类content文件夹 004 - Hugo, 分类 content文件夹 ├─.obsidian ├─categories │ ├─Python │ └─Test ├─page │ ├─about │ ├─archives │ ├─links │ └─search └─post├─chinese-test├─emoji-support├─Git教程├─Hugo分类├─…...

Vue3之ElementPlus中Table选中数据的获取与清空方法

Vue3之ElementPlus中Table选中数据的获取与清空方法 文章目录 Vue3之ElementPlus中Table选中数据的获取与清空方法1. 点击按钮获取与清空选中表格的数据1. 用到ElementPlus中Table的两个方法2. 业务场景3. 操作案例 1. 点击按钮获取与清空选中表格的数据 1. 用到ElementPlus中…...

Leetcode 516.最长回文子序列

题意理解&#xff1a; 给你一个字符串 s &#xff0c;找出其中最长的回文子序列&#xff0c;并返回该序列的长度。 子序列定义为&#xff1a;不改变剩余字符顺序的情况下&#xff0c;删除某些字符或者不删除任何字符形成的一个序列。 回文理解为元素对称的字串&#xff0c;这里…...

cool Node后端 中实现中间件的书写

1.需求 在node后端中&#xff0c;想实现一个专门鉴权的文件配置&#xff0c;可以这样来解释 就是 有些接口需要token调用接口&#xff0c;有些接口不需要使用token 调用 这期来详细说明一下 什么是中间件中间件顾名思义是指在请求和响应中间,进行请求数据的拦截处理&#xf…...

Leecode之面试题消失的数字

一.题目及剖析 https://leetcode.cn/problems/missing-number-lcci/description/ 数组nums包含从0到n的所有整数&#xff0c;但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗&#xff1f; 注意&#xff1a;本题相对书上原题稍作改动 示例 1&…...

STM32的三种下载方式

结果jlink&#xff0c;串口&#xff0c;stlink方式都没有问题&#xff0c;是当时缩减代码&#xff0c;看真正起作用的代码段有哪些&#xff0c;就把GPIO初始化中 /*开启GPIO外部时钟*/RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE); 把开启外部时钟的代码注释掉了。…...

华为 huawei 交换机 接口 MAC 地址学习限制接入用户数量 配置示例

目录 组网需求: 配置思路&#xff1a; 操作步骤&#xff1a; 配置文件&#xff1a; 组网需求: 如 图 2-14 所示&#xff0c;用户网络 1 和用户网络 2 通过 LSW 与 Switch 相连&#xff0c; Switch 连接 LSW 的接口为GE0/0/1 。用户网络 1 和用户网络 2 分别属于 VLAN10 和 V…...

使用Python生成二维码的完整指南

无边落木萧萧下&#xff0c;不如跟着可莉一起游~ 可莉将这篇博客收录在了&#xff1a;《Python》 可莉推荐的优质博主首页&#xff1a;Kevin ’ s blog 本文将介绍如何使用Python中的qrcode库来生成二维码。通过简单的代码示例和详细解释&#xff0c;读者将学习如何在Python中轻…...

排序前言冒泡排序

目录 排序应用 常见的排序算法 BubbleSort冒泡排序 整体思路 图解分析 ​ 代码实现 每趟 写法1 写法2 代码NO1 代码NO2优化 时间复杂度 排序概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递…...

红队笔记Day3-->隧道上线不出网机器

昨天讲了通过代理的形式&#xff08;端口转发&#xff09;实现了上线不出网的机器&#xff0c;那么今天就来讲一下如何通过隧道上线不出网机器 目录 1.网络拓扑 2.开始做隧道&#xff1f;No&#xff01;&#xff01;&#xff01; 3.icmp隧道 4.HTTP隧道 5.SSH隧道 1.什么…...

C 练习实例70-求字符串长度

题目&#xff1a;写一个函数&#xff0c;求一个字符串的长度&#xff0c;在 main 函数中输入字符串&#xff0c;并输出其长度。 解答&#xff1a; #include <stdio.h> int length(char *s); int main() {int len;char str[20];printf("请输入字符串:\n");scan…...

HarmonyOS—@State装饰器:组件内状态

State装饰的变量&#xff0c;或称为状态变量&#xff0c;一旦变量拥有了状态属性&#xff0c;就和自定义组件的渲染绑定起来。当状态改变时&#xff0c;UI会发生对应的渲染改变。 在状态变量相关装饰器中&#xff0c;State是最基础的&#xff0c;使变量拥有状态属性的装饰器&a…...

Linux系统——防火墙拓展及重点理解

目录 一、iptables 1.基本语法 2.四表五链——重点记忆 2.1四表 2.2五链 2.3总结 3.iptables选项示例 3.1 -Z 清空流量计数 3.2 -P 修改默认规则 3.3 -D 删除规则 3.4 -R 指定编号替换规则 5.白名单 6.通用匹配 7.示例 7.1添加回环网卡 7.2可以访问端口 7.3 主…...

阿里云短信验证码的两个坑

其它都参照官网即可&#xff0c;其中有两个坑需要注意&#xff1a; 1、除去官网pom引用的包之外&#xff0c;还需要引用以下包&#xff1a; <dependency><groupId>org.apache.httpcomponents.client5</groupId><artifactId>httpclient5</artifact…...

c入门第十五篇——学而时习之(阶段性总结)

古人说&#xff1a;“学而时习之。”古人又说&#xff1a;“温故而知新。”古人还说&#xff1a;“读书百遍&#xff0c;其义自见。” 总结一个道理那就是好书要反反复复的读&#xff0c;学习过的知识要时常去复习它&#xff0c;才有可能常读常新。 我&#xff1a;“师弟&…...

抽象的前端

问题背景&#xff1a;vue3&#xff0c;axios 直接导致问题&#xff1a;路由渲染失败 问题报错&#xff1a;Uncaught SyntaxError: The requested module /node_modules/.vite/deps/axios.js?v7bee3286 does not provide an export named post (at LoginIn.vue:16:9) 引入组…...

UPC训练赛二十/20240217

A:无穷力量 题目描述 2022年重庆突发山火让世界看到了中国一个又一个的感人事迹&#xff1a;战士们第一时间奔赴火场&#xff0c;志愿者们自发组成团队&#xff0c;为救火提供一切的可能的服务&#xff0c;人们自发输送物资&#xff0c;有的志愿者甚至几天几夜没有睡觉。每个…...

【51单片机】LCD1602(江科大)

1.LCD1602介绍 LCD1602(Liquid Crystal Display)液晶显示屏是一种字符型液晶显示模块,可以显示ASCII码的标准字符和其它的一些内置特殊字符,还可以有8个自定义字符 显示容量:162个字符,每个字符为5*7点阵 2.引脚及应用电路 3.内部结构框图 屏幕: 字模库:类似于数码管的数…...

conda与pip的常用命令

conda的常用命令 1.查看conda版本 $ conda --version conda 23.11.02.查看conda的配置信息 $ conda infoactive environment : baseactive env location : /home/myPc/miniconda3shell level : 1user config file : /home/myPc/.condarcpopulated config files : conda vers…...

你知道什么是物联网MQTT么?

目录 你知道什么是物联网MQTT么&#xff1f;MQTT的基本概念MQTT的工作原理MQTT的应用场景MQTT的实例案例智能家居场景工业监控场景 你知道什么是物联网MQTT么&#xff1f; MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的、基于发布/订阅模式…...

P8 pair vector

pair是一个模板类&#xff0c;用于表示一对值的组合&#xff0c;用<utility>中 pair模板有两个模板参数&#xff0c;t1 t2&#xff0c;分别表示第一个值和第二个值类型 pair类有两个成员变量&#xff0c;frist和 cond,分别表示第一个值与第二个值 还有一些成员函数和…...

奇异值分解(SVD)的应用——图像压缩

SVD方法是模型降阶的一类重要方法&#xff0c;本征正交分解&#xff08;POD&#xff09;和平衡截断&#xff08;BT&#xff09;都属于SVD类方法。 要想深入了解模型降阶技术&#xff0c;我们可以先从SVD的应用入手&#xff0c;做一个直观的了解。 1. SVD的定义和分类 我们想寻找…...

RTDETR改进系列指南

基于Ultralytics的RT-DETR改进项目.(89.9) 为了感谢各位对RTDETR项目的支持,本项目的赠品是yolov5-PAGCP通道剪枝算法.具体使用教程 自带的一些文件说明 train.py 训练模型的脚本main_profile.py 输出模型和模型每一层的参数,计算量的脚本(rtdetr-l和rtdetr-x因为thop库的问…...

类和结构体的区别

类&#xff08;class&#xff09;和结构体&#xff08;struct&#xff09;是面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;中常见的两种数据类型&#xff0c;它们在不同的编程语言中有一些共同之处&#xff0c;但也存在一些区别。以下是它们…...

利用Excel模拟投币试验

文章目录 试验前对Excel要进行的设置试验步骤计算正面频率结果图试验前对Excel要进行的设置 进入Excel依次点击如下选项,最后将分析工具库勾选 #mermaid-svg-bIvrxZGI9buCMW6U {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#m…...

WebService接口测试

WebService的理解 WebService就是Web服务的意思&#xff0c;对应的应用层协议为SOAP&#xff08;相当于HTTP协议&#xff09;&#xff0c;可理解为远程调用技术。 特点&#xff1a; 客户端发送的请求主体内容&#xff08;请求报文&#xff09;的格式为XML格式 接口返回的响…...

语音唤醒——

文章目录 配置主代码 参考文档&#xff1a;https://picovoice.ai/docs/quick-start/porcupine-python/ 配置 pip install pvporcupine主代码 ACCESS_KEY&#xff1a;需要将该参数填入即可 # # Copyright 2018-2023 Picovoice Inc. # # You may not use this file except in …...

typeScript 类型推论

什么是类型推论&#xff1f; 类型推论是 TypeScript 中的一个特性&#xff0c;它允许开发人员不必显式地指定变量的类型。相反&#xff0c;开发人员可以根据变量的使用情况让 TypeScript 编译器自动推断出类型。例如&#xff0c;如果开发人员将一个字符串赋值给一个变量&#…...

JavaScript 设计模式之代理模式

代理模式 其实这种模式在现在很多地方也都有使用到&#xff0c;如 Vue3 中的数据相应原理就是使用的 es6 中的 Proxy 代理及 Reflect 反射的方式来处理数据响应式 我们日常在使用数据请求时&#xff0c;也会用到一些代理的方式&#xff0c;比如在请求不同的域名&#xff0c;端…...

JavaScript 对象判断

如何判断一个对象是否是Set、Map、Array、Object 参考链接&#xff1a; https://blog.csdn.net/yunchong_zhao/article/details/115915624 let set new Set() let map new Map() let arr [] let obj {}console.log(Object.prototype.toString.call(obj)); // [object Obje…...

Android下SF合成流程重学习之onMessageInvalidate

Android下SF合成流程重学习之onMessageInvalidate 引言 虽然看了很多关于Android Graphics图形栈的文章和博客&#xff0c;但是都没有形成自己的知识点。每次学习了&#xff0c;仅仅是学习了而已&#xff0c;没有形成自己的知识体系&#xff0c;这次趁着有时间&#xff0c;这次…...

基于SpringBoot+WebSocket+Spring Task的前后端分离外卖项目-订单管理(十七)

订单管理 1. Spring Task1.1 介绍1.2 cron表达式1.3 入门案例1.3.1 Spring Task使用步骤1.3.2 代码开发1.3.3 功能测试 2.订单状态定时处理2.1 需求分析2.2 代码开发2.3 功能测试 3. WebSocket3.1 介绍3.2 入门案例3.2.1 案例分析3.2.2 代码开发3.2.3 功能测试 4. 来单提醒4.1 …...

【Java多线程进阶】JUC常见类以及CAS机制

1. Callable的用法 之前已经接触过了Runnable接口&#xff0c;即我们可以使用实现Runnable接口的方式创建一个线程&#xff0c;而Callable也是一个interface&#xff0c;我们也可以用Callable来创建一个线程。 Callable是一个带有泛型的interface实现Callable接口必须重写cal…...

Python算法100例-1.7 最佳存款方案

完整源代码项目地址&#xff0c;关注博主私信’源代码’后可获取 1.问题描述2.问题分析3.算法设计4.完整的程序 1&#xff0e;问题描述 假设银行一年整存零取的月息为0.63%。现在某人手中有一笔钱&#xff0c;他打算在今后5年中的每年年底取出1000元&#xff0c;到第5年时刚…...

ADO世界之FIRST

目录 一、ADO 简介 二、ADO 数据库连接 1.创建一个 DSN-less 数据库连接 2.创建一个 ODBC 数据库连接 3.到 MS Access 数据库的 ODBC 连接 4.ADO 连接对象&#xff08;ADO Connection Object&#xff09; 三、ADO Recordset&#xff08;记录集&#xff09; 1.创建一个 …...

【COMP337 LEC 5-6】

LEC 5 Perceptron &#xff1a; Binary Classification Algorithm 8 感应器是 单个神经元的模型 突触连接的强度取决于接受外部刺激的反应 X input W weights a x1*w1x2*w2....... > / < threshold Bias MaxIter is a hyperparameter 超参数 which has to be chosen…...

力扣72. 编辑距离(动态规划)

Problem: 72. 编辑距离 文章目录 题目描述思路复杂度Code 题目描述 思路 由于易得将字符串word1向word2转换和word2向word1转换是等效的&#xff0c;则我们假定统一为word1向word2转换&#xff01;&#xff01;&#xff01; 1.确定状态&#xff1a;我们假设现在有下标i&#x…...

linux tree命令找不到:如何使用Linux Tree命令查看文件系统结构

Linux tree命令是一个用于显示文件夹和文件的结构的工具&#xff0c;它可以帮助用户更好地理解文件系统的结构。如果你在linux系统上找不到tree命令&#xff0c;那么可能是因为你的系统中没有安装tree命令。 解决方案 Linux tree命令是一个用于显示文件夹和文件的结构的工具&…...

OJ_最大逆序差

题目 给定一个数组&#xff0c;编写一个算法找出这个数组中最大的逆序差。逆序差就是i<j时&#xff0c;a[j]-a[i]的值 c语言实现 #include <stdio.h> #include <limits.h> // 包含INT_MIN定义 int maxReverseDifference(int arr[], int size) { if (size…...

MyBatis-Plus 实体类里写正则让字段phone限制为手机格式

/* Copyright © 2021User:啾啾修车File:ToupiaoRecord.javaDate:2021/01/12 19:29:12 */ package com.jjsos.repair.toupiao.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomido…...

K8S之运用污点、容忍度设置Pod的调度约束

污点、容忍度 污点容忍度 taints 是键值数据&#xff0c;用在节点上&#xff0c;定义污点&#xff1b; tolerations 是键值数据&#xff0c;用在pod上&#xff0c;定义容忍度&#xff0c;能容忍哪些污点。 污点 污点是定义在k8s集群的节点上的键值属性数据&#xff0c;可以决…...

Sora爆火,普通人的10个赚钱机会

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。&#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精通…...

【C++】C++入门—初识构造函数 , 析构函数,拷贝构造函数,赋值运算符重载

C入门 六个默认成员函数1 构造函数语法特性 2 析构函数语法特性 3 拷贝构造函数特性 4 赋值运算符重载运算符重载赋值运算符重载特例&#xff1a;前置 与 后置前置&#xff1a;返回1之后的结果后置&#xff1a; Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&…...

沁恒CH32V30X学习笔记04--外部中断

外部中断 CH32V2x 和 CH32V3x 系列内置可编程快速中断控制器(PFIC– Programmable Fast Interrupt Controller),最多支持 255 个中断向量。当前系统管理了 88 个外设中断通道和 8 个内核中断通道 PFIC 控制器 88个外设中断,每个中断请求都有独立的触发和屏蔽控制位,有专…...

基础IO[三]

close关闭之后文件内部没有数据&#xff0c; stdout和stderr 他们一起重定向&#xff0c;只会重定向号文件描述符&#xff0c;因为一号和二号描述符虽然都是sydout&#xff0c;但是并不一样&#xff0c;而是相当于一个显示器被打开了2次。 分别重定向到2个文件的写法和直接写道…...