C++之继承多态

C++之继承&多态

  • 继承
    • 继承之形
    • 继承的作用域
    • 继承的构造与析构
    • 多继承
      • 菱形继承
  • 多态
    • 多态之形
    • final和override(C++11)
    • 纯虚函数&抽象类
    • 多态的原理
    • 打印虚表(在vs2022中)
    • 多继承下的虚表
    • 菱形虚继承中埋的坑
    • 静态多态与动态多态
    • 我对虚函数和普通成员函数调用区别的理解
  • 问答题
  • 结语

C++作为oo语言,封装,继承,多态是它的三大特性。前面我们学了 封装,今天我们学后两个。

继承

继承之形

  1. 继承,就是类层次的复用。被继承的称为基类(base) / 父类,另一个称为派生类(derived) / 子类。
class A//父类
{
public:A(int a = 0):_a(a){cout << "A()" << endl;}void func(){}~A(){cout << "~A()" << endl;}
private:int _a;
};
class B :public A//子类
{
public://B(int b):A(1),_b(b)B(int b):_b(b)//走A的默认构造{cout << "B()" << endl;}B(const B& b) :A(b), _b(b._b)//这里引用接收b中A的部分{cout << "B(const B& b)" << endl;}void func(int){}//构成隐藏,会屏蔽对A中func的直接访问~B(){cout << "~B()" << endl;}
private:int _b;
};
int main()
{B b;//b.func();//编译报错//b.A::func();//指明作用域,正确
}
  1. 继承方式:接在B后面的public A表示继承方式。
  • public继承:父类中的public成员 => 子类中成为public成员,protected => protected
  • protected继承:public & protected => protected
  • private继承:public & protected => private

注意,无论什么继承方式,父类的private成员,子类是无法直接访问 的。
所以,如果想被子类访问,而不想被外人访问,就用protected限定成员。而private既防外人,又防儿子。但是,说实话,搞复杂了,现实中基本都用public继承。
3. class的继承默认private继承,struct的为public。(了解)

子类对象给父类对象拷贝或赋值时,不存在隐式类型转换。B b; A a=b;这里编译器会去找出子类中的父类成员,从而依次拷贝过去。而当父类对象用引用或者指针接收时,那么该对象就会指向子类中父类的那一部分,这叫赋值兼容,通俗的叫“切片”。

继承的作用域

父类和子类有各自独立的作用域。所以允许子类和父类中有有同名成员,且子类会屏蔽对同名的父类成员的直接访问,这叫隐藏,也叫重定义。且成员函数只要名相同,就构成隐藏。
比如上图中B的func和A的func构成隐藏,而非重载,且使用B去调用func不传参会编译报错。
要想调用父的,需要显式指明作用域。

继承的构造与析构

  1. 构造:如果不在子类的初始化列表中调用父类的构造函数,编译器就走A的默认构造;如果显式调用了,就走你写的。构造顺序是先父后子。
    拷贝构造类似,在参数列表调用拷贝构造函数即可,因为是引用,可以接收子类对象。
  2. 析构:首先,析构函数名会被处理成destructor(因为多态),所以子与父的析构构成隐藏,要想调用父的析构,需要指定类作用域。
    其次,编译器会自动在子类析构函数结束时调用父类的析构。这是因为,编译器要保证*析构顺序是先子后父,*类似栈上的变量后进先出。所以不要显式手动去调父类析构。

同样的,赋值重载也要显式调用父类的operator=()

问:如何实现一个不能被继承的类?
答:封死构造或析构,或者final修饰类。将它们设为private或者=delete。

多继承

允许一个类C可以继承多个类比如B和C时,称为多继承。这会带来二义性和数据冗余问题。比如B和C中都有name成员,那就歧义了。而name通常只需要一个,那就冗余了。虽然二义性可以通过指明作用域来解决,但数据冗余的空间浪费可就严重了。
解决方法:虚继承。在继承处加上关键字virtual。class B:virtual public A{};这样name就是同一个了。

