【C++】—— 多态的基本介绍
前言:
在之前的学习过程中,我们已经对继承进行了详细的学习和了解。今天,我将带领大家学习的是关于 多态 的基本知识。
目录
(一)多态的概念
1、概念
(二)多态的定义及实现
1、多态的构成条件
2、虚函数
1️⃣纯虚函数
2️⃣ 面试题:虚函数与纯虚函数的区别
3、虚函数的重写
1️⃣虚函数重写的两个例外:
2️⃣析构函数的重写(基类与派生类析构函数的名字不同)
4、C++11 override 和 final
5、重载、覆盖(重写)、隐藏(重定义)的对比(面试题)
(三)抽象类
1、概念
2、接口继承和实现继承
(四)多态的原理
1、虚函数表
2、多态的原理
3、动态绑定与静态绑定
总结
(一)多态的概念
1、概念
- 多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会 产生出不同的状态。
【小结】
1、这些例子中,不同的对象根据自身的特性和行为对相同的消息做出了不同的响应,展现了多态的概念;
2、通过多态性,我们可以灵活地处理不同的对象,并针对每个对象的特点执行适当的操作,提高代码的可扩展性和复用性。
(二)多态的定义及实现
1、多态的构成条件
- 1. 必须通过基类的指针或者引用调用虚函数
- 2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
【小结】
要实现多态性,需要满足以下条件:
继承关系:存在一个继承关系的类层次结构,包括基类和派生类。派生类继承了基类的属性和方法。
方法重写:在派生类中重新定义(重写)与基类中相同名称的方法。子类通过重写基类方法来赋予自己独特的行为。
向上转型:将派生类的对象赋值给基类的引用变量。这样可以使得基类引用指向派生类的对象,从而可以调用派生类中重写的方法。
运行时绑定:在运行时确定调用哪个方法,实现动态绑定。由于基类引用指向的是派生类对象,因此根据实际的对象类型来决定调用哪个子类的方法。
当满足以上条件时,就实现了多态性。通过使用多态,可以提高代码的灵活性、可扩展性和可维护性,同时减少了代码的重复编写。
2、虚函数
【注意】
这里有一个大家容易混淆的点:那就是虚函数与之前学到的虚继承之间有什么联系吗?(强调:二者之间无联系,只是公用一个关键字而已)
C++中的虚函数和虚继承是两个不同的概念,它们在面向对象编程中发挥不同的作用。
- 下面是对这两个概念的解释以及它们之间的关系:
-
虚函数(Virtual Functions):
- 虚函数是用于实现动态多态性的机制。通过将基类中的函数声明为虚函数,可以在派生类中重写(override)该函数来实现不同的行为。
- 当通过基类指针或引用调用虚函数时,实际执行的函数取决于指针或引用指向的对象的类型,而不是指针或引用本身的类型。
- 通过使用虚函数,可以在运行时决定调用哪个函数,实现对象的多态行为。
-
虚继承(Virtual Inheritance):
- 虚继承是解决多继承带来的问题的一种机制。当一个类需要从多个基类继承时,如果其中的一些基类有共同的基类,那么在继承关系中就会产生多个对共同基类的实例。
- 通过使用虚继承,可以确保在继承关系中只有一个对共同基类的实例。这样可以避免派生类中对共同基类的成员访问和命名冲突。
- 虚继承会在派生类对象中引入虚基类指针(Virtual Base Pointer)和虚基类表(Virtual Base Table),用于管理共享基类的访问。
关系:
- 虚函数和虚继承是C++语言提供的两种不同的特性,它们在语法和作用上是独立的。
- 虚函数通过动态绑定实现运行时多态性,而虚继承通过调整继承关系来解决多继承带来的问题。
- 在某些情况下,我们可能需要同时使用虚函数和虚继承。例如,在存在多继承关系的类层次结构中,如果基类中有虚函数,并且派生类需要重写这些函数,那么可以通过虚继承来消除多个对共同基类的实例,同时实现动态多态性。
1️⃣纯虚函数
纯虚函数(Pure Virtual Function)是一种在基类中声明但不进行实现的虚函数。它通过在函数声明末尾添加 = 0
来标识,例如 virtual void func() = 0;
。
纯虚函数在基类中起到以下作用:
- 接口定义:纯虚函数可以被视为基类对于派生类的接口定义,定义了派生类必须实现的方法。
- 强制继承类实现:通过在基类中声明纯虚函数,强制要求派生类必须提供实现,确保每个派生类都具备相同的接口。
- 抽象类:包含一个或多个纯虚函数的类称为抽象类,无法实例化对象。抽象类通常用作基类,用于定义通用接口和行为。
派生类必须实现基类中的纯虚函数,如果未能实现,则派生类也成为了抽象类。只有当派生类实现了基类的所有纯虚函数时,才能实例化派生类的对象。
以下是一个展示纯虚函数的代码示例:
#include<iostream>
using namespace std;// 抽象基类 Animal
class Animal
{
public:// 纯虚函数,用于定义接口virtual void makeSound() = 0;
};// 派生类 Dog
class Dog : public Animal
{
public:// 实现基类的纯虚函数void makeSound() override {cout << "汪汪!" << endl;}
};// 派生类 Cat
class Cat : public Animal
{
public:// 实现基类的纯虚函数void makeSound() override {cout << "喵喵!" << endl;}
};int main()
{Dog dog;Cat cat;dog.makeSound(); // 输出:汪汪!cat.makeSound(); // 输出:喵喵!return 0;
}
【说明】
- 在上面的代码中,
Animal
是一个抽象基类,其中声明了一个纯虚函数makeSound()
。而Dog
和Cat
是派生类,它们必须实现makeSound()
函数才能被实例化。 - 通过上述代码,我们可以看到通过纯虚函数的方式,我们定义了一个通用的接口
makeSound()
,并要求派生类提供它们特定的实现。在main()
函数中,我们创建了Dog
和Cat
对象,并调用它们的makeSound()
函数,分别输出对应的结果!!
总结来说,纯虚函数是一种没有具体实现的函数,用于定义基类的接口和要求派生类提供实现。
2️⃣ 面试题:虚函数与纯虚函数的区别
首先,给大家先抛出概念性的东西,大家有个认识:
- 1. 虚函数和纯虚函数可以定义在同一个类中,含有纯虚函数的类被称为抽象类,而只含有虚函数的类不能被称为抽象类。
- 2. 虚函数可以被直接使用,也可以被子类重载以后,以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类有声明而没有定义。
- 3. 虚函数和纯虚函数都可以在子类中被重载,以多态的形式被调用。
- 4. 虚函数和纯虚函数通常存在于抽象基类之中,被继承的子类重载,目的是提供一个统一的接口。
- 5. 虚函数的定义形式: virtual{} ; 纯虚函数的定义形式: virtual { } = 0 ;
- 6. 在虚函数和纯虚函数 的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时要求前期绑定,然而虚函数却是动态绑定,而且被两者修饰的函数生命周期也不一样。
class A
{
public:virtual void foo(){cout << "A::foo() is called" << endl;}
};
class B :public A
{
public:void foo(){cout << "B::foo() is called" << endl;}
};
int main(void)
{A* a = new B();a->foo(); // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!return 0;
}
输出结果如下:
【说明】
- 这个例子是虚函数的一个典型应用,通过这个例子,也许你就对虚函数有了一些概念。它虚就虚在所谓“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时 刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以 被成为“虚”函数。
- 虚函数只能借助于指针或者引用来达到多态的效果。
virtual void funtion1()=0
3、虚函数的重写
概念:
- 是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重 写的函数一致。只有函数体不同(花括号内),派生类对象调用时会调用派生类的重写函数,不会调用被重写函数;
- 重写的基类中被重写的函数必须有virtual修饰。
接下来,我简单的用代码展示一下:
#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 ps;Student st;Func(ps);Func(st);return 0;
}
输出显示:
【注意】
- 在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因 为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用。
1️⃣虚函数重写的两个例外:
- 例如以下代码:
class a {};
class b : public a
{};class person
{
public:virtual a* f() { cout << "new A" << endl;return nullptr;}
};
class student : public person
{
public:virtual b* f() { cout << "new B" << endl;return nullptr;}
};void Func(person* p)
{p->f();delete p;
}int main()
{Func(new person);Func(new student);return 0;
}
输出显示:
而当我们想返回的是对象的时候,此时编译器就会发生报错:
【说明】
- 基类
Person
的虚函数f()
返回类型是A*
,而派生类Student
的重写函数f()
的返回类型是B*
,这违反了上述规则,因为B*
不是A*
的派生类。 - 在C++中,如果要进行虚函数重写,返回类型必须是完全匹配的,或者是基类返回类型的派生类。在这种情况下,你可以将
B*
转换为A*
,然后在函数中返回一个派生类对象的指针。
2️⃣析构函数的重写(基类与派生类析构函数的名字不同)
首先,我们先看这样的场景:
class Person
{
public:~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:~Student() { cout << "~Student()" << endl; }
};int main()
{Person p;Student s;return 0;
}
- 输出结果:
经过我们的分析,发现上述代码并没有问题。紧接着,我把代码改动一下,看最终的结果是什么!
- 具体如下:
【说明】
- 上述代码存在内存泄露问题。
- 在代码中,派生类
Student
是基类Person
的子类,并且在派生类中定义了析构函数。在main()
函数中,使用了动态内存分配来创建了两个对象p1
和p2
,分别指向Person
类型和Student
类型; - 然而,在释放这些动态分配的对象时,只调用了
delete
关键字,却没有使用虚析构函数。由于基类Person
的析构函数不是虚函数,因此在通过基类指针p2
删除指向派生类对象的指针时,将不会调用派生类Student
的析构函数,可能导致资源泄露。
为了解决这个问题:只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函 数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
- 修正后的代码如下所示:
【说明】
- 如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字, 都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同;
- 虽然函数名不相同, 看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处 理,编译后析构函数的名称统一处理成destructor。
4、C++11 override 和 final
- final:修饰虚函数,表示该虚函数不能再被重写
class Car
{
public:virtual void Drive() final {}
};
class Benz :public Car
{
public:virtual void Drive() { cout << "Benz-舒适" << endl; }
};
- override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Car {
public:void Drive(){}
};class Benz :public Car {
public:virtual void Drive() override {cout << "Benz-舒适" << endl; }
};int main()
{Car cc;Benz bb;cc.Drive();bb.Drive();return 0;
}
输出显示:

因此,想要达到相应的效果,我们需要在基类中用虚函数实现:
class Car {
public:virtual void Drive(){}
};class Benz :public Car {
public:virtual void Drive() override {cout << "Benz-舒适" << endl; }
};int main()
{Car cc;Benz bb;cc.Drive();bb.Drive();return 0;
}
5、重载、覆盖(重写)、隐藏(重定义)的对比(面试题)

上述对于重写我已经实现。接下来,我简单的实现一下剩下的两类:
- 我们在平时写代码中会用到几个函数但是他们的实现功能相同,但是有些细节却不同;
- 函数重载是 指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。
// 重载函数
void print(int num) {cout << "Integer: " << num << endl;
}void print(float num) {cout << "Float: " << num << endl;
}void print(const char* str) {cout << "String: " << str << endl;
}int main() {print(10); // 调用 print(int) 重载print(3.14f); // 调用 print(float) 重载print("Hello, World!"); // 调用 print(const char*) 重载return 0;
}
输出展示:
💨 隐藏(重定义)
- 在C++中,函数隐藏(Function Hiding)也称为函数重定义(Function Redefinition),指的是派生类中的函数隐藏了基类中的同名函数;
- 这种情况下,无法使用基类指针或引用调用派生类中隐藏的函数。
//函数隐藏
class Base
{
public:void print() {cout << "Base::print()" << endl;}
};class Derived : public Base
{
public:void print() {cout << "Derived::print()" << endl;}
};int main()
{Base base;Derived derived;base.print(); // 调用基类 Base 的 print()derived.print(); // 调用派生类 Derived 的 print()// 使用基类指针或引用调用派生类中隐藏的函数Base* basePtr = &derived;basePtr->print(); // 调用基类 Base 的 print(),派生类的函数被隐藏return 0;
}
输出演示:

