【C++私房菜】面向对象中的简单继承

文章目录

  • 一、 继承基本概念
  • 二、派生类对象及派生类向基类的类型转换
  • 三、继承中的公有、私有和受保护的访问控制规则
  • 四、派生类的作用域
  • 五、继承中的静态成员


一、 继承基本概念

通过继承(inheritance)联系在一起的类构成一种层次关系。通常在层次关系的根部都有一个基类(base class),其他类则直接或间接地从基类继承而来,这些继承得到的类称为派生类(derived class)。基类负责定义在层次关系中所有类所共同拥有的成员,而每个派生类定义自己特有的成员。

这个层次结构是如何体现的呢?继承作为面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,也就是派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。上一篇文章中【 C++私房菜】模板的入门与进阶-CSDN博客的都是函数复用,继承是类设计层次的复用。

继承的定义格式如下:

 class 派生类名 : 继承方式 基类1,继承方式 基类2{
     //...
 };

从上述格式可以看出C++是支持多继承的。派生类必须通过使用**类派生列表(class derivation list)**明确指出它是从哪个(哪些)基类继承而来的。类派生列表的形式是:首先是一个冒号,后面紧跟以逗号分隔的基类列表,其中每个基类前面可以有以下三种访问说明符中的一个:public、protected或者private。

此处我们定义一个基类和派生类来做说明,我们可以看到Person是基类。Student是派生类:

 class Person{
     //...  
 };
 class Student:public Person{
     //...
 };

派生类必须将其继承而来的成员函数中需要覆盖的那些重新声明。我们观察下文代码:

 class Quote {
 public:
     Quote() = default;
     Quote(const string& book, double sales_price)
         :bookNo(book), price(sales_price)   {}
     string isBn()const { return bookNo; }
     virtual double net_price(size_t n)const { return n * price; }
     virtual ~Quote() = default;
 protected:
     double price = 0;
 private:
     string bookNo;
 };
 class Bulk_quote :public Quote {
 public:
     Bulk_quote() = default;
     Bulk_quote(const string&, double, size_t, double);
     double net_price(size_t) const override;
 private:
     size_t min_qty = 0;
     double discount = 0;
 };

上述代码完成了哪些工作呢?Bulk_quote对象具有以下特征:

派生类对象存储了积累的数据成员(派生类继承了基类的实现)。

派生类对象可以使用基类的方法(派生类继承了基类的接口)。

因此Bulk_quote 类中必须包含一个 net_price 成员。Bulk_quote 类从它的基类继承了 isBn 函数和 bookNo、 price 等数据成员,还定义了新的版本,同时用于两个新增加的数据成员 min_qty 和 discount。这两个成员分别用于说明享受折扣所需购买的最低数量以及一旦该数量达到后具体的折扣信息。

需要在继承特性中添加什么呢?

派生类需要自己的构造函数。

派生类可以根据需要添加额外的数据成员和成员函数。

上文中只继承自一个类的这种继承被称为“单继承“。

在这里插入图片描述

现在需要记住的是作为继承关系中的根节点的类通常都会定义一个虚析构函数,即使该函数不执行任何实际操作。

本文我们暂时忽略 virtual 关键字,我将在后续的文章中对此进行叙述。
当然我们也可以防止继承的发生,有时我们可能会定义一些类且不希望其他类继承它,或者不想考虑它是否适合作为一个基类。

