成都市网站建设/买域名要多少钱一个
送给大家一句话:
一个犹豫不决的灵魂,奋起抗击无穷的忧患,而内心又矛盾重重,真实生活就是如此。 – 詹姆斯・乔伊斯 《尤利西斯》
_φ(* ̄ω ̄)ノ_φ(* ̄ω ̄)ノ_φ(* ̄ω ̄)ノ
_φ(* ̄ω ̄)ノ_φ(* ̄ω ̄)ノ_φ(* ̄ω ̄)ノ
_φ(* ̄ω ̄)ノ_φ(* ̄ω ̄)ノ_φ(* ̄ω ̄)ノ
从零开始认识多态
- 1 前言
- 2 什么是多态
- 3 多态的构成
- 3.1 协变
- 3.2 析构函数的重写
- 3.3 语法细节
- 3.4 C++11 override 和 final
- 3.5 重写(覆盖) - 重载 - 重定义(隐藏)
- 4 多态的底层实现
- 4.1 底层实现
- 4.2 验证虚表
- 5 抽象类
- 6 多继承中的多态
- 一般的多继承
- 菱形继承和菱形虚拟继承
- Thanks♪(・ω・)ノ谢谢阅读!!!
- 下一篇文章见!!!
1 前言
面向对象技术(oop)的核心思想就是封装,继承和多态。通过之前的学习,我们了解了什么是封装,什么是继承。
封装就是对将一些属性装载到一个类对象中,不受外界的影响,比如:洗衣机就是对洗衣服功能,甩干功能,漂洗功能等的封装,其功能不会受到外界的微波炉影响。
继承就是可以将类对象进行继承,派生类会继承基类的功能与属性,类似父与子的关系。比如水果和苹果,苹果就有水果的特性。
接下来我们就来了解学习多态!
2 什么是多态
多态是面向对象技术(OOP)的核心思想,我们把具有继承关系的多个类型称为多态类型,通俗来讲:就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
举个例子:就拿刚刚结束的五一假期买票热为例,买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。同样一个行为在不同的对象上就有不同的显现。
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。
#include<iostream>using namespace std;class Person
{
public:virtual void BuyTicket() { cout << "买票->全价" << endl; }
};class Student : public Person
{
public:virtual void BuyTicket() { cout << "买票->半价" << endl; }
};void Func(Person& p)
{p.BuyTicket();
}int main()
{Person p;Student s;//同一个函数对不同对象有不同效果Func(p);Func(s);return 0;
}
比如Student继承了Person。Person对象买票全价,Student对象买票半价。我们运行看看:
- 多态调用:运行时,到指定对象的虚表中找虚函数来调用(指向基类调用基类的虚函数,指向子类调用子类的虚函数)
- 普通调用:编译时,调用对象是哪个类型,就调用它的函数。
乍一看还挺复杂,接下来我们就来了解多态的构成。
3 多态的构成
继承的情况下才有虚函数,才有多态!!!
多态构成的条件:
- 必须通过基类的指针或者引用调用虚函数(virtual修饰的类成员函数)
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写(父子虚函数要求三同)
- 虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数
看起来很是简单,当时其实有很多的坑!!!一不小心就会掉进去。
3.1 协变
上面我们说了多态的条件:父子虚函数要求三同。但是却有这样一个特殊情况:协变!
协变:派生类重写基类虚函数时,与基类虚函数返回值类型不同:
- 基类虚函数返回基类对象的指针或者引用
- 派生类虚函数返回派生类对象的指针或者引用
这样的情况称为协变。
#include<iostream>using namespace std;class A {};
class B : public A {};
//这里明显返回类型不同但是结构仍然正常
class Person
{
public:virtual A* BuyTicket() { cout << "买票->全价" << endl; return nullptr; }
};class Student : public Person
{
public:virtual B* BuyTicket() { cout << "买票->半价" << endl; return nullptr; }
};
很明显派生类与基类的返回值不同(注意一定是:基类返回“基类”,派生类返回“派生类”):
但是结果确实正常的,依然构成多态,这样的情况就称为协变!!!
3.2 析构函数的重写
析构函数在编译阶段都会转换成:destructor()
,所以表面析构函数名字不同,但是实质上是一致的。这样就会构成多态。
来看正常情况下的析构:
#include<iostream>
using namespace std;class Person
{
public:~Person() { cout << "~Person()" << endl; }
};class Student : public Person
{
public:~Student() { cout << "~Student()" << endl; }
};
int main()
{Person p;Student s;return 0;
}
这样会正常的调用析构函数(子类析构会自动调用父类析构->先子后父):
再来看:
int main()
{//Person p;//Student s;//基类可以指向基类 也可以指向派生类的基类部分Person* p1 = new Person ;//通过切片来指向对应内容Person* p2 = new Student;delete p1;delete p2;return 0;
}
如果是这样呢?
这样调用的析构不对啊!Student对象没有调用自身的析构函数,而是调用Person的,为什么会出现这样的现象呢???
这样就可能会引起一个十分严重的问题:内存泄漏
#include<iostream>using namespace std;class Person
{
public:~Person() { cout << "~Person()" << endl; }
};
class Student : public Person
{
public:Student() { int* a = new int[100000000]; }~Student() { cout << "~Student()" << endl; }
};
int main()
{for(int i = 0; i< 100000 ; i++){Person* p2 = new Student;delete p2;}return 0;
}
如果我们在Student中申请一个空间,而析构的时候却不能调用其析构函数俩把申请的空间free这样就导致了内存泄漏!!!
这就十分危险了!!!
而我们希望的是指向谁就调用谁的析构:指向基类调用基类析构,指向派生类调用派生类析构。
那我们怎么做到呢 ----> 当然就是多态了!!!
那我们来看看现在满不满足多态的条件:
- 必须通过基类的指针或者引用调用虚函数(virtual修饰的类成员函数)
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写(父子虚函数要求三同)
在编译的时候,析构函数都会变成destructor
,这样满足三同!构成重写
那么我们就只需要将析构函数变为虚函数就可以了:
class Person
{
public:virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person
{
public:virtual ~Student() { cout << "~Student()" << endl; }
};
来运行看看:
老铁 OK了!!!应该释放的空间全都释放了!!!
所以建议析构函数设置为虚函数,避免出现上述的情况。
3.3 语法细节
- 派生类(基类必须写)的对应函数可以不写
virtual
(这个语法点非常奇怪!建议写上virtual
) - “重写”的本质是重新写函数的实现,函数声明(包括缺省参数的值)与基类一致
来看一道面试题:
以下程序输出结果是什么()
#include<iostream>using namespace std;class A
{
public:virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }virtual void test() { func(); }
};
class B : public A
{
public:void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};
int main(int argc, char* argv[])
{B* p = new B;p->test();return 0;
}
A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确
答案是:B
为什么呢?
- 首先:
- A类与B类构成继承关系
- func函数是虚函数(B类是派生类,可以不写virtual),并且AB 中满足三同。构成多态。
- test函数的参数是基类指针(
A* this
成员函数的默认参数),满足多态条件。
- 然后:
- 主函数中调用test函数,因为B是子类,没有test函数,所以会在父类A中寻找。
- test函数调用 func函数,参数this指向的是B类(指向谁调用谁),所以就会调用B类的func函数
B->
- 重写的本质是对函数的实现进行重写,函数的结构部分(包括参数,缺省值,函数名,返回值等)与基类一致。所以是
1
所以就可以判断是B选项。
当然实际中不能这么写代码奥!!!会有生命危险(Doge)
3.4 C++11 override 和 final
从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。
- final:
- 修饰类(最终类),表示该类不能被继承。(C++98直接粗暴使用private来做到不能继承)
class car final { };
- 修饰虚函数,表示该虚函数不能再被继承。
virtual void func() final { }
- 修饰类(最终类),表示该类不能被继承。(C++98直接粗暴使用private来做到不能继承)
- override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Car {
public:virtual void Drive() {}
};class Benz :public Car {
public:virtual void Drive() override { cout << "Benz-舒适" << endl; }
};
3.5 重写(覆盖) - 重载 - 重定义(隐藏)
我们来区分一下这三个类似的概念:
- 重载 :
- 两个函数作用在同一作用域
- 函数名相同,参数不同
- 重写(覆盖):
- 两个函数分别在基类作用域好派生类作用域
- 函数名、参数、返回值都一样(协变例外)
- 两个函数必须是虚函数!
- 重定义:
- 两个函数分别在基类作用域好派生类作用域
- 仅仅函数名相同
- 两个基类和派生类的同名函数不是重写就是重定义
重定义包含重写!!!
4 多态的底层实现
4.1 底层实现
首先我们来看一下具有多态属性的类的大小:
#include<iostream>
using namespace std;class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;char _ch = 'x';
};int main(int argc, char* argv[])
{cout << sizeof(Base) << endl;return 0;
}
Base的大小在x86环境下是12字节。这十二字节是怎么组成的呢?
首先类里面有一个虚函数表指针_vfptr
:
只要有虚函数就会有虚表指针,这个是实现多态的关键!!!
我们来探索一下:
通过VS的调试,我们可以发现:
那么如何实现传基类调用基类的虚函数,传派生类调用派生类的虚函数?
当然是使用切片了!
1. 首先每个实例化的类(如果有虚函数)会有一个虚函数表。
2. 传基类调用基类的虚函数,就正常在基类虚表中寻找其对应函数
3. 传派生类,因为多态函数时基类的指针,那么就会切片出来一个基类(虚函数表是派生类的),那么就会在派生类虚表调用对应虚函数。
这样就实现了执行谁就调用谁!!!
运行过程中去虚表中找对应的虚函数调用。具体的汇编语言实现还是比较直白的。
注意同类型的虚表是一样的!!!
- 满足多态,那么运行时汇编指令会去指向对象的虚表中找对应虚函数进行调用!!!
- 不满足多态,编译链接时直接根据对象类型,确定调用的函数,确定地址!!!
这里需要分辨一下两个概念:虚表与虚基表
- 虚表:虚函数表,存的是虚函数,用来形成多态!!!
- 虚基表:存的是距离虚基类的位置的偏移量,用来解决菱形继承的数据冗余和二义性!!!
注意:虚函数不是存在虚表中的 , 虚表中存的是虚函数的指针。那虚函数存在哪里呢?
来验证一下:
class Person
{
public:virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person
{
public:virtual ~Student() { cout << "~Student()" << endl; }
};int main()
{int i = 0;static int j = 1;int* p1 = new int;const char* p2 = "xxxxxxxx";printf("栈:%p\n", &i);printf("静态区:%p\n", &j);printf("堆:%p\n", p1);printf("常量区:%p\n", p2);Person p;Student s;Person* p3 = &p;Student* p4 = &s;printf("Person虚表地址:%p\n", *(int*)p3);printf("Student虚表地址:%p\n", *(int*)p4);return 0;
}
运行可以看到:
虚表地址与常量区最接近,那可以推断出虚表储存在常量区!!!
4.2 验证虚表
我们来看:
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;
};
梳理一下结构:
- Base作为基类 , Derive作为派生类!
- 派生类Derive重写了func1函数,构成多态!
- 其余函数均不构成多态。
然后我们来探索一下:
int main()
{Base b;Derive d;return 0;
}
通过监视窗口可以查看一下虚表的内容:
这是VS调试的一点BUG,导致监视中派生类的虚表不能显示。在内存窗口里存在4个函数指针,接下来我们来验证一下他们是不是对应的虚函数。
虚函数表本质是一个函数指针数组!
那么如何定义一个函数指针和函数指针数组呢?
//这样定义
//返回值是void 所以写void
void(*p)( //函数里面的参数 );
void(*p[10])( //函数里面的参数 )
当然可以使用typedef
来简化(这个typedef也很特别)
typedef void(*VFPTR)();
VFPTR p1;
VFPTR p2[10];
那么如果我们想要打印出虚表,我们可以设置一个函数:
//因为是函数指针数组,所以传参是函数指针的指针(int arr[10] 传入 int*)。
void PrintVFT(VFPTR* vft )
{for(size_t i = 0 ; i < 4 ; i++){printf("%p\n" , vft[i]);}}
这样就可以打印了,那么现在就需要解决如何获取虚表的首地址。虚表首地址是类的头4个字节(x86环境),我们如何取出来了呢?
直接把类强转为int类型不就4个字节了吗!?但是没有联系的类型是不能强转的。那怎么办呢???
C/C++中指针可以直接互相强转(BUG级别的操作!!!),整型与指针也可以互相转换。
VFPTR* p = (VFPTR*) *( (int*)&d );//这样就变成4个字节了!
&d
是取类的指针(int*)&d
将类指针强转为int*
指针!*( (int*)&d )
将int *
解引用为int
(VFPTR*) *( (int*)&d )
将int
转换为VFPTR*
,取到虚表首地址!!!
那么我们来验证一下:
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;
};
typedef void(*VFPTR)();void PrintVFT(VFPTR* vft)
{for (size_t i = 0; i < 4; i++){printf("%p ->", vft[i]);(*(vft[i]))();}}int main()
{Base b;Derive d;VFPTR* p = (VFPTR*)*((int*)&d);//这样就变成4个字节了!PrintVFT(p);return 0;
}
来看:
这样就成功获取到了虚标的内容,验证了虚表的内容中存在4个虚函数!!!
5 抽象类
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类)
抽象类不能实例化出对象。派生类继承后也不能实例化出对象。只有重写纯虚函数,派生类才能实例化出对象。
纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
//抽象类
class Car
{
public: //纯虚函数virtual void Drive() = 0;
};int main()
{Car c;return 0;
}
这样一个抽象类是不可以实例化的,进行实例化就会报错:
如果派生类进行了重新那么就可以正常使用:
class Car
{
public: virtual void Drive() = 0;
};class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};
class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};
void Test()
{//各种调用自身的虚函数Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}
int main()
{Test();return 0;
}
抽象类与override关键字的区别:
- 抽象类间接强制了派生类必须进行虚函数重写
- override是在已经重写的情况下,帮助进行重写的语法检查
6 多继承中的多态
多继承我们讲过,是一种很危险的继承,很容易导致菱形继承,引起数据冗余和二义性。那么我们再来看看多态在多继承中是然如何实现的 。
一般的多继承
class Base1 {
public:virtual void func1() { cout << "Base1::func1" << endl; }virtual void func2() { cout << "Base1::func2" << endl; }
private:int b1;
};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;
};
分析一下继承关系:
- 有两个基类:Base1类与Base2类
- Derive继承两个基类,对func1函数进行了重写构成多态
来看看Derive类的大小是多大:
我们分析一下:Base1类应该有一个虚表指针和一个int类型数据,所以应该为8字节。Base2同理8字节。
那么Derive由于多继承的缘故会包含两个基类,所以应该为16 + 4 = 20字节
:
运行一下,看来我们的分析没有问题!也就是有两张虚表,func1重写会改变两个虚表(因为两个基类都有func1函数),func3是放在Base1的虚表中的,通过虚表验证:
typedef void(*VFPTR) ();void PrintVTable(VFPTR vTable[])
{cout << " 虚表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}
int main()
{Derive d;VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);PrintVTable(vTableb1);//通过切片获取Base2 b2 = d;VFPTR* vTableb2 = (VFPTR*)(*(int*)&b2);PrintVTable(vTableb2);return 0;
}
运行看看:
多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中
菱形继承和菱形虚拟继承
实际中我们不建议设计出菱形继承及菱形虚拟继承,一方面太复杂容易出问题,另一方面这样的模型,访问基类成员有一定得性能损耗。所以菱形继承、菱形虚拟继承我们的虚表我们就不看了,一般我们也不需要研究清楚,因为实际中很少用。这里简单叙述一下:
先看菱形继承:
class A
{
public:virtual void func1() { cout << "A::func1" << endl; }int _a;
};class B : public A
{
public:virtual void func2() { cout << "B::func2" << endl; }int _b;
};class C : public A
{
public:virtual void func3() { cout << "C::func3" << endl; }int _c;
};
class D : public B, public C
{
public:virtual void func4() { cout << "D::func4" << endl; }int _d;
};
int main()
{D d;cout<< sizeof(d) << endl;return 0;
}
先来看一下这个类有多大:
28 字节,这个是怎么得到的,来分析一下:
- A类有一个虚函数表指针和一个整型 ,应该是8字节
- B类继承于A类 ,包含A类的内容,B的虚函数储存在A的虚表中,所以B类一个为
8 + 4 = 12
- C类同理
- D类继承于B类和C类,那么就包含B类与C类,D类的虚函数储存在B类的虚表中(A的虚表)
通过内存来验证一下:
可以看到只有两个虚表指针。所以菱形继承和多继承类似!
再来看菱形虚拟继承:
这个36字节是怎么得到的???
- 首先菱形虚拟继承会把共同的基类提取出来(也就是A被提出来了),那么B类就会有一个虚基表指针来指向这个提前出来的A类。所以B类大小为
4 (虚表指针) + 4(虚基表指针) + 4(int数据) = 12
- C类同理,那么现在就有
12 (B类) + 12(C类) + 4(A类的int)+ 4(D类的int) = 32
- 啊???这才32字节,剩下的4字节是什么?难不成还有一个虚表指针?!是的,A 里面还有一个虚表指针!!!
来看内存:
很明显,在A类中还有一个虚表指针!!!真滴复杂!
所以应该是:
12 (B类) + 12(C类) + 8(A类的int)+ 4(D类的int) = 36
那为什么A会有一个虚表指针,而不是D类有!?
-
首先派生类的成员是不会有虚表指针的,虚表指针都在基类的部分中!!!
-
我们这四个类都有自身的虚函数
- 菱形继承中,B类与C类都继承于A类,所以BC是派生类,就不需要有独立的虚表指针,而是与A类共用。父类有了就与父类共用,父类没有才会独立创建。
- 菱形虚拟继承中,B类与C类都虚拟继承于A类,A类被单独出去了,那么B类与C类的虚函数就不能放在A类里,因为A类是共享的,放进去就会产出问题!所以BC会独立创建一个虚表指针。
-
总结: 子类有虚函数,继承的父类有虚函数就有虚表,子类不需要单独建立虚表!!!如果父类是共享的,无论如何都有创建独立的虚表!!!
注意:虚基表中储存两个值:第一个是距离虚表位置的偏移量,第二个是距离基类位置的偏移量
Thanks♪(・ω・)ノ谢谢阅读!!!
下一篇文章见!!!
相关文章:

