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

C++ —— 多态

目录

1.多态的概念

2.多态的定义及实现

2.1构成多态的两个硬性条件

2.2虚函数的重写

2.3override和final

3.抽象类

3.1接口继承和实现继承

 4.多态原理

4.1虚函数表

4.2原理

4.3静态绑定和动态绑定

5.单继承和多继承体系的虚函数表

5.1单继承体系的虚函数表

5.2多继承体系的虚函数表

6.继承和多态常见面试问题

6.1概念考察

6.2问答题

1.多态的概念

多态的前提的是继承。当不同的对象去完成同一种行为时会产生不同的结果就是多态的通俗意义。

例如学生、成人两个对象去完成买票这个行为,那么学生的结果是获得半价,而成人获得的结果的是全价。

2.多态的定义及实现

2.1构成多态的两个硬性条件

调用的函数必须是虚函数,且派生类必须对虚函数重写;必须是基类的引用或指针调用虚函数。

虚函数就是在成员函数之前加一virtual关键字。虚函数的目的就是为了实现多态

下面的程序是一个多态的案例:

class Person
{
public:virtual void print(){cout << "Person::全价" << endl;}
};class Student : public Person
{
public:virtual void print(){cout << "Student::半价" << endl;}
};void test(Person* ptr)	//传入的对象不同调用不同的虚函数
{ptr->print();	//必须是基类的指针或引用调用虚函数
}
int main()
{Student s;Person p;test(&p);test(&s);return 0;
}

2.2虚函数的重写

派生类中有一个与基类完全相同的虚函数(返回值、函数名、参数),但是定义是派生类想要的,这样的,称派生类完成了对基类虚函数的重写(覆盖)。

派生类的重写虚函数不一定非要加virtual关键字,因为基类被继承之后其虚函数依旧保持其原有的虚函数属性,但是这样的代码风格是不规范的。

class Student : public Person
{
public:void print()	//不加virtual关键字也可以{cout << "Student::半价" << endl;}
};

如果派生类没有对虚函数进行重写,那么派生类依然有基类原有的虚函数。

class Student : public Person
{//派生类没有重写基类的虚函数//那么派生类依然持有基类原有的虚函数
};

虚函数重写的两个例外:

        1.协变(基类虚函数和派生类虚函数的返回值不同)

基类虚函数可以返回任意具有继承关系的基类的指针或引用;派生类虚函数可以返回任意具有继承关系的派生类的指针或引用;前两者的返回值必须是同一个继承系统。

class A
{};
class B : public A
{};// 基类虚函数可以返回任意具有继承关系的基类的指针或引用
class Person
{
public:virtual A* print(){cout << "Person::全价" << endl;return nullptr;}
};// 派生类虚函数可以返回任意具有继承关系的派生类的指针或引用
class Student : public Person
{
public:virtual B* print(){cout << "Student::半价" << endl;return nullptr;}
};

        2.派生类的析构函数与基类的析构函数构成重写关系

如果基类的析构函数是虚函数,那么派生类的析构函数无论是否带有virtual关键字都与基类的析构函数构成重写。其原因在于:在多态系统中,编译器会自动将基类和派生类的析构函数解析为同名的destructor()函数。必须让派生类的析构函数与基类的析构函数构成重写关系的原因如下段代码所示:

class A
{
public:virtual ~A(){cout << "A::~A()" << endl;}
};class B : public A
{
public:~B(){cout << "B:~B()" << endl;}
};int main()
{// 如果基类和派生类的析构函数不构成多态系统// 那么在清理资源时就会产生重复释放A* pa = new A;A* pb = new B;delete pa;delete pb;return 0;
}

2.3override和final

C++对重写的要求比较严格,我们可以借助override和final这两个关键字检测派生类是否完成对基类虚函数的重写。

        1.final放在类名后面,表示此类不能被继承:

class A final
{};class B : public A	//错误,A类不能被继承
{};

        2.final修饰虚函数,表明该虚函数不能被重写:

class Car {
public:virtual void Drive () final{}
};class Benz :public Car {
public:virtual void Drive() //错误,此虚函数不能被重写{ cout << "Benz-舒适" << endl; }
};

        3.override可以检查派生类虚函数是否完成对基类虚函数的重写:

class Car {
public:virtual void Drive () const{}
};class Benz :public Car {
public:// 报错,多余Drive函数,派生类没有正确重写virtual void Drive() override{}
};

3.抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口
类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生
类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

class Car
{
public:virtual void Drive() = 0;
};class Benz :public Car
{
public:
};int main()
{Benz b;	//错误,Benz类没有完成基类纯虚函数的重写return 0;
}

3.1接口继承和实现继承

普通成员函数的继承是一种实现继承,派生类继承了基类普通成员函数的所有东西(返回值、函数名、参数列表、函数定义)。虚函数的继承是一种接口继承,派生类只继承了基类虚函数的接口(返回值、函数名、参数列表),其目的就是为了派生类能够对虚函数进行重写(即使没有对基类虚函数进行重写,派生类依然保持基类原有虚函数,这是特性)。

下面是一道练习题:

// 下面程序输出结果是什么?
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 : 以上都不正确

解析:使用派生类指针p指向派生类对象,那么这是一次普通函数调用。调用的函数是基类继承到派生类的虚函数,但是派生类没有对此函数完成重写,所以调用func函数时的this指针是基类指针,是一次多态调用。这个基类指针指向的是派生类对象(外部new了一个派生类对象),所以调用的是派生类重写之后的func函数,又因为接口继承,所以派生类的func函数的参数的缺省值为1,所以最终结果为B->1。

 4.多态原理

4.1虚函数表

我们使用一段代码来引用出虚函数表:

// 32位平台下,sizeof(Base)是多少?
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};int main()
{cout << sizeof(Base) << endl;return 0;
}

解析:如果没有Func1这个虚函数,那么答案绝对是4。但是这里的输出为8。其原因在于,类中的虚函数需要放到虚函数表中,一个含有虚函数的类至少有一个虚函数表的指针,虚函数表也称虚表。所以一个含有虚函数的类,不仅仅存放了成员变量,还存放了一个虚表指针。

我们使用更为复杂的代码往下分析: 

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.派生类对象由两部分构成,一部分是自己的成员,另一部分是基类成员;派生类对象也有也有一个虚表指针,这个指针存放在基类部分当中。

        2.基类对象和派生类对象虚表是不一样的,这里我们发现派生类完成了Func1的重写,所以派生类对象的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖。覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。

        3.非虚函数的函数地址不会放入虚表中。

        4.虚函数表本质是一个存放虚函数指针的指针数组(函数指针数组),一般情况这个数组以nullptr结尾(VS环境下,Linux可能不一样)。

        5.派生类的虚函数表是怎样生成的?先将基类虚表内容拷贝一份至派生类的虚表当中(因为这两个虚表的地址不一样,所以可以断定派生类的虚表是自己生成的)。如果派生类完成了对某个虚函数的重写,就会将重写的虚函数的地址放入虚表的对应位置。派生类自己新增的虚函数会依照声明顺序增加到派生类虚表的后面。

4.2原理

class Person
{
public:virtual void print(){cout << "Person::全价" << endl;}
};class Student : public Person
{
public:virtual void print(){cout << "Student::半价" << endl;}
};void test(Person& ptr)	//传入的对象不同调用不同的虚函数
{ptr.print();	//必须是基类的指针或引用调用虚函数
}
int main()
{Student s;Person p;test(p);test(s);return 0;
}

为什么根据传入对象的不同就能调用不同的虚函数?无论基类的指针或引用绑定的对象是基类对象还是派生类对象,他们都是没有区别的(绑定到派生类对象时会发生切片动作),所以在编译的时候无法确定基类的指针或引用到底绑定的是基类对象还是派生类对象中的基类部分,但是这是正确的,所以编译会通过。当需要调用虚函数时,根本无法确定调用的是哪个虚函数,所以只能在程序运行时去虚函数表当中找被调用虚函数的地址。此时无论基类指针或引用绑定的对象是谁,只需要根据虚表内容不同,调用不同的虚函数即可。