为了这一目的,C++11提供了一种防止继承发生的方式,即在类名后跟一个关键字 final。 如class NoDerived final{ //... };。或者我们也可以将父类构造函数私有化,派生类实例化不出对象,也就不能被继承。被final修饰的类我们通常称为最终类。

但是如果我们在派生类定义了一个函数与基类虚函数名字相同但形参列表不同的函数,这仍是合法的行为。编译器将认为这个新定义的函数与基类中的是相互独立的。这时派生类的函数并没有覆盖掉基类中的版本。就实际的编程习惯而言,这种声明往往意味着发生了错误,因为我们可能原本希望派生类能覆盖掉基类中的虚函数,但是一不小心把形参列表弄错了。 要想调试并发现这样的错误显然非常困难。在C++11新标准中我们可以使用 override 关键字来说明派生类中的虚函数。这么做的好处是在使得程序员的意图更加清晰的同时让编译器可以为我们发现一些错误,后者在编程实践中显得更加重要。如果我们使用override标记了某个函数,但该函数并没有覆盖已存在的虚函数,此时编译器将报错,下面我们举几个例子:

 class Base {
 public:
     virtual void f1(int)const;
     virtual void f2();
     void f3();
 };
 class Derived1 :public Base{
     void f1(int)const override; //正确: f1与基类中的 f1 匹配
     void f2(int) override;      //错误: Base没有形如f2(int)的函数
     void f3()override;          //错误: f3不是虚函数
     void f4()override;          //错误: Base没有名为f4的函数
 };

因为只有虚函数才会被覆盖,所以编译器会认为 Derived 中的 f3是错误的。相同的 f4 的声明也是错误的,Base中没有为 f4的虚函数。我们在此处将 override与final 一起讨论:

 class Derived2 :public Derived1 {
     void f1(int)const final;  //不允许后续的其他函数覆盖 f1(int)
 };
 class Derived3 :public Derived2 {
     void f2();                  //正确: 覆盖从间接基类Base继承而来的 f
     void f1(int)const;          //错误: Derived2已经将f2声明为final
 };

⚠️成员变量所有的都会被继承,无论公有私有。


二、派生类对象及派生类向基类的类型转换

理解基类和派生类之间的类型转换是理解C++语言面向对象编程的关键所在。

一个派生类对象包含多个组成部分:一个含有派生类自己定义的(非静态)成员的子对象,以及一个与该派生类继承的基类对应的子对象,如果有多个基类,那么这样的子对象也有多个。因此,一个 Bulk_quote对象将包含四个数据元素:它从基类 Quote 继承而来的 bookNo 和 price 数据成员,以及 Bulk_quote 自己定义的 min_qty 和 discount 成员。

因为在派生类中含有与基类相对于的组成部分,所以我们可以把派生类的对象当成基类对象来使用,而且我们也能将基类的指针或引用绑定到派生类对象的基类部分上。

通常情况下,如果我们想把引用或指针绑定到一个对象上,则引用或指针的类型应与对象的类型一致,或者对象的类型含有一个可接受的 const 类型转换规则。存在继承关系的类是一个重要的例外:我们可以将基类的指针或引用绑定到派生类对象上。例如,我们可以用 Quote& 指向一个 Bulk_quote对象,也可以把一个 Bulk_quote 对象的地址赋给一个Quote*。

 Quote item;
 Bulk_quote bulk;
 // 子类对象可以赋值给父类对象/指针/引用
 Quote *p = &item;
 p = &bulk;
 Quote &r = bulk;
 Quote obj=bulk;

上述代码均是合法的,我们通常把这种转换称为派生类到基类的类型转换。和其他类型转换一样,编译器会隐式地执行此种转换。

这种隐式特性意味着我们可以把派生类对象或者派生类对象的引用用在需要基类引用的地方;同样的,我们也可以把派生类对象的指针用在需要基类指针的地方。

⚠️注意:

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片 或者切割。寓意把派生类中父类那部分切来赋值过去。

基类对象不能赋值给派生类对象。

在派生类对象中含有与其基类对应的组成部分,这一事实是继承的关键所在。

但是在对象之间并不存在类型转换,基类向派生类的隐式类型转换也不存在。为什么呢?

派生类向基类的自动类型转换只对指针或引用类型有效。在派生类类型和基类类型之间不存在这样的转换。很多时候,我们确实希望派生类对象转换成它的基类类型,但是这种转换的实际发生过程往往与我们期望的有所差别。

请注意,当我们初始化或赋值一个类类型的对象时,实际上是在调用某个函数。当执行初始化时,我们调用构造函数;而当执行赋值操作时,我们调用赋值运算符。这些成员通常都包含一个参数,该参数的类型是类类型的 const 版本的引用。 因为这些成员接受引用作为参数,所以派生类向基类的转换允许我们给基类的传递一个派生类的对象。这些操作不是虚函数。当我们给基类的构造函数传递一个派生类对象时,实际运行的构造函数是基类中定义的那个,显然该构造函数只能处理基类自己的成员。类似的,如果我们将一个派生类对象赋值给一个基类对象,则实际运行的赋值运算符也是基类中定义的那个,该运算符同样只能处理基类自己的成员。

 Bulk_quote bulk;            //派生类对象
 Quote item(bulk);           //使用Quote::Quote(const Quote&)构造函数
 item =bulk;                 //调用Quote::operator=(const Quote&)

当构造 item 时,运行 Quote 的拷贝构造函数。该函数只能处理 bookNo 和 price 两个成员,它负责拷贝 bulk 中Quote部分的成员,同时忽略掉 bulk 中 Bulk_quote 部分的成员。类似的,对于将bulk赋值给item的操作来说,只有bulk中Quote部分的成员被赋值给 item。因为在上述过程中会忽略 Bulk_quote 部分,所以我们可以说 bulk 的 Bulk_quote 部分被切割掉了,这就是派生类向基类赋值的过程。

之所以存在派生类向基类的类型转换是因为每个派生类对象都包含一个基类部分,而基类的引用或指针可以绑定到该基类部分上。一个基类的对象既可以以独立的形式存在,也可以作为派生类对象的一部分存在。如果基类对象不是派生类对象的一部分,则它只含有基类定义的成员,而不含有派生类定义的成员。因为一个基类的对象可能是派生类对象的一部分,也可能不是,所以不存在从基类向派生类的自动类型转换。

 Quote base;
 Bulk_quote* bulkP=&base;        //错误,不能将基类转换成派生类
 Bulk_quote& bulkRef= base;      //错误,不能将基类转换成派生类
 
 Bulk_quote bulk;
 Quote *itemP =&bulk;            //正确,动态类型是 Bulk_quote
 Bulk_quote *bulkP=itemP;        //错误,不能将基类转换成派生类

如若此方式合法,则我们可能会使用到 bulkP 或 bulkRef 访问 base 中不存在的成员。

当我们使用存在继承关系的类型时,必须将一个变量或其他表达式的**静态类型(static type)与该表达式表示对象的动态类型(dynamic type)**区分开来。表达式的静态类型是编译时总是已知的,它是变量声明时的类型或表达式生成的类型:动态类型则是变量或表达式表示的内存中的对象的类型。动态类型直到运行时才可知。此部分我们将在后文多态中详细介绍。


三、继承中的公有、私有和受保护的访问控制规则

每个类分别控制自己的成员初始化过程,与之类似,每个类还分别控制着其成员对于派生类来说是否是可访问的。继承方式有三种分别为 public继承、protected继承和private继承。访问限定符同样也是三种: public访问、protected访问和private访问。

protected成员:如前所述,一个类使用protected关键字来声明那些它希望与派生类分享但是不想被其他公共访问使用的成员。protected说明符可以看做是public和private中和后的产物。

和私有成员类似,受保护的成员对于类的用户来说是不可访问的。

和公有成员类似,受保护的成员对于派生类的成员和友元来说是可访问的。

派生类的成员或友元只能通过派生类对象来访问基类的受保护成员。派生类对于一个基类对象中的受保护成员没有任何访问特权。

我们举个例子来说明:

 class Base {
 protected:
     int prot_mem;
 };
 class Derived :public Base {
     friend void clobber(Derived&);
     friend void clobber(Base&);
 private:
     int j;
 };
 //错误: clobber不能访问Base的对象的private和protected成员
 void clobber(Base& b) { b.prot_mem = 0; }       
 //正确: clobber可以访问Derived的对象的private和protected成员
 void clobber(Derived& s) { s.j = s.prot_mem = 0; }

某个类对其继承而来的成员的访问权限受到两个因素影响:一是在基类中该成员的访问说明符,二是在派生类的派生列表中的访问说明符。举个例子,考虑如下的继承关系:

 class Base {
 public:
     void pub_mem();     //public成员
 protected:
     int prot_mem;       //protected成员
 private:
     char priv_mem;      //private成员
 };
 struct Pub_Derv :public Base {
     int f() { return prot_mem; }    //正确:派生类能访问protected成员
     char g() { return priv_mem; }   //错误:private成员对于派生类来说是不可访问的
 };
 struct Priv_Derv :private Base {
     int fl()const { return prot_mem; }      //private 不影响派生类的访问权限
 };

派生访问说明符对于派生类的成员(及友元)能否访问其直接基类的成员没什么影响。对基类成员的访问权限只与基类中的访问说明符有关。PubDerv和PrivDerv都能访问受保护的成员protmem,同时它们都不能访问私有成员privmem。 派生访问说明符的目的是控制派生类用户(包括派生类的派生类在内)对于基类成员的访问权限:

 Pub_Derv dl;            //继承自Base的成员是public的
 Priv_Derv d2;           //继承自Base的成员是private的
 d1.pub_mem();           //正确:pub mem在派生类中是public的
 d2.pub_mem();           //错误:pub mem在派生类中是private的  

Pub_Derv和Priv_Derv都继承了pub_mem函数。如果继承是公有的,则成员将遵循其原有的访问说明符,此时d1可以调用pub_mem。在Priv_Derv中,Base 的成员是私有的,因此类的用户不能调用pub_mem。 上述内容总结成如下内容:

特征类成员/继承方式public继承protected继承private继承
public成员变量派生类的public成员派生类的protected成员派生类的private成员
protected成员变量派生类的protected成员派生类的protected成员派生类的private成员
private成员变量在派生类中不可见,只能通过基类接口访问在派生类中不可见,只能通过基类接口访问在派生类中不可见,只能通过基类接口访问
能否隐式向上转换是(但只能在派生类中)

从上述表格我们可以观察到:

基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私 有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面 都不能去访问它。

实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 = Min(成员在基类的访问限定符,继承方式),public > protected > private。

使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public。

派生类向基类的转换是否可访问由使用该转换的代码决定,同时派生类的访问说明符也会由影响。假定 D 继承自 B:

只有当 D 公有地继承 B 时,用户代码才能使用派生类向基类的转换;如果 D 继承 B 的方式是受保护的或者私有的,则用户代码不能使用该转换。

 class A {
 public:
     virtual void print() { cout << "我是A" << endl; }
 };
 class B :public A{
 public:
     void print() { cout << "我是B 继承A" << endl; }
 };
 class C : private A{    //此处换为protected同理
 public: 
     void print() { cout << "我是C 继承A" << endl; }
 };
 int main(){
     A* p;   B b;    C c;
     p = &b;
     //p = &c;   // 错误:无法将'C'转换为其私有基类'A'。
     p->print();
 }

不论 D 以什么方式继承 B ,D 的成员函数和友元都能使用派生类向基类的转换;派生类向其直接基类的类型转换对于派生类的成员和友元来说永远是可访问的。

 class B {};
 class D :public B {
     void function(D& d) { B b = d; }
     friend void friendFunction(D& d) { B b = d; }
 };
 class E :protected B {
     void function(E& e) { B b = e; }
     friend void friendFunction(E& e) { B b = e; }
 };
 class F :private B {
     void function(F& f) { B b = f; }
     friend void friendFunction(F& f) { B b = f; }
 };

如果 D 继承 B 的方式是公有的或者受保护的,则 D 的派生类的成员和友元可以使用 D 向 B 的类型转换;反之,如果 D 继承 B 的方式是私有的,则不能使用。

 class B {};
 class D :public B {
     void function(D& d) { B b = d; }
     friend void friendFunction(D& d) { B b = d; }
 };
 class E :protected B {
     void function(E& e) { B b = e; }
     friend void friendFunction(E& e) { B b = e; }
 };
 class F :private B {
     void function(F& f) { B b = f; }
     friend void friendFunction(F& f) { B b = f; }
 };
 class G :private D {
     void function(D& d) { B b = d; }
 };
 class H :private E {
     void function(E& e) { B b = e; }
 };
 class I :private F {
     void function(F& d) {
         B b = f; //错误: B 是 B 的私有成员
     }
     friend void friendFunction2(F& f)
     {
         B b = f; //错误: B 是 B 的私有成员
     }
 };

对于代码中的某个给定节点来说,如果基类的公有成员是可访问的,则派生类向基类的类型转换也是可访问的;反之则不行。

⚠️友元关系不能继承,基类的友元在访问派生类成员时不具有特殊性,类似的,派生类的友元也不能随意访问基类的成员。即友元关系只对作出声明的类有效,每个类负责控制各自成员的访问权限。基类友元不能访问子类私有和保护成员。

当然有时候我们可以改变派生类继承的某个名字的访问级别,通过使用using 声明可以达到此目的。

 class Base{
 public:
     size_t size()const { return n; }
 protected:
     size_t n = 3;
 private:
     int s = 2;
 };
 class Derived :private Base {
 public:
     using Base::size;
 protected:
     using Base::n;
 private:
     using Base::s;//错误: 派生类只能为那些它可以访问的名字提供 using声明。
 };

因为 Derived 使用了私有继承,所以继承而来的成员 size 和 n (在默认情况下)是Derived 的私有成员。然而,我们使用 using 声明语句改变了这些成员的可访问性。改变之后,Derived的用户将可以使用 size 成员,而 Derived 的派生类将能使用 n。 通过在类的内部使用 using 声明语句,我们可以将该类的直接或间接基类中的任何可访问成员(例如,非私有成员)标记出来。using 声明语句中名字的访问权限由该using声明语句之前的访问说明符来决定。也就是说,如果一条 using 声明语句出现在类的private部分,则该名字只能被类的成员和友元访问;如果 using 声明语句位于 public部分,则类的所有用户都能访问它;如果using声明语句位于protected部分,则该名字对于成员、友元和派生类是可访问的。


四、派生类的作用域

每个类定义自己的作用域,在这个作用域内我们定义类的成员。当存在继承关系时,派生类的作用域嵌套在其基类的作用域之内。如果一个名字在派生类的作用域内无法正确解析,则编译器将继续在外层的基类作用域中寻找该名字的定义。 派生类的作用域位于基类作用域之内这一事实可能有点儿出人意料,毕竟在我们的程序文本中派生类和基类的定义是相互分离开来的。不过也恰恰因为类作用域有这种继承嵌套的关系,所以派生类才能像使用自己的成员一样使用基类的成员。

 Bulk_quote bulk;
 cout<<bulk.isBn();

下面我们来叙述 isBn() 的解析过程:

因为我们是通过 Bulk_quote的对象调用isbn的,所以首先在 Bulk_quote中查找,这一步没有找到名字isbn。 因为Disc_quote是Quote的派生类,所以接着查找Quote。此时找到了名字isBn,所以我们使用的isBn 最终被解析为 Quote中的 isBn。

通常在编译时进行名字查找,一个对象、引用或只在的静态类型决定了该对象的哪些成员是可见的。即使动态类型与静态类型不匹配。但是我们仍能使用哪些成员仍然是静态类型决定的。

当名字冲突时,和其他作用域相同,派生类也能重新定义在其之间基类或间接基类的成员变量和成员函数,此时定义在内存作用域的名字将**隐藏(也称为重定义)**定义在外层作用域的名字。

🔲派生类的成员将隐藏同名的基类成员。当然我们仍然可以通过域运算符来使用一个被隐藏的基类成员。(使用 基类::基类成员 显示访问)

 class Base{
 public:
     int func();
 };
 class Derived :public Base {
 public:
     int func(int);      //隐藏基类的 int func();
 };
 int main() {
     Derived d;
     Base b;
     b.func();           //正确: 调用Base::func()
     d.func(1);          //正确: 调用Derived::func(int)
     d.func();           //错误: 参数列表为空的func被隐藏了
     d.Base::func();     //正确: 调用Base::func()
 }

Derived 中的 func 声明隐藏了 Base 中的 func 声明。在上面的代码中前两条调用语句容易理解,第一个通过 Base对象 b 进行的调用执行基类的版本;类似的,第二个通过 d 进行的调用执行 Derived的版本;第三条调用语句有点特殊,d.func()是非法的。 为了解析这条调用语句,编译器首先在 Derived 中查找名字func 。因为 Derived确实定义了一个名为func 的成员,所以查找过程终止。一旦名字找到,编译器就不再继续查找了。Derived 中的func 版本需要一个int实参,而当前的调用语句无法提供任何实参,所以该调用语句是错误的。


五、继承中的静态成员

如果基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义。不管从基类派生出来多少个派生类,对于每个静态成员来说都只存在唯一的实例。

 class Base{
 public:
     static void statmen();
 };
 class Derived:public Base{
     void f(const Derived&);  
 };

静态成员遵循通用的访问控制规则,如果基类的成员是 private 的,则派生类无权访问它。假设某静态成员是可访问的,则我们既能通过基类使用它也能通过派生类使用它:

 void Derived::f(const Derived& dd){
     Base::statmen();        //正确,Base定义了statmen
     Derived::statmen();     //正确,Derived继承了statmen
     dd.statmen();           //正确,通过Derived对象访问
     statmen();              //正确,通过this对象访问
     //派生类的对象可以访问基类的静态成员。
 }

静态成员属于整个类,不属于任何对象,所以在整体体系中只有一份。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MaxScale实现mysql8读写分离

MaxScale 实验环境 中间件192.168.150.24MaxScale 22.08.4主服务器192.168.150.21mysql 8.0.30从服务器192.168.150.22mysql 8.0.30从服务器192.168.150.23mysql 8.0.30 读写分离基于主从同步 1.先实现数据库主从同步 基于gtid的主从同步配置 主库配置 # tail -3 /etc/my.…

杰发科技AC7801——SRAM 错误检测纠正

0.概述 7801暂时无错误注入&#xff0c;无法直接进中断看错误情况&#xff0c;具体效果后续看7840的带错误注入的测试情况。 1.简介 2.特性 3.功能 4.调试 可以看到在库文件里面有ecc_sram的库。 在官方GPIO代码里面写了点测试代码 成功打开2bit中断 因为没有错误注入&#x…

九州金榜|家庭教育小技巧,孩子好习惯养成记

家庭教育对于孩子的发展至关重要&#xff0c;家长一定要重视孩子在家里的举动&#xff0c;要及时纠正孩子的不足&#xff0c;发展孩子的优良品德和教孩子养成勤俭朴素的的好习惯。九州金榜家庭教育将从以下方面说一下家庭教育中的方法技巧。 一、家长以身作则 家长教育孩子&a…

Stable Diffusion 模型分享:AstrAnime(Astr动画)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五 下载地址 模型介绍 AstrAnime 是一个动漫模型&#xff0c;画风色彩鲜明&#xff0c;擅长绘制漂亮的小姐姐。 条目内容类型大模型…

XG5032HAN (SAW)振荡器)(piezoman压电侠)

