【C++】继承- 赋值兼容转换、虚基表

news/2024/4/28 1:15:31/文章来源:https://blog.csdn.net/weixin_61508423/article/details/127479246

前言

        Hi~大家好呀!欢迎来到我的C++系列学习笔记!

        我上一篇的C++笔记链接在这里哦~:【C++】模板的非类型参数、特化、分离编译_柒海啦的博客-CSDN博客

        C++类与对象博客在这里哦~:【C++】类和对象_柒海啦的博客-CSDN博客_c++类和对象

        本篇,开始介绍C++中类和对象中重要的继承思想,理解面向对象的本质,以及如何回避C++在继承这一方面的一些坑。

        废话不多说,我们直接开始吧~

目录

一、继承方式

1.面向对象三大特性

2.继承格式

继承格式定义:

二、继承中的作用域

1.父类域与子类域

重定义(隐藏)

2.赋值兼容转换(切片)

3.派生类的默认成员函数

构造函数

拷贝构造函数

赋值重载函数

析构函数

总结

4.继承与静态成员

三、菱形继承和菱形虚拟继承

1.多继承

2.菱形继承的特点

3.菱形虚拟继承

四、继承的理解与反思

1.继承和组合

2.不被继承的类


一、继承方式

1.面向对象三大特性

        我们知道,面向对象有着三大特性,分别是:封装、继承、多态 。

        封装在之前的类与对象中我们可以深刻的体会到,如何对外只开放我们想允许的接口,不暴露一些私有接口,以免让别人乱修改导致此结构造成破坏。比如在类中的private成员只能在本类访问,外面就没有办法访问(除了友元)。

        那么,现在我们就要开始了解面向对象的第二个特性:继承。

        继承,顾名思义,就是后辈继承前辈的一些特点保留下来,方便后辈使用。

        此机制同样被面向对象程序设计让代码可以复用的最重要手段。它能够允许程序员在原来程序设计的基础上进行扩展,增加功能。

        此时被继承的类称作基类(父类),继承的那个类称作派生类(子类)。

        继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。(以前设计的复用是函数复用,继承属类设计层次的复用)

2.继承格式

        让我们由一个简单的代码入手看看继承的格式吧~

class Person
{
public:void Init(string name, int height, string birthday)  // 基类初始化函数{_name = name;_height = height;_birthday = birthday;}void Print(){cout << "------基本信息------\n";cout << "姓名:" << _name << endl;cout << "身高:" << _height << "cm" << endl;cout << "生日:" << _birthday << endl;cout << endl;}
protected:string _name;  // 姓名int _height;  // 升高 cmstring _birthday;  // 生日 x月x日
};class PrettyDerby : public Person  // 公有继承 基类Person,派生类PrettyDerby
{
public:void IsAttribute(int speed, int endurance, int power, int willpower, int intelligence)  // 初始化属性{_speed = speed;_endurance = endurance;_power = power;_willpower = willpower;_intelligence = intelligence;}void Print()  // 这里会发生重定义(隐藏){cout << "------赛马娘------\n";Person::Print();  // 调用继承到派生域的基域被隐藏的函数 访问限定符cout << "------属性------" << endl;cout << "速度:" << _speed << endl;cout << "持久力:" << _endurance << endl;cout << "力量:" << _power << endl;cout << "意志力:" << _willpower << endl;cout << "智力:" << _intelligence << endl;}
protected:int _speed;  // 速度int _endurance;  // 持久力int _power;  // 力量int _willpower;  // 意志力int _intelligence;  // 智力// ......
};int main()
{Person p;p.Init("张三", 160, "10月24日");p.Print();PrettyDerby d1;d1.Init("星云天空", 155, "4月26日");d1.IsAttribute(109, 109, 98, 92, 92);d1.Print();return 0;
}

(诶嘿~调用麻烦了些,之后会对一些部分进行改进,现在主要是看进程相关的东西哦~)

