C++进阶02 多态性

news2024/9/23 13:29:03

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

  • 第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;
    }
    else
        cout << "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.coloradmin.cn/o/1534592.html

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

相关文章

1.3 Python是什么

Python是什么&#xff0c;Python简介 Python 是荷兰人 Guido van Rossum &#xff08;吉多范罗苏姆&#xff0c;中国程序员称其为“龟叔”&#xff09;在 1990 年初开发的一种解释型编程语言。 图1&#xff1a;Python 的标志&#xff08;Logo&#xff09; Python 的诞生是极具…

CSS问题精粹1

1.关于消除<li>列表前的符号 我相信很多人在初学CSS时会遇到该问题&#xff0c;无论是创作导航&#xff0c;还是列表&#xff0c;前面都会有个黑点点或其它符号。 解决该问题其实很简单 采用list-style-type:none或list-style:none直接解决 如果你想更换前面的黑点点&a…

计算机网络2 TCP/IP协议

目录 1 前言2 传输层2.1 端口号2.2 UDP2.3 TCP 3 网络层3.1 IP 4 数据链路层4.1 以太网4.2 ARP 5 DNS6 NAT 1 前言 2 传输层 2.1 端口号 端口号又分为&#xff1a; 知名端口&#xff1a;知名程序在启动之后占用的端口号&#xff0c;0-1023。 HTTP, FTP, SSH等这些广为使用的…

LeetCode 21 / 100

目录 矩阵矩阵置零螺旋矩阵旋转图像搜索二维矩阵 II LeetCode 73. 矩阵置零 LeetCode 54. 螺旋矩阵 LeetCode 48. 旋转图像 LeetCode 240. 搜索二维矩阵 II 矩阵 矩阵置零 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为…

Git的 .gitignore文件及标签使用

Git的 .gitignore文件及标签使用 什么是.gitignoregit check-ignore -v 文件名 查看.gitignore里面什么内容忽略了该文件 git add -f [filename] 强制添加把指定文件排除在 .gitignore 规则外的写法给命令配置别名标签创建标签git tag [name] 创建标签git tag 列出所有标签git …

ES高可用

分布式搜索引擎ES 分布式搜索引擎ES1.数据聚合1.1.聚合的种类1.2.DSL实现聚合1.3.RestAPI实现聚合 2.自动补全2.1.拼音分词器2.2.自定义分词器2.3.自动补全查询2.4.实现酒店搜索框自动补全 3.数据同步思路分析 4.集群4.1 ES集群相关概念4.2.集群脑裂问题4.3.集群分布式存储4.4.…

【文末附gpt升级4.0方案】英特尔AI PC的局限性是什么

为什么要推出英特尔AI PC&#xff1f; 英特尔AI PC的推出无疑为AIGC&#xff08;生成式人工智能&#xff09;的未来发展开启了一扇新的大门。这种新型的计算机平台&#xff0c;通过集成先进的硬件技术和优化的软件算法&#xff0c;为AIGC提供了更为强大和高效的支持&#xff0…

掌握增长转化漏斗策略的秘诀:打造高效营销之道

在不断发展的销售和营销领域&#xff0c;传统战略通常遵循一条可预测的路径&#xff0c;引导潜在客户通过漏斗&#xff0c;最终实现销售。然而&#xff0c;一种有趣的方法颠覆了这一传统模式&#xff1a;增长漏斗策略。这种创新方法重新规划了客户旅程&#xff0c;强调了培养现…

矩形总面积(第十四届蓝桥杯JavaB组省赛真题)

测试用例范围比较大&#xff0c;所以全部用long类型&#xff0c;如果用int类型只能通过60%&#xff0c;建议在内存和运行时间允许的情况下&#xff0c;比赛题都用long。 重点在于计算相交的面积&#xff0c;这里找的两个相交点是左上角&#xff08;m1,n1&#xff09;和右下角&a…

二叉搜索树(二叉排序树)(含力扣相关题及题解)

文章目录 二叉搜索树&#xff08;二叉排序树&#xff09;1、二叉搜索树概念2、二叉搜索树的操作2.1、二叉搜索树的查找2.2、二叉搜索树的插入2.2、二叉树的删除 3、二叉搜索树的实现&#xff08;含递归版本&#xff09;4、二叉搜索树的应用4.1、K模型4.2、KV模型 5、二叉搜索树…

5.MySQL创建表单和用户

1.数据库的创建 2.创建表单 3.创建用户 创建好用户之后&#xff0c;让用户只能访问一个表的权限 再创建一个数据库&#xff0c;用户名是刚刚创建的用户&#xff0c;密码是自己设置的密码&#xff0c;这样就缩小了权限。

2024.3.21

qt实现登录界面 #include "mainwindow.h" #include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);//设置纯净窗口this->setWindowFlag(Qt::FramelessWindowHint);/…

电影aac是什么意思?如何播放、转换、编辑aac?

"电影AAC"这个术语可能是指电影中的音频编码格式。AAC&#xff08;Advanced Audio Coding&#xff09;是一种常见的音频编码格式&#xff0c;通常用于压缩音频文件&#xff0c;以在保持高质量的同时减小文件大小。在电影中&#xff0c;AAC格式的音频通常用于提供高质…

深入解析Mybatis-Plus框架:简化Java持久层开发(十二)

&#x1f340; 前言 博客地址&#xff1a; CSDN&#xff1a;https://blog.csdn.net/powerbiubiu &#x1f44b; 简介 本章节介绍如何通过Mybatis-Plus进行实现批量新增。 &#x1f4d6; 正文 1 为何要批量插入&#xff1f; 前面章节已经介绍&#xff0c;Mapper接口只有一个…

那些场景需要额外注意线程安全问题

主要学习那些场景需要额外注意线程安全问题&#xff0c;在这里总结了四中场景。 访问共享变量或资源 第一种场景是访问共享变量或共享资源的时候&#xff0c;典型的场景有访问共享对象的属性&#xff0c;访问static静态变量&#xff0c;访问共享的缓存&#xff0c;等等。因为…

React 系列 之 React Hooks(一) JSX本质、理解Hooks

借鉴自极客时间《React Hooks 核心原理与实战》 JSX语法的本质 可以认为JSX是一种语法糖&#xff0c;允许将html和js代码进行结合。 JSX文件会通过babel编译成js文件 下面有一段JSX代码&#xff0c;实现了一个Counter组件 import React from "react";export defau…

【机器学习】深入解析线性回归模型

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;机器学习 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

原创!分解+集成思想新模型!VMD-CNN-BiGRU-Attention一键实现时间序列预测!以风速数据集为例

声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 目录 数据介绍 模型流程 创新点 结果展示 部…

【系统架构设计师】计算机系统基础知识 03

系统架构设计师 - 系列文章目录 01 系统工程与信息系统基础 02 软件架构设计 03 计算机系统基础知识 文章目录 系统架构设计师 - 系列文章目录 文章目录 前言 一、计算机系统概述 1.计算机组成 ​编辑2.存储系统 二、操作系统 ★★★★ 1.进程管理 2.存储管理 1.页式存储 …