C++进阶02 多态性

听课笔记简单整理,供小伙伴们参考~🥝🥝

  • 第1版:听课的记录代码~🧩🧩

编辑:梅头脑🌸

审核:文心一言


目录

🐳课程来源

🐳前言

🐋运算符重载

🐋8.1 双目运算符:复数类加减法运算

🐋8.2 单目运算符:++时钟类成员函数

🐋8.3 输出运算符

🐋虚函数

🐋8.4 虚函数

🐋8.5 析构虚拟函数

🐳抽象类

🐋8.6 抽象类

🐳override与final

🐋8.7 override

🐋8.8 final

🔚结语


🐳课程来源

  • 郑莉李超老师的公开课:🌸C++语言程序设计进阶 - 学堂在线 (xuetangx.com)

🐳前言

📇相关概念

多态性是C++中一个非常重要的概念,它允许我们使用共同的接口来处理不同类型的对象。这让我想起了刚刚在短视频里看到的海洋魔术师——拟态章鱼🐙。这只戏精章鱼可以根据环境和需要,通过伪装来变化自己的形态,从而有效地躲避捕食者。

在C++编程中,多态性也展现了类似的特点。它允许我们根据程序的实际需求,以多种形态或方式来执行同一功能。这主要是通过虚函数、继承和动态绑定等机制来实现的。

  • 虚函数:多态性的实现基础之一。通过在基类中声明虚函数,并在派生类中重写这个函数,我们可以实现当通过基类指针或引用调用该函数时,实际执行的是派生类中的函数版本;
  • 继承:通过继承,派生类不仅可以继承基类的数据和函数,还可以重写基类的虚函数以实现特定的功能或行为;
  • 动态绑定:在使用基类指针或引用调用虚函数时,程序不会在编译时确定函数调用的目标,而是在运行时根据对象的实际类型进行绑定。这样,就可以确保调用的是正确版本的函数。

多态性不仅提高了代码的灵活性和可重用性,还使得程序能够更加模块化和易于维护。它是面向对象编程中的一个核心概念,为我们提供了一种高效、灵活的处理不同对象的方式。

此外,本节课还有一些关于函数重载的内容~

  • 函数重载:允许我们为同一个函数名定义多个版本,每个版本接受不同类型或数量的参数。例如,我们可以为类设计一个 ++ 运算符的重载版本,使时钟的秒针增加一秒~
拟态章鱼,一只庞大的戏精

🐋运算符重载

这里的内容主要是关于怎么为我们的类设计运算符,使其实现我们想要的功能~

🐋8.1 双目运算符:复数类加减法运算

在写代码时,我们发现加减法通常只能作用于实数。如果需要虚数做加减法怎么办呢?没错,自己写一个加法。注意,重载运算符的话,函数名的结构为:

类名 operator 运算符(参数类型,参数名)

注意:

  • 如果运算符为双目运算符(运算需要两个操作数),那么参数名写一个,因为默认第一个操作数是我们指定类名的对象;
  • 如果运算符为单目运算符,那么参数名可以空着不写;

以下是个简单的例子,为Complex类重载了+-运算符,使它们能够用于复数对象的加法和减法~

⌨️复数与复数的加减法代码

#include <iostream>
using namespace std;
class Complex {
public:Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { }//运算符+重载成员函数Complex operator + (const Complex& c2) const;//运算符-重载成员函数Complex operator - (const Complex& c2) const;void display() const;   //输出复数
private:double real;    //复数实部double imag;    //复数虚部
};Complex Complex::operator+(const Complex & c2) const {//创建一个临时无名对象作为返回值 ,当前对象的实部与参数的实部相加,当前对象的虚部与参数的虚部相加~return Complex(real + c2.real, imag + c2.imag);
}Complex Complex::operator-(const Complex& c2) const {//创建一个临时无名对象作为返回值return Complex(real - c2.real, imag - c2.imag);
}void Complex::display() const {cout << "(" << real << ", " << imag << ")" << endl;
}int main() {Complex c1(5, 4), c2(2, 10), c3;cout << "c1 = "; c1.display();cout << "c2 = "; c2.display();c3 = c1 - c2;   //使用重载运算符完成复数减法cout << "c3 = c1 - c2 = "; c3.display();c3 = c1 + c2;   //使用重载运算符完成复数加法cout << "c3 = c1 + c2 = "; c3.display();return 0;
}