4.3静态绑定和动态绑定

        1.静态绑定:静态绑定在程序编译阶段就确定了行为,也称静态多态,例如函数重载。

        2.动态绑定:动态绑定在编译阶段无法确定行为,只能在程序运行期间根据具体类型调用具体的函数,也称动态多态。

5.单继承和多继承体系的虚函数表

5.1单继承体系的虚函数表

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; }
private:int b;
};int main()
{Derive d;return 0;
}

观察监视窗口发现:派生类的虚函数表中并没有func3函数的地址。是不是前面的理论是错误的?

其实不然,我们可以将虚表内容打印出来:

class Base {
public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }
private:int a = 1;
};class Derive :public Base {
public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }
private:int b = 2;
};typedef void (*VFPTR)();void print_VFtable(VFPTR vf[])
{cout << "虚表地址:0x" << vf << endl;for (int i = 0; vf[i] != nullptr; i++){printf("[%d]:0x%p->", i, vf[i]);vf[i]();}cout << endl;
}
int main()
{Base b;Derive d;// 对象的前4/8个字节存放的是虚表指针VFPTR* vfb = (VFPTR*)(*(int*)&b);print_VFtable(vfb);VFPTR* vfd = (VFPTR*)(*(int*)&d);print_VFtable(vfd);return 0;
}

 结论:派生类新增的虚函数的地址会放入派生类的虚表中,但是监视窗口不能体现出来

5.2多继承体系的虚函数表

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;
};typedef void (*VFPTR)();void print_VFtable(VFPTR vf[])
{cout << "虚表地址:0x" << vf << endl;for (int i = 0; vf[i] != nullptr; i++){printf("[%d]:0x%p->", i, vf[i]);vf[i]();}cout << endl;
}
int main()
{Base1 b1;Base2 b2;Derive d;// 对象的前4/8个字节存放的是虚表指针VFPTR* vfb1 = (VFPTR*)(*(void**)&b1);print_VFtable(vfb1);	//打印b1对象的虚表VFPTR* vfb2 = (VFPTR*)(*(void**)&b2);print_VFtable(vfb2);	//打印b2对象的虚表VFPTR* vfd1 = (VFPTR*)(*(void**)&d);print_VFtable(vfd1);	//打印d对象的第一张虚表VFPTR* vfd2 = (VFPTR*)(*(void**)((char*)&d + sizeof(Base1)));print_VFtable(vfd2);	//打印d对象的第二张虚表return 0;
}

结论:派生类重写虚函数时是对多个基类的虚函数重写;派生类自己定义的虚函数的地址存放在派生类的第一张虚表当中。

6.继承和多态常见面试问题

6.1概念考察

1. 下面哪种面向对象的方法可以让你变得富有( )
A: 继承 B: 封装 C: 多态 D: 抽象

答案:A。继承可以定义一个与其他类相似的一个新类,并且这两个类保持一定的联系。

2. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的调用则可以关联于具体的对象。
A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定
 答案:D。多态(动态绑定)可以实现题目描述的效果。

3. 面向对象设计中的继承和组合,下面说法错误的是?()
A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复
用,也称为白盒复用
B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动
态复用,也称为黑盒复用
C:优先使用继承,而不是组合,是面向对象设计的第二原则
D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封
装性的表现
 答案:C。优先应使用组合,因为组合并没有破坏原有类的封装性且耦合度较低。

4. 以下关于纯虚函数的说法,正确的是( )
A:声明纯虚函数的类不能实例化出对象

B:声明纯虚函数的类是虚基类

C:派生类必须实现基类的纯虚函数

D:纯虚函数必须是空函数

答案:A。B中的虚基类是虚继承的概念。

5.关于虚函数的描述正确的是()

A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型

B:内联函数不能是虚函数

C:派生类必须重新定义基类的虚函数

D:虚函数可以是一个static型的函数 

答案:B。虚函数的调用必须通过虚表,如果是内联函数就破坏了这个行为。D中的说法是错误的,因为static函数没有this指针。

6. 关于虚表说法正确的是( )
A:一个类只能有一张虚表
B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
C:虚表是在运行期间动态生成的
D:一个类的不同对象共享该类的虚表
答案:D。虚表也是存放在代码区的,与成员函数一样。

