【CPP】类与多态

news2024/10/9 7:15:08

目录

      • 15 类与多态
        • 15.1 如何理解多态
        • 15.2 动态绑定
        • 15.3 虚函数及重写的生效方式
        • 15.4 动态多态的生效方式
        • 15.5 坑
        • 15.6 协变
        • 15.7 析构函数的重写
        • 15.8 CPP11新增关键字
          • 15.8.1 `override`
          • 15.8.2 `final`
        • 15.8 纯虚函数与抽象类
        • 15.9 虚函数表指针和虚函数表
          • 15.9.1 粗看虚函数表指针和虚函数表
          • 15.9.2 虚函数表指针和虚函数表的一些细节
        • 15.2 进一步理解多态(静态多态和动态多态)

这里是oldking呐呐,感谢阅读口牙!先赞后看,养成习惯!
个人主页:oldking呐呐
专栏主页:深入CPP语法口牙

15 类与多态

15.1 如何理解多态
  • 我们举个简单的例子,我们可以定义一个类:“人”,"人"里有个方法是吃,同时定义一个人是否有胃病,要知道,正常情况下,人类的吃吃喝喝都没啥问题,一旦这个人有肠胃炎啥的,那生冷辣咸的东西可能就不能吃了

  • 接着,我们再定义一个食物作为父类,冰淇淋作为食物的子类,再定义一个鸡汤,同样作为食物的子类

class Food
{
public:
	Food(int calorie = 0) :_calorie(calorie) {}
	
	//这里是虚函数,我们在下一小节再细讲
	virtual void digest(bool stomach_trouble)
	{
		cout << "OK" << endl;
	}

protected:
	int _calorie;
};

class Ice_cream : public Food
{
public:
	Ice_cream(int calorie = 0) :Food(calorie) {}

	//覆写了父类的方法
	void digest(bool stomach_trouble)
	{
		if (stomach_trouble)
		{
			cout << "not OK" << endl;
		}
		else
		{
			cout << "OK" << endl;
		}
	}
};

class Chicken_soup : public Food
{
public:
	Chicken_soup(int calorie = 0) :Food(calorie) {}

	//覆写了父类的方法
	void digest(bool stomach_trouble)
	{
		cout << "OK" << endl;
	}
};

class Person
{
public:
	Person(bool stomach_trouble = false) :_stomach_trouble(stomach_trouble) {}

	//食物这么多,我们不可能单独进行定义,这样太浪费时间了
	//所以我们把食物作为一个父类,我们只需要传子类具体的食物进来就可以了
	//记不记得咱之前提到过,父类类型的引用可以引用子类中父类的部分,这里就用到了
	void eat(Food& food) { food.digest(_stomach_trouble); }

protected:
	bool _stomach_trouble;
};

int main()
{
	Person p1(false);
	Person p2(true);

	Ice_cream ice_cream(1000);
	Chicken_soup chicken_soup(2000);

	p1.eat(ice_cream);
	p2.eat(ice_cream);

	cout << endl;

	p1.eat(chicken_soup);
	p2.eat(chicken_soup);

	return 0;
}

//输出:
/*
OK
not OK

OK
OK
*/
  • 食物一般被吃下去,即感到饱腹,满足,所以在父类Food中我们向上面的例子一样定义函数,但如果根据情况,不是所有食物在任何情况下吃下去都会有这种感觉,所以在某些食物中,我们覆写了父类Food的方法,类似于冰淇淋

  • 覆写的概念很重要,从字面意思咱就知道,子类的情况可能会和父类不太一样,此时我们就可以覆写掉父类的方法,改成我们想让子类所呈现的模式,当然,这里的覆写在底层的角度上其实说不上是覆写,这个后面的小节会提到

  • 所以,简单说,从代码层面说,多态就是允许将不同的参数传入相同的函数而产生不同的结果(这句话是片面的),用于模拟生活中的各种情况

  • 例如典中典的火车票的例子,设计一个自助售票函数,设计一个父类为乘客,设计一个子类继承自父类为学生,再设计一个子类继承自父类为军人,每个种乘客类型可以买不同价格的票,当这类对象作为参数(传引用/指针)传进一个为父类"乘客"的自助售票函数中,根据函数判断生成符合当前乘客身份的票

  • 如果没有get到的话我们可以接着看下面的章节,感受一下多态的思想

15.2 动态绑定
  • 如上例中,函数eat实际上是调用了子类中的函数实现的不同效果
  • 事实上,函数eat通过子类的父类型的引用所调用的函数,编译时不会确定下来,因为具体是哪个函数是不确定的,只有当运行时才会被调用,我们称这种情况为动态绑定