XG5032HAN晶体振荡器通过其卓越的低抖动特性&#xff0c;为需要高频率精度和稳定性的电子设备提供了理想的解决方案。无论是在高性能的数据通信、精密测量XG5032HAN都能提供高质量、可靠的性能。同时&#xff0c;宽广的频率范围其25 MHz到250 MHz&#xff0c;小巧的封装5.0 3.…

IntelliJ IDEA 创建Spring Boot 项目整合jdbc详细步骤

IntelliJ IDEA 创建Spring Boot 项目&整合jdbc详细步骤 1、打开 IntelliJ IDEA 软件2、使用 "Spring Initializr" 作为项目类型&#xff0c;新建项目工程3、选择对应的SpringBoot版本和依赖4、Spring Boot 项目的结构5、创建一个TestController&#xff0c;并运行…

C++力扣题目 392--判断子序列 115--不同的子序列 583--两个字符串的删除操作 72--编辑操作

392.判断子序列 力扣题目链接(opens new window) 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;&quo…

开发技术-Java 获取集合中元素下标并移动至指定位置

1. 说明 某些业务需要特定的元素在列表的最后或者指定位置展示。 2. 代码 import lombok.AllArgsConstructor; import lombok.Data;import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream;Data AllArgsConstructor class Student {St…

智能图书馆开源项目

结尾有项目链接 技术栈介绍 ☃️前端主要技术栈 技术作用版本Vue提供前端交互2.6.14Vue-Router路由式编程导航3.5.1Element-UI模块组件库&#xff0c;绘制界面2.4.5Axios发送ajax请求给后端请求数据1.2.1core-js兼容性更强&#xff0c;浏览器适配3.8.3swiper轮播图插件&…

【漏洞复现】大华智能物联ICC综合管理平台文件读取漏洞

Nx01 产品简介 大华智能物联ICC综合管理平台是一个集成了多种智能物联应用服务能力的平台。该平台提供了一系列的基础能力&#xff0c;如中台基础能力、各智能物联应用服务能力以及周边生态支持。 Nx02 漏洞描述 大华智能物联ICC综合管理平台存在文件读取漏洞&#xff0c;攻击…

六、回归与聚类算法 - 欠拟合和过拟合

目录 1、定义 2、原因及解决方法 2.1 正则化 线性回归欠拟合与过拟合线性回归的改进 - 岭回归分类算法&#xff1a;逻辑回归模型保存与加载无监督学习&#xff1a;K-means算法 1、定义 2、原因及解决方法 2.1 正则化

【Unity3D】ASE制作天空盒

找到官方shader并分析 下载对应资源包找到\DefaultResourcesExtra\Skybox-Cubed.shader找到\CGIncludes\UnityCG.cginc观察变量, 观察tag, 观察代码 需要注意的内容 ASE要处理的内容 核心修改 添加一个Custom Expression节点 code内容为: return DecodeHDR(In0, In1);outp…

Flashbit空投

空投要点 明牌空投交互简单&#xff0c;仅需3步&#xff0c;零gas费要求加密钱包在eth链有过交易需要有x和discord账号 空投简介 是一个社区驱动的项目&#xff0c;专门针对Blast生态&#xff0c;项目方提出了空投计划&#xff0c;参与过该生态其他项目空投的都清楚&#xf…

探索什么是模糊测试 Fuzzing Test

什么是 "模糊测试"&#xff1f; Fuzzing 是一种发现软件缺陷的方法&#xff0c;它通过向程序提供随机输入来寻找导致程序崩溃的测试场景&#xff08;原理有点类似Monkey Test&#xff09;。可以帮助你快速了解程序整体的健壮性&#xff0c;并帮助你发现和修复关键的缺…

【Python常用包】pathlib

目录 简介Pathlib 库实现Path 创建路径对象检查路径类型创建和删除路径&#xff08;目录与文件&#xff09;读写文件路径匹配路径拼接和解析路径属性路径迭代和列出目录内容 小结 简介 Pathlib 是一个用于处理文件路径的 Python 库&#xff0c;提供了许多实用的函数和方法来处…

windows安装编译的python包

有时windows无法直接通过网络安装python包&#xff0c;需要从一个地方先下载好&#xff0c;再去安装&#xff0c;下载的一些编译好的python包&#xff0c;安装时发现提示“is not a supported wheel on this platform”&#xff0c;那可能就是下载编译好的版本不对。 可以通过…

代码随想录第二十四天 39.组合总和 40.组合总和II 131.分割回文串

LeetCode 39 组合总和 题目描述 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个…

电表(3)EC600N 4G模块通过mqtt向服务器发送数据

工具 1、ec600 2、stm32f030c8 3、keil5 4、腾讯云服务器&#xff08;ubutu20.04&#xff09; mqtt服务器 sudo apt install mosquitto mosquitto-clients sudo systemctl start mosquitto sudo vim /etc/mosquitto/mosquitto.conf sudo systemctl status mosquittolistene…

Aspose.Words For JAVA 动态制作多维度表格(涵2024最新无水印包)

全网最全Aspose.Words For JAVA 高级使用教程: CSDNhttps://blog.csdn.net/LiHaoHang6/article/details/133989664?spm1001.2014.3001.5501 运行截图&#xff1a; 所谓多维度表格通常包含多个维度, 每个维度都代表一种数据属性,多维度表格可以用于数据分析&#xff0c;通过不…

ArcgisForJS如何使用ArcGIS Server发布的切片地图服务?

文章目录 0.引言1.准备海量地理数据2.ArcGIS Server发布切片地图服务3.ArcgisForJS使用ArcGIS Server发布的切片地图服务 0.引言 ArcGIS Server是一个由Esri开发的地理信息系统&#xff08;GIS&#xff09;服务器软件&#xff0c;它提供了许多功能&#xff0c;包括发布切片地图…