【C++学习】继承

🐱作者:一只大喵咪1201
🐱专栏:《C++学习》
🔥格言:你只管努力,剩下的交给时间!
图

C++是面向对象的编程语言,它有很多的特性,但是最重要的就是封装,继承,多态三大特性,封装本喵就不介绍了,前面我们一直都在使用,这里本喵来详细介绍继承

继承

  • 🙀继承的概念和定义
    • 😸继承关系和访问限定符
  • 🙀基类和派生类的赋值转换
    • 😸特性
  • 🙀继承中的作用域
  • 🙀派生类的默认成员函数
    • 😸构造函数
    • 😸拷贝构造函数
    • 😸赋值运算符重载函数
    • 😸析构函数
  • 🙀继承与友元
  • 🙀继承与静态成员
  • 🙀菱形继承
    • 😸多继承
    • 😸虚拟继承
  • 🙀继承和组合
  • 🙀总结

🙀继承的概念和定义

  • 继承:是面向对象程序设计使代码可以复用的最重要手段,它运行程序员在保持原有类特性的基础上进程扩展。
  • 继承是类设计层次的复用。

图
如上图,这几个类都是在描述人扮演的不同角色,分别是学生,老师,其他职业的人。

每一个角色在描述的时候都有自己特有的属性,学生有学号班级,老师有工号和所教科目,其他职业的人也有工号和具体的岗位。

  • 但是不同的角色之间也有共同的属性,比如姓名,性别,年龄。
  • 将那些无论扮演什么角色都必须有的属性看作是共有的属性。

此时,在使用不同类型的类描述不同的角色时,都需要复用到人的属性。为了方便,创建一个Person的类型来描述人,在描述不同角色创建新的类时,只需要增加Person这个成员即可,然后再添加各自的属性。

下面3个蓝色框中的类复用红色框Person这个类的过程就叫做继承

具体到代码上来看:

class Person
{
public:
	void Print()
	{
		cout << _name << endl;
		cout << _age << endl;
	}
protected:
	string _name;//姓名
	int _age;//年龄
};

//继承
class Student : public Person
{
protected:
	int _stuid;//学号
};

Student类继承了Person类。

图

  • Student:称为基类,也叫做父类。
  • Person:称为派生类,也叫做子类。
  • public:是继承方式。

tu
在创建了Student对象后,该对象中不仅有class Student中的_stuid成员,还有class Person中的_name,_age成员,以及class Person中的Print()成员函数。

  • 基类中的一切都被派生类继承了下来。

😸继承关系和访问限定符

图
访问限定符之前本喵讲解过,继承方式也是有这三种。使用不同继承方式继承不同权限的基类成员,排列组合后共有9中继承结果。

类成员\继承方式public继承protected继承private继承
基类的public成员派生类的public成员派生类的protected成员派生类的private成员
基类的protected成员派生类的protected成员派生类的protected成员派生类的private成员
基类的private成员在派生类中不可见在派生类中不可见在派生类中不可见

看起来非常复杂,但是有规律可循。

基类中的private成员:

图

  • Person中是private成员。
  • 在Student继承Person的时候,分别采用public,protected,private三种继承方式。

在调试窗口中可以看到,每个派生类对象中是有基类成员的,但是基类成员的前面有个小锁(途中可能看不清),表示派生类中的基类成员无法访问

派生类对象在访问基类成员的时候,编译器会报不可访问的错误。

基类中的private成员无论以什么方式继承都是不可见的

  • 这里的不可见是指:基类的成员仍然被继承到了派生类中,但是在语法上不让派生类去访问。
  • 无论是在派生类的内部访问还是外部访问,都是不可以的。

基类中的protceted成员:

图

  • Person中是protected成员。
  • 在Student继承Person的时候,分别采用public,protected,private三种继承方式。

在调试窗口中可以看到,每个派生类中是有基类成员的,而且没有小锁,说明此时在派生类中的基类成员是可见的,也就是可以访问的。

  • 用派生类的成员函数是可以访问基类中的成员的。
  • 在派生类的外部访问基类成员会报错。

