【C++】多态详解

💗个人主页💗
⭐个人专栏——C++学习⭐
💫点击关注🤩一起学习C语言💯💫

目录

一、多态概念

二、多态的定义及实现

1. 多态的构成条件

2. 虚函数

 2.1 什么是虚函数

 2.2 虚函数的重写

 2.3 虚函数重写的两个例外

2.3.1 协变

2.3.2 析构函数的重写

3. override 和 final

4. 重载、覆盖(重写)、隐藏(重定义)的对比

三、抽象类

1. 概念

2. 接口继承和实现继承

四、多态的原理

1. 虚函数表

2. 多态的原理

3. 动态绑定与静态绑定

五、单继承和多继承关系的虚函数表

1. 单继承中的虚函数表

2. 多继承中的虚函数表


一、多态概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会 产生出不同的状态。

举个栗子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人 买票时是优先买票。 

多态的优势在于它增加了代码的灵活性和可扩展性。通过多态,我们可以编写通用的代码,处理多种类型的对象,使得程序更易于理解、扩展和维护。

二、多态的定义及实现

1. 多态的构成条件

那么在继承中要构成多态还有两个条件:

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了 Person。Person对象买票全价,Student对象买票半价。

class Person 
{
public:virtual void BuyTicket() {cout << "买票全价" << endl; }
};class Student : public Person 
{
public:virtual void BuyTicket() { cout << "买票半价" << endl; }
};
void Func(Person& people)
{people.BuyTicket();
}
int main()
{Person Mike;Func(Mike);Student Johnson;Func(Johnson);return 0;
}

2. 虚函数

 2.1 什么是虚函数

虚函数(virtual function)是面向对象编程中的一个概念,它是用来实现多态的机制之一。

虚函数:即被virtual修饰的类成员函数称为虚函数。

虚函数是在基类中声明的一种特殊的成员函数,用关键字virtual进行声明。当在派生类中重写(override)这个函数时,可以使用关键字override来显式标识。

通过将函数声明为虚函数,可以实现动态绑定(dynamic binding),即在运行时根据对象的实际类型来调用相应的函数。这意味着当通过基类指针或引用调用虚函数时,实际调用的是对应的派生类中的函数。

class Person 
{
public:virtual void BuyTicket() { cout << "买票全价" << endl; }
};

2.2 虚函数的重写

虚函数的重写是指在派生类中重新定义(override)基类中已经声明的虚函数。通过重写虚函数,可以在派生类中提供自己的实现,以替代基类中的默认实现。

重写虚函数的规则如下:

  1. 基类中的虚函数必须使用关键字virtual进行声明。
  2. 派生类中的重写函数必须具有相同的名称、参数列表和返回类型。
  3. 派生类中的重写函数必须使用关键字override进行标识,以确保编译器进行正确的检查。
  4. 重写函数可以被声明为虚函数,但这不是必需的。
