当前位置: 首页 > news >正文

【C++】面向对象之多态

文章内的所有调试都是在vs2022下进行的,

部分小细节可能因编译器不同存在差异。

文章目录

    • 多态的定义和实现
      • 概念引入
      • 多态的构成条件
        • 虚函数重写
        • 通过基类的指针或者引用调用虚函数
      • override和final
    • 抽象类
      • 概念
      • 实现继承和接口继承
    • 虚函数表
      • 单继承中的虚表
      • 打印虚表
      • 多继承中的虚表
      • 虚表的存储
    • 多态的原理
    • 几个小问题

多态的定义和实现

概念引入

对于一个火车票售票系统,

可能会有多重角色,

比如普通成人类、学生类、军人类、儿童类等等…

这些类可能都是从某个基类派生出来的,

而且每个类都有一个基本需求,就是买票,

所以对于同一个购票函数BuyTicket()

当不同的类去调用它时它应该执行不同的功能,

比如成人要全价卖票,学生可以半价买票,军人得优先买票…

所以怎样满足这一需求呢?

通过多态的机制。

所以多态其实就是不同继承关系的类实例化出来的对象去调用同一函数

最终用同一个函数了执行不同的动作

感觉其实有点儿函数重载的意味…


多态的构成条件

首先见一见多态是个什么情况。

这是一段代码:

class Person
{
public:virtual void BuyTicket(){ cout << "原价买票" << endl; }
};class Student : public Person
{
public:virtual void BuyTicket(){ cout << "半价买票" << endl; }
};class Solider : public Person
{
public:virtual void BuyTicket(){ cout << "优先买票" << endl; }
};void QueryPriority(Person* p)
{ p->BuyTicket(); }int main()
{Person p;QueryPriority(&p);Student stu;QueryPriority(&stu);Solider solider;QueryPriority(&solider);return 0;
}

运行结果如下:

image-20230428172555184

虚函数重写

在讲多态的构成条件之前要先引入一个虚函数的概念。

虚函数就是用virtual修饰的函数,

在继承一文中已经初步见识过virtual关键字了,

当时是用virtual进行虚拟继承,

在菱形继承中避免数据二义性和冗余问题,

这里是用来修饰函数使之成为虚函数,

作为多态的构成条件之一。

所以构成多态的第一个条件是被调用的函数必须是虚函数,

在上面的例子中被调用的函数是BuyTicket()函数,

所以它要定义成虚函数:

class Person
{
public:virtual void BuyTicket(){ cout << "原价买票" << endl; }
};

这样的话BuyTicket()函数会被继承到派生类中,

我们当然可以不加virtual

直接在派生类中重载BuyTicket()函数,

此时派生类中就有了两个BuyTicket()函数,

一个是派生类中重载的,一个是基类的,

这两个函数是构成隐藏关系的。

于是就有了数据冗余和二义性…

而我们只想保留一份函数,

基类对象调用时执行基类中定义的行为,

派生类对象调用时执行派生类中定义的行为。

显然重载是完成不了这个任务的,

所以取而代之的就是重写

class Student : public Person
{
public:virtual void BuyTicket(){ cout << "半价买票" << endl; }
};class Solider : public Person
{
public:virtual void BuyTicket(){ cout << "优先买票" << endl; }
};

此前在基类和派生类中重名的成员变量或成员函数是构成隐藏/重定义关系的,

但那是对于普通函数而言,

满足一定条件的虚函数则是会构成覆盖/重写关系,

意味着在派生类中只有这么一个函数存在,

继承下来的基类的函数完全被覆盖掉了。

上面说的满足一定条件

前提一定是虚函数

此外除了函数名相同

函数的返回值类型和参数列表也要相同

但是返回值类型相同还有例外,

基类与派生类的虚函数返回值类型可以不相同,

但一定要是继承关系中基类或派生类的指针或引用,

举个简单的例子:

class Student : public Person
{
public:virtual Student* BuyTicket(){ cout << "半价买票" << endl; }
}class Solider : public Person
{
public:virtual Solider* BuyTicket(){ cout << "优先买票" << endl; }
}

这种情况下仍然构成多态,

(这么鸡肋的写法应该没人用吧)…

以上就是构成多态的其一条件:

被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写


通过基类的指针或者引用调用虚函数

诸如上面的多态调用:

Person p;
Person* ptr = &p;
ptr->BuyTicket();Student stu;
ptr = &stu;
ptr->BuyTicket();Solider solider;
ptr = &solider;
ptr->BuyTicket();

想要实现函数的多态调用,

首先函数一定是重写过的虚函数,

再就是要通过基类指针或者引用来调用。

至于为什么,在后面的多态原理细细阐述。

此前在继承一文中提出过一个问题:

如果这么定义了一个对象(此处省略类的定义):

int main()
{A* p = new B;delete p;return 0;
}

此时运行结果如下:

此时只调用了A的析构,

对B的部分成员并没有处理,

因此造成了内存泄漏!

那么现在就可以解决这个问题,

就是将析构函数定义成虚函数

在析构时会多态调用B类的析构函数,

就不会发生内存泄漏了。

这里有一个细节,

前面提到函数构成重写的条件之一是函数名必须相同,

而析构函数显然不符合这个条件,

但为什么又能实现重写呢?

实际上编译器偷偷对析构函数进行了处理,

统一将析构函数处理成同名函数。


override和final

C++对函数重写的要求比较严格,

但是有些情况下由于疏忽,

可能会导致函数名字母次序写反而无法构成重写,

而这种错误在编译期间是不会报出的,

只有在程序运行时没有得到预期结果才来debug会得不偿失,

因此,C++11提供了overridefinal两个关键字,

可以帮助用户检测是否重写。

final:修饰虚函数,表示该虚函数不能再被重写

class Person
{
public:virtual void BuyTicket() final{cout << "原价买票" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "半价买票" << endl;}
};
image-20230425164921861

override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

class Person
{
public:virtual void BuyTicket(){cout << "原价买票" << endl;}
};class Student : public Person
{
public:virtual void BuyTickte() override{cout << "半价买票" << endl;}
};

上面故意错把派生类中的函数名写错了。

image-20230425165651144

抽象类

概念

在虚函数的后面写上=0

则这个函数为纯虚函数。

包含纯虚函数的类叫做抽象类(也叫接口类),

抽象类不能实例化出对象。

派生类继承后也不能实例化出对象,

只有重写纯虚函数,派生类才能实例化出对象。

纯虚函数规范了派生类必须重写,

另外纯虚函数更体现出了接口继承。

class Person
{
public:virtual void BuyTicket() = 0{cout << "原价买票" << endl;}void func(){}
};
image-20230425170133159

实现继承和接口继承

普通函数的继承是一种实现继承,

派生类继承了基类函数,可以使用函数,

继承的是函数的实现。

虚函数的继承是一种接口继承,

派生类继承的是基类虚函数的接口,

目的是为了重写,达成多态,

继承的是接口。

所以如果不实现多态,

不要把函数定义成虚函数。


虚函数表

64位地址太长,

所以为了方便观察,

下面统一换成32位地址。

现在定义一个基类:

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};

然后定义一个Base对象,

那这个对象的对象模型是怎样的呢?

相比于没有虚函数的普通对象多了一个_vfptr

image-20230426161739843

看样子是一个数组,

数组元素都是指针,

那看来_vfptr是一个指针数组:

image-20230426162434042

这里显示它有一个元素,

这个元素存放的是Func1函数的地址。

多出来的这个_vfptr虚函数表指针

完整应该叫做visual function table pointer

虚函数表我们一般简称为虚表,

注意和继承中的虚基表的概念区分开,

它存放的其实就是虚函数的地址

注意虚函数并不存放在这儿,

虚函数和普通函数一样是存放在代码段的