📇执行结果

那如果想实现实数与复数相加减,可能需要考虑 实数在前,实数在后的两种情况。

  • 实数在后:与类相加的情况是类似的,把第二个操作数改为 int 即可运行;
  • 实数在前:把第一个操作数指定为 int,第二个操作数 指定为类,是不可行的(除非,是将虚数隐式地转化为实数加减法),编译器会报错:它很不讲道理,认为第一个操作数就应该是类;这样与我们的需求就会有冲突,所以需要将重载函数实现为非成员函数(友元函数)

综合以上情况,我们可以写这样的代码~

⌨️复数与实数的加法代码

#include <iostream>  
using namespace std;class Complex {
public:Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}// 复数与复数相加  Complex operator+(const Complex& c2) const;// 复数与整数相加  Complex operator+(const int x) const;// 输出复数  void display() const;// 友元函数:整数与复数相加  friend Complex operator+(const int x, const Complex& c);private:double real;    // 复数实部  double imag;    // 复数虚部  
};// 复数与复数相加的实现  
Complex Complex::operator+(const Complex& c) const {return Complex(real + c.real, imag + c.imag);
}// 复数与整数相加的实现  
Complex Complex::operator+(const int x) const {return Complex(real + x, imag);
}// 整数与复数相加的友元函数实现  
Complex operator+(const int x, const Complex& c) {return Complex(x + c.real, c.imag);
}// 输出复数的实现  
void Complex::display() const {cout << "(" << real << ", " << imag << ")" << endl;
}int main() {Complex c1(5, 4), c2(2, 10), c3;int x = 3;int y = 5;cout << "c1 = ";c1.display();cout << "c2 = ";c2.display();c3 = c1 + c2;  // 复数与复数相加  cout << "c3 = c1 + c2 = ";c3.display();c3 = c1 + x;   // 复数与整数相加  cout << "c3 = c1 + " << x << " = ";c3.display();c3 = y + c2;   // 整数与复数相加  cout << "c3 = " << y << " + c2 = ";c3.display();return 0;
}

📇执行结果

🐋8.2 单目运算符:++时钟类成员函数

⌨️8.2 重载前置++和后置++为时钟类成员函数代码

#include <iostream>
using namespace std;class Clock {//时钟类定义
public:Clock(int hour = 0, int minute = 0, int second = 0);void showTime() const;//前置单目运算符重载Clock& operator ++ ();//后置单目运算符重载,名称无法与前置区分,因此需要通过 形参int 区分两个函数 Clock operator ++ (int);
private:int hour, minute, second;
};Clock::Clock(int hour, int minute, int second) {if (0 <= hour && hour < 24 && 0 <= minute && minute < 60&& 0 <= second && second < 60) {this->hour = hour;this->minute = minute;this->second = second;}elsecout << "Time error!" << endl;
}
void Clock::showTime() const {  //显示时间cout << hour << ":" << minute << ":" << second << endl;
}Clock & Clock::operator ++ () {second++;if (second >= 60) {second -= 60;  minute++;if (minute >= 60) {minute -= 60; hour = (hour + 1) % 24;}}return *this;   // 返回自己,因为前置++ 的功能为先自增,后赋值;
}Clock Clock::operator ++ (int) {//注意形参表中的整型参数Clock old = *this;++(*this);  //调用前置“++”运算符,统一两边的算法;但返回的是old,也就是也复制,后自增;return old;
}int main() {Clock myClock(23, 59, 59);cout << "First time output: ";myClock.showTime();cout << "Show myClock++:    ";(myClock++).showTime();cout << "Show ++myClock:    ";(++myClock).showTime();return 0;
}

📇执行结果

📇代码说明

以上代码实现了增加1秒钟的运算~