【说明】
- 在上述示例中,我们创建了一个基类
Base
和一个派生类Derived
。两个类中都定义了名为print()
的函数,其中派生类Derived
的print()
函数隐藏了基类Base
中的同名函数。 - 在
main()
函数中,我们分别创建了一个基类对象base
和派生类对象derived
。然后通过调用base.print()
和derived.print()
,可以分别看到基类和派生类中的print()
函数的输出结果。 - 接下来,我们使用基类指针
basePtr
指向派生类对象derived
,然后通过basePtr->print()
调用print()
函数。这时,会发现调用的是基类Base
中的print()
函数,而派生类中的函数被隐藏而不可访问。
(三)抽象类
1、概念
- 例如以下代码:
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();
}
输出显示:
2、接口继承和实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实 现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
(四)多态的原理
1、虚函数表
首先,这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;char _ch;
};int main()
{Base b;cout << sizeof(Base) << endl;return 0;
}
输出显示:
【说明】
那么派生类中这个表放了些什么呢?我们接着往下分析:
- 1.我们增加一个派生类Derive去继承Base
- 2.Derive中重写Func1
- 3.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;
}
通过调试,我们可以得到以下这正图片:
【说明】
1. 派生类对象 d 中也有一个虚表指针, d 对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。2. 基类 b 对象和派生类 d 对象虚表是不一样的,这里我们发现 Func1 完成了重写,所以 d 的虚表 中存的是重写的 Derive::Func1 ,所以虚函数的重写也叫作覆盖 ,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。3. 另外 Func2 继承下来后是虚函数,所以放进了虚表, Func3 也继承下来了,但是不是虚函数,所以不会放进虚表。4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个 nullptr 。5. 总结一下派生类的虚表生成:
- a.先将基类中的虚表内容拷贝一份到派生类虚表中
- b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
- c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
6. 这里还有一个童鞋们很容易混淆的问题: 虚函数存在哪的?虚表存在哪的?
- 虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。
- 但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。
- 另外对象中存的不是虚表,存的是虚表指针。
2、多态的原理
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 mike;Func(mike);Student john;Func(john);return 0;
}
接下来,我们对代码进行调试观察:
【说明】
- 1. 观察上图的红色箭头我们看到,p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚 函数是Person::BuyTicket。
- 2. 观察下图的蓝色箭头我们看到,p是指向johnson对象时,p->BuyTicket在johson的虚表中 找到虚函数是Student::BuyTicket。
- 3. 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。
- 4. 反过来思考我们要达到多态,有两个条件,一个是虚函数覆盖,一个是对象的指针或引用调 用虚函数。反思一下为什么?
- 5. 再通过下面的汇编代码分析,看出满足多态以后的函数调用,不是在编译时确定的,是运行 起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。
💨 其次,我在带大家看下底层的汇编指令,看多态状态和不是多态状态下的场景:
3、动态绑定与静态绑定
以下是一个示例,展示了如何在 C++ 中实现静态绑定:
class Base
{
public:void display() {cout << "买票-全价" << endl;}
};class Derived : public Base
{
public:void display() {cout << "买票-半价" << endl;}
};int main() {Base baseObj;Derived derivedObj;baseObj.display(); // 静态绑定,调用 Base 类的 display() 函数derivedObj.display(); // 静态绑定,调用 Derived 类的 display() 函数return 0;
}
输出显示:
【说明】
- 由于函数调用使用的是静态绑定,编译器在编译时就知道要调用哪个函数。因此,
baseObj.display()
调用了Base
类的display()
函数,而derivedObj.display()
调用了Derived
类的display()
函数。
这就是 C++ 中如何实现静态绑定的方式。可以看出,在没有使用虚函数或基类指针/引用的情况下,默认使用的是静态绑定。
- 1. 虚函数。基类中必须有虚函数,在派生类中必须重写虚函数。
- 2. 通过基类类型的指针或引用来调用虚函数。
class Base {
public:virtual void display() {cout << "Base::display()" << endl;}
};class Derived : public Base {
public:void display() override // 使用 override 关键字指明这是一个重写的虚函数{ cout << "Derived::display()" << endl;}
};int main()
{Base baseObj;Derived derivedObj;Base* ptr1 = &baseObj; // 基类指针指向基类对象Base* ptr2 = &derivedObj; // 基类指针指向派生类对象ptr1->display(); // 动态绑定,调用 Base 类的 display() 函数ptr2->display(); // 动态绑定,调用 Derived 类的 display() 函数return 0;
}
输出展示:
【说明】
- 由于函数调用使用的是动态绑定,当我们通过指针调用
display()
函数时,实际调用的函数版本根据指针指向的对象类型来确定; - 因此,
ptr1->display()
调用了基类Base
的display()
函数,而ptr2->display()
调用了派生类Derived
的display()
函数。
这就是在 C++ 中实现动态绑定的方式。通过使用虚函数和基类指针/引用,我们能够在运行时根据对象的实际类型确定要调用的函数版本。
最后给大家推荐一篇文章,帮助大家更好的理解:
- C++ 虚函数表解析
总结
到此,关于多态相关的知识便讲解结束了。感谢大家的观看与支持!!!
相关文章:

【C++】—— 多态的基本介绍
前言: 在之前的学习过程中,我们已经对继承进行了详细的学习和了解。今天,我将带领大家学习的是关于 多态 的基本知识。 目录 (一)多态的概念 1、概念 (二)多态的定义及实现 1、多态的构成条…...

一文详解:自动化测试工具——Selenium
前言 Selenium是一个用于Web应用程序测试的工具。是一个开源的Web的自动化测试工具,最初是为网站自动化测试而开发的,类型像我们玩游戏用的按键精灵,可以按指定的命令自动操作,不同是Selenium可以直接运行在浏览器上,…...
[模版总结] - 集合划分类DFS模版
题目描述 给定一个数组,给定一个数字k, 问能不能讲数组内数等分成k份,使得每一个集合中数和相等。 题目链接 下面两道题问题及其类似,可作为同一类题目思考 Leetcode 698 Leetcode 473 题目思路 这道题是一道经典集合划分类问题&#…...
JavaScript中复制新的数组与原数组删除某个值——不影响新复制的数组的方法详解
系列文章目录 文章目录 系列文章目录前言一、使用slice()方法复制数组二、使用concat()方法复制数组三、使用扩展运算符(...)复制数组总结 前言 在JavaScript中,我们经常需要处理数组的复制和修改。本文将详细介绍如何在JavaScript中复制一个新的数组,并…...