所以一个含有虚函数的类中都至少都有一个虚表指针。


单继承中的虚表

我们再进一步看在继承中虚表是怎样的,

在上面代码的基础上我们再写点东西:

class Base
{
public:virtual void Func1(){ cout << "Base::Func1()" << endl; }virtual void Func2(){ cout << "Base::Func2()" << endl; }void Func3(){ cout << "Base::Func3()" << endl; }private:int _b = 1;
};class Derive : public Base
{
public:virtual void Func1(){ cout << "Derive::Func1()" << endl; }virtual void Func4(){ cout << "Derive::Func4()" << endl; }
private:int _d = 2;
};

此时再看一下它们的对象模型有什么变化:

image-20230426175251849

基类对象的结构还是原来那样,

只不过虚表中多了一个指向Func2函数的指针,

Func3并不在这儿,因为它不是虚函数。

派生类继承了基类的虚表,

但是存的指针有些变化,

可以看到_vfptr[0]存放的是被重写后的Func1的地址。

所以就可以下一个简单的结论:

派生类先继承基类的虚表,

如果派生类重写了基类中某个虚函数,

用派生类自己的虚函数覆盖虚表中基类的虚函数。

那派生类自己的虚函数呢?

按理来说派生类有虚函数,

实例化出来的对象也应该有一个虚基表,

但是这里好像并不是这样。

实际上,

派生类自己新增加的虚函数,

会按其在派生类中的声明次序增加到基类虚表的最后,

和基类共用一个虚表。

而这里继承的基类的虚表没有显示出派生类的虚函数,

这是编译器的监视窗口故意隐藏了这个函数,

也可以认为是他的一个小bug。

那么我们如何查看d的虚表呢?


打印虚表

我们既然有虚表指针_vfptr

那我们肯定就有办法打印它指向的虚表的内容,

也就是各个虚函数的地址。

下面就来看一下怎样获取。

首先在对象的存储结构中虚表指针存放在最上面,

也就是对象头四个字节(64位指针是八个字节),

所以对象模型如下:

image-20230427161200348

因为我们最终要访问的指针类型是函数指针,

所以我们可以先typedef一下这个函数指针类型:

typedef void(*VFPTR)()

我们先取d的地址:&d

&d此时的类型是Derive*

我们需要对其进行类型转换,

我们看到_vfptr的类型是void**,

所以对其进行类型转换:(void**)&d

void*在32位平台下是4个字节,64位平台下是8个字节,

所以对void**解引用就可以访问头4/8个字节的空间。

然后对其解引用找到虚表:*(void**)&d

此时就拿到了虚表指针,但它的类型是void*

而虚表是一个函数指针数组,

所以我们再做一次类型转换就拿到了可以访问数组元素的虚表指针:(VFPTR*)(*(void**)&d)

所以我们就可以通过下标访问访问到函数指针:((VFPTR*)(*(void**)&d))[0] -> Derive::Func1()

我们还可以通过拿到的函数指针调用函数:((VFPTR*)(*(void**)&d))[0]()

所以现在我们可以通过下面的代码遍历虚表,

打印虚表中存放的函数指针,

并通过函数指针调用函数,

看看是哪个函数:

for (int i = 0; i < n; i++)  // n是虚表中有几个函数指针
{cout << ((VFPTR*)(*(void**)&d))[i] << "->";((VFPTR*)(*(void**)&d))[i]();
}

这里有三个虚函数,所以n就是3

运行结果如下:

image-20230427164259380

验证了此前所说的。

我们还可以看一下基类对象的虚表:

image-20230427164523671

多继承中的虚表

看下面的代码:

class Base1 
{
public:virtual void func1() { cout << "Base1::func1" << endl; }virtual void func2() { cout << "Base1::func2" << endl; }private:int _b1 = 1;
};class Base2 
{
public:virtual void func1() { cout << "Base2::func1" << endl; }virtual void func2() { cout << "Base2::func2" << endl; }private:int _b2 = 2;
};class Derive : public Base1, public Base2 
{
public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3(){ cout << "Derive::func3" << endl; }private:int _d1 = 3;
};