前置单目运算符:

  • 重载函数没有形参;
  • 秒钟自增,若满足进位条件就开始进位计算;
  • 最后返回自己的指针;

后置单目运算符:

  • 重载函数需要有一个int形参(若不与前置单目运算符在参数表有区分,编译器会完全不知道该执行哪个...);
  • 记录自己现在的值;
  • 调用前置单目运算符,完成自增的同时实现了代码重用,方便管理;
  • 但是返回的值,是自增前的值。而内存存储的实际值,是自增后的值;

因此,在结果上:

  • 先执行后置自增,返回23,59,59,内存的实际值0,0,0;
  • 再执行前置自增,返回0,0,1,内存的实际值0,0,1;

🐋8.3 输出运算符

在执行复数的加减法时,我们使用成员函数display来显示复数的值,如下所示:

void Complex::display() const {cout << "(" << real << ", " << imag << ")" << endl;
}

然而,如果我们认为每次调用display()函数都比较麻烦,我们也可以实现类的级联输出,它允许我们连续输出多个对象而不需要中断。

为了实现级联输出,我们需要重载“<<”运算符。重载后的“<<”运算符将返回一个ostream对象,这样它就可以被连续调用,从而实现级联输出。

在本例中,我们重载了“<<”运算符,使其能够输出Complex对象,就像这样:

ostream& operator<<(ostream& out, const Complex& c) {  // 通过重载<<运算符,我们实现了Complex对象的级联输出  out << "(" << c.real << ", " << c.imag << ")"; // 输出复数的实部和虚部  return out; // 返回ostream对象以实现连续调用  
}

因此,我们通常将<<运算符重载为全局函数,并将其声明为类的友元函数,以便访问类的私有成员,而无需通过类的公共接口。这就是级联输出通常不作为成员函数实现的原因。

⌨️输出运算符代码

#include <iostream>
using namespace std;class Complex {
public:Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { }friend Complex operator+(const Complex& c1, const Complex& c2); // 不再是类的成员函数,而是一个全局函数friend Complex operator-(const Complex& c1, const Complex& c2);friend ostream& operator<<(ostream& out, const Complex& c);
private:double real;  //复数实部double imag;  //复数虚部
};Complex operator+(const Complex& c1, const Complex& c2) {return Complex(c1.real + c2.real, c1.imag + c2.imag);   // 实部与虚部相加减,返回临时无名对象
}
Complex operator-(const Complex& c1, const Complex& c2) {return Complex(c1.real - c2.real, c1.imag - c2.imag);
}ostream& operator<<(ostream& out, const Complex& c) {       // 通过ostream,重载输出运算符,使之级联输出out << "(" << c.real << ", " << c.imag << ")";return out;
}int main() {Complex c1(5, 4), c2(2, 10), c3;cout << "c1 = " << c1 << endl;cout << "c2 = " << c2 << endl;c3 = c1 - c2;   //使用重载运算符完成复数减法cout << "c3 = c1 - c2 = " << c3 << endl;c3 = c1 + c2;   //使用重载运算符完成复数加法cout << "c3 = c1 + c2 = " << c3 << endl;return 0;
}

📇执行结果

🐋虚函数

🐋8.4 虚函数

关于虚继承,这个之前我们有介绍过,篇幅很长就不再赘述了,感兴趣可以看向这里~

🌸C++进阶01 继承与派生-CSDN博客

当类声明了虚函数时,编译器会为该类生成一个虚函数表(vtable),表中包含指向虚函数实现的指针。类的实例中则包含一个指向这个虚函数表的指针(通常称为vptr)。在运行时,当通过基类的指针或引用调用虚函数时,会根据vptr来查找虚函数表,并进而调用与对象实际类型相匹配的函数实现。

虚函数的主要用途是实现动态绑定(dynamic binding),这样程序就能在运行时根据对象的实际类型来确定应该调用哪个函数实现。在涉及多继承的复杂情况中,虚函数表和相关的调用机制确保始终调用正确的函数,避免了因继承结构而导致的调用歧义。

🐋8.5 析构虚拟函数