class Animal 
{
public:virtual void makeSound() {cout << "Animal is making a sound" << endl;}
};class Dog : public Animal 
{
public:void makeSound() override { // 重写虚函数cout << "Dog is barking" << endl;}
};class Cat : public Animal 
{
public:void makeSound() override { // 重写虚函数cout << "Cat is meowing" << endl;}
};int main() 
{Animal* animal = new Animal();Animal* dog = new Dog();Animal* cat = new Cat();animal->makeSound(); // 输出 "Animal is making a sound"dog->makeSound(); // 输出 "Dog is barking"cat->makeSound(); // 输出 "Cat is meowing"delete animal;delete dog;delete cat;return 0;
}

在上述代码中,Animal类中的makeSound()函数被声明为虚函数。派生类Dog和Cat分别重写了这个函数,并提供了自己的实现。

在main()函数中,通过基类指针调用makeSound()函数时,实际上会根据指针指向的对象类型来调用相应的派生类中的函数。这就是虚函数的重写实现多态性的一种方式。

 2.3 虚函数重写的两个例外

2.3.1 协变

虚函数的协变(covariant)是指派生类可以返回比基类更具体的类型。

换句话说,派生类可以重写基类中的虚函数,并返回一个派生类类型的指针或引用,而不仅仅是基类类型的指针或引用。

协变发生在满足以下条件时:

  1. 基类中的虚函数必须返回指针或引用类型。
  2. 派生类中重写的函数可以返回指向派生类的指针或引用类型,该派生类是基类中返回类型的派生类。
class Animal 
{
public:virtual Animal* create() {return new Animal();}
};class Dog : public Animal 
{
public:Dog* create() override { // 协变的重写函数return new Dog();}
};class Cat : public Animal 
{
public:Cat* create() override { // 协变的重写函数return new Cat();}
};int main() 
{Animal* animal = new Animal();Animal* dog = new Dog();Animal* cat = new Cat();Animal* newAnimal = animal->create();Animal* newDog = dog->create();Animal* newCat = cat->create();delete animal;delete dog;delete cat;delete newAnimal;delete newDog;delete newCat;return 0;
}

在上述代码中,Animal类中的create()函数返回一个指向Animal类型的指针。派生类Dog和Cat分别重写了这个函数,并返回指向Dog和Cat类型的指针。

在main()函数中,通过调用基类指针的create()函数,实际上会调用对应派生类中重写的create()函数。由于协变的存在,可以将返回的指针赋值给指向基类的指针,这样便保留了派生类的类型信息。

注意,为了实现协变,返回类型必须是指针或引用,而不能是值类型。否则无法满足派生类返回类型更具体的要求。

2.3.2 析构函数的重写

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字, 都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同, 看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。

以下是一个示例,展示了虚函数中的析构函数重写是没有意义的:

class Animal 
{
public:Animal() {cout << "Animal constructor called." << endl;}virtual ~Animal() {cout << "Animal destructor called." << endl;}virtual void speak() {cout << "Animal speaks." << endl;}
};class Dog : public Animal 
{
public:Dog() {cout << "Dog constructor called." << endl;}~Dog() {cout << "Dog destructor called." << endl;}void speak() override {cout << "Dog barks." << endl;}
};int main() 
{Animal* animal = new Dog();animal->speak();delete animal;return 0;
}

Animal类定义了一个虚函数speak(),而派生类Dog中重写了该函数。在main()函数中,通过基类指针创建了一个Dog对象,然后调用speak()函数。

从结果可以看出,析构函数按照预期顺序调用,无需在派生类中显式重写。同时,虚函数speak()也可以正确地根据对象的实际类型调用对应的版本。

3. override 和 final

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有 得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。

1. final:修饰虚函数,表示该虚函数不能再被重写

class Base 
{
public:virtual void foo() final {// 函数实现}
};class Derived : public Base 
{
public:void foo() override { // 编译错误,不能重写被声明为final的虚函数// 函数实现}
};

2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

class Base 
{
public:virtual void foo() {// 函数实现}
};class Derived : public Base 
{
public:void foo() override { // 使用override关键字重写基类的虚函数// 函数实现}
};

4. 重载、覆盖(重写)、隐藏(重定义)的对比

三、抽象类

1. 概念

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口 类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生 类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

抽象类可以用于实现接口的一致性和多态性。通过基类指针或引用,可以在运行时动态地选择调用派生类的实现。

class Animal 
{
public:virtual void makeSound() = 0; // 纯虚函数void sleep() {cout << "Zzz..." << endl;}
};class Dog : public Animal 
{
public:void makeSound() override {cout << "Woof!" << endl;}
};class Cat : public Animal {
public:void makeSound() override {cout << "Meow!" << endl;}
};int main() 
{Animal* animalPtr = new Dog();animalPtr->makeSound(); // 输出 "Woof!"animalPtr->sleep(); // 输出 "Zzz..."delete animalPtr;animalPtr = new Cat();animalPtr->makeSound(); // 输出 "Meow!"animalPtr->sleep(); // 输出 "Zzz..."delete animalPtr;return 0;
}

 Animal是一个抽象类,包含一个纯虚函数makeSound()和一个普通的成员函数sleep()DogCatAnimal的派生类,必须实现makeSound()函数。通过基类指针,可以调用派生类的实现。

需要注意的是,无法直接实例化抽象类,因为它包含纯虚函数没有具体的实现。派生类必须实现所有纯虚函数,才能被实例化。抽象类可以作为基类来定义其他类,从而实现代码的复用和扩展。

2. 接口继承和实现继承

接口继承和实现继承是面向对象编程中的两种继承方式。

  1. 接口继承(Interface Inheritance)是指一个接口(interface)可以继承自另一个接口,继承接口的子接口会继承父接口的所有方法定义。接口继承主要用于定义一组共享的方法规范,子接口可以继续扩展这些方法规范,同时可以添加自己的方法规范。接口继承可以实现多继承的效果,一个类可以同时实现多个接口。

  2. 实现继承(Implementation Inheritance)是指一个类(class)可以继承自另一个类,继承类会继承父类的属性和方法。实现继承主要用于类之间的层级关系,子类可以继承父类的行为和状态,同时还可以添加自己特有的行为和状态。实现继承是一种单继承的方式,一个类只能直接继承一个父类。

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

四、多态的原理

1. 虚函数表

我们来看下面的代码,想一下sizeof(a)是多少。

class A
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};
int main()
{A a;cout << sizeof(a) << endl;return 0;
}

第一眼看去,这不就是4吗,我们来看一下运行结果:

为什么呢?我们打开监视窗口来看一下:

我们发现,a里面不仅仅存在一个_b,还有一个_vfptr放在对象的前面(注意有些 平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。

一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数 的地址要被放到虚函数表中,虚函数表也简称虚表。

 那么派生类中这个表放了些什么呢?我们接着往下分析,针对上面的代码我们做出以下改造

  1. 我们增加一个派生类Derive去继承Base 
  2. Derive中重写Func1 
  3. Base再增加一个虚函数Func2和一个普通函数Func3
class A
{
public:virtual void Func1(){cout << "A::Func1()" << endl;}virtual void Func2(){cout << "A::Func2()" << endl;}void Func3(){cout << "A::Func3()" << endl;}
private:int _a = 1;
};
class B : public A
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _b = 2;
};
int main()
{A a;B b;cout << sizeof(a) << endl;cout << sizeof(b) << endl;return 0;
}

我们再来看下这个代码结果是多少呢:

这又是为什么呢?我们继续来看一下调试窗口:

通过观察和测试,我们发现了以下几点问题:

  1. 派生类对象b中也有一个虚表指针,b对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
  2. 基类a对象和派生类b对象虚表是不一样的,这里我们发现Func1完成了重写,所以b的虚表中存的是重写的B::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
  3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函 数,所以不会放进虚表。
  4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。

总结一下派生类的虚表生成:

  • 先将基类中的虚表内容拷贝一份到派生类虚表中
  • 如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
  • 派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

这里还有一个童鞋们很容易混淆的问题:虚函数存在哪的?虚表存在哪的?

答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。

注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下是存在代码段的。

2. 多态的原理

上面分析了这个半天了那么多态的原理到底是什么?我们来继续探索。

class A
{
public:virtual void Func1(){cout << "A::Func1()" << endl;}
private:int _a = 1;
};
class B : public A
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _b = 2;
};
int main()
{A a;B b;return 0;
}

 我们会疑惑,为什么多态可以实现指向父类调用父类函数 ,指向子类调用子类函数?

我们来看看虚表里的指针内容:

在监视窗口里我们可以看到a对象和b对象里的内容,我们继续打开内存窗口,取地址a,

我们看到第一个内容就是_vfptr的地址,继续通过_vfptr的地址,我们就能看到虚表里的内容,这时我们又看到了虚函数的地址。

我们来取地址b,发现和上诉同理。

但是,这时我们发现怎么在B类里的A的虚函数地址,和它原本在A类里的地址不一样,这就是虚函数的重写覆盖。

当一个类声明了虚函数时,编译器会为该类生成一个虚函数表。虚函数表是一个存储了虚函数指针的表格,每个包含虚函数的类都会有自己的虚函数表。虚函数表的每个表项存储了相应虚函数的地址。对于派生类,虚函数表中的表项可能会被重写以指向派生类中的虚函数。

当使用基类指针或引用调用虚函数时,编译器会根据对象的实际类型来查找虚函数表,并调用相应的虚函数。这种通过虚函数表来实现动态绑定的机制使得C++中的对象能够在运行时根据实际类型来确定调用的函数,而不是在编译时就确定下来。

3. 动态绑定与静态绑定

动态绑定和静态绑定是两种不同的函数调用机制,它们在编程语言中起到的作用也不同。

  • 静态绑定(静态分派): 静态绑定是在编译时期完成的,也称为早期绑定。在静态绑定中,函数调用的目标函数是在编译时根据对象的静态类型确定的。当调用一个函数时,编译器会根据对象的声明类型来确定要调用的函数,而不管对象的实际类型。

静态绑定适用于非虚函数和静态函数,这些函数的调用目标不会发生改变。在静态绑定中,对象的方法调用是基于声明类型进行的,也就是编译期间根据对象的静态类型来确定调用哪个函数,无法体现多态性。

  • 动态绑定(动态分派): 动态绑定是在运行时期完成的,也称为晚期绑定。在动态绑定中,函数调用的目标函数是根据对象的实际类型来确定的。当调用一个虚函数时,编译器会根据对象的实际类型来确定要调用的函数。

动态绑定适用于虚函数,虚函数表(vtable)是实现动态绑定的一种机制。每个包含虚函数的类都会有一个虚函数表,该表存储了类中虚函数的地址。运行时系统通过指向基类的指针或引用,查找对象实际的类型,并根据其虚函数表来确定要调用的函数。

动态绑定允许程序在运行时根据对象的实际类型来选择调用的函数,实现了多态性。这样可以实现基于对象的具体行为,并提高代码的灵活性。

五、单继承和多继承关系的虚函数表

1. 单继承中的虚函数表

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;
};
int main()
{Base b;Derive d;return 0;
}

观察下图中的监视窗口中我们发现看不见func3和func4。这里是编译器的监视窗口故意隐藏了这 两个函数,也可以认为是他的一个小bug。那么我们如何查看d的虚表呢?下面我们使用代码打印 出虚表中的函数。

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(*VFTPTR)();void PrintVFPtr(VFTPTR a[])
{for (size_t i = 0; a[i] != 0; i++){printf("a[%d]:%p\n", i, a[i]);}cout << endl;
}int main()
{Base b;Derive d;PrintVFPtr((VFTPTR*)*(int*)&b);PrintVFPtr((VFTPTR*)*(int*)&d);return 0;
}

 

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) ();
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);VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));PrintVTable(vTableb2);return 0;
}

