【C++】——多态详解
目录
1、什么是多态?
2、多态的定义及实现
2.1多态的构成条件
2.2多态语法细节处理
2.3协变
2.4析构函数的重写
2.5C++11 override 和 final关键字
2.6重载—重写—隐藏的对比分析
3、纯虚函数和抽象类
4、多态的原理分析
4.1多态是如何实现的
4.2虚函数表
面向对象技术(OOP)的核心思想就是封装,继承和多态。通过之前的学习,我们认识到什么是封装,什么是继承。
封装就是对将一些属性装载到一个类对象中,不受外界的影响,实现一个独立性很强的模块,比如:洗衣机就是对洗衣服功能,甩干功能,漂洗功能等的封装,其功能不会受到外界的微波炉影响。
继承就是可以将类对象进行继承,派生类会继承基类的方法与属性,提高开发效率并降低维护成本,类似父与子的关系。比如蔬菜和黄瓜,黄瓜就有蔬菜的特性。
接下来我们就来学习认识多态!!
1、什么是多态?
多态是面向对象技术(OOP)的核心思想之一,我们称具有继承关系的多个类型称为多态类型,通俗来讲:就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
多态分为编译时多态(静态多态)和运行时多态(动态多态),编译时多态(静态多态)主要就是函数重载和函数模板,他们传不同类型的参数就可以调用不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在编译时完成的,通常我们把编译时一般归为静态,运行时归为动态。
运行时多态,具体点就是去完成某个行为(函数),可以传不同的对象就会完成不同的行为,就达到多种形态。比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是优惠买票(5折或75折);军人买票时是优先买票。
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。
class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-打折" << endl; }
};
void Func(Person* ptr)
{// 这里可以看到虽然都是Person指针Ptr在调⽤BuyTicket // 但是跟ptr没关系,而是由ptr指向的对象决定的。 ptr->BuyTicket();
}
int main()
{Person ps;Student st;Func(&ps);Func(&st);return 0;
}
Student继承了Person,Person对象买票全价,而Student对象买票半价
原理是什么呢??
- 多态调用:运行时,到指定对象的虚表中找虚函数来调用(指向基类调用基类的虚函数,指向子类调用子类的虚函数)
- 普通调用:编译时,调用对象是哪个类型,就调用它的函数。
一看还挺复杂,接下来我们就来从零学习多态。
2、多态的定义及实现
2.1多态的构成条件
第一大前提!!只有继承情况下才有虚函数,才能实现多态!!!
多态构成的条件:
<1>必须通过基类的指针或者引用调用虚函数(virtual修饰的类成员函数)
<2>被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写(父子虚函数要求三同)
- 虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数
补充说明:要实现多态效果,第一必须是基类的指针或引用,因为只有基类的指针或引用才能既指向派生类对象也可以指向基类对象(继承切片);第二派生类必须对基类的虚函数重写/覆盖,重写或者覆盖了,派生类才能有不同的函数,多态的不同形态效果才能达到。
2.2多态语法细节处理
- 派生类(基类必须写)的对应函数可以不写
virtual
(这个语法点非常奇怪!建议写上virtual
) - “重写”的本质是重新写函数的实现,函数声明(包括缺省参数的值)参考基类的缺省值,与基类保持一致
看一道经典面试题:
#include<iostream>using namespace std;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(int argc, char* argv[])
{B* p = new B;p->test();return 0;
}
A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确
答案是:B 为什么呢?
- 首先
- A类与B是继承关系
- func函数是虚函数(B类是派生类,可以不写virtual),并且A和B中满足三同构成多态
- test定义在基类A中函数的默认参数是基类指针(
A* this
成员函数的默认参数),满足多态条件
- 其次
- 主函数中调用test函数,因为B是子类,没有test函数,所以会在父类A中寻找。
- test函数调用 func函数满足多态虚函数,基类参数this指向的是B类(指向谁调用谁),所以就会调用B类的func函数
B->
- 重写的本质是对函数的实现进行重写,函数的结构部分(包括参数,缺省值,函数名,返回值等)与基类一致。所以是
1
2.3协变
多态的条件有着父子虚函数要求三同,但是有一个特殊情况就是协变!
协变:派生类重写基类虚函数时,与基类虚函数返回值类型不同:
- 基类虚函数返回基类对象的指针或者引用
- 派生类虚函数返回派生类对象的指针或者引用
举个例子:
class A {};
class B : public A {};
//返回类型不同但是仍构成多态
class Person {
public://virtual Person* BuyTicket() 自己的类型也是可以只要遵循 基类对应基类virtual A* BuyTicket() //派生类对应派生类即可{cout << "买票-全价" << endl;return nullptr;}
};
class Student : public Person {
public://virtual Student* BuyTicket()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.4析构函数的重写
基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同看起来不符合重写的规则,是因为析构函数在编译阶段都会转换成:destructor()
,所以表面析构函数名字不同,但是实质上是一致的。这样就会构成多态。那么为什么有这样的细节处理呢??
正常情况下的析构:
class A
{
public:~A(){cout << "~A()" << endl;}
};
class B :public A
{
public:~B(){cout << "~B()" << endl;}
};
int main()
{A a; B b;return 0;
}
这样会正常的调用析构函数(子类析构会自动调用父类析构->先子后父)
那如果是指针或者引用类型呢?
class A
{
public:~A(){cout << "~A()" << endl;}
};
class B :public A
{
public:~B(){cout << "~B()" << endl;}
};
int main()
{//A a;//A b;//基类指针既可以指向 基类也可以指向派生类 A* a = new A;//切片指向对应内容A* b = new B;delete a;delete b;return 0;
}
竟然只是调用了基类的析构函数??那么派生类的析构函数怎么调用呢?
如果不解决这个问题就会导致内存泄漏
class A
{
public:~A(){cout << "~A()" << endl;}
};
class B :public A
{
public:~B(){cout << "~B()->delete" << endl;delete _p;}int* _p = new int[10000000];
};
int main()
{for (int i = 0; i < 100000; i++){A* p = new B;delete p;}return 0;
}
可以看到派生类B有着资源申请,析构后需要释放空间,但是按照上一个例子调用的是基类的析构函数,那么空间得不到释放就会造成内存泄漏!
这就十分危险了!!!
而我们所希望的是指向谁就调用谁的析构:指向基类调用基类析构,指向派生类调用派生类析构。
那我们怎么做到呢???当然就是多态!!!
那我们来看看现在满不满足多态的条件:
- 必须通过基类的指针或者引用调用虚函数(virtual修饰的类成员函数)
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写(父子虚函数要求三同)
在编译的时候,析构函数都会变成destructor
,这样满足三同!构成重写(甚至可以理解为,编译器处理析构函数为 destructor 就是为了实现多态)
那么只需要最后一步,将析构函数变为虚函数即可!
class A
{
public:virtual ~A(){cout << "~A()" << endl;}
};
class B :public A
{
public:virtual ~B()//此处可以不加 virtual 最好对应加上{cout << "~B()->delete" << endl;delete _p;}int* _p = new int[10000000];
};
可以看到实现了我们的目的:指向基类调用基类析构,指向派生类调用派生类析构,应该释放的空间也全部释放了!
所以建议析构函数设置为虚函数,避免出现上述的情况。
2.5C++11 override 和 final关键字
从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重写,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。
<1>final
- 修饰类(最终类),表示该类不能被继承。(C++98使用private来做到不能继承)
class car final { };
- 修饰虚函数,表示该虚函数不能再被继承。
virtual void func() final { }
<2>
override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
// error C3248: “Car::Drive”: 声明为“final”的函数⽆法被“Benz::Drive”重写
class Car
{
public:virtual void Drive() final {}
};class Benz :public Car
{
public:virtual void Drive() { cout << "Benz-舒适" << endl; }
};
2.6重载—重写—隐藏的对比分析
<1>重载
- 两个函数作用在同一作用域
- 函数名相同,参数不同,类型或个数不同,返回值无要求
<2>重写
- 两个函数分别在基类作用域和派生类作用域
- 函数名、参数、返回值都一样(协变例外)本质:函数体不同
- 两个函数必须是虚函数
<3>隐藏
- 两个函数分别在基类作用域和派生类作用域
- 仅仅函数名相同就可以构成
- 两个基类和派生类的同名函数不是重写就是重定义
3、纯虚函数和抽象类
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。纯虚函数是不需要定义实现的(实现没啥意义因为要被派生类重写,但是语法上可以实现),只要声明即可。
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后(不重写纯虚函数)也不能实例化出对象。只有重写纯虚函数,派生类才能实例化出对象。
纯虚函数某种程度上强制了派生类重写虚函数,因为不重写实例化不出对象,另外纯虚函数更体现出了接口继承。
class Car
{
public:virtual void Drive() = 0;
};
class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};
class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};
int main()
{// 编译报错:error C2259: “Car”: ⽆法实例化抽象类 Car car;Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();return 0;
}
抽象类与override关键字的区别:
- 抽象类间接强制了派生类必须进行虚函数重写
- override是在已经重写的情况下,帮助进行重写的语法检查
4、多态的原理分析
4.1多态是如何实现的
多态类和普通类的区别是什么呢?——虚函数!
先来看看具有虚函数的类的大小:
//32位环境
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;
}
如果是没有虚函数Func1的话,那么就是 4+1 然后对齐一下8个字节。
运行一下:
Base的大小在x86环境下是12字节。这十二字节是怎么组成的呢?
首先类里面有一个虚函数表指针_vfptr
:
只要有虚函数就会存在虚表指针!这个是实现多态的关键之处!
增加几个函数调试一下
Base里面有虚函数,存了一个虚表指针,这个指针指向虚函数表(函数指针数组),该表存储着虚函数的地址
用例子结合虚函数表解释一下:
class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
protected:string _name;
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-打折" << endl; }
protected:int _id;
};
class Soldier : public Person {
public:virtual void BuyTicket() { cout << "买票-优先" << endl; }
protected:string _codename;//代号
};
void Func(Person* ptr)
{// 这⾥可以看到虽然都是Person指针Ptr在调用BuyTicket // 但是跟ptr没关系,⽽是由ptr指向的对象决定的。 ptr->BuyTicket();
}
int main()
{// 其次多态不仅仅发生在派生类对象之间,多个派生类继承基类,重写虚函数后 // 多态也会发生在多个派生类之间。 Person ps;Student st;Soldier sr;Func(&ps);Func(&st);Func(&sr);return 0;
}
如何实现指向谁调用谁呢??
那么如何实现传基类调用基类的虚函数,传派生类调用派生类的虚函数?
当然是使用切片了!
1. 首先每个实例化的类(如果有虚函数)会有一个虚函数表。
2. 传基类调用基类的虚函数,就正常在基类虚表中寻找其对应函数
3. 传派生类,因为多态函数是基类的指针,那么就会切片出来一个基类部分(虚函数表是派生类的),那么就会在派生类虚表调用对应虚函数。
汇编层次是如何表现的呢?具体的汇编语言实现还是比较直白的。
动态绑定与静态绑定:
- 对不满足多态条件(指针或者引用+调用虚函数)的函数调用是在编译时绑定,也就是编译时确定调用函数的地址,叫做静态绑定。
- 满足多态条件的函数调用是在运行时绑定,也就是在运行时到指向对象的虚函数表中找到调用函数的地址,叫做动态绑定。
// ptr是指针+BuyTicket是虚函数满⾜多态条件。 // 这⾥就是动态绑定,编译在运⾏时到ptr指向对象的虚函数表中确定调⽤函数地址 ptr->BuyTicket();00EF2001 mov eax,dword ptr [ptr] 00EF2004 mov edx,dword ptr [eax] 00EF2006 mov esi,esp 00EF2008 mov ecx,dword ptr [ptr] 00EF200B mov eax,dword ptr [edx] 00EF200D call eax// BuyTicket不是虚函数,不满⾜多态条件。 // 这⾥就是静态绑定,编译器直接确定调⽤函数地址 ptr->BuyTicket();00EA2C91 mov ecx,dword ptr [ptr] 00EA2C94 call Student::Student (0EA153Ch)
可以对比汇编层次理解分析
4.2虚函数表
class Base {
public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }void func5() { cout << "Base::func5" << endl; }
protected:int a = 1;
};
class Derive : public Base
{
public:// 重写基类的func1 virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func1" << endl; }//派生类自己的虚函数void func4() { cout << "Derive::func4" << endl; }
protected:int b = 2;
};int main()
{Base b1;Base b2;Derive d;return 0;
}
- 基类对象的虚函数表中存放基类所有虚函数的地址。(同类型对象虚表共用,不同类型对象虚表各自独立)
- 派生类由两部分构成,继承下来的基类和自己的成员,一般情况下,继承下来的基类中有虚函数表指针,自己就不会再生成虚函数表指针。但是要注意的这里继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同⼀个,就像基类对象的成员和派生类对象中的基类对象成员也是独立的。
- 派生类中重写的基类的虚函数,派生类的虚函数表中对应的虚函数(与基类相同)就会被覆盖成派生类重写的虚函数地址。
- 派生类的虚函数表中包含,基类的虚函数地址,派生类重写的(覆盖的)虚函数地址,派生类自己的虚函数地址三个部分。
- 虚函数表本质是⼀个存虚函数指针的指针数组,一般情况这个数组最后面放了一个0x00000000标记。(这个C++并没有进行规定,各个编译器自行定义的,vs系列编译器会再后面放个0x00000000 标记,g++系列编译不会放)
虚函数和虚函数表存放在哪里呢??
注意:虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在代码段的,只是虚函数的地址又存到了虚函数表中。
那么虚函数表存放在哪呢?这个问题严格说并没有标准答案 C++标准并没有规定,我们可以验证一下:
class Base {
public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }void func5() { cout << "Base::func5" << endl; }
protected:int a = 1;
};
class Derive : public Base
{
public:// 重写基类的func1 virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func1" << endl; }void func4() { cout << "Derive::func4" << endl; }
protected:int b = 2;
};
int main()
{int i = 0;static int j = 1;int* p1 = new int;const char* p2 = "xxxxxxxx";printf("栈:%p\n", &i);printf("静态区:%p\n", &j);printf("堆:%p\n", p1);printf("常量区:%p\n", p2);Base b;Derive d;Base* p3 = &b;Derive* p4 = &d;printf("Person虚表地址:%p\n", *(int*)p3);printf("Student虚表地址:%p\n", *(int*)p4);printf("虚函数地址:%p\n", &Base::func1);printf("普通函数地址:%p\n", &Base::func5);return 0;
}
这里是怎么拿到虚表地址的呢?
强制转换为 int*之后再解引用拿到前四个字节
对比一下可以看到虚表地址与常量区最接近,那可以推断出虚表储存在常量区!!!(vs2022编译环境)
相关文章:

【C++】——多态详解
目录 1、什么是多态? 2、多态的定义及实现 2.1多态的构成条件 2.2多态语法细节处理 2.3协变 2.4析构函数的重写 2.5C11 override 和 final关键字 2.6重载—重写—隐藏的对比分析 3、纯虚函数和抽象类 4、多态的原理分析 4.1多态是如何实现的 4.2虚函数…...

STM32上实现FFT算法精准测量正弦波信号的幅值、频率和相位差(标准库)
在研究声音、电力或任何形式的波形时,我们常常需要穿过表面看本质。FFT(快速傅里叶变换)就是这样一种强大的工具,它能够揭示隐藏在复杂信号背后的频率成分。本文将带你走进FFT的世界,了解它是如何将时域信号转化为频域…...

计算机毕业设计 农场投入品运营管理系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试
🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点…...

【笔记】2.1 半导体三极管(BJT,Bipolar Junction Transistor)
一、结构和符号 1. 三极管结构 常用的三极管的结构有硅平面管和锗合金管两种类型。各有PNP型和NPN型两种结构。 左图是NPN型硅平面三极管,右图是PNP型锗合金三极管。 从图中可见平面型三极管是先在一块大的金属板上注入杂质使之变成N型,然后再在中间注入杂质使之变成P型,…...