大多数情况下,编译器自动生成的析构函数就足够用了。但是,有时我们需要自己定义一个析构函数,特别是在涉及到动态内存分配或者需要执行一些特殊清理任务的时候~

⌨️没有析构虚函数的代码

#include <iostream>
using namespace std;
class Base {
public:Base();~Base(); //不是虚函数
};
Base::Base() {cout << "Base constructor" << endl;
}
Base::~Base() {cout << "Base destructor" << endl;
}
class Derived : public Base {
public:Derived();~Derived(); //不是虚函数
private:int* p;
};
Derived::Derived() {cout << "Derived constructor" << endl;p = new int(0);
}
Derived::~Derived() {cout << "Derived destructor" << endl;delete p;
}
void func(Base* b) {delete b;   // 静态绑定,只会调用Base的析构函数,不会调用Derived的析构函数
}
int main() {Base* b = new Derived();func(b);return 0;
}

📇执行结果

📇代码解释

通过本行代码“Base* b = new Derived();”,我们创造了一个Derived对象,并通过一个Base类的指针来指向它。然而,在销毁这个对象的时候,只有Base类的析构函数被调用,而Derived类的析构函数却没有被调用。这意味着Derived对象中动态分配的内存(通过new操作符分配的int)没有被正确释放,从而导致了内存泄漏。

⌨️含有析构虚函数的代码 

#include <iostream>
using namespace std;
class Base {
public:virtual ~Base();	// 虚析构函数
};
Base::~Base() {cout << "Base destructor" << endl;
}
class Derived : public Base {
public:Derived();virtual ~Derived();
private:int* p;
};
Derived::Derived() {p = new int(0);
}
Derived::~Derived() {cout << "Derived destructor" << endl;delete p;
}
void func(Base* b) {delete b;			 // 动态绑定,会调用Derived的析构函数
}
int main() {Base* b = new Derived();func(b);return 0;
}

📇执行结果

当我们在基类中将析构函数声明为虚函数时,就可以确保在删除指向派生类对象的基类指针时,首先调用派生类的析构函数,然后调用基类的析构函数。这样可以避免内存泄漏和其他潜在的清理问题。

通过引入虚析构函数,我们可以确保动态绑定的正确执行,即在运行时确定应该调用哪个类的析构函数。这就像为基类指针提供了一个“哆啦A梦的传送门”,让它能够正确地找到并销毁派生类对象。


🐳抽象类

🐋8.6 抽象类

📇相关概念

还有一些时候,我们的基类不想定义具体的类。例如,动物园里有狮子、熊猫、猴子等,我们可以想象到狮子、熊猫、猴子是什么样子的,他们都属于动物。但是说到动物这个很抽象的名词,就很难想得到它长什么样子,它是一种泛指,也就类似于我们的抽象基类。

抽象基类的实现如下,我们通过代码展示如何创建抽象类,以及如何通过继承调用派生类的函数~

⌨️代码

#include <iostream>
using namespace std;class Base1 {
public:virtual void display() const = 0;   // 抽象类,纯虚函数,不能定义对象,只能通过派生类的对象调用
};class Base2 : public Base1 {
public:virtual void display() const;       // 覆盖基类的虚函数,可以定义对象
};
void Base2::display() const {cout << "Base2::display()" << endl;
}class Derived : public Base2 {
public:virtual void display() const;       // 覆盖基类的虚函数,可以定义对象
};
void Derived::display() const {cout << "Derived::display()" << endl;
}
void fun(Base1* ptr) {ptr->display();
}
int main() {Base2 base2;Derived derived;fun(&base2);fun(&derived);return 0;
}