【C++】从零开始认识多态
送给大家一句话: 一个犹豫不决的灵魂,奋起抗击无穷的忧患,而内心又矛盾重重,真实生活就是如此。 – 詹姆斯・乔伊斯 《尤利西斯》 _φ(* ̄ω ̄)ノ_φ(* ̄ω ̄)&…...

为什么叫“机器学习”Machine Learning 而不是“计算机学习”——深度学习Note
有一门学科“机器学习”火了起来,它是计算机科学与数学结合的产物,它的目的是使计算机“聪明”起来,实现人工智能。可是,令人困惑的是它明明就是计算机学习,为什么不叫“计算机学习”而叫“机器学习”呢?这…...

Spring Boot集成RabbitMQ-之6大模式总结
A.集成 一:添加依赖 在pom.xml文件中添加spring-boot-starter-amqp依赖,以便使用Spring Boot提供的RabbitMQ支持: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp&…...

后端开发面经系列 -- 滴滴C++一面面经
滴滴C一面面经 公众号:阿Q技术站 来源:https://www.nowcoder.com/feed/main/detail/38cf9704ef084e27903d2204352835ef 1、const在C和C区别,const定义的类成员变量如何初始化? 区别 C中的const: 在C中,c…...

Three.js的几何形状
在创建物体的时候,需要传入两个参数,一个是几何形状【Geometry】,一个是材质【Material】 几何形状主要是存储一个物体的顶点信息,在Three中可以通过指定一些特征来创建几何形状,比如使用半径来创建一个球体。 立方体…...

