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

深度理解多态的底层实现


前言

首先先回顾一下上次的知识

一、多态的概念
多态(polymorphism)的概念:通俗来说,就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态),这⾥我们重点讲运⾏时多态,编译时多态(静态多态)和运⾏时多态(动态多态)。编译时多态(静态多态)主要就是我们前⾯讲的函数重载和函数模板,他们传不同类型的参数就可以调⽤不同的函数,通过参数不同达到多种形态,之所以叫编译时多态,是因为他们实参传给形参的参数匹配是在编译时完成的,我们把编译时⼀般归为静态,运⾏时归为动态。

运⾏时多态,具体点就是去完成某个⾏为(函数),可以传不同的对象就会完成不同的⾏为,就达到多种形态。⽐如买票这个⾏为,当普通⼈买票时,是全价买票;学⽣买票时,是优惠买票(5折或75折);军⼈买票时是优先买票。再⽐如,同样是动物叫的⼀个⾏为(函数),传猫对象过去,就是”(>ω<)喵“,传狗对象过去,就是"汪汪"。

二、多态的定义及实现

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

2.实现多态的重要条件:
必须是基类的指针或者引⽤调⽤虚函数
被调⽤的函数必须是虚函数,并且完成了虚函数重写/覆盖
多态必须存在于继承和派生类之间


一、多态的原理

1.1 虚函数表

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

在这里插入图片描述
在这里插入图片描述

通过观察测试我们发现b对象是8bytes,除了_b成员,还多一个_vftptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function,t代表table)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。


下面我们看一看基类和派生类的虚表里面有什么?


class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }virtual void Func1() {cout << "Person::Func1()" << endl;}virtual void Func2() {cout << "Person::Func2()" << endl;}//protected:int _a = 0;
};class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }private:virtual void Func3(){//_b++;cout << "Student::Func3()" << endl;}
protected:int _b = 1;
};void Func(Person& p)
{p.BuyTicket();
}void test()
{Person ps1;Student st1;
}int main()
{Person ps;Student st;st._a = 10;ps = st;Person* ptr = &st; //指向父类对象看到的父类的虚表Person& ref = st;  //指向子类对象看到的子类中父类的那一部分的虚表test();return 0;
}

在这里插入图片描述
在这里插入图片描述

派生类的虚表是怎么生成的呢?
只要是虚函数就会被放入虚表,可以认为派生类的虚表是先把父类的虚表先拷贝过来然后再把派生类重写过的虚函数在父类的虚表上进行覆盖,而没有被重写的虚函数就被继承了下来保持不变。
派生类自己的虚函数写在后面(VS环境下在监视窗口不会显示,得去内存窗口查看)

总结一下派生类的虚表中包括:

  1. 先将基类中的虚表内容拷贝一份到派生类虚表中
  2. 如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
  3. 派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

  • 基类对象的虚函数表中存放基类所有虚函数的地址。同类型的对象共⽤同⼀张虚表,不同类型的对象各⾃有独⽴的虚表,所以基类和派⽣类有各⾃独⽴的虚表。

  • 在这里插入图片描述

  • 派⽣类由两部分构成,继承下来的基类和⾃⼰的成员,⼀般情况下,继承下来的基类中有虚函数表指针,⾃⼰就不会再⽣成虚函数表指针。但是要注意的这⾥继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同⼀个,就像基类对象的成员和派⽣类对象中的基类对象成员也独⽴的。

  • 派⽣类中重写了基类的虚函数,派⽣类的虚函数表中对应的虚函数就会被覆盖成派⽣类重写的虚函数地址。覆盖就是指虚表中虚函数
    的覆盖。重写是语法的叫法,覆盖是原理层的叫法。

  • 派⽣类的虚函数表中包含,(1)基类的虚函数地址,(2)派⽣类重写的虚函数地址完成覆盖,(3)派⽣类自己的虚函数地址三个部分。

  • 虚函数表本质是⼀个存虚函数指针的指针数组,⼀般情况这个数组最后⾯放了⼀个0x00000000标记。(这个C++并没有进⾏规定,各个编译器⾃⾏定义的,vs系列编译器会再后⾯放个0x00000000标记,g++系列编译不会放) 。

  • 虚函数存在哪的?虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在代码段的,只是虚函数的地址⼜存到了虚表中。

  • 虚函数表存在哪的? 这个问题严格说并没有标准答案C++标准并没有规定,我们写下⾯的代码可以对⽐验证⼀下。vs下是存在代码段(常量区)

  • 从操作系统层面来说是代码段,而从语言的角度来说是叫常量区

