【C++深度探索】全面解析多态性机制(二)
前言
我们知道C++多态实现有两个条件——一是基类的指针或引用调用虚函数,另一个是基类中有虚函数并且在派生类中实现虚函数重写;这两个条件缺一不可,这与多态实现的底层原理有关,今天我们就来学习一下多态实现的原理🥳🥳
目录
- 前言
- 1.虚函数表
- 2.派生类中的虚表
- 情况一:只有基类有虚函数,派生类没有
- 情况二:基类和派生类中都有虚函数,并且虚函数没有被重写
- 情况三:基类中定义虚函数,并且派生类中对该虚函数进行了重写
- 综合这三种情况
- 3.多态原理
- 动态绑定与静态绑定
- 4.多继承中的虚函数表
- 5.结语
1.虚函数表
虚函数表(Virtual Function Table,VTable)是C++中实现动态多态性的一种机制。每个包含虚函数的类都有一个对应的虚函数表,用于存储该类的虚函数地址。
虚函数表是一个包含函数指针的数组,每个函数指针指向相应虚函数的实现。
也就是说在类中定义了虚函数,那么该类就会包含一个虚函数表来存放虚函数的函数指针,注意这里是指类中会存储虚函数表的指针来达到效果,因为如果虚函数很多,直接存储虚函数表可能会占用很多空间。
例如:
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};int main()
{cout << sizeof(Base) << endl;return 0;
}
当我们计算Base类的大小时,发现只有一个int类型的成员变量_b,所以应该是4个字节,但是我们可以看一下结果:
这里显示的是8字节,这是因为Base类中创建了虚函数,而每个包含虚函数的类都有一个对应的虚函数表,虚函数的地址要被放到虚函数表中,所以需要多余的空间来存储虚函数表的指针,这个指针我们叫做虚函数表指针。
如下图所示:
Base对象b中除了_b成员,还多一个__vfptr指针放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。
注意是虚函数表的指针,而不是直接存储虚函数表。
因为上述例子是在32位操作系统下执行的,所以指针的大小是4字节,Base类大小是8字节;如果是64位那么指针的大小是8字节,Base类的大小就应该参考结构体内存对齐规则,应该是16字节。
2.派生类中的虚表
如果基类中没有虚函数,派生类中有虚函数,那么它的虚函数表和上面的一致。
例如:
class Base
{
private:int _b = 1;
};class Derive :public Base
{
public:virtual void Func2(){cout << "Func2()" << endl;}
private:int _d = 2;
};int main()
{Derive d;return 0;
}
上述代码基类中没有虚函数也就不存在虚函数表,但派生类Derive类中存在虚函数,所以会存放一个虚函数表指针__vfptr,来指向虚函数表,而虚函数表中又会存放Derive类中虚函数的指针。
如下图所示:
但是如果基类中有虚函数表,那么派生类该如何继承呢?
情况一:只有基类有虚函数,派生类没有
例如:
//情况一:只有基类有虚函数
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};class Derive :public Base
{
private:int _d = 2;
};int main()
{Derive d;return 0;
}
d对象由两部分构成,一部分是父类继承下来的成员,另一部分是自己的成员,如下图所示:
我们看到d对象继承了基类的成员变量_b和虚函数表的指针,虚函数表里面存放的是基类中虚函数
Func1()
的地址。
情况二:基类和派生类中都有虚函数,并且虚函数没有被重写
例如:
//情况二:基类和派生类中都有虚函数,并且虚函数没有被重写
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};class Derive :public Base
{
public:virtual void Func2(){cout << "Func2()" << endl;}
private:int _d = 2;
};int main()
{Derive d;return 0;
}
首先基类中有虚函数,所以派生类中包含基类的那部分成员肯定会包含基函数表的指针,但是派生类的虚函数应该怎么存放呢?结果如下图所示:
我们发现派生类并没有生成自己的虚函数表,所以它的虚函数应该存放在从基类继承下来的虚函数表中,上图中看到虚函数数组内只存放一个虚函数
Func1()
,没有派生类自己的虚函数Fun2()
,这里是编译器的监视窗口故意隐藏了Fun2()
函数,也可以认为是他的一个小bug,那么我们可以思考从打印虚函数地址的思路入手,查看Fun1()
和Fun2()
的地址是否是存放在一起。
虚函数的地址存放在虚函数表中,而对象中前四个字节存放的是虚函数表的指针,所以我们可以使用强制类型转换取出对象的前四个字节,但是int
类型与Base
和Derive
类型不兼容,不能相互转换,但是指针之间可以相互转换,所以我们考虑先取Base
和Derive
类对象的地址然后强制转换成int*
类型,然后再解引用就得到了虚函数表的地址🥳🥳
代码如下:
//情况二:基类和派生类中都有虚函数,并且虚函数没有被重写
//基类和派生类代码如上//先定义一个函数指针类型
typedef void(*VFPTR) ();//打印函数指针数组中存放的函数地址
void PrintVTable(VFPTR vTable[])
{// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout << " 虚表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; i++){printf(" 第%d个虚函数地址 :0X%x->", i, vTable[i]);VFPTR f = vTable[i];f();//调用该函数}cout << endl;
}void test()
{Base b;Derive d;//打印b对象中虚函数的地址VFPTR* vTableb = (VFPTR*)(*(int*)&b);PrintVTable(vTableb);//打印d对象中虚函数的地址VFPTR* vTabled = (VFPTR*)(*(int*)&d);PrintVTable(vTabled);
}
结果如下:
我们发现基类对象和派生类对象的虚函数表是不同的,并且派生类对象虚函数表中存放了两个虚函数的地址,其中一个与基类的虚函数地址相同也就是
Func1()
的地址,另一个则是派生类自己定义的虚函数Func2()
的地址。
综上所述,如果派生类和基类都定义了自己的虚函数,并且基类的虚函数没有在派生类中重写的话,那么派生类中虚函数的地址会存放在派生类继承的基类那部分的虚函数表中的末尾,并且基类定义的对象和派生类定义的对象的虚函数表的地址是不同的。
同一类定义的不同对象使用的基函数表是同一个。
如下图所示:
情况三:基类中定义虚函数,并且派生类中对该虚函数进行了重写
例如:
//情况三:基类中定义虚函数,并且派生类中对该虚函数进行了重写
class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}
private:int _b = 1;
};class Derive :public Base
{
public:virtual void Func1()//重写虚函数Fun1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main()
{Base b;Derive d;return 0;
}
按照之前的结论,派生类中的虚函数的地址是存放在继承的基类的虚函数表中的,那么对于重写的虚函数是写在基类虚函数表的末尾,还是将基类被重写的虚函数地址覆盖呢?
结果如下图:
可以看到派生类的重写的虚函数的地址覆盖了继承的基类的虚函数的地址,我们还可以使用上文中打印虚函数地址的方式更加直观的看清楚:
上述例子中基类b对象和派生类d对象虚表是不一样的,这里我们发现
Func1
完成了重写,所以d的虚表中存的是重写的Derive::Func1
,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
综合这三种情况
- 如果基类没有虚函数,子类有虚函数,那么子类会自己生成一个虚函数表来存放自己的虚函数;
- 如果基类有虚函数,子类也有自己的虚函数,那么子类中虚函数的地址会存放在子类继承的基类那部分的虚函数表中的末尾;
注意虽然说是继承基类的虚函数表,但是基类对象和子类对象的虚函数表是不同的表,它们各自有各自的表。只是因为子类会继承基类的虚函数,所以基类的虚函数指针也会存在该子类的虚函数表中,相当于将基类的虚函数表直接继承下来,再将子类自己的虚函数指针存放进去,子类也就不用自己再生成一个虚函数表。
- 如果基类有虚函数,并且子类对该虚函数进行了重写,那么子类虚函数表中基类被重写的虚函数地址就会被子类重写的虚函数地址覆盖,而不再和第二点一样写在虚函数表的尾部。
例如:
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;}
private:int _d = 2;
};int main()
{Base b;Derive d;
}
基类b对象和派生类d对象虚表是不一样的,这里我们发现
Func1
完成了重写,所以d的虚表中存的是重写的Derive::Func1
,另外Func2
继承下来后是虚函数,所以放进了虚表,Func3
也继承下来了,但是不是虚函数,所以不会放进虚表。
虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr
。
总结一下派生类的虚表生成:
a.先将基类中的虚表内容拷贝一份到派生类虚表中
b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
✨✨这里还有一个很容易混淆的问题:虚函数存在哪的?虚表存在哪的?
虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下是存在代码段的。
代码如下:
int main()
{int i = 0;static int j = 1;int* p1 = new int;const char* p2 = "aaaaa";Base b;Derive d;printf("栈:%p\n", &i);printf("静态区:%p\n", &j);printf("堆:%p\n", p1);printf("常量区:%p\n", p2);printf("Base虚表地址:%p\n", *(int*)&b);printf("Derive虚表地址:%p\n", *(int*)&d);return 0;
}
通过比较不同区域的地址来判断虚表地址在哪,发现虚表离常量区最近,如下图所示:
3.多态原理
了解了虚函数表我们就可以深入学习多态的原理。
例如:
//多态原理
class Person {
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
};void Func(Person& p)
{p.BuyTicket();
}int main()
{Person Black;Func(Black);Student tutu;Func(tutu);return 0;
}
对于买票这个行为,不同的对象会有不同的结果,普通人买票是全价,学生则可能是半价,如下图所示:
那么我们可以通过调试来看看它有不同结果的原因:
当使用Person类对象调用函数Func时:
当使用Student类对象调用函数Func时:
我们看到,p是指向Black对象时,p->BuyTicket在Black的虚表中找到虚函数是Person::BuyTicket。p是指向tutu对象时,p->BuyTicket在tutu的虚表中找到虚函数Student::BuyTicket。
我们发现不同的对象调用Func函数时,使用的虚函数表是不同的,Person类对象和Student类对象都使用各自的虚函数表,所以调用不同的虚函数,如下图所示:
这样就实现出了不同对象去完成同一行为时,展现出不同的形态。
反过来思考我们要达到多态,有两个条件,一个是虚函数覆盖,一个是基类对象的指针或引用调用虚函数。具体原因,我们先要了解一下动态绑定。
动态绑定与静态绑定
- 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
- 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。
那么当我们直接使用对象调用成员函数时走的是静态绑定,是指编译期间就确定的程序行为;当我们使用基类指针或引用调用虚函数时走的是动态绑定,需要通过虚函数表来确定不同对象调用不同的函数,根据具体拿到的类型确定程序的具体行为。
所以对于多态实现的两个条件,首先我们需要通过基类对象的指针或引用调用虚函数才能走动态绑定,其次派生类的虚函数还需要重写,这样不同类的对象使用的虚函数才是不一样的,才会显现不同的状态,实现多态。
如果只是完成了虚函数的覆盖而没有通过基类对象的指针或引用调用,或者只有第二个条件都无法完成多态的实现。
多态实现的两个条件缺一不可。
4.多继承中的虚函数表
在多继承中,派生类会继承多个基类,每个基类都有自己的虚表。因此,派生类会有多个虚表,每个虚表对应于一个基类。
这是为了保证派生类能够正确调用和重写每个基类的虚函数。当派生类实例化时,会为每个基类分配一个虚表指针,这些虚表指针会存储在派生类对象的内存中。
例如:
class Base1 {
public:virtual void func1() { cout << "Base1::func1" << endl; }virtual void func2() { cout << "Base1::func2" << endl; }
private:int b1;
};class Base2 {
public:virtual void func1() { cout << "Base2::func1" << endl; }virtual void func2() { cout << "Base2::func2" << endl; }
private:int b2;
};class Derive : public Base1, public Base2 {
public:virtual void func1() { cout << "Derive::func1" << endl; }//重写Fun1()virtual void func3() { cout << "Derive::func3" << endl; }
private:int d1;
};//打印虚表地址以及虚函数地址
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{cout << " 虚表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}int main()
{Derive d;//第一个虚表地址VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);PrintVTable(vTableb1);//找到第二个虚表的地址VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));PrintVTable(vTableb2);return 0;
}
结果如下:
上图可以看出多继承派生类的未重写的虚函数
func3()
放在第一个继承基类部分的虚函数表中,图示如下:
5.结语
虚函数表的存在是为了实现动态绑定也就是实现多态,当派生类对基类的虚函数进行重写时,通过基类对象指针和引用调用虚函数时,就会通过虚函数表来确定不同对象调用不同的函数,根据具体拿到的类型确定程序的具体行为,所以多态实现的两个条件缺一不可。以上就是今天所有的内容啦~ 完结撒花🥳🎉🎉
相关文章:
![](https://i-blog.csdnimg.cn/direct/d577686cfe594e6b969543029c402cee.png)
【C++深度探索】全面解析多态性机制(二)
🔥 个人主页:大耳朵土土垚 🔥 所属专栏:C从入门至进阶 这里将会不定期更新有关C/C的内容,欢迎大家点赞,收藏,评论🥳🥳🎉🎉🎉 前言 我…...
![](https://www.ngui.cc/images/no-images.jpg)
MySQL配置数据库的连接命令
MySQL配置数据库连接命令 在MySQL中,配置数据库连接的命令涉及创建用户、授予权限、配置主从复制等多个方面。以下是常用的命令及其用途: 创建用户 创建一个新的数据库用户并为其设置密码: CREATE USER usernamehost IDENTIFIED BY passwo…...
![](https://www.ngui.cc/images/no-images.jpg)
[PaddlePaddle飞桨] PaddleSpeech-自动语音识别-小模型部署
PaddleSpeech的GitHub项目地址 环境要求: gcc > 4.8.5 paddlepaddle < 2.5.1 python > 3.8 OS support: Linux(recommend), Windows, Mac OSXpip下载指令: python -m pip install paddlepaddle-gpu2.5.1 -i https://pypi.tuna.tsinghua.edu.c…...
![](https://www.ngui.cc/images/no-images.jpg)
redis查询慢,你们是如何排查优化的?(总结篇)
1,先进行基准测试,查看redis是否存在查询过慢情况,根据自己的情况而定 2、检查网络连接是否出现延迟,数据丢包问题(可能性小 3、开启慢查询日志,通过日志可以清楚知道哪些命令比较耗时,同时避…...
![](https://www.ngui.cc/images/no-images.jpg)
Docker 容器出现 IP 冲突
Docker 容器出现 IP 冲突的情况可能由以下几个原因导致: 静态 IP 分配:如果你在 docker-compose.yml 文件中为多个容器手动设置了相同的静态 IP 地址,那么这些容器在启动时就会出现 IP 冲突。确保每个容器分配的静态 IP 地址是唯一的。桥接网…...
![](https://i-blog.csdnimg.cn/direct/dfa6242d2b0a4f1db626fe08705fe4c9.png)
paddlepaddle2.6,paddleorc2.8,cuda12,cudnn,nccl,python10环境
1.安装英伟达显卡驱动 首先需要到NAVIDIA官网去查自己的电脑是不是支持GPU运算。 网址是:CUDA GPUs | NVIDIA Developer。打开后的界面大致如下,只要里边有对应的型号就可以用GPU运算,并且每一款设备都列出来相关的计算能力(Compu…...
![](https://www.ngui.cc/images/no-images.jpg)
【D3.js in Action 3 精译】1.3 D3 视角下的数据可视化最佳实践(上)
当前内容所在位置 第一部分 D3.js 基础知识 第一章 D3.js 简介 1.1 何为 D3.js?1.2 D3 生态系统——入门须知 1.2.1 HTML 与 DOM1.2.2 SVG - 可缩放矢量图形1.2.3 Canvas 与 WebGL1.2.4 CSS1.2.5 JavaScript1.2.6 Node 与 JavaScript 框架1.2.7 Observable 记事本 1…...
![](https://i-blog.csdnimg.cn/direct/75946c85ccf24a21abe7b648553e3910.png)
如何在Linux上如何配置虚拟主机
在Linux上配置虚拟主机可以通过使用Apache HTTP服务器来实现。Apache是一个开源的跨平台的Web服务器软件,可以在多种操作系统上运行并支持虚拟主机的配置。 以下是在Linux上配置虚拟主机的步骤: 安装Apache HTTP服务器 在终端中运行以下命令来安装Apache…...
![](https://www.ngui.cc/images/no-images.jpg)
c语言alpha-beta剪枝六子棋
c语言Alpha-Beta剪枝算法六子棋[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2i5w8kc1-1720756528545)(https://i-blog.csdnimg.cn/direct/464b9db7d6384a63ab8c3213efff0e99.png)] 1.介绍 Alpha-Beta剪枝算法是一种用于优化博弈树搜索的算法&…...
![](https://img-blog.csdnimg.cn/img_convert/2fbb116e2277061b8c1db072c29329e8.png)
基于PyTorch深度学习实践技术应用
近年来,Python语言由于其开源、简单等特点,受到了广大程序开发者的偏爱,丰富的函数库使得其在各行各业中得到了广泛的应用。伴随着新一轮人工智能(尤其是深度学习)的快速发展,许多深度学习框架应运而生&…...
![](https://www.ngui.cc/images/no-images.jpg)
数据湖仓一体(五)安装spark
上传安装包到/opt/software目录并解压 [bigdatanode106 software]$ tar -zxvf spark-3.3.1-bin-hadoop3.tgz -C /opt/services/ 重命名文件 [bigdatanode106 services]$ mv spark-3.3.1-bin-hadoop3 spark-3.3.1 配置环境变量 [bigdatanode106 ~]$ sudo vim /etc/profile…...
![](https://i-blog.csdnimg.cn/direct/95f2d0c7aa7a4a10bab485e12565232e.png)
项目收获总结--本地缓存方案选型及使用缓存的坑
本地缓存方案选型及使用缓存的坑 一、摘要二、本地缓存三、本地缓存实现方案3.1 自己编程实现一个缓存3.2 基于 Guava Cache 实现本地缓存3.3 基于 Caffeine 实现本地缓存3.4 基于 Encache 实现本地缓存3.5 小结 四、使用缓存的坑4.1 缓存穿透4.2 缓存击穿4.3 缓存雪崩4.4 数据…...
![](https://i-blog.csdnimg.cn/direct/687d1431a1ca4accbc308f6ecb22544d.png)
java使用poi-tl模版引擎导出word之if判断条件的使用
文章目录 模版中if语句条件的使用1.数据为False或空集合2.非False或非空集合 模版中if语句条件的使用 如果区块对的值是 null 、false 或者空的集合,位于区块中的所有文档元素将不会显示,这就等同于if语句的条件为 false。语法示例:{{?stat…...
![](https://i-blog.csdnimg.cn/direct/fd4b6c819d114f8dba7fb6bd5623f899.png)
扩散的魔法:如何打造未来生物打印?
生物打印技术正在快速发展,它允许我们将生物材料、细胞和生长因子等生物活性成分精确地打印成具有特定形状和功能的结构。而扩散现象在生物打印中扮演着至关重要的角色,它影响着打印结构的特性、机械性能、生物功能和形态。为了更好地利用扩散现象&#…...
![](https://www.ngui.cc/images/no-images.jpg)
Bag of mice(概率dp)
https://www.luogu.com.cn/problem/CF148D 思路: 概率dp,设f[a][b]为白鼠为a个,黑鼠为b个时,赢的期望。 f[i][0]1; 1.当先手取到白鼠时 a/(ab); 2.当先手未取到白鼠,先手要向赢,后手也不能取到白鼠&am…...
![](https://www.ngui.cc/images/no-images.jpg)
Python的基础语法——持续更新版
1、type查看数据类型 # 直接输出结果 print(type("Hello")) # 先用变量存储 string_type type("Hello") print(string_type) 2、 类型转化 任何类型可以转化为字符串,但字符串不可以随意转化,要求字符串类内容都是数字 # 类型…...
![](https://i-blog.csdnimg.cn/direct/6ce13cf595e74effb7fb257d24c05667.png)
百度智能云将大模型引入网络故障定位的智能运维实践
物理网络中,某个设备发生故障,可能会引起一系列指标异常的告警。如何在短时间内从这些告警信息中找到真正的故障原因,犹如大海捞针,对于运维团队是一件很有挑战的事情。 在长期的物理网络运维工作建设中,百度智能云通…...
![](https://www.ngui.cc/images/no-images.jpg)
晚上定时编译android系统
1、问题 可能偶然想晚上定时编译android系统 2、解决 at.sh #!/bin/sh# at -f at.sh now1min # at -lset -e set -xecho $SHELLecho at build begin /bin/date >> at_build.log/bin/bash -c source build/envsetup.sh >> at_build.log 2>&1; lunch xxx-us…...
![](https://img-blog.csdnimg.cn/ddede5bb3d7044b7b45758ab11689ee6.jpg)
轻薄鼠标的硬核选购攻略,很多人都在“高性价比”鼠标上栽跟头了
轻薄款设计的鼠标是目前鼠标市场的出货大头, 也是价格最卷的一类鼠标。 比游戏鼠标或许更卷一些。 这和当前的移动办公趋势关系很大。 这类鼠标主要跟笔记本和iPad搭配。 核心的使用场景是办公。 因此轻薄和静音是这类鼠标的核心卖点。 同时用户并不愿意付出太…...
![](https://www.ngui.cc/images/no-images.jpg)
Python制作签到系统
import datetime sign_in_records {} def sign_in(username): today datetime.date.today() if username not in sign_in_records: sign_in_records[username] [] sign_in_records[username].append(today) print(f"{username} 签到成功&#…...
![](https://www.ngui.cc/images/no-images.jpg)
面试题007-Java-Spring
面试题007-Java-Spring 目录 面试题007-Java-Spring题目自测题目答案1. 简单介绍一下Spring?2. Spring有哪些模块?3. 什么是Spring IoC ?4. 什么是依赖注入?有哪几种方式可以进行依赖注入?5. 什么是Spring AOP ?6. 什…...
![](https://i-blog.csdnimg.cn/direct/db3f70a2395f4bd9b791f1dcd4f18a8b.png)
后端之路——登录校验前言(Cookie\ Session\ JWT令牌)
前言:Servlet 【登录校验】这个功能技术的基础是【会话技术】,那么在讲【会话技术】的时候必然要谈到【Cookie】和【Session】这两个东西,那么在这之前必须要先讲一下一个很重要但是很多人都会忽略的一个知识点:【Servlet】 什么是…...
![](https://i-blog.csdnimg.cn/direct/04936c2f018044499b2f08ab8df6175a.jpeg)
【蓄势·致远】 同为科技(TOWE)2024年年中会议
2024年7月2日-8日,同为科技(TOWE)召开2024年年中工作会议。会议回顾上半年总体工作情况,分析研判发展形势,规划部署下半年工作。 为期一周的工作会议,由同为科技(TOWE)创始人、董事长…...
![](https://i-blog.csdnimg.cn/direct/46375c7d495344e28b3c330badc5512b.png)
通过git将文件push到github 远程仓库
1.先git clone 代码地址 git clone htttp://github.com/用户名/test.git 2. 添加文件 例如:touch 1.txt 3.将文件添加到暂存区 git add 1.txt 4.提交 git commit -m "commit 1.txt" 5.与远程仓库建立关联 git remote add 远程仓库名 远程仓库…...
![](https://www.ngui.cc/images/no-images.jpg)
如何判断服务器是否被攻击
如何判断服务器是否被攻击 一、异常流量模式 一种判断服务器是否遭到攻击的方法是监控网络流量。异常的流量模式,例如流量突然剧增或减少,都可能是攻击的迹象。通常,大量的入站流量表明分布式拒绝服务(DDoS)攻击的可能…...
![](https://i-blog.csdnimg.cn/direct/9e37bb6e03ec42ce9ea9827583c0f588.png)
泽众一站式性能测试平台P-One监控指标的意义
在当今数字化和信息化高度发展的时代,企业把保障系统稳定运行、优化业务流程和提升用户体验摆在首要位置。然而,在现如今复杂的分布式系统中,各个组件和服务之间的交互频繁且紧密,当系统出现性能瓶颈时,传统的监测手段…...
![](https://i-blog.csdnimg.cn/direct/140a79a09bd644b4b41816d5880d14d3.png)
前端Canvas入门——一些注意事项
创建渐变的三种方法: createLinearGradient() - 线性渐变 createRadialGradient() - 径向渐变(放射性渐变) createConicGradient() - 锥形渐变 这三种的核心观点都是: 创建一个gradient对象,然后调用addColorStop()方法…...
![](https://www.ngui.cc/images/no-images.jpg)
移动互联安全扩展要求测评项
安全物理环境-无线接入点的位置选择 应为无线接入设备的安装选择合理位置,避免过度覆盖和电磁干扰。 无线接入设备的安装位置选择不当,易被攻击者利用,特别是攻击者会通过无线信号过度覆盖的弱点进行无线渗透攻击,因此要选择合理…...
![](https://i-blog.csdnimg.cn/direct/1defae163a134d999a8987b169a7f749.png)
【代码随想录】【算法训练营】【第64天】 [卡码117]软件构建 [卡码47]参加科学大会
前言 思路及算法思维,指路 代码随想录。 题目来自 卡码网。 day 64,周三,继续ding~ 题目详情 [卡码117] 软件构建 题目描述 卡码117 软件构建 解题思路 前提: 思路: 重点: 代码实现 C语言 [卡码…...
![](https://i-blog.csdnimg.cn/direct/137866553cc44313a40646180cdf1675.png)
【python算法学习1】用递归和循环分别写下 fibonacci 斐波拉契数列,比较差异
问题: fibonacci 斐波拉契数列,用递归和循环的方法分别写,比较递归和循环的思路和写法的差别 最直接的思路,是写递归方法 循环方法的稍微有点绕,我觉得问题主要是出在,总结循环的通项公式更麻烦,难在数学…...
![](/images/no-images.jpg)
免费的汽车网站源码/网络推广常见的方法
{0}转载于:https://www.cnblogs.com/thlzhf/p/4377174.html...
![](https://img-blog.csdnimg.cn/img_convert/820e69236cb49f9bd7e1b211b83e5750.png)
网站项目建设与管理论文/北京网络营销策划公司
智能会议室是钉钉官方的免费应用,智能会议室属于企业会议管理系统,帮助企业高效有序组织会议,提升会议室使用效率。[灵光一闪]一、如何新建会议室?企业开通智能会议室后,管理员可以添加并录入会议室信息哦手机端&#…...
![](/images/no-images.jpg)
上海电信网站备案/深圳抖音seo
环景: ubuntu 16.04 docker Version: 20.10.7 问题描述: 怎么进入Docker 容器查看容器内文件,并复制容器内文件至本机 解决方案: 1.#docker ps 执行结果如下: CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS…...
![](https://images2015.cnblogs.com/blog/866948/201601/866948-20160106122956778-422944630.jpg)
wordpress 模版制作/郑州建网站的公司
六、克隆GitHub仓库 1、创建仓库目录,目录位置没有要求,比如D:\learngit。 2、配置ssh(如果不配置会每次都输入用户名和密码) 使用TortoiseGit生成ssh-key:开始菜单找到“”-->“PuTTYgen” 生成Key:并保…...
![](/images/no-images.jpg)
网站三d图怎么做/结构优化
题目 编写程序,实现生产者和消费者问题。输入:由英文字母和数字组成的不定长度字符串数组,例如{“abc”,”23d”,”1a”}。每隔100毫秒,生产者线程读入数据,放入生产者公用的仓库;消费者从仓库中取出字符串…...
![](https://common.cnblogs.com/images/copycode.gif)
网站工程工资一般多少钱/有创意的营销案例
一、使用正则表达式 1 string str "sztq数字提取123sztq数字提取"; 2 string result System.Text.RegularExpressions.Regex.Replace(str, "[^0-9]", ""); 3 Console.WriteLine("使用正则表达式提取"); 4 Console.WriteLine(result)…...