C++封装、继承、多态(虚函数)

目录

1、封装

2、继承

继承方式:

(1)公有继承;public

(2)保护继承;protected

(3)私有继承;private

菱形继承: 

同名隐藏?

含义:

产生原因:

 同名覆盖?(函数重写)

定义

作用

 3、多态

(1)多态的分类

(2)虚表:

(3)代码示例 

指针: 

引用实现:

(1)派生类对象可以给基类,但基类不能给派生类。 

(2)强制类型转换后,查的仍然是Base的虚表:

 (3)定义obj类型的对象,访问的仍是Obj的虚表,

 (4)继承关系中,动态创建派生类对象,但是是拿基类对象指向的,Object * op = new Base();在delete *op时,调用派生类的析构函数,解决办法是将基类的析构函数设为虚函数,之后,就可以先调用~Base();再调基类的析构是为什么?

原理分析


1、封装

封装是面向对象编程(OOP)的四大基本特性之一(另外三个是继承、多态和抽象),它是一种将数据(属性)和操作这些数据的方法(行为)捆绑在一起,并对外部隐藏对象的内部实现细节的机制。

class 类名:继承方式 基类名1,继承方式 基类名2,。。。继承方式 基类名n

{

   派生类成员定义

};

2、继承

单继承:一个类只有一个直接基类。

多继承:一个类拥有多个直接基类。

继承方式:

(1)公有继承;public

  • 基类的私有成员在派生类中不能直接访问。
  • 基类的保护成员只能在派生类内部访问,不能在派生类外部访问, 在派生类中,继承而来的基类保护成员依然是protected。
  • 基类的公有成员在派生类内部和外部都可以被访问得到,在派生类中,继承而来的基类的公有成员依然是public。

(2)保护继承;protected

  • 基类的私有成员在派生类中不能直接访问。
  • 基类的保护成员只能在派生类内部访问,不能在派生类外部访问, 在派生类中,继承而来的基类保护成员依然是protected。
  • 基类的公有成员在派生类内部可以被访问得到,在派生类中,继承而来的基类的公有成员变成了是protected。

(3)私有继承;private

  • 基类的私有成员在派生类中不能直接访问。
  • 基类的保护成员只能在派生类内部访问,不能在派生类外部访问, 在派生类中,继承而来的基类保护成员是private。
  • 基类的公有成员在派生类内部可以被访问得到,在派生类中,继承而来的基类的公有成员变成了private。

注:基类的私有成员在派生类中时存在的,但是不能在派生类中直接访问,即无论通过何种方式继承,都无法在派生类内部直接访问继承自基类的私有成员。只能通过基类中的公共函数,来访问基类的私有成员。绝大多数情况下的继承是公有继承。

菱形继承: 

C在继承了B1类和B2类之后对于B1和B2中同样来自于A类的数据就会造成访问二义性问题。 会造成数据冗余。来自A的数据有两份。

解决办法:使用虚继承

派生类访问间接基类的数据时,实际上访问的是该类对象中的虚基表指针,通过虚基表指针访问到了虚基表,而虚基表中存储的内容是当前虚基表指针位置到间接基类成员的地址偏移量。那么这样子就能够在使用派生类访问间接基类成员时,通过偏移量直接找到继承而来的间接基类的成员。所以在内存中只用保留一份间接基类的成员就行 。

同名隐藏?

含义:

同名隐藏指在继承关系里,当派生类定义了和基类中同名的成员(包含成员变量和成员函数)时,基类的同名成员会被派生类的成员隐藏。这意味着在派生类的作用域内,若直接使用该成员名,默认访问的是派生类的成员,基类的同名成员就好像 “被隐藏” 了,若要访问基类的同名成员,需要使用作用域解析运算符(::

产生原因:

这种机制源于 C++ 等语言在处理继承时的名称查找规则。当在派生类中使用一个名称时,编译器会先在派生类的作用域内查找该名称,若找到就使用该名称对应的成员,不再去基类的作用域中查找;若在派生类的作用域内没找到,才会去基类的作用域中查找。

 同名覆盖?(函数重写)

在面向对象编程中,同名覆盖(也常被称为函数重写,Override)是一种重要的多态性机制,主要发生在具有继承关系的类之间。以下是关于它的详细介绍:

定义

当派生类中定义了一个与基类中虚函数具有相同签名(函数名、参数列表、返回值类型)的函数时,就发生了同名覆盖。此时,派生类的对象在调用该函数时,会执行派生类中重写的版本,而不是基类中的版本。

作用

同名覆盖是实现多态性的关键手段之一。通过它,我们可以在不修改基类代码的情况下,在派生类中根据具体需求对基类的虚函数进行重新定义,从而实现不同的行为。这样,当使用基类指针或引用指向不同的派生类对象时,调用相同的函数名可以产生不同的效果,提高了代码的可扩展性和可维护性。

 3、多态

(1)多态的分类

     编译时多态,在程序编译时确定同名操作和具体的操作对象。(早期绑定)

               强制多态—强制类型转换

               重载多态—函数重载和运算符重载

               参数化多态—类模板及函数模板

     运行时多态,在程序运行时才会确定同名操作和具体的操作对象。通过类继承关系和虚函数来实现。

                  包含多态—虚函数重写

虚函数的重写:三同:函数名、返回类型、参数列表

(2)虚表:

在 C++ 里,只要类包含虚函数,编译器就会为该类创建一个虚表(Virtual Table,简称 VTable)。虚表本质上是一个存储类的虚函数地址的指针数组,这个数组的首元素上存储RTTI(运行时类型识别信息的指针),从数组下标0开始依次存储虚函数地址。最后面放了一个nullptr。类的每个对象中都有一个指向该类虚表的指针(虚表指针,vptr)。

指针数组是指一个数组,其元素的类型为指针。也就是说,指针数组中的每个元素都存储着一个内存地址

虚函数地址表在 .data 区。

运行时多态:必须用指针或引用调用虚函数,对象.虚函数,这是编译时,不是运行时多态。 

(3)代码示例 

#include<stdio.h>
#include<iostream>
#include <cassert>
using namespace std;class Object
{
private:int value;
public:Object(int x = 0) :value(x){}~Object(){}virtual void add() { cout << "Object::add" << endl; }virtual void func() { cout << "Object::func" << endl; }virtual void print()const { cout << "Object::printf" << endl; }
};class Base :public Object
{
private:int num;
public:Base(int x=0):Object(x),num(x+10){}//重写虚函数virtual void add() { cout << "Base::add" << endl; }virtual void func() { cout << "Base::func" << endl; }virtual void show() { cout << "Base::show" << endl; }};class Test :public Base
{
private:int count;
public:Test(int x=0):Base(x),count(x+10){}virtual void add() { cout << "Test::add" << endl; }virtual void show() { cout << "Test::show" << endl; }virtual void print()const { cout << "Test::printf" << endl; }};void funcPobj(Object* pobj)
{assert(pobj != nullptr);pobj->add();pobj->func();pobj->print();
}
int main()
{Test test(10);funcPobj(&test);return 0;
}

以上代码,在内存中的虚表大致如下:

sizeof(Object):8;int+一个指向虚表的指针(32位操作系统) 

指针: 

通过虚表指针,访问Test类的虚表

引用实现:

(1)派生类对象可以给基类,但基类不能给派生类。 

(2)强制类型转换后,查的仍然是Base的虚表:

 (3)定义obj类型的对象,访问的仍是Obj的虚表,

访问obj的虚表,obj中没有派生类的show方法,执行到“000000”报错。这种强转可以理解为:无效的。

(Base*) & obj 和 (Test*) & obj)只是简单地改变了指针的类型,而不会改变对象本身的实际类型。obj 实际上是 Object 类型的对象,尽管你把它的指针强制转换为 Base* 或 Test* 类型,但对象的内存布局和实际类型依旧是 Object

  • 虚函数调用Base 和 Test 类继承自 Object 类,并且有各自的虚表。当你把 Object 类型的指针强制转换为 Base* 或 Test* 类型并调用虚函数时,程序会依据转换后的指针类型去访问相应的虚表。然而,obj 实际上是 Object 类型的对象,它只有 Object 类的虚表,这就会导致程序访问错误的虚表,从而引发未定义行为。
  • 成员访问Base 和 Test 类可能包含 Object 类没有的成员变量和成员函数。当你通过强制转换后的指针访问这些额外的成员时,程序会尝试访问不存在的内存位置,这也会导致未定义行为。
 (4)继承关系中,动态创建派生类对象,但是是拿基类对象指向的,Object * op = new Base();在delete *op时,调用派生类的析构函数,解决办法是将基类的析构函数设为虚函数,之后,就可以先调用~Base();再调基类的析构。

在继承关系里,当使用基类指针指向动态创建的派生类对象,并且基类的析构函数不是虚函数时,在执行 delete 操作时只会调用基类的析构函数,这可能会造成派生类对象的部分资源无法正确释放,进而引发内存泄漏等问题。

当基类的析构函数不是虚函数时delete 操作依据指针的静态类型来决定调用哪个析构函数。由于指针类型是基类指针,所以只会调用基类的析构函数,派生类的析构函数不会被调用。 

 

基类析构不是虚函数示例代码如下: 

#include <iostream>class Object {
public:~Object() {std::cout << "Object::~Object()" << std::endl;}
};class Base : public Object {
public:~Base() {std::cout << "Base::~Base()" << std::endl;}
};int main() {Object* op = new Base();delete op; return 0;
}