当B和C都继承A,再来一个D继承B和C时,就发生菱形继承。这是个大坑。尽量避开

菱形继承

我们前面说了,菱形继承能避开就避开,但是面试可能涉及它的解决原理,也就是虚继承的底层。也就是虚继承是怎么解决二义性和数据冗余的问题。

class A
{int _a = 1;
};
class B :virtual public A
{int _b = 2;
};
class C :virtual public A
{int _c = 3;
};
class D :public B, public C
{int _d = 4;
};
int main()
{D d;
}

非虚继承时
上图中,首先看到,不使用虚继承时,_a是有两份的。
以下方便起见,我们在32位下做实验。
虚继承底层
可以看到,B和C在原本存_a的位置分别存了一个指针,指向的位置的第4~8字节(前四字节为多态使用)用来标识从B/C起始到_a的偏移量(单位为字节),比如C的起始地址+0c也就是+12字节,正好指向A的起始部分,也就找到了那个唯一的A部分。

问1:既然我们/编译器知道A部分在最下面,那还需要存偏移量吗?
答1:要。如果是“切片”,比如B*ptrb=&d;这个ptrb肯定是指向D中B部分起始位置的,但它不知道D中其他部分,那它解引用ptrb->a;怎么找_a呢?如果没有偏移量,它可不知道_a在哪里。还有当使用虚继承后,如果定义B b;B*ptrb2=&b;,可以看到汇编层面ptrb和ptrb2解引用找_a是一模一样的,这里的b也是在_a的位置存了指针指向偏移量,可见,此处设计是有深远考量的。

问2:二义性解决了,那数据冗余呢?
答2:虚继承后,此处多出的是8字节,32位下两个指针大小。指向空间不用考虑,因为定义很多d对象时,它们指向的是同一个偏移量。所以当A部分大于8字节时,在空间上,虚继承就赚了。

问3:这里找到了_a,如何找到A的其他成员?
答3:找到了A的起始位置,接下来编译器按结构体内存对齐计算就行。

问4:为什么不存绝对位置?
答4:可以是可以,但是尊重本贾尼的选择。大佬有大佬的考量。

注意,谁先被继承,谁先被声明。所以,初始化列表中,谁先被继承,先调谁的构造函数。比如,class D :public B, public C 这一行中,D先继承B,就先构造B。注意,A一定是最先初始化的,无论D中调默认构造还是有参构造,而B和C中对A的初始化无效。

组合和继承的区别:组合典型的是内部类,耦合度低。组合是has-a的关系,比如你有眼睛;继承耦合度高,它是is-a的关系,比如你是个人。

多态

多态之形

多态,即不同的对象做同样的行为却有不同的结果。比如有人吃榴莲觉得香,有人觉得臭。

class A
{
public:virtual void func(){cout << "class A->func" << endl;}
};
class B:public A
{
public:virtual void func(){cout << "class B->func" << endl;}
};
void func(A& x)
{x.func();
}
int main()
{A a;B b;func(a);func(b);
}

在这里插入图片描述
在子类和父类的同名、同参数列表(不看缺省值)和同返回值的函数前写virtual关键字(virtual修饰的成员函数称为虚函数),那么在父类指针或引用去调用该虚函数时,根据整体对象,调对应的函数。这里子类和父类中虚函数的关系,我们称为重写/覆盖。

虚函数的两个例外:1.子类中虚函数前可以不写virtual。一种解释是不加virtual子类也继承并重写这个函数的实现,符合接口继承的思想。2.返回值可以不同,但必须是父子关系的引用或指针。比如A中func返回一个A*,B中返回一个B*。返回其他的父子类或者都返回父类也可以。这叫协变。

上面我们提到了接口继承。接口可以理解为函数声明。那么接口继承就是把函数体上面那一行全继承下来,诸如返回值,函数名和参数列表。

总结多态条件:1.虚函数的重写。2.父类指针或引用调用该虚函数。
测试题:

class A 
{
public:virtual void func(int val = 1){cout << "A->" << val << endl;}virtual void test() { func(); }
};
class B:public A
{
public:virtual void func(int val = 0){cout << "B->" << val << endl;}
};
void func(A& x)
{x.func();
}
int main()
{B* p = new B;p->test();
}

选择:A.A->0 B.B->0 C.B->1 D.A->1 E.编译报错 F.以上全错


答案:C
解释:p调用继承下来的test中,依然是A* 类型的this指针去调用func,又虚函数重写,所以多态。多态时,虽然this是父类的,但整体对象是子类,所以走子类的func。又因为接口继承(可以理解为把父类中的func接口照搬下来),所以val是1。
如果用A*指针接收B呢?当然还是C。因为整体对象还是B。

变式:

class A 
{
public:virtual void func(int val = 1){cout << "A->" << val << endl;}
};
class B:public A
{
public:virtual void func(int val = 0){cout << "B->" << val << endl;}virtual void test() { func(); }
};
void func(A& x)
{x.func();
}
int main()
{B* p = new B;p->test();
}

选项一样。


答案:B。因为子类指针调用func,无多态。所以没有重写、接口继承的概念,就是个普通函数调用。

重载和重写和重定义的区别?
函数重载:1.两函数在在同一作用域。2.名同,参数不同。
重写(覆盖):1.分别在父类和子类的作用域。2.都是虚函数。3.三同
重定义(隐藏):1.分别在父类和子类的作用域。2.名同。3.不是重写,就是重定义。

final和override(C++11)

  1. final有两个用途,一个是用于类名后,该类不能被继承,称为最终类。另一个是在虚函数后面,表示不能被重写。
  2. override是在子类虚函数接口后面,用于检查虚函数是否重写,没有就报错。

纯虚函数&抽象类

  • 在虚函数接口后加上=0,就成了纯虚函数。
  • 包含纯虚函数的类叫抽象类。抽象类永远无法实例化出对象,而且如果被继承的类不重写纯虚函数,那它也无法实例化出对象来。继承抽象类的子类只有重写纯虚函数,才能实例化。
  • 可见,它可以强制重写。
    done

多态的原理

首先,虚函数的存在会让类的size多出4/8字节,一个指针,我们称为虚函数表指针(virtual function table pointer),简称虚表指针。它指向的是虚函数表,里面存的是虚函数指针数组。下面是测试代码:

class A
{
public:virtual void func(){cout << "A->func" << endl;}//virtual void func2(){}
};
class B:public A
{
public:virtual void func(){cout << "B->func" << endl;}
};
void func(A* x)
{x->func();
}
int main()
{A* a = new A;B* b = new B;func(a);func(b);
}

虚函数表
我们会发现,父类和子类对象中都会有一个虚表指针,指向各自实现的虚函数。所以分别调用各自的虚函数即可。那么多态时,即使是父类的指针/引用,那它指向的完整对象中的虚表中指向的虚函数还是子类的实现,如果指向的是父类对象,那调的就是父类中虚表指向的虚函数了,这不就多态了嘛。注意,虚表是属于类的,因为类定义好了,虚函数也就定了,虚表就出来了。所以,同一个类的对象共用一个虚表

  1. 所以为什么多态条件之一是父类引用/指针?
    因为指针/引用跟普通类型不同,既可以指向/接收父类(的虚表),又可以指向/接收子类(的虚表),而且(后面个人理解)便于操作去找虚表,引用底层也是指针。另外,如果是普通对象接收,它切片时会发生拷贝,那它敢拷贝子类的虚表吗?不敢,父类里的虚表是子类的虚表,不乱套了?所以切片时,只拷成员,不拷虚表,也就没法多态了。
  2. 为什么多态另一个条件重写,又叫覆盖?
    我们在上面代码中去掉注释,即在A中再写一个虚函数而B不重写,见下图,可以发现,B类的虚表中func的地址与a中不同,但func2的地址一样。说明子类的虚表是照搬(继承)父类的,我们不重写的话,就不变,重写就覆盖掉。

