强化学习AI构建实战 - 基于“黄金点”游戏(二)

服务端接口

为了让大家的AI可以顺利地进行游戏,并验证我们对策略和AI的一些实现,我们需要一些基础设施来帮助我们完成一些工作。这些工作包括游戏回合的控制、参与者之间的数据同步、游戏数据的储存等功能。

为了简化这些基础工作,以便大家可以更好地集中于AI本身的设计和实现,我们搭建了一个服务器提供了一些基本的接口。使用这些接口,AI可以做到简单的调用REST API接口实现游戏回合时间同步、获取历史数据、提交预测数据等功能。

下图描述了服务器如何驱动游戏一回合接着一回合的运转,同时指出了AI或客户端应何时与服务器交互。

当AI或客户端进入游戏后,应立即向服务器请求获取当前回合的状态,此时可以知道服务器上正在进行的游戏回合的编号,以及本回合还有多长时间结束。AI或客户端可以按照返回的回合编号向服务器提交预测值,并且可以根据本回合剩余时间,设定一个定时器,在下一回合开始时,再次执行获取回合状态的接口,来取得下一回合的状态。这样依次轮转下去,AI或客户端就可以一直参与在游戏中。 同时,AI或客户端还可以在每回合开始时,调用获取历史数据的接口,来得到前几回合的比赛数据。这样可以知道自己在上一回合是否得分胜出,并可以根据历史数据来指导当前回合的预测值。

接口概述

服务器地址是https://goldennumber.aiedu.msra.cn,提供RESTful API接口。所有请求需要的参数都拼装在URL中,并且需要对值进行URL编码。所有的响应报文内容都是JSON格式。如果服务器响应代码不是2**或3**,表示该次请求失败。失败的响应报文至少包含一个message属性:

属性名数据类型备注
messageString出错的具体信息

服务端REST接口提供了Swagger描述文档: swagger.json 中文版 英文版

可以参考该API文档直接来调用服务器接口,也可以借助第三方工具从swagger文档生成所需语言的SDK来使用。比如,可以借助SwaggerEditor来生成各种语言版本的客户端SDK,可以极大的方便开发。

另外,服务端也提供了API试用页面,可以方便直接的在线试验API接口。

下面是各个接口的详细描述:

新建玩家

请求方式:GET

路径:/api/NewUser

客户端使用该接口可以新建一个玩家。

请求需要用到的参数:

参数名数据类型是否必需备注
nickNameString可选用户昵称
如果长度超过20,将被截断
建议设置昵称,昵称相对于标识有更好的辨识度

响应报文内容中的属性:

属性名数据类型备注
userIdString用户标识,格式为Guid格式
nickNameString用户昵称

设置用户昵称

请求方式:POST

路径:/api/NickName

使用该接口可以用来修改用户的昵称,昵称相对于标识来说,有更佳的辩识度。

请求需要用到的参数:

参数名数据类型是否必需备注
uidString必需用户标识
nicknameString必需用户昵称,长度大于20会被截断

获取新游戏房间

请求方式:GET

路径:/api/NewRoom

使用该接口创建一个新的游戏房间并获取对应的编号。

请求需要用到的参数:

参数名数据类型是否必需备注
uidstring必需房间创建者的标识
numbersInt可选设置游戏支持的每个玩家可以提交的预测值的个数,目前支持提交1个或2个数
默认是1,表示每个玩家可以提交一个数
durationInt可选设置游戏中每回合的间隔时间
默认值是60秒,取值范围在10~200之间
userCountint可选设置游戏房间中允许的最大玩家数
默认值是0,表示没有限制
有玩家数量限制的房间,当所有玩家都提交预测值后,会立即计算本回合结果,并开始下一轮
注意:这里的玩家数量限制是针对房间的,不是针对一个回合,只要玩家在房间内任一回合提交过预测值,则认为该玩家始终在房间内
roundCountint可选设置比赛总回合数
默认值是0,表示没有限制
如果某一回合没有玩家提交数据,认为该回合无效,不计在回合数内
如果有效回合数达到设置的总回合数,游戏结束,不再允许提交数据
manuallyStartInt可选是否手动开始游戏
默认值0,表示创建完房间后,游戏自动开始
如果是1,表示需要由创建者手动开始游戏

响应报文内容中的属性:

属性名数据类型备注
roomIdInt游戏房间编号