无论什么继承方式,在派生类的内部是可以访问基类中的保护成员的,但是在派生类的外部不能访问基类的保护成员

基类中的public成员:

图

  • Person中是public成员。
  • 在Student继承Person的时候,分别采用public,protected,private三种继承方式。

在调试窗口中可以看到,派生类中有基类的成员,并且没有小锁,说明是可见的。

  • 派生类内部都可以访问基类的成员。
  • 派生类外部:
    共有继承的公有成员:派生类外部可以访问基类成员。
    保护继承的公有成员:派生类外部不可以访问基类成员。
    私有继承的公有成员:派生类外部不可以访问基类成员。

无论哪种继承方式,派生类内部都可以访问基类公有成员,保护继承和私有继承,派生类只能在内部访问基类成员

规律总结:

在刚学习类和对象的时候,本喵说过将访问限定符private和protected暂时看成是一样的,现在就可以介绍他俩的区别了。

三个访问限定符的访问权限大小关系如下:

  • 访问权限:public > protected > private

继承关系和访问限定符的组合可以分为两大类:

  • 基类中是private成员:无论哪种继承方式,基类成员对于派生类都是不可见的,不可访问。
  • 基类中的其他权限成员:继承方式和基类成员访问限定符二者取权限小的作为派生类中成员的权限。

例如,基类中是protected成员,使用public继承方式,那么继承下来的基类成员在派生类中的访问权限就是protected。而此时protected就和private是一样了,在类的内部可以访问,在类的外部无法访问。

再例如,基类中的public成员,使用private继承方式,那么继承下来的基类成员在派生类中的访问限定符就是private。

可以看出,protected访问限定符就是因为继承才出现的。所以,protected和private的区别在继承中才得以体现。

  • 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承。

因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

  • 继承方式可以省略不写,采用默认继承方式:

提

  • class定义的类,内部如果不写访问限定符,成员的默认权限是private。
  • class定义的派生类,如果不写继承方式,默认的继承方式也是private。

图

  • struct定义的类,内部如果不写访问限定符,所有成员的默认访问权限是public。
  • struct定义的派生类,如果不写继承方式,默认的继承方式也是public。

不过,最好还是显示写出继承方式。

🙀基类和派生类的赋值转换

  • 派生类对象可以赋值给基类的对象/基类的指针/基类的引用。
class Person
{
public:
	void Print()
	{

	}
protected:
	string _name;//姓名
	string _sex;//性别
	int _age;//年龄
};

class Student : public Person
{
public:
	int _No;
};

基类和派生类如上。

图
可以看到,派生类对象赋值给基类的对象/基类的指针/基类的引用全部都可以。

图
派生类赋值给基类的原理如上图所示。派生类中基类有的变量保留,其余的舍弃,_No就是派生类特有的,所以在赋值的时候会舍弃。

  • 这里有个形象的说法叫切片或者切割。寓意把派生类中基类那部分切来赋值过去。

😸特性

  • 派生类对象赋值给基类对象不存在类型转换。

图
double类型的变量赋值给int&就会报错,这是因为:

图

  • 类型转换时,会产生中间变量
  • double值会先赋给int类型的临时变量,再将临时变量的值赋给int类型的变量。

上面代码中,int&变量是临时变量的引用,而临时变量具有常性,此时相当于权限放大了,所以会报错。

图
在引用变量前加const就可以解决这个问题。

而派生类对象赋值给基类的引用时就没有这个问题,可以之间赋值,所有说派生类对象赋值给基类对象不存在类型转换

没有类型转换可以很大程度上节省系统的开销。

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

🙀继承中的作用域

class Person
{
protected:
	string _name = "张三";
	int _num = 150;
};

class Student : public Person
{
public:
	void Printf()
	{
		cout << "姓名:" << _name << endl;
		cout << "身份证号:" << _num << endl;
		cout << "学号:" << _num << endl;
	}
protected:
	int _num = 370;
};

基类和派生类中都有变量_num,在派生类的成员函数中,既想打印基类中的_num,也想打印派生类中的_num。