此时派生类是继承了两个基类的派生类,

那它现在的对象模型是怎样的呢?

image-20230427171825648

可以看到普通多继承的场景下它完整继承了两个基类的虚表指针,

两个基类中都有Func1函数,

可以看到此时Func1函数都被重写后的函数覆盖了。

那派生类它自己的虚函数呢?

这里直接给出结论:多继承派生类的虚函数放在第一个继承基类部分的虚函数表中

所以第一个虚表中存放了三个函数指针,

分别指向Derive::Func1()Base1::Func2()Derive::Func3()

第二个虚表中存放了两个函数指针,

分别指向Derive::Func1()Base2::Func2()

所以对象模型如下:

image-20230427173632364

可以通过打印虚表来验证一下。

这里直接对d取地址可以拿到Base1::_vfptr

但是要怎么拿到Base2::_vfptr呢?

我们可以直接让指针偏移sizeof(Base1)个字节,

也就是把&d改为(char*)&d + sizeof(Base1)

结果如下:

image-20230427174559132

虚表的存储

我们现在再明确一下概念,

虚函数是函数,

和普通函数一样,

存放在代码段

虚表是一个指针数组,

存放指向虚函数的指针。

而类实例化出来的对象中存放的是一个虚表指针,

是指向虚表的指针。

所以对象中虚表指针存放在哪是很明确的,

就看对象存放在哪,

对象在栈上,那它的虚表指针也在栈上,

对象在堆上,那它的虚表指针就在堆上。

那问题来了,虚表存在哪呢?

我们可以通过一个简单的比对来看看:

Derive d;
Derive* pd = new Derive;cout << "栈: " << &d << endl;Derive* pd = new Derive;
cout << "堆: " << pd << endl;cout << "代码段: " << ((VFPTR*)(*(void**)&d))[0] << endl;cout << "d的虚表地址: " << (void*)*(void**)&d << endl;cout << "pd的虚表地址: " << (void*)*(void**)pd << endl;

栈、堆、代码段上的空间都是连续的,

我们我们可以将虚表的地址和它们进行比较:

通过对比可以发现虚表是存放在代码段的,

而且无论是临时对象还是动态开辟的对象,

都是共用一个虚表

当一个c++程序编译成可执行程序之后,

此时虚表已经形成了,

和函数一样存放在代码段。

当我们实例化对象时,

对应的构造函数会对对象的虚表指针进行初始化,

将虚表的地址写入到虚表指针中,

所以虚表是编译完就有了的,

而虚表指针是运行时才有的。


多态的原理

前面我们看了虚函数表,

那这个虚函数表和多态调用有什么密不可分的关系吗?

下面以文章开头的那段代码为例进行讲解:

class Person
{
public:virtual void BuyTicket(){ cout << "原价买票" << endl; }
};class Student : public Person
{
public:virtual void BuyTicket(){ cout << "半价买票" << endl; }
};class Solider : public Person
{
public:virtual void BuyTicket(){ cout << "优先买票" << endl; }
};void QueryPriority(Person* p)
{ p->BuyTicket(); }int main()
{Person p;Student stu;Solider solider;QueryPriority(&p);QueryPriority(&stu);QueryPriority(&solider);return 0;
}
image-20230428173300287

首先我们有一个基类指针,

当我们使用这个指针去调用虚函数时,

会去访问这个基类指针指向的对象的虚表指针,

然后通过虚表指针找到虚表,

在虚表中找到对应的函数然后调用。

如果调用的函数不存在于虚表中,

则会发生报错,

最简单的就是使用基类指针去调用派生类自己定义的虚函数。

在文章虚表的存储部分最后说了,

对象的虚表指针是在构造函数中初始化的,是运行时才有的,

在程序运行期间,