开始游戏

请求方式:GET

路径:/api/StartGame

如果创建游戏时设置的是手动开始,那么游戏创建者可以调用该接口开始游戏。

请求需要用到的参数:

参数名数据类型是否必需备注
uidstring必需房间创建者的标识
roomidint可选房间编号
如果未设置,默认为0号游戏房间

获取游戏状态

请求方式:GET

路径:/api/State

客户端使用该接口可以获取当前房间内的游戏状态,可以根据当前游戏支持提交的预测值的个数进行提交。同时还可以知道当前回合什么时间结束,推算出什么时候可以取得本回合的比赛数据以及获取下一轮比赛的相关信息。

请求需要用到的参数:

参数名数据类型是否必需备注
uidString可选用户标识
roomidInt可选房间编号
如果此参数为空,默认0号房间

响应报文内容中的属性:

属性名数据类型备注
userIdString用户标识
nickNameString用户昵称
roomIdInt房间编号
numbersInt当前房间内的游戏支持提交的预测值的个数,1或2
roundIdstring当前房间内正在进行的游戏回合标识
leftTimeint当前游戏回合还有多少秒截止提交
roundEndTimedatetime当前回合截止提交的UTC时间
stateint当前游戏状态
0代表进行中
1代表未开始,需要房间创建者手动开始
2代表已结束,不允许再向房间内提交数据
hasSubmittedbool当前用户本回合是否已提交预测值
isRoomCreatorbool当前用户是否是当前房间的创建者。
如果房间在创建时没有指定自动开始,需要创建者手动开始游戏
maxUserCountint创建房间时设定的玩家数
0表示没有限制
最大不能超过200
设置人数上限的房间中,在获取格式化的历史数据时,会将未加入游戏的玩家的预测值用0来填补,保证每回合取到的数据都是固定列数的规整数据
同时,设置人数上限的房间中,如果所有玩家都已提交,则立该结束当前回合,并开始下一回合
currentUserCountint当前房间内提交过预测值的玩家数量
totalRoundCountint创建房间时设定的该房间可以进行的有效回合数
finishedRoundCountint当前房间内已经进行的有效回合数
玩家提交过预测值的回合认为是有效回合,否则忽略该回合,继续等待玩家提交
enabledTokenbool当前房间是否已启用身份验证

提交预测值

请求方式:POST

路径:/api/Submit

客户端使用该接口可以向服务器提交预测值。每回合只允许提交一次,提交成功后不可修改。

如果当前房间设置了玩家人数上限,则当所有玩家提交了预测值后,立即计算本回合结果,并开始下一回合。

请求需要用到的参数:

参数名数据类型是否必需备注
ridString必需要提交预测值的回合标识,需要是GUID的格式
uidstring必需提交预测值的用户标识
n1Double必需预测值,必须是0到100之间的有理数,不包括0和100
n2Double可选第二个预测值,如果当前游戏是支持两个数的游戏,此参数也为必需项;如果当前游戏仅支持一个数,此参数将被忽略
tokenstring可选启用身份验证的房间必须带有正确的验证信息才可以提交
由房间创建者提供原始令牌,将用户标识、回合标识、原始令牌连接为新字符串,先做一次SHA256,然后做一次Base64,得到的结果做为token的值

获取黄金点历史数据

请求方式:GET

路径:/api/TodayGoldenList

使用该接口可以获取当前房间内当天的黄金点历史数据,玩家可以基于此来预测下一轮的黄金点值。

请求需要用到的参数:

参数名数据类型是否必需备注
roomidString可选房间编号
如果该参数为空,默认0号房间
roundCountInt可选查询的回合数量
如果此参数为空,默认最近100回合的数据
如果需要查询所有数据,需设置为-1
注意:当回合数特别大时,返回的数据包会特别大

响应报文内容中的属性:

属性名数据类型备注
goldenNumberListArray房间中当天的黄金点历史数据,数组的最后一个值是最新一轮的黄金点值

获取玩家提交的历史数据

请求方式:GET

路径:/api/TodayNumbers

使用该接口可以获取当前房间内,当天已完成的回合中,所有玩家提交的历史数据。

玩家历史数据以数组形式返回,数组中每个元素都有用户索引号和回合索引号,可以按不同维度分别统计某个玩家的提交规律或某个回合详细数据,可以按照自己的需要,对该数据进行建模或训练对应的模型。

