【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;
}
运行结果如下:
虚函数重写
在讲多态的构成条件之前要先引入一个虚函数的概念。
虚函数就是用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提供了override和final两个关键字,
可以帮助用户检测是否重写。
final:修饰虚函数,表示该虚函数不能再被重写
class Person { public:virtual void BuyTicket() final{cout << "原价买票" << endl;} };class Student : public Person { public:virtual void BuyTicket(){cout << "半价买票" << endl;} };![]()
override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
class Person { public:virtual void BuyTicket(){cout << "原价买票" << endl;} };class Student : public Person { public:virtual void BuyTickte() override{cout << "半价买票" << endl;} };上面故意错把派生类中的函数名写错了。
![]()
抽象类
概念
在虚函数的后面写上=0 ,
则这个函数为纯虚函数。
包含纯虚函数的类叫做抽象类(也叫接口类),
抽象类不能实例化出对象。
派生类继承后也不能实例化出对象,
只有重写纯虚函数,派生类才能实例化出对象。
纯虚函数规范了派生类必须重写,
另外纯虚函数更体现出了接口继承。
class Person
{
public:virtual void BuyTicket() = 0{cout << "原价买票" << endl;}void func(){}
};
实现继承和接口继承
普通函数的继承是一种实现继承,
派生类继承了基类函数,可以使用函数,
继承的是函数的实现。
虚函数的继承是一种接口继承,
派生类继承的是基类虚函数的接口,
目的是为了重写,达成多态,
继承的是接口。
所以如果不实现多态,
不要把函数定义成虚函数。
虚函数表
64位地址太长,
所以为了方便观察,
下面统一换成32位地址。
现在定义一个基类:
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};
然后定义一个Base对象,
那这个对象的对象模型是怎样的呢?
相比于没有虚函数的普通对象多了一个_vfptr:

看样子是一个数组,
数组元素都是指针,
那看来_vfptr是一个指针数组:

这里显示它有一个元素,
这个元素存放的是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;
};
此时再看一下它们的对象模型有什么变化:

基类对象的结构还是原来那样,
只不过虚表中多了一个指向Func2函数的指针,
Func3并不在这儿,因为它不是虚函数。
派生类继承了基类的虚表,
但是存的指针有些变化,
可以看到_vfptr[0]存放的是被重写后的Func1的地址。
所以就可以下一个简单的结论:
派生类先继承基类的虚表,
如果派生类重写了基类中某个虚函数,
用派生类自己的虚函数覆盖虚表中基类的虚函数。
那派生类自己的虚函数呢?
按理来说派生类有虚函数,
实例化出来的对象也应该有一个虚基表,
但是这里好像并不是这样。
实际上,
派生类自己新增加的虚函数,
会按其在派生类中的声明次序增加到基类虚表的最后,
和基类共用一个虚表。
而这里继承的基类的虚表没有显示出派生类的虚函数,
这是编译器的监视窗口故意隐藏了这个函数,
也可以认为是他的一个小bug。
那么我们如何查看d的虚表呢?
打印虚表
我们既然有虚表指针_vfptr,
那我们肯定就有办法打印它指向的虚表的内容,
也就是各个虚函数的地址。
下面就来看一下怎样获取。
首先在对象的存储结构中虚表指针存放在最上面,
也就是对象头四个字节(64位指针是八个字节),
所以对象模型如下:
因为我们最终要访问的指针类型是函数指针,
所以我们可以先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,
运行结果如下:
验证了此前所说的。
我们还可以看一下基类对象的虚表:
多继承中的虚表
看下面的代码:
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;
};
此时派生类是继承了两个基类的派生类,
那它现在的对象模型是怎样的呢?
可以看到普通多继承的场景下它完整继承了两个基类的虚表指针,
两个基类中都有Func1函数,
可以看到此时Func1函数都被重写后的函数覆盖了。
那派生类它自己的虚函数呢?
这里直接给出结论:多继承派生类的虚函数放在第一个继承基类部分的虚函数表中。
所以第一个虚表中存放了三个函数指针,
分别指向Derive::Func1(),Base1::Func2(),Derive::Func3(),
第二个虚表中存放了两个函数指针,
分别指向Derive::Func1(),Base2::Func2()。
所以对象模型如下:
可以通过打印虚表来验证一下。
这里直接对d取地址可以拿到Base1::_vfptr,
但是要怎么拿到Base2::_vfptr呢?
我们可以直接让指针偏移sizeof(Base1)个字节,
也就是把&d改为(char*)&d + sizeof(Base1),
结果如下:
虚表的存储
我们现在再明确一下概念,
虚函数是函数,
和普通函数一样,
存放在代码段。
虚表是一个指针数组,
存放指向虚函数的指针。
而类实例化出来的对象中存放的是一个虚表指针,
是指向虚表的指针。
所以对象中虚表指针存放在哪是很明确的,
就看对象存放在哪,
对象在栈上,那它的虚表指针也在栈上,
对象在堆上,那它的虚表指针就在堆上。
那问题来了,虚表存在哪呢?
我们可以通过一个简单的比对来看看:
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;
}
首先我们有一个基类指针,
当我们使用这个指针去调用虚函数时,
会去访问这个基类指针指向的对象的虚表指针,
然后通过虚表指针找到虚表,
在虚表中找到对应的函数然后调用。
如果调用的函数不存在于虚表中,
则会发生报错,
最简单的就是使用基类指针去调用派生类自己定义的虚函数。
在文章虚表的存储部分最后说了,
对象的虚表指针是在构造函数中初始化的,是运行时才有的,
在程序运行期间,
根据具体拿到的类型确定程序的具体行为,
调用具体的函数,
这就是所谓的动态绑定,也叫动态多态。
我们可以通过汇编代码看一下普通调用和多态调用时的区别:

与动态绑定相对的是静态绑定,也叫静态多态,
我们常用的函数重载就是一种静态多态,
是在在程序编译期间确定了程序的行为。
几个小问题
-
内联函数(inline)可以是虚函数吗?
可以。
不过编译器就忽略inline属性,
这个函数就不再是inline,
因为虚函数要放到虚表中去。 -
静态成员可以是虚函数吗?
不能。
因为静态成员函数没有this指针,
使用类型::成员函数的调用方式无法访问虚函数表,
所以静态成员函数无法放进虚函数表 -
构造函数可以是虚函数吗?
不能。
因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。
如果构造函数定义成虚函数,
那想调用构造函数就要去虚函数表中寻找,
而虚表指针还没有初始化,
就找不到构造函数了。 -
对象访问普通函数快还是虚函数更快?
首先如果是通过"对象.函数"的方式去调用,
是一样快的。
如果是指针对象或者是引用对象,
则调用的普通函数快,
因为调用虚函数时还需要先到虚函数表中去查找。
相关文章:
【C++】面向对象之多态
文章内的所有调试都是在vs2022下进行的, 部分小细节可能因编译器不同存在差异。 文章目录 多态的定义和实现概念引入多态的构成条件虚函数重写通过基类的指针或者引用调用虚函数 override和final 抽象类概念实现继承和接口继承 虚函数表单继承中的虚表打印虚表多继…...
卡尔曼滤波器简介——多维卡尔曼滤波
原文:多维卡尔曼滤波 (kalmanfilter.net) 目录 前言 基本背景 状态外推方程 示例 - 飞机 - 无控制输入 示例 - 带控制输入的飞机 示例 – 坠落物体 状态外推方程维度 线性时不变系统 线性动态系统建模 状态外推方程的推导 状态空间表示形式 示例 - 等速…...
如何用 GPT-4 帮你写游戏?
你知道的,GPT-4 发布了。 目前你想要用上 GPT-4,主要的渠道是 ChatGPT Plus 。作为交了订阅费的用户,你可以在对话的时候选择模型来使用。 另一种渠道,就是申请官方 API 的排队。我在申请 New Bing Chat 的时候,耐心被…...
R语言的贝叶斯时空数据模型实践技术应用
时间-空间数据(以下简称“时空数据”)是最重要的观测数据形式之一,很多科学研究的数据都以时空数据的形式得以呈现,而科学研究目的可以归结为挖掘时空数据中的规律。另一方面,贝叶斯统计学作为与传统统计学…...
Lazysysadmin靶机渗透过程
准备工作 下载好靶机到本地后 VMware导入OVA 启动靶机 扫描信息 首先扫描整个C段发现主机 进一步扫描端口 从扫描结果可知: Samba服务MySQLSSH端口网站端口 先对网站进行目录遍历 发现有wordpress网站和phpmyadmin管理系统 出现了非常多遍My name is togie.可能…...
为什么网络安全缺口很大,招聘却很少?
2020年我国网络空间安全人才数量缺口超过了140万,就业人数却只有10多万,缺口高达了93%。这里就有人会问了: 1、网络安全行业为什么这么缺人? 2、明明人才那么稀缺,为什么招聘时招安全的人员却没有那么多呢࿱…...
SpringBoot手册
目录 依赖管理关于各种的 start 依赖关于自动配置关于约定大于配置中的配置SpringBoot 整合 SpringMVC定制化 SpringMVC静态资源处理对上传文件的处理对异常的处理Web原生组件注入(Servlet、Filter、Listener)Interceptor 自定义拦截器DispatcherServlet…...
【Linux】如何实现单机版QQ,来看进程间通信之管道
学会了管道,就可以实现简单的qq哦~ 文章目录 前言一、匿名管道总结 前言 为什么要进行进程间通信呢?因为需要以下这些事: 数据传输:一个进程需要将它的数据发送给另一个进程 资源共享:多个进程之间共享同样的资源。 …...
从一到无穷大 #6 盘满排查过程
文章目录 引言df/du 原理排查思路文件系统预留空间进程占用句柄挂载覆盖 引言 核心在于执行df和du的时候发现显示的存储量完全不同,我本地系统盘有99G空间,du显示占用了45G,但是df却显示使用了99G,排查的过程本文所示。 先记录几…...
ChatGPT技术原理 第九章:数据集和训练技巧
目录 9.1 对话数据集 9.2 数据预处理 9.3 预训练技巧 9.4 微调技巧 9.5 多任务学习...
NCR被攻击后服务中断!原是BlackCat勒索软件作祟
近日,在遭到BlackCat勒索软件攻击后,NCR 的 Aloha 销售点平台出现中断。 NCR公司是全球关系管理技术解决方案领导供应商,为全球零售、金融、传讯、制造、旅游、交通及保安等客户提供服务。凭著累积多年的业界知识、专业顾问经验、专业增值应用…...
带你认识什么是BMS(电池管理系统)
文章目录 概述BMS的硬件拓扑BMS的电气架构BMS的功能BMS的总压采集(主板功能)BMS的电流采集(主板功能)BMS的电芯电压和温度采集(从板功能)BMS的SOC、SOP和SOH(ASW计算)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包。本文就来用它实现个人手机定位分析,感兴趣的小伙伴可以了解一下 但其实交通时空大数据并不仅仅局限于交通工具产生的数据,我们的日常生活中也会产生大量的数据。比如我们的手机…...
Unity Navgation系统杂记
立即停止寻路 使用agent.isStoppedtrue,可以停止寻路,但是有很大的延迟,视觉体验很不好。 使用agent.enabledfalse,通过禁用NavMeshAgent组件的方式实现立即停止寻路。因为组件被禁用可能会产生其它问题,比如失去了Ob…...
[2021.11.9]lighteffect架构优化详细设计文档
1 lighteffect系统架构图 图1-1 整改前lighteffect系统架构图 上图为整改前lighteffect系统架构图,存在的问题如下: (1)代码bug 原因:由于系统中兼容了lighteffect和lighteffect2,写代码时只记了一个,出现代码bug。…...
经典回归算法
回归的概念 回归方程: 写成矩阵: 核心问题,构建预测函数z来映射特征矩阵x和标签y的线性关系 预测的目标值,有连续值也有离散值 连续值,就直接预测输出就行离散值,需要在输出端加一个变换函数例如。Si…...
Python两三行代码轻松批量添加~防韩还是很有必要的~
人生苦短,我用python 一直想做一个这种系列的但是因为七七八八的事情总是忘记, 今天正好有空,来开整一下~ 首先, 天冷防韩是什么梗? 【天冷防韩】 “天冷防韩”是“天冷防寒”的谐音, 不过“寒”指的…...
开心消消乐
给定一个 N 行 M 列的二维矩阵,矩阵中每个位置的数字取值为 0 或 1,矩阵示例如: 1 1 0 0 0 0 0 1 0 0 1 1 1 1 1 1 现需要将矩阵中所有的 1 进行反转为 0,规则如下: 当点击一个 1 时,该 1 被反转为 0&am…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...
如何通过git命令查看项目连接的仓库地址?
要通过 Git 命令查看项目连接的仓库地址,您可以使用以下几种方法: 1. 查看所有远程仓库地址 使用 git remote -v 命令,它会显示项目中配置的所有远程仓库及其对应的 URL: git remote -v输出示例: origin https://…...

