【C++】Virtual function and Polymorphism

在这里插入图片描述

《C++程序设计基础教程》——刘厚泉,李政伟,二零一三年九月版,学习笔记


文章目录

  • 1、多态性的概念
  • 2、虚函数的定义
    • 2.1、引入虚函数的原因
    • 2.2、虚函数的定义与使用
    • 2.3、虚函数的限制
  • 3、抽象类
    • 3.1、纯虚函数
    • 3.2、抽象类
  • 4、应用实例


更多有趣的代码示例,可参考【Programming】


1、多态性的概念

多态性(Polymorphism)是面向对象编程(OOP)中的一个核心概念,它允许对象通过同一接口调用不同函数的行为。在 C++ 中,多态性主要通过虚函数(virtual functions)和继承(inheritance)来实现。多态性增强了程序的灵活性和可扩展性,使得代码更加通用和易于维护。

提供了同一个接口可以用多种方法调用的机制。简单概括为一个接口,多种方法

eg:函数的重载、运算符重载都是多态现象

多态性分为静态多态性(Static Polymorphism)动态多态性(Dynamic Polymorphism)

  • 静态多态性,也称为编译时多态性,主要通过函数重载(Function Overloading)和模板(Templates)来实现。这种多态性在编译时就已经确定,因此称为“静态”。
  • 动态多态性,也称为运行时多态性,主要通过虚函数(Virtual Functions)和继承(Inheritance)来实现。这种多态性在运行时根据对象的实际类型来决定调用哪个函数,因此称为“动态”。

在这里插入图片描述

多态性允许我们编写更通用的代码,这些代码可以与不同类型的对象一起工作,而不需要知道这些对象的具体类型。在 C++ 中,这通常通过基类指针或引用来调用派生类的重写函数来实现。

2、虚函数的定义

在C++中,虚函数(virtual function)是一种允许在派生类中重写基类方法的机制,以实现多态性。通过虚函数,程序可以在运行时根据对象的实际类型来调用相应的函数,而不是在编译时决定。动态多态性,动态联编。

2.1、引入虚函数的原因

1、实现动态多态性

  • 动态绑定:虚函数通过虚函数表(vtable)实现动态绑定,使得程序可以在运行时决定调用哪个函数。这与静态绑定(在编译时决定调用哪个函数)形成对比。

  • 灵活性:动态多态性提供了更大的灵活性,允许程序根据对象的实际类型来执行不同的操作,而无需在编译时知道对象的类型。

2、支持抽象基类和接口

  • 抽象基类:虚函数允许基类定义抽象接口,即只声明函数而不提供实现。派生类必须实现这些函数,从而确保它们符合基类的接口。

  • 接口设计:通过虚函数,可以设计出清晰、简洁的接口,使得代码更加模块化和可维护。

3、提高代码的可扩展性和可维护性

  • 可扩展性:通过虚函数,可以很容易地添加新的派生类,而无需修改现有的代码。只需实现基类的虚函数即可。
  • 可维护性:虚函数使得代码更加模块化,每个类只负责自己的实现。这有助于降低代码的复杂性,提高可维护性。

4、实现回调和事件处理

  • 回调机制:虚函数可以用于实现回调机制,允许程序在特定事件发生时调用用户定义的函数。
  • 事件处理:在图形用户界面(GUI)编程中,虚函数常用于处理用户事件,如按钮点击、鼠标移动等。

5、遵循开闭原则(Open/Closed Principle)

  • 开闭原则:软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改。虚函数使得类可以通过派生来扩展功能,而无需修改现有的代码。

一般对象的指针之间没有联系,彼此独立,不能混用。但派生类是由基类派生出来,它们之间有继承关系,因此,指向基类和派生类的指针之间也有一定的联系,如果使用不当,将会出现一些问题

eg 12-1 没有使用虚函数的例子

#include <iostream>
using namespace std;class Base
{private:int x,y;public:Base(int x=0, int y=0) // 带有默认值的构造函数{this->x = x;this->y = y;}void view(){cout << "Base:" << x << " " << y << endl; }
};class SubBase: public Base
{private:int z;public:SubBase(int x, int y, int z):Base(x,y){this->z = z;}void view(){cout << "SubBase:" << z << endl; }};int main() 
{Base obj(3,4), *objp;SubBase subobj(1,2,3);objp = &obj;objp->view();objp = &subobj;objp->view();return 0;
}