请求需要用到的参数:

参数名数据类型是否必需备注
roomidint可选房间编号
如果未设置,默认为0号游戏房间
roundCountInt可选查询的回合数量
如果此参数为空,默认最近100回合的数据
如果需要查询所有数据,需设置为-1
注意:当回合数特别大时,返回的数据包会特别大

响应报文内容中的属性:

属性名数据类型备注
validNumbersint当前房间支持的可提交数字个数,1或者2。
当为1时,下面的数据只有number1是有效的;
当为2时,下面的数据中number1和number2均为有效数字。
numberListarray用户提交的数字列表,数组中的每个元素包含以下属性:
userIndex, roundIndex, number1, number2
    userIndexint用户索引号,相同的用户索引号表示同一个用户在不同回合提交的数字
    roundIndexint回合索引号,相同的回合索引号表示不同用户在同一回合提交的数字
    number1double用户提交的第一个数字
    number2double用户提交的第二个数字,仅当validNumbers为2时有效

获取玩家得分

请求方式:GET

路径:/api/TodayScore

使用该接口可以查询游戏房间内所有玩家的得分情况。用户得分按从高到低排列。

请求需要用到的参数:

参数名数据类型是否必需备注
roomidint可选房间编号
如果未设置,默认为0号游戏房间

响应报文内容中的属性:

属性名数据类型备注
scoreListarray数组中的每个元素包含以下属性:
userId, nickName, score, index
    userIdstring用户标识
    nickNamestring用户昵称
    scoreint得分
    indexint该用户在当前房间内的索引号

获取分页历史数据

请求方式:GET

路径:/api/History

使用该接口可以获取当前房间内的历史数据,包括每回合的黄金点、每个玩家的预测值、得分等信息。

没有指定任何参数时,返回0号房间内最新的10回合的历史。

请求需要用到的参数:

参数名数据类型是否必需备注
roomidString可选房间编号
如果该参数为空,默认0号房间
startridString可选开始查询的游戏回合标识
如果该参数为空,默认为当前正在进行的回合
countInt可选指定从startrid开始返回多少回合的历史,不包括startrid回合
如果没有指定该参数,默认为10,最大不超过100
directionInt可选查询的方向
默认值是0,表示从startrid查询旧的历史数据
另一个值是1,表示从startrid查询更新数据

响应报文内容中的属性:

属性名数据类型备注
roundsArray查询到的回合的数组,数组的每个元素包含以下属性:
roundId, time, goldenNumber, userNumbers
    roundIdString回合标识
    indexint该回合在当前房间中的索引编号
    timeString该回合的截止时间,UTC
    goldenNumberDouble该回合的黄金点
    userNumbersArray该回合所有玩家提交的数的数组,数组的每个元素所含以下属性:
userId, masterNumber, slaveNumber, score
        userIdString用户标识
        masterNumberdouble用户提交的第一个预测值
        slaveNumberdouble用户提交的第二个预测值,仅当当前游戏支持提交两个数的时候有效
        scoreInt用户在当前回合的得分
nickNamesobject用户编号和用户昵称的字典
用户编号是key,用户昵称是value

主体流程分析和实现

从上述的服务器接口描述和定义,我们可以看出,一个最基本的黄金点游戏程序应该具有哪些功能。

要顺利地进行游戏,最核心的两个功能,就是通过服务器提供的RESTful API进行获取当前回合状态GET /api/State)和提交数字POST /api/Submit)。这两个核心操作中,由于提交数字时必须知道当前的游戏设置(如需要提交一个数还是两个数),以及当前的回合ID,所以在提交之前,正确地获取当前回合的状态是必要的。否则提交会失败。有了这两个功能,我们前面列举的一些最简单的游戏策略就可以被实现了,比如提交随机数,提交固定的数等。

当然,上面提到的最简单的两个策略可能不会表现得很好。为此,我们还需要调用获取黄金点历史数据接口(GET /api/TodayGoldenList),这一接口为我们实现诸如重复上一轮的黄金点、计算以前数轮黄金点的均值提供了可能;另外,还可以调用获取玩家提交的历史数据接口(GET /api/TodayNumbers)来得到每个玩家的数据,从而可以推测他人策略、学习历史数据进行建模等。