覆盖

打印虚表(在vs2022中)

前情提要:

  1. 虚表指针在类对象的起始位置存放,无论32位/64位。
    在这里插入图片描述
  2. 在我的vs2022编译器中,会在虚表中虚函数指针的末尾补一个nullptr。
    在这里插入图片描述
    下见代码:
class Base
{
public:virtual void func1(){cout << "Base::func1()" << endl;}virtual void func2(){cout << "Base::func2()" << endl;}
};
class Derive :public Base
{
public:virtual void func1(){cout << "Derive::func1()" << endl;}virtual void func2(){cout << "Derive::func2()" << endl;}
};
typedef void(*VF_PTR)();
void PrintVFTable(VF_PTR* table)
{for (int i = 0; table[i]!=nullptr; i++){printf("[%d]:%p->", i, table[i]);table[i]();//(*table[i])()也行}
}
int main()
{Base b;Derive d;//下面是先取出b的虚表指针VF_PTR,再强转成VF_PTR*//PrintVFTable((VF_PTR*)(*(int*)&b));//在64位下要换成long longPrintVFTable(*(VF_PTR**)(&b));PrintVFTable(*(VF_PTR**)(&d));return 0;
}

打印结果(32位下也类似,但是vs切换平台时,记得重新生成解决方案)
打印虚表

问1:虚表是在什么阶段生成的?
答1:编译阶段。编译阶段就有虚函数地址了。

问2:对象中虚表指针什么时候初始化?
答2:构造函数的初始化列表。

问3:虚表存在哪里?
答3:首先排除栈。堆是给程序员动态申请的,不合理。只剩数据段(静态区)和代码段(常量区)了。见下图,可见vs2022下,虚表存在常量区。虚表出来后就不会被修改,有道理。但静态区也可,看编译器。
在这里插入图片描述

多继承下的虚表

前情提要:多继承时子类对象结构be like:
在这里插入图片描述

首先,既然子类的虚表是照搬、覆盖父类的虚表,那么继承了几个父类,就有几张虚表。
那么如果一个子类中有一个没重写的虚函数,它在哪?
测试代码:

class Base1
{
public:virtual void func1(){cout << "Base1::func1()" << endl;}virtual void func2(){cout << "Base1::func2()" << endl;}
};
class Base2
{
public:virtual void func1(){cout << "Base2::func1()" << endl;}virtual void func2(){cout << "Base2::func2()" << endl;}
};
class Derive :public Base1,public Base2
{
public:virtual void func1(){cout << "Derive::func1()" << endl;}virtual void func3(){cout << "Derive::func3()" << endl;}
};
typedef void(*VF_PTR)();
void PrintVFTable(VF_PTR* table)
{for (int i = 0; table[i] != nullptr; i++){printf("[%d]:%p->", i, table[i]);table[i]();}
}
int main()
{Derive d;PrintVFTable(*(VF_PTR**)(&d));//PrintVFTable(*(VF_PTR**)(char*)&d + sizeof(Base1));//手动偏移Base2* b2 = &d;//切片偏移PrintVFTable(*(VF_PTR**)b2);/*下去看着汇编调试Base1* b1 = &d;Base2* b2 = &d;b1->func1();b2->func1();*/return 0;
}

结果是存在先声明(继承)的子类虚表中。
多继承虚表
但是,新问题来了,为什么上图中两张虚表中func1的地址不一样?不是说覆盖吗,都是Derive的func1实现,地址怎么会不一样?
解析:首先,调用的函数是一样的,因为我实现的PrintVFTable中决定了只有调用的都是func1才能打出"Derive::func1()"。那么为什么虚表中地址不一样呢?此处就直接揭晓了,因为如果是切片后指向d中b2部分的指针(也就是传过去的this指针)去调用func1,多态使它应该调用d中第二张虚表里的func1,但它的this指针指向是错的。this应该指向d的起始,而此处指向d中Base2部分的起始。所以在jump到func1地址前,要完成this指针的修正工作,即减一个Base1的size。才能正确调用d的成员函数func1。
下图是调用func1前this的修正,ecx在调用成员函数时存的是this指针,减的4是Base1的大小。
在这里插入图片描述

建议,下去调试跟着汇编看一眼,理解一下修正this指针,也可以改变一下Base1的大小之类的。

菱形虚继承中埋的坑

前面我提到虚基表中前四字节为多态所用。首先,菱形继承有三份虚表,B、C各一份用来存没重写的虚函数指针,A一份存D重写的虚函数指针(注意是BC都重写了,但只存D的,避免歧义)。可以发现,菱形继承的代码添加上虚函数,那四字节在32位就变成了-4,64位是-8。所以,虚基表前四字节盲猜是存储的虚表指针相对虚基表指针的偏移量。

静态多态与动态多态

  1. 静态多态,又称静态绑定/前期绑定/早绑定,典型的有函数重载。它是在编译时就确定好要call的函数地址,比如cout根据后面跟的数据类型,编译时就决定调哪个cout。
  2. 动态多态,又称动态绑定/后期绑定/晚绑定,也就是这里的虚函数重写的多态。它是在运行时才确定要call的函数地址,比如虚函数的多态是在运行时去虚表里找地址(但上面说过这个虚函数地址是在编译时就有了的)。

done

我对虚函数和普通成员函数调用区别的理解

普通成员函数直接在编译时就能拿到函数地址,直接调即可。
虚函数要先用(修正后的)传入的this指针找到虚表指针,再通过后者找到虚表里虚函数地址,再去调用虚函数。

问答题

  1. 什么是多态?分为静态多态和动态多态。前者有重载,在编译时拿到函数地址;后者有虚函数的多态,在运行时拿到函数地址。
  2. 什么是重载、重写(覆盖)、重定义(隐藏)?见多态之形结尾部分。
  3. 多态的实现原理?虚表中存各自的虚函数地址,调用时即可根据对象调用不同的函数。
  4. inline函数可以是虚函数吗?理论上不可以,但编译时可以通过。inline函数无地址不能放进虚表,但是如果写virtual编译器可能把它当作虚函数,而拒绝inline的请求。
  5. 静态成员可以是虚函数吗?不能,静态成员函数无this指针,如果用类名调用就访问不到虚表。
  6. 构造函数可以是虚函数吗?不行,虚表指针在初始化列表才初始化,这不就是蛋鸡相生的哲学问题吗?注意,virtual赋值重载函数可编过,但不应该,赋值重载和构造都要调用父类的对应函数,这不符合虚函数重写“非父即子”的理念。
  7. 析构函数可以是虚函数吗?可以且建议。
  8. 访问普通成员函数快还是虚函数快?普通对象调用是一样快的,因为不走多态;指针或引用对象调用,构成多态,去虚表找虚函数地址,虚函数就慢了。
  9. 虚函数表存在哪里,什么阶段生成?
  10. 菱形继承问题?怎么解决?

小细节1:virtual只在类内声明时加,类外定义时不加。
小细节2:下图中第二个指针调用func是普通函数调用吗?

class Base
{
public:virtual void func(){}
};
int main()
{Base b;b.func();Base* pb = &b;pb->func();return 0;
}

自己下去分析汇编,结果是多态调用。这里没有重写虚函数,也没人继承Base,为什么走多态调用呢?这是因为编译器在遇到对象指针/引用去调用虚函数时,无论是否重写,都走多态的话,成本最低,最稳妥(万一别的c文件里继承了Base重写了func呢)。

结语

OK,完工。整了4~5天吧,累了,麻了,没想到东西这么多。哎,继续前进吧,为C++献出心脏!后面还有大boss呢,红黑树。。。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习-36-对ML的思考之机器学习研究的初衷及科学研究的期望

文章目录 1 机器学习最初的样子1.1 知识工程诞生(专家系统)1.2 知识工程高潮期1.3 专家系统的瓶颈(知识获取)1.4 机器学习研究的初衷2 科学研究对机器学习的期望2.1 面向科学研究的机器学习轮廓2.2 机器学习及其应用研讨会2.3 智能信息处理系列研讨会2.4 机器学习对科学研究的重…

arm 汇编技巧

汇编标号&#xff1a;f表示forward&#xff0c; b表示backward&#xff1a; Here is an example: 1: branch 1f 2: branch 1b 1: branch 2f 2: branch 1b Which is the equivalent of: label_1: branch label_3 label_2: branch label_1 label_3: branch label_4 label_4: bra…

特色3D打印stm32迷你8轴双核心主板

我自己设计的3D打印机主板 1. 这是一块迷你的8轴主板, 主板尺寸为100mm*75mm, 使用一个8cm静音风扇散热足够了2. 这是一个带有保护的板子, 驱动上的gpio具有过压保护功能, 能够直接抗住24V的冲击, 意味着一个驱动炸了, 板子不烧, 并且其他的驱动也没事, 主板支持自动关机3. 8…

golang分布式缓存项目 Day2 单机并发缓存

注&#xff1a;该项目原作者&#xff1a;https://geektutu.com/post/geecache-day1.html。本文旨在记录本人做该项目时的一些疑惑解答以及部分的测试样例以便于本人复习。 支持并发读写 接下来我们使用 sync.Mutex 封装 LRU 的几个方法&#xff0c;使之支持并发的读写。在这之…

2024 年将 Swagger 导入 Postman 图文教程

2024 年将 Swagger 导入 Postman 图文教程

从入门到精通:hello-algo开源项目助你系统学习数据结构与算法

文章目录 前言1.关于hello-algo2.安装Docker和Docker compose3.本地部署hello-algo4. hello-algo本地访问5.cpolar内网穿透工具安装6.创建远程连接公网地址7.固定Uptime Kuma公网地址 前言 本文将探讨如何在本地环境中部署hello-algo这一算法学习必备项目&#xff0c;并利用cp…

SystemVerilog学习笔记(七):函数与任务

函数 函数的主要用途是编写一段可以随时调用n次的代码&#xff0c;只需调用函数名即可&#xff0c;不需要任何模拟时间来执行。函数是返回类型&#xff0c;仅返回函数声明中提到的单个值&#xff0c;如果未声明则返回一个位的值。 语法&#xff1a; initial begin functio…

地下水数值模拟、 地下水环评、Visual modflow Flex、Modflow

地下水数值模拟软件Visual modflow Flex实践技术应用 地下水数值模拟软件的应用&#xff0c;主要围绕的目前应用较为广泛的Visual Modflow Flex 6.1软件版本开展&#xff0c;结合具体应用场景&#xff0c;实例讲解软件的全流程应用过程&#xff0c;包括数据处理分析、数值模型…

丹摩征文活动|Llama3.1:从安装到熟练使用的全方位教程

0.前言 目前关于 Llama 3.1 的详细安装和使用指南在网络上较为分散&#xff0c;对于许多想要深入了解和应用该模型的人来说&#xff0c;缺乏一个系统、全面的指导资料。为了填补这一空白&#xff0c;本文应运而生。旨在为广大读者提供从 Llama 3.1 的安装到熟练使用的全方位指…

UI自动化测试|CSS元素定位实践

前言 自动化测试元素定位是指在自动化测试过程中&#xff0c;通过特定的方法或策略来准确识别和定位页面上的元素&#xff0c;以便对这些元素进行进一步的操作或断言。这些元素可以是文本框、按钮、链接、图片等HTML页面上的任何可见或不可见的组件。 在自动化测试中&#xf…

软件架构与模式分析

软件架构模式分析 软件架构模式和架构风格是两个相关但不同的概念。 软件架构模式&#xff08;Software Architecture Patterns&#xff09;是一种在软件工程领域广泛应用的规范化、可复用的架构设计方案。它是通过抽象和提炼出解决特定问题所需的结构、组件、关系和规则等&am…

npm完整发包流程(亲测可验证)

1. 准备工作 &#xff08;1&#xff09; 在npm官网上注册一个账号 &#xff08;2&#xff09; 注册成功之后&#xff0c;npm会发送一封邮件给你&#xff0c;点击邮件里面的链接&#xff0c;做确认关联操作&#xff08;必需&#xff09; 2. 创建自己的npm包 &#xff08;…

无插件直播流媒体音视频播放器EasyPlayer.js播放器多分屏超过6路不能播放如何解决

EasyPlayer.js H5播放器&#xff0c;是一款能够同时支持HTTP、HTTP-FLV、HLS&#xff08;m3u8&#xff09;、WS、WEBRTC、FMP4视频直播与视频点播等多种协议&#xff0c;支持H.264、H.265、AAC、G711A、Mp3等多种音视频编码格式&#xff0c;支持MSE、WASM、WebCodec等多种解码方…

从零开始使用YOLOv11——Yolo检测detect数据集自建格式转换为模型训练格式:20w+图片1w+类别代码测试成功

在之前的文章中记录了YOLO环境的配置安装和基本命令的一些使用&#xff0c;上一篇博文的地址快速链接&#xff1a;从零开始使用YOLOv8——环境配置与极简指令&#xff08;CLI&#xff09;操作&#xff1a;1篇文章解决—直接使用&#xff1a;模型部署 and 自建数据集&#xff1a…

【HAProxy06】企业级反向代理HAProxy调度算法之其他算法

HAProxy 调度算法 HAProxy通过固定参数 balance 指明对后端服务器的调度算法&#xff0c;该参数可以配置在listen或backend选项中。 HAProxy的调度算法分为静态和动态调度算法&#xff0c;但是有些算法可以根据不同的参数实现静态和动态算法 相互转换。 官方文档&#xff1…

Leetcode 检测相邻递增子数组

3349. 检测相邻递增子数组 I 给你一个由 n 个整数组成的数组 nums &#xff0c;请你找出 k 的 最大值&#xff0c;使得存在 两个 相邻 且长度为 k 的 严格递增 子数组 。具体来说&#xff0c;需要检查是否存在从下标 a 和 b (a < b) 开始的 两个 子数组&#xff0c;并满…

【STL栈和队列】:高效数据结构的应用秘籍

前言&#xff1a; C 标准模板库&#xff08;STL&#xff09;为我们提供了多种容器&#xff0c;其中 stack&#xff08;栈&#xff09;和 queue&#xff08;队列&#xff09;是非常常用的两种容器。 根据之前C语言实现的栈和队列&#xff0c;&#xff08;如有遗忘&#xff0c;…

香江电器从A股到港股7年漫长上市路,收入后退停滞不前

《港湾商业观察》施子夫 9月29日&#xff0c;湖北香江电器股份有限公司&#xff08;以下简称&#xff0c;香江电器&#xff09;递表港交所引起外界关注&#xff0c;公司的独家保荐机构为国金证券。 回顾香江电器的IPO之旅&#xff0c;可以说是颇为坎坷&#xff0c;多次尝试A股…

从python源码到可自动更新软件

相关阅读 标题链接如何打包python程序为exebczl【auto-py-to-exe 可视化打包python到exe】51CTO ZATA 1. python源码 打包时需要特别注意的源码编写规范 除了基本的 Python 编码规范之外,在准备程序进行打包时,还需要特别注意以下几点: 1.1 依赖管理 确保 requirements.t…

2024智能视觉与数据建模国际学术会议(ICIVD 2024)

重要信息 主会官网&#xff1a;www.iccaid.net 大会时间&#xff1a;2024年12月13-15日 大会地点&#xff1a;中国南昌 大会简介 2024智能视觉与数据建模国际学术会议&#xff08;ICIVD 2024&#xff09;作为第四届计算机图形学、人工智能与数据处理国际学术会议&#xff…