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

C++三大特性——继承性(超万字详解)

目录

前言

一、封装

1. 封装(Encapsulation)

二、继承

1. 构造函数的调用顺序

原理:

2. 析构函数的调用顺序

原理:

3、派生类的隐藏 

1. 成员函数隐藏

2. 成员变量隐藏

3. 基类函数的重载隐藏

三、多重继承问题

1. 构造函数的调用顺序

2. 析构函数的调用顺序

3. 多重继承中的命名冲突

4. 菱形继承问题(Diamond Problem)

四、虚继承

1. 菱形继承问题(Diamond Problem)

2. 虚继承解决菱形继承问题

3. 虚基类表(Virtual Base Table,VBTBL)

虚基类表的主要功能:

4. 虚基类指针(Virtual Base Pointer,VBPTR)

虚基类指针的主要功能:

5. 虚基类表和虚基类指针的工作机制

6. 虚继承的内部工作过程

 五、虚继承问题例子

1、实例        

2. 虚基类指针(VBPtr)的生成

3. FinalDerived的继承情况

4. 虚基类指针如何工作

虚基类指针的作用:


前言

    在C++中,三大特性通常是指面向对象编程(OOP)的三大基本特性,它们是 封装(Encapsulation)继承(Inheritance)多态(Polymorphism)。本文重点讲述继承性,也简单介绍一些封装性。

一、封装

       

1. 封装(Encapsulation)

  • 定义:封装是将数据(成员变量)和操作数据的方法(成员函数)结合在一起,组成一个类,从而实现对数据的隐藏和保护。
  • 目的:通过封装,类的内部细节对外部隐藏,外部只能通过类提供的公有接口(如public的成员函数)来访问和操作内部数据。这种数据隐藏和访问控制机制,增强了程序的安全性和可维护性。
  • 实现
    • 数据成员通常声明为私有的(private),只有通过公共成员函数(如gettersetter)才能访问。
    • 通过访问控制符(publicprotectedprivate)来控制类的成员访问权限。
class Person {
private:std::string name;int age;public:void setName(std::string newName) {name = newName;}std::string getName() {return name;}
};

        像上面这个代码体现了C++中的封装,他将函数,变量放入了一个类当中,作为了类的成员,像我们之前用的友元函数就破坏了封装,使得外部函数能够调用类中的成员。

二、继承

        是指,一个新的类继承/获取已存在的一个类或多个类的属性和行为

若一个类继承其他的一个类,称为单一继承,如果一个类继承其他多个类,称为多继承

当类B继承类A后:

称类A为父类,或者基类

称类B为子类,或者派生类

         那么如果你了解类的结构的话,应该知道,类当中每个成员都有对应的权限,当一个类继承了另一个类的时候,这些对应成员权限继承之后,在子类当中应该是什么权限呢?

        其实,我们在继承类的时候,也有一个继承方式,是按照public(公共),protected(保护),private(私有),这三中方式来进行继承,但是在父类当中的成员来说,他们也有自己的权限,其中的继承方式如下:

类的成员访问权限:

1、public:公有权限,类中、类外、子类都可以访问

2、protected:受保护权限,类中、子类中能够访问,类外不能访问

3、private:私有权限,类中能够访问,类外、子类都不能访问

继承方式:也有3种

1、public继承方式:基类的成员是什么权限,继承到派生类也是对应权限(除了private)

        a、基类中public的成员,继承到派生类中public下

        b、基类中protected的成员,继承到派生类中protected下

        c、基类中private的成员,虽然继承到派生类,但是子类无法访问(没有继承到private下)

2、protected继承方式:基类的权限会提高到protected(除了private)

        a、基类中public的成员,继承到派生类中protected下

        b、基类中protected的成员,继承到派生类中protected下

        c、基类中private的成员,虽然继承到派生类,但是子类无法访问(没有继承到private下)

