【C++入门到精通】C++入门 —— 多态(抽象类和虚函数的魅力)
阅读导航
- 前言
- 一、多态的概念
- 1. 概念
- 2. 多态的特点
- 二、多态的定义及实现
- 1. 多态的构成条件
- 2. 虚函数
- 3. 虚函数的重写
- ⭕虚函数重写的两个例外
- 1.协变(基类与派生类虚函数返回值类型不同)
- 2.析构函数的重写(基类与派生类析构函数的名字不同)
- 4. override 和 final(C++11 )
- override
- final
- 5. 重载、覆盖(重写)、隐藏(重定义)的对比
- 1. 重载
- 2. 覆盖(也称为重写)
- 3. 隐藏(也称为重定义)
- 三、抽象类
- 1. 抽象类的概念
- 2. 接口继承和实现继承
- -- 接口继承
- -- 实现继承
- -- 适用类型
- 四、多态的原理
- 1. 虚函数表
- 2. 动态绑定与静态绑定
- 动态绑定
- 静态绑定
- 3. 多态的原理
- 五、单继承和多继承关系中的虚函数表
- 1. 单继承中的虚函数表
- 2. 多继承中的虚函数表
- 六、多态总结
- 温馨提示
前言
前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的命名空间的一些知识点以及关于C++的缺省参数、函数重载,引用 和 内联函数也认识了什么是类和对象以及怎么去new一个 ‘对象’ ,也了解了C++中的模版,以及学习了几个STL的结构也相信大家都掌握的不错,接下来博主将会带领大家继续学习有关C++比较重要的知识点—— 多态(抽象类和虚函数的魅力)。下面话不多说坐稳扶好咱们要开车了😍
一、多态的概念
1. 概念
多态是面向对象编程中的一个重要概念,指的是同一个消息被不同类型的对象接收时产生不同的行为。通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
⭕多态的核心思想是通过基类指针或引用来调用派生类的方法,在运行时确定实际调用的方法。这种动态绑定的特性使得代码更加灵活,能够适应不同的对象和场景。
2. 多态的特点
- 多态性通过继承和虚函数的机制来实现。
- 多态性在运行时动态地确定调用的函数,而不是在编译时静态地确定。
- 多态性可以让相同的代码适用于不同类型的对象,提高代码的可重用性和扩展性。
- 多态性能够进行方法重写,不同的派生类可以根据自身的需求提供不同的方法实现。
二、多态的定义及实现
1. 多态的构成条件
实现多态性的关键是满足以下两个条件:
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
🚨虚函数:通过在基类中使用 virtual
关键字声明函数为虚函数,可以实现动态绑定,使得在运行时选择调用实际对象的方法。(下面会有概念及定义)
虚函数允许通过基类指针或引用调用派生类的方法,而在运行时动态地确定实际调用的函数。当通过基类指针或引用调用虚函数时,编译器会根据实际对象的类型来调用相应的派生类函数。
以下是实现多态性的典型代码结构:
class Base {
public:virtual void polymorphicFunction() {// 基类虚函数的默认实现}
};class Derived : public Base {
public:virtual void polymorphicFunction() {// 派生类重写基类虚函数的实现}
};
基类 Base
声明了一个虚函数 polymorphicFunction()
,派生类 Derived
重写了这个虚函数。通过基类指针或引用来调用该虚函数时,实际执行的是派生类中的函数实现。
总结起来,多态的构成条件是基于继承关系和虚函数的使用。满足这两个条件后,就可以通过基类的指针或引用调用派生类的函数,实现多态性的效果。
下面这个图片模拟了一个简单的多态的定义以及实现的过程,实现了“不同人群,买了不同的票”
2. 虚函数
⭕虚函数:即被virtual修饰的类成员函数称为虚函数。例如:
class Base {
public:virtual void myFunction() {// 基类虚函数的默认实现}
};
3. 虚函数的重写
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
class Derived : public Base {
public:virtual void myFunction() {// 派生类重写基类虚函数的实现}
};
⭕虚函数重写的两个例外
1.协变(基类与派生类虚函数返回值类型不同)
🚩派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
协变的实现和使用需要满足以下条件:
-
继承关系:
协变是基于继承关系的特性,需要存在基类和派生类。派生类继承了基类的属性和方法。 -
虚函数:
必须在基类中将要协变的虚函数声明为虚函数,并在派生类中重新定义(重写)该虚函数。基类中的虚函数返回类型应该是指向某种类型的指针或引用,而派生类中的重写函数的返回类型可以是指向派生类类型的指针或引用。
下面是一个示例来说明协变的使用方法:
class Base {
public:virtual Base* createInstance() {return new Base();}
};class Derived : public Base {
public:Derived* createInstance() override {return new Derived();}
};
在上述示例中,Base
类声明了一个虚函数 createInstance()
,该函数返回类型为 Base*
,即指向 Base
类对象的指针。在派生类 Derived
中重写了这个函数,将返回类型修改为 Derived*
,即指向 Derived
类对象的指针。
通过协变,派生类 Derived
可以返回比基类 Base
更具体的类型,即 Derived*
。这样,在使用基类指针调用派生类的虚函数时,实际会返回派生类的对象指针。
🚨注意
- 协变只适用于指针或引用类型的返回值,而不适用于值类型。
- 派生类重写函数的返回类型必须在基类函数返回类型的派生类型层次结构中。
2.析构函数的重写(基类与派生类析构函数的名字不同)
🍪如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor
以下是一个示例演示如何在派生类中重写基类的析构函数:
class Base {
public:virtual ~Base() {// 基类析构函数的实现}
};class Derived : public Base {
public:~Derived() override {// 派生类析构函数的实现}
};
在上述示例中,基类 Base
声明了一个虚析构函数 ~Base()
。派生类 Derived
重写了基类的虚析构函数,使用 ~Derived()
来重新定义析构函数的实现。
🚨注意:
- 虚析构函数只需要在基类中声明,不需要在派生类中显式声明为虚函数。
- 为了确保正确的析构顺序,派生类的析构函数可以在末尾隐式调用基类的析构函数,不需要显式调用。
⭕重写基类的析构函数在处理继承关系和多态性时非常重要,它能够确保在销毁派生类对象时,正确地调用相应的析构函数,避免资源泄漏和未定义行为。
4. override 和 final(C++11 )
override
和 final
都是 C++11 中引入的关键字,用于标识和修饰虚函数的行为。
override
override
检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
示例:
class Base {
public:virtual void myFunction() {// 基类虚函数的默认实现}
};class Derived : public Base {
public:void myFunction() override {// 派生类重写基类虚函数的实现}
};
在上述示例中,派生类 Derived
使用 override
关键字表明它重写了基类 Base
的虚函数 myFunction()
。如果在派生类中意外地使用了错误的函数签名(参数列表或返回类型不匹配),编译器会发出错误提示。
final
final
用于标识类、成员函数或虚函数,表示它们被声明为最终版本,禁止在派生类中进一步继承或重写。
示例:
class Base final {
public:virtual void myFunction() {// 基类虚函数的默认实现}
};class Derived : public Base {
public:void myFunction() /* override 不可使用 */ {// 派生类重写基类虚函数的实现}
};
在上述示例中,基类 Base
使用 final
关键字标识它是最终类,不允许被继续派生。同时,派生类 Derived
中的 myFunction()
不能使用 override
关键字进行标识,因为基类已经被声明为最终类。
5. 重载、覆盖(重写)、隐藏(重定义)的对比
1. 重载
重载是指在同一个作用域内,根据函数的参数列表的不同,可以定义多个具有相同名称但参数不同的函数。重载函数可以有不同的返回类型,但不能仅仅通过返回类型的不同来进行重载。
示例:
void myFunction(int x) {// 函数实现
}void myFunction(double x) {// 函数实现
}
在上述示例中,myFunction
函数被重载了,分别接受一个 int
类型和一个 double
类型的参数。
2. 覆盖(也称为重写)
覆盖是指派生类中重新定义(重写)基类的虚函数,以实现多态性。派生类中的虚函数必须与基类的虚函数具有相同的函数签名(包括参数列表和返回类型),并使用 override
关键字进行标识。
示例:
class Base {
public:virtual void myFunction() {// 基类虚函数的默认实现}
};class Derived : public Base {
public:void myFunction() override {// 派生类重写基类虚函数的实现}
};
在上述示例中,派生类 Derived
重写了基类 Base
的虚函数 myFunction()
,并使用 override
关键字进行标识。
3. 隐藏(也称为重定义)
隐藏是指派生类中定义了与基类中同名的非虚函数或静态函数,从而隐藏了基类中的同名函数。隐藏函数不具有多态性,调用时会根据对象的静态类型来确定调用的函数。
示例:
class Base {
public:void myFunction() {// 基类函数的实现}
};class Derived : public Base {
public:void myFunction() {// 派生类函数的实现}
};
在上述示例中,派生类 Derived
定义了与基类 Base
同名的函数 myFunction()
,从而隐藏了基类的同名函数。
总结:
- 重载是指在同一个作用域内定义多个具有相同名称但参数列表不同的函数。
- 覆盖是指派生类中重新定义(重写)基类的虚函数,以实现多态性。
- 隐藏是指派生类中定义了与基类中同名的非虚函数或静态函数,从而隐藏了基类中的同名函数。
三、抽象类
1. 抽象类的概念
抽象类是一种特殊的类,它不能实例化对象,只能作为其他类的基类。抽象类用于定义一组相关的类的共同接口,它包含纯虚函数和非纯虚函数。
可以通过在类中声明纯虚函数来创建抽象类。纯虚函数是一个在基类中声明但没有具体实现的虚函数,它通过在函数声明后添加 “= 0” 来指示。
抽象类的主要目的是定义一组规范和约束,要求派生类实现纯虚函数,以便在派生类中实现具体的功能。
以下是一个抽象类的示例:
class AbstractClass {
public:virtual void pureVirtualFunction() const = 0;virtual void virtualFunction() const {// 可选的虚函数实现}void regularFunction() const {// 非虚函数的实现}
};
在上述示例中,AbstractClass
是一个抽象类。它包含了纯虚函数 pureVirtualFunction()
,该函数没有具体实现,需要在派生类中进行实现。同时,它还包含了一个可选的虚函数 virtualFunction()
和一个非虚函数 regularFunction()
。
抽象类具有以下特点:
- 不能创建抽象类的对象,只能通过派生出的非抽象子类来创建对象。
- 子类必须实现抽象类中的纯虚函数,否则子类也成为抽象类。
- 抽象类可以包含非纯虚函数和数据成员。
- 抽象类的指针可以用于引用或指向派生类的对象。
抽象类提供了一个接口的定义和封装,强制要求派生类实现接口中的方法。这种设计方式有助于实现多态性和代码的可扩展性,提高代码的模块化和重用性。
2. 接口继承和实现继承
– 接口继承
- 接口继承关注的是类与类之间的协议或合约。一个类可以通过继承一个或多个接口来表明它具有实现这些接口所定义的方法和行为。
- 接口(Interface)定义了一组纯虚函数(只有声明没有实现),表示了一种约定或契约,派生类必须实现这些纯虚函数。
- 接口继承使得类之间的关系更加松耦合,可以实现更好的代码复用和扩展性。
– 实现继承
- 实现继承关注的是类与类之间的具体的实现关系。一个类可以通过继承的方式获取另一个类的属性和方法的实现。
- 在实现继承中,子类(派生类)继承父类(基类)的成员变量和成员方法,并可以通过继承的方式进行扩展或修改。
- 实现继承可以扩展基类的功能,实现代码重用,但也可能导致类之间的紧密耦合。
– 适用类型
- 当关注类之间的协议和约定,希望实现一种行为的一致性时,可以选择接口继承。
- 当一个类希望获取另一个类的具体实现,并在此基础上进行扩展时,可以选择实现继承。
四、多态的原理
1. 虚函数表
我们首先来看一道题目:sizeof(Base)是多少?
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};
通过观察测试我们发现Base
对象是8bytes
,除了_b
成员,还多一个__vfptr
放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v
代表virtual
,f
代表function
)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。
针对上面的代码我们做出以下改造
- 我们增加一个派生类
Derive
去继承Base
Derive
中重写Func1
Base
再增加一个虚函数Func2
和一个普通函数Func3
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:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main()
{Base b;Derive d;return 0;
}
通过观察和测试,我们发现了以下几点问题:
-
派生类对象
d
中也有一个虚表指针,d
对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。 -
基类
b
对象和派生类d
对象虚表是不一样的,这里我们发现Func1
完成了重写,所以d
的虚表中存的是重写的Derive::Func1
,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。 -
另外
Func2
继承下来后是虚函数,所以放进了虚表,Func3
也继承下来了,但是不是虚函数,所以不会放进虚表。 -
虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个
nullptr
。 -
总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
-
虚函数存在哪的?
答:虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。(虚表存的是虚函数指针,不是虚函数,另外对象中存的不是虚表,存的是虚表指针)
2. 动态绑定与静态绑定
动态绑定
- 动态绑定是在运行时确定函数、方法或操作要与调用者绑定的过程。
- 动态绑定适用于虚函数和纯虚函数,通过基类的指针或引用来调用派生类的虚函数。
- 在动态绑定中,根据对象的实际类型(而不是引用或指针的类型)来确定要调用的函数。这意味着在运行时才能确定函数的具体绑定。
- 动态绑定通过虚函数表和虚函数指针来实现。编译器会查找虚函数表,并根据对象的实际类型找到对应的虚函数进行调用。
⭕动态绑定实现了多态性,使得通过基类的指针或引用调用派生类的虚函数时,能够在运行时根据对象的实际类型决定要调用的函数,从而实现了多态行为。
静态绑定
- 静态绑定是在编译时确定函数、方法或操作要与调用者绑定的过程。
- 在静态绑定中,根据引用或指针的声明类型来确定要调用的函数、方法或操作。
- 静态绑定适用于非虚函数、静态函数和全局函数。编译器在编译阶段就能够确定调用哪个函数。
- 静态绑定通常比动态绑定更快,因为在编译时已经决定了函数调用的路径。
🚨注意:静态绑定和动态绑定是相对的概念,多态性是建立在动态绑定的基础上的。在面向对象编程中,静态绑定主要用于非虚函数或静态函数,而动态绑定主要用于虚函数。
3. 多态的原理
🔴多态的原理主要基于两个关键的机制:虚函数和动态绑定。
🚩多态的实现步骤
- 定义一个基类,并在其中声明一个或多个虚函数。
- 派生类继承基类,并可以选择重写基类中的虚函数。
- 创建基类的指针或引用,并指向派生类的对象。
- 通过基类的指针或引用调用虚函数。
- 运行时系统根据对象的实际类型确定要调用的虚函数,实现动态绑定,即根据对象的真正类型来调用正确的函数。
需要注意的是,多态性只适用于通过指针或引用调用虚函数的情况。当直接通过对象调用函数时,编译器在编译时就能够确定调用哪个函数,不会发生动态绑定。
⭕多态通过虚函数和动态绑定实现,允许我们以统一的方式处理不同类型的对象,并在运行时根据对象的实际类型调用适当的虚函数,从而实现了面向对象编程中的多态性。
五、单继承和多继承关系中的虚函数表
1. 单继承中的虚函数表
✅**在单继承关系中,每个类只有一个直接基类,因此虚函数表的实现相对简单。**每个类的虚函数表存储了该类及其直接基类中的所有虚函数的地址。当通过对象的指针或引用调用虚函数时,编译器会使用对象的虚函数指针(vptr
)来查找虚函数表,并根据对象的实际类型确定要调用的具体虚函数。
2. 多继承中的虚函数表
我们首先来看一段代码
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;
};typedef void (*VFPTR)(); // 定义函数指针类型 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]; // 将当前虚函数的地址赋值给函数指针变量 ff(); // 通过调用函数指针 f 执行对应的虚函数}cout << endl; // 输出换行符表示当前虚函数的输出结束
}int main()
{Derive d;VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);PrintVTable(vTableb1);VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d+sizeof(Base1)));PrintVTable(vTableb2);return 0;
}
观察下图可以看出:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中
这里有两篇文章推荐大家看一下
C++ 虚函数表解析
C++ 对象的内存布局
六、多态总结
-
多态性通过虚函数和动态绑定实现。虚函数是在基类中声明的带有关键字
virtual
的函数,它可以在派生类中重写(覆盖)。动态绑定是根据对象的实际类型来确定要调用的虚函数。 -
多态允许通过基类的指针或引用操作不同类型的对象,而无需关心对象的具体类型。这提高了代码的可扩展性和可维护性。
-
多态遵循面向对象编程的封装和抽象原则,能够隐藏对象的具体实现细节,将关注点放在接口和行为上。
-
在单继承关系中,每个类只有一个直接基类,虚函数表的实现相对简单,直接存储类及其直接基类的虚函数地址。通过对象的指针或引用调用虚函数时,根据对象的虚函数指针和虚函数表确定要调用的函数。
-
在多继承关系中,一个类可以有多个直接基类,可能涉及虚基类表和虚函数表指针数组的实现。虚基类表用于存储虚基类的相关信息,虚函数表指针数组在派生类的虚函数表中包含指向每个基类虚函数表的指针。
-
多态性只适用于通过指针或引用调用虚函数的情况。直接通过对象调用函数时,编译器在编译时就能够确定调用哪个函数,不会发生动态绑定。
温馨提示
感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!
再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
相关文章:

【C++入门到精通】C++入门 —— 多态(抽象类和虚函数的魅力)
阅读导航 前言一、多态的概念1. 概念2. 多态的特点 二、多态的定义及实现1. 多态的构成条件2. 虚函数3. 虚函数的重写⭕虚函数重写的两个例外1.协变(基类与派生类虚函数返回值类型不同)2.析构函数的重写(基类与派生类析构函数的名字不同) 4. override 和 final(C11 …...

基于springboot学生社团管理系统/基于Java的高校社团管理系统的设计与实现
摘 要 随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代&…...

【C++】C++ 引用详解 ⑦ ( 指针的引用 )
文章目录 一、二级指针可实现的效果二、指针的引用1、指针的引用 等同于 二级指针 ( 重点概念 )2、引用本质 - 函数间接赋值简化版本3、代码示例 - 指针的引用 一、二级指针可实现的效果 指针的引用 效果 等同于 二级指针 , 因此这里先介绍 二级指针 ; 使用 二级指针 作为参数 …...

ubuntu安装goland
下载并解压goland sudo tar -C /opt/ -xzvf goland-2023.1.3.tar.gz配置应用图标 新建文件: vim /usr/share/applications/goland.desktop文件中写入如下内容: [Desktop Entry] TypeApplication NameGoLand Icon/opt/GoLand/bin/goland.png Exec/op…...

海康摄像头通过SDK接入到LiveNVR实现双向语音喊话对讲与网页无插件播放,并支持GB28181级联语音对讲...
目录 1、确认摄像头是否支持对讲2、摄像头视频类型复合流3、通道配置SDK接入4、视频广场点击播放5、相关问题 5.1、如何配置通道获取直播流?5.2、如何GB28181级联国标平台?6、RTSP/HLS/FLV/RTMP拉流Onvif流媒体服务 1、确认摄像头是否支持对讲 可以访问摄…...

解锁开发中的创意:用户为中心的设计思维的力量
引言 设计思维,起源于20世纪60年代,是一种解决问题的方法。它不仅仅是设计师的专利,而是一种可以广泛应用于各种行业和领域的方法。设计思维强调了用户至中的重要性,认为任何问题的解决都应该从用户的需求出发。这种方法鼓励我们…...

python+mysql+前后端分离国内职位数据分析(源码+文档+指导)
系统阐述的是使用国内python职位数据分析系统的设计与实现,对于Python、B/S结构、MySql进行了较为深入的学习与应用。主要针对系统的设计,描述,实现和分析与测试方面来表明开发的过程。开发中使用了 Flask框架和MySql数据库技术搭建系统的整体…...
uniapp封装ajax请求
import /common/api/interceptor.js; // 引入拦截器文件 export default{common:{baseUrl:"http://localhost:3000/api",data:{},header:{"Access-Control-Allow-Origin":"*","Content-Type":"application/json","Cont…...

电路原理分析2:应急照明灯电路
k是线圈,1-2(常开)和2-3(常闭)是2个触点。 1、220v交流电正常供电时,变压器触头位置提供12v的电压,这个时候,v2二极管是导通状态,所以线圈k吸合,这个时候1-2…...

构造函数内的方法 直接写在构造函数内部 与 写在prototype上 的区别
文章目录 前言区别总结 前言 以前没注意过, 去创建一个构造函数的时候, 方法都是直接写在函数内的. 在构造函数需要多次实例化的情况下有缺点, 不过幸好以前项目里的构造函数也不需要多次实例化, 缺点没有生效. 区别 为了比较, 先在构造函数内部直接书写方法, 查看实例化结果…...
系统架构主题之七:基于架构的软件设计方法及应用
1 基于架构的软件设计方法概念 关键词:ABSD、自顶向下、递归迭代、与需求同步、设计元素、视角与视图、用例和质量场景、预期和非预期等。 总的来讲,ABSD方法分为如下六个大的阶段: 1)体系结构需求阶段 相比传统软件系统设计&…...
Python-if __name__ == ‘__main__‘和collections.Mapping的用法
1.if __name__ __main__ 1.1解释 if __name__ __main__: 是 Python 中的一个常见惯用法,用于确定一个 Python 脚本是被直接运行还是被导入为模块使用。 在 Python 中,每个模块都有一个特殊的内置变量 __name__,该变量的值决定了模块的运…...

Linux 基金会宣布正式进驻中国
在 LinuxCon 2017 (北京)即将召开前夕,我们Linux 中国会同 51CTO、开源中国对 Linux 基金会执行董事 Jim Zemlin 进行了一场远跨大洋的视频专访。 在这次专访中,Jim 先生回答了几个开源界和互联网领域关注的问题,并披…...
Pyecharts教程(二):使用pyecharts绘制3D散点图——以营养元素为例
Pyecharts教程(二):使用pyecharts绘制3D散点图——以营养元素为例 作者:安静到无声 个人主页 目录 Pyecharts教程(二):使用pyecharts绘制3D散点图——以营养元素为例1. 准备工作2. 读取JSON数据3. 配置图形选项4. 构造数据5. 实验结果推荐专栏在本文中,我们将学习如何使用…...
软考高级系统架构设计师系列论文第100篇:论软件的可维护性设计
软考高级系统架构设计师系列论文第100篇:论软件的可维护性设计 一、摘要二、正文三、总结一、摘要 2020年3月1日至12月20日,我参加了“数据安全访问平台”项目的开发,担任系统分析员的工作。该项目是某行业用户“数据中心二期”建设的主要内容,目标是:建立数据统一访问接口…...
curl 使用发送POST GET请求 HEADER设置
curl 使用发送POST GET请求 HEADER设置 文章目录 Get请求POST请求1. application/x-www-form-urlencoded2. Multipart/form-data3. application/json4. text/xml 文件内容作为提交的数据 curl 设置自定义HEADER 头注意事项:shell批处理外传 Get请求 get请求偏简单&…...

使用 Transformer 和 Amazon OpenSearch Service 构建基于列的语义搜索引擎
在数据湖中,对于数据清理和注释、架构匹配、数据发现和跨多个数据来源进行分析等许多操作,查找相似的列有着重要的应用。如果不能从多个不同的来源准确查找和分析数据,就会严重拉低效率,不论是数据科学家、医学研究人员、学者&…...

算法通关村第九关——透彻理解二分查找
1.前言 常见的查找算法有顺序查找、二分查找、插值查找、斐波那契查找、树表查找、分块查找、哈希查找等。如果进行归类,那么二分查找、插值查找(一种查找算法)以及斐波那契查找都可以归为插值查找(大类)。而插值查找…...

【字节跳动青训营】后端笔记整理-4 | Go框架三件套之GORM的使用
**本人是第六届字节跳动青训营(后端组)的成员。本文由博主本人整理自该营的日常学习实践,首发于稀土掘金。 我的go开发环境: *本地IDE:GoLand 2023.1.2 *go:1.20.6 *MySQL:8.0 本文介绍Go框架三…...

【TI毫米波雷达笔记】UART串口外设配置及驱动(以IWR6843AOP为例)
【TI毫米波雷达笔记】UART串口外设初始化配置及驱动(以IWR6843AOP为例) 最基本的工程建立好以后 需要给SOC进行初始化配置 int main (void) {//刷一下内存memset ((void *)L3_RAM_Buf, 0, sizeof(L3_RAM_Buf));int32_t errCode; //存放SOC初…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...

什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...

华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...