对于上述三个主要的接口,上文已经对作为其输入的HTTP请求,和服务器输出的数据结构的格式做了一番说明,并提供了Swagger描述,可以方便的生成任何语言的客户端SDK。这里我们对主体流程和相关的要点做一下整理:

  1. 创建新用户或使用已有的userId,获取当前回合状态,初始化客户端环境。包括玩家ID、回合倒计时等;
  2. 尝试在回合倒计时结束前,向服务器提交数据。
    • 提交时必须声明提交针对的回合,已经过去的、不合法的回合会导致提交失败。
  3. 倒计时结束后,获取新一回合——也即当前回合——的状态。
    • 由于时间同步可能不稳定,只有观察到回合ID较之前改变了,新回合才算开始。
  4. 回到第2步。

伪代码如下:

// 任一房间号。省略的话,默认是0号房间。
roomId=42

// 初始化。
user = NewUser(nickName="foo")
userId = user.userId // 整个循环中,只使用一个用户ID,以正确统计得分。

// 游戏主循环。
while true:   

    // 获取当前游戏的状态,包括当前回合的标识号,回合结束的剩余时间等
    state = GetState(uid=userId, roomId=roomId) 

    // 获取当前房间的历史记录。默认是从当前回合开始追溯。
    history = GetTodayGoldenList(roomId) 

    // 尝试在当前回合结束前,向服务器提交数据。
    while DateTime.now < state.roundEndTime:
        if HaveNotSubmittedForRound(state.roundId):

            // 核心:策略实现。
            num = Calculate(history) 

            Submit(num, uid=userId, rid=roundId) // 提交数据必须指定提交者ID和目标回合ID。

    while true:
        nextState = GetState(uid=userId, roomId=roomId) // 使用当前用户ID,避免分配新ID。

        // 直到确认新回合开始了,才更新客户端状态。
        if nextState.roundId != state.roundId: 
            state = nextState
            break

我们也提供了C#和Python的几个Bot示例代码,大家可以尝试运行一下。

游戏策略

前面介绍了服务器的接口及BOT程序的主要框架,下面将讨论下可以使用哪些策略来玩这个游戏。

基于规则的策略

当我们思索游戏的制胜策略时,我们想到或许一些简单的策略就能取得出乎意料的效果。比如人在思索游戏对策的时候,经常想到的一种对策就是基于规则的,其表现为一系列“当对方……我就……”形式的条件和计划。当游戏的对手确实执行了我们预测中的操作时,我们从预先定义的计划里找到对应的手段并执行。这种基于规则的策略往往要求我们先尽可能地预测对手的行为,并做出针对性的压制计划。

典型的基于规则的策略有:重复上一回合的黄金点、上一回合黄金点乘以0.618、取最近十轮中黄金点的均值、使用固定值如42、生成(0,100)区间内的随机数等等。或者稍微复杂一些的规则,比如前面我们提到2018年微软学生夏令营中同学们的策略:当黄金点的取值连续多轮变化不大时,用一个比较大的值拉高黄金点的值,另一个值可以适应比前一轮黄金点高一些。

Azure Notebooks中的BotDemo.ipynb就是一个简单的基于规则的Bot,可以在线运行试一下。

基于识别的策略

基于规则的策略能达到的成就或许有限,因为我们处于一个复杂且多变的游戏环境,我们可能很难有效地制定一些应对性的规则。除了简单规则之外,我们还可以尝试的一种手段是基于识别的策略,即我们可以先尝试识别对手使用的是怎样的策略,然后再预测其在下一轮比赛中可能采取的行动,然后再做针对性的应对。比如,如果我们识别了对手采取的是“重复上一轮黄金点”这一基于规则的策略,我们就能有效地进行应对(如提交任意比上轮黄金点小的数)。我们可以看出,这种策略比起基于规则的策略有着更为超前的视野。然而,考虑到游戏本身的复杂性,我们能有效识别的敌方策略,可能会很有限。

基于强化学习的策略

除了上述的策略,我们还可以采用基于强化学习的策略。观察过几组游戏的历史记录后,我们注意到这些历史记录中似乎是存在一定规律的,但是其潜在的规律难以用简单的规则或手工组织的程序来捕捉。对此,我们可以尝试通过强化学习的方式来完成这一任务。

以前面介绍的Q-Learning为例,首先我们要确定可能的状态和动作,但很明显,这里的状态空间和动作空间都是无限大的,我们尝试强行作个简化然后再使用Q-Learning。