观察下图可以看出:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PyPDF2拆分PDF文件为单个页面

本文目录 前言一、拆分成为单页1、代码解析2、处理效果图3、完整代码二、其它知识1、enumerate是什么① 语法② 功能③ 示例④ 更改起始索引前言 感觉之前写的不是很满意,为了充分满足付费用户的权益,所以打算把PyPDF2的各种类和用法都重新写一下,以便满足所有订阅用户的各…

transformer模型学习路线_transformer训练用的模型

Transformer学习路线 完全不懂transformer&#xff0c;最近小白来入门一下&#xff0c;下面就是本菜鸟学习路线。Transformer和CNN是两个分支&#xff01;&#xff01;因此要分开学习 Transformer是一个Seq2seq模型&#xff0c;而Seq2seq模型用到了self-attention机制&#xf…

三分钟看懂SMD封装与COB封装的差异

全彩LED显示屏领域中&#xff0c;COB封装于SMD封装是比较常见的两种封装方式&#xff0c;SMD封装产品主要有常规小间距以及室内、户外型产品&#xff0c;COB封装产品主要集中在小间距以及微间距系列产品中&#xff0c;今天跟随COB显示屏厂家中品瑞一起快速看懂SMD封装与COB封装…

使用高斯混合模型识别餐厅热点

