c++进阶学习--------多态

news2024/11/17 21:16:12

前言

需要声明的,本节课件中的代码及解释都是在vs2022下的x86程序中,涉及的指针都是4bytes。 如果要其他平台下,部分代码需要改动。

比如:如果是x64程序,则需要考虑指针是8bytes问题等等

1. 多态的概念

1.1 概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会 产生出不同的状态。

举个栗子:

比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。

再举个栗子:

最近为了争夺在线支付市场,支付宝年底经常会做诱人的扫红包支付给奖励金的活动。

那么大家想想为什么有人扫的红包又大又新鲜8块、10块...

而有人扫的红包都是1毛,5 毛....

其实这背后也是一个多态行为。

支付宝首先会分析你的账户数据,比如你是新用户、比如 你没有经常支付宝支付等等,那么你需要被鼓励使用支付宝,那么就你扫码金额 = random()%99;

比如你经常使用支付宝支付或者支付宝账户中常年没钱,那么就不需要太鼓励你去使用支付宝,那么就你扫码金额 = random()%1;

总结一下:同样是扫码动作,不同的用户扫得到的不一样的红包,这也是一种多态行为。

2. 多态的定义及实现

2.1多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。

比如Student继承了 Person。Person对象买票全价,Student对象买票半价。

那么在继承中要构成多态还有两个条件:

1. 必须通过基类的指针或者引用调用虚函数

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

少一个都不构成多态!!! 

但是有两点是例外:

1.除父类外,其他子类中的函数不必使用 virtual 修饰,此时仍然能构成多态(注意三同,需要构成重写)

2.父子类中的虚函数返回值可以不相同,但此时需要返回对应的父类指针或子类指针,确保构成多态,这一现象称为协变

如何快速判断是否构成多态?

首先观察父类的函数中是否出现了 virtual 关键字

其次观察是否出现虚函数重写现象。是否满足三同 。      三同:返回值、函数名、参数(协变例外)

最后再看调用虚函数时,是否为【父类指针】或【父类引用】

父类指针或引用调用函数时,如何判断函数调用关系?

若满足多态:看其指向对象的类型,调用这个类型的成员函数

不满足多态:看具体调用者的类型,进行对应的成员函数调用 

2.2 虚函数

虚函数:即被virtual修饰的类成员函数称为虚函数。  

2.3虚函数的重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的 返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

虚函数的作用是在目标函数(想要构成多态的函数)之间构成重写(覆盖),一旦构成了重写(覆盖),那么子类对象在实现此虚函数时,会继承父类中的虚函数接口(返回值、函数名、参数列表)

然后覆盖至子类对应的虚函数处,因此重写又叫做覆盖

所以虚函数就是虚拟的函数,可以被覆盖的、实际形态未确定的函数,使用 virtual 修饰后,就是在告诉编译器:标记此函数,调用时要触发覆盖行为,同时虚表指针需要找到正确的函数进行调用

那析构函数加virtual,是不是虚函数重写?

是,因为类析构函数都被处理成destructor这个统一的名字

通过上图,可以发现构成虚函数重写的析构函数,和普通的构都一样,在继承中都是先析构子类,然后析构子类中的父类成员最后再析构父类,所以为什么会费劲c++祖师爷为什么费心思地想让析构函数也能实现虚函数重写呢

原因如下: 

 

会发现这里没销毁子类Student,会造成内存泄漏

造成内存泄漏的原因是:

在创建对象时,系统会自动调用构造函数进行初始化,这样需要申请内存,同样在程序结束时,或者需要销毁对象然后调用析构函数

但是这边创建对象的类型Person*,普通调用看的是当前对象的类型

p=new Student的时候,new Student的时候调用了Student和Person的默认构造(因为Student是继承了父类)

delete的时候,析构的是当前对象p的类型,p的类型是Person*,所以只销毁了子类中父类的对象
并没有销毁子类Student,造成内存泄漏