3、private继承方式:基类的权限会提高到private(除了private)

        a、基类中public的成员,继承到派生类中private下

        b、基类中protected的成员,继承到派生类中private下

        c、基类中private的成员,虽然继承到派生类,但是子类无法访问(没有继承到private下)

 基类和派生的关系

        派生类会继承基类的所有成员,除了(基类的构造函数、析构函数);基类的private成员继承到派生类,但是派生类无法访问,如果一定要访问基类的私有成员,基类要有对应的接口函数    

  • 基类的私有成员是派生类无法直接访问的。这是为了实现面向对象编程中的封装性,保证基类的私有数据不被外界(包括派生类)随意修改或访问。
  • 如果派生类确实需要访问基类的私有成员,通常基类会提供公共或受保护的接口函数
class Base {
private:int privateData;protected:int getPrivateData() {return privateData;  // 基类提供受保护的接口函数}public:void setPrivateData(int data) {privateData = data;  // 基类提供公共的接口函数}
};class Derived : public Base {
public:void printPrivateData() {// 可以通过基类的公有或受保护函数访问私有成员std::cout << "Private data: " << getPrivateData() << std::endl;}
};int main() {Derived d;d.setPrivateData(42);  // 通过公有接口访问私有成员d.printPrivateData();  // 输出:Private data: 42return 0;
}

 派生类不会继承基类的构造函数和析构函数,如下:

class Base {
public:Base(int x) {  // 基类的构造函数std::cout << "Base constructor called with " << x << std::endl;}
};class Derived : public Base {
public:Derived(int y) : Base(y) {  // 派生类通过初始化列表调用基类构造函数std::cout << "Derived constructor called" << std::endl;}
};int main() {Derived d(10);  // 输出:Base constructor called with 10// 输出:Derived constructor calledreturn 0;
}

1. 构造函数的调用顺序

当实例化一个派生类对象时,系统会按照以下顺序调用构造函数:

  • 基类的构造函数先被调用,负责初始化基类的成员。
  • 然后调用派生类的构造函数,负责初始化派生类的成员。
原理:
  • 在实例化派生类时,基类的构造函数必须首先运行,因为派生类的对象本质上是一个扩展的基类对象。如果基类没有正确初始化,派生类无法保证其功能的正确性。
  • 如果派生类的构造函数没有显式调用基类的构造函数,则默认会调用基类的默认构造函数(如果有)。
  • 可以通过派生类的构造函数的初始化列表来显式指定调用哪个基类的构造函数。
#include <iostream>
using namespace std;class Base {
public:Base() {cout << "Base constructor called" << endl;}Base(int x) {cout << "Base constructor with argument " << x << " called" << endl;}~Base() {cout << "Base destructor called" << endl;}
};class Derived : public Base {
public:Derived() : Base(10) {  // 在初始化列表中显式调用基类的构造函数cout << "Derived constructor called" << endl;}~Derived() {cout << "Derived destructor called" << endl;}
};int main() {Derived d;  // 实例化派生类对象return 0;
}

2. 析构函数的调用顺序

析构函数的调用顺序与构造函数的顺序相反

  • 首先调用派生类的析构函数,释放派生类对象的资源。
  • 然后调用基类的析构函数,负责清理基类的资源。
原理:
  • 当销毁一个派生类对象时,派生类的资源先被释放,因为派生类的成员依赖基类对象的成员。
  • 只有当派生类对象的析构函数执行完毕后,基类的析构函数才会被调用,确保所有成员都正确销毁。
class Base {
public:Base() {cout << "Base constructor called" << endl;}~Base() {cout << "Base destructor called" << endl;}
};class Derived : public Base {
public:Derived() {cout << "Derived constructor called" << endl;}~Derived() {cout << "Derived destructor called" << endl;}
};int main() {Derived d;  // 实例化派生类对象return 0;
}

3、派生类的隐藏 

1. 成员函数隐藏

        如果派生类中的成员函数与基类的成员函数同名,即使参数不同,基类中的函数也会被隐藏,而不是重载。为了调用基类中的同名成员函数,需要使用作用域运算符::)来显式调用基类的版本。

#include <iostream>
using namespace std;class Base {
public:void display() {cout << "Base class display" << endl;}void display(int x) {cout << "Base class display with argument: " << x << endl;}
};class Derived : public Base {
public:void display() {cout << "Derived class display" << endl;}
};int main() {Derived d;d.display();  // 调用派生类的 display 函数// d.display(10);  // 错误!派生类隐藏了基类的所有同名函数// 显式调用基类的函数d.Base::display(10);  // 调用基类的 display(int) 函数return 0;
}

注意,在调用基类的成员函数的时候,要加上作用域运算符,来表示是个函数是基类的,否则报错,

2. 成员变量隐藏

        与成员函数类似,如果派生类中的成员变量与基类中的成员变量同名,派生类的成员变量也会隐藏基类的成员变量。要访问基类中的同名成员变量,依然需要使用作用域运算符。

class Base {
public:int var = 10;
};class Derived : public Base {
public:int var = 20;
};int main() {Derived d;cout << "Derived class var: " << d.var << endl;      // 输出派生类的 varcout << "Base class var: " << d.Base::var << endl;   // 使用作用域运算符访问基类的 varreturn 0;
}

3. 基类函数的重载隐藏

        在C++中,重载函数的隐藏行为比较特殊。如果派生类中定义了一个与基类同名的函数,即使该函数的参数列表不同,基类中的所有同名函数都会被隐藏。这与函数重载不同,因为在这种情况下,派生类并没有继承基类的同名函数。

class Base {
public:void func() {cout << "Base class func()" << endl;}void func(int x) {cout << "Base class func(int): " << x << endl;}
};class Derived : public Base {
public:using Base::func;  // 显式引入基类的 func 重载void func() {cout << "Derived class func()" << endl;}
};int main() {Derived d;d.func();        // 调用派生类的 func()d.func(10);      // 调用基类的 func(int) 重载return 0;
}

三、多重继承问题

        多重继承是C++中的一个独特特性,允许一个派生类同时继承多个基类。这意味着一个类可以从两个或多个父类派生,从而继承这些基类中的成员。C++通过这种方式实现了极大的灵活性,但同时也引入了潜在的复杂性,如命名冲突菱形继承问题

        

#include <iostream>
using namespace std;class Base1 {
public:void func1() {cout << "Function from Base1" << endl;}
};class Base2 {
public:void func2() {cout << "Function from Base2" << endl;}
};class Derived : public Base1, public Base2 {
public:void derivedFunc() {cout << "Function from Derived" << endl;}
};int main() {Derived d;d.func1();       // 调用 Base1 的函数d.func2();       // 调用 Base2 的函数d.derivedFunc(); // 调用 Derived 类的函数return 0;
}

1. 构造函数的调用顺序

当一个类继承了多个父类(多重继承)时,构造函数的调用顺序是:

  • 父类的构造函数先被调用,然后再调用派生类的构造函数。
  • 如果有多个父类,按照继承的声明顺序调用基类的构造函数。
  • 派生类的构造函数总是在所有基类的构造函数执行完之后才执行。
#include <iostream>
using namespace std;class Base1 {
public:Base1() {cout << "Base1 constructor called" << endl;}
};class Base2 {
public:Base2() {cout << "Base2 constructor called" << endl;}
};class Derived : public Base1, public Base2 {
public:Derived() {cout << "Derived constructor called" << endl;}
};int main() {Derived d;return 0;
}

2. 析构函数的调用顺序

析构函数的调用顺序与构造函数的顺序正好相反

  • 当一个派生类对象被销毁时,首先调用派生类的析构函数
  • 然后按照继承的逆序依次调用各个基类的析构函数。
  • 如果有多个父类,按照继承声明的逆序调用基类的析构函数。
#include <iostream>
using namespace std;class Base1 {
public:~Base1() {cout << "Base1 destructor called" << endl;}
};class Base2 {
public:~Base2() {cout << "Base2 destructor called" << endl;}
};class Derived : public Base1, public Base2 {
public:~Derived() {cout << "Derived destructor called" << endl;}
};int main() {Derived d;return 0;
}

3. 多重继承中的命名冲突

在多重继承中,如果两个基类拥有同名的成员(变量或函数),派生类将面临命名冲突。这种情况下,派生类需要通过作用域运算符显式地指定调用哪个基类的成员。

#include <iostream>
using namespace std;class Base1 {
public:void show() {cout << "Base1 show" << endl;}
};class Base2 {
public:void show() {cout << "Base2 show" << endl;}
};class Derived : public Base1, public Base2 {
public:void display() {cout << "Derived display" << endl;}
};int main() {Derived d;// d.show();  // 错误:编译器无法确定调用哪一个基类的 show()d.Base1::show();  // 显式调用 Base1 的 show()d.Base2::show();  // 显式调用 Base2 的 show()d.display();      // 调用 Derived 类的 display()return 0;
}

4. 菱形继承问题(Diamond Problem)

        菱形继承是多重继承中的一个经典问题,通常在两个基类都有相同的基类时出现。这种情况下,派生类会通过不同路径继承相同的基类,导致基类中的成员被继承多次,出现二义性和冗余。

#include <iostream>
using namespace std;class Base {
public:int value;Base() : value(10) {}
};class Derived1 : public Base {
};class Derived2 : public Base {
};class FinalDerived : public Derived1, public Derived2 {
public:void show() {// value 是从 Derived1 和 Derived2 都继承来的,出现二义性// cout << value;  // 错误:编译器不知道该从 Derived1 还是 Derived2 继承的 Base 使用 value}
};int main() {FinalDerived fd;// fd.show();  // 无法编译通过,二义性问题return 0;
}

 这里就出现了菱形继承问题,那么我们通过虚继承这种机制来解决这种二义性问题

四、虚继承

        虚继承(virtual inheritance)是C++解决菱形继承问题的一种机制。虚继承主要用于处理多重继承中可能出现的重复继承同一个基类的情况,以避免派生类中存在多个相同基类的副本,从而引发的冗余和二义性问题。

1. 菱形继承问题(Diamond Problem)

        菱形继承问题通常在一个类通过多重继承继承自两个基类,而这两个基类又共享同一个基类时出现。这样,最底层的派生类会通过两个不同的路径继承同一个基类,导致该基类的成员在派生类中出现多份副本,从而引发歧义或重复定义的问题。在多重继承中不可避免

        菱形继承结构如下:

       Base/    \
Derived1  Derived2\    /FinalDerived

2. 虚继承解决菱形继承问题

        为了解决这个问题,C++提供了虚继承。通过虚继承,基类的成员在派生类中只存在一个副本,即使通过多个路径继承基类,也不会创建多份冗余副本。

        虚继承: 在中间子类继承公共基类时,在继承方式前面加上关键字 virtual 。 而后派生子类(汇聚到子类),就只会保留一份公共继承的数据 在派生到子类时,汇聚到子类中,在子类的构造函数需要手动指定 公共基类的构造函数

        只要通过 virtual 关键字 进行 虚继承,在子类中额外添加了虚基类指针,指向虚基类表,存储公共基类的成员,

        那么下面我将介绍在虚继承问题中,虚基类表指针和虚基类表的工作原理,以便理解和记忆

 虚基类指的是那些在继承关系中通过虚继承的方式继承的基类,而不是仅仅指“最开始继承的类”。通过虚继承,派生类不会创建多份基类的副本,无论通过几条继承路径,最终派生类中都只有一份虚基类的实例。

3. 虚基类表(Virtual Base Table,VBTBL)

虚基类表是C++编译器在编译过程中生成的数据结构,主要用于虚继承的类。当类使用虚继承时,编译器创建一个虚基类表,用于存储派生类和基类成员之间的偏移量,以确保在多重继承情况下访问基类的成员时,可以正确地找到基类的成员。

        注意:虚基类表(Virtual Base Table, VBTBL)的主要功能就是存储虚基类成员在派生类对象中的偏移量。除此之外,虚基类表本身不存储其他信息。它的作用相对简单,但在虚继承的场景下,它是至关重要的,确保基类的成员能够在多重继承中被唯一、正确地访问。

虚基类表的主要功能:
  • 存储基类成员的偏移量:虚基类表中记录了基类成员相对于派生类对象的内存偏移量。这样,当需要访问虚基类的成员时,能够通过偏移量正确定位。
  • 管理公共基类的访问:当多个类通过虚继承继承同一个基类时,虚基类表确保在最终派生类中,只有一份公共基类的数据。

4. 虚基类指针(Virtual Base Pointer,VBPTR)

虚基类指针是每个使用虚继承的类的对象内部的一个指针,指向该类的虚基类表。通过虚基类指针,编译器可以动态确定派生类中访问基类成员的位置。

虚基类指针的主要功能:
  • 指向虚基类表:虚基类指针存储在每个使用虚继承的类的对象中,指向虚基类表。
  • 确保唯一的基类副本:在复杂的继承关系中,虚基类指针帮助派生类正确访问公共基类,确保派生类只保留一个基类的副本,而不是多次继承同一个基类副本。

5. 虚基类表和虚基类指针的工作机制

        当一个类通过虚继承继承基类时,编译器在每个虚继承类的对象中插入一个虚基类指针,指向该类的虚基类表。在派生类中,虚基类表用于存储公共基类的成员在派生类对象中的相对偏移位置。这样,派生类的对象可以通过虚基类指针找到虚基类表,并通过虚基类表访问公共基类的成员。

6. 虚继承的内部工作过程

  • 虚基类指针:每个虚继承类的对象中都有一个虚基类指针,用于指向虚基类表。虚基类指针帮助派生类正确访问虚基类的成员。
  • 虚基类表:虚基类表记录了公共基类在派生类对象中的位置偏移量,这样在复杂继承关系中,无论从哪条路径访问基类,最终都能访问到同一个基类副本。

 五、虚继承问题例子

1、实例        

看下面的例子:

class Base {
public:int baseValue;Base() : baseValue(42) {}
};class Derived1 : virtual public Base {
public:int derived1Value;Derived1() : derived1Value(100) {}
};class Derived2 : virtual public Base {
public:int derived2Value;Derived2() : derived2Value(200) {}
};class FinalDerived : public Derived1, public Derived2 {
public:int finalValue;FinalDerived() : finalValue(300) {}
};

在这里:

  • Base类是一个基类。
  • Derived1Derived2分别虚继承Base
  • FinalDerived类通过Derived1Derived2间接继承Base

那么虚基类表指针是怎么来的呢?

2. 虚基类指针(VBPtr)的生成

当一个类通过虚继承继承基类时,编译器为该类的对象插入一个虚基类指针(VBPtr),该指针指向虚基类表(VBTBL),虚基类表存储了基类成员在派生类对象内存布局中的偏移量。

在继承链中,Derived1Derived2 都虚继承了 Base,因此:

  • Derived1的对象会有一个虚基类指针,指向虚基类表,用于确定Base类成员在Derived1对象中的位置。
  • Derived2的对象也有类似的虚基类指针,指向虚基类表,用于确定Base类成员在Derived2对象中的位置。

3. FinalDerived的继承情况

FinalDerived继承了Derived1Derived2时,由于Base是通过虚继承共享的,FinalDerived只会拥有Base类的一个实例。在这种情况下,FinalDerived继承了Derived1Derived2中的虚基类指针(VBPtr),但这两个指针指向的是同一个虚基类表,用于管理Base类的唯一实例。

 那么那么肯定有这样一个问题:

FinalDerived派生类在继承Derived1后,是不是也继承了虚基类表指针,那么这个指针存放的是BaseDerived1对象的偏移量,还是FinalDerivedBase的偏移量?”

答案是:虚基类表中的偏移量指的是Base类成员相对于最终派生类(在此例中是FinalDerived类)对象起始地址的偏移量

  • FinalDerived继承了Derived1Derived2时,虚基类表记录的是Base类成员相对于FinalDerived对象的偏移量。这是因为FinalDerived对象是实际使用的类,而Base类的成员在FinalDerived对象中的具体位置需要通过虚基类表的偏移量来确定。
  • 虚基类表中的偏移量并不会记录Derived1Base之间的偏移量,因为虚基类表的任务是帮助确定虚基类成员在最终派生类对象中的位置,而不是中间派生类对象中的位置。这样你理解了吗。

4. 虚基类指针如何工作

FinalDerived中,Base类的成员(例如baseValue)只存在一份副本。编译器会确保通过虚基类指针,派生类对象可以正确访问Base类的成员。

虚基类指针的作用:
  • Derived1的虚基类指针FinalDerived对象中指向虚基类表,该表包含Base类相对于FinalDerived的偏移量。
  • Derived2的虚基类指针同样指向同一个虚基类表,确保无论是通过Derived1路径还是通过Derived2路径访问Base类成员,访问的都是FinalDerived对象中的唯一Base实例。

 关于虚继承中析构函数和构造函数的调用顺序,这里简单说明一下,和多重继承中的顺序是一样的

相关文章:

C++三大特性——继承性(超万字详解)

目录 前言 一、封装 1. 封装&#xff08;Encapsulation&#xff09; 二、继承 1. 构造函数的调用顺序 原理&#xff1a; 2. 析构函数的调用顺序 原理&#xff1a; 3、派生类的隐藏 1. 成员函数隐藏 2. 成员变量隐藏 3. 基类函数的重载隐藏 三、多重继承问题 1. 构…...

electron使用npm install出现下载失败的问题

我在使用electron进行下载时&#xff0c;经常出现一个错误。 HTTPError: Response code 404 (Not Found) for https://registry.npmmirror.com/v21.4.4/electron-v21.4.4-win32-x64.zip 这个时候需要修改一些npm的配置。使用命令npm config list -ls 滑到下面&#xff0c;找到一…...

HT513 2.8W I2S 输入单声道D类音频功率放大器

1 特性 ● 电源供电 PVDD 2.5-6.5V; DVDD/AVDD 3.3V ● 灵活的音频输入: I2S,LJ, RJ TDM 输入 8,16,32,44.1,48,88.2,96,192kHz 采样频率输出功率: 1.40W(PVDD3.6V,RL4Ω,THDN10%) 2.80W(PVDD5.0V,RL4Ω,THDN10%) 4.70W(PVDD6.5V,RL4Ω,THDN10%) ● THDN0.08%(Po1W, …...

[PICO VR]Unity如何往PICO VR眼镜里写持久化数据txt/json文本

前言 最近在用PICO VR做用户实验&#xff0c;需要将用户实验的数据记录到PICO头盔的存储空间里&#xff0c;记录一下整个过程 流程 1.开启写入权限 首先开启写入权限&#xff1a;Unity->Edit->Player->安卓小机器人->Other Settings->Configuration->Wri…...

zico2打靶记录

一、环境搭建 下载地址&#xff1a;https://download.vulnhub.com/zico/zico2.ova 直接双击下载的.ova文件即可在VMware中打开 设置好保存路径后在虚拟机的设置中删除仅主机这个网卡 然后启动靶机 二、信息收集 扫描靶机ip arp-scan -l 扫描一下开放的端口 nmap -p- -sV…...

pick你的第一个人形机器人——青龙强化学习环境测试

文章目录 一、环境配置二、开始训练三、训练成果 最近感受到的大趋势是具身智能&#xff0c;强化学习&#xff0c;模仿学习做人形机器人&#xff0c;这个赛道很火&#xff0c;颇有前些年全力投入做自动驾驶的架势&#xff0c;正好最近用强化学习解决POMDP问题接触到了强化学习&…...

了解主机及进程资源占用情况、性能情况、性能瓶颈,TOP命令输出解释

列表前的字段解释 字段通俗解释top - 03:08:50 up 19:36当前时间是 03:08:50,系统已经运行了 19 小时 36 分钟1 user当前有 1 个用户登录使用系统load average: 0.00, 0.02, 0.00系统在过去 1 分钟、5 分钟和 15 分钟内平均的工作繁忙程度,数值越大表示越忙 对于一个 x个核的…...

计算机网络-小型综合网络的搭建涉及到无线路由交换安全

目录 1 拓扑架构 2 做项目的思路 3 做配置 3.1先做核心交换 3.2 防火墙的配置 4 ac 和ap 的配置 4.1 ac上配置安全的东西 5.1 测试​编辑 1 拓扑架构 要求看上面的图 2 做项目的思路 这张网很明显是一个小综合&#xff0c;设计到我们的无线交换&#xff0c;路由…...

CleanClip For Mac 強大的剪貼簿助手Paste替代工具 v2.2.1

软件介绍&#xff1a; CleanClip是一款专为Mac设计的强大剪贴板管理工具&#xff0c;旨在提升用户的工作效率和生产力。这款应用完全采用原生Swift编写&#xff0c;为Mac用户提供了流畅、快速且直观的使用体验。CleanClip不仅支持文本内容的管理&#xff0c;还能处理图片、文件…...

python全栈学习记录(十八)re、os和sys、subprocess

re、os和sys、subprocess 文章目录 re、os和sys、subprocess一、re1.正则字符2.正则表达式的使用3.group的使用4.贪婪匹配与惰性匹配5.其他注意事项 二、os和sys1.os2.sys 三、subprocess四、打印进度条 一、re python中的re模块用来使用正则表达式&#xff0c;正则就是用一系…...

GO Fsnotify学习与使用

文章目录 说明demo 说明 fsnotify 是 Go 的一个文件系统通知库&#xff0c;可以监视文件或目录的变化。基本用法如下&#xff1a; 安装库&#xff1a; go get github.com/fsnotify/fsnotify创建 watcher&#xff1a; watcher, err : fsnotify.NewWatcher() if err ! nil {log.…...

除了递归算法,要如何优化实现文件搜索功能

大家好&#xff0c;我是 V 哥&#xff0c;今天的文章来聊一聊 Java实现文件搜索功能&#xff0c;并且比较递归算法、迭代方式和Memoization技术的优缺点。 以下是一个使用 Java 实现的文件搜索功能&#xff0c;它会在指定目录及其子目录中搜索包含特定关键字的文件。此实现使用…...

【AI算法岗面试八股面经【超全整理】——NLP】

AI算法岗面试八股面经【超全整理】 概率论【AI算法岗面试八股面经【超全整理】——概率论】信息论【AI算法岗面试八股面经【超全整理】——信息论】机器学习【AI算法岗面试八股面经【超全整理】——机器学习】深度学习【AI算法岗面试八股面经【超全整理】——深度学习】NLP【A…...

Unity 设计模式 之 创建型模式 -【单例模式】【原型模式】 【建造者模式】

Unity 设计模式 之 创建型模式 -【单例模式】【原型模式】 【建造者模式】 目录 Unity 设计模式 之 创建型模式 -【单例模式】【原型模式】 【建造者模式】 一、简单介绍 二、单例模式 (Singleton Pattern) 1、什么时候使用单例模式 2、单例模式的好处 3、使用单例模式的…...

CSS中的字体样式、文本样式、列表样式以及背景和渐变

一、字体样式和文本样式 1.span标签 span标签的作用&#xff1a;能让某几个文字或者是词语凸显出来 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-…...

教你如何在Java中操作Redis

Redis的Java客户端 Redis 的 Java 客户端很多&#xff0c;常用的几种&#xff1a; Jedis&#xff1a;基于 Java 的 Redis 客户端&#xff0c;提供了 Redis 命令的全面支持。Lettuce&#xff1a;高性能的 Java 客户端库&#xff0c;用于与 Redis 数据库进行交互。它支持同步、…...

nsight-system教程

一 安装 参考 nsight-compute使用教程-CSDN博客https://blog.csdn.net/youzjuer/article/details/142549332 二 使用 nsys profile -t cuda,nvtx --force-overwrite true -o xxxx python xxx.py xxxx表示生成文档的名字 如果是cuda c 则 python xxx.py替换为./xxx即可 这…...

进程的那些事--进程控制

目录 前言 一、创建进程 二、退出进程 void exit (int retval) 三、进程等待 四、进程替换 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 前面我们认识了进程&#xff0c;现在让我们认识几个进程的接口 提示&#xff1a;以下是本篇文章正文内容…...

FortiOS SSL VPN 用户访问权限配置

简介 使用不同用户组或用户登录 SSL VPN 隧道模式后&#xff0c;可配置不同的访问权限。 本文介绍为不同用户组分配不同访问权限的配置方法。 相关组件 FortiGate&#xff1a;FortiOS v6.4.14 build2093 (GA) 客户端&#xff1a;Windows11&#xff0c;安装 FortiClient VPN 7.…...

C++_vector类

欢迎来到本期节目- - - vector类 本期直接先上代码&#xff0c;然后以代码为例介绍需要注意的问题. 模拟实现&#xff1a; #pragma once #include<iostream> #include<assert.h> using namespace std;namespace my_room {template<class T>class vector{p…...

Spring Boot入门到精通:网上购物商城系统

第3章 系统分析 3.1 可行性分析 在系统开发之初要进行系统可行分析&#xff0c;这样做的目的就是使用最小成本解决最大问题&#xff0c;一旦程序开发满足用户需要&#xff0c;带来的好处也是很多的。下面我们将从技术上、操作上、经济上等方面来考虑这个系统到底值不值得开发。…...

在Vue.js中,你可以使用Element UI的el-input组件结合计算属性来实现模糊查询

<template><div><el-input v-model"searchQuery" placeholder"请输入查询内容"></el-input><div v-for"item in filteredList" :key"item">{{ item }}</div></div> </template><s…...

delphi制作漂亮的农历窗体(IntraWeb+Layui的完美结合)

delphi制作漂亮的农历窗体&#xff08;IntraWebLayui的完美结合&#xff09; 不需要安装服务器&#xff0c;Apache和IIS都不需要&#xff0c;自带企业级服务器。 运行exe服务器就架好了&#xff0c;直接打开手机浏览器或者电脑浏览器&#xff0c;网页就出来了&#xff0c;如果…...

发票OFD格式转换成PDF

引入依赖&#xff0c;低版本的报错&#xff0c;2.0.2能够实现转换 <dependency><groupId>org.ofdrw</groupId><artifactId>ofdrw-converter</artifactId><version>2.0.2</version><exclusions><exclusion><groupId&g…...

高通AI应用程序开发3:网络模型(一)

1. 支持的网络模型 Qualcomm神经处理SDK支持下表所列的网络模型。 有关支持的运行时和单个图层类型的限制和约束的详细信息&#xff0c;请参阅 限制 。 GPU运行时中支持的所有层对两种GPU模式都有效&#xff1a;GPU_FLOAT32_16_HYBRID和GPU_FLAAT16。GPU_FLOAT32_16_HYBRID-…...

03. 前端面试题之ts : typescript 的数据类型有哪些?

文章目录 一、typescript是什么二、typescript有哪些数据类型booleannumberstringarraytupleenumanynull 和 和 undefinedvoidneverobject 三、总结 一、typescript是什么 typescript 和 javascript几乎一样&#xff0c;拥有相同的数据类型&#xff0c;另外在javascript基础上…...

PyCharm和VS Code 安装通义灵码,可本地安装包安装,解决插件安装不上问题

PyCharm和VS Code 安装通义灵码&#xff0c;可本地安装包安装&#xff0c;解决插件安装不上问题 PyCharm、VS Code 安装通义灵码介绍主要应用场景支持编程语言安装指南JetBrains IDEs 中安装指南步骤 1&#xff1a;准备工作步骤 2&#xff1a;在 JetBrains IDEs 中安装通义灵码…...

机器人速度雅可比矩阵求解(2自由度平面关节机器人)

关节速度和末端速度空间的映射需要计算雅可比矩阵的逆矩阵,在博途PLC里如何计算一个方阵的逆矩阵,大家可以参考下面这篇文章: 博途PLC矩阵求逆 矩阵求逆 博图SCL_博图矩阵运算-CSDN博客文章浏览阅读839次。本文介绍如何用C语言实现矩阵求逆的过程,详细解析了相关代码,适…...

【AI大模型-文心-思维树解读-开篇】

提问&#xff1a;什么是“”“思维树”“”模型框架 回答&#xff1a;如下 版本&#xff1a;文心大模型3.5 “思维树”&#xff08;Tree of Thoughts, ToT&#xff09;模型框架是一个利用大型语言模型进行问题解决的框架。它借鉴了人类认知研究的成果&#xff0c;特别是关于人…...

2、electron vue3 怎么创建子窗口,并给子窗口路由传参

接上回初始化vue3 electron项目&#xff0c;创建完vue3 electron项目后&#xff0c;现在要实现在渲染进程中点击按钮创建一个新的子窗口 开始 子窗口创建操作只能在主线程内完成&#xff0c;而创建操作是在渲染线程触发&#xff0c;因此就需要进行两者间的通讯。 1、创建子窗…...