结果:

 

 (放张图休息休息~)

         通过上面的程序,我们就可以发现,PrettyDerby类公有继承了Person类,然后PrettyDerby类就可以使用基类的成员变量和成员方法。那么关于这些是否存在限制呢?还是说只有这一种继承方式?和public、protected、private属性有没有关呢?

        

         答案是自然有关的,还是大大的有关,我们先来着手于public : Person和public、protected、private这些属性的关系:

继承格式定义:

class 派生类 : 继承方式 基类

继承格式:

访问限定\继承方式public继承protected继承private继承
publicpublicprotectedprivate
protectedprotectedprotectedprivate
private不可见不可见不可见
 记忆方法:                                                                                                                                        两者选取最小的访问权限。基类若为private在派生类内均为不可见。                               不可见:不可见也就是在派生类里,也不能访问基类的private的成员                                     protected、private 类里可以访问,类外不能访问 不可见类似于隐身,类里类外均不可访问。

         根据上述C++的定义,我们可以得到如下结论:

    注意:
        1权限两个合并为权限小的那一个
        2私有成员的意义:不想被子类继承的成员,可以设计为私有
        3实际开发中,一般使用public继承,很少出现其他继承。同时private基类也很少不想继承给派生类 所以一般使用的是public继承的public    protected成员
        4继承方式不显示写的话,class默认是私有继承,struct默认是公有继承

        了解了继承的格式之后,我们来具体看看重定义(隐藏)是有着什么样的含义呢?按照我们以前的理解,继承子类继承下来的话,父类和子类相同名字,参数不同的话不是构成重载吗,为什么叫重定义(隐藏)呢?这就要和接下来的作用域扯上关系了,我们慢慢道来~

二、继承中的作用域

1.父类域与子类域

        之前,我们见过许多作用域:命名空间(namespace)类域等等。同样的,在继承体系中为了更好的区分不同变量之间的关系,也就出现了基类域(父类域)、派生域(子类域)。

基类域:存放继承下来的成员(基类)

子类域:存放派生类自己的成员(派生类)

        在不同的作用域取相同的名字的成员是被允许的,同时也是优先局部,其次全局。(如果子类不存在和父类同名的,可以直接进行访问。建议不进行重名)(想要访问父类域的前面必须加上访问限定符 基类::)

        所以,上面我们举的例子程序中,派生类PrettyDerby继承了基类Person,所以在派生类的基类域存放基类的成员与变量(_name...... Init() Print()),派生类域存放着自己的(_speed...... IsAttribute() Print())。

重定义(隐藏)

        但是此时出现了同名的函数Print。因为就近原则,所以外界访问子类对象的Print函数的时候会优先访问子类的Print,自然的就将父类忽视掉了,故叫做重定义,或者隐藏。而不是重载哦~

        重载要求在同一作用域哦~ 当然,在上面程序中,我们同时也调用了父类域的Print函数,这样子类也能输出和父类同样的功能。 

2.赋值兼容转换(切片)

        所谓赋值兼容,即两者继承之后,两个类型的对象可不可以相互赋值呢?

    1.子类对象可以赋值给父类对象/指针/引用 -> 大变小 -> 不是同类型,不是隐式类型转换(可以测试 基类& = 派生类对象)
        但是注意,必须是公有继承,其余继承就不行了。
        指针的类型看到的就是子类当中属于父类的那一部分。引用同理
    2.反过来,父类赋值给子类对象是不支持的,强制类型转化也就不行。(指针、引用可以,以后讲)                                

               

 3.注意:基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来进行识别后进行安全转换。

        即:只能大的给小的,小的给大的话给不全哦。大的自然是指的是继承的派生类,小的就是基类了。

        我们可以通过一下程序调用了解一下:

void test2()
{Person p;PrettyDerby d1;d1.Init("星云天空", 155, "4月26日");d1.IsAttribute(109, 109, 98, 92, 92);// 1 子类对象给父类对象p = d1;p.Print();// 2 子类对象给父类引用Person& p2 = d1;p2.Print();// 3 子类对象给父类指针Person* p3 = &d1;p3->Print();// 4 父类强转给子类指针PrettyDerby* pp = (PrettyDerby*)&p;pp->Print();
}

         那么在继承之中的默认生成的函数是如何继承实现的呢?针对于作用域是不是分别进行初始化呢?