那么虚表取的时候有涉及到大小端,判断大端小端怎么把低位的第一个字节取出来,如果低位的字节为1低位存低地址,int强转成char,取地址是int是四个字节,但是是指向第一个字节的开始,解引用看4个字节,类型决定看多大,强转成char就看第一个字节。具体看大小端博客讲解。

class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }virtual void Func1() {cout << "Person::Func1()" << endl;}virtual void Func2() {cout << "Person::Func2()" << endl;}//protected:int _a = 0;
};class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }private:virtual void Func3(){//_b++;cout << "Student::Func3()" << endl;}
protected:int _b = 1;
};void Func(Person& p)
{p.BuyTicket();
}看一下虚表存在哪里int main()
{Person ps;Student st;int a = 0;printf("栈:%p\n", &a);static int b = 0;printf("静态区:%p\n", &b);int* p = new int;printf("堆:%p\n", p);const char* str = "hello world";printf("常量区:%p\n", str);printf("虚表1:%p\n", *((int*)&ps));printf("虚表2:%p\n", *((int*)&st));return 0;
}//宏定义
typedef void(*FUNC_PTR) ();打印函数指针数组   看一看Func到底在不在派生类虚函数表中
// void PrintVFT(FUNC_PTR table[])
void PrintVFT(FUNC_PTR* table)
{for (size_t i = 0; table[i] != nullptr; i++){printf("[%d]:%p->", i, table[i]);FUNC_PTR f = table[i];//正常应该是通过对象去调用,这里直接通过取地址,f();}printf("\n");
}int main()
{Person ps;Student st;int vft1 = *((int*)&ps);PrintVFT((FUNC_PTR*)vft1);int vft2 = *((int*)&st);PrintVFT((FUNC_PTR*)vft2);return 0;
}

1.2 静态绑定与动态绑定

  • 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态, 比如:函数重载
  • 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

编译时多态通过不同参数匹配实现调用不同的函数,函数模板编译时根据实际调用,对函数模板进行实例化出三个函数,通过重载去匹配对应的函数。这两个都是在编译时进行匹配和实例化
编译时通过参数确定的,达到不同的参数调用不同的函数,形成多种形态
在语法层完成某个行为就是调某个函数,之前都是通过参数匹配

满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到指定对象的中虚表去找的。不满足多态的函数调用时编译时确认好的。

运行起来到指定对象的虚表里去找对应的地址call进行调用

 多态静态(编译时)的多态,函数重载,函数模板动态(运行时)的多态, 通过继承,虚函数重写实现多态。核心机制是虚函数表和虚函数表指针
int main()
{int i = 1;double d = 1.1;cout << i << endl;cout << d << endl;Person ps;Person* ptr = &ps;ps.BuyTicket();ptr->BuyTicket();return 0;
}

1.3 多继承中的虚函数表

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 PrintVTable(VFPTR vTable[])
{cout << " 虚表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}
int main()
{Derive d;VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);PrintVTable(vTableb1);VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d+sizeof(Base1)));PrintVTable(vTableb2);return 0;
}

多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中
在这里插入图片描述
在这里插入图片描述

派生类没必要单独产生虚表,因为派生类继承父类,派生类里面的父类就包含虚表。这样就够了就可以实现多态。
多态是父类的指针或引用,指向父类对象时找的时父类的虚函数,指向子类时切片切出子类当中父类的那一部分,去这部分找出被子类重写的虚函数覆盖的那部分即可。

这个虚表也可以算是子类自己的,子类中的父类也算是子类的成员,并且虚表也不是和父类共用的是把父类的拷贝下来自己再进行覆盖等等。


这里还有一个为什么重写func1,但Base1和Base2的虚表中Func1的地址不一样?
这里就涉及到this指针,ecx,call eax地址,jmp 反汇编
因为Derive和Base1起始地址一样,因为Derive先继承的Base1,而Base2调用Func1的时候需要sub ecx,8;找到func1的实际内存地址进行调用;