easyui主表子表维护页面
easyui主表子表维护页面 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>Title</title><!-- <#include "common.html"/> --><link rel"stylesheet" type&quo…...
k8s exam
Pause 容器是 Pod 中的第一个启动的容器,其他所有的用户容器都是其子进程当 Pod 被从节点中删除时,与之关联的 emptyDir 中的数据也将被永久删除。持久存储用PV,PVCService 资源定义了如何访问应用,但实际的网络流量管理和路由是由…...

C#,中国福利彩票《刮刮乐》的数学算法(02)——时来运转
1 中国福利彩票 中国福利彩票始于1987年7月27日,以“团结各界热心社会福利事业的人士,发扬社会主义人道主义精神,筹集社会福利资金,兴办残疾人、老年人、孤儿福利事业和帮助有困难的人”、即“扶老、助残、救孤、济困”为宗旨。随…...

我的观影记录表【个人向】
目录 前言电影评分标准闪电侠(2023)银河护卫队3(2023) 前言 这里是我本人的观影记录,这个想法2年前就有了,但是一直比较懒,现在(上班摸鱼)准备重新开始,评价…...
网络安全策略应包含哪些?
网络安全策略是保护组织免受网络威胁的关键措施。良好的网络安全策略可以确保数据和系统的保密性、完整性和可用性。以下是一个典型的网络安全策略应包含的几个重要方面: 1. 强化密码策略:采用强密码,要求定期更换密码,并使用多因…...