使用 GMM 识别加拿大多伦多的直观餐厅集群&#xff08;附 Python 代码&#xff09; 聚类算法&#xff08;例如 GMM&#xff09;是一种有用的工具&#xff0c;可帮助识别数据中的模式。它们使我们能够识别数据集中的子组&#xff0c;从而提高你的理解或增强预测模型。在本文中&a…

DDR3(一)

目录 1 SDRAM1.1 同步动态随机存储器1.2 位宽1.3 SDRAM结构1.4 SDRAM引脚图 2 SDRAM操作指令2.1 读写指令2.2 刷新和预充电2.3 配置模式寄存器2.4 读/写突发2.5 数据屏蔽 SDRAM是DDR3的基础&#xff0c;在学习DDR3之前&#xff0c;我们先来学习一下SDRAM的相关知识。 1 SDRAM …

同样的APP为何在Android 8以后网络感觉变卡?

前言 在无线网络技术不断发展的今天&#xff0c;Wi-Fi已经成为了我们日常生活中不可或缺的一部分。无论是家庭娱乐、办公还是在线游戏&#xff0c;Wi-Fi都在提供着便捷的互联网接入服务。然而&#xff0c;在安卓8.1后&#xff0c;为了进一步延长安卓设备的待机时间。原生安卓(A…