对于状态空间来说,我们可以考虑将黄金点数据用特定的编码方式编码,如将黄金点的趋势转换为“上升/下降X%”的形式,其中X可以是几个固定的值如10、20、50等。更简单的还可以将最近几回合的黄金点趋势总结为上升/下降了几次。

对于动作空间来说,一种简单的处理是将前面基于规则和策略拿过来,多定义几种规则作为多个动作。

最后以这个有限的状态和动作来构造Q表进行强化学习的训练。

Azure Notebooks中的RLBotDemo.ipynb就是一个简单的使用Q-Learning的Bot,可以在线运行试一下。

必须要提到的一点是,上面这种简化并不是特别合理,有时训练出来的模型可能比不过单纯基于简单规则的,所以需要大家尝试定义不同的状态空间和动作空间,看是否能玩的更好。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

旧路由重置新路由设置新路由设置教程|适用于PPPoE拨号

前言 前几天朋友说路由器想要重置&#xff0c;但不知道怎么弄。所以就想着只帮忙重置路由器的话&#xff0c;只能帮到一个人。但把整个过程写成图文&#xff0c;就可以帮助更多人。 本文章适合电脑小白&#xff0c;请注意每一步哦&#xff01; 注意事项 开始之前需要确认光猫…

TAX个税小知识

人&#xff1a;个人&#xff0c;非居民个人 种类&#xff1a;工资薪金&#xff0c;劳务报酬所得&#xff0c;特许使用费&#xff0c;稿酬所得 工资按月支付扣税&#xff0c;不用个人提供发票&#xff0c;有签劳动合同&#xff1b; 其他三项按月或按次扣税&#xff0c;需要开发…

TTL篇-TtlAgent的使用

接上篇&#xff0c;TTL篇-TTL的使用-CSDN博客 TtlAgent是什么 ttl使用的典型代码是如下所示&#xff0c; DataAllArgsConstructorpublic static class Bean {private String name;}Testpublic void testBean() throws InterruptedException {TransmittableThreadLocal<Be…

10、Kafka ------ 消费者组 和 消费者实例,分区 和 消费者实例 之间的分配策略

目录 消费者组 和 消费者实例★ 消费者组★ 分区 和 消费者实例★ 分配分区&#xff08;分区 和 消费者实例 之间的分配策略&#xff09;3种分配策略★ range策略★ round-robin策略 &#xff08;轮询&#xff09;★ stick策略 如何指定分配分区的策略&#xff1a; 消费者组 和…

Git 基本使用

Git Git是一种分布式版本控制系统&#xff0c;它可以帮助开发团队更好地管理代码并进行协同开发。 gitee,github,gitlab&#xff0c;是基于git的代码托管平台。 官方文档地址(中文)&#xff1a;Git - Git 是什么&#xff1f; 安装和配置 官网下载地址&#xff1a;Git - Downl…

面试官心声:个个都说会自动化,结果面试一问细节全露馅了

今年我们部门计划招聘几名自动化测试工程师&#xff0c;为此我进行了面试和培训&#xff0c;发现了一个让我感到担忧的趋势&#xff0c;许多候选人可以轻松地回答有关脚本编写、元素定位、框架API等问题。然而一问到实际项目&#xff0c;比如 “如何从0开始搭建自动化体系”、“…

k8s---pod控制器

pod控制器发的概念&#xff1a; 工作负载&#xff0c;workload用于管理pod的中间层&#xff0c;确保pod资源符合预期的状态。 预期状态&#xff1a; 1、副本数 2、容器重启策略 3、镜像拉取策略 pod出故障的出去等等 pod控制器的类型&#xff1a; 1、replicaset&#xf…

from表单通过遍历的方式显示

当我们绘制表单数据的时候&#xff0c;有时候知道需要绘制的内容&#xff0c;但是不想在页面一条一条写数据&#xff0c;如果通过遍历显示呢 下面是在uniapp中写h5的方法&#xff0c;直接上代码 <view><view classitem v-for(item,index) in arr :keyindex><v…

【学习资源】分享三个文献互助平台(科研通 / 谷粉 / 纬度)

最近博主准备阅读一些专业相关的文献&#xff0c;有一些可以从博主所在单位购买的数据中直接下载&#xff0c;然而有一些论文数据库是没有购买的&#xff0c;所以要博主自己想办法去下载。 一般来说&#xff0c;两年以前的SCI论文&#xff0c;基本上都可以借助Scihub下载的到&…