output

Base:3 4
Base:1 2

在 C++ 中,如果基类的成员函数不是虚函数,那么通过基类指针或引用调用该函数时,将始终调用基类的版本,而不会调用派生类的版本。这种行为称为静态绑定。

上述代码中,Base 类的 view 方法不是虚函数,因此即使 objp 指向一个 SubBase 对象,objp->view() 也会调用 Base 类的 view 方法。

原因是调用成员函数在编译时静态联编了

为解决这个问题,就要引入虚函数的概念

改进如下

#include <iostream>
using namespace std;class Base
{private:int x,y;public:Base(int x=0, int y=0) // 带有默认值的构造函数{this->x = x;this->y = y;}virtual void view(){cout << "Base:" << x << " " << y << endl; }
};class SubBase: public Base
{private:int z;public:SubBase(int x, int y, int z):Base(x,y){this->z = z;}void view(){cout << "SubBase:" << z << endl; }};int main() 
{Base obj(3,4), *objp;SubBase subobj(1,2,3);objp = &obj;objp->view();objp = &subobj;objp->view();return 0;
}

在基类的成员函数 view 前加上 virtual 即可

output

Base:3 4
SubBase:3

补充,派生类对象指针使用时应注意的问题

(1)声明为指向基类对象的指针可以指向它的公有派生类的对象,但不允许指向它的私有派生类的对象

(2)允许声明为指向基类对象的指针指向它的公有派生类的对象,但不允许将一个声明为指向派生类对象的指针指向基类的对象。

(3)声明为指向基类对象的指针,当其指向它的公有派生类的对象时,只能直接访问派生类中从基类继承下来的成员,不能直接访问公有派生类中定义的成员。要想访问其公有派生类中的成员,可将基类指针用显示类型转换方式转化为派生类指针。

2.2、虚函数的定义与使用

(1)虚函数的定义

当基类中的某个成员函数被声明为虚函数后,它就可以在派生类中被重新定义。

在派生类重新定义时,其函数原型,包括返回类型、函数名、参数个数和类型、参数的顺序都必须与基类的原型完全一致。

虚函数定义的一般形式为

virtual <函数类型> <函数名> (形参表)
{函数体
}

(2)虚函数与重载函数的关系

函数重载要求其函数的参数或参数类型必须不同,函数的返回类型也可以不同

虚函数被重新定义时,要求函数名、返回类型、参数个数、参数的类型和参数的顺序必须与基类中的虚函数原型完全相同(否则系统认为时普通的函数重载,虚函数的特性将丢失掉)

  • 绑定时间
    虚函数:动态绑定,在运行时决定调用哪个函数版本。
    重载函数:静态绑定,在编译时决定调用哪个函数版本。

  • 作用范围
    虚函数:涉及基类和派生类之间的多态性。
    重载函数:在同一作用域内,根据参数列表区分不同的函数版本。

  • 实现机制
    虚函数:通过虚函数表(vtable)实现动态绑定。
    重载函数:通过名称修饰(name mangling)和编译时类型检查实现静态绑定。

  • 多态性
    虚函数:支持多态性,允许通过基类接口操作派生类对象。
    重载函数:不支持多态性,仅在同一作用域内提供多个同名函数。

  • 函数签名
    虚函数:在基类和派生类中,函数签名(包括返回类型、函数名、参数列表)必须相同(或兼容),才能实现重写。
    重载函数:在同一作用域内,函数名相同但参数列表必须不同。

(3)虚函数与重载函数的结合使用

虚函数可以重载:在基类中,虚函数可以被重载,即基类可以有多个同名虚函数,但参数列表不同

派生类中的重写:派生类可以重写基类中的虚函数,但不能直接重写重载函数中的某一个版本;它必须根据参数列表重写相应的虚函数。

(4)多重继承和虚函数

一个虚函数无论被继承多少次,仍保持其虚函数的特性,与继承的次数无关。