📇执行结果

  • 抽象类:包含一个或多个纯虚函数的类被称为抽象类。纯虚函数是在基类中声明但没有实现的虚函数,其声明形式为 virtual 函数类型 函数名(参数列表) = 0;。抽象类不能被直接实例化来创建对象。在例子中,Base1 是一个抽象类,因为它有一个纯虚函数 display()

  • 继承与实现:其他类可以通过继承抽象类来成为它的派生类,并提供纯虚函数的实现。在例子中,Base2 和 Derived 都继承了 Base1 并实现了 display() 函数,因此它们都可以被实例化。

  • 多态性:通过基类的指针或引用调用虚函数时,会调用相应对象实际类型的虚函数实现,这就是多态性。在例子中,fun() 函数接受一个指向 Base1 的指针作为参数,并调用其 display() 函数。由于 Base2 和 Derived 都提供了这个函数的实现,并且都继承自 Base1,所以可以传入指向这两个类中任何一个的指针,fun() 函数都会正确地调用相应的 display() 函数实现。这就是多态性的一个例子。


🐳override与final

🐋8.7 override

📇相关概念

有的时候,我们自己想写一个派生类实现对基类的覆盖,但是往往差那么一点点,例如只差了一个const,结果导致编译器把两个同名函数认为成两个不同的函数。这类型错误不属于编译型错误,很难排查。

这个时候就可以借助override,在写代码的时候拜托编译器帮助我们检查一下,有没有正确覆盖同名基类的函数,没有的话他就会提醒我们语法可能有问题,保证我们可以正确地实现多态性~

⌨️错误代码

#include <iostream>
using namespace std;class Base {
public:virtual void f1(int) const;virtual ~Base() { };
};void Base::f1(int) const {cout << "Base::f1" << endl;return;
}class Derived : public Base {
public:void f1(int);   // 错误:是否含有const会影响覆盖,void f1(int); 与 void f1(int) const于不同的函数,影响调用的多态性~Derived() { };
};void Derived::f1(int) {cout << "Derived::f1" << endl;return;
}int main()
{Base *b;b = new Base;b->f1(1);b = new Derived;b->f1(1);return 0;
}

📇执行结果

两次都调用了基类函数,而非调用一个基类函数,一个派生类函数。emm...这是因为编译器还没办法区分“virtual void f1(int) const;”与“void f1(int);”,因为差一个const,所以编译器认为,写f1()的去找基类,写f1 const()的才可以找派生类。

⌨️正确代码

#include <iostream>
using namespace std;class Base {
public:virtual void f1(int) const;virtual ~Base() { };
};void Base::f1(int) const {cout << "Base::f1" << endl;return;
}class Derived : public Base {
public:void f1(int) const override;  // 正确,使用override关键字可以检查是否覆盖了基类的虚函数~Derived() { };
};void Derived::f1(int) const{cout << "Derived::f1" << endl;return;
}int main()
{Base* b;b = new Base;b->f1(1);b = new Derived;b->f1(1);return 0;
}

📇执行结果 

成功输出了一个基类一个派生类,实现了多态化。

override 关键字:这是 C++11 引入的一个特性,用于显式地指出派生类中的成员函数意图覆盖基类中的虚函数。这样做有两个好处:

  1. 它使你的意图更清晰,提高了代码的可读性。
  2. 编译器会检查你是否正确地覆盖了基类中的虚函数。如果你声明了一个与基类虚函数不匹配(函数签名不同)的成员函数,并试图使用 override 关键字,编译器会报错。

🐋8.8 final

📇相关概念

当你觉得你这个代码不想被别人修改使用(无论是因为代码本身比较重要,亦或是因为你本人比较任性),那么可以用final,防止别人继承自己的类或虚函数~

⌨️代码

// 8.8 final.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。struct Base1 final {};struct Derived1 : Base1 {}; // 类继承编译错误:Base1 已经被声明为 final,不能被继承struct Base2 {virtual void f() final;
};struct Derived2 : Base2 {void f() override;	    // 函数继承编译错误:Base2::f 已经被声明为 final,不能被覆盖
};

🔚结语

博文到此结束了,写得模糊或者有误之处,期待小伙伴留言讨论与批评,督促博主优化内容{例如有错误、难理解、不简洁、缺功能}等,博主会顶锅前来修改~~😶‍🌫️😶‍🌫️

我是梅头脑,本片博文若有帮助,欢迎小伙伴动动可爱的小手默默给个赞支持一下,感谢点赞小伙伴对于博主的支持~~🌟🌟