当把基类的析构函数设为虚函数后,delete 操作会依据对象的实际类型来决定调用哪个析构函数。因为对象的实际类型是派生类,所以会先调用派生类的析构函数,然后再调用基类的析构函数。

#include <iostream>class Object {
public:virtual ~Object() {std::cout << "Object::~Object()" << std::endl;}
};class Base : public Object {
public:~Base() {std::cout << "Base::~Base()" << std::endl;}
};int main() {Object* op = new Base();delete op; return 0;
}

 Base::~Base()

Object::~Object()

原理分析
  • 虚表机制:当基类的析构函数被声明为虚函数时,编译器会为基类和派生类分别创建虚表。在对象的内存布局中,会有一个虚表指针指向对应的虚表。当执行 delete 操作时,程序会通过对象的虚表指针找到对应的虚表,然后从虚表中获取析构函数的地址并调用。由于对象的实际类型是派生类,所以会先调用派生类的析构函数。
  • 析构顺序:在 C++ 里,析构函数的调用顺序与构造函数的调用顺序相反。当创建派生类对象时,会先调用基类的构造函数,再调用派生类的构造函数;而在销毁对象时,会先调用派生类的析构函数,再调用基类的析构函数,以此确保对象的资源能够被正确释放。
(5)运行时多态是怎么实现的? 

运行时多态主要基于继承和虚函数实现。当基类指针或引用指向派生类对象时,通过该指针或引用调用虚函数,程序会在运行时根据对象的实际类型来决定调用哪个类的虚函数,从而实现不同的行为。

用一个指针指向一个对象,调用函数的时候,指向对象虚表的地址给edx,调用第几个函数就(edx+偏移量 4n) 

运行时多态怎么实现的(汇编)?例:
#include <iostream>class Base {
public:virtual void func1() {std::cout << "Base::func1()" << std::endl;}virtual void func2() {std::cout << "Base::func2()" << std::endl;}
};class Derived : public Base {
public:void func1() override {std::cout << "Derived::func1()" << std::endl;}void func2() override {std::cout << "Derived::func2()" << std::endl;}
};int main() {Base* ptr = new Derived();ptr->func1();ptr->func2();delete ptr;return 0;
}

当创建 Derived 类的对象并让 Base 类型的指针 ptr 指向它时,Derived 对象的内存布局起始位置会有一个虚表指针,该指针指向 Derived 类的虚表。

函数调用过程
  • 获取虚表指针:当执行 ptr->func1() 时,程序首先通过 ptr 指针找到对象的内存地址,进而获取对象的虚表指针,通常会把这个虚表指针的值存到某个寄存器(如你所说的 edx)中。
  • 计算函数地址:虚表本质是一个存储函数指针的数组,每个函数指针在虚表中按声明顺序排列,且每个指针占一定字节数(在 32 位系统中一般是 4 字节,64 位系统中是 8 字节)。要调用第 n 个虚函数,就需要在虚表指针的基础上加上偏移量 4n(32 位系统)或 8n(64 位系统)来获取该函数的地址。例如,调用 func1() 时,偏移量为 0;调用 func2() 时,偏移量为 4(32 位)或 8(64 位)。
  • 调用函数:获取到函数地址后,程序就会跳转到该地址处执行相应的函数代码。

4、静态联编和动态联编

静态联编:在编译和链接阶段,就将函数实现和函数调用关联起来。

C语言中,所有的联编都是静态联编。

C++语言中,函数重载和函数模版也是静态联编。

C++中,对象.成员运算符,去调用对象虚函数,也是静态联编。