地址也可以一样,再提前修正一步
在这里插入图片描述


二、注意事项

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();因为子类继承父类的成员函数test, 只是子类可以访问test函数,但是这个test函数本身还是父类对象的,这个时候这个test函数中的this指针依旧还是父类的是A*调用test()中的func();return 0;/*不构成多态就看它的类型,构成多态就看其指向的对象*/
}B能调用test()是因为继承,  this->func()A*p->test(); 传一个B*给A*,切片,看的是A对象,调用的是A的func  B*转A*  例如:B bb; A* p = &bb;本质是让A*指向了B的对象

子类继承父类的时候,并不会把父类的成员变量/函数拷贝下来,两个类还是独立的只是说在生成一个B的对象的时候里面由两部分构成,一部分父类的,一部分子类的;

隐藏:
成员函数名相同且在两个不同的作用域就构成隐藏
会先在子类里面找,找到了子类就对父类形成隐藏
( 如果需要直接调用父类的需要加上域作用限定符指定访问;)
找不到就会去父类找;

因为隐藏的问题,在继承体系下有一个隐藏的说法,就是子类和父类同名函数,子类会隐藏父类,
所以直接用子类指针找,访问的是子类的,如果子类没有,那么子类就会继承父类的,去父类哪里寻找访问。


2、虚析构函数

为什么基类中的析构函数建议设计为虚函数?
因为多态的原因析构函数要统一名字。
某种情况下派生类可以不加virtual,也是为了这里析构函数。
如果要设计一个类,这个类想要被继承,就把基类的析构函数加上virtual写成虚函数;

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指向的对象正确的调⽤析构函数。
void func(A* ptr)
{//ptr->f();delete ptr;// ptr->调用各自的析构函数() //不构成多态时,就是普通对象会根据当前的类型调用两次A的析构函数// ptr->destructor()      //构成多态// operator delete(ptr)
}
int main()
{//这种情况没问题A* aa1;B* bb1;//这个OK//A* p1 = new A;//B* p2 = new B;//delete p1;//delete p2;这就情况就过不了func(new A);func(new B);//这个同理A* p1 = new A;A* p2 = new B;delete p1;delete p2;return 0;
}

析构函数的重写:
基类的析构函数为虚函数,此时派⽣类析构函数只要定义,⽆论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派⽣类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统⼀处理成destructor,所以只要基类的析构函数加了vialtual修饰,派⽣类的析构函数就构成重写。

下⾯的代码我们可以看到,如果~A(),不加virtual,那么delete p2时只调⽤的A的析构函数,没有调⽤B的析构函数,就会导致内存泄漏问题,因为~B()中在释放资源。(使用父类指针或者引用去操作子类对象,只会调用父类中的析构函数,并不会调用子类的析构函数)。

涉及到一个知识点:
派生类的析构函数不需要显示调用父类的析构函数,会自动调用


3、构成多态的必要条件思考

构成多态的条件(为什么不能是派生类的指针或引用?为什么不能用父类的对象)
1.必须是基类的指针或引用调用虚函数
2.虚函数的重写(接口继承)

一、必须是基类的指针或引用调用虚函数:
1、为什么不能是派生类的指针或引用?
答:
因为只有父类才可以即可以指向父类的对象又可以指向子类的对象,实现指向父类调父类指向子类调父类。
如果是子类的指针,只能指向子类的对象,到虚表里找的时候只有子类的虚函数,不能实现出多态行为。

2、为什么不能用父类的对象?
答:
对象的切片和指针或引用是不同的,对象会发生拷贝。
子类赋值给父类对象切片时,不会拷贝虚表。如果拷贝虚表,把子类的虚表拷贝到父类那么再用父类的指针或引用指向父类的对象反而会调用子类。那么父类对象虚表中是父类的虚函数还是子类的虚函数就不确定了。

二、虚函数的重写(接口继承)
虚函数的重写重写的派生类函数的实现,用的还是父类的声明;
在这里插入图片描述
只有实现了虚函数的重写才能实现指向派生类调用派生类的虚函数,指向基类调用基类的虚函数
因为没实现虚函数重写的话派生类的虚表里只会是基类虚表的拷贝,就算指向派生类也只会调用基类