企业中文档团队的三种组织形式
大家好!今天咱们来聊聊企业里文档团队都是怎么组织的。 企业中,常见的文档团队组织形式有三种。 首先,最常见的就是集中式的文档团队。就是说,公司里头有几个不同的部门,每个部门都需要做文档。于是呢,公…...

古诗词四首鉴赏
1、出自蓟北门行 唐李白 虏阵横北荒,胡星曜精芒。 羽书速惊电,烽火昼连光。 虎竹救边急,戎车森已行。 明主不安席,按剑心飞扬。 推毂出猛将,连旗登战场。 兵威冲绝漠,杀气凌穹苍。…...

全国行政区划下载(高德、阿里、天地图)
一、网站链接: 阿里云数据可视化平台: DataV.GeoAtlas地理小工具系列 (aliyun.com) 链接: link 高德地图API获取行政区域: 高德地图API获取行政区域 (naivemap.com) 链接: link 天地图服务中心: 天地图 服务中心 (tianditu.g…...

Springboot提升-MapStruct组件使用
文章目录 1. 添加依赖2. 创建映射接口3. 在Spring Boot中使用MapStruct映射器4. 配置MapStruct 在Spring Boot项目中使用MapStruct可以帮助你更方便地管理对象之间的映射逻辑。下面是一些基本步骤来设置和使用MapStruct: 1. 添加依赖 首先,你需要在项目…...

如何借助ChatGPT提升论文质量:实战指南
在学术写作的过程中,非英语母语人士经常面临诸多挑战,尤其是当论文要提交给国际期刊时,语言规范和表达逻辑成为了必须克服的障碍。本文将通过实例详细解析如何利用ChatGPT来润色论文,使其达到发表级别的标准。 一、优秀学术论文的写作特点 要让学术论文在国际期刊上发表,…...

NLP开端:Tokenizer-文本向量化
Tokenizer 问题背景 An was a algorithm engineer 如上所示,在自然语言处理任务中,通常输入处理的数据是原始文本。但是算法模型自能处理数值类型,因此需要找到一种方法,将原始的文本数据转换为数值类型的数据。这就是分词器所…...

STM32 MCU学习资源
STM32 MCU学习资源 文档下载需要注册登录账号 ST公司官方文档 STM32 MCU开发者资源 STM32F446 相关PDF文档 ST中文论坛 中文译文资料 ST MCU中文官网 其他学习资源 野火STM32库开发实战指南 零基础快速上手STM32开发(手把手保姆级教程) 直接使…...

Python知识点:Python内存管理与优化
开篇,先说一个好消息,截止到2025年1月1日前,翻到文末找到我,赠送定制版的开题报告和任务书,先到先得!过期不候! Python内存管理与优化指南 Python是一种动态类型的解释型语言,它提…...

SpringBoot Kafka发送消息与接收消息实例
前言 Kafka的基本工作原理 我们将消息的发布(publish)称作 producer(生产者),将消息的订阅(subscribe)表述为 consumer(消费者),将中间的存储阵列称作 broker(代理),这…...

【资料分析】刷题日记2
第一套 √ 2013-2016一共有13,14,15,16四年,亦即16 - 13 1 4年 √ 是多少倍 ③vs④:都是只给出了年均增速,③求的是其中一年的,无法确定;④求的是这个时段总共的,可…...

Aigtek功率放大器怎么选择型号
功率放大器在各个领域中都扮演着重要的角色,用于增强信号的幅度,以满足特定的需求。在选择功率放大器型号时,需要综合考虑多个因素,如应用领域、功率要求、频率范围、输入输出特性等。下面安泰电子官网将从这些方面详细介绍功率放…...

【RabbitMQ】重试机制、TTL
重试机制 在消息从Broker到消费者的传递过程中,可能会遇到各种问题,如网络故障、服务不可用、资源不足等,这些问题都可能导致消息处理失败。为了解决这些问题,RabbitMQ提供了重试机制,允许消息在处理失败之后重新发送…...

Linux用户及用户组操作命令笔记
1.用户概念及作用 用户:指的是Linux操作系统中用于管理系统或者服务的人 Linux下一切皆文件,所以用户管理的是相应的文件 基本上分为两种: 基本管理:文件的创建、删除、复制、查找、打包压缩等;文件的权限增加、减…...

threejs加载高度图渲染点云,不支持tiff
问题点 使用的point来渲染高度图点云,大数据图片无效渲染点多(可以通过八叉树过滤掉无效点增加效率,这个太复杂),但是胜在简单能用 效果图 code 代码可运行,无需npm <!DOCTYPE html> <html la…...

MySQL面试题——第二篇
1. MySQL的优化手段有哪些? MySQL的常见的优化手段有以下五种 1. 查询优化 避免select * ,只查询需要的字段。小表驱动大表,即小的数据集驱动大的数据集,比如当B表的数据集小于A表时,用in优化exist。两表执行顺序是先查B表&#x…...

Unity Transform 组件
在 Unity 中,Transform 是一个非常重要的组件,它定义了物体的位置、旋转和缩放,几乎每个 GameObject 都包含一个 Transform 组件。Transform 组件的主要属性如下: 1. position 表示物体在世界空间中的位置。可以通过 transf…...

LeeCode 3. 无重复字符的最长子串
经典方法滑动窗口:(两个指针) 针对这个题我们首先假定两个指针 left 和 right 分别指在数组最左端. 然后两个变量记录长度length和maxlength. 并且因为不能有重复的字符,我们使用HashSet结构来当收集结果的表. 随着右指针不断往右移,左指针和右指针之间的就为截取的字符,而这…...

使用canal.deployer-1.1.7和canal.adapter-1.1.7实现mysql数据同步
1、下载地址 --查看是否开启bin_log日志,value on表示开启 SHOW VARIABLES LIKE log_bin; -- 查看bin_log日志文件 SHOW BINARY LOGS; --查看bin_log写入状态 SHOW MASTER STATUS; --查看bin_log存储格式 row SHOW VARIABLES LIKE binlog_format; --查看数据库服…...

VMware Workstation Pro 17下载及安装教程
下载 好消息!从VMware Workstation Pro 17开始,个人可以免费使用了,再也不需要找破解激活码啥的了。 但是坏处却不小:其下载变得异常复杂。首先需要注册账号,外网非常慢很可能注册不上;其次根本找不到下载…...

集采良药:从“天价神药”到低价良药,伊马替尼的真实世界研究!
在医疗科技日新月异的今天,有一种药物以其卓越的疗效和深远的影响力,成为了众多患者心中的“精准武器”——伊马替尼。这款药物不仅在慢性髓细胞白血病(CML)的治疗上屡创佳绩,更是胃肠道间质瘤(GIST&#x…...

00898 互联网软件应用与开发自考复习题
资料来自互联网软件应用与开发大纲 南京航空航天大学 高纲4295和JSP 应用与开发技术(第 3 版) 马建红、李学相 清华大学出版社2019年 第一章 一、选择题 通过Internet发送请求消息和响应消息使用()网络协议。 FTP B. TCP/IP C. HTTP D. DNS Web应…...

linux 进程间通信之pthread(条件变量共享和互斥锁共享)
0,互斥锁共享 初始化和销毁mutex互斥锁 int pthread_mutexattr_init(pthread_mutexattr_t *attr); int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); 进程共享属性有两种值: 1、PTHREAD_PROCESS_PRIVATE,这个是默认值(1),同一个进程中的多个线程访问同一个…...

数据结构-2.7.单链表的查找与长度计算
注:本文只探讨"带头结点"的情况(查找思路类似循环找到第i-1 个结点的代码) 一.按位查找: 1.代码演示: 版本一: #include<stdio.h> #include<stdlib.h> //定义单链表结点类型 typedef struct LNo…...

iotop 命令:磁盘IO监控和诊断
一、命令简介 iotop命令用于监视磁盘I/O,实时显示每个进程或线程的读写速率等信息。非常适合用于诊断系统中的I/O瓶颈。 安装 iotop 在大多数Linux发行版中,iotop可能不是预装的。可以使用包管理器来安装它。 例如,在…...

解锁编程新境界:GitHub Copilot 让效率翻倍
Number.1:工具介绍 功能特点: 智能代码生成与补全:通过学习大量代码库和开发者的编码风格,能根据上下文自动推断可能的代码补全选项,甚至可以自动完成函数定义、循环结构等复杂代码片段。例如,当编写一个算…...

爱普生相机SD卡格式化后数据恢复指南
我借了朋友的爱普生相机,想查看一下内存,哎呀,一不小心按错了,竟然执行了格式化操作,这可真是太让人郁闷了,这还有机会挽救数据吗?心塞,求帮助! 随着数码摄影的普及&am…...