图

  • 派生类对象中,既有自己的_num,也有基类中的_num。
  • 在使用的时候,直接使用_num时,发现使用的是派生类中的_num。
  • 派生类中的_num和基类中的_num属于不同作用域。
  • 默认情况下,使用的是派生类中的_num。

当基类和派生类中的成员变量名相同时,基类中的成员变量会被隐藏,也叫做重定义

图

  • 要想使用被隐藏的基类中成员变量,需要在变量前加上域名和域作用限定符(显示访问)。

图
基类和派生类中各有一个成员函数,而且成员函数名相同。此时这俩个函数之间构成的关系是隐藏/重定义,而不是函数重载

  • 函数重载的前提是:同名函数在同一个作用域中。

此时基类和派生类中的同名函数显然不在同一个作用域,所以不能构成重载,同样是隐藏关系。

图

默认情况下同样调用的是派生类中的成员函数。

图
要想使用基类中被隐藏的成员函数,也是需要加域名和域作用限定符(显示访问)。

注意:

  • 如果是成员函数的隐藏,只需要函数名相同就构成隐藏,因为作用域不同,不构成重载。
  • 在继承体系里面最好不要定义同名的成员。

🙀派生类的默认成员函数

先回顾一下普通类的默认成员函数:

图
派生类同样只看四个默认成员函数。

😸构造函数

class Person
{
public:
	Person(const char* name = "张三")
		:_name(name)
	{
		cout << "Person(const char* name = \"张三\")" << endl;
	}
protected:
	string _name;
};

class Student : public Person
{
protected:
	int _num;//学号
};

Person有显示定义的默认构造函数,Student没有显示定义的默认构造函数。

图
在创建派生类对象的时候,发现基类的默认构造函数被调用了。

图
在派生类中显示定义默认构造函数,在创建派生类对象的时候,发现先调用了基类的默认构造函数,再调用了派生类的默认构造函数

我们知道,派生类中继承了基类中的成员,那么能不能在派生类的构造函数中去初始化基类的成员呢?

图
此时就报错了,说明派生类的构造函数不能直接去初始化基类的成员

图
但是可以在派生类的构造函数中显示调用基类的构造函数来初始化基类

图
当基类中没有默认构造函数时,必须在派生类的构造函数中显示调用基类的构造函数进行初始化。

结论:

  • 派生类对象在创建的时候,先调用基类的构造函数来初始化基类在派生类中的成员,再调用派生类的构造函数初始化自己的成员。
  • 派生类只能通过显示调用基类的构造函数来控制基类初始化,不能直接去初始化从基类中继承下来的成员。
  • 如果基类中没有默认构造函数,派生类必须在构造函数中显示调用基类的构造函数,并且传值。

派生类相比于普通类的构造函数,多了一步对基类成员的处理

😸拷贝构造函数

class Person
{
public:
	Person(){}//默认构造函数
	//拷贝构造函数
	Person(const Person& p)
		:_name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}
protected:
	string _name;
};

class Student : public Person
{
public:
	Student(){}//默认构造函数
	//拷贝构造函数
	Student(const Student& s)
		:_num(s._num)
		, Person(s)
	{
		cout << "Student(const Student& s)" << endl;
	}
protected:
	int _num;//学号
};

图
拷贝构造函数是构造函数的重载,所以它们的特性几乎是一样的。

  • 派生类的拷贝构造函数先调用基类的拷贝构造函数。
  • 派生类的拷贝构造函数不能直接处理基类的成员,必须显示调用基类的拷贝构造函数。

图

  • 派生类的拷贝构造函数在显示调用基类的拷贝构造函数时,传的值是派生类对象。
  • 基类的拷贝构造函数的形参是基类对象。
  • 类型不同,但是没有发生类型转换,而是发生了切割

😸赋值运算符重载函数

class Person
{
public:
	Person& operator=(const Person& p)
	{
		cout << "Person& operator==(const Person& p)" << endl;
		if (this != &p)
		{
			_name = p._name;
		}
		return *this;
	}
protected:
	string _name;
};

