C++之继承
本节我们将要学习C++作为面向对象语言的三大特性之一的继承。
前言
一、继承的概念
二、继承的定义
2.1 定义格式
2.2 继承基类成员访问方式的变化
2.3 继承类模板
三、基类和派生类之间的转换
四、继承中的作用域
五、派生类的默认成员函数
六、实现一个不能被继承的类
七、继承与友元
八、继承与静态成员变量
九、多继承及其菱形继承问题
9.1 继承模型
9.2 虚继承
十、继承和组合
继承(Inheritance)
组合(Composition)
总结
前言
我们在之前的学习中,已经学习过了C++中面向对象中三大特性之一的封装,这一特性通过将对象的各种属性和方法通过一个类包装在一起,这样就能够提供清晰接口,隐藏内部实现,提升代码质量和维护性。本节我们就来看看C++中的另一个重要特性——继承。
一、继承的概念
什么是继承呢?我们先从字面上猜一下,继承难道就是从一个东西上来获取一些东西来给自己来使用嘛。在C++中这样说就有点太抽象了,我们目前在C++中使用最多的就是类,那么继承应该也是围绕着类来做手脚的吧,其实事实确实如此。继承机制是面向对象程序设计使代码可以复用的一个重要手段,它允许我们在原来的类上进行扩展,增加新的属性(成员变量)和方法(成员方法),从而生成一个新的类,称之为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的是函数层次的复用,而继承就是类设计层次的复用。
二、继承的定义
2.1 定义格式
我们可以看到下面的图片中:Student是派生类,它是继承自基类Person,在两个类中间还有一个继承方式。(由于翻译的不同,基类也叫做父类,派生类也叫做子类)
如下代码是一个简单的继承,我们将那些派生类所共有的属性/方法都放到了基类中去了,然后我们将派生类从基类中进行继承,然后再在派生类中增加一些属性/方法。从监视窗口,我们可以看出来派生类实例化出来的对象是具有基类的所有属性的。
class animal
{
public:void eat(){}void sleep(){}protected:int age=3;string name;string color="黄色";
};class Cat :public animal
{
public:void Grapmouse(){}protected:string address="安徽";string name="mimi";};class Dog :public animal
{
public:void bark(){}protected:string food="骨头";string name="wangwang";};int main()
{Cat mimi;Dog wangwang;return 0;
}
2.2 继承基类成员访问方式的变化
上面两个图是我们的继承方式和访问限定符的汇总,我们可以清楚地看出,两个的内容是一样的,都是public,protected,private。我们在之前的学习中已经基本了解了public,private这两个的用法了,一个是公有的,无论是类外还是类内都是可以进行访问的,另一个是私有的,只能够在类内进行访问在类外面是绝对不可以进行访问的。今天我们又要学习一个新的访问限定符——protected.这个可以使派生类可以访问到基类的成员变量/成员方法。
说了这么多,为什么要用这三个相同的关键字来表示两个方式呢?因为我们后面在继承中,不能简单地通过某一种方式来确定其访问方式了,我们需要通过上面两种方式综合来确定其访问方式。
现在我就根据上面表格简单说一说,访问方式的规则:
1.对于基类的继承方式是private的话,那么不论派生类是什么(public/protected/private)都是无妨进行访问基类中的成员的;
2.对于基类的继承方式是public或protected的话,我们可以这样来进行判断访问权限:派生类的访问权限=Min(基类的继承方式,派生类的访问方式);
3.我们一般都是使用public来作为基类的继承方式的,因为这样才能很好的实现继承这一特性,如果我们使用protected/private来作为基类的继承方式,就会很容易造成派生类不可见的情况,实际扩展性不强,那么继承又有什么意义呢?
4.我们使用不同的关键字(class/struct)来定义类时,如果我们不显示写继承方式的话,其默认的继承方式和之前的是一样的:class——private,struct——public。我们建议自己显示写继承方式,那样就会减少很多不必要的麻烦。
//实例展示不同限定访问符与继承方式
class animal
{
public:void eat(){}
protected:int age = 3;
private:int id = 110;
};class Cat :private animal
//class Cat :protected animal
//class Cat :public animal
{
public:void mouse(){}
protected:string name="mimi";
private:string food = "fish";
};int main()
{Cat a;a.mouse();return 0;
}
我们使用private作为基类的继承方式,我们从监视窗口可以看出来,我们虽然不能够访问基类的成员,但它们并非是不存在的,我们只是不可见而已,这点我们需要注意一下。
2.3 继承类模板
这个我们使用之前模拟实现stack作为例子,我们之前模拟实现stack是通过类模板和容器适配器(复用容器)的。现在我们学习了继承,我们是不是可以通过继承来获取基类的一些成员,从而模拟实现出来呢?
namespace hjc
{//template<class T>//class vector//{};// stack和vector的关系,既符合is-a,也符合has-a template<class T>class stack : public vector<T>{public:void push(const T& x){// 基类是类模板时,需要指定⼀下类域, // 否则编译报错:error C3861: “push_back”: 找不到标识符 // 因为stack<int>实例化时,也实例化vector<int>了 // 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到 vector<T>::push_back(x);//push_back(x);}void pop(){vector<T>::pop_back();}const T& top(){return vector<T>::back();}bool empty(){return vector<T>::empty();}};
}
int main()
{hjc::stack<int> st;st.push(1);st.push(2);st.push(3);while (!st.empty()){cout << st.top() << " ";st.pop();}return 0;
}
我们从上面的模拟实现代码,我们可以看出来我们是将stack作为vector的派生类来生成的,我们在模拟实现stack的成员方法时,是直接通过复用基类的成员方法来进行实现的。但是我们每次复用基类的成员方法时,我们都需要使用作用域运算符来指定类域,虽然按道理来说,我们实例化出来派生类了,基类不应该也实例化出来了嘛,事实确实如此,但是基类并不是一把实例化出来的,他的有些成员还没实例化出来,这时候我们就需要自己手动指示类域进行实例化了(如果我们不指明的话,编译器就会报未声明的报错),这就是我们之前所说的按需实例化。
三、基类和派生类之间的转换
我们在之前的学习中,对于一些类型之间的转换,有时候在转换中出现一些临时变量,这些变量具有常性,因此就无法赋值给普通类型的变量,因为这样就会造成权限的放大(这样是违法的),因此我们一般都是需要加上const,才能避免报错。
但是到了我们继承这,有一种特殊的操作——public继承的派生类可以直接赋值给基类的指针/基类的引用了。它们之间不会产生临时变量,因此也不具有常性。这是未为什么呢?这是因为派生类是通过继承基类产生的,派生类具有基类中的成员,因此我们可以直接将派生类中基类的那一部分直接给基类。有个形象的说法是切片/切割。寓意是把派生类中基类的那一部分切割下来,然后基类的指针和引用指向派生类被切割出来的这一部分。
基类是不可以直接赋值给派生类指针或引用的,因为基类有的,派生类也有,而派生类有的,基类可能没有。
基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针 是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来进行识别后进行安全转换。
class Person
{
protected :string _name; // 姓名 string _sex; // 性别 int _age; // 年龄
};
class Student : public Person
{
public :int _No ; // 学号
};
int main()
{Student sobj ;// 1.派⽣类对象可以赋值给基类的指针/引⽤ Person* pp = &sobj;Person& rp = sobj;// ⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的 Person pobj = sobj;//2.基类对象不能赋值给派⽣类对象,这⾥会编译报错 sobj = pobj;return 0;
}
四、继承中的作用域
在C++中基类与派生类是两个不同的作用域,我们不能认为派生类是由基类继承下来的,那么它们两个就是共有一个作用域,这是不对的。
隐藏规则
1.派生类中如果有与基类中同名的成员变量,那么派生类就会屏蔽基类中的同名成员变量,这叫做隐藏(如果我们想要使用基类中的那个同名成员变量,我们就显示调用 基类::同名成员变量);
2.对于成员函数,只要两者的函数名相同,那么两者就会构造隐藏关系,不必看函数参数是否相同;(我们之前的函数重载,必须要求是在同一个作用域中,函数名相同,函数参数不同才构成函数重载)
3.注意我们在实际中建议不要使用同名成员。
class Person
{
public:protected:int num = 12345; //身份证号码int age = 18;
};class Student :public Person
{
public:void print(){cout << Person::num << endl;cout << num << endl;}
protected:int num = 999; //学号
};int main()
{Student s;s.print();return 0;
}
五、派生类的默认成员函数
上面是我们类中编译器默认生成的六种默认成员函数,由于后面两种成员函数不是十分重要,我们着重介绍上面四种成员函数。
派生类本质也是一种类,只不过它与基类有着千丝万缕的关系。现在我们来介绍一下派生类的四个默认成员函数。
1.派生类的构造函数必须调用基类的构造函数来初始化基类的那一部分成员,如果基类中没有的那一部分就需要我们自己在派生类的初始化列表中自己来显示初始化;
2.派生类的拷贝构造必须调用基类的拷贝构造完成基类的拷贝初始化;
3.派生类的operator=必须调用基类的operator=来完成基类的赋值,其余的部分需要我们自己来实现赋值。我们需要注意的是派生类的operator=与基类的operator=构成了隐藏关系,因此我们调用基类的operator=时需要自己显示来进行调用;
4.派生类的析构函数会在被调用完后自动调用基类的析构函数来清理基类内村,这样就能确保析构顺序正确(先调用的后析构);
5.构造函数的调用顺序:先基类再派生类。析构函数的调用顺序:先派生类再基类;
6.因为多态中一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们多态章节会讲解)。那么编译器会对析构函数名进行特殊处理,处理成destructor(),所以基类析构函数不加 virtual的情况下,派生类析构函数和基类析构函数构成隐藏关系。
如下代码是四个默认成员函数的显示实现方式。我们可以看到派生类的四个默认成员函数的显示实现方式:它基本都是调用了基类的默认成员函数,然后自己再补充点基类中所没有的成员变量初始化/赋值。但是在析构函数的显示实现中,我们并没有看到调用了基类的析构函数,这是因为我们显示实现了派生类的析构函数后,基类的析构函数会在派生类的析构函数调用完后自动进行调用。如果我们再显示写基类的析构函数的话,就会造成基类析构函数出现好几个的情况。
class Person
{public :Person(const char* name = "peter"): _name(name ){cout<<"Person()" <<endl;}Person(const Person& p): _name(p._name){cout<<"Person(const Person& p)" <<endl;}Person& operator=(const Person& p ){cout<<"Person operator=(const Person& p)"<< endl;if (this != &p)_name = p ._name;return *this ;}~Person(){cout<<"~Person()" <<endl;}protected :string _name ; // 姓名
};class Student : public Person
{
public :Student(const char* name, int num): Person(name), _num(num ){cout<<"Student()" <<endl;}Student(const Student& s): Person(s), _num(s ._num){cout<<"Student(const Student& s)" <<endl ;}Student& operator = (const Student& s ){cout<<"Student& operator= (const Student& s)"<< endl;if (this != &s){// 构成隐藏,所以需要显⽰调⽤ Person::operator =(s);_num = s ._num;}return *this ;} ~Student(){cout<<"~Student()" <<endl;}
protected :int _num ; //学号
};int main()
{Student s1 ("jack", 18);Student s2 (s1);Student s3 ("rose", 17);s1 = s3 ;return 0;
}
六、实现一个不能被继承的类
我们如果想要实现一个不能够被继承的类,我们有如下两种方法:
方法一:这种方法有点粗暴,我们直接将我们的基类放在private成员那里,那样我们就不能使用派生类来进行继承了;
方法二:在派生类继承基类时,在基类的旁边加上一个关键字final,这样就能够强制限制它不能够被继承了。这种方法是在C++11中才开始实行的。
// C++11的⽅法
class Base final
{
public:void func5() { cout << "Base::func5" << endl; }
protected:int a = 1;
private:// C++98的⽅法 /*Base(){}*/
};
class Derive :public Base
{void func4() { cout << "Derive::func4" << endl; }
protected:int b = 2;
};
int main()
{Base b;Derive d;return 0;
}
七、继承与友元
友元是在我们刚刚学习类不久就开始接触到了,我们什么时候使用友元呢?我们定义的一个函数的参数是不能够省略的(作为类的成员函数有一个隐藏的参数),于是我们就需要自己在类外面实现。但是我们后面要在类中调用这个函数,于是我们就需要将这个函数定为友元函数(在那个函数声明前加上一个关键字friend,再放到类中)。友元在继承中同样适用的,毕竟它们的本质都是类,但是友元是不能够继承的,也就是说如果我们在基类中定义了一个友元函数,它的派生类是不能继承这个友元函数的。打个比方:比如说你的爸爸和王叔叔是在战场上出生入死过的战友,有着过命的交情,平时你爸爸跟王叔叔如果借钱的话也就是一句话的事,但是你找王叔叔借钱的话也是一句话的事嘛?这显然是不太可能的,毕竟你跟王叔叔不熟,人家不会一句话就把钱借给你。因此,如果你也想像你爸爸和王叔叔那样,你就要和王叔叔打好关系,与他做过命的朋友,到那时候借钱也就是一句话的事了。
class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};
class Student : public Person
{
protected:int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;
}
int main()
{Person p;
Student s;// 编译报错:error C2248: “Student::_stuNum”: ⽆法访问 protected 成员 // 解决⽅案:Display也变成Student 的友元即可 Display(p, s);return 0;
}
八、继承与静态成员变量
我们知道静态成员变量是放在静态区的,它是在全局存在的,因此如果我们在基类中定义了一个静态成员变量的话,那么派生类在继承基类的那个静态成员变量就是同一个了。我们之前所说的继承成员变量,都是派生类根据基类重新定义的一个变量并不是原版的了。基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。无论派生出多少个派生类,都只有一个static成员实例。
class Person
{
public:string _name;static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:int _stuNum;
};
int main()
{Person p;Student s;// 这⾥的运⾏结果可以看到⾮静态成员_name的地址是不⼀样的 // 说明派⽣类继承下来了,⽗派⽣类对象各有⼀份 cout << &p._name << endl;cout << &s._name << endl;// 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的 // 说明派⽣类和基类共⽤同⼀份静态成员 cout << &p._count << endl;cout << &s._count << endl;// 公有的情况下,⽗派⽣类指定类域都可以访问静态成员 cout << Person::_count << endl;
cout << Student::_count << endl;return 0;
}
九、多继承及其菱形继承问题
9.1 继承模型
单继承:当一个派生类只有一个直接基类时,我们就称之为单继承;
多继承:当一个派生类有两个或以上的直接基类时,我们称之为多继承,多继承对象在内存中的模型是:先继承的在前面,后继承的在后面,最后一个是派生类成员;
菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下面的对象成员模型构造,可以 看出菱形继承有数据冗余和二义性的问题,在Assistant的对象中Person成员会有两份。支持多继承就一定会有菱形继承,像Java就直接不支持多继承,规避掉了这里的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的。
class Person
{
public:
string _name; // 姓名
};
class Student : public Person
{
protected:int _num; //学号
};
class Teacher : public Person
{
protected:int _id; // 职⼯编号
};
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};
int main()
{// 编译报错:error C2385: 对“_name”的访问不明确 Assistant a;a._name = "peter";// 需要显⽰指定访问哪个基类的成员可以解决⼆义性问题,但是数据冗余问题⽆法解决 a.Student::_name = "xxx";a.Teacher::_name = "yyy";return 0;
}
如上代码,它就使用了菱形继承,它定义了一个基类,三个派生类,它首先通过Person这个基类来定义了两个派生类Student,Teacher,然后再将这两个派生类作为基类继承生成一个派生类Assistant。我们在实例化一个Assistant对象后,我们调用它的成员变量时就出现了二义性的问题,我们不能够确定那个_name是从哪个基类继承来的,因此如果我们需要自己显示指定一下哪个基类,但是这样做只是解决了二义性的问题,数据冗余的问题还是没能够被解决。
9.2 虚继承
C++中的多继承虽然说在实际中可能应用挺广泛的,但是它应运而生的菱形继承所带来的问题有点多,这也是java中没有多继承的一个主要原因,但是C++对于之前已经创立的语法是不可以进行修改的,我们只能够向前兼容,于是我们就又创建了一个新的语法——虚继承。虚继承就是在我们派生类继承基类的时候,在继承方式前加上一个关键字virtual即可。在C++中,虚继承(Virtual Inheritance)是一种用于解决多继承中重复继承问题的机制。其主要意义在于确保共同的基类只被继承一次,避免数据冗余和访问歧义。
#include <iostream>class A {
public:A() { std::cout << "A的构造函数被调用\n"; }~A() { std::cout << "A的析构函数被调用\n"; }
};class B : virtual public A {
public:B() { std::cout << "B的构造函数被调用\n"; }~B() { std::cout << "B的析构函数被调用\n"; }
};class C : virtual public A {
public:C() { std::cout << "C的构造函数被调用\n"; }~C() { std::cout << "C的析构函数被调用\n"; }
};class D : public B, public C {
public:D() { std::cout << "D的构造函数被调用\n"; }~D() { std::cout << "D的析构函数被调用\n"; }
};int main() {D obj;return 0;
}
上述代码,我们使用了虚继承的方式来模拟展示,每个类构造/析构时的方式。我们从下面的运行结果可以看出来,它们只会调用它们的基类一次避免了基类被重复调用的问题,而且构造函数的调用顺序是按照继承层次的深度优先顺序进行调用的,析构则是先调用的后析构。
总而言之,我们可以设计多继承,但是不建议设计菱形继承,即使我们有其解决方法但是这样总归会有运行消耗的,因此我们在后续的学习中是不建议设计菱形继承的。
十、继承和组合
在C++中,继承与组合是两种重要的机制,用于构建类之间的关系和复用代码。理解它们的区别和应用场景,对于设计高效、可维护的代码至关重要。
继承(Inheritance)
继承是一种“is-a”关系,表示一个类(子类)是另一个类(父类)的特例。子类继承父类的属性和方法,并可以添加新的功能或重写父类的方法。
特点:
- 代码复用:子类可以复用父类的代码,减少重复编码。
- 层次结构:通过继承,可以构建类的层次结构,反映现实世界中的分类关系。
- 多态性:继承支持多态性,子类可以重写父类的方法,以实现不同的行为。
示例:
class Animal {
public:virtual void sound() = 0;
};class Dog : public Animal {
public:void sound() override {std::cout << "汪汪叫" << std::endl;}
};class Cat : public Animal {
public:void sound() override {std::cout << "喵喵叫" << std::endl;}
};
在这个示例中,Dog
和Cat
类都继承自Animal
类,并重写了sound()
方法,展示了继承和多态性的应用。
组合(Composition)
组合是一种“has-a”关系,表示一个类(容器类)包含另一个类(被包含类)的对象。通过组合,容器类可以复用被包含类的功能,而不必继承其所有属性和方法。
特点:
- 模块化设计:组合允许将功能分解为独立的类,提高代码的模块化和可维护性。
- 灵活性:容器类可以根据需要动态地添加或移除被包含类的对象,而不会影响整体结构。
- 避免多重继承问题:组合可以避免多重继承带来的二义性和复杂性。
示例:
class Engine {
public:void start() {std::cout << "引擎启动" << std::endl;}
};class Car {
private:Engine engine;
public:void startEngine() {engine.start();}
};
在这个示例中,Car
类通过组合Engine
类,复用了引擎的功能,而无需继承Engine
类。
继承与组合的比较
特性 | 继承 | 组合 |
---|---|---|
关系 | “是”(is-a) | “拥有”(has-a) |
代码复用 | 通过继承父类的属性和方法 | 通过包含其他类的对象 |
层次结构 | 构建类的层次结构 | 提供模块化设计 |
多态性 | 支持多态性 | 不直接支持多态性 |
灵活性 | 修改父类可能影响所有子类 | 动态添加或移除功能 |
复杂性 | 可能导致类之间的耦合性增加 | 减少多重继承带来的复杂性 |
选择继承还是组合
在实际编程中,选择继承还是组合,需要根据具体的需求和设计原则来决定:
-
“是”关系:如果类之间存在“是”的关系,即子类是父类的特例,那么继承是合适的选择。
-
“拥有”关系:如果类之间是“拥有”的关系,或者需要模块化设计和功能复用,那么组合更合适。
-
避免多重继承:如果需要避免多重继承带来的复杂性,组合是一个更好的选择。
-
代码复用与扩展:如果需要通过继承实现代码复用和扩展,同时保持层次结构的清晰,继承是合适的选择。
-
动态功能添加:如果需要在运行时动态地添加或移除功能,组合提供了更大的灵活性。
总结
继承与组合是C++中两种重要的机制,用于构建类之间的关系和复用代码。继承适用于“是”的关系,提供了代码复用和多态性的能力;组合适用于“拥有”的关系,提供了模块化设计和动态功能添加的灵活性。在实际编程中,应根据具体的需求和设计原则,选择合适的机制,或结合两者以实现最优的设计。
相关文章:
C++之继承
本节我们将要学习C作为面向对象语言的三大特性之一的继承。 前言 一、继承的概念 二、继承的定义 2.1 定义格式 2.2 继承基类成员访问方式的变化 2.3 继承类模板 三、基类和派生类之间的转换 四、继承中的作用域 五、派生类的默认成员函数 六、实现一个不能被继承的类 七、继承…...
服务器申请 SSL 证书注意事项
一、确认证书类型 基础域名型(DV):这类证书验证速度最快,通常只需验证域名所有权,几分钟到几小时即可颁发。适用于个人博客、小型企业展示网站等对安全性要求不是顶级严苛,且急需启用 HTTPS 的场景。但它仅…...
【资料分享】全志T536(异构多核ARMCortex-A55+玄铁E907 RISC-V)工业核心板说明书
核心板简介 创龙科技SOM-TLT536是一款基于全志科技T536MX-CEN2/T536MX-CXX四核ARM Cortex-A55 +...
博途 TIA Portal之1200做从站与调试助手的TCP通讯
其实,1200做从站与调试助手做TCP通讯很简单,只需要在组态时把“主动建立连接”放在对侧即可。但是我们还是要从头讲起,以方便没有基础的朋友能直接上手操作。 1、硬件准备 1200PLC一台,带调试助手的PC机一台,调试助手…...
移动端六大语言速记:第9部分 - 并发与多线程
移动端六大语言速记:第9部分 - 并发与多线程 本文将对比Java、Kotlin、Flutter(Dart)、Python、ArkTS和Swift这六种移动端开发语言在并发与多线程方面的特性,帮助开发者理解和掌握各语言的并发编程机制。 9. 并发与多线程 9.1 线程与进程 各语言线程与进程的创建和管理方…...
基于大模型的ALS预测与手术优化系统技术方案
目录 技术方案文档:基于大模型的ALS预测与手术优化系统1. 数据预处理与特征工程模块流程图伪代码2. 多模态融合预测模型模型架构图伪代码3. 术中实时监测与动态干预系统系统流程图伪代码4. 统计验证与可解释性模块验证流程图伪代码示例(SHAP分析)5. 健康教育与交互系统系统架…...
【Vue3知识】组件间通信的方式
组件间通信的方式 概述**1. 父子组件通信****父组件向子组件传递数据(Props)****子组件向父组件发送事件(自定义事件)** **2. 兄弟组件通信****通过父组件中转****使用全局状态管理(如 Pinia 或 Vuex)** **…...
【数据挖掘】岭回归(Ridge Regression)和线性回归(Linear Regression)对比实验
这是一个非常实用的 岭回归(Ridge Regression)和线性回归(Linear Regression)对比实验,使用了 scikit-learn 中的 California Housing 数据集 来预测房价。 📦 第一步:导入必要的库 import num…...
RuntimeError: Error(s) in loading state_dict for ChartParser
一 bug错误 最近使用千问大模型有一个bug,报错信息如下 raise RuntimeError(Error(s) in loading state_dict for {}:\n\t{}.format( RuntimeError: Error(s) in loading state_dict for ChartParser:Unexpected key(s) in state_dict: "pretrained_model.em…...
汽车无钥匙启动125KHz低频发射天线工作原理
汽车智能钥匙低频天线是无钥匙进入(PE)及无钥匙启动(PS)系统的一部分,主要负责发送低频信号,探测智能钥匙与各低频天线间的相对位置,判断车内是否存在智能钥匙。 支持PEPS系统实现便捷操作 无…...
【Docker基础-镜像】--查阅笔记2
目录 Docker镜像概述base镜像镜像的分层结构镜像的理解镜像的构建docker commit 制作镜像DockerfileDockerfile 指令FROMLABELRUNARGENVADDCOPYWORKDIRUSERVOLUMEEXPOSECMD 和 ENTRYPOINT Docker镜像概述 镜像是Docker容器的基石,容器是镜像的运行实例,…...
LeetCode 第47题:旋转数组
LeetCode 第47题:旋转数组 题目描述 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 示例1: 输入…...
数据库管理工具实战:IDEA 与 DBeaver 连接 TDengine(二)
五、DBeaver 连接 TDengine 实战 5.1 安装 DBeaver 下载安装包:访问 DBeaver 官方网站(https://dbeaver.io/download/ ),根据你的操作系统选择合适的安装包。如果是 Windows 系统,下载.exe 格式的安装文件࿱…...
4S店汽车维修保养管理系统 (源码+lw+部署文档+讲解),源码可白嫖!
摘要 二十一世纪我们的社会进入了信息时代,信息管理系统的建立,大大提高了人们信息化水平。传统的管理方式已经与当今4S店汽车维修保养管理系统的业务需求不相适应,也与4S店汽车维修保养管理系统化建设的发展趋势不相适应。本文针对这一需求设计并实现了…...
【Mysql】主从复制部署(保姆级)
本次部署用到三台Ubuntu虚拟机(一主两从): Master服务器:192.168.166.107 Slave1服务器:192.168.166.101 Slave2服务器:192.168.166.103 一、部署思路 首先我们要先捋清主从复制的部署思路…...
华为AR1200密码忘记
1、通过Console口连接设备并重启设备。在设备启动过程中,看到提示信息“Press CtrlB to break auto startup...”时,在三秒内按下CtrlB,输入BootLoader密码后,默认密码:Adminhuawei ,进入BootLoader主菜单…...
高级java每日一道面试题-2025年3月26日-微服务篇[Nacos篇]-在Spring Cloud项目中如何集成Nacos?
如果有遗漏,评论区告诉我进行补充 面试官: 在Spring Cloud项目中如何集成Nacos? 我回答: 在Spring Cloud项目中集成Nacos,可以充分利用Nacos作为服务注册与发现中心以及配置管理中心的功能。以下是详细的步骤和说明,帮助你完成这一集成过程…...
YOLO-LLTS:低光照实时交通标志检测算法详解
论文地址:https://arxiv.org/pdf/2503.13883 目录 一、论文概述 1.1 研究背景 1.2 论文结构 二、核心创新点 2.1 CNTSSS数据集 2.2 HRFM-TOD模块 2.3 MFIA模块 2.4 PGFE模块 三、实验与结果 3.1 实验设置 3.2 性能对比 编辑3.3 消融实验 四、代码复现建议 4.…...
golang 性能优化分析工具 pprof
pprof简介 pprof 是 Go 语言标准库提供的一个强大的性能分析工具,它能帮助开发者深入了解程序的运行时行为,找出性能瓶颈,进而对代码进行优化。下面从多个方面对 pprof 进行详细介绍: 主要功能 CPU 性能分析:能够记…...
机器学习 Day09 线性回归
1.线性回归简介 线性回归知识讲解 定义与公式 定义:线性回归是利用回归方程(函数)对自变量(特征值)和因变量(目标值)之间关系进行建模的分析方式 。自变量只有一个时是单变量回归,…...
2025高频面试算法总结篇【字符串】
文章目录 直接刷题链接直达如何找出一个字符串中的最大不重复子串给定一个数,删除K位得到最大值字符串的排列至少有K个重复字符的最长子串 直接刷题链接直达 如何找出一个字符串中的最大不重复子串 滑动窗口 --> 滑动窗口直到最后一个元素,每当碰到重…...
JavaScript性能优化(上)
1. 减少 DOM 操作 减少 DOM 操作是优化 JavaScript 性能的重要方法,因为频繁的 DOM 操作会导致浏览器重绘和重排,从而影响性能。以下是一些具体的策略和技术,可以帮助有效减少 DOM 操作: 1.1. 批量更新 DOM 亲切与母体ÿ…...
数据结构与算法——链表OJ题详解(1)
文章目录 一、前言二、OJ题分享2.1移除链表元素——非val尾插法2.2反转链表2.2.1头插法2.2.2三指针法 2.3链表的中间结点——快慢指针法2.4合并两个有序链表2.4.1空链表法2.4.2非空链表法 2.5链表的回文结构2.5.1投机取巧数组法2.5.2反转链表法 三、总结 一、前言 前几天博主已…...
sedex认证2025年变化重点
近日,SEDEX突然宣布:2025年7月1日起,全通知审核正式退出历史舞台,取而代之的是至少3周窗口期的半通知突击审核。这场被业内称为“供应链透明化革命”的调整,或将重塑全球工厂合规生态。 三大变化划重点: 1…...
Scala课后总结(8)
集合计算高级函数 过滤(filter) 从集合里挑出符合特定条件元素组成新集合 。比如从整数集合里选出偶数, list.filter(x > x % 2 0) ,就是筛选出能被2整除的元素。 转化/映射(map) 对集合每个元素应…...
老硬件也能运行的Win11 IoT LTSC (OEM)物联网版
#记录工作 Windows 11 IoT Enterprise LTSC 2024 属于物联网相关的版本。 Windows 11 IoT Enterprise 是为物联网设备和场景设计的操作系统版本。它通常针对特定的工业控制、智能设备等物联网应用进行了优化和定制,以满足这些领域对稳定性、安全性和长期支持的需求…...
蓝桥杯冲刺题单--二分
二分 知识点 二分: 1.序列二分:在序列中查找(不怎么考,会比较难?) 序列二分应用的序列必须是递增或递减,但可以非严格 只要r是mid-1,就对应mid(lr1)/2 2.答…...
计网 2025/4/8
CDMA? CRC循环冗余检验 PPP协议的帧格式 字节填充(异步传输、7E->7D5E)零比特填充(同步传输、确保不会出现连续6个1) CSMA/CD协议 多点接入载波监听碰撞检测 一些概念: 争用期 一些公式: 最短有效帧…...
java设计模式-工厂模式
工厂模式 简单工厂模式 请看类: org.xwb.springcloud.factory.simple.PizzaStore 1、简单工厂模式是属于创建型模式,是工厂模式的一种,简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实力。简单来工厂模式就是工厂模式家族中最简单最…...
2025年客运从业资格证备考刷题题库
题库中通常包含大量的题目,以全面覆盖考试的知识点。通过做大量的题目,考生可以熟悉各种考试题型和命题方式,提高答题的速度和准确性,同时也能发现自己在知识掌握上的薄弱环节,有针对性地进行复习和强化训练。 1、驾驶…...
Zephyr、FreeRTOS、RT-Thread 任务创建对比分析
一、任务模型与核心概念 特性ZephyrFreeRTOSRT-Thread任务术语线程(Thread)任务(Task)线程(Thread)执行单元线程(单地址空间)任务(共享内存空间)线程&#x…...
RK-realtime Linux
rk3562实时性数据:最大76us rk3568实时性数据:最大126us rk3588实时性数据:最大30us 注意事项 (1)RK3568 需要使用RT版本的BL31,实时性能更好 a)rkbin需要更新到最新,且包含这个补丁:...
Ubuntu 22 Linux上部署DeepSeek+RAG知识库操作详解(Dify方式)之1
一、安装Docker 1. 更新你的包索引 首先,确保你的包列表是最新的。打开终端并运行以下命令: sudo apt update 2. 安装必要的依赖项 安装Docker之前,你需要安装一些必要的依赖项。运行以下命令来安装它们: sudo apt install apt…...
将飞帆制作的网页作为 div 集成到自己的网页中
并且自己的网页可以和飞帆中的控件相互调用函数。效果: 上链接 将飞帆制作的网页作为 div 集成到自己的网页中 - 文贝 进入可以复制、运行代码...
【C++游戏引擎开发】《几何算法》(3)AABB/OBB碰撞检测
一、AABB(轴对齐包围盒) 1.1 定义 最小点: m i n = ( x min , y min , z min ) \mathbf{min} = (x_{\text{min}}, y_{\text{min}}, z_{\text{min}}) min=(xmin,ymin,zmin)最大点: m a x = ( x max , y max , z max ) \mathbf{max} = (x_{\text{max}}, y_{\text{…...
基于人工智能的高中教育评价体系重构研究
基于人工智能的高中教育评价体系重构研究 一、引言 1.1 研究背景 在科技飞速发展的当下,人工智能技术已广泛渗透至各个领域,教育领域亦不例外。人工智能凭借其强大的数据处理能力、智能分析能力和个性化服务能力,为教育评价体系的创新与发…...
【C++游戏引擎开发】数学计算库GLM(线性代数)、CGAL(几何计算)的安装与使用指南
写在前面 两天都没手搓实现可用的凸包生成算法相关的代码,自觉无法手搓相关数学库,遂改为使用成熟数学库。 一、GLM库安装与介绍 1.1 vcpkg安装GLM 跨平台C包管理利器vcpkg完全指南 在PowerShell中执行命令: vcpkg install glm# 集成到系…...
Python 字典和集合(常见的映射方法)
本章内容的大纲如下: 常见的字典方法 如何处理查找不到的键 标准库中 dict 类型的变种set 和 frozenset 类型 散列表的工作原理 散列表带来的潜在影响(什么样的数据类型可作为键、不可预知的 顺序,等等) 常见的映射方法 映射类型…...
Qt 自带的QSqlDatabase 模块中使用的 SQLite 和 SQLite 官方提供的 C 语言版本(sqlite.org)对比
Qt 自带的 QSqlDatabase 模块中使用的 SQLite 和 SQLite 官方提供的 C 语言版本(sqlite.org)在核心功能上是相同的,但它们在集成方式、API 封装、功能支持以及版本更新上存在一些区别。以下是主要区别: 1. 核心 SQLite 引擎 Qt 的…...
按键长按代码
这些代码都存放在定时器中断中。中断为100ms中断一次。 数据判断,看的懂就看吧...
zk源码—3.单机和集群通信原理一
大纲 1.单机版的zk服务端的启动过程 (1)预启动阶段 (2)初始化阶段 2.集群版的zk服务端的启动过程 (1)预启动阶段 (2)初始化阶段 (3)Leader选举阶段 (4)Leader和Follower启动阶段 1.单机版的zk服务端的启动过程 (1)预启动阶段 (2)初始化阶段 单机版zk服务端的启动&…...
车企数字化转型:从“制造工厂”到“移动科技平台”的升维路径
一、战略重构:政策与产业变革的双重倒逼 中国《智能网联汽车技术路线图2.0》明确要求2030年L4级自动驾驶新车渗透率达20%,而麦肯锡数据显示,全球车企数字化投入占比已从2018年的7%跃升至2025年的18%。当前车企面临三大核心挑战:用…...
C++-Mongoose(2)-https-server-openssl
OpenSSL生成HTTPS自签名证书 - 简书 1.Openssl windowsubuntu下载http://www.openssl.vip/download1.VS2019编译OpenSSL 2.VS2019编译第一个OpenSSL项目 1.ubuntu编译OpenSSL 3.0 2.编写第一个OpenSSL 1.windows下编译OpenSSL 安装vs2019 perl nasm安装activePerl…...
nginx正向代理https
一、需求 公司内部服务器向外访问腾讯接口:https://qyapi.weixin.qq.com/cgi-bin,不能使用http直接访问。并且不支持域名,还需要设置互联网出口-出向白名单ip。 如何在尽量少改动代码的情况下实现应用的出向访问链接,考虑使用正向…...
Flask中的蓝图(Blueprint)浅讲
BluePrint Flask中的蓝图(Blueprint)是一种强大的组织工具,能够将大型应用拆分为可重用的模块化组件 1. 模块化组织 用途:将应用按功能拆分为独立模块,提升代码可维护性。示例: # user/views.py fr…...
虚拟表、TDgpt、JDBC 异步写入…TDengine 3.3.6.0 版本 8 大升级亮点
近日,TDengine 3.3.6.0 版本正式发布。除了此前已亮相的时序数据分析 AI 智能体 TDgpt,本次更新还带来了多个针对性能与易用性的重要增强:虚拟表全面上线,支持更灵活的一设备一表建模;JDBC 写入机制全新升级࿰…...
大型语言模型智能应用Coze、Dify、FastGPT、MaxKB 对比,选择合适自己的LLM工具
大型语言模型智能应用Coze、Dify、FastGPT、MaxKB 对比,选择合适自己的LLM工具 Coze、Dify、FastGPT 和 MaxKB 都是旨在帮助用户构建基于大型语言模型 (LLM) 的智能应用的平台。它们各自拥有独特的功能和侧重点,以下是对它们的简要对比: Coz…...
WEB安全--XSS--DOM破坏
一、前言 继XSS基础篇后,我们知道了三种类型的XSS,这篇文章主要针对DOM型XSS的原理进行深入解析。 二、DOM型XSS原理 2.1、什么是DOM 以一个形象的比喻: 网页就像是一座房子,而 **DOM** 就是这座房子的“蓝图”或者“结构图”。…...
持续集成:GitLab CI/CD 与 Jenkins CI/CD 的全面剖析
一、引言 在当今快速迭代的软件开发领域,持续集成(Continuous Integration,CI)已成为保障软件质量、加速开发流程的关键实践。通过频繁地将代码集成到共享仓库,并自动进行构建和测试,持续集成能够尽早发现并解决代码冲突和缺陷。而 GitLab CI/CD 和 Jenkins CI/CD 作为两…...
Go语言sync.Mutex包源码解读
互斥锁sync.Mutex是在并发程序中对共享资源进行访问控制的主要手段,对此Go语言提供了非常简单易用的机制。sync.Mutex为结构体类型,对外暴露Lock()、Unlock()、TryLock()三种方法,分别用于阻塞加锁、解锁、非阻塞加锁操作(加锁失败…...