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;
    }
    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博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode困难题----84.柱状图中的最大矩形

今天刷LeetCode时遇到了一个很有意思的题: 看了半天题解还是没理解他的代码想要表达的是什么意思,在思考了很久之后,终于,我理解了这道题,接下来让我带你们走进这道题。 这道题的大概意思是,给你一个heights[]数组,(宽为1)让你求出他们可以组合出的最大面积 首先,我们先用暴力法…

MQTTnet实现客户端连接

使用MQTTnet(Version=4.3.1.873)库实现多客户端连接多服务端,同时实现断线重连; 如下图所示,开启3个客户端连接3个服务端,当其一个服务端出现异常(服务停止,网络异常无法连接)导致连接断开时,实现每5秒连接一次 MQTT连接服务核心类:业务需求是一个客户端对应的一个MQ…

libVLC 设置视频宽高比

宽高比是指视频图像的宽度和高度之间的比率。 投影屏幕尺寸一般都按照对角线的大小来定义的。根据图像制式不同&#xff0c;屏幕的长宽比例也有几种格式&#xff1a; 传统影视的宽高比是 4&#xff1a;3&#xff0c;宽屏幕电影的宽高比是 1.85&#xff1a;1&#xff0c;高清晰…

python中获取当前项目的目录

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂 今天介绍一下&#xff0c;如何在python中获取当前项目所在的目录&#xff0c;而不是运行脚本的目录。 class ProjectPaths:# 初始化时获取当前脚本的路径staticmethoddef get_script_dir():…

C# 设置AutoScroll为true没效果的原因分析和解决办法

C#中添加tabControl 分页&#xff0c;将autoscroll设置为true发现缩小窗口没有滚动条效果。该问题出现后&#xff0c;检索发现也有很多人询问了该问题&#xff0c;但是都没有给出解决方案。 原因是内部button的属性Anchor设置为top、left、right、bottom导致的缩小界面窗口也没…

算法:一些DFS的经验

DFS:可以看作是向下遍历树的模拟 剪枝&#xff1a;减少时间复杂度 一个dfs所需要具备的元素&#xff1a; 一&#xff0c;出口 1.出口&#xff1a;每一个进入的dfs的出口&#xff0c;可以是枚举全部元素后退出该dfs,也可以是大于层数或剪枝条件........ 二&#xff0c;向下搜…

操作符(C语言)—第二期

赋值操作符 赋值操作符是一个很棒的操作符&#xff0c;他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。 int weight 120;//体重 weight 89;//不满意就赋值 double salary 10000.0; salary 20000.0;//使用赋值操作符赋值。赋值操作符可以连续使用&#x…

Design Script官方案例解析2:程序简写

在本练习中,我们将调整新的简写技能,以创建由范围和公式定义的精美蛋壳曲面。在本练习中,请注意我们如何串联使用代码块和现有 Dynamo 节点:我们将代码块用于繁重的数据提升,而 Dynamo 节点以可视方式布局来使定义清晰易读。 首先,通过连接上述节点创建曲面。请勿使用数字…

关于VMware Workstation Pro无法与Windows互相进行复制粘贴的解决方案

说明&#xff1a;要实现Windows在wmware虚拟机上实现复制粘贴需要在虚拟机上下载 VMware Tools 工具。 1.查看虚拟机是否下载了VMware Tools工具。&#xff08;下载了vMware Tools 会变成灰色的&#xff09; 2.要是成功安装的话&#xff0c;你在去改一下这里。 设置完到这里理…

Redis缓存穿透的几种解决方案

目录 缓存穿透原理&#xff1a; 缓存穿透一般有几种解决方案&#xff1a; 1.缓存空值 2.使用锁 3.布隆过滤器 优缺点 布隆过滤器误判理解 布隆过滤器的简单使用流程 4.组合方案 那么当我们高并发的访问短链接或者人为的去穿透的时候呢&#xff1f; 最近做项目遇到了缓…

多聆听,少评判

当朋友来找你倾诉、吐槽、诉苦&#xff0c;或是表达情绪的时候&#xff0c;你是怎样回应的&#xff1f; 许多人总有这样的习惯&#xff1a;每当听到朋友的倾诉&#xff0c;或者在网上看到别人诉苦时&#xff0c;第一反应往往是提建议&#xff1a;为什么你不试试这样做呢&#x…

代码随想录算法训练营第二十九天|491. 非递减子序列,46.全排列

491. 非递减子序列 题目 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素&#xff0c;如出现两个整数相等&#xff0c;也可以视作递增序列的一种…

Kubernetes中PV和PVC的几种状态类型

文章目录 1、PV和PVC概念1.1、PV1.2、PVC 2、PV / PVC的关系3、PV / PVC的状态类型3.1. Available&#xff08;可用&#xff09;3.2. Bound&#xff08;已绑定&#xff09;3.3. Released&#xff08;已释放&#xff09;3.4. Pending&#xff08;待定&#xff09;3.5. Failed&am…

关于UDP协议

UDP协议是基于非连接的发送数据就是把数据包简单封装一下&#xff0c;然后从网卡发出去就可以&#xff0c;数据包之间没有状态上的联系&#xff0c;UDP处理方式简单&#xff0c;所以性能损耗非常少&#xff0c;对于CPU、内存资源的占用远小于TCP&#xff0c;但是对于网络传输过…

设计编程网站集:生活部分:饮食+农业,植物(暂记)

这里写目录标题 植物相关综合教程**大型植物&#xff1a;****高大乔木&#xff08;Trees&#xff09;&#xff1a;** 具有坚硬的木质茎&#xff0c;通常高度超过6米。例如&#xff0c;橡树、松树、榉树等。松树梧桐 **灌木&#xff08;Shrubs&#xff09;&#xff1a;** 比乔木…

旧版本navicat更换颜色/护眼背景(利用regedit注册表编辑器 )

navicat默认的背景颜色是白色的&#xff0c;新版本可以如图直接在工具选项里面设置&#xff0c;可以先检查一下&#xff0c;如果没有相关设置&#xff0c;如果没有再往后看解决方法 另外&#xff0c;还可以安装其他护眼软件&#xff0c;但 若是设置里没有这个选项&#xff0c;…

webgl canvas系列——快速加背景、抠图、加水印并下载图片

文章目录 ⭐前言⭐canvas绘制图片&#x1f496;绘制csdn图片&#x1f496;给png图片加背景&#x1f496;cavans下载图片&#x1f496;cavans上传图片并抠图&#x1f496;cavans添加文字水印&#x1f496;inscode 完整代码块 ⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#x…

mysql性能调优

mysql性能调优 sysbench压测调优到百万级别qps sysbench压测调优到百万级别qps 这篇文章https://www.percona.com/blog/millions-queries-per-second-postgresql-and-mysql-peaceful-battle-at-modern-demanding-workloads/#:~:textWe%20contacted%20SysBench%20author%20Alex…

抖音,剪映,TikTok,竖屏短视频转场pr模板视频素材

120个叠加效果视频转场过渡素材&#xff0c;抖音,剪映,TikTok,短视频转场pr模板项目工程文件。 效果&#xff1a;VHS、光效、胶片、霓虹灯闪光、X射线、信号、老电影等。 适用软件&#xff1a;Adobe Premiere Pro 2018 12.0或更高版本。 视频素材与大多数应用程序兼容&#xff…

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.…