15.3 虚函数及重写的生效方式
  • 虚函数即在父类中允许子类进行重写的函数(也可以指子类中被重写过的新的函数),还允许在函数中通过类型父类但指向子类的指针或引用直接访问

  • 我们接着看上一小节的例子

class Food
{
public:
	Food(int calorie = 0) :_calorie(calorie) {}
	
	virtual void digest(bool stomach_trouble)
	{
		cout << "OK" << endl;
	}

protected:
	int _calorie;
};

class Ice_cream : public Food
{
public:
	Ice_cream(int calorie = 0) :Food(calorie) {}

	//注意这里的override,这个关键字在CPP11被引入
	//本身含义不大,但可以提高代码的可读性,告诉程序员这个函数是被覆写的
	//还可以检查一下函数名写错的小漏洞
	void digest(bool stomach_trouble) override
	{
		if (stomach_trouble)
		{
			cout << "not OK" << endl;
		}
		else
		{
			cout << "OK" << endl;
		}
	}
};

class Chicken_soup : public Food
{
public:
	Chicken_soup(int calorie = 0) :Food(calorie) {}

	void digest(bool stomach_trouble) override
	{
		cout << "OK" << endl;
	}
};

class Person
{
public:
	Person(bool stomach_trouble = false) :_stomach_trouble(stomach_trouble) {}

	void eat(Food& food) { food.digest(_stomach_trouble); }
	//在这里,虽然引用的类型是父类,但实际上还是指向的子类,因此能够调用子类重写过的函数

protected:
	bool _stomach_trouble;
};
  • 我们在父类定义虚函数,考虑到子类可能会在某些地方与父类不同而并不是简单的在父类的基础上进行扩充,就可以在子类中重写这个方法

  • 不难看出,重写的生效方式非常简单

    1. 首先,被重写的函数必须是父类的虚函数(加virtual)
    2. 子类中覆写父类的函数必须在返回值,函数名,参数类型及其数量(参数名不做要求)都与父类中被重写的虚函数相同

Ps: 子类中覆写父类的虚函数可以不加virtualoverride,当然我一般选择virtualoverride二选一加上以保持一定的可读性

15.4 动态多态的生效方式
  • 动态多态我们可以暂时的理解为形如以上的例子的调用方式,需要满足两个重要条件

    1. 通过类型为父类但指向子类的指针或引用来调用子类中的虚函数
    2. 子类中的虚函数需要是覆写过父类的虚函数
15.5 坑
class A
{
public:
	virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
	virtual void test() { func(); }
};

class B : public A
{
	void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};

int main()
{
	B* p = new B;
	p->test();
	return 0;
}
  • Q: 输出什么?

  • 分析

class A
{
public:
	virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
	//注意注意!!!这里的test()的this指针的类型一定是A*
	virtual void test() { func(); }
};

//B继承自A
class B : public A
{
	//虽然B中没有virtual,缺省值也不一样,但依旧构成覆写,但是,覆写后的函数的函数头还是覆写前的函数头,哪怕是缺省值也是覆写前的
	//这就是为什么覆写后的函数头可以不加virtual,因为直接沿用了覆写前的函数头
	//且不管加不加,一定会沿用覆写前的函数头,这就导致了缺省值都和覆写前一样(可以理解为覆写仅仅只是覆写函数体而不包括函数头!)
	void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};

int main()
{
	//这里类型为B*的p指向的是B对象
	B* p = new B;
	//因为这里p的类型是B*,所以这里传进去的this指针的类型也是B*,由test中类型为A*的this指针接收,所以调用B::func()
	p->test();

	//如果是以下这种情况,就不会构成多态了,

	return 0;
}
  • 综上,输出了一个完全不像是正确答案的答案,即B->1
15.6 协变
  • 协变允许子类虚函数的返回值不跟随父类,但要求是返回指向子类的指针/引用(可以不是当前类,可以是其他)

  • 同时也允许父类虚函数返回指向父类的指针/引用(同样可以是其他类)

class A
{
public:
	virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
	virtual void test() { func(); }
};

class B : public A
{
public:
	void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};

class C
{
public:
	virtual A& func1(int val) {}
};

class D : public C
{
public:
	B& func1(int val) override {}
};
15.7 析构函数的重写
  • 这俩类的设计乍一看可能没什么问题,正常调用的话也是先调用~B()然后自动调用~A()