7. 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )
A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址
B:A类对象和B类对象前4个字节存储的都是虚基表的地址
C:A类对象和B类对象前4个字节存储的虚表地址相同
D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表
答案:D。B中虚基表的说法是错误的。

8. 下面程序输出结果是什么? ()

#include<iostream>
using namespace std;
class A {
public:A(const char* s) { cout << s << endl; }~A() {}
};
class B :virtual public A
{
public:B(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class C :virtual public A
{
public:C(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class D :public B, public C
{
public:D(const char* s1, const char* s2, const char* s3, const char* s4) :B(s1, s2), C(s1, s3), A(s1){cout << s4 << endl;}
};
int main() {D* p = new D("class A", "class B", "class C", "class D");delete p;return 0;
}

 A:class A class B class C class D         B:class D class B class C class A
C:class D class C class B class A         D:class A class C class B class D

答案:A。因为是菱形虚拟继承,所以A类成员是B类和C类共享的,所以B和C两个类其中任意一个去初始化A都不合适(它们两个的构造函数显示调用A的构造函数是因为或许在某个场景下需要单独实例化B或C的对象),所以初始化A的任务只能放给D了。初始化列表的初始化顺序与声明顺序有关,很明显:D类是先继承B再继承C,而在继承B之前B已经继承A了,所以初始化顺序为,A->B->C->D。

9. 多继承中指针偏移问题?下面说法正确的是( )

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main(){
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
return 0;
}

A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3
答案:C。Derive类对象模型可以是下面这样:

10. 以下程序输出结果是什么()

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。上面已经讲解过。

6.2问答题

1. 什么是多态?答:参考本篇博客内容。

2. 什么是重载、重写(覆盖)、重定义(隐藏)?答:参考本篇博客内容与上一篇继承博客。

3. 多态的实现原理?答:参考本篇博客内容。

4. 虚函数可以声明为inline函数吗?答:可以,不过编译器会忽略内联属性,因为虚函数不能做内联函数(inline是一个建议选项)。

5. 静态成员可以是虚函数吗?答:不能,因为静态成员函数没有this指针,使用类作用域限定符直接访问函数无法访问虚表(通过指针运算访问),所以静态成员函数不能被放进虚表。

6. 构造函数可以是虚函数吗?答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的

7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?答:可以。在继承体系中,外部有动态释放的资源时。

8. 对象访问普通函数快还是虚函数更快?答:如果非多态调用,二者一样快;如果是多态调用,虚函数的访问比普通函数慢,因为调用虚函数需要经过虚表。

9. 虚函数表是在什么阶段生成的,存在哪的?答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。

10. C++菱形继承的问题?虚继承的原理?答:参考继承博客。注意虚基表是虚继承的概念,虚基表存放的是偏移量;虚表是多态的概念,存放的是虚函数的地址。

11. 什么是抽象类?抽象类的作用?答:参考本篇博客内容。

 

相关文章:

C++ —— 多态

目录 1.多态的概念 2.多态的定义及实现 2.1构成多态的两个硬性条件 2.2虚函数的重写 2.3override和final 3.抽象类 3.1接口继承和实现继承 4.多态原理 4.1虚函数表 4.2原理 4.3静态绑定和动态绑定 5.单继承和多继承体系的虚函数表 5.1单继承体系的虚函数表 5.2多继…...

java agent设计开发概要

agent开发设计 agent 开发的一些心得&#xff0c;适合熟悉agent或者有agent开发需求的同学 1 有个基础的agent&#xff0c;是java 标准的agent。这是agent代码入口 2 设计包结构&#xff0c; 基础agent agent下有plugin,加载plugin可以自己定义一个类加载器 plugin&#xff1…...

node.js笔记-模块化(commonJS规范),包与npm(Node Package Manager)

目录 模块化 node.js中模块的分类 模块的加载方式 模块作用域 向外共享模块作用域中的成员 向外共享成员 包与npm&#xff08;Node package Manager&#xff09; 什么是包&#xff1f; 包的来源 为什么需要包&#xff1f; 查找和下载包 npm下载和卸载包命令 配置np…...

Linux 磁盘坏块修复处理(错误:read error: Input/output error)

当磁盘出现坏块时&#xff0c;你对所关联的文件进行读取时&#xff0c;一般会出现 read error: Input/output error 这样的错误。 反过来讲&#xff0c;当你看到 read error: Input/output error 这种错误时&#xff0c;很大可能就是磁盘出现了坏块问题。 解决步骤&#xff1a…...

API 面试四连杀:接口如何设计?安全如何保证?签名如何实现?防重如何实现?

下面我们就来讨论下常用的一些API设计的安全方法&#xff0c;可能不一定是最好的&#xff0c;有更牛逼的实现方式&#xff0c;但是这篇是我自己的经验分享. 一、token 简介 Token&#xff1a;访问令牌access token, 用于接口中, 用于标识接口调用者的身份、凭证&#xff0c;减…...

操作系统题目收录(六)

1、某系统采用基于优先权的非抢占式进程调度策略&#xff0c;完成一次进程调度和进程切换的系统时间开销为1us。在T时刻就绪队列中有3个进程P1P_1P1​、P2P_2P2​和P3P_3P3​&#xff0c;其在就绪队列中的等待时间、需要的CPU时间和优先权如下表所示。若优先权值大的进程优先获…...

2023年十款开源测试开发工具推荐!

今天为大家奉献一篇测试开发工具集锦干货。在本篇文章中&#xff0c;将给大家推荐10款日常工作中经常用到的测试开发工具神器&#xff0c;涵盖了自动化测试、性能压测、流量复制、混沌测试、造数据等。 1、AutoMeter-API 自动化测试平台 AutoMeter 是一款针对分布式服务&…...

MySQL慢查询分析和性能优化

1 背景我们的业务服务随着功能规模扩大&#xff0c;用户量扩增&#xff0c;流量的不断的增长&#xff0c;经常会遇到一个问题&#xff0c;就是数据存储服务响应变慢。导致数据库服务变慢的诱因很多&#xff0c;而RD最重要的工作之一就是找到问题并解决问题。下面以MySQL为例子&…...

C++学习笔记(四)

组合、继承。委托&#xff08;类与类之间的关系&#xff09; 复合 queue类里有一个deque&#xff0c;那么他们的关系叫做复合。右上角的图表明复合的概念。上图的特例表明&#xff0c;queue中的功能都是通过调用c进行实现&#xff08;adapter&#xff09;。 复合关系下的构造和…...

【4】深度学习之Pytorch——如何使用张量处理时间序列数据集(共享自行车数据集)

表格数据 表格中的每一行都独立于其他行&#xff0c;他们的顺序页没有任何关系。并且&#xff0c;没有提供有关行之前和行之后的列编码信息。 表格类型的数据是指通过表格的形式表示的数据&#xff0c;它以行和列的方式组织数据。表格中的每一行代表一个数据项&#xff0c;每…...

mulesoft MCIA 破釜沉舟备考 2023.02.10.01

mulesoft MCIA 破釜沉舟备考 2023.02.10.01 1. What is a defining charcateristic of an integration-Platform-as-a-Service(iPaaS)?2. An application deployed to a runtime fabric environment with two cluster replicas is designed to periodically trigger of flow f…...

干货 | PCB拼板,那几条很讲究的规则!

拼板指的是将一张张小的PCB板让厂家直接给拼做成一整块。一、为什么要拼板呢&#xff0c;也就是说拼板的好处是什么&#xff1f;1.为了满足生产的需求。有些PCB板太小&#xff0c;不满足做夹具的要求&#xff0c;所以需要拼在一起进行生产。2.提高SMT贴片的焊接效率。只需要过一…...

笔试题-2023-思远半导体-数字IC设计【纯净题目版】

回到首页:2023 数字IC设计秋招复盘——数十家公司笔试题、面试实录 推荐内容:数字IC设计学习比较实用的资料推荐 题目背景 笔试时间:2022.08.20应聘岗位:数字IC设计工程师笔试时长:90min笔试平台:牛客网题目类型:填空题(2道),不定项选择题(3道),单选题(2道),问…...

canvas根据坐标点位画图形-canvas拖拽编辑单个图形形状

首先在选中图形的时候需要用鼠标右击来弹出选择框&#xff0c;实现第一个编辑节点功能 在components文件夹下新建右键菜单 RightMenu文件&#xff1a; <template><div v-show"show" class"right-menu" :style"top:this.ypx;left:this.xpx…...

JavaEE 初阶 — 确认应答机制

文章目录确认应答机制&#xff08;安全机制&#xff09;1 什么是后发先至问题1 如何解决后发先至问题确认应答机制&#xff08;安全机制&#xff09; 确认应答 是实现可靠传输的最核心机制。 这里指的 可靠传输 不是说 100% 可以把消息发给接收方&#xff0c;而是尽力而为&…...

0207 事件

事件监听事件监听版本事件类型事件概念事件在编程时系统内发生的动作或者发生的事情例子点击按钮鼠标经过拖拽鼠标事件监听&#xff08;注册事件&#xff0c;绑定事件&#xff09;让程序员检测是否有事件产生&#xff0c;一旦有事件触发&#xff0c;就立即调用一个函数做出响应…...

SpringBoot整合Swagger

目录 一、swagger介绍 二、springboot集成swagger 1、创建一个springboot-web项目 2、导入相关依赖 3、编写一个Hellow工程 4、配置swagger --->config 5、启动springboot工程 6、配置swagger信息 7、配置swagger扫描接口 8、如何设置Swagger在生产环境中使用&…...

20230210英语学习

Why Do So Many Cats Have White ‘Socks’ on Their Paws? 为什么好多猫咪脚上都“穿着白袜子”&#xff1f; If you see a house cat, the odds are high that it will have white paws, a look that many owners affectionately call "socks."But socks are rar…...

【图像处理OpenCV(C++版)】——4.5 全局直方图均衡化

前言&#xff1a; &#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; &#x1f31f;&#x1f31f;&#x1f31f; 本专栏主要结合OpenCV和C来实现一些基本的图像处理算法并详细解释各参数含义&#xff0c;适用于平时学习、工作快…...

2022年API安全研究报告

目录 导读 2022年API安全风险概况 2022年平均每月遭受攻击的API数量超21万...

【内网安全-横向移动】基于SMB协议-PsExec

目录 一、SMB协议 1、简述&#xff1a; 2、工具&#xff1a; 二、PsExec 1、简述&#xff1a; 2、使用&#xff1a; 1、常用参数&#xff1a; 2、情况&#xff1a; 3、插件 三、PsExec&#xff08;impacket&#xff09; 1、简述&#xff1a; 1、impacket&#xff1…...

whistle 一个神奇的前端调试工具(抓包\代理工具)

在进行前端开发过程中&#xff0c;我们常常需要对一些接口进行处理&#xff0c;以及当后端接口没有弄好需要我们mock一些假数据&#xff0c;针对这些场景&#xff0c;我们就可以使用whistle 来解决。首先&#xff0c;我们要知道能满足我们需求的工具有很多&#xff0c;例如&…...

node.js下载和vite项目创建以及可能遇到的错误

目录 一、node.js的下载 1、去官网下载 节点.js (nodejs.org) 2、下载过程 第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a; 第四步&#xff1a; 第五步: 二、vite项目的创建&#xff08;使用的工具是Hbuilder x&#xff09; 第一步&#xff1a; 出现报错…...

如何使用python画一个爱心

1 问题 如何使用python画一个爱心。 2 方法 桌面新建一个文本文档&#xff0c;文件后缀改为.py&#xff0c;输入相关代码ctrls保存&#xff0c;关闭&#xff0c;最后双击运行。 代码清单 1 from turtle import * def curvemove(): for i in range(200): right(1) …...

1 Flutter UI Container和 Text 和图片组件

一 Text 组件Text 文本组件的一些属性如下body: const Text("this is leonardo fibonacci",// 文本对齐的方式textAlign: TextAlign.center,// 文本方向textDirection: TextDirection.rtl,// 字体显示最大的行数maxLines: 2,// 文字超出屏幕之后的显示方式 ellipsi…...

【Hello Linux】 Linux基础命令(持续更新中)

作者&#xff1a;小萌新 专栏&#xff1a;Linux 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;介绍Linux的基础命令 Linux基础命令ls指令lsls -als -dls -ils -sls -lls -nls -Fls -rls -tls -Rls -1总结思维导图pwd指令whoami指令…...

记录一下slf4j2打印一直不成功

整理一个之前的老项目问题&#xff0c;发现日志一直打印不出来&#xff0c;本地启动发现了第一个问题日志如下&#xff1a;此处可发现&#xff0c;jar包冲突问题&#xff0c;去掉冲突的jar包即可&#xff0c;此处不做过多赘述。然后发现了重新启动项目&#xff0c;发现jar包冲突…...

【安全知识】——对Linux密码文件的处理

作者名&#xff1a;白昼安全主页面链接&#xff1a; 主页传送门创作初心&#xff1a; 一切为了她座右铭&#xff1a; 不要让时代的悲哀成为你的悲哀专研方向&#xff1a; web安全&#xff0c;后渗透技术每日emo&#xff1a;他既乐观又悲观&#xff0c;生活也一无是处昨天在挖掘…...

动手深度学习笔记(四十七)8.3. 语言模型和数据集

动手深度学习笔记(四十七)8.3. 语言模型和数据集 8.3. 语言模型和数据集8.3. 语言模型和数据集 在 8.2节中, 我们了解了如何将文本数据映射为词元, 以及将这些词元可以视为一系列离散的观测,例如单词或字符。 假设长度为 T T T的文本序列中的词元依次为 x 1 , x...

URL编码和Base64编码

URL编码和Base64编码前言一、URL编码1. URLEncoder和URLDecoder2. URL编码规则3. Javascript 原生提供三对 Url编码 的函数3.1 三对函数的不同点二、Base64编码1. Base64编码规则2. Base64编码使用3. JavaScript 原生提供两个 Base64 相关的方法总结前言 数据操作过程中&#…...

免费空间asp网站/腾讯广告代理商加盟

C#導出Excel方法有多種﹐常用的按照Microsoft 的方法如下: 1﹑"Use Automation to Transfer Data Cell by Cell " 2﹑"Use Automation to Transfer an Array of Data to a Range on a Worksheet " 3﹑"Use Automation to Transfer an ADO Recordset t…...

有什么可以接单做的网站/郑州网站运营专业乐云seo

AD域环境部署 实验之前先克隆两台虚拟机&#xff0c;右键虚拟机点击管理再选择克隆 2. 点击下一步 3. 点击下一页 4. 选择创建完整克隆再点击下一页 5. 修改虚拟机名称和位置再点击完成即可 6. 克隆完成点击关闭 7. 启动刚克隆好的虚拟机 8. 输入密码进去虚拟机&#xff0c;进…...

方案公司/杭州排名优化公司电话

效果视频 Shader特效——“消逝的网格” 的实现 【GLSL】 Shader特效——“消逝的网格(旋转)” 的实现 【GLSL】 图片效果...

重庆网站建设服务价格/广东东莞疫情最新消息今天又封了

格式描述&#xff1a; 这个作业属于哪个课程 课程的链接 这个作业要求在哪里 作业要求的链接 我在这个课程的目标是 正视自己学软件工程的初心 从迷茫中找到适合自己的方向 学会独立思考的能力&#xff0c;从大一到现在一直都是浑浑噩噩的&#xff0c;完成的作业大多参考着…...

wordpress自动审核/淘宝网店代运营正规公司

作为一个普通的人工智能工程师&#xff0c;不是所有的数学都需要。但是更多的数学知识和能力从来不是多余的。从本质上讲&#xff0c;机器学习的算法核心还是数学&#xff0c;人工智能的覆盖面更广泛一些&#xff0c;需要了解一些逻辑。 对于数据挖掘、人工智能、模式识别主要是…...

phpcms做双语网站/百度推广竞价技巧

1&#xff0c; 被测系统是由驱动进程mdrv.exe(多线程驱动的进程)和r3vuser.exe来产生压力的&#xff0c;其中r3vuser.exe仿真应用程序的客户端&#xff0c;如IE浏览器。它执行了三个主要的操作&#xff1a; Kli> cpp (C 语言pre-processor) 1&#xff09;cci &a…...