class Student : public Person
{
public:
	Student& operator=(const Student& s)
	{
		cout << "Student& operator=(const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator=(s);
			_num = s._num;
		}
		return *this;
	}
protected:
	int _num;//学号
};

派生类的赋值运算符重载函数中,调用基类的赋值运算符重载函数赋值从基类继承下来的那部分成员,然后再初始化自己的、这里同样发生了派生对象给基类对象赋值时的切割现象。

图

  • 由于基类的运算符重载函数是在派生类的运算符重载函数内部调用的,所以在给派生类对象赋值时,会先调用派生类的,在派生类运算符重载函数中再调用基类的。
  • 同样不可以在派生类的运算符重载函数中自行处理基类的成员,必须使用基类的运算符重载函数去处理。

图
基类和派生类的运算符重载函数构造了隐藏/重定义

图

在派生类中调用基类的operator=()时,必须指明作用域,否则会默认调用派生类的,此时就会造成栈溢出。

😸析构函数

图
按照之前几个默认成员函数的做法,在派生类的析构函数中显示调用基类的析构函数,但是发现基类的析构函数一共调用了两次。

这显然是不行的,一块动态空间只能被释放一次。

class Person
{
public:
	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name;
};

class Student : public Person
{
public:
	~Student()
	{
		cout << "~Student()" << endl;
	}
protected:
	int _num;//学号
};

图
在派生类的析构函数中没有显示调用基类的析构函数,发现父类和子类的析构函数各调用了一次。

  • 析构函数第一怪:派生类析构函数会自动调用基类析构函数,这是必然发生的,所以无需显示调用基类的析构函数。
  • 先调用派生类的析构函数,再调用基类的析构函数。
  • 这样做是为了保证先清理派生类成员再清理基类成员。
  • 析构函数第二怪:派生类析构函数和基类析构函数构成隐藏关系。(由于多态关系需求,所有析构函数都会特殊处理成destruct函数名,以后会讲解)。

图
派生类相比于普通类的四类默认成员函数,多了一步对基类成员的处理,而且只能通过基类的默认成员函数去处理,不能由派生类自行处理

图
上图表示了派生类和基类对象的行为。

🙀继承与友元

图
定义一个Display函数,它是基类的友元函数,可以访问基类内部的保护成员。

  • 由于基类中的友元声明中包含派生类,但是编译器只会向上寻找,所以必须在友元声明之前加上派生类的声明。
  • 否则会报Student未声明的错误。

图

在调用基类的友元函数访问派生类中的成员时报错了,不让访问。因为:友元关系不继承

图

  • 若想让基类中的友元也成为派生类中的友元,需要在派生类中也进行友元声明。

注意: 一般不建议使用友元,因为它会破坏类的封装。

🙀继承与静态成员

class Person
{
public:
	int _Pnum = 1;
	static int _cout;
};

int Person::_cout = 0;

class Student : public Person
{
public:
	int _Snum = 1;//学号
};

基类中有一个int类型的变量,有一个static变量,派生类中自己有一个int类型的变量。

图

  • 创建两个对象,一个基类对象,一个派生类对象。
  • 派生类会继承基类中的成员,所以基类对象和派生类对象中都有成员变量_Pnum。

两个对象创建后,分别将基类对象和派生类对象中的_Pnum打印出来,发现它们的值是一样的。

  • 将基类对象中的_Pnum加1,然后打印出来,发现值加1。
  • 将派生类对象中的_Pnum加1,然后打印出来,发现值加1。
  • 基类对象和派生类对象中的_Pnum中的值是相互独立的,也就是有两个值。

图

  • 派生类同样会继承基类中的静态变量。
  • 基类对象中对静态变量加1,发现值加1.
  • 派生类对象中对静态变量加1,发现值相对于刚创建时加了2.

也就是说,基类对象对静态变量的值加1,同样作用到了派生类对象中的静态变量上。

  • 它们两个对象中的静态变量不是独立的。

在基类对象和派生类对象中的静态变量是同一个变量