class A
{
public:
	A(int a = 0) :_a(a) {}

	~A()
	{
		cout << "~A()" << endl;
	}

protected:
	int _a;
};

class B : public A
{
public:
	B() :_b(new int) {}

	~B()
	{
		cout << "~B()" << endl;
		delete _b;
	}

protected:
	int* _b;
};
  • 但有一种调用情况会出现极大的问题
int main()
{
	//我们仔细看,这里pb指向的内容开辟在堆上,且是一个子类
	//将这个在子类的指针传给父类指针,实际上是指向的子类对象的父类部分
	A* pb = new B;

	//我们假设中间要做某些操作	
	//...

	//最终我们想要释放pb所指向的空间的时候
	//由于子类和父类的析构函数并未构成重载
	//导致指向子类的父类类型的指针只能调用到父类的析构函数
	//这就导致子类的一个开辟在堆上的成员函数无法释放了
	//最终造成了内存泄漏
	delete pb;

	return 0;
}
  • 所以就设计出了析构函数的重写
class A
{
public:
	A(int a = 0) :_a(a) {}

	virtual ~A()
	{
		cout << "~A()" << endl;
	}

protected:
	int _a;
};

class B : public A
{
public:
	B() :_b(new int) {}

	//在调用了子类的析构之后,由于析构默认的特性,又会去调用父类的析构,以保证释放时先子后父的原则
	~B() override
	{
		cout << "~B()" << endl;
		delete _b;
	}

protected:
	int* _b;
};


int main()
{
	A* pa = new A;
	A* pb = new B;

	delete pa;
	delete pb;

	return 0;
}
  • 析构函数重写的独特之处在于,不要求其函数名相同(其实本质上析构函数的函数名会在编译的过程中改成destructor,所以本质上函数名是相同的)

  • 换句话说,正是存在这个特殊的场景,所以才会需要把析构的函数名改成destructor以覆写

15.8 CPP11新增关键字
15.8.1 override
  • 可以检测一些小bug,验证是否构成重写,同时增加可读性,前面的例子有演示,就不再提了
15.8.2 final
  • 如果不想让子类重写这个函数,可以加上final关键字

请添加图片描述

15.8 纯虚函数与抽象类
  • 生活中可以实例化出具体的对象的东西有很多,但往往有一些名词在某些场景不适合实例化出具体的东西,比如说"汽车"这个名词,放在二手车网站上就不太合适,因为这是一个大的概念而非具体的车型和型号
  • 所以我们就可以定义出纯虚函数和抽象类
//这就是一个抽象类
class Car
{
public:
	//这就是一个纯虚函数
	virtual void drive() = 0;
};

class BMW : public Car
{
public:
	void drive() override
	{
		cout << "操控型" << endl;
	}
};

class Benz : public Car
{
public:
	void drive() override
	{
		cout << "舒适型" << endl;
	}
};

int main()
{
	//不可实例化抽象类
	//Car c;

	return 0;
}

Ps:如果在子类不重写父类的虚函数,那子类也会变成抽象类

15.9 虚函数表指针和虚函数表
15.9.1 粗看虚函数表指针和虚函数表
  • 前面我们说过子类可以覆写父类的虚函数,如何证明这一点呢?

  • 我们可以将全部食物类全部实例化看一下

int main()
{
	Person p1(false);
	Person p2(true);

	//将所有食物类全部实例化
	Food food(500);
	Ice_cream ice_cream(1000);
	Chicken_soup chicken_soup(2000);

	p1.eat(ice_cream);
	p2.eat(ice_cream);

	cout << endl;

	p1.eat(chicken_soup);
	p2.eat(chicken_soup);

	return 0;
}

请添加图片描述

  • 我们观察一下这个调试窗口,不难发现这个父类的区域中竟然有一个类型为void**名为_vfptr的指针

  • 可以看到,这里虽然子类中都包含有父类的部分,但父类的部分中,digest方法的类域竟然是子类的类域

  • 意味着虽然都是父类的区域,但不同的子类类型,这个指针指向的区域不一样

  • 我们再在这基础上看看

// X86 !!!!

class Food
{
public:
	Food(int calorie = 0) :_calorie(calorie) {}

	virtual void digest(bool stomach_trouble)
	{
		cout << "OK" << endl;
	}

protected:
	int _calorie;
};

class Ice_cream : public Food
{
public:
	Ice_cream(int calorie = 0) :Food(calorie) {}

	void digest(bool stomach_trouble)
	{
		if (stomach_trouble)
		{
			cout << "not OK" << endl;
		}
		else
		{
			cout << "OK" << endl;
		}
	}
};