设计模式——单例模式(Singleton)
单例模式(Singleton Pattern)是设计模式中的一种,它确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。这种模式在多种场景下都非常有用,比如配置文件的读取、数据库连接的创建、线程池的管理等。 实现…...

springboot3项目练习详细步骤(第二部分:文章分类模块)
新增文章分类 接口文档 业务实现 参数校验 文章分类列表 接口文档 业务实现 获取文章分类详情 接口文档 业务实现 更新文章分类 接口文档 业务实现 分组校验 问题 概念 实现步骤 总结 删除文章分类 接口文档 业务实现 该模块大部分请求的路径相同&…...

VUE中父组件向子组件进行传值
在 Vue 中,父组件向子组件传值主要通过在子组件的标签上绑定属性(props)的方式来实现。以下是一个具体的示例。 父组件(ParentComponent.vue): <template><div><!-- 父组件中使用子组件,并传…...

alpine安装中文字体
背景 最近在alpine容器中需要用到中文字体处理视频,不想从本地拷贝字体文件, 所以找到了一个中文的字体包font-droid-nonlatin,在此记录下。 安装 apk add font-droid-nonlatin安装好后会出现在目录下/usr/share/fonts/droid-nonlatin/ 这…...

JavaScript学习—JavaScript高级
原型链和继承 在 JavaScript 中,每个对象都有一个原型(prototype),这个原型指向另一个对象。这个链式的原型关系被称为原型链。当访问一个对象的属性时,如果该对象没有该属性,它会沿着原型链向上查找&…...