因为静态变量同样存放在数据段,基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例

🙀菱形继承

😸多继承

首先需要知道的是单继承:一个子类只有一个直接父类时称这个继承关系为单继承。

图
如上图,虽然最后的子类中有不止一个父类中的成员,但是每个子类都只有一个父类。

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

图
如上图,子类只有一个,但是父类有两个,这种就属于多继承。

  • 多继承中,一个子类可以有多个父类。

但是由于多继承的存在,就会引起菱形继承的问题。

图

  • Student继承自Person,Teacher也继承自Person。
  • Assistant继承自Student和Teacher。
class Person
{
public:
	string _name;//姓名
};

class Student : public Person
{
public:
	int _num;//学号
};

class Teacher : public Person
{
public:
	int _id;//工号
};

class Assistant : public Student, public Teacher
{
public:
	string _majorCourse;
};

在设计上,菱形继承完全是合理的,学生和老师属于人,所以继承人的属性,而助教既是老师也是学生,所以继承老师和学生的属性。

但是菱形继承会存在问题。

图
从上面的对象成员模型构造,可以看出菱形继承有数据冗余二义性的问题。在Assistant的对象中Person成员会有两份。

数据冗余:
图
可以看到,Assistant中有两个_name成员变量,一个是继承自Student的,一个是继承自Teaher的,但是都是继承自Person的。

  • 也就是相同的值存在了两份,实际上只有一个就够用了。

二义性:

图

如上图所示,在访问_name的时候,编译器也不知道你要访问的是Student作用域中的还是Teacher作用域中的,所以就造成了二义性。

二义性的解决办法之一:

图
可以通过指定作用域的方法来解决二义性的问题,如上图所示,但是并不符合实际情况,一个人虽然有多种角色,但是名字怎么会有两个甚至多个呢?

😸虚拟继承

虚拟继承就是专门用来解决菱形继承导致的数据冗余和二义性问题的。

图

  • 在腰部位置使用虚拟继承(virtual)。

哪里造成了菱形继承的问题就在哪里使用虚拟继承,毫无疑问,是在腰部位置造成的,所以腰部的两个派生类都使用虚拟继承。

下面本喵来给大家分析一下,虚拟继承是如果解决数据冗余和二义性问题的。

为了方便分析,我们创建几个简单的类:

class A
{
public:
	int _a;
};

class B : virtual public A
{
public:
	int _b;
};

class C : virtual public A
{
public:
	int _c;
};

class D : public B, public C
{
public:
	int _d;
};

TU
上面代码菱形继承的关系如上图所示。

数据冗余和二义性的解决:

首先我们来看,不使用虚拟继承时的内存模型:

图
在D对象创建后,通过内存窗口来看它内部的成员分别情况,如上图所示。

  • 最外边的蓝色框是整个d对象,它一共有5个int类型的变量。
  • 中间的红色框中的成员是从B中继承下来的,有两个int类型的变量。
  • 中间的律师框中的成员是从C中继承下来的,有两个int类型的变量。
  • 红色框和绿色框中橘色细框中的变量都是从A中继承下来的。

此时可以清除的看到,d对象中有两个_a变量,分布在B域和C域中。

再看使用菱形继承后的内存模型:

TU

  • 最外部的蓝色框是整个d对象,他一共有6个四字节的数据。
  • 中间的红色框中的成员是从B继承下来的,有2个四字节的数据。
  • 中间的绿色框中的成员是从C继承下来的,有2个四字节的数据。
  • 最下边的红色细框中的变量是从A继承下来的。

先不管多了什么东西,单看从A中继承下来的变量,发现只有一个了。从原理的两个变成了一个,解决了数据冗余的问题。

菱形继承中原本冗余的成员最后只有一个,而且放在最终派生类对象中的最后位置

此时数据冗余和二义性是解决了,因为派生类对象中只有一个从A继承下来的成员了,但是相比原来不用虚拟继承多出来4个字节不说,还将原本是成员所在位置变成了奇奇怪怪的东西。