推荐三款常用接口测试工具!

接口测试是软件开发中至关重要的一环&#xff0c;通过对应用程序接口进行测试&#xff0c;可以验证其功能、性能和稳定性。随着互联网和移动应用的快速发展&#xff0c;接口测试变得越来越重要。为了提高测试效率和质量&#xff0c;开发人员和测试人员需要使用专业的接口测试工…

【SCAU操作系统】期末复习简答及计算题例题解析

目录 一、写出下列英文缩写词在计算机系统中的英文或中文全名。 二、进程状态/调度/周转问题 &#xff08;1&#xff09;进程状态 &#xff08;2&#xff09;进程状态转换 &#xff08;3&#xff09;进程调度 &#xff08;4&#xff09;最短进程优先调度算法 三、逻辑地…

鸿蒙开发设备管理:【@ohos.runningLock (Runninglock锁)】

Runninglock锁 该模块主要提供Runninglock锁相关操作的接口&#xff0c;包括创建、查询、持锁、释放锁等操作。 说明&#xff1a; 本模块首批接口从API version 7开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import runningLock f…

经典资料分享:macOS设计指南(持续更新)

​MacOS设计指南是Mac应用设计必备的指导手册&#xff0c;学姐特提示完整指南文档给大家&#xff0c;原版中英文对照&#xff0c;实用方便查询。 因为文档内容太多&#xff0c;在后继的几个月会持续更新。本周更新内容为&#xff1a; 『MacOS设计指南-应用程序架构篇』 指南内容…

Vuex的基本使用

1.安装vuex npm i vuex3 2.引入 import Vuex from vuex 3.使用 Vue.use(Vuex) 4.在src下的目录创建store&#xff0c;新建index.js import store from ./store 5.编写index.js import Vue from vue import Vuex from vuex Vue.use(Vuex)//用于操作组件中的动作 const actions{a…