#include <iostream>
using namespace std;class Base
{private:int x,y;public:Base(int x=0, int y=0) // 带有默认值的构造函数{this->x = x;this->y = y;}virtual void view(){cout << "Base:" << x << " " << y << endl; }
};class SubBase: public Base
{private:int z;public:SubBase(int x, int y, int z):Base(x,y){this->z = z;}void view(){cout << "SubBase:" << z << endl; }};class SubBase2: public SubBase
{private:int p;public:SubBase2(int x, int y, int z, int p):SubBase(x,y,z){this->p = p;}void view(){cout << "SubBase2:" << p << endl; }};int main() 
{Base obj(1,2), *objp;SubBase subobj(1,2,3);SubBase2 subobj2(1,2,3,4);objp = &obj;objp->view();objp = &subobj;objp->view();objp = &subobj2;objp->view();return 0;
}

output

Base:1 2
SubBase:3
SubBase2:4

2.3、虚函数的限制

(1)虚函数的声明只能出现在类声明的函数原型的声明中,不能出现在函数体实现中,同时基类中只有保护成员或公有成员才能被声明为虚函数

(2)在派生类中重新定义虚函数时,关键字 virtual 可以写也可以不写,但在容易引起混乱时,应该写上关键字

(3)动态联编只能通过成员函数来调用或通过指针、引用来访问虚函数,如果用对象名的形式来访问虚函数,将采用静态联编。

(4)虚函数必须时所在类的成员函数,不能是友元函数或静态成员函数。 但可以在另一个类中被声明为友元函数。

(5)构造函数不能声明为虚函数,析构函数可以声明为虚函数

(6)由于内联函数不能在运行中动态确定其外治,所以它不能声明为虚函数。

(7)虚函数通过虚函数表(vtable)实现动态绑定,这需要在运行时查找函数地址,相较于静态绑定(编译时决定调用哪个函数),会有一定的性能开销。

3、抽象类

抽象类是带有纯虚函数的类

3.1、纯虚函数

纯虚函数是C++面向对象编程中的一个重要概念,主要用于定义接口和抽象类。理解纯虚函数对于掌握C++的多态性和抽象类设计至关重要。

纯虚函数是在基类中声明的虚函数,它在基类中没有实现,要求任何派生类都必须提供自己的实现,除非派生类本身也是抽象类。

一个抽象类带有至少一个纯虚函数。

纯虚函数的一般定义形式为:

virtual <函数类型> <函数名> (参数表)=0;

纯虚函数与普通虚函数的定义的不同在于书写形式上加了 “=0”