总结

  • 多态的核心:通过虚函数重写和继承来实现运行时动态绑定。

  • 多态的核心意义:“通过继承和虚函数实现统一接口,多样实现”: 通过基类接口屏蔽不同子类的差异,实现一对多的调用逻辑,不同子类通过基类接口调用。

  • 关键点:虚函数重写、虚函数表、虚析构函数、final / override关键字。

  • 面向对象编程:只关注"做什么"(接口),而不是"怎么做"(具体实现)。

优势:

  • 提高代码灵活性:新增加子类时,无需修改已有代码,只需扩展新的子类即可(符合"开闭原则")。
  • 简化代码逻辑:通过统一的的接口处理多种对象类型,减少条件分支(如if / else或 switch)
  • 可维护性和扩展性:代码耦合度减低,不同子类的实现相互独立。新增子类不影响现有代码。
  • 工厂模式、策略模式等依赖多态实现,是设计复杂系统(框架,库)的基础。

相关文章:

深度理解多态的底层实现

前言 首先先回顾一下上次的知识 一、多态的概念 多态(polymorphism)的概念&#xff1a;通俗来说&#xff0c;就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态)&#xff0c;这⾥我们重点讲运⾏时多态&#xff0c;编译时多态(静态多态)和运⾏时多态(动态多态…...

【深度学习】Pytorch项目实战-基于协同过滤实现物品推荐系统

一、推荐系统的了解 1. 定义 推荐系统是一个信息过滤系统&#xff0c;旨在为用户提供个性化的内容推荐。它利用用户的历史行为、偏好以及其他相关数据来推测用户可能感兴趣的项目或信息。推荐系统广泛应用于电子商务、社交媒体、流媒体服务等领域&#xff0c;帮助用户发现商品…...

空字符串““、空白字符串“ “和 null 三者的区别

空字符串、空白字符串和 null 三者的区别表格&#xff1a; 类型定义示例长度是否有值空字符串字符串长度为 0&#xff0c;但不是 null&#xff0c;即存在一个有效的空字符串对象。""0有值&#xff08;空值&#xff09;空白字符串字符串包含空格、制表符等空白字符&a…...

【Pandas】pandas Series sample

Pandas2.2 Series Computations descriptive stats 方法描述Series.align(other[, join, axis, level, …])用于将两个 Series 对齐&#xff0c;使其具有相同的索引Series.case_when(caselist)用于根据条件列表对 Series 中的元素进行条件判断并返回相应的值Series.drop([lab…...

AF3 _build_query_to_hit_index_mapping函数解读

AlphaFold3 中templates模块的_build_query_to_hit_index_mapping函数是将原始查询序列(original_query_sequence)中的索引与hit 序列(hit_sequence)中的索引进行映射。 在蛋白质序列比对(如 HHsearch)中,hit 是与查询序列部分匹配的区域。由于存在缺口(-)和部分比对…...

在mfc中使用自定义三维向量类和计算多个三维向量的平均值

先添加一个普通类, Vector3.h, // Vector3.h: interface for the Vector3 class. // //#if !defined(AFX_VECTOR3_H__53D34D26_95FF_4377_BD54_57F4271918A4__INCLUDED_) #define AFX_VECTOR3_H__53D34D26_95FF_4377_BD54_57F4271918A4__INCLUDED_#if _MSC_VER > 1000 #p…...

UE_C++ —— Container TSet

目录 一&#xff0c;TSet 二&#xff0c;Creating and Filling a Set Editing UPROPERTY TSets 三&#xff0c;Iteration 四&#xff0c;Queries 五&#xff0c;Removal 六&#xff0c;Sorting 七&#xff0c;Operators 八&#xff0c;Slack 九&#xff0c;DefaultKe…...

多线程和并发篇

多线程和并发篇 创建一个对象时底层汇编指令实现步骤&#xff08;cpu可能会进行指令重排序&#xff09;&#xff1a;一、二、三级缓存的实现&#xff1a;并发编程三要素&#xff1a;线程的五大状态&#xff1a;创建线程的三种方式&#xff1a;线程的特征和状态&#xff1a;Thre…...