根据具体拿到的类型确定程序的具体行为,

调用具体的函数,

这就是所谓的动态绑定,也叫动态多态。

我们可以通过汇编代码看一下普通调用和多态调用时的区别:
在这里插入图片描述

与动态绑定相对的是静态绑定,也叫静态多态,

我们常用的函数重载就是一种静态多态,

是在在程序编译期间确定了程序的行为。


几个小问题

  1. 内联函数(inline)可以是虚函数吗?
    可以。
    不过编译器就忽略inline属性,
    这个函数就不再是inline,
    因为虚函数要放到虚表中去。

  2. 静态成员可以是虚函数吗?
    不能。
    因为静态成员函数没有this指针,
    使用类型::成员函数的调用方式无法访问虚函数表,
    所以静态成员函数无法放进虚函数表

  3. 构造函数可以是虚函数吗?
    不能。
    因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。
    如果构造函数定义成虚函数,
    那想调用构造函数就要去虚函数表中寻找,
    而虚表指针还没有初始化,
    就找不到构造函数了。

  4. 对象访问普通函数快还是虚函数更快?
    首先如果是通过"对象.函数"的方式去调用,
    是一样快的。
    如果是指针对象或者是引用对象,
    则调用的普通函数快,
    因为调用虚函数时还需要先到虚函数表中去查找。

相关文章:

【C++】面向对象之多态

文章内的所有调试都是在vs2022下进行的&#xff0c; 部分小细节可能因编译器不同存在差异。 文章目录 多态的定义和实现概念引入多态的构成条件虚函数重写通过基类的指针或者引用调用虚函数 override和final 抽象类概念实现继承和接口继承 虚函数表单继承中的虚表打印虚表多继…...

卡尔曼滤波器简介——多维卡尔曼滤波

原文&#xff1a;多维卡尔曼滤波 (kalmanfilter.net) 目录 前言 基本背景 状态外推方程 示例 - 飞机 - 无控制输入 示例 - 带控制输入的飞机 示例 – 坠落物体 状态外推方程维度 线性时不变系统 线性动态系统建模 状态外推方程的推导 状态空间表示形式 示例 - 等速…...

如何用 GPT-4 帮你写游戏?

你知道的&#xff0c;GPT-4 发布了。 目前你想要用上 GPT-4&#xff0c;主要的渠道是 ChatGPT Plus 。作为交了订阅费的用户&#xff0c;你可以在对话的时候选择模型来使用。 另一种渠道&#xff0c;就是申请官方 API 的排队。我在申请 New Bing Chat 的时候&#xff0c;耐心被…...

R语言的贝叶斯时空数据模型实践技术应用

时间&#xff0d;空间数据&#xff08;以下简称“时空数据”&#xff09;是最重要的观测数据形式之一&#xff0c;很多科学研究的数据都以时空数据的形式得以呈现&#xff0c;而科学研究目的可以归结为挖掘时空数据中的规律。另一方面&#xff0c;贝叶斯统计学作为与传统统计学…...

Lazysysadmin靶机渗透过程

准备工作 下载好靶机到本地后 VMware导入OVA 启动靶机 扫描信息 首先扫描整个C段发现主机 进一步扫描端口 从扫描结果可知&#xff1a; Samba服务MySQLSSH端口网站端口 先对网站进行目录遍历 发现有wordpress网站和phpmyadmin管理系统 出现了非常多遍My name is togie.可能…...

为什么网络安全缺口很大,招聘却很少?

2020年我国网络空间安全人才数量缺口超过了140万&#xff0c;就业人数却只有10多万&#xff0c;缺口高达了93%。这里就有人会问了&#xff1a; 1、网络安全行业为什么这么缺人&#xff1f; 2、明明人才那么稀缺&#xff0c;为什么招聘时招安全的人员却没有那么多呢&#xff1…...

SpringBoot手册