图

  • B中黄色框中地址中的数据是一个地址,在新内存窗口中输入该地址,得到一个新的黄色框。
  • C中紫色框中地址中的数据是一个地址,在新内存窗口中输入该地址,得到一个新的紫色框。
  • 本喵用的机器是小端存储方式,按照小端模式得到d对象中存放的两个地址。

在两个新内存窗口中看到的两个新的框被称为虚基表

黄色虚基表:

图

  • 虚基表中,第一个int类型的数据存放的是0,具体什么意义在多态的时候再讲。
  • 虚基表中第二个int类型的数据存放的是0x14,它是一个偏移量

再看d对象的内存模型:

  • 从B继承下来的成员,起始地址是0x0055FA90。
  • 从A继承下来的成员,它的地址是0x0055FAA4。

这两个地址之间相差0x14,也就是20。

  • 当使用d.B::_a来访问A继承下来的成员时,就从B继承下来的成员的起始地址处,根据偏移量去访问具体的_a。

紫色虚基表:

图

  • 虚基表中第二个int类型的数据存放的是0x0c,同样是一个偏移量

再看d对象的内存模型:

  • 从C继承下来的成员,起始地址是0x0055FA98。
  • 从A继承下来的成员,它的地址是0x0055FAA4。

这两个地址之间相差0x0c,也就是12。

  • 当使用d.C::_a来访问A继承下来的成员时,就从C继承下来的成员的起始地址处,根据偏移量去访问具体的_a。

再看派生类对象的内存窗口:

图
B区域和A的偏移量是20,C区域和A的偏移量是12。

图
从汇编中也可看到,使用d.B::_a和d.C::_a步骤都比直接访问其他成员多,因为这两种方式虽然访问的都是一个地址,但是需要根据偏移量去计算。

图
由于使用了虚拟继承,所以B对象和C对象同样采用有虚基表的结构,将从A继承下来的成员放在最后,原本的位置存放对应虚基表的地址,虚基表中存放偏移量。

虚基表存在的原因:

现在有个疑问,为什么要根据偏移量来找从A中继承下来的那个成员?B对象C对象,或者是D对象,它们自己肯定会知道自己成员的位置啊。

图

  • B的指针拿到的是对象b的地址时,解引用访问_a,此时只是在自己内部寻找,不用偏移量也可以理解。
  • B的指针拿到的是对象d的地址时,此时会发生切片,但是d中的_a仍然会保留下来,但是此时站在B指针的角度来看,它根部不知道_a在哪里,因为这是d对象安排的。
  • 所以此时就需要通过虚基表获取_a距离B的偏移量来访问_a。

所以虚基表还是很有必要的。这部分只要了解就好。

🙀继承和组合

组合我们之前其实一直都在使用,就是在一个类中,它的成员变量是其他类。继承和组合都是一种类设计层面的复用方式

图
此时Student中同样有Person的成员变量,但是不是通过继承得到的,而是通过组合得到的。

  • public继承是一种is-a的关系。

图
如上图中,学生是人,所以Student和Person就用继承关系比较好。

  • 组合是一种has-a的关系。

图
如上图中,头上有眼睛,而不能说成头是眼睛,所以此时用组合更合适。

比较:

  • 继承方式:派生类和基类耦合度比较高。基类中的成员变量发生改变后,对派生类的影响较大,除去private成员外,其他成员的改变都会影响派生类。

  • 组合方式:最终的类和被组合的类耦合度比较低。被组合类中成员变量发生改变后,对最终类的影响较小,只有public成员改变,才会影响最终的类。

  • 继承方式中,派生类中可以看到基类的所有成员,只是基类的private成员不可以访问,所以被称为白箱复用
  • 组合方式中,最终类只能看到被组合类的public成员,其他成员和细节都看不到,也无法访问,所以被称为黑箱复用

结论: 在继承和组合都可以使用的情况下,尽量使用组合而不用继承。

🙀总结

很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。掌握好继承的各种特性,对于后面的多态非常有帮助。

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

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

相关文章

【3DoF算法】