所以这才是在析构函数引入了虚函数,并且在底层同一命名为destructor这个统一的名字就是为了构成虚函数重写然后实现多态

虚函数的重写需要注意:

1.除了类中的成员函数外,普通函数不能添加 virtual 关键字进行修饰,因为虚函数、虚函数表、虚表指针是一体的,普通函数没有

2.此处的 virtual 修饰函数为虚函数,与 virtual 修饰类继承为虚继承没有关系:一个是实现多态的基础,而另一个是解决萎形继承的问题

3.同样的,假设不是父类指针或引用进行调用,不会构成多态,也不会发生重写(覆盖)行为

多态的原理就是基于虚函数的重写实现的------如果读者依旧不清楚虚函数的重写可以着重看看

本文章的第四点4.1----虚函数表

2.4C++11 override 和 final

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数 名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有 得到预期结果才来debug会得不偿失

因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。  

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

 

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

设计不想被继承类,如何设计?
方法1:基类构造函数私有

在父类public成员前面加CreateOb,可以让子类无法访问父类的public成员

但是创建对象的时候要先调用默认构造才能调用函数

所以在成员函数前面加static就可以了

静态成员函数属于类不属于对象,没有实例化也能调用 

方法2:基类加个final 

2.5 重载、覆盖(重写)、隐藏(重定义)的对比

截至目前为止,我们已经学习了三个“重”相关函数知识:重载、重写、重定义

这三兄弟不止名字很像,而是功能也都差不多,很多面试题中也喜欢考这三者的区别

重载:即函数重载,函数参数不同而触发,不同的函数参数最终修饰结果不同,确保链接时不会出错,构成重载

重写(覆盖):发生在类中,当出现虚函数且符合重写的三同原则时,则会发生重写(覆盖)行为,具体表现为父类虚函数接口+子类虚函数体,是实现多态的基础

重定义(隐藏):发生在类中,当子类中的函数名与父类中的函数名起冲突时,会隐藏父类同名函数,默认调用子类的函数,可以通过::指定调用

重写和重定义比较容易记混,简言之先看看是否为虚函数,如果是虚函数且三同,则为重写;若不是虚函数且函数名相同,则为重定义

注:在类的继承中,仅仅是函数名相同(未构成重写的情况下),就能触发重定义(隐藏) 

3. 抽象类 

3.1 概念

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口 类)

抽象类不能实例化出对象,派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

注意:只要类中有一个函数被修饰为纯虚函数,那么这个类就会变成抽象类

3.2、抽象类的用途

抽象类适合用于描述无法拥有实体的类,比如 人、动物、植物,毕竟这些都是不能直接使用的,需要经过继承赋予特殊属性后,才能作为一个独立存在的个体(对象) 

 再强调一次基类用了纯虚函数,派生类必须将这个虚函数重写

不然派生类是无法创建对象的!!!!!!

抽象类的继承很好的体现了函数重写时,继承的是父类虚函数接口的事实,这正是实现多态的基础

普通继承:子类可以直接使用父类中的函数

接口继承:子类虚函数继承父类虚函数的接口,进行重写,构成多态

建议:假如不是为了多态,那么最好不要使用 virtual 修饰函数,更不要尝试定义纯虚函数

4.多态的原理

 4.1虚函数表

这是一个空类,其中什么成员都没有,但有两个虚函数

所以一个对象的大小为多少?

 

答案是4,当前是32位平台下,如果是在64位平台,大小会变为8

大小随平台而变的只能是指针了,因此可以推测当前类中藏着一个虚表指针就是依靠这个虚表指针+虚表实现了多态

我们再用代码进行进一步的观察 

 

 

通过观察测试我们子类对象中除了_b成员,还多一个__vfptr放在对象的前面(注意有些 平台可能会放到对象的最后面,这个跟平台有关)