目录 依赖管理关于各种的 start 依赖关于自动配置关于约定大于配置中的配置SpringBoot 整合 SpringMVC定制化 SpringMVC静态资源处理对上传文件的处理对异常的处理Web原生组件注入&#xff08;Servlet、Filter、Listener&#xff09;Interceptor 自定义拦截器DispatcherServlet…...

【Linux】如何实现单机版QQ,来看进程间通信之管道

学会了管道&#xff0c;就可以实现简单的qq哦~ 文章目录 前言一、匿名管道总结 前言 为什么要进行进程间通信呢&#xff1f;因为需要以下这些事&#xff1a; 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享&#xff1a;多个进程之间共享同样的资源。 …...

从一到无穷大 #6 盘满排查过程

文章目录 引言df/du 原理排查思路文件系统预留空间进程占用句柄挂载覆盖 引言 核心在于执行df和du的时候发现显示的存储量完全不同&#xff0c;我本地系统盘有99G空间&#xff0c;du显示占用了45G&#xff0c;但是df却显示使用了99G&#xff0c;排查的过程本文所示。 先记录几…...

ChatGPT技术原理 第九章:数据集和训练技巧

目录 9.1 对话数据集 9.2 数据预处理 9.3 预训练技巧 9.4 微调技巧 9.5 多任务学习...

NCR被攻击后服务中断!原是BlackCat勒索软件作祟

近日&#xff0c;在遭到BlackCat勒索软件攻击后&#xff0c;NCR 的 Aloha 销售点平台出现中断。 NCR公司是全球关系管理技术解决方案领导供应商&#xff0c;为全球零售、金融、传讯、制造、旅游、交通及保安等客户提供服务。凭著累积多年的业界知识、专业顾问经验、专业增值应用…...

带你认识什么是BMS(电池管理系统)

文章目录 概述BMS的硬件拓扑BMS的电气架构BMS的功能BMS的总压采集&#xff08;主板功能&#xff09;BMS的电流采集&#xff08;主板功能&#xff09;BMS的电芯电压和温度采集&#xff08;从板功能&#xff09;BMS的SOC、SOP和SOH&#xff08;ASW计算&#xff09;BSM的绝缘检测B…...

安装Ubuntu22.04虚拟机的一些常见问题解决方法