VR 3DoF算法介绍 核心&#xff1a;3DoF算法应用场景&#xff0c;在VIO应用中&#xff0c;当只有测量没有观测的情况下&#xff0c;6DoF算法的预测会退化成一个只有测量的3DoF算法&#xff0c;这时候需要使用3DoF算法&#xff0c;来更加稳定准确的获取3DoF位姿&#xff0c;直到…

【VSCode】Windows 下搭建 Fortran 环境

文章目录Part.I 预备知识Part.II 安装与配置Chap.I 编译环境Chap.II 插件Part.III 测试Chap.I 一个示例Chap.II 注意事项Part.I 预备知识 Fortran 是一种比较古老的语言了&#xff0c;当时作为一种科学计算工具&#xff0c;还是比较火的&#xff0c;因为很多有名的软件都是基于…

LFM雷达实现及USRP验证【章节2:LFM雷达测距】

目录 1. 参数设计 几个重要的约束关系 仿真参数设计 2. matlab雷达测距代码 完整源码 代码分析 回顾&#xff1a;LFM的基本原理请详见第一章 本章节将介绍LFM雷达测距的原理及实现 1. 参数设计 几个重要的约束关系 带通采样定理&#xff1a; 因此如果我们B80MHz时&a…

SQL优化13连问,收藏好!

1.日常工作中&#xff0c;你是怎么优化SQL的&#xff1f; 大家可以从这几个维度回答这个问题&#xff1a; 分析慢查询日志 使用explain查看执行计划 索引优化 深分页优化 避免全表扫描 避免返回不必要的数据&#xff08;如select具体字段而不是select*&#xff09; 使用…

【Android -- 开发工具】Xshell 6 安装和使用教程

一、简介 Xshell 其实就是一个远程终端工具&#xff0c;它可以将你的个人电脑和你在远端的机器连接起来&#xff0c;通过向 Xshell 输入命令然后他通过网络将命令传送给远端Linux机器然后远端的Linux机器将其运行结果通过网络传回个人电脑。 二、Xshell 6 的安装 首先&#…

如何通过命令行查看CentOS版本信息和linux系统信息

1.如何查看已安装的CentOS版本信息&#xff1a; 1.cat /proc/version 2.uname -a 3.uname -r 4.cat /etc/centos-release 5.lsb_release -a 6.hostnamectl1. 第一种方式输出的结果是&#xff1a; Linux version 3.10.0-1127.el7.x86_64 (mockbuildkbuilder.bsys.centos.org) …

算法基础-回溯算法

回溯算法大致分为以下几类&#xff1a; 组合&#xff1a;组合、组合总和、电话号码的字母组合 分割&#xff1a;分割回文串、复原IP地址 子集&#xff1a;子集 排列&#xff1a;全排列 棋盘问题&#xff1a;N皇后、解数独 其他&#xff1a;递增子序列、重新安排行程 一、什么是…

gns3:动态路由(ospf) area0 骨干网络(域间)(ABR)+ ospf 连接 rip (外部)(ASBR)+ 区域划分

1.配置好接口ip 全部处于up状态2.配置好lookback口 增加一个虚拟直连网段全部为 255.255.255.0的子网掩码实现上边ospf之间通信r1的全局模式router ospf 1network 192.168.1.0 0.0.0.255 area 1network 1.1.1.0 0.0.0.255 area 1宣告直连 并且划分area 区域为1r2全局模式router…

一种LCD屏闪问题的调试

背景 项目使用ESP32-S3 RGB接口驱动的LCD, 框架 idf-v5.0, LVGL-v7.11 显示画面正常, 但肉眼可见的像是背光在闪烁, 背光电路是应用很久的经典电路, 且排查背光驱动无错, 但开机一段时间后, 闪烁会明显减轻 记录 这块屏的显示驱动芯片为ST7701S, 查看芯片手册有说明特定的上…

全网最完整,接口测试总结彻底打通接口自动化大门,看这篇就够了......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 所谓接口&#xff0…

音视频开发—MediaCodec 解码H264/H265码流视频