CompletableFuture使用案例
优化代码时,除了Async注解,项目中如何使用多线程异步调用? 举个例子,去餐厅吃饭的时候。先点餐,厨师做菜,在厨师做菜的时候打游戏,然后根据厨师做的菜的口味去买矿泉水还是可乐。这样࿰…...

安卓使用so库
最近需要给小伙伴扫盲一下如何使用Android Studio 生成一个SO文件,网上找了很多都没有合适的样例,那只能自己来写一个了。 原先生成SO是一个很麻烦的事情,现在Android Studio帮忙做了很多的事情,基本只要管好自己的C代码即可。 …...

【介绍下LeetCode的使用方法】
🌈个人主页: 程序员不想敲代码啊 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共…...

重学java 30.API 1.String字符串
于是,虚度的光阴换来了模糊 —— 24.5.8 一、String基础知识以及创建 1.String介绍 1.概述 String类代表字符串 2.特点 a.Java程序中的所有字符串字面值(如“abc”)都作为此类的实例(对象)实现 凡是带双引号的,都是String的对象 String s "abc&q…...

【区块链】共识算法简介
共识算法简介 区块链三要素: 去中心化共识算法智能合约 共识算法作为区块链三大核心技术之一,其重要性不言而喻。今天就来简单介绍共识算法的基本知识。 最简单的解释,共识算法就是要让所有节点达成共识,保证少数服从多数&#x…...