3.派生类的默认成员函数

        我们首先可以来复习一下基础的类和对象,几大默认成员函数:

构造函数

构造函数        类名(参数)

        编译器默认生成:1.派生类域(本域)内置类型不处理,自定义类型调用其默认构造函数2.基类域调用基类的默认构造函数进行初始化

        显示生成;1.不允许直接初始化基类成员,基类和派生类必须分开处理。基类域调用基类的处理,派生类自己处理。2.类似于匿名对象一样的(初始化:基类(参数))3.如果没有显示调用会调用基类的默认构造函数

        总结:如果不初始化基类属性可不调用基类构造,需要初始化基类域属性就需要显示调用,派生类属性对应初始化即可

        针对上面的描述,我们就可以将我们上面的程序改造一下,使其通过构造函数进行初始化成员。

class Person
{
public:// 默认构造函数哦~Person(string name, int height, string birthday)//Person(string name = "星云天空", int height = 155, string birthday = "4月26日"):_name(name),_height(height),_birthday(birthday){}// ......
}class PrettyDerby : public Person  // 公有继承 基类Person,派生类PrettyDerby
{
public:PrettyDerby(string name, int height, string birthday, int speed, int endurance, int power, int willpower, int intelligence):Person(name, height, birthday)  // 此时不显示调用,_speed(speed),_endurance(endurance),_power(power),_willpower(willpower),_intelligence(intelligence){}// ......
}

         注意,因为基类构造函数不存在默认构造函数,(可以重载一下)所以派生类必须要显示调用(编译器调用默认的构造函数)。如果将基类修改成了默认构造函数就可以派生类不用显示调用了:

         此时测试,发现我们想要在构造时修改基类成员不可以,因为没有提供给基类域成员初始化的参数:

void test4()
{Person p;p.Print();PrettyDerby pp("张三", 175, "10月24日", 109, 109, 98, 92, 92);  // 此时基类调用的是默认构造函数,此时想给基类域成员传参时不行的哦~pp.Print();
}

 

拷贝构造函数

拷贝构造函数        类名(const 类名&)

        编译器默认生成:1.对于派生类域成员:内置类型,值拷贝;自定义类型调用它的拷贝构造)2.继承的父类成员,必须调用父类的拷贝构造函数初始化

        显示生成:1.调用基类的拷贝对象 :基类(子类传入的对象)  -> 这里就是利用的切片原理(系统支持)(也可以利用指针强转,可是没必要))2.写具体的深浅拷贝

        总结:当派生类对象属性需要深拷贝时,在初始化列表调用基类的拷贝构造(传入的就是派生类对象即可)完成对基类属性的拷贝,其次在对派生类属性进行深拷贝即可

        在当前没有写拷贝构造函数的情况下:

void test5()
{PrettyDerby pp("星云天空", 155, "4月26日", 109, 109, 98, 92, 92);//PrettyDerby p1 = pp;PrettyDerby p2(pp);  // 使用系统默认的拷贝构造p2.Print();
}

         可以看到完全没有问题,因为类对象的属性均为内置类型,所以值拷贝即可,我们可以调试一下看编译器是不是默认调用了基类的拷贝构造函数(可以先写一下,不写的话也是系统默认生成的,当然在非初始化队列也可以写出输出语句,这样不调试也可以查到我们想要查到的信息)

         首先设置断点,调试到拷贝这一步:

         然后F11进入派生类的拷贝构造:

         可以发现率先就是默认调用的就是基类的拷贝构造(此时派生类的拷贝构造并没有显示写出来,表明默认拷贝构造同样调用基类的拷贝构造对基类成员进行拷贝)

        上述实践操作验证成功,现在我们就显示写一下派生类的拷贝构造:

	PrettyDerby(const PrettyDerby& p):Person(p)  // 切片操作,_speed(p._speed),_endurance(p._endurance),_power(p._power),_willpower(p._willpower),_intelligence(p._intelligence){}