class Chicken_soup : public Food
{
public:
	Chicken_soup(int calorie = 0) :Food(calorie) {}

	void digest(bool stomach_trouble)
	{
		cout << "OK" << endl;
	}
};

class Person
{
public:
	Person(bool stomach_trouble = false) :_stomach_trouble(stomach_trouble) {}

	void eat(Food& food) { food.digest(_stomach_trouble); }

protected:
	bool _stomach_trouble;
};

int main()
{
	Person p1(false);
	Person p2(true);

	Ice_cream ice_cream(1000);
	Chicken_soup chicken_soup(2000);

	cout << sizeof(Ice_cream) << endl;
	cout << sizeof(Chicken_soup) << endl;
	cout << sizeof(Food) << endl;

	return 0;
}

//输出:
//8
//8
//8
  • 按理讲,不管是父类还是子类,因为只包含一个bool类型的成员变量,其大小在对齐之后应该都是4才对,为什么会是8呢?

  • 不妨与这个_vfptr指针联想一下,不难猜出这多的4字节就是这个_vfptr指针

  • 这个指针我们称为虚函数表指针,简而言之就是指向一个表的指针

  • 我们对上面的例子做一下改进,方便理解虚函数表

class Food
{
public:
	Food(int calorie = 0) :_calorie(calorie) {}

	virtual void digest(bool stomach_trouble)
	{
		cout << "OK" << endl;
	}

	virtual void func() { cout << "Food::func()" << endl; }

protected:
	int _calorie;
};

class Ice_cream : public Food
{
public:
	Ice_cream(int calorie = 0) :Food(calorie) {}

	void digest(bool stomach_trouble) override
	{
		if (stomach_trouble)
		{
			cout << "not OK" << endl;
		}
		else
		{
			cout << "OK" << endl;
		}
	}

	void func() override { cout << "Ice_cream::func()" << endl; }
};

class Chicken_soup : public Food
{
public:
	Chicken_soup(int calorie = 0) :Food(calorie) {}

	void digest(bool stomach_trouble) override
	{
		cout << "OK" << endl;
	}

	void func() override { cout << "Chicken_soup::func()" << endl; }
};
  • 我们在每个类中都加了一个函数,从监视中可以看到,每个类都有一个自己的虚函数表

请添加图片描述

  • 这意味着,父类指针依旧指向子类中父类的部分,只不过编译器用某种方法把__vfptr的指针改到对象自己的虚函数表上了

  • 虚函数表和虚函数表指针是在对象被创建时就生成的,编译器会把重写过的函数的指针通通塞到表里,等到要用的时候只需要在此对象的虚函数表里找具体的函数

请添加图片描述

  • 我们知道,函数实际并不会存放到类的区域中,而是会存放到代码段中,一旦想要类调用此函数,在汇编层面就是直接call到相应的地址去
15.9.2 虚函数表指针和虚函数表的一些细节
  • 编译器编译时,根据父类生成虚函数表,子类会直接拷贝(继承)父类的虚函数表给子类用来放虚函数表的空间

  • 然后编译器会检查子类有没有重写父类的虚函数,如果有,就用重写的函数指针替换掉虚函数表中对应的父类的被重写的函数的函数指针

  • 最后检查子类中有没有单独定义虚函数,如果有,也一并塞到虚函数表里

  • 所以我们可以说,一种类型对象的虚函数表可以包含有从父类继承下来的虚函数,也有重写了父类的虚函数,也有父类没有,但在自己这边定义的虚函数

  • Ps1:同种类型的对象指向的虚函数表是一样的,不会额外再开空间放虚函数表,不同类型的对象的虚函数表不同(子类和父类就不会是同一个虚函数表)

  • Ps2:VS下的虚函数表的末尾会存一个空指针,g++就没有

  • Ps3(重要):虚函数存在在代码段,但虚函数表存在哪里没有规定,VS下也是存在代码段