Qt---day2-信号与槽
1、思维导图 2、 拖拽式 源文件 #include "mywidget.h" #include "ui_mywidget.h" MyWidget::MyWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::MyWidget) { ui->setupUi(this); //按钮2 this->btn2new QPushButton("按钮2",th…...

Python中设计注册登录代码
import hashlib import json import os import sys # user interface 用户是界面 UI """ 用户登录系统 1.注册 2.登陆 0.退出 """ # 读取users.bin def load(path): return json.load(open(path, "rt")) # 保存user.bin def save(dic…...

AI伦理和安全风险管理终极指南
人工智能(AI)正在迅速改变各个领域的软件开发和部署。驱动这一转变的两个关键群体为人工智能开发者和人工智能集成商。开发人员处于创建基础人工智能技术的最前沿,包括生成式人工智能(GenAI)模型、自然语言处理&#x…...

golang testing使用
testing包服务于自动化测试 基本测试 Table Drvien Test 基于表的测试通过表形式进行测试每种情况的输入和期望输出,从而测试程序的正确性 func TestFib(t *testing.T) {var fibTests []struct {in int // inputexpected int // expected result}{{1, 1}…...

在Excel中使用正则提取单元格内容
在办公自动化的浪潮中,Excel 作为数据处理的利器,一直在不断进化。最近,我注意到了不坑盒子Office插件一个非常实用的功能更新——bk_regex_string 公式。这个功能对于我们这些日常需要处理大量文本和数据的办公人员来说,无疑是一…...

SQL查询语句(二)逻辑运算关键字
上一篇文章中我们提到了条件查询除了一些简单的数学符号之外,还有一些用于条件判断的关键字,如逻辑判断 关键字AND,OR,NOT和范围查找关键字BETWEEN,IN等;下面我们来介绍一些这些关键字的用法以及他们所表达的含义。 目录 逻辑运算关键字 AND…...

矿山机械自动化中的激光雷达技术探索
在矿山机械自动化技术的快速发展中,激光雷达技术作为其关键组成部分,正发挥着越来越重要的作用。本文将深入探讨激光雷达在矿山机械自动化中的应用,以及其所面临的挑战与未来发展趋势。 一、激光雷达在矿山机械自动化中的应用 激光雷达技术…...

MOSFET场效应管栅极驱动电流的计算
MOSFET驱动 MOSFET场效应管是电压驱动器件,输入有电容,因此为可靠驱动MOSFET,栅极需要施加较大的驱动电流。 功率MOSFET开关模型 该模型显示了影响开关性能的最重要的寄生器件。 栅极所需驱动电流计算公式 一个很重要的参数是计算栅极驱…...

Python 爬虫:Spring Boot 反爬虫的成功案例
前言 在当今数字化时代,网络数据成为了信息获取和分析的重要来源之一。然而,随着网络数据的广泛应用,爬虫技术也逐渐成为了互联网行业的热门话题。爬虫技术的应用不仅可以帮助企业获取有价值的信息,还可以用于数据分析、市场研究…...

计算机毕业设计Python+Vue.js天气预测系统 中国气象质量采集与可视化 天气数据分析 天气可视化 天气大数据 天气爬虫 大数据毕业设计
摘要 随着科技技术的不断发展,人民物质生活质量不断提高,我们越来越关注身边的气象、空气等地理环境。对于普通居民我们会选择合适的气象进行出游,提高精神层面的生活质量;对于企业会关注气象变换状况,来定制相关的生产…...

【busybox记录】【shell指令】tr
目录 内容来源: 【GUN】【tr】指令介绍 【busybox】【tr】指令介绍 【linux】【tr】指令介绍 使用示例: 转换字符 - 默认 转换字符 - 不翻译指定字符数组 此指令目前接触少,用得少,把精力放到其他常用指令上 常用组合指令…...

Mac虚拟机软件哪个好用 mac虚拟机parallels desktop有什么用 Mac装虚拟机的利与弊 mac装虚拟机对电脑有损害吗
随着多系统使用需求的升温,虚拟机的使用也变得越来越普遍。虚拟机可以用于创建各种不同的系统,并按照要求设定所需的系统环境。另外,虚拟机在Mac电脑的跨系统使用以及测试软件系统兼容性等领域应用也越来越广泛。 一、Mac系统和虚拟机的区别 …...

Type-C转音频(USB2.0数据传输)+PD充电芯片乐得瑞LDR6500/LDR6023
LDR6500 USB-C DRP 接口 USB PD 通信芯片概述 Type-C转音频(USB2.0数据传输)PD充电芯片乐得瑞LDR6500LDR6500是乐得瑞科技针对USB Type-C标准中的Bridge设备而开发的USB-C DRP(Dual Role Port,双角色端口)接口USB PD(Power Deliv…...

【busybox记录】【shell指令】expand
目录 内容来源: 【GUN】【expand】指令介绍 【busybox】【expand】指令介绍 【linux】【expand】指令介绍 使用示例: 把制表符转化为空格 - 默认输出 把制表符转化为空格 - 修改制表符转空格的个数 把制表符转化为空格 - 修改制表符转空格的个数…...

软件测试—— 接口测试之通讯流程相关概念
通讯流程 1、协议 通讯规则 2、HTTP协议 协议的一种 3、接口规范文档 如何发请求的要求文档,获取什么响应内容的说明文档(相当于菜单)...