【3.5JavaScript】JavaScript字符串对象

文章目录 1.获取字符串长度2.大小写转换3.获取某一个字符4.截取字符串5.替换字符串6.分割字符串7.检索字符串位置8.例题&#xff1a;统计某一个字符的个数 在 JavaScript 中&#xff0c;对象是非常重要的知识点。对象分为两种&#xff1a;一种是 ”自定义对象“&#xff0c;另…...

Apache Flink架构深度解析:任务调度、算子数据同步与TaskSlot资源管理机制

Apache Flink是一个分布式流处理框架&#xff0c;其核心架构设计围绕有界与无界数据流的统一处理能力展开。以下从任务分配、算子数据同步、TaskManager与JobManager的TaskSlot机制三个维度展开详细分析&#xff1a; 一、任务分配机制 Flink的任务分配基于并行度&#xff08;P…...

路由基本配置

学习目标 • 根据拓扑图进行网络布线。 • 清除启动配置并将路由器重新加载为默认状态。 • 在路由器上执行基本配置任务。 • 配置并激活以太网接口。 • 测试并检验配置。 • 思考网络实施方案并整理成文档。 任务 1&#xff1a;网络布线 使用适当的电缆类型连接网络设备。…...

windows上vscode cmake工程搭建

安装vscode插件&#xff1a; 1.按装fastc&#xff08;主要是安装MinGW\mingw64比较方便&#xff09; 2.安装C&#xff0c;cmake&#xff0c;cmake tools插件 3.准备工作完成之后&#xff0c;按F1&#xff0c;选择cmake:Quick Start就可以创建一个cmake工程。 4.设置Cmake: G…...

VUE3+TS+element-plus项目从0开始入门 - 创建项目、认识基本结构

文章目录 写在前面1、创建vue3项目npm create vuelatestnpm i 2、项目结构.vscodevue3结构a、项目树结构b、package.jsonc、tsconfig.jsond、index.htmld、srce、main.tsf、App.vue 写在前面 开前请自行下载vs code、node.js, 在vs code里面安装Vue - Official插件。本文使用的…...

shared_ptr 不析构的问题记录

片段1&#xff1a; 片段2&#xff1a; 你们猜 哪个有问题 &#xff1f;...

DeepSeek模型量化

技术背景 大语言模型&#xff08;Large Language Model&#xff0c;LLM&#xff09;&#xff0c;可以通过量化&#xff08;Quantization&#xff09;操作来节约内存/显存的使用&#xff0c;并且降低了通讯开销&#xff0c;进而达到加速模型推理的效果。常见的就是把Float16的浮…...

原生稀疏注意力机制(NSA):硬件对齐且可原生训练的稀疏注意力机制-论文阅读

摘要 长上下文建模对于下一代语言模型至关重要&#xff0c;但标准注意力机制的高计算成本带来了巨大的计算挑战。稀疏注意力提供了一种在保持模型能力的同时提高效率的有前途的方向。本文提出了一种名为 NSA&#xff08;原生可训练稀疏注意力机制&#xff09; 的方法&#xff…...

从0到1:固件分析

固件分析 0x01 固件提取 1、从厂商官网下载 例如D-link的固件&#xff1a; https://support.dlink.com/resource/products/ 2、代理或镜像设备更新时的流量 发起中间人攻击MITM #启用IP转发功能 echo 1 > /proc/sys/net/ipv4/ip_forward#配置iptables&#xff0c;将目…...

Zookeeper(58)如何在Zookeeper中实现分布式锁?

在 Zookeeper 中实现分布式锁是一种常见的用例。Zookeeper 提供了强一致性、高可用性的分布式协调服务&#xff0c;使得它非常适合用来实现分布式锁。以下是详细的步骤和代码示例&#xff0c;展示如何在 Zookeeper 中实现分布式锁。 1. Zookeeper 分布式锁的基本原理 Zookeep…...

23种设计模式 - 观察者模式

模式定义 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;定义了一对多的依赖关系&#xff1a;当一个对象&#xff08;称为主题&#xff09;状态变化时&#xff0c;所有依赖它的对象&#xff08;称为观察者&#xff09;会自动收到通知并…...

conda、anaconda、pip、pytorch、tensorflow有什么区别?