文章目录 VirttalBox 开启共享剪切板文件夹、拖放的功能VirtualBox 安装 ubuntu后安装增强工具无效的解决办法解决ubuntu您没有权限查看“ 某某文件夹”的内容所需的权限linux更换源的两种方法[如何在 Ubuntu 20.04 上安装 Visual Studio Code - ](https://zhuanlan.zhihu.com/…...

银河麒麟操作系统,安装Gitlab 基于docker

不废话。直接上干货 操作系统信息 ############## Kylin Linux Version ################# Release: Kylin Linux Advanced Server release V10 (Sword) Kernel: 4.19.90-24.4.v2101.ky10.aarch64 Build: Kylin Linux Advanced Server release V10 (SP2) /(Sword)-aarch64-…...

基于Python实现个人手机定位分析

TransBigData是一个为交通时空大数据处理、分析和可视化而开发的Python包。本文就来用它实现个人手机定位分析&#xff0c;感兴趣的小伙伴可以了解一下 但其实交通时空大数据并不仅仅局限于交通工具产生的数据&#xff0c;我们的日常生活中也会产生大量的数据。比如我们的手机…...

Unity Navgation系统杂记

立即停止寻路 使用agent.isStoppedtrue&#xff0c;可以停止寻路&#xff0c;但是有很大的延迟&#xff0c;视觉体验很不好。 使用agent.enabledfalse&#xff0c;通过禁用NavMeshAgent组件的方式实现立即停止寻路。因为组件被禁用可能会产生其它问题&#xff0c;比如失去了Ob…...

[2021.11.9]lighteffect架构优化详细设计文档

1 lighteffect系统架构图 图1-1 整改前lighteffect系统架构图 上图为整改前lighteffect系统架构图&#xff0c;存在的问题如下&#xff1a; (1)代码bug 原因&#xff1a;由于系统中兼容了lighteffect和lighteffect2&#xff0c;写代码时只记了一个&#xff0c;出现代码bug。…...

经典回归算法

回归的概念 回归方程&#xff1a; 写成矩阵&#xff1a; 核心问题&#xff0c;构建预测函数z来映射特征矩阵x和标签y的线性关系 预测的目标值&#xff0c;有连续值也有离散值 连续值&#xff0c;就直接预测输出就行离散值&#xff0c;需要在输出端加一个变换函数例如。Si…...

Python两三行代码轻松批量添加~防韩还是很有必要的~

人生苦短&#xff0c;我用python 一直想做一个这种系列的但是因为七七八八的事情总是忘记&#xff0c; 今天正好有空&#xff0c;来开整一下~ 首先&#xff0c; 天冷防韩是什么梗&#xff1f; 【天冷防韩】 “天冷防韩”是“天冷防寒”的谐音&#xff0c; 不过“寒”指的…...

开心消消乐

给定一个 N 行 M 列的二维矩阵&#xff0c;矩阵中每个位置的数字取值为 0 或 1&#xff0c;矩阵示例如&#xff1a; 1 1 0 0 0 0 0 1 0 0 1 1 1 1 1 1 现需要将矩阵中所有的 1 进行反转为 0&#xff0c;规则如下&#xff1a; 当点击一个 1 时&#xff0c;该 1 被反转为 0&am…...

有效日志管理在软件开发和运营中的作用

作者&#xff1a;Luca Wintergerst, David Hope, Bahubali Shetti 当今存在的快速软件开发过程需要扩展和复杂的基础架构和应用程序组件&#xff0c;并且操作和开发团队的工作不断增长且涉及多个方面。 有助于管理和分析遥测数据的可观察性是确保应用程序和基础架构的性能和可靠…...

【五一创作】【笔记】Git|如何将仓库中所有的 commit 合成一个?又名,如何清除所有 git 提交记录?(附 git rebase 机制的简要分析)

在对代码进行开源时&#xff0c;我们往往并不希望代码开发过程中的提交记录被其他人看到&#xff0c;因为提交的过程中往往会涵盖一些敏感信息。因此会存在 将仓库中所有 commit 合成一个 的需求。 直觉上&#xff0c;往往会用 rebase 和 squash 或 reset&#xff0c;不过我尝…...

如何写出高质量代码?

作为一名资深开发人员&#xff0c;写出高质量的代码是我们必须要追求的目标。然而&#xff0c;在实际开发中&#xff0c;我们常常会遇到各种问题。比如&#xff0c;代码的可读性、可维护性、健壮性和灵活性等&#xff0c;这些都会影响代码的质量。那么&#xff0c;究竟如何才能…...

外卖项目优化-01-redis缓存短信验证码、菜品数据、Spring Cache(注解开发缓存)、(注解开发)缓存套餐数据

文章目录 外卖项目优化-01课程内容前言1. 环境搭建1.1 版本控制解决branch和tag命名冲突 1.2 环境准备 2. 缓存短信验证码2.1 思路分析2.2 代码改造2.3 功能测试 3. 缓存菜品信息3.1 实现思路3.2 代码改造3.2.1 查询菜品缓存3.2.2 清理菜品缓存 3.3 功能测试3.4 提交并推送代码…...

Chapter1:控制系统数学模型(下)

第一章:控制系统数学模型 Exercise1.13 已知控制系统结构图如下图所示,求系统的输出 C 1 ( s ) C_1(s) C...

排序算法总结

常见排序算法的时间复杂度、空间复杂度及稳定性分析&#xff1a; 时间复杂度空间复杂度是否有稳定性基于比较的排序算法选择排序 O(N^2)O(1)否 冒泡排序O(N^2)O(1)是插入排序O(N^2)O(1)是归并排序O(N*logN)O(N)&#xff0c;每次需要额外一个数组用于拷贝是快排O(N*log…...

java+jsp企业物流货运快递管理系统servlet

功能需求具体描述&#xff1a; (1)用户功能模块包括用户登录注册&#xff0c;用户信息的修改&#xff0c;用户发布货物信息&#xff0c;给客服人员留言&#xff0c;对运输公司进行评价。 (2)企业功能模块包括企业注册登录&#xff0c;企业信息的修改&#xff0c;受理用户发布的…...

【ROS仿真实战】获取机器人在gazebo位置真值的三种方法(三)

文章目录 前言一. 使用ROS tf库二、 使用Gazebo Model Plugin三、 使用libgazebo_ros_p3d插件四、总结 前言 在ROS和Gazebo中&#xff0c;获取机器人的位置信息通常通过ROS消息传递进行。在这篇文章中&#xff0c;我们将介绍三种获取机器人在Gazebo中位置真值的方法&#xff1…...

Winform从入门到精通(35)——FontDialog(史上最全)

文章目录 前言一、属性1、Name2、AllowScriptChange3、AllowSimulations4、AllowVectorFonts5、AllowVerticalFonts6、Color7、FixedPitchOnly8、Font9、FontMustExist10、MaxSize11、MinSize12、 ScriptsOnly13、ShowApply14、ShowColor15、ShowEffects16、ShowHelp...

AcWing 854. Floyd求最短路Floyd模板

Floyd算法&#xff1a; 标准弗洛伊德算法&#xff0c;三重循环&#xff0c;基于动态规划。 循环结束之后 d[i][j]存储的就是点 i 到点 j 的最短距离。 需要注意循环顺序不能变&#xff1a;第一层枚举中间点&#xff0c;第二层和第三层枚举起点和终点。 特点&#xff1a; 1.复杂…...

做钻石资讯网站/搜索引擎营销实训报告

本文实例讲述了android通过Location API显示地址信息的实现方法。分享给大家供大家参考。具体如下&#xff1a;android的Locatin API&#xff0c;可以通过Geocoder类&#xff0c;显示具体经纬度的地址信息。如&#xff1a;通过Geocoder的方法getFromLocation()可以得到Address对…...

画江湖网站开发文档/怎样在百度上发表文章

设计模式学习笔记&#xff08;十三&#xff09;——Proxy代理模式 Proxy代理模式是一种结构型设计模式&#xff0c;主要解决的问题是&#xff1a;在直接访问对象时带来的问题&#xff0c;比如说&#xff1a;要访问的对象在远程的机器上。在面向对象系统中&#xff0c;有些对象由…...

西安网站建设企业/网络营销主要做些什么工作

给要打印的背景的元素添加样式 -webkit-print-color-adjust: exact; 或者添加!important color: #def1f7!important ; 这样加入后颜色在打印的时候就会出现了....

用asp做网站大概多久/营销到底是干嘛的

【计算机】数据结构-严蔚敏/清华大学P6 第二章 线性表 2.3线性表类型的实现 三、 purge算法当以链式映像实现时&#xff0c;时间复杂度是平方级的。 四、一个带头结点的线性链表类型 -----------------END-----------------...

企业网站建设及前期准备/百度题库

这里是吧容器的本地日志目录挂载filebeat,然后filebeat 读取日志写入到kafka --- apiVersion: v1 kind: ConfigMap metadata:name: filebeat-confignamespace: kube-systemlabels:k8s-app: filebeat data:filebeat.yml: |-filebeat.inputs:- type: logpaths:- /var/lib/docke…...

公司建设官方网站/策划方案

$.getscriptjQuery .getScript&#xff08;&#xff09;函数的一个功能是&#xff0c;它为每个ajax脚本调用都包含唯一的ID&#xff08;时间戳等&#xff09; 。 当我运行setTimeout来获取脚本但它正在重新加载相同的脚本时&#xff0c;这为我带来了一个问题……不好。 因此&am…...