【c++随笔14】虚函数表
【c++随笔14】虚函数表
- 一、虚函数表(Virtual Function Table)
- 1、定义
- 2、查看虚函数表
- 2.1、 问题:三种类型,包含一个int类型的class、一个int类型的变量、int类型的指针:这三个大小分别是多少呢?
- 2.2、怎么发现虚函数表存在的?
- 2.3、查看虚函数表里面都有什么?
- 3、继承——虚函数的重写与覆盖
- 4、虚函数为何可以实现多态?
- 5、对象也能切片,为什么不能实现多态?(普通的继承为何不能实现多态?)
- 6、打印虚函数表
- 6.1同一个类型它们的虚表内存地址都是一样的,同一类型的对象共用一份虚表。
- 6.2打印虚函数表
- 二、多态原理
- 1、动态绑定、动态类型、静态类型
- 2、动态绑定和静态绑定对比
- 3、虚函数表的存储位置
- 4、汇编层面看多态实现原理
- 三、多继承中的虚函数表
- 1、多继承会有两张虚表(继承两个时),
- 2、派生类定义的虚函数,存放在第一张虚函数表中
- 四、经典问题
原创作者:郑同学的笔记
原创地址:https://zhengjunxue.blog.csdn.net/article/details/131932164
qq技术交流群:921273910
一、虚函数表(Virtual Function Table)
1、定义
虚函数表是一个由虚函数组成的表格,用于实现动态绑定和多态性。每个包含虚函数的类都有自己的虚函数表,该表列出了该类及其所有基类的虚函数。当一个对象被创建时,它的类虚函数表也被创建,并且可以通过该对象的指针或引用来调用虚函数表中的函数。
虚函数表是一种实现动态多态性的机制。每个包含虚函数的类都有一个虚函数表,其中存储着该类的虚函数地址。当通过基类指针或引用调用虚函数时,程序会根据对象的实际类型查找对应类的虚函数表,并调用正确的虚函数。
2、查看虚函数表
2.1、 问题:三种类型,包含一个int类型的class、一个int类型的变量、int类型的指针:这三个大小分别是多少呢?
#include <iostream>
using namespace std;class Base {
private:int _b = 1;
public:void Func1() {cout << "Func1()" << endl;}void Func2() {cout << "Func2()" << endl;}void Func3() {cout << "Func3()" << endl;;}
};int main(void)
{Base b;cout << sizeof(b) << endl;cout << sizeof(int) << endl;cout << sizeof(int *) << endl;return 0;
}
输出
-
答案:(64位系统)
- class 对象实例:占4个字节
- int 变量:占4个字节
- int 指针:占用8个字节
-
其他结论:
- 类class占用内存的大小,就是类calss成员变量占用内存的大小;
- 类class占用内存的大小,和成员函数无关;
- 类也可以作为一种数据类型来看待;
-
Base实例b里面有什么
只有成员变量_b
2.2、怎么发现虚函数表存在的?
- 2.2.1加了virtual后,虚函数Base的大小
#include <iostream>
using namespace std;class Base {
private:int _b = 1;
public:virtual void Func1() {cout << "Func1()" << endl;}virtual void Func2() {cout << "Func2()" << endl;}virtual void Func3() {cout << "Func3()" << endl;;}
};int main(void)
{Base b;cout << sizeof(b) << endl;return 0;
}
输出
结论:
加了virtua后,虚函数Base大小为16;
- 2.2.2、加了虚函数表后为何变成了16字节?
调试查看Base类的实例b里面都有什么
调试截图如下,除了 _b 成员外,还有了一个 _vfptr 在 b1对象中
结论:
- 由于类里面,除了 _b 成员外,还增加了_vfptr,所以由4字节变成了16字节;
- _vfptr就是虚函数表指针(virtual function pointer),指向虚函数表;
扩展:虚函数表指针占用8字节,int类型占用4字节;那8+4应该是12字节,为何总的内存变成了18字节呢,这里面有个内存对齐的问题。打个比方,你int类型占用4字节,double占用8字节,那么会总的便会占用16字节,int类型也会分配8字节的空间。
2.3、查看虚函数表里面都有什么?