零知识学习之DPDK与RDMA(3)—— 认识DPDK(3)

接前一篇文章&#xff1a;零知识学习之DPDK与RDMA&#xff08;2&#xff09;—— 认识DPDK&#xff08;2&#xff09; 本文内容参考&#xff1a; 《Linux高性能网络详解 从DPDK、RDMA到XDP》 刘伟著 人民邮电出版社 https://blog.51cto.com/u_15301988/5181201 特此致谢&…

Shell Expect自动化交互(示例)

Shell Expect自动化交互 日常linux运维时&#xff0c;经常需要远程登录到服务器&#xff0c;登录过程中需要交互的过程&#xff0c;可能需要输入yes/no等信息&#xff0c;所以就用到expect来实现交互。 关键语法 ❶&#xff3b;#!/usr/bin/expect&#xff3d; 这一行告诉操…

昇思MindSpore学习笔记3-02热门LLM及其他AI应用--K近邻算法实现红酒聚类

摘要&#xff1a; 介绍了K近邻算法&#xff0c;记录了MindSporeAI框架使用部分wine数据集进行KNN实验的步聚和方法。包括环境准备、下载红酒数据集、加载数据和预处理、搭建模型、进行预测等。 一、KNN概念 1. K近邻算法K-Nearest-Neighbor(KNN) 用于分类和回归的非参数统计…

计算机网络部分知识点整理

停止等待协议的窗口尺寸为 1。 √以太网标准是IEEE802.3TCP/IP四层&#xff0c;OSI模型有7层&#xff0c;地址解析协议 ARP 在 OSI 参考七层协议属于数据链路层&#xff0c;在TCP/IP 协议属于网络层&#xff0c;ARP作用&#xff1a;将 IP 地址映射到第二层地址&#xff0c;交换…

Spring中使用FactoryBean配置注入Bean

spring通过FactoryBean配置可以将第三方框架整合到Spring中来&#xff0c;FactoryBean配置如下&#xff1a; 写一个用于注于的实体类&#xff0c;如User,并对这个类写一个实现FactoryBean的中间类&#xff08;UserFactoryBean&#xff09; User类 /** *Description: *author…

粒子扩展卡尔曼滤波|MATLAB代码

粒子滤波PF与扩展卡尔曼滤波EKF结合 下载链接:https://download.csdn.net/download/callmeup/89512392 粒子滤波 粒子滤波是一种用于估计状态变量的非线性滤波方法。它通过引入一组粒子来近似表示概率分布,从而利用蒙特卡洛方法进行状态估计。粒子滤波的主要思想是根据系统…

什么是带有 API 网关的代理?

带有 API 网关的代理服务显著提升了用户体验和性能。特别是对于那些使用需要频繁创建和轮换代理的工具的用户来说&#xff0c;使用 API 可以节省大量时间并提高效率。 了解 API API&#xff0c;即应用程序编程接口&#xff0c;是服务提供商和用户之间的连接网关。通过 API 连接…

昂科烧录器支持MindMotion灵动微电子的32位微控制器MM32L052NT

芯片烧录行业领导者-昂科技术近日发布最新的烧录软件更新及新增支持的芯片型号列表&#xff0c;其中MindMotion灵动微电子的32位微控制器MM32L052NT已经被昂科的通用烧录平台AP8000所支持。 MM32L052NT使用高性能的ARM Cortex-M0为内核的32位微控制器&#xff0c;最高工作频率…

Revit 专业实用的BIM模型设计软件下载安装,Revit 最新版下载安装

Revit&#xff0c;该软件是专门为建筑信息模型&#xff08;BIM&#xff09;量身打造的&#xff0c;不仅极大提升了建筑设计师的工作效率&#xff0c;更为他们创造了一个更加精确、高效的设计环境。 在Revit的助力下&#xff0c;建筑设计师们能够轻松地进行建筑建模&#xff0c…