15.2 进一步理解多态(静态多态和动态多态)
  • 多态多态,即同一类型事物的多种形态,官方一点的说法是,为不同的数据类型提供统一的接口

  • 静态多态的体现,可以是函数的重载,也可以是函数模板的实例化

    • 关于函数的重载,我定义多个同名的函数,其中构成函数重载,虽然调用的函数名相同,但我可以传入不同的参数达到不同的结果,即为多数据类型的传入提供统一的接口
    • 函数模板也是同理,我根据函数模板生成的函数,函数名(接口名)相同,但数据类型可以多种多样
    • 而是否静态的体现,则在于所调用的函数在编译之后是否是确定的,在函数重载和函数模板的例子中,编译生成了机器码之后,实际所需要调用的函数是被确定的,可以是重载中已经被程序员显式定义出来了,也可以是编译器根据函数模板生成的函数,这些都在编译完之后直接被确定下来了
  • 动态多态的体现,一般体现在函数通过统一的父类接口,以达成不同对象传入导致不同结果的设计

  • 这种设计由于虚函数表的存在,导致编译器不会在设定call的函数地址的时候直接找到具体的函数地址,而是程序运行之后再在虚函数表中查找所需函数

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

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

相关文章

List子接口

1.特点&#xff1a;有序&#xff0c;有下标&#xff0c;元素可以重复 2.方法&#xff1a;包含Collection中的所有方法&#xff0c;还包括自己的独有的方法&#xff08;API中查找&#xff09; 还有ListIterator&#xff08;迭代器&#xff09;&#xff0c;功能更强大。 包含更多…

一篇文章快速认识YOLO11 | 关键改进点 | 安装使用 | 模型训练和推理

前言 本文分享YOLO11的关键改进点、性能对比、安装使用、模型训练和推理等内容。 YOLO11 是 Ultralytics 最新的实时目标检测器&#xff0c;凭借更高的精度、速度和效率重新定义了可能性。 除了传统的目标检测外&#xff0c;YOLO11 还支持目标跟踪、实例分割、姿态估计、OBB…

ACM介绍

ACM介绍 1. ACM简介 ACM&#xff08;Association for Computing Machinery&#xff0c;国际计算机学会&#xff09;是世界上最大的计算机领域专业性学术组织&#xff0c;汇集了国际计算机领域教育家、研究人员、工业界人士及学生。ACM致力于提高在中国的活动的规格与影响力&a…

【微服务】—SpringBoot入门

⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记仓库&#x1f449;https://github.com/A-BigTree/tree-learning-notes 个人主页&#x1f449;https://www.abigtree.top ⭐⭐⭐⭐⭐⭐ 文章目录 1 SpringBoot快速入门1.1 SpringBoot简介1.1.1 简介1.1.2…

新电脑 Windows 系统初始配置

文章目录 前言1 前置配置2 安装软件2.1 通讯工具2.2 后端开发工具2.3 硬件开发工具2.4 前端开发工具2.4 其它工具 3 Windows 11 优化4 写在最后 前言 分区&#xff08;个人习惯&#xff09;&#xff1a;1TB SSD 分为 2 个分区&#xff0c;一个 256GB 分区为系统盘&#xff0c;剩…

网络受限情况下安装openpyxl模块提示缺少Jdcal,et_xmlfile

1.工作需要处理关于Excel文件内容的东西 2.用公司提供的openpyxl模块总是提示缺少jdcal文件,因为网络管控,又没办法直接使用命令下载&#xff0c;所以网上找了资源&#xff0c;下载好后上传到个人资源里了 资源路径 openpyxl jdcal et_xmlfile 以上模块来源于&#xff1a;Py…

[已解决]Mac使用更改idea.vmoptions文件导致idea打不开

直接在访达中全局搜索 idea.vmoptions 文件&#xff0c;然后根据报错信息更改成默认的或者更改成默认的格式即可。

陪伴系统,会成为女性向游戏的下一个争夺点吗?

乙游提供给女性玩家的只有恋爱感吗&#xff1f; 一般来说&#xff0c;对于乙女游戏的概括常常以为玩家提供“恋爱陪伴感”为主&#xff0c;恋爱很好理解&#xff0c;通过与多位男主角的剧情互动来模拟在真实恋爱中的情感交互&#xff0c;当下乙游都将重点放在了营造恋爱感上。…

武汉正向科技|无人值守起重机,采用格雷母线定位系统,扎根智能制造工业

武汉正向科技开发的无人值守起重机系统在原起重机系统的基础上&#xff0c;利用格雷母线位置检测技术&#xff0c;信息技术&#xff0c;网络技术及传感器技术为起重机系统添加管理层&#xff0c;控制层和基础层。实现起重机智能化&#xff0c;无人化作业的库区综合管理系统。 正…

用AI构建小程序需要多久?效果如何?

随着移动互联网的快速发展&#xff0c;多端应用的需求日益增长。为了提高开发效率、降低成本并保证用户体验的一致性&#xff0c;前端跨端技术在如今的开发界使用已经非常普遍了&#xff0c;技术界较为常用的跨端技术有小程序技术、HTML5技术两大类。 2023年以来&#xff0c;伴…