对象中的这个指针我们叫做虚函数表指针(v代 表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。

那么这个虚函数表的作用是什么呢?

我们往下分析

在继承中要构成多态有两个条件:

1. 必须通过基类的指针或者引用调用虚函数

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

可以发现虚函数表的核心作用是在运行时实现多态。

当基类的指针或引用指向派生类的对象时,通过虚函数表指针能够调用虚函数表,从而调用派生类中重写的虚函数,而不是基类的版本。这使得不同的对象可以根据它们的实际类型执行不同的操作。

那如果发生虚函数的重写,基类和派生类的虚函数表中会发生什么变化?

首先来捋一捋虚函数表中存放着什么

--------存放着虚函数的地址指针

虚函数表的主要内容是该类中所有虚函数的地址指针。每个虚函数表中的条目对应类中声明的一个虚函数,并指向该虚函数的实现地址。表中条目的顺序与虚函数在类中声明的顺序相对应。

继承结构中的虚函数覆盖

在继承体系中,当派生类覆盖了基类中的虚函数时,派生类的虚函数表中的相应条目会指向派生类中的实现。

如果一个基类声明并定义了虚函数,而派生类没有重写这个虚函数,派生类中的虚函数表中对应该函数的条目将指向基类版本的实现。

如果派生类重写了基类的虚函数,虚函数表中的相应条目将被更新为指向派生类的实现。

每个类(包括基类和派生类)都有自己独立的虚函数表。

对于没有重写的虚函数,派生类的虚函数表条目仍然指向基类中的实现。

对于新添加的虚函数,派生类会在它的虚函数表中增加新的条目。

文字太枯燥了,我们结合代码具体分析

 所以虚函数的重写本质上就是覆盖

对于监视窗口观察虚函数表遇到的问题

我们都知道派生类如果新添加的虚函数,派生类会在它的虚函数表中增加新的条目。

但是我们通过监视窗口是看不到的!!!

只能通过内存窗口观察到,因为虚函数表的末尾会以空来结尾(注意是vs的环境下,Linux是没有这个特点的)

但是新增的函数真的会在虚函数表里面吗,这个其实是可以证明的 

除了用内存监视窗口证明

还有一种方法

虚函数表本质上就是函数指针数组,通过这个特性我们还可以通过调用虚函数表中的每一个函数来证明新增的函数是否在虚函数表中

虚函数表的位置存储在哪里?

这里还有一个人很容易混淆的问题:

虚函数存在哪的?虚表存在哪的?

答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的!!但是人都是这样深以为然的。

注意:

虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。

另外对象中存的不是虚表,存的是虚表指针。

那么虚表存在哪的呢?

实际我们去验证一下会发现vs下是存在代码段的,

 %p特别用于指针类型的数据,输出的是指针所指向的内存地址。

因为虚表 指针就存放在对象开始的前四个字节,所以将对象强转为int*可以使得指针访问权限缩短为四个字节,正好是虚函数表指针的地址

于是就可以输出虚函数表指针的内容,也就是虚函数的表的位置

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

为什么不能用通过基类的对象调用虚函数?

Person类所创建的所有对象都有虚表指针,而且指针指向的都是同一个虚表

当Student st传参给函数Func()------也就是Func(st)

st赋值给p的过程不会产生临时变量,st会直接切片赋值,会将st中父类的成员变量都赋值给Person p,但是此时Person p中的虚表还是父类的,并不是st中的父类的虚表

此时无论形参传的是父类或者子类,调用的都是父类中的虚表,所以就实现不了多态 

有人说为什么不直接拷贝st中父类的虚表到p中?

这样就有可能使得父类对象调用子类的虚函数,如果父类拷贝子类的虚函数表,那么父类对象虚表中是父类虚函数还是子类就不确定了。

为什么要通过基类的指针或者引用调用虚函数? 

函数的参数列表是基类的指针或者引用的时候

我们传递子类的时候-----Func(&st)

Student*会强制转换成Person*然后赋予给p

强转类型然后赋予给p的过程中会改变p对虚表访问的位置

没有被强转类型前,p指向的是Person类型对象的虚表还有Person类的成员函数

强转类型然后赋予给p之后,p指向的是Student类型对象的虚表指针还有Person类的成员函数

不过通过这个监视窗口仍然可以发现p的指向内容不单单是student类型对象的虚表指针还有Person类的成员函数

还有Person类的成员变量_a和Student类的对象,但是其实指针都是访问不了的,只是方便监视而已 

4.3 动态绑定与静态绑定

1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态, 比如:函数重载

2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

 5.多继承关系的虚函数表

因为前面说的都是单继承关系的虚函数表,下面我们说说多继承的

此时出现了两个问题:

1.子类 Derive 中新增的虚函数 func3 位于哪张虚表中?

2.为什么重写的同一个 func1 函数,在两张虚表中的地址不相同?

这两个问题是多继承多态中的主要问题

在单继承中,子类中新增的虚函数会放到子类的虚表中,但这里是多继承,子类有两张虚表,所以按照常理来说,应该在两张虚表中都新增虚函数才对

但实际情况是 子类中新增的虚函数默认添加至第一张虚表中

通过 PrintVFTable 函数打印虚表进行验证

第一个问题解决了,现在看看第二个问题:

为什么重写的同一个 func1 函数,在两张虚表中的地址不相同?

这个就要从汇编底层的逻辑出发去观察代码了

先来看看ptr1如何调用函数的:

再看看ptr2如何调用函数的

我们可以发现ptr1调用func1的时候直接是从虚表中找到func1函数的位置,然后跳转到func1函数

而ptr2调用func1的时候会先跳转到别的位置执行指令,将自身的地址减8个字节,然后再跳转到func1函数的位置

为什么ptr2要进行跳转呢?

Dervie d这个对象里面有两个虚函数指针,一个是Base1,一个是Base2。

Base2* ptr2调用func1这个重写的虚函数的时候,它本身指向的内容是错误的。

ptr2指向的是第二张虚表的内容,而func1这个函数的函数地址是存储在第一张虚表中的,所以会让ptr2先跳转到其他地址。

那个地址有个命令,会让跳转完之后ptr2,减去八个字节。此时ptr2就指向第一张虚表的内容。就可以调用func1这个重写的虚函数了 

6.面试题

这个是大厂的面试题,可以说很多码农都稍不小心就答错了

 每个派生类对象都是一个基类对象。

这个成员函数是写在代码段的,然后子类继承这个函数而且并没有重写这个函数,那么现在代码段的这个成员函数将由子类和父类共同拥有,是指内存上共享同一段内存的意思

父类的成员函数,成员函数里面的this成员都是父类类型的对象

B*  p去调用text(),使得p的类型B*强转成A*,然后将p赋予给成员函数里的对象this,使得this此时指向子类中父类的虚表指针

text中的this成员去调用func()

多态条件:
1.三同(函数名、返回类型、参数列表)【func()满足】
2.基类指针或者引用去调用【func()由this成员调用,this成员此时类型是A*,func()也满足】
所以func()满足多态

然后此时调用的B类的func()函数,因为this成员指向的是子类中父类的虚表指针

最重要的一点:虚函数重写,函数名返回类型参数列表在父类,不会改变,只会重写内容。

所以B类的func函数,参数列表里面的val实际上是1

问答题:
1.什么是多态?
多态是指不同继承关系的类对象,去调用同一函数,产生了不同的行为。多态又分为静态的多态和动态的多态

2.为什么要在父类析构函数前加上 virtual 修饰?
与子类析构函数构成多态,确保析构函数能被成功调用

3.什么是重载、重写、重定义?三者区别是什么?
重载:同名函数因参数不同而形成不同的函数修饰名,因此同名函数可以存在,并且能被正确匹配调用

重写:父子类中的函数被 virtual 修饰为虚函数,并且符合“三同”原则,构成重写

重定义:父子类中的同名函数,在不被重写的情况下,构成重定义,父类同名函数被隐藏

重载可以出现任何位置,只要函数在同一作用域中,而重定义是重写的基础,或者是重写包含重定义,假设因为没有 virtual 修饰不构成重写,那么必然构成重定义,重写和重定义只能发生在继承关系中

4.为什么内联修饰可以构成多态?

不同环境下结果可能不同

内联对编译器只是建议,当编译器识别为虚函数时,会忽略 inline

5.静态成员函数为什么不能构成多态?

没有 this 指针,不进虚表,构造函数也不能构成多态

6.普通函数与虚函数的访问速度?

没有实现多态时,两者一样快实现多态后,普通函数速度快,因为虚函数还需要去虚表中调用

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

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

相关文章

.NET内网实战:白名单文件反序列化执行命令

01阅读须知 此文所节选自小报童《.NET 内网实战攻防》专栏,主要内容有.NET在各个内网渗透阶段与Windows系统交互的方式和技巧,对内网和后渗透感兴趣的朋友们可以订阅该电子报刊,解锁更多的报刊内容。 02基本介绍 本文内容部分节选自小报童…

【易社保-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 1. 暴力破解密码,造成用户信息泄露 2. 短信盗刷的安全问题,影响业务及导致用户投诉 3. 带来经济损失,尤其是后付费客户,风险巨大,造…

Java泛型方法的定义和使用、泛型类、泛型接口、泛型方法、通配符、泛型的上界与下界

文章目录 一、包装类1.1、基本数据类型和对应的包装类1.2、自动装箱和自动拆箱 二、基本介绍2.1、泛型引入背景2.1、什么是泛型2.2、为什么使用泛型 三、常见泛型字母含义四、泛型的使用4.1、泛型类4.2、泛型接口4.3、泛型方法 五、泛型的继承5.1、泛型不具备继承性5.2、何为数…

【Python】递归

专栏文章索引:Python 有问题可私聊:QQ:3375119339 文章内容改自:bilibili博主(又懂啦) 目录 一、递归函数 二、理解递归函数 一、递归函数 一个函数在其函数体内调用函数自身,这样的函数就称为递归函数。递归函数的…

每日一练 2024.9.29(2)

目录 解题思路与代码实现 题目分析 一、解题策略 关键步骤: 二、代码实现 三、代码解析 四、复杂度分析 五、运行示例 示例1: 示例2: 六、总结 解题思路与代码实现 题目分析 这道题目要求我们找到字符串列表 strs 中的相似字符组…

Arch - 架构安全性_验证(Verification)

文章目录 OverView导图1. 引言:数据验证的重要性概述2. 数据验证的基本概念3. 数据验证的层次前端验证后端验证 4. 数据验证的标准做法5. 自定义校验注解6. 校验结果的处理7. 性能考虑与副作用8. 小结 OverView 即使只限定在“软件架构设计”这个语境下&#xff0c…

物理学基础精解【40】

文章目录 矢量积矢量积(又称叉积、外积)的几何意义一、面积表示二、垂直性三、方向性四、应用实例五、数学表达 矢量积(叉积)的坐标表示法矢量积的坐标表示法的几何意义矢量积的性质矢量积的应用 矢量积(又称叉积、外积…

Linux——k8s组件

kubernetes 使用1.31.1 版本搭建集群核心组件,选择flannel 网络插件为整体集群的运行提供网络通信功能。 flannel 网络插件 kube-flannel kube-flannel-ds-9fgml 1/1 Running 1 (18m ago) 2d21h kube-flannel kube-flannel-ds-ghwbq …

<<迷雾>> 第 3 章 怎样才能让机器做加法 示例电路

全加器示意图 info::操作说明 鼠标单击开关切换开合状态 primary::在线交互操作链接 https://cc.xiaogd.net/?startCircuitLinkhttps://book.xiaogd.net/cyjsjdmw-examples/assets/circuit/cyjsjdmw-ch03-01-full-adder.txt 原图 由3个全加器组成的3比特加法机 info::操作说明…

Linux——pod的调度

pod的调度 控制器: rc/rs 副本数量控制器 主要保证pod的数量符合管理员要求,并不会对pod进行额外的管理 以下三种控制器,本质上是服务控制器。具备以下特性: 副本数量的控制服务的滚动更新(更新pod)支持更新失…

基于springboot vue 投票系统设计与实现

博主介绍:专注于Java vue .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设,从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的…

基于51单片机的2路电压采集proteus仿真

地址:https://pan.baidu.com/s/1oNOJJv78ecfWZkdlMyhNVQ 提取码:1234 仿真图: 芯片/模块的特点: AT89C52/AT89C51简介: AT89C52/AT89C51是一款经典的8位单片机,是意法半导体(STMicroelectron…

Linux:LCD驱动开发

目录 1.不同接口的LCD硬件操作原理 应用工程师眼中看到的LCD 1.1像素的颜色怎么表示 ​编辑 1.2怎么把颜色发给LCD 驱动工程师眼中看到的LCD 统一的LCD硬件模型 8080接口 TFTRGB接口 什么是MIPI Framebuffer驱动程序框架 怎么编写Framebuffer驱动框架 硬件LCD时序分析…

OpenAI全新多模态内容审核模型上线:基于 GPT-4o,可检测文本和图像

在数字时代,内容安全问题愈发受到重视。9月26日,OpenAI 正式推出了一款全新的多模态内容审核模型,名为 “omni-moderation-latest”。 该模型基于最新的 GPT-4o 技术,能够准确地识别检测有害文本图像。这一更新将为开发者提供强大…

Java | Leetcode Java题解之第445题两数相加II

题目&#xff1a; 题解&#xff1a; class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {Deque<Integer> stack1 new ArrayDeque<Integer>();Deque<Integer> stack2 new ArrayDeque<Integer>();while (l1 ! null) {stack1.…

AI Agent应用出路到底在哪?

1 Agent/Function Call 的定义 Overview of a LLM-powered autonomous agent system&#xff1a; Agent学会调用外部应用程序接口&#xff0c;以获取模型权重中缺失的额外信息&#xff08;预训练后通常难以更改&#xff09;&#xff0c;包括当前信息、代码执行能力、专有信息源…

《深度学习》OpenCV 角点检测、特征提取SIFT 原理及案例解析

目录 一、角点检测 1、什么是角点检测 2、检测流程 1&#xff09;输入图像 2&#xff09;图像预处理 3&#xff09;特征提取 4&#xff09;角点检测 5&#xff09;角点定位和标记 6&#xff09;角点筛选或后处理&#xff08;可选&#xff09; 7&#xff09;输出结果 3、邻域…

深度学习反向传播-过程举例

深度学习中&#xff0c;一般的参数更新方式都是梯度下降法&#xff0c;在使用梯度下降法时&#xff0c;涉及到梯度反向传播的过程&#xff0c;那么在反向传播过程中梯度到底是怎么传递的&#xff1f;结合自己最近的一点理解&#xff0c;下面举个例子简单说明&#xff01; 一、…

Qt开发技巧(九)去掉切换按钮,直接传样式文件,字体设置,QImage超强,巧用Qt的全局对象,信号槽断连,低量数据就用sqlite

继续讲一些Qt开发中的技巧操作&#xff1a; 1.去掉切换按钮 QTabWidget选项卡有个自动生成按钮切换选项卡的机制&#xff0c;有时候不想看到这个烦人的切换按钮&#xff0c;可以设置usesScrollButtons为假&#xff0c;其实QTabWidget的usesScrollButtons属性最终是应用到QTabWi…

衡石分析平台系统管理手册-功能配置之AI 助手集成嵌入指南

AI 助手集成嵌入指南​ 本文档将引导您通过几个简单的步骤&#xff0c;将 AI 助手集成或嵌入到您的系统中。HENGSHI SENSE AI 助手提供了多种集成方式&#xff0c;您可以通过 iframe、JS SDK 或 API 调用等方式将 AI 助手嵌入集成到您的系统中。 1. 通过 iframe 集成​ ifra…