#include <iostream>
using namespace std;// 抽象基类
class Shape {
public:virtual void draw() const = 0;  // 纯虚函数virtual ~Shape() {}             // 虚析构函数
};// 派生类
class Circle : public Shape {
public:void draw() const override {cout << "Drawing a Circle" << endl;}
};class Square : public Shape 
{public:void draw() const override {cout << "Drawing a Square" << endl;}
};int main() 
{Shape* shapes[2];shapes[0] = new Circle();shapes[1] = new Square();for (int i = 0; i < 2; ++i) {shapes[i]->draw();  // 调用派生类的 draw 方法}for (int i = 0; i < 2; ++i) {delete shapes[i];}return 0;
}

output

Drawing a Circle
Drawing a Square

注意事项

  • 不能实例化抽象类,当类声明中包含纯虚函数时,则不能创建该类的对象。这里,纯虚函数的类只用作基类。
  • 析构函数应为虚函数
  • 纯虚函数没有函数体
  • 最后的 =0 并不表示函数的返回值为 0,它只是形式上的作用,告诉编译系统这是纯虚函数。
  • 是一个声明语句,最后以分号结束
  • 多重继承中的纯虚函数:在多重继承中,如果多个基类有同名的纯虚函数,派生类需要明确重写该函数。

纯虚函数和虚函数的对比

在这里插入图片描述

3.2、抽象类

抽象类是包含至少一个纯虚函数的类,不能直接实例化。其主要作用是定义接口,强制派生类实现特定方法。

  • 纯虚函数:在基类中声明但没有实现的虚函数,要求派生类必须实现。
  • 无法实例化:抽象类不能创建对象,只能作为基类使用。
  • 设计契约:通过纯虚函数,抽象类定义了派生类必须遵循的接口。

比如抽象类为动物,可以派生出狮子、老虎、孔雀

比如设计一个游戏角色系统,不同角色类型实现特定的行为。

eg 12-4 使用抽象类访问队列与栈

注意队列是先进先出

栈是先进后出

注意:抽象类本身不能实例化,但可以定义指向抽象类对象的指针。这是因为指针只是存储对象的地址,并不需要实际创建对象。

#include <iostream>
using namespace std;class list  // 抽象类
{public:list *head, *tail, *next; // 定义头指针,尾指针和指向下一个结点的指针int num;list(){head = tail = next = nullptr; // 初始化}virtual void store(int i) = 0; // 纯虚函数,保存值virtual int retrieve() = 0;  // 纯虚函数,读取值
};class queue: public list  // 派生类
{public:void store(int i);  // 声明int retrieve();  // 声明
};void queue::store(int i) // 保存值
{list * item;item = new queue;if(!item){cout << "Allocation error\n";exit(1);}item->num = i;if(tail)tail->next = item;  // 将当前 tail 的 next 指向新节点tail = item; // 更新 tail 为新节点if(!head)head = tail;   // 如果队列为空,head 也应该指向新节点}int queue::retrieve()  // 读取值
{int i;list *p;if(!head){cout << "queue empty\n";return 0;}i = head->num;  // 先进先出p = head;            head = head->next;delete p;return i;
}class stack: public list  // 派生类
{public:void store(int i);  // 声明int retrieve();  // 声明
};void stack::store(int i)  // 保存值
{list *item;item = new stack;if (!item){cout << "Allocation error\n";exit(1);}item->num =i;if(head)item->next = head;head = item;if(!tail)tail = head;
}int stack::retrieve()  // 读取值
{int i;list *p;if(!head){cout << "stack empty\n";return 0;}i=head->num;p=head;head=head->next;delete p;return i;
}int main() 
{list *p;queue qb;p = &qb;p->store(1);p->store(2);p->store(3);cout << "Queue:";cout << p->retrieve();cout << p->retrieve();cout << p->retrieve();cout << endl;cout << p->retrieve();cout << endl;stack sb;p = &sb;p->store(1);p->store(2);p->store(3);cout << "Stack:";cout << p->retrieve();cout << p->retrieve();cout << p->retrieve();cout << endl;cout << p->retrieve();return 0;
}

output

Queue:123
queue empty
0
Stack:321
stack empty
0

例子中基类 list 是一个抽象类,其中定义了向单向列表中保存值的纯虚函数 store() 和从中读取值的纯虚函数 retrieve()

抽象类派生了两个队列类 queue 和栈类 stack,它们根据各自的需要,重新定义了纯虚函数 store()retrieve()

在这里插入图片描述

注意事项

(1)抽象类只能用作其它类的基类,不能建立抽象类的对象。但可以定义指向抽象类的指针

(2)抽象类不能用作参数类型、函数的返回类型或显示转换的类型。

(3)可以声明抽象类的指针和引用,通过它们,可以指向并访问派生类对象,从而访问派生类的成员

(4)若抽象类的派生类中没有给出所有的纯虚函数的函数体,这个派生类仍是一个抽象类。若抽象类的派生类中给出了所有的纯虚函数的函数体,这个派生类不再是一个抽象类,可以声明自己的对象。

4、应用实例

eg 12-5 先建立一个 Point 类,包含 x 和 y,以它为基类,派生出一个 Circle 类,增加数据成员 r,再以 Circle 类为直接基类,派生出一个 Cylinder(圆柱体)类,再增加数据成员 h。要求编写程序,重载运算符 <<>>,使之能用于输出以上类对象。

在 C++ 中,当我们在成员函数声明的末尾添加 const 关键字时,我们是在声明该成员函数为 常量成员函数(也称为只读成员函数)。这意味着该成员函数不会修改其所属对象的任何成员变量(除非这些成员变量被声明为 mutable)。

#include <iostream>
using namespace std;
#define PI 3.14159class Point
{protected:float x,y;public:Point(float xx, float yy) // 构造函数{x = xx;y = yy;}void setPoint(float xx, float yy)  // 设置坐标值{x = xx;y = yy;}float getX() const {return x;}  // 获取 x 坐标, 只读float getY() const {return y;} // 获取 y 坐标, 只读friend ostream &operator<<(ostream &, const Point &); // 重载运算符 <<, 声明为友元函数
};ostream &operator<<(ostream &output, const Point &p)
{output << "["<<p.x<<","<<p.y<<"]" << endl;return output;
}class Circle: public Point
{protected:float r;public:Circle(float x=0, float y=0, float r=0):Point(x,y){this->r = r;}void setRadius(float rr){ r=rr;}float getRadius() const { return r; }float area() const {return (PI *r *r); }friend ostream &operator<<(ostream &, const Circle &); // 重载运算符 <<, 声明为友元函数  
};ostream &operator<<(ostream &output, const Circle &c)
{output << "Center=["<<c.x<<","<<c.y<<"], Radium=" << c.r << ", Area=" << c.area() <<endl;return output;
}class Cylinder: public Circle  // 圆柱体
{protected:float h;public:Cylinder(float x=0, float y=0, float r=0, float h=0):Circle(x,y,r){this->h = h;}void setHeight(float hh) {h = hh;}float getHeight() const {return h;}float area() const {return (2*Circle::area() + 2*PI*r*h);} // 表面积float Volumn() const {return Circle::area()*h;}friend ostream &operator<<(ostream &, const Cylinder &); // 重载运算符 <<, 声明为友元函数  
};ostream &operator<<(ostream &output, const Cylinder &cy)
{output << "Center=["<<cy.x<<","<<cy.y<<"], Radium=" << cy.r << ", Height=" << cy.h << ", Area=" << cy.area() << ", Volumn=" << cy.Volumn() << endl;return output;
}int main() 
{// PointPoint p(3.5, 6.4);cout << "x=" << p.getX() << ", y=" << p.getY() << endl;p.setPoint(8.5, 6.8); cout << "p(new):" << p;// CircleCircle c(3.5,6.4,5.2);cout << "原来的圆的数据:x=" << c.getX() << ", y=" << c.getY() << ", r=" << c.getRadius() << ", area=" << c.area() << endl;c.setRadius(7.5);c.setPoint(5,5);cout << "修改后圆的数据:x=" << c.getX() << ", y=" << c.getY() << ", r=" << c.getRadius() << ", area=" << c.area() << endl;// CylinderCylinder cy(3.5, 6.4, 5.2, 10);cout << "原来的圆柱体的数据:x=" << cy.getX() << ", y=" << cy.getY() << ", r=" << cy.getRadius() << ", h=" << cy.getHeight() << ", area=" << cy.area() << ", volumn=" << cy.Volumn() << endl;cy.setHeight(15);cy.setRadius(7.5);cy.setPoint(5,5);cout << "修改后圆柱体的数据:x=" << cy.getX() << ", y=" << cy.getY() << ", r=" << cy.getRadius() << ", h=" << cy.getHeight() << ", area=" << cy.area() << ", volumn=" << cy.Volumn() << endl;// overload// << as PointPoint &pRef1 = cy;  // pRef 是 Point 类的引用变量,被 C 初始化cout << "Point:" << pRef1; // << as CircleCircle &pRef2 = cy;cout << "Circle:" << pRef2;// << as Cylindercout << cy;return 0;
}

output

x=3.5, y=6.4
p(new):[8.5,6.8]
原来的圆的数据:x=3.5, y=6.4, r=5.2, area=84.9486
修改后圆的数据:x=5, y=5, r=7.5, area=176.714
原来的圆柱体的数据:x=3.5, y=6.4, r=5.2, h=10, area=496.623, volumn=849.486
修改后圆柱体的数据:x=5, y=5, r=7.5, h=15, area=1060.29, volumn=2650.72
Point:[5,5]
Circle:Center=[5,5], Radium=7.5, Area=176.714
Center=[5,5], Radium=7.5, Height=15, Area=1060.29, Volumn=2650.72

上面的例子是有关继承和运算符重载内容的综合应用例子,更适合放在上个章节,哈哈

【C++】Inheritance and Derivation


更多有趣的代码示例,可参考【Programming】

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

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

相关文章

图解LLM智能体(LLM Agents):构建与运作机制的全面解析

LLM智能体:构建与运作机制 LLM智能体(LLM Agents)正在迅速普及,似乎逐渐取代了我们熟悉的传统对话式LLM。这些令人惊叹的能力并非凭空而来,而是需要多个组件协同工作。 本文包含超过60张定制插图,将深入探讨LLM智能体的领域、其核心组件以及多智能体框架的工作原理。 文…

自动驾驶背后的数学:特征提取中的线性变换与非线性激活

在上一篇博客「自动驾驶背后的数学&#xff1a;从传感器数据到控制指令的函数嵌套」—— 揭秘人工智能中的线性函数、ReLU 与复合函数中&#xff0c;我们初步探讨了自动驾驶技术中从传感器数据到控制指令的函数嵌套流程&#xff0c;其中提到了特征提取模块对传感器数据进行线性…

W80x使用WM IoT SDK 2.X 开发(二)驱动tft屏幕

一、硬件准备 开发板依然是官方送的w803&#xff0c;屏幕我的是2.4寸的ST7789 二、查看sdk 1、例程 tft的有这4个程序&#xff0c;我这里直接看最简单的polling吧 首先就是创建一个任务&#xff0c;这跟上一篇点亮led创建任务的步骤一样 继续点进去 2、spi初始化 先看初始…

Linux系统编程(四)--进程概念

文章目录 1.基本概念与基本操作1.1 描述进程-PCB1.2 task_struct-PCB的一种1.3 task_struct内容分类1.4 查看进程1.5 通过系统调用获取进程的PID和PPID1.6 PPID&#xff08;Parent Process ID&#xff09;1.7 通过系统调⽤创建进程-fork初识fork创建子进程使用if进行分流 2.进程…

从 0 到 1 掌握鸿蒙 AudioRenderer 音频渲染:我的自学笔记与踩坑实录(API 14)

最近我在研究 HarmonyOS 音频开发。在音视频领域&#xff0c;鸿蒙的 AudioKit 框架提供了 AVPlayer 和 AudioRenderer 两种方案。AVPlayer 适合快速实现播放功能&#xff0c;而 AudioRenderer 允许更底层的音频处理&#xff0c;适合定制化需求。本文将以一个开发者的自学视角&a…

linux 命令 cd

以下是 Linux 中 cd 命令的详细用法总结&#xff0c;涵盖基础操作、快捷方式和常见场景&#xff1a; 1. 命令功能 cd&#xff08;Change Directory&#xff09;用于切换当前工作目录&#xff0c;是 Linux 文件系统操作中最常用的命令之一。 2. 基本语法 cd [选项] [目录路径…

安卓开发调用本地接口以及设置base_url思路

去年接手pad端开发时曾问过其它组的老安卓一个问题&#xff0c;我们的安卓项目本地开发时能否调用本地接口&#xff0c;回答是否定的。也许是由于通用底座加入的限制&#xff0c;也许是因为太忙了&#xff0c;不想给我解释繁琐的解决方案。 那么在个人PC上玩耍总是能够调用本地…

中小型企业大数据平台全栈搭建:Hive+HDFS+YARN+Hue+ZooKeeper+MySQL+Sqoop+Azkaban 保姆级配置指南

目录 背景‌一、环境规划与依赖准备‌1. 服务器规划(3节点集群)2. 系统与依赖‌3. Hadoop生态组件版本与下载路径4. 架构图二、Hadoop(HDFS+YARN)安装与配置‌1. 下载与解压(所有节点)2. HDFS高可用配置3. YARN资源配置‌4. 启动Hadoop集群三、MySQL安装与Hive元数据配置…

003 SpringCloud整合-LogStash安装及ELK日志收集

SpringCloud整合-LogStash安装及ELK日志收集 文章目录 SpringCloud整合-LogStash安装及ELK日志收集1.安装ElasticSearch和kibana2.Docker安装logstash1.拉取docker镜像2.创建外部挂载目录3.拷贝容器内部文件到宿主机4.修改外部挂载文件5.运行docker容器 3.整合kibana1.进入kiba…

《TCP/IP网络编程》学习笔记 | Chapter 19:Windows 平台下线程的使用

《TCP/IP网络编程》学习笔记 | Chapter 19&#xff1a;Windows 平台下线程的使用 《TCP/IP网络编程》学习笔记 | Chapter 19&#xff1a;Windows 平台下线程的使用内核对象内核对象的定义内核对象归操作系统所有 基于 Windows 的线程创建进程与线程的关系Windows 中线程的创建方…

【Git学习笔记】Git分支管理策略及其结构原理分析

【Git学习笔记】Git分支管理策略及其结构原理分析 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;Git学习笔记 文章目录 【Git学习笔记】Git分支管理策略及其结构原理分析前言一.合并冲突二. 分支管理策略2.1 分支策略2.2 bug分支2.3 删除临…

STAR Decomposition 一种针对极端事件的信号分解方法 论文精读加复现

STAR 分解&#x1f680; 在时序预测任务中&#xff0c;为了情绪化信号的各种成分&#xff0c;例如趋势信息季节信息等往往都需要对信号进行分解。目前熟知的分解方式有很多种&#xff0c;经验模态分解 EMD 变分模态分解 VMD &#xff0c;还有 集合经验模态分解 EEMD&#xff0c…

大一新生备战蓝桥杯c/c++B组——2024年省赛真题解题+心得分享

一&#xff0c;握手问题 这个题用点像小学奥数&#xff0c;直接手算就行 答案&#xff1a;1204 二&#xff0c;小球反弹 这个题思路简单&#xff0c;但是运行会显示超时。在思考思考&#xff0c;后续补代码。 三&#xff0c;好数 思路一&#xff1a; #include <iostream&…

【最新版】智慧小区物业管理小程序源码+uniapp全开源

一.系统介绍 智慧小区物业管理小程序,包含小区物业缴费、房产管理、在线报修、业主活动报名、在线商城等功能。为物业量身打造的智慧小区运营管理系统,贴合物业工作场景,轻松提高物业费用收缴率,更有功能模块个性化组合,助力物业节约成本高效运营。 二.搭建环境 系统环…

OLE注册是什么?

在Windows操作系统的生态中&#xff0c;‌OLE&#xff08;Object Linking and Embedding&#xff0c;对象链接与嵌入&#xff09;‌ 是一项核心技术&#xff0c;它使得不同应用程序之间能够共享数据和功能。例如&#xff0c;用户可以在Word文档中嵌入一个Excel表格&#xff0c;…

深入理解Linux文件系统:从磁盘结构到inode与挂载

博客总结 核心内容 磁盘物理结构 机械硬盘&#xff08;HDD&#xff09;与固态硬盘&#xff08;SSD&#xff09;的区别&#xff0c;磁盘的组成&#xff08;盘片、磁头、磁道、扇区&#xff09;及工作原理&#xff08;磁头悬浮、高速旋转&#xff09;。 企业级磁盘与桌面级磁盘的…

Spring Data JPA 参数陷阱:从 500 错误到完美解决的奇妙之旅 ✨

&#x1f680; Spring Data JPA 参数陷阱&#xff1a;从 500 错误到完美解决的奇妙之旅 &#x1f31f; 嘿&#xff0c;各位技术冒险家&#xff01;&#x1f44b; 今天我要带你们走进一场 Spring Data JPA 的“参数迷雾”救援行动——从一个让人抓狂的 500 错误&#xff0c;到最…

YOLO obb全流程

内容&#xff1a;xanylabeling 数据标注工具&#xff1b;pytorch&#xff08;python&#xff09;&#xff1b;yolo-obb 模型 一、数据集 1、数据集工具xanylabeling的安装 &#xff08;详细配置与使用方法参考&#xff1a;X-Anylabeling自动标注软件安装使用教程含conda环境…

基于大语言模型与知识图谱的智能论文生成工具开发构想

基于大语言模型与知识图谱的智能论文生成工具开发构想 一、研究背景与意义 1.1 学术写作现状分析 #mermaid-svg-FNVHG5EiEgVSCpHK {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-FNVHG5EiEgVSCpHK .error-icon{fil…

学c++的人可以几天速通python?

学了俩天啊&#xff0c;文章写纸上了 还是蛮有趣的