C++练级计划->《多态》虚函数表,菱形继承多态
目录
什么是多态?
多态的条件
虚函数:
虚函数的重写:
协变
析构函数的重写
C++11 final 和 override
final:
override:
总结:
三重对比:重载重写重定义对比
抽象类
多态的原理
虚函数表
为什么只能是父类的指针或者引用来调用才能形成多态?
动态绑定和静态绑定
单继承的虚函数表
多继承的虚函数表
菱形继承和菱形虚拟函数继承
什么是多态?
现实中买票的测策略:学生 社会人 军人都是要买票的,但是不同的人买票买到的价格却可能是不一样的,学生票,普通票,军人票。虽然我们都执行了买票的操作,但是我们的操作行为是不一样的。
多态其实是一种语法特性,只有你使用时他才会出现,不使用时,这个特性不会出现如下是多态形成的条件
多态的条件
1.必须是基类的指针或者引用来调用虚函数。
当然(如果使用派生类对象直接访问,那打印出来的就是派生类函数的内容,如果是用基类的指针或者引用进行访问,会根据这个基类的赋值对象的类进行访问)括号内是继承的内容,多态特性要形成的条件是1.用基类的指针或者引用来调用虚函数;
#include <iostream>
using namespace std; class Base {
public: void show() { // 非虚函数 cout << "Base class show function" << endl; } virtual void virtualShow() { // 虚函数 cout << "Base class virtual show function" << endl; }
}; class Derived : public Base {
public: void show() { // 隐藏了Base::show,而不是重写 cout << "Derived class show function (hiding)" << endl; } void virtualShow() override { // 重写了Base::virtualShow cout << "Derived class virtual show function" << endl; }
}; int main() { Derived derivedObj; derivedObj.show(); // 输出 "Derived class show function (hiding)",因为调用的是Derived::show derivedObj.virtualShow(); // 输出 "Derived class virtual show function",因为调用的是重写的虚函数 Base* basePtr = &derivedObj; basePtr->show(); // 输出 "Base class show function",因为调用的是Base::show(隐藏不影响基类指针调用) basePtr->virtualShow(); // 输出 "Derived class virtual show function",因为调用的是重写的虚函数 return 0;
}
如上代码我们直接用派生类对象调用虚函数打印出来的还是派生类的内容,但是我们用基类的指针指向派生类,此时调用虚函数打印出来的依然是派生类的内容。
2.形成条件2:被调用的函数必须是虚函数,并且派生类要对虚函数进行重写。
综上形成多态的条件有两个:
1.必须是基类的指针或者引用来调用虚函数。
2:被调用的函数必须是虚函数,并且派生类要对虚函数进行重写。
虚函数:
它的格式如下:
class Person
{
public://被virtual修饰的类成员函数virtual void BuyTicket(){cout << "买票-全价" << endl;}
};
只要在函数前面加上一个virtual就可以了。
注意:
1.只要在你觉得是虚函数的函数前面加virtual,其他只作用于本类的函数不要加(因为虚函数是强制要求重写的)。
2.静态函数不能是虚函数。因为静态函数全部类中只能有一份,就算继承下去的也是那一份。如果给其加上虚函数,那就要求重写一份,此时这个重写的静态函数和原本的静态函数就冲突了。
3.虚函数的virtual和虚继承的virtual都是虚的,二者的用法是不同的,虚函数用于多态,虚继承则是为了解决菱形继承时的数据的二义性和冗余的。
虚函数的重写:
虚函数的重写也叫虚函数的覆盖,虚函数的重写就是把继承下来的虚函数里的内容(定义)重新写一遍,将原来替换下来的函数给替换。
//父类
class Person
{
public://父类的虚函数virtual void BuyTicket(){cout << "买票-全价" << endl;}
};
//子类
class Student : public Person
{
public://子类的虚函数重写了父类的虚函数virtual void BuyTicket(){cout << "买票-半价" << endl;}
};
//子类
class Soldier : public Person
{
public://子类的虚函数重写了父类的虚函数virtual void BuyTicket(){cout << "优先-买票" << endl;}
};
如上代码:三者的虚函数除了内容(定义不同)其他都是相同的。
此时我们在使用基类的指针或者引用去调用三者就形成了多态
void Func(Person& p)
{//通过父类的引用调用虚函数p.BuyTicket();
}
void Func(Person* p)
{//通过父类的指针调用虚函数p->BuyTicket();
}
int main()
{Person p; //普通人Student st; //学生Soldier sd; //军人Func(p); //买票-全价Func(st); //买票-半价Func(sd); //优先买票Func(&p); //买票-全价Func(&st); //买票-半价Func(&sd); //优先买票return 0;
}
注意:在派生类中重写虚函数时是可以不用加virtual的,因为它已经把基类的虚的特性继承下来了,但是建议还是加上virtual,便于其他程序员观看。
协变
协变就是在虚函数的层次下,基类和派生类的返回值不同,因为可能会有这样的需求,要求基类的这个虚函数返回的是基类的对象指针或者引用,或者派生类返回派生类的对象的指针或者引用。
//基类
class A
{};
//子类
class B : public A
{};
//基类
class Person
{
public://返回基类A的指针virtual A* fun(){cout << "A* Person::f()" << endl;return new A;}
};
//子类
class Student : public Person
{
public://返回子类B的指针virtual B* fun(){cout << "B* Student::f()" << endl;return new B;}
};
在我们重写协变函数发现不同指向的指针调用函数,调用的依然是对应的虚函数,说明虚函数重写是成功的。
int main()
{Person p;Student st;//父类指针指向父类对象Person* ptr1 = &p;//父类指针指向子类对象Person* ptr2 = &st;//父类指针ptr1指向的p是父类对象,调用父类的虚函数ptr1->fun(); //A* Person::f()//父类指针ptr2指向的st是子类对象,调用子类的虚函数ptr2->fun(); //B* Student::f()return 0;
}
析构函数的重写
析构函数如果你自己定义了,那不管有没有加virtual都是虚函数(尽管名字不同),为什么呢?
//父类
class Person
{
public:virtual ~Person(){cout << "~Person()" << endl;}
};
//子类
class Student : public Person
{
public:virtual ~Student(){cout << "~Student()" << endl;}
};
上面代码写了析构函数,试想这样的场景,我们用基类的指针指向new出来的基类对象和派生类对象,此时如果没有虚函数的定义,delete时这个基类的指针应该只会调用基类的析构函数,而对于派生类而言我们继承的定义是:先调用基类的析构函数后调用派生类的析构函数。所以析构函数默认就是虚函数
int main()
{//分别new一个父类对象和子类对象,并均用父类指针指向它们Person* p1 = new Person;Person* p2 = new Student;//使用delete调用析构函数并释放对象空间delete p1;delete p2;return 0;
}
其实因为子类的析构函数是先调用父类的析构后调用自己的析构,已经是多态行为了,父类的指针按理来说调用的就是父类的析构函数,这里的多态就是把子类的析构函数重写成了先调用父类的析构函数后再去调用子类的析构函数。
注意:为什么子类和父类的析构函数构成重写,虽然表面写的时候析构函数的名字是不同的,但是在编译后,析构函数都会被统一命名为destructor();这样就形成了相同的函数。构成虚函数重写
C++11 final 和 override
final:
final:修饰虚函数,表示这个虚函数不能再被重写。
//父类
class Person
{
public://被final修饰,该虚函数不能再被重写virtual void BuyTicket() final{cout << "买票-全价" << endl;}
};
//子类
class Student : public Person
{
public://重写,编译报错virtual void BuyTicket(){cout << "买票-半价" << endl;}
};
//子类
class Soldier : public Person
{
public://重写,编译报错virtual void BuyTicket(){cout << "优先-买票" << endl;}
};
上述代码用final修饰person的函数,此时如果后面依然有虚函数重写就会导致编译报错。
override:
override:检查派生类是否重写了虚函数,如果没有重写则报错
//父类
class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};
//子类
class Student : public Person
{
public://子类完成了父类虚函数的重写,编译通过virtual void BuyTicket() override{cout << "买票-半价" << endl;}
};
//子类
class Soldier : public Person
{
public://子类没有完成了父类虚函数的重写,编译报错virtual void BuyTicket(int i) override{cout << "优先-买票" << endl;}
};
如上代码 我们在子类的函数上加上override,这时override就会去基类判断子类是否重写对了。如果没有发现基类中有相同函数则编译报错(必须是虚函数)
总结:
override和final的优点就是,写虚函数时要求很高,要求函数名称参数等等全都相同,这就导致了程序员在写虚函数时如果犯错,这时就发现不了因为可能被认作是一个新的函数,所以这两个关键词可以用来检查虚函数,final可以用来判断派生类有没有同名函数,override可以用来解决虚函数写错的问题
三重对比:重载重写重定义对比
重载很好理解:就是同一作用域的同名函数构成
重写和重定义的区别是:
重写要求是虚函数,函数名称参数返回值等等都是一样的只是定义不一样。
重定义要求只要函数名相同,且不是虚函数,重定义类似于基类派生类间的重载。
抽象类
先说说抽象:我们现实生活中泛指一类东西时,比如我是人,你是人,他是人,人就是抽象的;
抽象类要求在虚函数后面加上=0,表示这个函数为纯虚函数,然后只有一个类中出现了一个纯虚函数就可以叫做抽象类。抽象类不能实例化出对象,就像人是一个泛指,不能准确的指出是谁。
#include <iostream>
using namespace std;
//抽象类(接口类)
class Car
{
public://纯虚函数virtual void Drive() = 0;
};
int main()
{Car c; //抽象类不能实例化出对象,errorreturn 0;
}
子类继承了父类,这时父类是抽象类,那子类继承下来原本按理来说也是抽象类,但是只要把纯虚函数重写成普通函数,这时就子类就不抽象了(纯虚函数被改了)就可以实例化了。
#include <iostream>
using namespace std;
//抽象类(接口类)
class Car
{
public://纯虚函数virtual void Drive() = 0;
};
//派生类
class Benz : public Car
{
public://重写纯虚函数virtual void Drive(){cout << "Benz-舒适" << endl;}
};
//派生类
class BMV : public Car
{
public://重写纯虚函数virtual void Drive(){cout << "BMV-操控" << endl;}
};
int main()
{//派生类重写了纯虚函数,可以实例化出对象Benz b1;BMV b2;//不同对象用基类指针调用Drive函数,完成不同的行为Car* p1 = &b1;Car* p2 = &b2;p1->Drive(); //Benz-舒适p2->Drive(); //BMV-操控return 0;
}
所以说抽象类存在的意义是什么?它也可以直接用虚函数不用纯虚函数也能完成
1.既然说了人是抽象的那现实生活中抽象的东西也很多,这时也希望在编程时能有抽象这一概念,虽然没有具体的实例对象,但是有存在这个名词。所以可以更好的用来表示现实世界
2.抽象类也可以变相的要求派生类强制子类重写虚函数,因为不重写纯虚函数子类也没办法实例化出对象。
多态的原理
虚函数表
Base类实例化出对象的大小是多少?(笔试题)
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};
我们可以直接实例化出Base对象,然后sizeofBase对象,发现对象有8个字节
我们的_b成员是4字节,那剩下的4字节是哪里来的?
这里就能引出我们的主题:虚函数表(Virtual Function Table)也可以叫做虚表
虚函数表和虚基表的区别:虚函数表是因为虚函数出现的(多态),而虚基表则是因为菱形继承为了防止二义性和冗杂的(继承)
这里先讨论虚函数表:只有虚函数出现时这个表(指针)才会出现。
看下面代码我们Base中有三个函数
#include <iostream>
using namespace std;
//父类
class Base
{
public://虚函数virtual void Func1(){cout << "Base::Func1()" << endl;}//虚函数virtual void Func2(){cout << "Base::Func2()" << endl;}//普通成员函数void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};
//子类
class Derive : public Base
{
public://重写虚函数Func1virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};
int main()
{Base b;Derive d;return 0;
}
在Derive类中重写了Func1。此时观察虚函数表中存储了什么?
1.可以发现只有虚函数才能进入 虚函数表中 func3没有进入。虚函数也是在代码区的
2.在d对象中func1被重写为Derive类的func1.这就是重写(覆盖)原来的func1的地址重写为了派生类的func1
3.对于func3而言他是普通函数,它当然也被继承了,它的位置是在代码区的(这个类的函数都放在一个统一的代码区,不同对象调用时用的也是同一份代码,就不会冗余)
4.如果派生类有自己的虚函数而不是继承下来的,这时派生类的虚函数位置应该在继承下来的虚函数的下面。
虚函数表的初始化时间?虚函数存在哪里?虚表存在哪里?
1.虚表在构造函数初始化阶段进行初始化的,虚表存的是虚函数的地址,而不是虚函数,
2.虚函数和普通函数一样,都是存在代码区中的,只是他的地址也存到了虚函数表中(为了重写,调用时就知道调用的是哪一个函数)
3.虚表也是存在于代码区的
现在回想:我们构成多态的两个条件:1.必须有虚函数2.只能调用父类的指针或者引用来调用对象
为什么只能是父类的指针或者引用来调用才能形成多态?
看上图,我们有两个对象都是person类继承下来的,Mike是成人,johnson是学生
二者都有继承的或者重写buyticket函数 ,所以用person指针调用二者的butticket函数结果是不同。
这样就是多态了,同一个行为不同的形态。
Person* p1 = &Mike;
Person* p2 = &Johnson;
大家还记得切片吗?父类的指针或者引用调用子类对象,是一种切片行为
可以调用父类中有的成员 ,而父类中没有的成员 则会被切掉,不能调用。
Person p1 = Mike;
Person p2 = Johnson;
如果直接是用子类构造父类对象, 父类对象它依然是父类对象,因为在构造时首先会构造一个临时对象,这个临时对象因为是person类,所以他去调用了person的构造函数,所以指向的依然是父类的虚表,只是内置类型构造用的是子类的内容。
总结:
1.如果构成多态,是一个指针或者引用,指向的是什么类型的对象调用什么虚函数,和对象有关
2.如果不构成多态,是一个父类对象,这时父类对象依旧是父类所以虚函数表也是父类的,但是成员的构造是复制的子类的成员
动态绑定和静态绑定
静态绑定: 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也成为静态多态,比如:函数重载。
动态绑定: 动态绑定又称为后期绑定(晚绑定),在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。
观察如下代码:
//父类
class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};
//子类
class Student : public Person
{
public:virtual void BuyTicket(){cout << "买票-半价" << endl;}
};
int main()
{Student Johnson;Person p = Johnson; //不构成多态p.BuyTicket();return 0;
}
这串代码我们没用引用或者指针构造p,所以不构成多态。然后我们查看汇编代码
可以发现此时调用buyticket时是直接找到函数位置并进行调用的。
而如果我们用多态的方式调用buyticket
int main()
{Student Johnson;Person& p = Johnson; //构成多态p.BuyTicket();return 0;
}
然后查看汇编码
会发现有八条汇编指令大概意思就是我们对象要先去虚函数表中,然后通过虚函数表找到对应的虚函数,然后在进行调用。在使用时才去寻找。
对比:
可以发现1.静态绑定是直接找到函数,而动态绑定是先找到虚函数表的位置,然后在虚函数表中找到对应函数的位置。
2.在多态的两条件没够成时,函数调用是不会根据虚函数表的,而是直接调用类中函数(静态绑定)。
3.而多态构成时,才会去找虚函数表,因为只有多态构成时才有可能出现重定义,所以要根据虚函数表来找到要调用的函数(动态绑定)
单继承的虚函数表
//基类
class Base
{
public:virtual void func1() { cout << "Base::func1()" << endl; }virtual void func2() { cout << "Base::func2()" << endl; }
private:int _a;
};
//派生类
class Derive : public Base
{
public:virtual void func1() { cout << "Derive::func1()" << endl; }virtual void func3() { cout << "Derive::func3()" << endl; }virtual void func4() { cout << "Derive::func4()" << endl; }
private:int _b;
};
根据上图发现:只有重写的函数在虚函数表中被覆盖,其他都是正常继承的,然后自己类中多出来的虚函数(普通函数不进入)放在继承的下面 。
注意:基类的虚函数和派生类的虚函数都是生成了的,覆盖的意义就是把原本基类虚函数的地址改为派生类虚函数的地址
过程:
1.派生类先把基类的表继承下来,所以现在都是基类的虚函数。
2.把重写基类的虚函数在表中进行覆盖,此时原本被继承下来的虚函数的地址改为了派生类虚函数的地址。
3.派生类自己有的虚函数放在后面。
注意:在部分编译器中派生类自己的新增的虚函数可能不会在监视窗口中显示。所以此时可以使用内存级的监视窗口查看,可以找到在虚函数表中是有对应的函数地址的。 当然你也可以使用打印的方式
typedef void(*VFPTR)(); //虚函数指针类型重命名
//打印虚表地址及其内容
void PrintVFT(VFPTR* ptr)
{printf("虚表地址:%p\n", ptr);for (int i = 0; ptr[i] != nullptr; i++){printf("ptr[%d]:%p-->", i, ptr[i]); //打印虚表当中的虚函数地址ptr[i](); //使用虚函数地址调用虚函数}printf("\n");
}
int main()
{Base b;PrintVFT((VFPTR*)(*(int*)&b)); //打印基类对象b的虚表地址及其内容Derive d;PrintVFT((VFPTR*)(*(int*)&d)); //打印派生类对象d的虚表地址及其内容return 0;
}
这个方法首先就是定义了重命名了一个虚函数指针(4字节),然后通过for循环每次打印一个指针大小的内容。
多继承的虚函数表
多继承就是一个类继承了不止一个类。
//基类1
class Base1
{
public:virtual void func1() { cout << "Base1::func1()" << endl; }virtual void func2() { cout << "Base1::func2()" << endl; }
private:int _b1;
};
//基类2
class Base2
{
public:virtual void func1() { cout << "Base2::func1()" << endl; }virtual void func2() { cout << "Base2::func2()" << endl; }
private:int _b2;
};
//多继承派生类
class Derive : public Base1, public Base2
{
public:virtual void func1() { cout << "Derive::func1()" << endl; }virtual void func3() { cout << "Derive::func3()" << endl; }
private:int _d1;
};
其实很好理解:如果继承了一个类会出现一个虚函数表那继承了两个类那就生成两个虚函数表就好了。
注意:虚函数表是一个地址,这个地址下存着虚函数的指针(可以想成存着指针的数组)
由上图就可以知道,虽然两个base中都有func1和func2,但是因为有不同的虚函数表所以覆盖的原理还是一样的。只是在于派生类自己多出来的虚函数,此时是存在第一张虚函数表中的而不是两表都存。
当然有些编译器也会出现上述问题监视窗口不显示派生类新增的虚函数,此时依然可以用上面的两个方法,只是在定位时要注意找第二个表时要+sizeof(base1)跳过base1的内容找到base2.
菱形继承和菱形虚拟函数继承
class A
{
public:virtual void funcA(){cout << "A::funcA()" << endl;}
private:int _a;
};
class B : virtual public A
{
public:virtual void funcA(){cout << "B::funcA()" << endl;}virtual void funcB(){cout << "B::funcB()" << endl;}
private:int _b;
};
class C : virtual public A
{
public:virtual void funcA(){cout << "C::funcA()" << endl;}virtual void funcC(){cout << "C::funcC()" << endl;}
private:int _c;
};
class D : public B, public C
{
public:virtual void funcA(){cout << "D::funcA()" << endl;}virtual void funcD(){cout << "D::funcD()" << endl;}
private:int _d;
};
菱形继承就是两个父类同时继承了同一个类,此时派生类同时继承了这两个父类。
A类对象的成员分布:
B类对象的成员分布:
C类对象的成员分布:
D类对象的成员分布:
虚基表:菱形继承防止二义性和数据冗杂的。
虚表:多态时用于存储虚函数的。
先说菱形继承:因为菱形继承两个父类是继承于同一个类BC类中都有A对象,此时如果直接继承在D中不就出现了两份A类对象吗?为了防止这样的数据冗余和二义性,所以干脆就只存一个A类对象,所以此时在D类对象中BC类要找到A对象所以就出现了虚基表(里面存的是BC类对象找到A类对象的距离)。
菱形虚函数继承:因为A类对象在D中单独一份了,所以D对funcA的重写是单独的。所以BC中不存在A的虚函数了,对于BC中的内容实际和多继承相同。最后多了两个虚基表用于让BC找到A对象。
注意:
正常来说不要设计出菱形虚拟继承和菱形继承,语法太复杂容易出错,同时在虚继承下由于多态在调用函数时是有多余的损耗的。
相关文章:

C++练级计划->《多态》虚函数表,菱形继承多态
目录 什么是多态? 多态的条件 虚函数: 虚函数的重写: 协变 析构函数的重写 C11 final 和 override final: override: 总结: 三重对比:重载重写重定义对比 抽象类 多态的原理 虚函数…...
OkHttp3 - 2. OkHttp的核心组件与架构
1 OkHttp的工作原理 OkHttp3 的核心设计遵循以下原则: 请求与响应的分离:通过 Request 和 Response 对象解耦请求构建与结果处理。异步与同步支持:使用 Call 对象管理请求,可以同步或异步执行。高效连接复用:通过连接…...
异或操作解决一些问题
前提: 异或操作符合交换律,结合律(因为其根本上来抽象理解,就是查看所有项二进制数相同位是否有奇数个1,对运算结果二进制数而言,没有该位为0,有该位为1,与顺序无关)。 …...

操作系统之输入输出
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…...
Centos 安装 Node.js 和 npm
方法2:使用 NVM(Node Version Manager)安装 安装 NVM curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash 重新加载配置 source ~/.bashrc 安装最新的 LTS 版本的 Node.js nvm install --lts 验证安装…...

C语言——指针初阶(一)
目录 一.什么是指针??? 指针是什么? 指针变量: 总结: 总结: 二.指针和指针类型 指针-整数: 总结: 指针的解引用 总结: 三.野指针 如何规避野指针 往期…...
React Native 原生开发指南
写在前面 React Native (RN) 是一个用于构建跨平台移动应用的框架。它允许开发者使用 JavaScript 和 React 来编写应用程序,并将其转换为原生代码。虽然 RN 提供了许多内置的组件和 API,但有时候你可能需要访问原生平台的特定功能或性能优化。为此&…...

【前端】JavaScript中的柯里化(Currying)详解及实现
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 💯前言💯什么是柯里化?💯柯里化的特点💯柯里化的简单示例💯通用的柯里化实现💯柯里化让代码更易读的原因💯…...
解决 docker 部署 vsftpd 速度慢问题
解决 docker 部署 vsftpd 速度慢问题 Docker 部署 ftp version: 3.8services:ftps:image: fauria/vsftpdcontainer_name: my-ftpsenvironment:- FTP_USERyourusername- FTP_PASSyourpassword- PASV_ADDRESS192.168.0.123 # 使用环境变量或直接指定IP地址- PASV_MIN_PORT4900…...
Java基础夯实——2.9 多线程如何共享数据
在 Java 多线程编程中,共享数据通过以下几种方式实现: 1. 使用共享对象 多个线程可以通过引用同一个对象来实现数据共享。例如: class SharedData {private int count;public synchronized void increment() {count;}public synchronized …...
【Leetcode Top 100】234. 回文链表
问题背景 给你一个单链表的头节点 h e a d head head,请你判断该链表是否为 回文链表(回文 序列是向前和向后读都相同的序列)。如果是,返回 t r u e true true;否则,返回 f a l s e false false。 数据…...

GitLab指定用户分配合并权限
进入项目 -》 Project Settings Repository -》展开 Protected branches -》 添加要保护的分支,设置角色 管理用户角色权限 查看到不同用户的角色,一般设置Developer只有Merger Request权限,Maintainer还有Merge审批权限 GitLab 中的权限…...

五,[GXYCTF2019]Ping Ping Ping1
进入靶场,有提示 我们在url试着输入本地IP,返回了ping命令 既然要在url处传参,那就用postman,再输入ip127.0.0.1 & ls,试着列出目录内容 ok,好像是个脏话,它过滤了空格 试着穿越又看到了脏话࿰…...
基于STM32的智能无人机自主飞行与目标识别系统设计
目录 引言系统需求分析 2.1 功能需求 2.2 硬件需求 2.3 软件需求系统设计 3.1 总体架构 3.2 各模块设计系统实现 4.1 硬件实现 4.2 软件实现系统调试与优化总结与展望 1. 引言 随着无人机技术的快速发展,无人机在军事侦察、环境监测、物流配送等领域的应用逐渐增多…...

C 语言数组与函数:核心要点深度剖析与高效编程秘籍
我的个人主页 我的专栏:C语言,希望能帮助到大家!!!点赞❤ 收藏❤ 目录 引言数组基础 2.1 数组的定义与初始化 2.2 一维数组的基本操作 2.3 二维数组及其应用 2.4 数组与指针的关系函数基础 3.1 函数的定义与调用 3.2…...

汽车轮毂结构分析有哪些?国产3D仿真分析实现静力学+模态分析
本文为CAD芯智库原创,未经允许请勿复制、转载! 之前分享了如何通过国产三维CAD软件如何实现「汽车/汽配行业产品设计」,兼容NX(UG)、Creo(Proe),轻松降低企业上下游图纸交互成本等。…...

解决jupyter notebook 新建或打开.ipynb 报500 : Internal Server Error(涉及jinja2兼容性问题)
报错: [E 10:09:52.362 NotebookApp] 500 GET /notebooks/Untitled16.ipynb?kernel_namepyt hon3 (::1) 93.000000ms refererhttp://localhost:8888/tree ...... 重点是: from .exporters import * File "C:\ProgramData\Anaconda3\lib\site-p…...

【若依ruoyi Vue前端线上个人服务器部署】以及常见报错问题解决
提示:【若依ruoyi Vue前端线上个人服务器部署】以及常见报错问题解决 文章目录 前言一、若依ruoyi Vue前端部署常见两种错误1、404问题2、找不到….模块 二、使用步骤(正式开始)1.修改vue.config.js中的publicPath属性。2.修改router/index.j…...

Python学习第十天--处理CSV文件和JSON数据
CSV:简化的电子表格,被保存为纯文本文件 JSON:是一种数据交换格式,易于人阅读和编写,同时也易于机器解析和生成,以JavaScript源代码的形式将信息保存在纯文本文件中 一、csv模块 CSV文件中的每行代表电…...

python基础(一)
python语言特点 解释型语言代码执行过程中通过解释器将代码转换为机器语言,并立即执行;编译型语言执行前需要经过编译整个代码文件为机器语言的可执行文件,然后执行能找出大部分错误错误处理解释型语言在运行时发现错误,编译型语…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...