同系列的博文:🌸数据结构_梅头脑_的博客-CSDN博客

同博主的博文:🌸随笔03 笔记整理-CSDN博客

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

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

相关文章

JAVA安全(偏基础)

SQL注入 SQLI(SQL Injection)&#xff0c; SQL注入是因为程序未能正确对用户的输入进行检查&#xff0c;将用户的输入以拼接的方式带入SQL语句&#xff0c;导致了SQL注入的产生。攻击者可通过SQL注入直接获取数据库信息&#xff0c;造成信息泄漏。 JDBC JDBC有两个方法获取s…

数据挖掘之关联规则

“啤酒和尿布的荣誉” 概念 项 item&#xff1a;单个的事物个体 &#xff0c;I{i1,i2…im}是所有项的集合&#xff0c;|I|m是项的总数项集&#xff08;item set)/模式&#xff08;pattern)&#xff1a;项的集合&#xff0c;包含k个项的项集称为k-项集数据集(data set)/数据库…

Linux快速入门,上手开发 02.VMware的安装部署

倘若穷途末路&#xff0c;那便势如破竹 —— 24.3.21 一、VMware的作用 在Windows或IOS系统下&#xff0c;给本地电脑安装VMware虚拟机&#xff0c;用来在虚拟机上安装Linux系统&#xff0c;避免重复资源的浪费&#xff0c;可以在虚拟机上搭建Linux系统进行学习 二、VMware的安…

树莓派夜视摄像头拍摄红外LED灯

NoIR相机是一种特殊类型的红外摄像头&#xff0c;其名称来源于"No Infrared"的缩写。与普通的彩色摄像头不同&#xff0c;NoIR相机具备红外摄影和低光条件下摄影的能力。 一般摄像头能够感知可见光&#xff0c;并用于普通摄影和视频拍摄。而NoIR相机则在设计上去除了…

Python基础----函数(持续更新中)

函数 函数的定义 函数是组织好的&#xff0c;可以重复使用的&#xff0c;用来实现单一的&#xff0c;或相关的代码段 函数&#xff1a;可以自己定义&#xff0c;也可以调用python中的第三方函数&#xff0c;print() 函数非调用不执行 自定义函数 1、函数以关键字 def 开头&…

C语言疑难题:杨辉三角形、辗转相除求最大公约数、求π的近似值、兔子问题、打印菱形

杨辉三角形&#xff1a;打印杨辉三角形的前10行 /* 杨辉三角形&#xff1a;打印杨辉三角形的前10行 */ #include<stdio.h> int main(){ int i,j; int a[10][10]; printf("\n"); for(i0;i<10;i){ a[i][0]1; a[i][i]1; …

PSNR/SSIM/LPIPS图像质量评估三件套(含代码)

在图像质量评估上&#xff0c;有三个重要指标&#xff1a;PSNR&#xff0c;SSIM&#xff0c;LPIPS。本文提供简易脚本分别实现。 PSNR&#xff0c;峰值信噪比&#xff0c;是基于MSE的像素比较低质量评估&#xff0c;一般30dB以上质量就不错&#xff0c;到40dB以上肉眼就很难分…

Java例子

例题1 – 需要有 6中解决方式. 铁道部发布了一个售票任务&#xff0c;要求销售1000张票&#xff0c;要求有3个窗口来进行销售&#xff0c;请编写多线程程序来模拟这个效果 &#xff08;该题涉及到线程安全&#xff0c;https://www.jb51.net/article/221008.htm&#xff09;   …

compose学习

compose学习链接 Alertdialog | 你好 Compose

委托复习【C#】

原因&#xff1a; 主窗体内&#xff0c;新建子窗体后&#xff1b; 主窗体可以调用子窗体的【属性】【方法】等&#xff1b; 但是子窗体内无法调用主窗体的【属性】【方法】。 解决办法&#xff1a; 只有全局的属性和方法&#xff0c;才能被任意调用。 所以&#xff0c;2个窗体相…