pc端微信QQ使用代理解决方案

背景 多数金融类公司都会限制QQ和微信访问&#xff0c;但某些情况导致不得不使用&#xff0c;时不时使用手机会让你的老板感觉你在摸鱼&#xff0c;所以站在技术的角度上研究一下如果在pc端使用 代理开启 以微信举例&#xff0c;如果你用的clash&#xff0c;这里的地址可以设…

2024 解决matplotlib中文字体问题

第一种代码&#xff08;失败代码&#xff09; import matplotlib as mpl import matplotlib.pyplot as plt from matplotlib.font_manager import FontPropertiesfont_path /Users/huangbaixi/Desktop/SimHei.ttfdef plot_demo():#print(mpl.get_cachedir())# 绘制折线图font…

人脸考勤技术,这个隐藏功能太好用了!

随着科技的不断发展&#xff0c;人脸识别技术在各个领域得到了广泛的应用。其中&#xff0c;三维人脸考勤系统作为一种高级的人脸识别技术&#xff0c;不仅提高了考勤系统的准确性&#xff0c;还增强了安全性。 客户案例 制造企业 山东某大型制造企业引入了泛地缘科技推出的三…

【excel密码】Excel中如何使部分单元格区域实现加密

Excel文件可以设置保护工作表&#xff0c;那么可以只保护工作表中的部分单元格&#xff0c;其他地方可以正常编辑吗&#xff1f;当然是可以的&#xff0c;今天我们学习&#xff0c;如何设置保护部分单元格。 首先&#xff0c;我们先将整张工作表选中&#xff08;Ctrl A&#…

【KMP】【二分查找】【C++算法】100207. 找出数组中的美丽下标 II

作者推荐 【矩阵快速幂】封装类及测试用例及样例 本文涉及的基础知识点 二分查找算法合集 LeetCode100207. 找出数组中的美丽下标 II 给你一个下标从 0 开始的字符串 s 、字符串 a 、字符串 b 和一个整数 k 。 如果下标 i 满足以下条件&#xff0c;则认为它是一个 美丽下标…

主流浏览器设置代理IP之QQ浏览器

给浏览器设置代理IP是目前代理IP的主流使用场景之一&#xff0c;接下来小编就手把手教你如何对QQ浏览器进行代理IP设置 注&#xff1a;本次使用IP来源于携趣代理平台QQ浏览器内设置IP代理 1、首先需要进入浏览器【设置】 2.点击【工具】选择【lnternet选项】然后进行点击。 3.…

零基础精酿啤酒,这款智能啤酒酿造机掀起啤酒消费的DIY热潮

破壁机、台式单烤、空气炸锅……回顾2023年&#xff0c;小家电行业在高端、智能等趋势下迎来了新一轮的消费迭代热潮&#xff0c;越来越多契合消费者细分需求的“新兴家电”正在成为市场新宠。比如&#xff0c;此前很少有消费者会关注的啤酒酿造机。 啤酒如何酿造&#xff1f;…

Unity ComputeShader 使用GPU快速计算复杂问题

Unity ComputeShader 使用GPU快速计算复杂问题 前言项目创建ComputeShader编写CompturShader创建Unity代码场景布置运行场景 参考 前言 遇到一个问题&#xff0c;需要大量的计算&#xff0c;在Unity中直接写会长时间的阻塞主线程&#xff0c;正好使用ComputeShader让GPU来帮我…

Windows 下 QT开发环境的搭建:

下载QT:Index of /archive/qt/5.14 下载Cmake :CMake - Upgrade Your Software Build System (1)QT在windows,C, 打包exe&#xff1a; step1:window上安装QT软件&#xff1a; Windows下的QT系统开发环境搭建_qt windows-CSDN博客. step2:新建一个界面工程&#xff1a; (1)打…

C++标准学习--智能指针

shared_ptr和weak_ptr的配合使用是个问题。unique_ptr的使用场合似乎比较局限。 文章C 智能指针详解&#xff08;一&#xff09;——unique_ptr - 知乎 (zhihu.com) 介绍了unique_ptr的使用。它可以由shared_ptr转来&#xff0c;主要用到了std::move。 主要场景其中提到&#…