赋值重载函数

赋值重载函数        类名& operator=(const 类名&)

        编译器默认生成:1.对于派生类域属性:内置类型,值拷贝;自定义类型调用它的赋值重载。2.对于基类域属性,调用基类的赋值重载函数

        显示生成:1.派生类域属性自己深浅拷贝,2.需要调用派生类的赋值,赋值调用可以显示调用父类的(注意访问限定,默认局部 -- 隐藏关系(子域和父域出现同名函数,指定调用父类的)))

        总结:派生类涉及到深拷贝需要显示写,对于基类属性显示调用其赋值,派生类属性进行深拷贝即可

        和上面默认拷贝构造同理,这里代码不再做演示,这里完善一下显示写赋值重载函数:

	PrettyDerby& operator=(const PrettyDerby& p){// 注意这里需要区分基类域和派生类域// operator=(p);  不行,就近原则,会自己调自己,形成无限递归哦Person::operator=(p);  // 这里使用的是基类赋值重载编译器默认生成  基类没有需要深拷贝的内置类型_speed = p._speed;_endurance = p._endurance;_power = p._power;_willpower = p._willpower;_intelligence = p._intelligence;return *this;}

 

析构函数

析构函数

        编译器默认生成:1.首先调用基类自己的析构函数2.对于派生类域属性,自定义类型调用其析构,内置类型不做处理

        显示生成:1.特殊:派生类的析构函数和基类的析构函数构成隐藏,所以如果要显示写,需要加上作用域(基类::)2.派生类域成员属性内置类型需要析构就析构,自定义类型自动调用其析构函数。3.派生类不需要调用基类的析构函数,即使显示写编译器还会调用一次基类的析构函数。

        注意:1.构成重定义(隐藏)的原因:(隐藏的原因:1.不同作用域 2.同名)因为为了析构函数后面多态的需要,所以编译器会统一的将整个家族的析构函数处理为destructtor()函数,所以构成了隐藏。2.编译器自动调用基类析构函数的原因:保证基类域先创建,后析构,派生类域成员后创建,先析构的原则(先进后出,后进先出),所以编译器会在派生类析构函数结束时调用基类析构函数达到完成该实例化对象的清理。

        总结:只需要在析构函数里写对派生类资源的清理即可

        结论如上,如下我们可以利用上面的程序演示下派生类析构函数会自动调用基类析构函数的特点,首先我们可以将基类和派生类析构的输出语句写出:

// ......~Person()  // 基类析构函数{// 本身不存在需要手动清理的资源cout << "我是人" << endl;}
// .......~PrettyDerby()  // 派生类析构函数{Person::~Person();  // 显示调用基类析构函数需要指定作用域cout << "我是你的赛马娘呀~" << endl;}
// .......

        1.编译器自动调用基类析构函数:

void test7()
{PrettyDerby pp("星云天空", 155, "4月26日", 109, 109, 98, 92, 92);
}

        直接实例化对象,查看调用析构时候的输出语句:

        可以看到,“我是人”输出了两次,证明了编译器无论你是否显示调用基类析构函数都会在派生类的析构函数末尾调用基类的析构函数:

         所以,我们不能在派生类自己调用基类析构函数去清理资源,编译器会自动调用。

总结

构造函数:存在传入参数,显示写构造函数,如果基类是默认构造函数可不用显示调用,析构函数一定不能自己去调用,其余均需要自己去调用。

4.继承与静态成员

        如果在基类定义了一个静态成员,那么在此继承体系会共享此成员

        比如,我们在人基类定义一个id,让子类继承,编写测试程序观察是否共享:

void test8()
{Person p("张三", 160, "10月24日");PrettyDerby pp("星云天空", 155, "4月26日", 109, 109, 98, 92, 92);cout << pp.id << endl;  // 0p.id = 1;cout << pp.id << endl;  // 1
}

        上面都只是单方面继承的事,那么C++里面可以多继承吗?

三、菱形继承和菱形虚拟继承

1.多继承

        谈到菱形继承,首先就不得先谈谈多继承了:

    单继承:一个子类只有一个直接父类时的继承关系为单继承
    多继承:一个子类有两个或以上的直接父类时,这个继承关系为多继承

多继承继承格式:派生类 : 继承权限 基类1, 继承权限 基类2, ......

        但是谈到多继承,那么脑阔疼(bushi)的菱形继承就来了,什么是菱形继承?别急,下面用一张图搞定你:

2.菱形继承的特点

         可以发现,在上图中,首先中间的两个赛马娘继承了第一行的赛马娘,随后第三行的赛马娘右同时继承第二行的两个赛马娘。此时第一行的因子在第三行是否就存在两份了呢?

        答案是正确的。下面我们通过一个简单的程序进一步了解一下:

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

        比如上述就是一个简单的菱形继承,如下是调试查看变量窗口以及内存窗口:

         首先第一个窗口,黄色和绿色分别代表B基类域和C基类域,外面的蓝色窗口就代表着是D派生类域,就可以发现里面均有相同的A基类域属性,并且同名,这样就会造成二义性问题。

        其次查看内存窗口,也可以发现B和C继承的基类域成员分别在这两个基类域中保存了一份。内置类型还好,如果是很大的一个类型的实例化对象呢?就会造成数据冗余的问题。

        综上,我们就可以发现多继承中的菱形继承所带来的危害:二义性和数据冗余问题。

        当然,我们也可以拿出代码实例来说明这两个问题:

         二义性问题只能靠指定访问域来进行避开:

int main()
{D d;//d._a = 0;  二义性d.B::_a = 0;d.C::_a = 0;return 0;
}

         但是这个时候可以看到数据冗余的问题没有得到解决。

        有没有什么办法能在菱形继承这里帮我们解决这些问题呢?

3.菱形虚拟继承

        在java中解决这种方法采用了直接暴力的方法:去掉多继承。C++中则采用了关键字virtual的虚拟继承。

虚拟继承:

格式:        派生类 : virtual 访问限定符 基类 .....

作用:此时继承的基类不再是直接将数据写入派生类的基类域,而是会形成一个虚基表。(对应位置保存虚基表的指针)

        先不谈虚基表是什么,我们先来验证一下:

        在B继承A类和C继承A类加上virtual后,实现如下程序验证_a是否解决二义性和数据冗余问题:

int main()
{D d;d.B::_a = 0;d.C::_a = 1;cout << d._a << endl;return 0;
}

 

         可以看到,这三个_a果然就是同一个_a,这样就解决了二义性和数据冗余问题了。这个时候我们再来看看这里究竟是怎么回事,查看内存和变量窗口:

         可以看到,变量属性窗口和菱形继承类似,因为vs编译器进行了处理,那么我们这里不妨直接看内存窗口,可以和菱形继承的内存窗口相比,会发现在原本的B类域和C类域继承的A类属性_a的值换成了一个地址,而保留_a的在整个内存的最下面。

        我们可以对B类域和C类域保存的指针进行查看;(注意我的机子是小端存储,低位在低地址,高位在高地址)

         按理来说,这两个指针应该指向下面_a的地址(0x00CDFCC8)可是转入指针查看发现结果并不是我们想的那样,而是第一行是一个0,往下走4个字节会存在一个偏移值

        对于B类域的虚基表中偏移量14,B类域虚基表指针此时所在的地址为0xcdfcb4加上14时就可以发现刚好等于0xcdfcc8不就是_a在内存窗口的位置吗。同理,对于C类域中虚基表的偏移量加上对应的地址也可以找到。

        此时我们对虚拟继承就可以通过下面一张图得以窥视:

 

四、继承的理解与反思

1.继承和组合

        继承是一种复用方式,我们之前学的组合也是一种,那么在具体的环境中这两种有如何区别呢?

(public继承)
        继承是一种is a的关系 -- 每一个子类都是一个父类。 人 - 学生 植物-玫瑰花
        组合是has -a的关系 -- 即在一个类里调用另一个类的一个实例化对象。间接的去用。  车 - 轮胎 脑袋 - 眼睛
    适合哪个就用哪个。当有些类型都可以(is a has a)优先使用组合。比如:vector/list/deque
                                          stack
    低耦合,高内聚 :(软件工程)继承是白箱复用(实现的角度)组合是黑箱复用(功能的角度)
                       耦合度高,依赖关系强                 耦合度低,依赖关系低
                    (容易互相影响)                (不容易互相影响)
                基类的任何改变都会影响派生类        
    但是组合并不能替代继承。--多态,is a的关系。
    一般虚继承放在共用的基类的直接继承类。

2.不被继承的类

        如果想要让类不被继承的话,除了让构造函数在私有域(这样类的实例化对象无法创造了)还可以加上关键字final,这样就表示这个类为最终类,那么就无法被继承。

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

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

相关文章

【异步系列五】关于async、await、promise、微任务、宏任务的执行顺序解析【最终篇】

前段时间总结了几篇关于异步原理、Promise原理、Promise面试题、async/await 原理的文章&#xff0c;链接如下&#xff0c;感兴趣的可以去看下&#xff0c;相信会有所收获。 一篇文章理清JavaScript中的异步操作原理 Promise原理及执行顺序详解 10道 Promise 面试题彻底理解…

输入输出、文件读写、数据类型

package chapter01 /* object:关键字&#xff0c;声明一个单例对象&#xff08;伴生对象&#xff09;*/ object HelloWorld {/*main方法&#xff1a;从外部可以直接调用执行的方法def 方法名称&#xff08;参数名称&#xff1a;参数数据类型&#xff09;:方法返回值类型 { 方法…

2.8 标准输入与格式化输出

文章目录1. Input 标准输入1.1 标准输入1.2 阻塞状态1.3 输入提示1.4 获取输入字符串1.5 输入版本差异1. Python3 输入数据类型2. Python2 输入数据类型2. Print 格式化输出2.1 输入2.2 sep 参数2.3 end 参数2.4 快捷写法2.5 格式化输出1. 语法格式2. 字典形式传值3. 元组形式传…

什么是GPT

什么是GPT 参考资料&#xff1a; https://zhuanlan.zhihu.com/p/350017443 https://zhuanlan.zhihu.com/p/106462515 https://www.cnblogs.com/yifanrensheng/p/13167796.html https://blog.csdn.net/weixin_45577864/article/details/119651372 Generative Pre-trained T…

这可能是你需要的vue考点梳理

对 React 和 Vue 的理解&#xff0c;它们的异同 相似之处&#xff1a; 都将注意力集中保持在核心库&#xff0c;而将其他功能如路由和全局状态管理交给相关的库&#xff1b;都有自己的构建工具&#xff0c;能让你得到一个根据最佳实践设置的项目模板&#xff1b;都使用了Virt…

Golang学习之路3-基础认识(下)

文章目录前言一、数组1.定长数组2.不定长数组二、map1.使用关键字 map 来声明2.使用 make 来声明3.添加元素4.检索key的value是否存在5.删除元素6.遍历map7.map的注意点在这里插入图片描述三、指针1.使用指针& 及 *2.空指针四、循环与条件判断1.循环2.条件判断前言 学习一…

Go语言函数

什么是函数 func main() {fmt.Println("hello,world")//调用函数fmt.Println(add(1, 2)) }// func 函数名&#xff08;参数&#xff0c;参数。。。&#xff09;&#xff0c;函数调用返回值类型&#xff08;&#xff09; func add(a, b int) int {c : a breturn c }函…

Ray tracing 光线追踪 之 embree ,从入门到精通 02 从源码编译与安装

1. 下载预编译的ispc&#xff0c;安装 网址&#xff1a; https://ispc.github.io resources -> github page 进入ispc 的github的release页&#xff1a;Releases ispc/ispc GitHub 找到一个预编译好了的ispc&#xff0c;其中在windows平台上是&#xff1a;https://github…

Redis缓存穿透、击穿、雪崩介绍

面试高频&#xff0c;工作常用 缓存穿透&#xff08;查不到&#xff09; 概念 用户想要查询一个数据&#xff0c;发现redis内存数据库没有&#xff0c;也就是缓存没有命中&#xff0c;于是向持久层数据库查询。发现也没有&#xff0c;于是本次查询失败&#xff0c;当用户很多的…

GO实现跳跃表

GO实现跳跃表 文章目录GO实现跳跃表跳跃表介绍跳跃表的实现跳跃表的结构创建跳跃表跳跃表的插入和删除跳跃表的排名操作跳跃表的区间操作完整实现跳跃表介绍 跳跃表&#xff08;skiplist&#xff09;是一种有序的数据结构&#xff0c;它通过建立多层"索引"&#xff…

世界城市日|数字城市里看不见的“保安”,真面目竟是…

2022年10月31日&#xff0c;是第8个世界城市日。在数字化浪潮席卷全球的当下&#xff0c;城市发展亦进入新的阶段。建造数字城市&#xff0c;全面推进城市数字化转型成为当前城市建设的热议话题。数字城市、万物互联&#xff0c;与网络空间的融合必不可少。然而系统的复杂度越高…

简单使用gige千兆网口工业相机,国产崛起(二,c#)

发现海康的sdk不错&#xff0c;可以用海康&#xff0c;basler&#xff0c;大华工业相机&#xff0c;估计其他的也可以&#xff0c;有机会试一试&#xff01;国产厉害&#xff0c;崛起了&#xff01;赞一个&#xff0c;热情爆棚&#xff01;且随窃喜&#xff01; 首先下载海康工…

网站SEO标题撰写技巧,做到这些可以提高点击率

搜索引擎认为&#xff0c;一个网站的点击率越高&#xff0c;那么这个网站就越受欢迎&#xff0c;因此就会提高网站的关键词排名。网站的点击率越高&#xff0c;就会获得更多流量。网站标题和点击率息息相关&#xff0c;一个好的网站标题&#xff0c;能够轻松获得流量。那么&…

[carla入门教程]-2 pythonAPI的使用

本专栏教程将记录我从安装carla到调用carla的pythonAPI进行车辆操控的全流程,带领大家从安装carla开始,到最终能够熟练使用carla仿真环境进行传感器数据采集和车辆控制. 第二节 pythonAPI的使用 本小节主要学习使用 pythonAPI来与carla服务器进行交互.包括获取信息,发送信息.…

IDEA热部署插件JRebel使用

JRebel安装与激活 JRebel 使用 此时已经安装好并已激活&#xff0c;我们使用 JRebel debug的时候&#xff0c;修改代码&#xff0c;不能实现热部署&#xff0c;因此还需要设置其他地方 1.项目自动编译 设置 compiler.automake.allow.when.app.running ctrlshiftA 或者 help->…

vue相关原理

vue 原理 面试为什么要考察原理 知其然知其所以然&#xff0c;各行各业通用的道理了解原理才能用的很好&#xff0c;专业性考察&#xff0c;技术的追求竞争激烈&#xff0c;则优录取大厂造轮子&#xff08;业务定制&#xff1a;有些框架不能满足需求&#xff09; 面试中如何…

【Spark NLP】第 19 章:生产化 NLP 应用程序

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

docker下快速部署openldap与PHPLdapAdmin

在一个组织中&#xff0c;为了简化各种内部系统的账号和密码的管理&#xff0c;往往就需要ldap来进行管理了。 对于ldap的实现方式也非常多&#xff0c;但在免费的开源系统中&#xff0c;openldap是ldap的首选系统。 同时&#xff0c;在这一切讲究快速的时代&#xff0c;采用d…

大数据ClickHouse进阶(二十二):ClickHouse优化

文章目录 ClickHouse优化 一、表优化 1、日期字段避免使用String存储 2、Nullable值处理 <

计算机毕业设计(附源码)python音蕾心动

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…