使用MediaCodec目的 MediaCodec是Android底层多媒体框架的一部分&#xff0c;通常与MediaExtractor、MediaMuxer、AudioTrack结合使用&#xff0c;可以编码H264、H265、AAC、3gp等常见的音视频格式 MediaCodec工作原理是处理输入数据以产生输出数据 MediaCodec工作流程 Med…

SpringBoot整合Flink(施耐德PLC物联网信息采集)

SpringBoot整合Flink&#xff08;施耐德PLC物联网信息采集&#xff09;Linux环境安装kafka前情&#xff1a;施耐德PLC设备&#xff08;TM200C16R&#xff09;设置好信息采集程序&#xff0c;连接局域网&#xff0c;SpringBoot订阅MQTT主题&#xff0c;消息转至kafka&#xff0c…

计算机网络体系结构——“计算机网络”

各位CSDN的uu们你们好呀&#xff0c;今天小雅兰来学习一个全新的知识点&#xff0c;就是计算机网络啦&#xff0c;下面&#xff0c;开始虚心学习。 计算机网络的概念 计算机网络的功能 计算机网络的组成 计算机网络的分类 标准化工作 计算机网络的性能 计算机网络的概念 …

Hadoop集群环境配置搭建

一、简单介绍 Hadoop最早诞生于Cutting于1998年左右开发的一个全文文本搜索引擎 Lucene&#xff0c;这个搜索引擎在2001年成为Apache基金会的一个子项目&#xff0c;也是 ElasticSearch等重要搜索引擎的底层基础。 项目官方&#xff1a;https://hadoop.apache.org/ 二、Linux环…

SpringBoot 结合RabbitMQ与Redis实现商品的并发下单【SpringBoot系列12】

SpringCloud 大型系列课程正在制作中&#xff0c;欢迎大家关注与提意见。 程序员每天的CV 与 板砖&#xff0c;也要知其所以然&#xff0c;本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发 1 项目准备 SpringBoot 整合 RabbitMQ 消息队…

【前端八股文】浏览器系列:性能优化——HTML、CSS、JS、渲染优化

文章目录HTMLCSSCSS加载会造成阻塞吗JavaScript渲染优化参考本系列目录&#xff1a;【前端八股文】目录总结 是以《代码随想录》八股文为主的笔记。详情参考在文末。 代码随想录的博客_CSDN博客-leecode题解,ACM题目讲解,代码随想录领域博主 性能优化&#xff0c;从以下几个方…

【C++】STL容器、算法的简单认识

几种模板首先认识一下函数模板、类模板、栈模板。函数模板函数模板就是一个模型&#xff0c;而模板函数是函数模板经过类型实例化的函数。如下template<class T>是一个简单的函数模板&#xff1a;template<class T> T Max(T a, T b) {return a > b ? a : b; } …

Joomla未授权访问漏洞CVE-2023-23752

1、前言Joomla是一套全球知名的内容管理系统&#xff08;CMS&#xff09;&#xff0c;其使用PHP语言加上MySQL数据库所开发&#xff0c;可以在Linux、Windows、MacOSX等各种不同的平台上运行。2月16日&#xff0c;Joomla官方发布安全公告&#xff0c;修复了Joomla! CMS中的一个…

cjson文件格式介绍

cjson是一种轻量级的JSON解析库&#xff0c;它支持将JSON格式的数据转换为C语言中的数据结构&#xff0c;同时也支持将C语言中的数据结构转换为JSON格式的数据。cjson的文件格式是指在使用cjson库时&#xff0c;将JSON格式的数据存储在文件中&#xff0c;然后通过cjson库读取文…

C++ 学习笔记(十)(继承、抽象篇)

前言&#xff1a;主要是自己学习过程的积累笔记&#xff0c;所以跳跃性比较强&#xff0c;建议先自学后拿来作为复习用。 文章目录1 定义父类和子类1.1 定义父类访问说明符 protected1.2 定义子类1.3 子类向父类的转换1.4 转换的例外1.5 子类的构造函数1.6 静态成员不能继承1.7…