【Git】Git GitHub
1. Git1.1 Git基本操作1.2 Git版本回退1.3 Git分支操作 2. Git 配合GitHub2.1 生成密钥2.2 GitHub添加公钥2.3 Git连接GitHub2.4 本地仓库关联远程仓库2.5 本地代码push远程仓库2.6 本地clone远程仓库2.7 本地fetch和pull 1. Git 1.1 Git基本操作 touch test.py 工作区创建文…...

[STL]详解list模拟实现
[STL]list模拟实现 文章目录 [STL]list模拟实现1. 整体结构总览2. 成员变量解析3. 默认成员函数构造函数1迭代器区间构造函数拷贝构造函数赋值运算符重载析构函数 4. 迭代器及相关函数迭代器整体结构总览迭代器的模拟实现begin函数和end函数begin函数和end函数const版本 5. 数据…...
C和C++的区别与联系
C语言(C)和C语言(C)是两种编程语言,它们之间有许多区别和联系。以下是它们之间的主要区别和联系: 区别: 历史和起源: C语言是由Dennis Ritchie于20世纪70年代初在贝尔实验室开发的。…...
springboot通过接口执行本地shell脚本
首先创建springboot项目 shell脚本 #!/bin/shecho Hello World!然后编写执行shell脚本的util类 import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List;pub…...

工欲善其事必先利其器,IT工作电脑更要维护好
目录 一:电脑的组成 二:维护措施 三:助力记忆 一:电脑的组成 当谈到电脑主机时,我们通常指的是电脑的中央处理器(CPU)、内存、主板、电源、硬盘、显卡、声卡、网卡等核心部件组成的整体。这些部件共同协作ÿ…...

移动端个人中心UI设计
效果图 源码如下 页面设计 <template><div class"container"><!-- 顶部用户信息 start--><div class"header"><div class"user-info"><van-image class"user-img" round width"70" :sr…...
开发接口,你需要先搞懂这些概念!
SOA Service Oriented Ambiguity 即面向服务架构, 简称SOA。 SOA的提出是在企业计算领域,就是要将紧耦合的系统,划分为面向业务的,粗粒度,松耦合,无状态的服务。服务发布出来供其他服务调用,一…...
zookeeper常用命令
zkClient 简介 zkClient是简易的客户端程序 进入zkClient 在bin目录下输入zkCli.sh 节点命令 增 create 路径 数据 -s:顺序节点 -e:临时节点 默认情况下,不添加-s或者-e参数的,创建的是持久节点改 set 路径 数据 版本…...

亚马逊水基灭火器UL8测试报告ISO17025实验室办理
在跨境电商平台上销售的境外电商,在美国市场中需要提供相关的安全规范报告。其中,美国相关部门要求,如果商家未能提交UL(Underwriters Laboratories)标准的检测报告,将会被责令停止销售。而为了在亚马逊、T…...
Shell学习脚本-if多分支结构
语法: if 条件then指令集 else指令集 fi特殊写法: if [ -f "$file1" ]; then echo 1; else echo 0; fi 相当于: [ -f "$file1" ] && echo 1 || echo 0 多分支结构: if 条件then指令 elif 条件th…...
[SQL挖掘机] - 窗口函数 - lead
介绍: lead() 是一种常用的窗口函数,它用于获取某一行之后的行的值。它可以用来在结果集中的当前行后面访问指定列的值。 用法: lead() 函数的语法如下: lead(列名, 偏移量, 默认值) over (partition by 列名1, 列名2, ... order by 列名 [asc|desc]…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...