antd-tree的半选回显,不联动父类节点,非严格模式下也可以

出现的问题&#xff0c;树节点在非严格模式下&#xff0c;如果回显&#xff0c;会自动选中父节点以下的所有节点 方案1 在请求回来前设置 check-strictly 为true,请求完成后&#xff0c;设置为 false&#xff0c;没生效 方案2 在请求回来回显的接口时&#xff0c;取没有childr…

[C++ 核心编程]笔记 1 内存分区模型

内存分区模型 C程序在执行时&#xff0c;将内存大方向划分为4个区域&#xff1a; 代码区: 存放函数体的二进制代码&#xff0c;由操作系统进行管理的。 全局区: 存放全局变量和静态变量以及常量。 栈区: 由编译器自动分配释放&#xff0c;存放函数的参数值、局部变量等。 堆…

Linux 之 Linux应用编程概念、文件IO、标准IO

Linux应用编程概念、文件IO、标准IO 学习任务&#xff1a; 1、 学习Linux 应用开发概念&#xff0c;什么是系统调用&#xff0c;什么是库函数 2、 学习文件IO&#xff1a;包括 read、write、open、close、lseek 3、 深入文件IO&#xff1a;错误处理、exit 等 4、 学习标准IO&a…

wsl环境下安装Ubuntu,并下载MySQL5.7

安装操作需root权限&#xff0c;切换root用户有两种方式&#xff1a; 1-通过 sudo su - &#xff0c;切换到root用户&#xff08;登录后长期有效&#xff09;。 2-在每一个命令前加上sudo&#xff0c;临时提升权限&#xff08;仅对一条命令有效&#xff09;。 1、下载apt仓库…

【网络安全】利用XSS、OAuth配置错误实现token窃取及账户接管 (ATO)

未经许可,不得转载。 文章目录 正文正文 目标:target.com 在子域sub1.target.com上,我发现了一个XSS漏洞。由于针对该子域的漏洞悬赏较低,我希望通过此漏洞将攻击升级至app.target.com,因为该子域的悬赏更高。 分析认证机制后,我发现: sub1.target.com:使用基于Cook…

解决雪花ID在前端精度丢失问题

解决雪花ID在前端精度丢失问题 在现代分布式系统中&#xff0c;雪花算法&#xff08;Snowflake&#xff09;被广泛用于生成唯一的ID。这些ID通常是Long类型的整数。然而&#xff0c;当这些ID从后端传递到前端时&#xff0c;JavaScript的精度限制可能会导致精度丢失&#xff0c…

记录:Vscode 安装插件报:提取扩展出错 phpstudy中下载服务报网络异常

昨天出现了两个莫名其妙的问题&#xff1a; 一个是在phpstudy中下载服务的时候突然报了这个错误&#xff1a;当前网络不稳定&#xff0c;下载失败 不知道什么原因&#xff0c;从来也没见过 在网上找了一圈&#xff0c;没有解决&#xff0c;就放弃了 然后在刚刚&#xff0c;像用…

Photo Sphere Viewer:探索360°全景图的JavaScript库,轻松实现360°全景展示,探索360°全景世界的神奇之旅

嗨&#xff0c;大家好&#xff0c;我是小华同学&#xff0c;关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 你是否曾想过将360全景图嵌入到你的网站或应用中&#xff0c;为用户带来沉浸式的体验&#xff1f;Photo Sphere Viewer可以帮助你实现这个目标&#x…

打卡第六天 P10287 [GESP样题 七级] 最长不下降子序列

今天是我打卡第六天&#xff0c;做个普及/提高−题吧(#^.^#) 原题链接&#xff1a;[GESP样题 七级] 最长不下降子序列 - 洛谷 题目描述 输入格式 输出格式 输出一行一个整数表示答案。 输入输出样例 输入 #1 5 4 2 10 6 3 1 5 2 2 3 3 1 1 4 输出 #1 3 输入 #2 6 11 …

IvorySQL 西安站活动回顾|一键了解IvorySQL新兼容性

9月7日&#xff0c;IvorySQL社区携手中国开源软件联盟PostgreSQL分会&#xff0c;在古都西安成功举办PostgreSQL技术峰会及IvorySQL西安用户组成立仪式&#xff0c;现场吸引了众多西安PG圈技术爱好者和资深开发小伙伴们的热情参与&#xff01;&#x1f44f;&#x1f44f;&#…