先画一张图&#xff0c;可以大致看出它们的区别和关联&#xff1a; pytorch、tensorflow都是Python的第三方库&#xff0c;相当于封装的代码工具集库&#xff0c;通过import导入使用。这两个都是深度学习框架&#xff0c;用来搭建AI模型什么的&#xff0c;使用范围非常之广&…...

项目设置内网 IP 访问实现方案

在我们平常的开发工作中&#xff0c;项目开发、测试完成后进行部署上线。比如电商网站、新闻网站、社交网站等&#xff0c;通常对访问不会进行限制。但是像企业内部网站、内部管理系统等&#xff0c;这种系统一般都需要限制访问&#xff0c;比如内网才能访问等。那么一个网站应…...

Vue面试2

1.跨域问题以及如何解决跨域 跨域问题&#xff08;Cross-Origin Resource Sharing, CORS&#xff09;是指在浏览器中&#xff0c;当一个资源试图从一个不同的源请求另一个资源时所遇到的限制。这种限制是浏览器为了保护用户安全而实施的一种同源策略&#xff08;Same-origin p…...

合合信息2025届春季校园招聘全面启动!

世界因你而AI&#xff0c;合合信息2025届春季校园招聘启动&#xff01; 我们是谁&#xff1f; 我们是一家行业领先的人工智能及大数据科技企业 18年深耕AI领域&#xff0c;C端产品与B端服务布局矩阵完善 9.4亿全球累计用户首次下载量&#x1f4a5; 来到这里你能得到什么&a…...

如何利用 Vue 的生命周期钩子进行初始化和清理操作?

一、初始化操作的核心钩子 1. created&#xff08;选项式API&#xff09; export default {data() {return { user: null };},created() {// 适合初始化数据、发起非DOM操作请求this.fetchUser();},methods: {async fetchUser() {const response await fetch(/api/user);thi…...

Excell 代码处理

文章目录 Excell 代码处理cvc格式xlsl格式小结 Excell 代码处理 有时候要对excell进行分析&#xff0c;或者数据的导入导出&#xff0c;这个时候如果可以用代码读写分析操作那么会方便很多 cvc格式 CSV&#xff08;Comma-Separated Values&#xff0c;逗号分隔值&#xff09;是…...

KMP的next数组构建详解

KMP的next数组构建详解 1. next数组的作用 核心功能&#xff1a;在KMP算法中&#xff0c;当模式串与主串发生不匹配时&#xff0c;next数组决定模式串指针回退的位置&#xff0c;避免无效匹配。 定义&#xff1a;next[i]表示子串s[0...i]的最长公共前后缀长度。例如&#xff…...

Docker 的安全配置与优化(二)

Docker 安全优化策略 &#xff08;一&#xff09;多阶段构建优化镜像大小 多阶段构建是 Docker 17.05 版本引入的强大功能&#xff0c;它允许在一个 Dockerfile 中定义多个构建阶段&#xff0c;每个阶段都可以使用不同的基础镜像和依赖项&#xff0c;最终只将必要的文件和依赖…...

shiro代码层面追踪

文章目录 环境漏洞分析硬编码 反序列化Gadget构造 环境 环境搭建&#xff1a;https://blog.csdn.net/qq_44769520/article/details/123476443 漏洞分析 硬编码 shiro是对rememberMe这个cookie进⾏反序列化的时候出现了问题。 相应代码 // // Source code recreated from …...

通信系统中物理层与网络层联系与区别

在通信系统中&#xff0c;物理层和网络层是OSI&#xff08;开放系统互连&#xff09;模型中的两个重要层次&#xff0c;分别位于协议栈的最底层和第三层。它们在功能、职责和实现方式上有显著的区别&#xff0c;但同时也在某些方面存在联系。以下是物理层与网络层的联系与区别的…...

虚拟机网络ssh连接失败,没有网络

vscode进行ssh时连接失败&#xff0c;发现是虚拟机没有网络。 虚拟机ping不通www.baidu.com但可以ping通内网 ping 8.8.8.8ping不通。 sudo dhclient -r ens33 sudo dhclient ens33 ip route show可以了。 20250221记录&#xff1a;不知道是不是重启了虚拟机还是咋了&#…...