零基础学python:10、 函数的基础3

函数 1. 生成式 列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。 #需求:生成一个1~10的整数列表 list1 = list(range(1,11)) print(list1) #[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]#需求:生成列表:[1*1, 2*2, 3*3, 4*4, 5*5, 6*6, …

C# WinForms应用程序中,FileSystemWatcher使用监视文件系统更改

在C# WinForms应用程序中&#xff0c;FileSystemWatcher 类用于监视文件系统更改&#xff0c;例如文件的创建、删除、修改以及目录的更改。以下是一个使用 FileSystemWatcher 的简单示例&#xff0c;展示了如何在WinForms应用程序中设置和使用它&#xff1a; 首先&#xff0c;…

ROS机器人入门第一课:ROS快速体验——python实现HelloWorld

文章目录 ROS机器人入门第一课&#xff1a;ROS快速体验——python实现HelloWorld一、HelloWorld实现简介&#xff08;一&#xff09;创建工作空间并初始化&#xff08;二&#xff09;进入 src 创建 ros 包并添加依赖 二、HelloWorld(Python版)&#xff08;二&#xff09;进入 r…

Java JDK8新日期API

一、 JDK8 中增加了一套全新的日期时间 API&#xff0c;这套 API 设计合理&#xff0c;是线程安全的。 java.time – 包含值对象的基础包java.time.chrono – 提供对不同的日历系统的访问java.time.format – 格式化和解析时间和日期java.time.temporal – 包括底层框架和扩展…

考研数学老师怎么选❓看这一篇就够了

张宇、汤家凤、武忠祥、李永乐、杨超、王式安、方浩这些老师都有自己擅长的细分 比如张宇老师&#xff0c;杨超&#xff0c;汤家凤&#xff0c;武忠祥老师的高数讲的很好&#xff0c;李永乐老师是线代的神&#xff0c;王式安、方浩概率论讲的很好&#xff0c;所以对于不同的学…

【文末附gpt升级4.0方案】FastGPT详解

FastGPT知识库结构讲解 FastGPT是一个基于GPT模型的知识库&#xff0c;它的结构可以分为以下几个部分&#xff1a; 1. 数据收集&#xff1a;FastGPT的知识库是通过从互联网上收集大量的文本数据来构建的。这些数据可以包括维基百科、新闻文章、论坛帖子等各种类型的文本。 2…

【openCV】手写算式识别

OpenCV 机器学习库提供了一系列 SVM 函数和类来实现 SVM 模型的训练和预测&#xff0c;方便用户实现自己的 SVM 模型&#xff0c;并应用于分类问题。本文主要介绍使用 openCV 实现手写算式识别的工作原理与实现过程。 目录 1 SVM 模型 1.1 SVM 模型介绍 1.2 SVM 模型原理 2…

在DelayMS加入bsp_Idle,把单片机延时空闲利用起来

在单片机应用中&#xff0c;使用延时函数 DelayMS() 会导致程序在延时期间无法执行其他任务&#xff0c; 这可能影响系统对一些响应时间要求较高的任务的处理。 为了提高系统的响应速度和利用单片机的空闲时间&#xff0c;可以在延时函数中加入 bsp_Idle() 函数&#xff0c; 以…

3.21系统栈、数据结构栈、栈的基本操作、队列、队列的基本操作------------》

栈 先进后出、后进先出 一、系统栈 大小&#xff1a;8MB 1、局部变量 2、未经初始化为随机值 3、代码执行到变量定义时为变量开辟空间 4、当变量的作用域结束时回收空间 5、函数的形参和返回值 6、函数的调用关系、保护现场和恢复现场 7、栈的增长方向&#xff0c;自高…

5.1.5、【AI技术新纪元:Spring AI解码】HuggingFace Chat

HuggingFace 推理端点允许您在云中部署和提供机器学习模型,使其可以通过 API 访问。 入门 关于 HuggingFace 推理端点的更多详细信息可以在此处找到。 先决条件 添加 spring-ai-huggingface 依赖项: <dependency><groupId>org.springframework.ai</groupId…