结论:
- 虚函数表里面是一个数组;
- 数组里面存储的是每一个虚函数的地址;
扩展
其实,看了我之前写的文章就知道,其实类的实例的每一个成员函数,都有一个单独的地址,存储在代码段.text段。
3、继承——虚函数的重写与覆盖
代码:现在我们增加一个子类 Derive 去继承 Base:
#include <iostream>
using namespace std;class Base {
private:int _b = 1;public:virtual void Func1() {cout << "Func1()" << endl;}virtual void Func2() {cout << "Func2()" << endl;}virtual void Func3() {cout << "Func3()" << endl;;}
};// 子类 Derive
class Derive : public Base {
public:virtual void Func1() {cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main(void)
{Derive d;cout << sizeof(d) << endl;return 0;
}
输出
父类 b 对象和子类 b 对象虚表是不一样的,这里看我们发现 Func1 完成了重写,
所以 d 的虚表中存的是重写的 Derive::Func1,所以虚函数的重写也叫做覆盖。
就可以理解为:子类的虚表拷贝了父类的虚表,子类的 Func1 覆盖掉了父类上的 Func1。
(覆盖指的是虚表中虚函数的覆盖)
- 虚函数重写:语法层的概念,子类对继承父类虚函数实现进行了重写。
- 虚函数覆盖:原理层的概念,子类的虚表,拷贝父类虚表进行了修改,覆盖重写那个虚函数。
🔺 总结:虚函数的重写与覆盖,重写是语法层的叫法,覆盖是原理层的叫法。
4、虚函数为何可以实现多态?
多态调用实现是依靠运行时去指向对象的虚表中查,调用函数地址。
#include <iostream>
using namespace std;class Base {
private:int _b = 1;public:virtual void Func1() {cout << "Base::Func1()" << endl;}virtual void Func2() {cout << "Base::Func2()" << endl;}virtual void Func3() {cout << "Base::Func3()" << endl;;}
};// 子类 Derive
class Derive : public Base {
public:virtual void Func1() {cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main(void)
{Base b;Derive d;Base* ptr = &b;ptr->Func1(); // 调用的是父类的虚函数ptr = &d;ptr->Func1(); // 调用的是子类的虚函数return 0;
}
输出
5、对象也能切片,为什么不能实现多态?(普通的继承为何不能实现多态?)
既然指针和引用可以实现多态,那父类赋值给子类对象也可以切片,
根本原因是:对象切片时,子类对象只会拷贝成员给父类对象,并不会拷贝虚表指针。(没有虚函数表)
之前我们讨论过,为何没有静态多态的概念,除了当时说的部符合多态的定义外,本质的原因就在这里。
6、打印虚函数表
6.1同一个类型它们的虚表内存地址都是一样的,同一类型的对象共用一份虚表。
#include <iostream>
using namespace std;class Base {
public:int _b = 1;public:virtual void Func1() {cout << "Base::Func1()" << endl;}virtual void Func2() {cout << "Base::Func2()" << endl;}
};// 子类 Derive
class Derive : public Base {
public:virtual void Func1() {cout << "Derive::Func1()" << endl;}virtual void Func3() {cout << "Derive::Func3()" << endl;;}
public:int _d = 2;
};using pf = void(*)();
//typedef void(*pf)(void); //和上面的写法相等,看不懂的可以看下我的另外一篇博客《函数指针》int main(void)
{Base b;Derive d1;Derive d2;return 0;
}
查看局部变量的窗口
可以看到子类继承自父类的虚函数表中Func1函数地址是重写之后的函数地址,已经将父类的func函数地址覆盖掉。
- 如果父类中的虚函数没有被子类重写,那么子类的虚函数表中的地址仍然是父类中虚函数的地址。
- 只有虚函数才会进虚函数表,非虚函数是不进虚函数表的。
- 如果派生类中存在新增加的虚函数,那么就会按照在派生类中的声明顺序依次添加到派生类的虚函数表的最后。
- 虚函数表本质就是一个虚函数指针数组,而虚函数表指针本质就是这个数组的首元素地址。虚函数表的最后一个字段通常置为nullptr。
6.2打印虚函数表
我们例子中,每一个虚函数返回值类型void,参数无,所以,虚函数指针数组中元素的类型为void(*)(void);(不懂的可以查看我的另外一篇博客《【c++随笔09】函数指针》
- 注意:派生类的虚函数,visual并没显示出来,但是我们打印出来了,可见:visual的可视化是有些问题的。
#include <iostream>
using namespace std;class Base {
public:int _b = 1;public:virtual void Func1() {cout << "Base::Func1()" << endl;}virtual void Func2() {cout << "Base::Func2()" << endl;}
};// 子类 Derive
class Derive : public Base {
public:virtual void Func1() {cout << "Derive::Func1()" << endl;}virtual void Func3() {cout << "Derive::Func3()" << endl;;}
public:int _d = 2;
};using pf = void(*)();
//typedef void(*pf)(void); //和上面的写法相等,看不懂的可以看下我的另外一篇博客《函数指针》int main(void)
{Base b;Derive d1;Derive d2;Base* ptr = &d1;//ptr->Func1(); // 调用的是父类的虚函数//Base* ptr2 = new Derive();pf* pfun = (pf*)*(long long*)ptr;//2.pp这个指针是函数指针数组的首元素的地址。while (*pfun){cout << *pfun << endl;(*pfun)();cout << endl;pfun++;}cout << "----------------------------------------" << endl;ptr = &d2;pfun = (pf*)*(long long*)ptr;while (*pfun){cout << *pfun << endl;(*pfun)();cout << endl;pfun++;}cout << "----------------------------------------" << endl;ptr = &b;pfun = (pf*)*(long long*)ptr;while (*pfun){cout << *pfun << endl;(*pfun)();cout << endl;pfun++;}return 0;
}
输出
二、多态原理
1、动态绑定、动态类型、静态类型
我们依然查看《C++ Primer 第5版》第15章节末尾 术语表中的介绍(p575-576页)
-
动态绑定(dynamic binding) 直到运行时才确定到底执行函数的哪个版本。在C++语言中,动态绑定的意思是在运行时根据引用或指针所绑定对象的实际类型来选择执行虚函数的某一个版本。
-
动态类型(dynamic type) 对象在运行时的类型。引用所引对象或者指针所指对象的动态类型可能与该引用或指针的静态类型不同。基类的指针或引用可以指向一个派生类对象。在这样的情况中,静态类型是基类的引用(或指针),而动态类型是派生类的引用(或指针)。
-
静态类型(static type) 对象被定义的类型或表达式产生的类型。静态类型在编译时是已知的。
《C++ Primer 第5版》第15.2章节(p529页)
- 以动态绑定有时又被称为运行时绑定(run-time binding)。
- 在C++语言中,当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定。
2、动态绑定和静态绑定对比
由于《C++ Primer 第5版》并没有给出静态绑定的概念,我们暂时把在程序编译期间确定了程序的行为,也称为静态绑定。比如函数重载。
- 动态绑定
#include <iostream>
using namespace std;class Base {
private:int _b = 1;
public:void Func1() {cout << "Func1()" << endl;}
};int main(void)
{Base *ptr1 = new Base();ptr1->Func1();return 0;
}
- 静态绑定
#include <iostream>
using namespace std;class Base {
private:int _b = 1;
public:virtual void Func1() {cout << "Func1()" << endl;}
};int main(void)
{Base *ptr1 = new Base();ptr1->Func1();return 0;
}
- 汇编层面分析静态绑定和动态绑定的区别
g++ main.cpp
objdump -h -d -x ./a.out
对比反汇编的代码,如下截图
3、虚函数表的存储位置
推断:虚表存储在只读数据段上。

4、汇编层面看多态实现原理
- 多态
#include <iostream>
using namespace std;class Base {
private:int _b = 1;
public:virtual void Func1() {cout << "Func1()" << endl;}
};// 子类 Derive
class Derive : public Base {
public:virtual void Func1() {cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};void pf(Base *b)
{b->Func1();
}int main(void)
{Base *ptr1 = new Base();pf(ptr1);Base *ptr2 = new Derive();pf(ptr2);return 0;
}
- 非多态
#include <iostream>
using namespace std;class Base {
private:int _b = 1;
public:void Func1() {cout << "Func1()" << endl;}
};// 子类 Derive
class Derive : public Base {
public:void Func1() {cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};void pf(Base *b)
{b->Func1();
}int main(void)
{Base *ptr1 = new Base();pf(ptr1);Base *ptr2 = new Derive();pf(ptr2);return 0;
}
g++ main.cpp
objdump -h -d -x ./a.out
对比反汇编的代码,如下截图
三、多继承中的虚函数表
// 基类A
class A {
public:virtual void func1() {std::cout << "A::func1()" << std::endl;}virtual void func2() {std::cout << "A::func2()" << std::endl;}};// 基类B
class B {
public:virtual void func1() {std::cout << "B::func1()" << std::endl;}/*virtual void func2() {std::cout << "B::func2()" << std::endl;}*/};// 派生类C,多继承自A和B
class C : public A, public B {
public:virtual void func1() override {std::cout << "C::func1()" << std::endl;}/*virtual void func2() override {std::cout << "C::func2()" << std::endl;}*/virtual void func3() {std::cout << "C::func3()" << std::endl;}
};int main() {C c;
}
我们先透过监视简单看一下:
2、派生类定义的虚函数,存放在第一张虚函数表中
#include <iostream>// 基类A
class A {
public:virtual void func1() {std::cout << "A::func1()" << std::endl;}virtual void func2() {std::cout << "A::func2()" << std::endl;}};// 基类B
class B {
public:virtual void func1() {std::cout << "B::func1()" << std::endl;}/*virtual void func2() {std::cout << "B::func2()" << std::endl;}*/};// 派生类C,多继承自A和B
class C : public A, public B {
public:virtual void func1() override {std::cout << "C::func1()" << std::endl;}/*virtual void func2() override {std::cout << "C::func2()" << std::endl;}*/virtual void func3() {std::cout << "C::func3()" << std::endl;}
};int main() {C c;// 打印A的虚函数表std::cout << "A's vtable: " << std::endl;void** aVTable = *(void***)(&c);for (int i = 0; aVTable[i] != nullptr; i++) {std::cout << " [" << i << "]" << aVTable[i]<<" -> "<<" 函数执行";void(*func)() = (void(*)())(aVTable[i]);func();}// 打印B的虚函数表std::cout << "B's vtable: " << std::endl;void** bVTable = *(void***)(((char*)&c) + sizeof(A));for (int i = 0; bVTable[i] != nullptr; i++) {std::cout << " [" << i << "]" << aVTable[i]<<" -> " << " 函数执行";void(*func)() = (void(*)())(bVTable[i]);func();} 根据虚函数表地址运行虚函数//std::cout << "Running virtual function using A's vtable: " << std::endl;//void(*aFunc)() = (void(*)())(aVTable[0]);//aFunc();//std::cout << "Running virtual function using B's vtable: " << std::endl;//void(*bFunc)() = (void(*)())(bVTable[0]);//bFunc();return 0;
}
输出
四、经典问题
-
- inline函数可以是虚函数嘛?
inline函数可以是虚函数,但是其内联的特性也就没有了,因为inline只是对编译器的建议。内联函数是在调用的地方展开,没有函数地址,而虚函数的地址是要写入虚函数表的,所以内联函数和虚函数只能为其中的一个,不可兼得。
-
- 静态成员函数可以是虚函数嘛?
不能,因为静态成员函数没有this指针,使用类名::成员函数的调用方式 无法访问虚函数表,所以静态成员函数无法放进虚函数表。
-
- 构造函数可以是虚函数嘛?
不可以,因为虚函数表指针是在构造函数的初始化列表初始化的,但是虚函数又要借助虚函数表指针来调用虚函数,两者矛盾,所以不可以为虚函数。
-
- 析构函数可以是虚函数嘛?
可以,并且建议将析构函数定义为虚函数,因为这样可以避免内存泄漏的问题。如果子类对象是动态开辟的,使用父类指针指向子类对象,在delete时如果构成多态那么就会调用子类析构函数,而调用子类析构函数前系统会默认先调用父类析构函数,这样可以避免内存泄漏。
-
- 对象访问普通函数快还是访问虚函数快?
如果是通过实例化的对象访问那么是一样快的,如果是指针或引用对象访问的话是访问普通函数快的,因为指针或引用去访问虚函数时走的是多态调用是一个晚绑定,需要在运行时去需表中找函数的地址。
-
- 虚函数表是在什么时候形成的?存在哪?
和虚函数相关的字符,字符在只读数据区(.rodata),但是虚函数的实现代码应该是和其他的函数一样,存储在代码段(.text)
-
- 什么是抽象类?
函数纯虚函数的类叫做抽象类,此类不能实例化出对象,这也强制了其派生类如果想要实例化出对象那么就必须重写纯虚函数。
-
- C++菱形继承解决方案和多态原理?
菱形继承具有数据冗余和二义性的问题,解决的方法是通过虚继承的方式,虚继承的派生类中会产生一个虚基表指针,该指针指向虚基表,表中的内容是一个到冗余数据的偏移量,而原本冗余的数据会被放到派生类对象的最后。
多态的原理是通过重写虚函数,达到在派生类的虚函数表中重写的虚函数地址覆盖掉原本的地址,然后通过基类的指针或者引用指向派生类对象时,调用虚函数调用的时子类重写后的虚函数,而执行基类对象时调用的就是基类的虚函数达到多态的行为。
不要将虚基表和虚函数表搞混。
相关文章:
【c++随笔14】虚函数表
【c随笔14】虚函数表 一、虚函数表(Virtual Function Table)1、定义2、查看虚函数表2.1、 问题:三种类型,包含一个int类型的class、一个int类型的变量、int类型的指针:这三个大小分别是多少呢?2.2、怎么发现…...
分布式链路追踪实战篇-日志库集成opentelemetry的思路
由上文分布式链路追踪入门篇-基础原理与快速应用可以知道分布式链路追踪的作用,但是距离应用到项目中,我们还需要对项目中一些关键组件进行opentelemetry的集成,例如日志库,ORM、http框架、rpc框架等。 一、日志库如何集成opentel…...
电脑投屏到电视的软件,Mac,Linux,Win均可使用
电脑投屏到电视的软件,Mac,Linux,Win均可使用 AirDroid Cast的TV版,可以上笔记本电脑或台式电脑直接投屏到各种安卓电视上。 无线投屏可以实现本地投屏及远程投屏,AirPlay协议可以实现本地投屏,大家可以按需…...
基于vue+element-plus+echarts编写动态绘图页面
我们都知道网页的echarts可以画图,但是很多情况下都需要编码实现绘图逻辑,如果有一个前端页面可以让我输入数据然后动态生成图表的话那么该多好,其实这个需求不难实现,先看效果。 整体页面分为左右两个部分,其中左边的…...
无人机巡检如何做到实时识别,从数据到模型全流程解读
在数字化和自动化飞速发展的今天,AI识别算法正在加速进入行业生产系统。 基于巡检数据的智能开发,识别算法突破性进展的核心驱动力在于需求——从全天候巡视的平安城市,到潮汐变化的交通网络,从广阔的水域,到繁忙的街道…...
zlmediakit实现rtsp流服务器
本次实现是将内存中的H264数据经过zlmediakit实现为rtsp流。 我是用的是CAPI的方式,将zlmediakit作为一个sdk嵌入到自己的程序中而不是作为一个独立的进进程服务。 1.编译完成zkmedialit后会得到bin include lib三个文件夹如图 其中bin中的MediaServer是作为独立的…...
保姆级 ARM64 CPU架构下安装部署Docker + rancher + K8S 说明文档
1 K8S 简介 K8S是Kubernetes的简称,是一个开源的容器编排平台,用于自动部署、扩展和管理“容器化(containerized)应用程序”的系统。它可以跨多个主机聚集在一起,控制和自动化应用的部署与更新。 K8S 架构 Kubernete…...
耶鲁博弈论笔记
编辑记录: 1126:开个新坑,耶鲁大学的博弈论课程, 和专业相关不大,纯兴趣,尽量写好一点吧 1. 首先指出博弈论是一种研究策略形式的方法,对于经济学中,完全竞争市场只能被动接受均衡…...
一个简易的URL爬虫程序(java)
该程序是一个简单的Java程序,用于从指定的URL中获取网页内容并保存到本地文件。通过URL类打开指定的URL链接,并使用openStream()方法获取输入流。然后使用Scanner类读取输入流中的内容,并使用PrintWriter类将读取到的内容写入到本地文件中。 …...
Deep Learning(wu--46)
文章目录 ContentsBeginBasic逻辑回归SGD导数计算图(反向传播)向量化广播numpy Neural Network向量化激活函数梯度下降深层表示反向传播 Contents Begin Basic 逻辑回归 SGD 导数 计算图(反向传播) 向量化 广播 numpy Neural Netw…...
Java网络爬虫实战
List item 文章目录 ⭐️写在前面的话⭐️📌What is it?分类网络爬虫按照系统结构和实现技术,大致可以分为以下几种类型:通用网络爬虫(General Purpose Web Crawler)、聚焦网络爬虫(Focused Web Crawler&a…...
srs的webrtc信令分析
关于webrtc的流信令只有四个 /rtc/v1/publish/,这是推流接口,是推流客户端跟SRS交换SDP的接口 /rtc/v1/play/,这是拉流接口,是拉流客户端跟SRS交换SDP的接口 /rtc/v1/whip/,这也是推流接口,作用是也是交换…...
实现简单的操作服务器和客户端(上)
一、说明 描述:本教程介绍如何使用 simple_action_server 库创建斐波那契动作服务器。此示例操作服务器生成斐波那契序列,目标是序列的顺序,反馈是计算的序列,结果是最终序列。 内容 创建操作消息编写一个简单的服务器 代码...
基于Java SSM框架+Vue实现药品销售进销存网站项目【项目源码+论文说明】
基于java的SSM框架Vue实现药品销售进销存网站演示 摘要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势;对于药品管理系统当然也不能排除在外,随着网络技术的不断成熟,带动了…...
【刷题笔记】加油站||符合思维方式
加油站 文章目录 加油站1 题目描述2 思路3 解题方法 1 题目描述 https://leetcode.cn/problems/gas-station/ 在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i1 个加油站需要消…...
【ArcGIS Pro微课1000例】0037:ArcGIS Pro中模型构建器的使用---以shp批量转kml/kmz为例
文章目录 一、ArcGIS Pro模型构建器介绍二、shp批量转kml/kmz1. 打开模型构建器2. 添加工作空间4. 添加【创建要素图层】工具5. 添加【图层转kml】工具6. 输出文件命名7. 运行模型三、模型另存为1.py文件2. 保存为工具一、ArcGIS Pro模型构建器介绍 模型构建器是一种可视化编程…...
前端 vue 面试题(二)
文章目录 如何让vue页面重新渲染组件间通信vue为什么要mutation、 action操作插槽、具名插槽、作用域插槽vue编译使用的是什么库?vue怎么实现treeshakingwebpack实现treeshaking为什么只有es module 能支持 tree shaking mixin 的作用mixin的底层原理nexTick原理vue…...
MySQL 高可用架构
MySQL 是实际生产中最常用的数据库,生产环境数据量极为庞大,对性能和安全要求很高,单机的 MySQL 是远远达不到的,所以必须搭建一个主从复制架构,同时可以基于一些工具实现高可用架构,在此基础上,…...
JVM虚拟机:G1垃圾回收器的日志分析
本文重点 本文我们将学习G1垃圾回收器的日志 使用 执行命令 java -Xms20M -Xmx20M -XX:PrintGCDetails -XX:UseG1GC 类名 分析 前面我们学习了G1垃圾回收器,它的回收有三种可能: YGC FGC MixedGC GC pause表示STW,Evacuation表示复制对象,…...
解决视口动画插件jquery.aniview.js使用animate.css时无效的问题(最新版本网页视口动画插件的使用及没作用、没反应)
当网站页面元素进入视口时自动应用过渡效果。CSS过渡效果可以为网页添加动画效果,并提供了一种平滑的转换方式,使元素的变化更加流畅和生动。而通过jQuery插件来获取页面滚动位置决定合适调用动画效果。 一、官网 animate.css官网 一款强大的预设css3动…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...
抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...