动态联编:程序执行的时候才将函数实现和函数调用关联起来。

C++中,使用引用、指针->,则程序在运行时选择虚函数的过程称为动态联编。

5、例题:memset对vptr的影响:

class Object
{
private:int value;
public:Object(int x = 0) :value(x){memset(this, 0, sizeof(Object));}void func(){	cout << "func" << endl;			}virtual void add(int x) {	cout << "obj add" << endl;}
};
int main()
{Object obj;Object* op = &obj;obj.add(1); //静态联编op->add(2); //报错
}

     op->add(2);编译会报错

原因:

1. memset 对虚表指针的影响

在 C++ 里,要是一个类包含虚函数,编译器会为这个类创建虚表,并且在类的每个对象里插入一个虚表指针(vptr),此指针一般处于对象内存布局的起始位置。memset(this, 0, sizeof(Object)); 这个操作会把对象的整个内存区域都置为 0,这就包含了虚表指针。一旦虚表指针被置为 0,就无法正确指向对应的虚表。

2. 虚函数调用机制

当借助基类指针(这里是 op)调用虚函数(像 op->add(2);)时,程序会通过对象的虚表指针找到对应的虚表,再从虚表中获取该虚函数的地址,最后调用这个函数。但由于虚表指针被 memset 置为 0 了,程序就无法找到正确的虚表,从而引发运行时错误。

3. 直接对象调用和指针调用的区别
  • obj.add(1);这是直接通过对象调用虚函数。在这种情形下,编译器能够在编译时就确定要调用的函数,所以不会借助虚表指针,也就不会受到 memset 操作的影响。
  • op->add(2);:这是通过指针调用虚函数,需要在运行时依靠虚表指针来确定要调用的函数。由于虚表指针被置为 0,程序就无法找到正确的虚表,进而导致运行时错误。

6、例题:

class Object
{
private:int value;
public:Object(int x = 0) :value(x){}void print() {cout << "obj::print" << endl;add(1);}virtual void add(int x) {	cout << "obj::add"<<x << endl;}
};
class Base :public Object {
private:int num;
public :Base(int x = 0) :Object(x + 10), num(x){}void show() { cout << "Base::show" << endl; print(); //this->print();}virtual void add(int x) { cout << "base::add" << x << endl; }
};
int main()
{Base base;base.show();return 0;
}

 

类的成员函数在调用数据时有this, 

调用过程分析
  1. main 函数中调用 base.show():创建了一个 Base 类的对象 base,然后调用其 show 方法。
  2. Base::show 方法中调用 print 方法:在 Base::show 方法里调用了 print 方法,由于 Base 类没有重写 print 方法,所以实际上调用的是基类 Object 的 print 方法。这里的 this 指针指向的是 Base 类的对象 base
  3. Object::print 方法中调用 add 方法:在 Object::print 方法中调用了 add(1)。因为 add 方法在 Object 类中被声明为虚函数(virtual void add(int x)),并且 Base 类重写了该虚函数,所以在运行时会根据 this 指针所指向对象的实际类型来决定调用哪个 add 方法。由于 this 指针指向的是 Base 类的对象 base,所以会调用 Base 类中重写的 add 方法。

