C++多态的认识与理解
多态的定义
多态其实就是同一操作在不同的对象上可以有不同的实现方式。
多态的类型
多态分为静态多态和动态多态两种,而静态多态其实我们之前就了解过,今天主要是讲解一下动态多态。
- 静态多态(编译时多态):静态多态其实就是在函数进行编译阶段根据函数的参数列表来确定调用哪个具体的函数的,其中主要就是函数重载和运算符重载。
- 动态多态(运行时多态):运行时多态是在程序运行阶段才能确定具体调用哪个函数(通过对象的虚表指针)。它是基于函数重写实现的。当通过基类指针或引用调用虚函数时,实际调用的是派生类中重写后的虚函数。
构成多态的条件
- 虚函数重写
- 必须通过父类的指针或引用去调用虚函数
虚函数重写
虚函数就是被virtual关键字修饰的成员函数。
虚函数的重写就是派生类中有一个跟基类完全相同的虚函数,这就称子类的虚函数重写了基类的虚函数。而这里的完全相同是派生类虚函数与基类虚函数的返回值类型、函数名、参数列表类型完全相同
class Person
{
public:virtual void test() { ... }//虚函数
};
class Student : public Person
{
public:virtual void test() { ... }//虚函数重写
};
注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,也是可以构成重写,因为继承时基类的虚函数被继承下来了,而在派生类依旧保持虚函数属性,所以可以不加virtual关键字修饰。但是有点不咋规范。
父类的指针或引用调用虚函数
首先我们要知道指针是一个变量,指向的是一个地址,而指针的类型恰恰就决定了通过该地址可以访问的空间大小。而引用是取别名(声明时初始化,之后不能绑定其他对象)。基类的指针或引用指向派生类的话,就会使得基类的对象指向的是派生类的前部分空间的内容。所以当基类指针或引用的对象去调用虚函数时就形成了多态,从而调用的实际就是派生类的虚函数。而内部实现机制后文有解释。
多态调用实例
class Person
{
public:virtual void BuyTicket() { cout << "买票-全价" << endl; return nullptr; }//
};
class Student : public Person
{
public:virtual void BuyTicket() { cout << "买票-半价" << endl; return nullptr; }//
};
void Func(Person& p)//父类的引用为形参
{p.BuyTicket();
}
int main()
{Person ps;Student st;Func(ps);//传递父类对象Func(st);//传递子类对象(形成多态)return 0;
}
通过指针或引用调用虚函数看的并不是通过参数的类型去调用对应的函数方法,而是该参数所实际指向的对象,指向父类对象就调用父类的虚函数,指向子类对象就调用子类的虚函数。
虚函数重写的两个例外
协变
协变就是虚函数重写的时候,基类和派生类的函数返回值类型可以不同但是必须是父子类关系的指针或引用。如下:
class A {};
class B :public A{};class Person
{
public:virtual A* test() {return nullptr; }//
};
class Student : public Person
{
public:virtual B* test() {return nullptr; }//
};
就像上面的例子,父子类虚函数的返回值 必须是父子类关系的指针或引用,因为A类与B类也是父子关系,所以虚函数的返回值也应该将person类与student类的父子关系对应起来,即父类A对应父类Person,子类Student对应子类B。
析构函数的重写
其实基类与派生类的析构函数也是可以构成虚函数重写的,因为针对一些特定的情况,必须要使得基类与派生类实现重写才可以实现代码的正确安全性。
如以下情况:
看以下代码运行:
class Person { public:Person(){cout << "Person()" << endl;}~Person(){cout << "~Person()" << endl;} }; class Student : public Person { public:Student(){cout << "Student()" << endl;}~Student(){cout << "~Student()" << endl;} }; int main() {Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0; }
首先我们知道new对象时,会为该对象分配空间,所以会自动调用该对象的构造函数。但是student类继承了person类,所以在创建student的对象时,会先调用父类的构造函数,然后再调用自己的构造函数。
同样当我们进行delete时也会先进行调用析构函数然后在进行空间的释放。但是delete是根据具体的delete对象的类型去调用析构函数的,并不是根据该对象原先new的方式进行delete的。所以出现以上现象:delete p2时只会调用的就是Person类的析构函数,并不会调用Student的析构。
因为我们new的是Student对象,所以释放肯定是想着要释放Student对象的。但是delete是根据具体的delete对象的类型去调用析构函数的,是父类的指针就调用父类的析构,是子类的指针就调用子类的析构。而并不会根据你原先new的空间进行析构。但是我们创建的是子类的对象:因为实现了多态,所以父类的指针不仅仅是源于父类也可能经过子类赋值给父类的。所以此时析构的调用实际上就与我们意想中的不同了,这种情况就极有可能造成了内存泄漏。
解决:
首先我们要知道在子类中是不能显示调用析构函数的,其原因就是父子类的析构函数的函数名其实是被修饰过成一样的:destructor。而父子类的函数名相同则会构成隐藏。
所以可以通过以下方式在子类中进行显示的调用父类的析构函数:
Person::~Person();
其实C++这一设计(父子类的析构函数名相同)其实就是为了解决以上的问题,所以既然函数名相同,且都没有参数。那么通过virtual进行修饰的话,那么父子类的析构函数不就构成了重载吗。所以回到题目中delete p2,而p2指向的是子类的对象,那么不就会调用子类Student的析构函数吗。又因为父子类的继承关系,调用子类的析构结束以后不就也会调用父类的析构吗。
如下:
class Person { public:Person(){cout << "Person()" << endl;}virtual ~Person()//virtual构成重写{cout << "~Person()" << endl;} }; class Student : public Person { public:Student(){cout << "Student()" << endl;}virtual ~Student(){cout << "~Student()" << endl;} }; int main() {Person* p = new Student;delete p;return 0; }
多态例题解析
class A
{
public:virtual void func(int val = 1){cout << "A->" << val << endl;}virtual void test() //隐藏的this指针类型A*{func();}
};
class B : public A
{
public:void func(int val = 0){cout << "B->" << val << endl;}
};
int main()
{B* p = new B;p->test();//p的类型是B*return 0;
}
解析:首先A类的test函数会被B类继承下来,但是B类并没有对test进行重写,而func函数在父子类之间形成了重写。
我们知道p的类型是B*的,然后调用了test函数父类的test函数。而test函数内部又调用了func()函数,但是别忘了test()成员函数内部有一个隐藏的this指针,类型是A*,所以在test内部调用func函数的过程就形成了父类对象的指针调用虚函数,同时虚函数进行了重写。此时就构成了多态的条件。
但最关键的一点是:子类在虚函数重写时重写的是函数内部的实现,而函数接口声明的部分是会从父类继承下来的(也就是会复用父类的函数接口参数)。
所以参数部分就取决于A类的func()函数,而内部的实现就看B类的func()函数。
重写隐藏重载的区别
函数重载:重载函数发生在同一作用域里,函数名相同,参数列表不同。
函数隐藏(重定义):发生在基类和派生类中,只要求函数名相同即可。
函数重写(覆盖):发生在基类和派生类中,且都为虚函数,同时函数名相同,函数参数列表相同,返回值相同(除了协变)
基类和派生类的同名函数不是重写就是隐藏(绝不是函数重载)。
认识抽象类
在了解抽象类之前要知道什么是纯虚函数,纯虚函数就是在虚函数后面写上=0;而该纯虚函数所在的类就是抽象类。(包含纯虚函数的类就是抽象类)
抽象类的特点:不能实例化出对象,而且继承了抽象类的子类也不能实例化对象,除非该子类重写纯虚函数,才可以实例化类对象。
所以纯虚函数规范了派生类必须进行重写。故纯虚函数用于定义一个通用的接口,让不同的派生类去实现这个接口,以适应不同的具体需求。纯虚函数主要体现接口继承。
而接口继承其实就是派生类继承了基类纯虚函数的接口,目的是为了重写达成多态。
class Shape //图形抽象类
{
public:virtual void draw() = 0;virtual double area() = 0;
};class Circle : public Shape
{
public:void draw() override {// 绘制圆形的具体代码}double area() override {// 计算圆形面积的具体代码}
};class Rectangle: public Shape
{
public:void draw() override {// 绘制长方形的具体代码}double area() override {// 计算长方形面积的具体代码}
};
多态的原理(X64)
虚函数表
虚函数是形成多态的重要条件,我们知道类的成员函数是存在代码段的,其实虚函数也不例外,但是虚函数的地址是单独被拿出来了,放到了一个虚函数表(相当于一个函数指针数组)当中,而一个有虚函数的类中不仅仅存放成员变量,还存放这个虚函数表的地址即虚表指针。
在VS的X64环境下演示:
class Base { public:virtual void Func1(){cout << "Func1()" << endl;}virtual void Func2(){cout << "Func2()" << endl;}void Func3()//非虚函数{cout << "Func3()" << endl;} private:int _b = 1; }; int main() {Base b;cout << sizeof(Base) << endl; }
重写后的的虚函数表
我们只知道,有成员虚函数就有虚函数表,在多态中我们知道虚函数的重写是构成多态的重要条件,下面我们就来看一下重写后的的虚函数表是什么样子:
class Base { public:virtual void Func1(){cout << "Func1()" << endl;}virtual void Func2()//非虚函数{cout << "Func2()" << endl;} private:int _a = 1; }; class Child:public Base { public:virtual void Func1(){cout << "Func1()" << endl;} private:int _b = 3; };
首先我们能发现父子类的虚函数表不是共有的,是各自独有一份的。
父类的虚函数表就如之前演示的一样,因为父类中有两个虚函数,所以虚函数表中就有两个数据(虚函数指针)。而子类继承了父类,但是子类的func1()函数与父类的func1()函数构成重写,同时子类也会继承父类未重写的虚函数。子类的虚函数表就是上图的样子,虚函数表中子类的非重写虚函数继承了父类的非重写虚函数,所以函数指针不变。而对于重写的虚函数则会进行覆盖父类的虚函数,因此重写后的虚函数指针发生改变。所以可以说子类重写虚函数就相当于父类虚函数被子类覆盖。
所以言归正传,当我们将子类对象给父类的指针或引用时,此时父类对象所指向的内容就会发生改变,从而指向的就是子类那一部分空间(红框框部分),所以当父类对象调用重写后的虚函数时,其实访问就是子类的虚函数表指针,故而访问的就是子类的虚函数,所以就形成了多态。
其实尽管子类中没有虚函数,全是继承父类的虚函数的情况下(父子类虚表存放的函数指针是一模一样的),父类也不会套用子类的虚表指针,也是单独存一份虚函数表指针。虚表指针不同,所以虚表也不同,但是虚表里面的虚函数指针是一模一样的。而且一个类无论创建多少个对象,所存储的的虚表指针都是一样的。 所以说一个类的所有对象都共用一张虚表
通过切片无法形成多态
根据前面的认识我们知道当一个类中有虚函数时,编译器会为该类创建一个虚函数表。虚函数表是一个存储虚函数指针的数组,每一个元素对应着一个虚函数。
首先我们要知道动态多态的实现是依靠对象中的虚表指针所指向的虚表。指向父类就调用父类的虚函数,指向子类就调用子类的虚函数。
首先假设我们有两个类,父类:Animal,子类Dog。当Dog d = Animal();时:
此时就会进行切片处理,将子类的成员拷贝给父类,但是虚表指针并不会拷贝过去。所以d对象在调用虚函数时,只会调用Dog类的成员虚函数,而不是Animal类。因为编译器在编译阶段,对于通过对象(而非指针或引用)调用虚函数的情况,会按照对象的静态类型来确定虚函数表的入口。因为 animal
的静态类型是 Animal
,所以它的 vptr
会指向 Animal
类的虚函数表,即使它是从 Dog
对象切片得来的。其实说实话就是虚表指针并不会随着切片的过程中赋值给父类对象。因为非指针或引用的对象在编译阶段就已经通过对象的静态类型确定好了哪个类的虚表。还有就是因为如果简单的赋值切片的过程就会赋值虚表指针的话,那么仅通过够通过父类对象就会形成多态,那么后续的父类对象就无法保证父类的虚表。后续使用会十分紊乱。
所有虚函数都存在虚表中
我们知道如果一个类中有虚函数的话就存在虚函数表。所以而我们监视窗口中还可以看到类中是存放有一个指向虚表首元素地址的指针,虚表中存放的是虚函数的指针,所以当我们计算类的大小时要将虚指针也计算在内。
回到问题,我们先用监视窗口查看一下虚表的内容:
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; }virtual void func4() { cout << "Derive::func4" << endl; }
private:int b;
};
此时从监视窗口中可以看出Derive类中虚表只存放了两个虚函数一个是继承并重写的func1 虚函数,另一个是继承下来的func2 函数,但是为什么不见Derive类中的func3和func4呢。其实监视窗口只会展开部分内容,最终想查看详细信息还是直接看内存空间的内容:(X64平台)
很明显全0的位置就是分界线的地方(仅猜测),而且虚表中存放了四个函数指针的值,自然知道分别是重写的func1继承的func2,以及原有的func3和func4.为了验证这一问题我们可以先初步的通过函数指针去调用对应的虚函数,如果调用没问题则证明我们的猜想是正确的,反之则是错误的:
typedef void(*VF)();//将无参的函数指针类型重命名成VF void Print(VF* p)//该函数接收虚表指针,然后依次调用虚函数 {int i = 1;while (*p)//vs结束标记:函数指针内容是全0{printf("func%d = %p->",i++, *p);(*p)();//*p就是虚表里的函数指针,可以代替函数名直接调用函数p++;//跳过当前函数指向下一个函数}} int main() {Base b;Derive d;Print((VF*)*(long long*)&b);//传虚表的地址cout << endl;Print((VF*)*(long long*)&d);//*(long long*)&b 是拿到该对象前八个字节的内容,也就是虚表指针//防止传参时类型不匹配,强转return 0; }
所以可以证明:一个类中所有的虚函数都会存在该类对应的虚函数表中
多继承下的虚表
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(*VF)();//将无参的函数指针类型重命名成VF
void Print(VF* p)//该函数是为了实现打印虚表内的函数指针并且直接调用
{int i = 1;while (*p){printf("func%d = %p->", i++, *p);(*p)();//*p就是虚表里的函数指针,可以代替函数名直接调用函数p++;}}
首先我们要了解,多继承的派生类中不是只有一个虚表。就拿上面的例子来说,Derive类同时先后继承了Base1类和Base2类,而Base1和Base2类都有各自对应的虚表,同时三个类都有同一个func1函数,被Derive继承以后也就构成了重写,所以说将两个基类的func1函数都进行了重写。但是问题是Derive类对象中会有几张虚表指针呢?而且Derive类对象的func3虚函数该存放到哪里呢?通过打印内存情况展示:
其实经过虚表内容的打印后,不难看出func3其实是放在第一个继承下来的虚表内部的。对于重写的func1函数,我们可以看到Derive类中对继承的两个Base类的func1函数都进行了重写。
所以说多继承中子类继承了几个父类(都有虚表指针)就有几个虚表指针,内存布局情况取决于继承顺序。
可以为虚函数吗?
1.构造函数可以是虚函数吗?
编译报错,因为对象中的虚函数表指针是在构造函数阶段才开始初始化的,虚函数调用要在虚表中去寻找函数指针,但此时虚表指针还未初始化。
2.inline函数可以是虚函数吗?
可以,符合内联的函数没有函数地址,是直接在调用处进行展开的。但是多态调用时会忽略inline的作用,只有普通调用inline才会起作用。所以准确说:内联函数不可以是虚函数,但是多态调用时会忽略inline,即不再是被当做内联函数。
3.析构函数可以是虚函数吗?
可以,而且最好是实现虚函数的重写,此时内存空间可以正确的进行释放。
4.静态函数可以是虚函数吗?
编译报错,因为虚函数实际是通过类内的虚表指针去访问的,而static修饰的静态成员函数没有this指针,所以没有虚表指针,也就无法访问虚表。
经典题目
test_1
class A { public:A() :m_iVal(0) { test();}virtual void func() {cout << m_iVal << ' '; }void test() {func(); }public:int m_iVal;};class B : public A { public:B() {test();}virtual void func(){++m_iVal;cout << m_iVal << ' ';}}; int main(int argc, char* argv[]) {A* p = new B;p->test();return 0; }
在
A
类的构造函数中调用test()
函数,而test()
函数又调用了虚函数func()
。当创建B
类的对象时,会先调用A
的构造函数,此时B
类对象的虚表指针(vptr)还未被正确设置为指向B
类的虚函数表(即虚函数表指针还未初始化),因为在A
的构造函数中,对象被认为是A
类对象,其虚表指针指向A
类的虚函数表。因此在A
的构造函数中调用test()
并调用func()
时,会调用A::func()
而不是B::func()
。此后开始调用类的构造函数,此时B
类对象的虚表指针(vptr)已经被正确设置为指向B
类的虚函数表,所以之后的调用就符合多态。所以说:虚函数指针是在对象构造阶段才开始初始化,而虚表是在编译阶段就已经生成(存储在代码段)
test_2
class A { public:virtual void f(){cout << "A::f()" << endl;}};class B : public A { private://私有限定符virtual void f(){cout << "B::f()" << endl;} }; int main() {A* pa = (A*)new B;pa->f();B b;return 0; }
多态不会受到访问限定符的限制。
在C++中,访问限定符(如public、private、protected)用于控制类中成员的访问权限。
但是,在多态中,派生类必须能够访问基类中的虚函数,以便实现多态性,无论这个虚函数的访问限定符是什么派生类都可以通过继承方式获取基类中的所有成员函数,包括私有成员函数。
相关文章:
C++多态的认识与理解
多态的定义 多态其实就是同一操作在不同的对象上可以有不同的实现方式。 多态的类型 多态分为静态多态和动态多态两种,而静态多态其实我们之前就了解过,今天主要是讲解一下动态多态。 静态多态(编译时多态):静态多态其实就是在…...
improve-gantt-elastic(vue2中甘特图实现与引入)
1.前言 项目开发中需要使用甘特图展示项目实施进度,左侧为表格计划,右侧为图表进度展示。wl-gantt-mater,dhtmlx尝试使用过可拓展性受到限制。gantt-elastic相对简单,可操作性强,基础版本免费。 甘特图(Gan…...
模型 笛卡尔思维
系列文章分享模型,了解更多👉 模型_思维模型目录。怀疑一切,分析整合,验证真理。 1 笛卡尔思维模型的应用 1.1 笛卡尔思维模型在城市规划中的应用 背景:某城市计划进行新的城市规划,以提高城市的可持续性…...
LabVIEW桥接传感器数据采集与校准程序
该程序设计用于采集来自桥接传感器的数据,执行必要的设置(如桥接配置、信号采集参数、时间与触发设置),并进行适当的标定和偏移校正,最终通过图表呈现采集到的数据信息。程序包括多个模块,用于配置通道、触…...
无人机技术架构剖析!
一、飞机平台系统 飞机平台系统是无人机飞行的主体平台,主要提供飞行能力和装载功能。它由机体结构、动力装置、电气设备等组成。 机体结构:无人机的机身是其核心结构,承载着其他各个组件并提供稳定性。常见的机身材料包括碳纤维、铝合金、…...
飞牛 使用docker部署Watchtower 自动更新 Docker 容器
Watchtower是一款开源的Docker容器管理工具,其主要功能在于自动更新运行中的Docker容器 Watchtower 支持以下功能: 自动拉取镜像并更新容器。 配置邮件通知。 定时执行容器更新任务。 compose搭建Watchtower 1、新建文件夹 先在任意位置创建一个 w…...
【Flink系列】4. Flink运行时架构
4. Flink运行时架构 4.1 系统架构 Flink运行时架构——Standalone会话模式为例 1)作业管理器(JobManager) JobManager是一个Flink集群中任务管理和调度的核心,是控制应用执行的主进程。也就是说,每个应用都应该被…...
【机器学习实战入门】使用Pandas和OpenCV进行颜色检测
Python 颜色检测项目 今天的项目将非常有趣和令人兴奋。我们将与颜色打交道,并在项目过程中学习许多概念。颜色检测对于识别物体来说是必要的,它也被用作各种图像编辑和绘图应用的工具。 什么是颜色检测? 颜色检测是检测任何颜色名称的过程…...
C++ K2 (2)
提示:文章 文章目录 前言一、背景标准库基础知识堆栈 总结 前言 前期疑问: 本文目标: 一、背景 接上文 标准库 1、(单选)【STL】在以下容器中间插入一个元素,时间复杂度为O(1)的是(A&#x…...
【React】静态组件动态组件
目录 静态组件动态组件创建一个构造函数(类)使用 class 实现组件**使用 function 实现类组件** 静态组件 函数组件是静态组件: 组件第一次渲染完毕后,无法基于内部的某些操作让组件更新「无法实现自更新」;但是,如果调用它的父组…...
Spring Web MVC综合案例
承接上篇文章——Spring Web MVC探秘,在了解Spring Web MVC背后的工作机制之后,我们接下来通过三个实战项目,来进一步巩固一下前面的知识。 一、计算器 效果展示:访问路径:http://127.0.0.1:8080/calc.html 前端代码&a…...
OpenCV相机标定与3D重建(60)用于立体校正的函数stereoRectify()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 为已校准的立体相机的每个头计算校正变换。 cv::stereoRectify 是 OpenCV 中用于立体校正的函数,它基于已知的相机参数和相对位置&am…...
SDL2基本的绘制流程与步骤
SDL2(Simple DirectMedia Layer 2)是一个跨平台的多媒体库,它为游戏开发和图形应用提供了一个简单的接口,允许程序直接访问音频、键盘、鼠标、硬件加速的渲染等功能。在 SDL2 中,屏幕绘制的流程通常涉及到窗口的创建、渲染目标的设置、图像的绘制、事件的处理等几个步骤。…...
计算机网络 (42)远程终端协议TELNET
前言 Telnet(Telecommunication Network Protocol)是一种网络协议,属于TCP/IP协议族,主要用于提供远程登录服务。 一、概述 Telnet协议是一种远程终端协议,它允许用户通过终端仿真器连接到远程主机,并在远程…...
重拾Python学习,先从把python删除开始。。。
自己折腾就是不行啊,屡战屡败,最近终于找到前辈教我 第一步 删除Python 先把前阵子折腾的WSL和VScode删掉。还是得用spyder,跟matlab最像,也最容易入手。 从VScode上搞python,最后安装到appdata上,安装插…...
51c大模型~合集106
我自己的原文哦~ https://blog.51cto.com/whaosoft/13115290 #GPT-5、 Opus 3.5为何迟迟不发 新猜想:已诞生,被蒸馏成小模型来卖 「从现在开始,基础模型可能在后台运行,让其他模型能够完成它们自己无法完成的壮举——就像一个老…...
node安装教程及环境配置
1.下载安装包 下载的网址:Node.js — Download Node.js 根据自己电脑系统及位数选择,电脑是Windows系统、64位、想下载稳定版的.msi(LTS为长期稳定版)这里选择windows64位.msi格式安装包。 .msi和.zip格式区别: .msi…...
Temp123
MapDB:的持久化机制,以及源码分析和摘取 1、spark streaming--struct streaming 基于 时间间隔 攒批 2、kafka-connect-hdfs 控制 flush.size 和 interval.ms控制 攒批 - 完全自研 攒批机制 - 使用 embeded 版 https://lxblog.com/qianwen/share?shar…...
YOLO系列代码
Test-Time Augmentation TTA (Test Time Augmentation)是指在test过程中进行数据增强。其思想非常简单,就是在评测阶段,给每个输入进行多种数据增广变换,将一个输入变成多个输入,然后再merge起来一起输出,形成一种ens…...
2025.1.16——一、NewsCenter
题目来源:攻防世界 NewsCenter 目录 一、题目 二、sqlmap解题——bp抓包存为txt文件进行爆破 step 1:search框内随便输入,进行抓包 step 2:抓包后,存到txt文件,进行sqlmap step 3:-r获取文…...
Java中的继承
引入继承 Java中使用类对实体进行描述,类经过实例化之后的产物对象,就可以用来表示现实中的实体,描述的事物错综复杂,事物之间可能会存在一些关联,因此我们就需要将他们共性抽取,面向对象的思想中提出了继…...
vue3+ts+uniapp 微信小程序(第一篇)—— 微信小程序定位授权,位置信息权限授权
文章目录 简介一、先看效果1.1 授权定位前,先弹出隐私协议弹框1.2 上述弹框点击同意,得到如下弹框1.3 点击三个点,然后点设置 1.4 在1.2步骤下,无论同意或者拒绝 二、manifest.json 文件配置三、微信公众平台配置3.1 登录进入微信…...
在Playwright中使用PO模式
1.新建项目 安装库 npm init -y npm install -D playwright npm install -D playwright/test npm install typescript ts-node types/node npx playwright install 项目目录 2.编写代码 package.json {"name": "pom_playwright","version": …...
三台 Centos7.9 中 Docker 部署 Redis 哨兵模式
三台 Centos7.9 中 Docker 部署 Redis 哨兵模式 1. 环境规划2. 配置 Docker Compose3. 配置 Redis 密码和持久化4. 配置哨兵5. 启动服务6. 验证 Redis 哨兵模式7. 注意事项 1. 环境规划 三台服务器的角色分配如下: IP Address容器端口角色192.168.15.128redis-mas…...
JSON数据格式的序列化和反序列化jackson针对首字母小学的字段返回序列化后第2个大写字母也变成小写的问题处理
SpringBoot类属性”第二个字母大写“反序列化问题。key第二个字母大写会被转成小写 , 这个为 jackson 的bug。 后台 : String pName; public String getPName() { return pName; } 前台 : { pname : xxx } 解决方案: 使用JsonProperty("pName")…...
Android wifi列表中去自身的热点
Android wifi列表中去自身的热点 一、前言 Android wifi列表中能搜索到自身的热点wifi? 正常手机上都不会出现这个问题;可能是系统底层已经做了过滤处理。 现实开发中Android设备的Wifi能搜索到自身热点也可能会存在。 比如基于两个单独的wifi双模组硬…...
Elasticsearch:Jira 连接器教程第二部分 - 6 个优化技巧
作者:来自 Elastic Gustavo Llermaly 将 Jira 连接到 Elasticsearch 后,我们现在将回顾最佳实践以升级此部署。 在本系列的第一部分中,我们配置了 Jira 连接器并将对象索引到 Elasticsearch 中。在第二部分中,我们将回顾一些最佳实…...
单线性激光扫描、多线性激光扫描?激光扫描三维重建算法环节
分类 都属于激光扫描技术,但它们在光源的数量和工作方式上存在一些差异。 单线性激光扫描: 单线性激光扫描使用单个线状光源进行扫描。光源沿一个方向移动,将一条直线上的目标进行扫描。这种方式适用于需要获取目标表面在一个维度上的信息的…...
git 常用命令 git archive
git archive 是 Git 中用于创建一个包含指定提交或分支中所有文件的归档文件(如 .tar 或 .zip)的命令。这个命令非常适合用于分发项目快照、备份代码库或导出特定版本的文件。 git archive --formatzip --outputproject.zip HEAD …...
CMD批处理命令入门(6)——常用的特殊字符
CMD批处理命令入门(6)——特殊字符 本章内容主要学习要点:重定向符 >、>>命令管道符 |组合命令 &、&&、||转义字符 ^变量引导符 %界定符 "" 本章内容主要学习要点: >、>>重定向符| 命令…...
大模型WebUI:Gradio全解11——Chatbot:融合大模型的多模态聊天机器人(6)
大模型WebUI:Gradio全解11——Chatbot:融合大模型的多模态聊天机器人(6) 前言本篇摘要11. Chatbot:融合大模型的多模态聊天机器人11.6 为LLM Agent构建UI11.5.1 使用代理构建1. 使用transformers.agents的实际示例2. 使…...
【算法】前缀和
前缀和 1.【模板】前缀和2.最大子段和3.【模板】二维前缀和4.激光炸弹 前缀的核心思想是预处理,可以在暴力枚举的过程中,快速查询出某一段区间内的和,从而优化时间复杂度。是经典的用空间替换时间的做法。 1.【模板】前缀和 【模板】前缀和…...
android Recyclerview viewholder统一封装
Recyclerview holder 统一封装 ViewHolder类 import android.annotation.SuppressLint import android.content.Context import android.graphics.Color import android.graphics.drawable.GradientDrawable import android.os.Build import android.os.CountDownTimer import…...
【Linux系统】Ext系列磁盘文件系统二:引入文件系统(续篇)
inode 和 block 的映射 该博文中有详细解释:【Linux系统】inode 和 block 的映射原理 目录与文件名 这里有几个问题: 问题一: 我们访问文件,都是用的文件名,没用过 inode 号啊? 之前总是说可以通过一个…...
为AI聊天工具添加一个知识系统 之46 蒙板程序设计(第一版):Facet六边形【意识形态:操纵】
本文要点 要点 (原先标题冒号后只有 “Facet”后改为“Face六边形【意识形态】” ,是 事后想到的,本文并未明确提出。备忘在这里作为后续的“后期制作”的备忘) 前面讨论的(“之41 纯粹的思维”)中 说到,“意识”三…...
Vue.js组件开发-如何处理跨域请求
在Vue.js组件开发中,处理跨域请求(CORS,即跨来源资源共享)通常不是直接在Vue组件中解决的,而是需要后端服务器进行相应的配置,以允许来自不同源的请求。不过,前端开发者也需要了解一些基本的COR…...
unity学习19:unity里用C#脚本获取 gameobject 和 Componenet
目录 1 gameObject 和component 2 gameObject 与C#脚本 2.1 使用 this.gameObject 或gameObject(注意大小写) 2.2 获得其他信息 3 获取其他 GameObject的方法 3.1 获得自身挂载的GameObject 3.2 用find去查找其他的GameObject的名字或tag 3.3 …...
【React】插槽渲染机制
目录 通过 children 属性结合条件渲染通过 children 和 slot 属性实现具名插槽通过 props 实现具名插槽 在 React 中,并没有直接类似于 Vue 中的“插槽”机制(slot)。但是,React 可以通过 props和 children 来实现类似插槽的功能…...
【数据分享】1929-2024年全球站点的逐月平均气温数据(Shp\Excel\免费获取)
气象数据是在各项研究中都经常使用的数据,气象指标包括气温、风速、降水、湿度等指标,其中又以气温指标最为常用!说到气温数据,最详细的气温数据是具体到气象监测站点的气温数据!本次我们为大家带来的就是具体到气象监…...
立创开发板入门第六课 音频-扬声器和麦克风 I2S驱动
音频芯片介绍 开发板上带有一个麦克风,一个扬声器,音频编解码芯片使用ES8311。麦克风直接连接到了ES8311芯片上,ES8311和扬声器之间,还有一个音频驱动放大器。ES8311通过I2S接口与ESP32-C3连接。 ES8311这个芯片不仅使用I2S接口…...
使用 Java 实现基于 DFA 算法的敏感词检测
使用 Java 实现基于 DFA 算法的敏感词检测 1. 引言 敏感词检测在内容审核、信息过滤等领域有着广泛的应用。本文将介绍如何使用 DFA(Deterministic Finite Automaton,确定有限状态自动机) 算法,在 Java 中实现高效的敏感词检测。…...
springboot集成websocket实现实时大量数据,效率性能高
前言 小编我将用CSDN记录软件开发求学之路上亲身所得与所学的心得与知识,有兴趣的小伙伴可以关注一下! 也许一个人独行,可以走的很快,但是一群人结伴而行,才能走的更远!让我们在成长的道路上互相学习&…...
如何在 Google Cloud Shell 中使用 Visual Studio Code (VS Code)?
Google Cloud Shell 是一个基于浏览器的命令行界面,它提供了一个临时的虚拟机环境,允许开发者在没有本地环境配置的情况下使用 Google Cloud 的各种服务。它还提供了一个免费的 5GB 存储空间以及可以在其中执行所有 Google Cloud 操作的命令行界面。 Vis…...
ChatGPT大模型极简应用开发-目录
引言 要理解 ChatGPT,了解其背后的 Transformer 架构和 GPT 技术一路的演进则变得非常必要。 ChatGPT 背后的 LLM 技术使普通人能够通过自然语言完成过去只能由程序员通过编程语言实现的任务,这是一场巨大的变革。然而,人类通常容易高估技术…...
ZooKeeper 核心知识全解析:架构、角色、节点与应用
1.ZooKeeper 分布式锁怎么实现的 ZooKeeper 是一个高效的分布式协调服务,它提供了简单的原语集来构建更复杂的同步原语和协调数据结构。利用 ZooKeeper 实现分布式锁主要依赖于它的顺序节点(Sequential Node)特性以及临时节点(Ep…...
redis实现限流
令牌桶逻辑 计算逻辑: 代码: import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool;/*** ClassName RedisRateLimiterTokenBucket* Description TODO* Author zhang zhengdong* DATE 2025/1/17 20:22* Version 1.0*/ public class…...
云服务器扫描出漏洞怎么办?
随着云计算技术的发展,越来越多的企业和个人选择使用云服务器来托管其应用和服务。然而,安全问题也随之而来。当云服务器的安全扫描显示存在漏洞时,如何正确应对成为了关键。本文将详细介绍发现漏洞后的处理步骤,并提供一些实用的…...
【影刀RPA_启动任务api】
影刀RPA_启动任务api #启动任务api import requests import json from time import sleepyingdao_Info{"accessKeyId":"XXX","accessKeySecret":"XXX","scheduleUuid":XXX,"robotUuid1":"XXX","r…...
Swift语言的多线程编程
Swift语言的多线程编程 在现代软件开发中,多线程编程是提高应用性能和响应速度的重要手段。尤其是在 iOS 和 macOS 开发中,由于用户界面(UI)的交互性和复杂性,合理利用多线程可以极大地提升用户体验。本文将深入探讨 Swift 语言中的多线程编…...
js-判断一个object(对象)是否为空
1.Object.keys() 方法 const obj {};if (Object.keys(obj).length 0) {console.log(Object is empty); } else {console.log(Object is not empty); }Object.keys() 方法返回一个包含对象自身可枚举属性名称的数组。如果这个数组为空,那么对象就是空的。 2.JSO…...