 如果在构造、析构函数里调用虚函数,调用谁的?答:调用自身类型的。不会查虚表。

7、动态+静态联编例题:

class Object
{
private:int value;
public:virtual void func(int a=10) { cout << "obj::func: a"<<a <<   endl; }
};
class Base :public Object {private:virtual void func(int b = 20) { cout << "Base::func: b"<<b << endl; }
};
int main()
{Base base;Object* op = &base;op->func();return 0;
}

1. 虚函数调用机制

在 C++ 里,当使用基类指针(如 Object* op)指向派生类对象(如 Base base),并且通过该指针调用虚函数(如 op->func())时,会在运行时依据对象的实际类型来决定调用哪个类的函数版本。由于 op 指向的是 Base 类的对象 base所以会调用 Base 类中重写的 func 函数。

2. 默认参数的绑定规则

默认参数是在编译时确定的,而不是运行时。当调用 op->func() 时,编译器会查看指针的静态类型(也就是 Object*)来确定默认参数的值。在 Object 类中,func 函数的默认数 a 被设定为 10,所以在调用 func 函数时,默认参数的值会使用 Object 类中定义的 10,而非 Base 类中定义的 20。

3. 总结

结合虚函数调用机制和默认参数的绑定规则,op->func() 会调用 Base 类的 func 函数,不过默认参数会使用 Object 类中定义的 10,因此输出结果为 Base::func: b 10

C++中,构造函数不能为虚。

构造函数的任务:设置虚表指针。 

构造函数的主要作用是初始化对象的成员变量,为对象分配内存并设置初始状态。在创建对象时,编译器已经明确知道要创建的对象类型,因此可以直接调用相应的构造函数,不需要通过虚函数机制在运行时动态确定。

构造函数执行时,对象还未完全创建好,虚表指针可能还未被正确初始化。如果构造函数是虚函数,就需要通过虚表指针来调用它,但此时虚表指针可能还没指向正确的虚表,这会导致无法正确调用构造函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/75273.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯冲刺:一维前缀和

系列文章目录 蓝桥杯系列&#xff1a;一维前缀和 文章目录 系列文章目录前言一、暴力的写法&#xff1a;二、一维前缀和的模板&#xff1a; 具体实现&#xff1a; 三、具体例题&#xff1a;求和 1.题目参考&#xff1a;2.以下是具体代码实现&#xff1a; 总结 前言 上次我介绍…

使用UDP建立连接,会存在什么问题?

使用UDP建立连接&#xff0c;会存在可靠性、有序性、连接状态管理等方面的问题&#xff1a; 1、数据传输不可靠&#xff1a; UDP没有确认和重传机制&#xff0c;发送方发送数据后&#xff0c;不会等待接收方的确认消息。这意味着如果数据在传输过程中丢失&#xff0c;发送方不…

YOLOv5配置训练以及华为昇腾910B推理

参考文章&#xff1a; 保姆式yolov5教程&#xff0c;训练你自己的数据集 - 知乎 Windows 10|11下安装mmyolo-0.5.0版本 - 知乎 Ubuntu22.04安装教程&基于华为Ascend AI处理器的om模型atc转换环境安装_ubuntu安装atc工具-CSDN博客嵌入式AI---在华为昇腾推理自己的yolov5目标…

基于yolov11的汽车损伤检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv11的汽车损伤检测系统是一种先进的计算机视觉技术&#xff0c;旨在快速准确地识别汽车的各种损伤类型。该系统利用YOLOv11模型的强大性能&#xff0c;实现了对车辆损伤的精确检测与分类。 该系统能够识别的损伤类型包括裂纹&#xff08;crack&#xff…

[ 3分钟算法 ] | 递归搜索题目 : 合并两个有序链表(递归版)

目录 1. 题目链接&#xff1a; 2. 思路分析&#xff1a; 1. 重复子问题&#xff1f; 2. 具体子问题&#xff1f; 3. 递归出口&#xff1f; 3. 代码实现&#xff1a; 4. 小结&#xff1a; 1. 循环(迭代) vs 递归 2. 递归 vs 深搜 1. 题目链接&#xff1a; 21. 合并…

单元测试原则之——不要模拟值对象 (1)

1. 什么是值对象(Value Objects)? 值对象是指那些不可变且仅通过其属性(数据)来定义的对象。它们通常没有复杂的逻辑或行为,主要用于存储和传递数据。例如: ● 字符串(String) ● 数字(Integer, Double) ● 日期(LocalDate, Instant) ● 自定义的简单数据类(如…

【软件】在Windows和Ubuntu上使用TFTP和NFS

在Windows和Ubuntu上使用TFTP和NFS 零、介绍 最近在玩Linux开发板&#xff0c;在开发的过程中发现需要用到tftp和nfs来帮助传输文件&#xff0c;故此记录如何使用这两种软件。 TFTP&#xff08;Trivial File Transfer Protocol&#xff09; &#xff1a;是一种简化的文件传输…

JS判断变量是否为空的方法

在 JavaScript 中&#xff0c;判断变量是否为空需要根据不同的数据类型和具体需求来处理。以下是常见场景的解决方案&#xff1a; 1. 基础判断&#xff1a;null 或 undefined javascript if (value null || value undefined) {// 变量为空 } 或简写为&#xff1a; javasc…

Linux更换挂载nfs迁移数据流程

当前&#xff1a;原nfs&#xff08;10.16.2.1:/myData&#xff09;挂载在/myData&#xff0c;新的nfs&#xff08;10.16.2.2:/myData&#xff09;未挂载 目标&#xff1a;把旧nfs的数据迁移到新的nfs上&#xff0c;并把新nfs挂载到/myData 步骤&#xff1a; 1、新nfs挂载到一…

深入解析音频:格式、同步及封装容器

物理音频和数字音频 物理音频 定义&#xff1a;物理音频就是声音在自然界中的物理表现形式&#xff0c;本质上是一种机械波&#xff0c;通过空气或其他介质传播。例如&#xff0c;当我们说话、乐器演奏或物体碰撞时&#xff0c;都会产生振动&#xff0c;这些振动会引起周围介…

AI与.NET技术实操系列(四):使用 Semantic Kernel 和 DeepSeek 构建AI应用

1. 引言 在人工智能技术飞速发展的今天&#xff0c;大型语言模型&#xff08;Large Language Models, LLMs&#xff09;已成为智能应用开发的核心驱动力。从智能客服到自动化内容生成&#xff0c;LLMs的应用正在深刻改变我们的工作和生活方式。 对于.NET开发者而言&#xff0c;…

导出cad实体所有信息到txt并打开(生成唯一文件名) ——c#cad二次开发

效果如下: 建议在保存时指定编码为UTF-8&#xff1a; using (StreamWriter sw new StreamWriter(filePath, false, Encoding.UTF8)) { // 写入内容 } 最终 using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD…

Redis 源码硬核解析系列专题 - 第一篇:Redis源码入门与整体架构

1. 引言 Redis作为一个高性能的内存键值数据库,其源码以简洁高效著称。通过解析Redis源码,我们可以深入理解其单线程模型、事件驱动机制以及模块化设计的精髓。本篇将从Redis的源码目录结构入手,剖析其整体架构,并聚焦启动流程和事件循环的核心实现。 2. Redis源码目录结构…

异步加载+内存分析

异步加载 Resources和AB包的同步加载与异步加载对比代码&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI;public class AsyncLoad : MonoBehaviour {// Start is called before the first frame updatev…

将视频m4s文件转换为mp4格式

将视频m4s文件转换为mp4格式 一般情况&#xff1a;偏大的文件为视频&#xff0c;偏小的文件为音频。 环境要求&#xff1a;下载并安装ffmpeg&#xff0c;并配置好环境变量&#xff0c;如下图&#xff1a; 转换代码&#xff1a; import subprocessdef merge_m4s_to_mp4(vide…

EXCEL报错:无法共享此工作薄,因表包含excel表或xml映射的解决方法

在分享工作薄是&#xff0c;如果出现了“无法共享此工作薄&#xff0c;因表包含excel表或xml映射”的报错&#xff0c;那么有两个原因&#xff1a; 1.包含Excel表格&#xff0c;这个也是相对比较常见的原因。 首先选中表格。如果你不知道表的位置在哪&#xff0c;那么在Excel左…

w2ui 水平滚动移动 虚拟列 数据丢失

https://w2ui.com/web/docs/1.5/w2grid.disableCVS https://github.com/vitmalina/w2ui/issues/1398 解决方案来源 问题现象: 窗口缩小 导致多列 出现水平滚动,滚动时触发本地样式重绘,导致record undefined,从而引发多列报错 解决方案: 使用 disableCVS : true 一次加载到d…

在ensp进行OSPF+RIP+静态网络架构配置

一、实验目的 1.Ospf与RIP的双向引入路由消息 2.Ospf引入静态路由信息 二、实验要求 需求&#xff1a; 路由器可以互相ping通 实验设备&#xff1a; 路由器router7台 使用ensp搭建实验坏境&#xff0c;结构如图所示 三、实验内容 1.配置R1、R2、R3路由器使用Ospf动态路由…

基于mediapipe深度学习和限定半径最近邻分类树算法的人体摔倒检测系统python源码

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 Mediapipe人体姿态检测原理 4.2 限定半径最近邻分类树算法原理 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 人工智能算法python程序运行环…

deep-sync开源程序插件导出您的 DeepSeek 与 public 聊天

一、软件介绍 文末提供下载 deep-sync开源程序插件导出您的 DeepSeek 与 public 聊天&#xff0c;这是一个浏览器扩展&#xff0c;它允许用户公开、私下分享他们的聊天对话&#xff0c;并使用密码或过期链接来增强 Deepseek Web UI。该扩展程序在 Deepseek 界面中添加了一个 “…