ROS仿真软件Turtlebot-Gazebo的安装使用以及错误处理[机器人避障]
很多时候由于机器人价格比较贵,而且会因为环境因素、操作失误或者摔坏等,所以我们可以先在仿真软件上做测试,也可以避免这些问题,虽然没有那么真实感,可毕竟是免费的嘛。我们可以在这些仿真的机器人身上去学习如何控制机器人,读取它们的传感器数据,解析这些传感器数据并做出决策,通过前面我们学到的话题、服务、动作来驱动机器人。
1、操作仿真机器人
1.1、安装仿真软件
这里主要介绍turtlebot-gazebo的安装以及在这个过程中遇到的一些问题,主要是版本问题。
安装命令如下
sudo apt-get install ros-indigo-turtlebot-gazebo
如果出现错误:
E: Unable to locate package ros-indigo-turtlebot-gazebo
这种安装出错的原因是Ubuntu的版本问题,比如本人的是Ubuntu 18.04版本,所以需要将indigo修改为melodic,这个在前面的文章也有介绍:Ubuntu18.04版本安装ROS及出现错误的处理方法
所以在安装之前,先查看自己的OS版本:uname -v
修改之后,执行命令
sudo apt-get install ros-melodic-turtlebot-gazebo
不出意外的情况,将依然报错
E: Unable to locate package ros-melodic-turtlebot-gazebo
这个时候我们可以双击tab键来让其补全或出现可用的列表,因为很大程度上可能是输入的名称有误或者升级之后的名称有变化等,这种小技巧出了确保正确之外,还可以提高你的输入效率。
输入:sudo apt-get install ros-melodic-turtlebot,将自动补全为turtlebot3,继续双击tab键,将出现的正确的提示:sudo apt-get install ros-melodic-turtlebot3-gazebo
从这里可以看到,turtlebot-gazebo版本已更新到了turtlebot3-gazebo,丢弃了以前的名称。
当然这里的情况不一定就适合大家,只能说出现错误,一般就是这个名称有误的问题,这个时候就使用双击Tab键来正确提示!!
1.2、一些错误处理
安装好了之后,我们来启动仿真软件
roslaunch turtlebot3_gazebo turtlebot3_world.launch
出现如下错误:
... logging to /home/yahboom/.ros/log/23567ca0-54f7-11ee-91f8-000c294b0b84/roslaunch-YAB-3881.log
Checking log directory for disk usage. This may take a while.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.RLException: Invalid <arg> tag: environment variable 'TURTLEBOT3_MODEL' is not set.
Arg xml is <arg default="$(env TURTLEBOT3_MODEL)" doc="model type [burger, waffle, waffle_pi]" name="model"/>
The traceback for the exception was written to the log file
从错误信息来看,这里有两个问题,一个是磁盘空间小了,日志文件需要超过1GB的容量;另一个问题是arg这个标签的问题,没有设置TURTLEBOT3_MODEL环境变量。
清理日志:
rosclean purge
rosclean check
设置环境变量:
export TURTLEBOT3_MODEL=burger
这里就是选定一个机器人,这里就选burger吧,还有一个waffle,大家可以试下
查看环境变量:
export [-p]
然后再次执行:roslaunch turtlebot3_gazebo turtlebot3_world.launch
当然这种环境变量的更改是临时的,推荐使用下面方法让其永久生效:
echo "export TURTLEBOT3_MODEL=burger" >> ~/.bashrc
source ~/.bashrc
再次启动,仿真软件的界面在初始化的时候,突然就出现下面这样的错误然后终止了:
log file: /home/yahboom/.ros/log/558bb39a-5509-11ee-86fb-000c294b0b84/spawn_urdf-4*.log
[Err] [REST.cc:205] Error in REST requestlibcurl: (51) SSL: no alternative certificate subject name matches target host name 'api.ignitionfuel.org'
VMware: vmw_ioctl_command error Invalid argument.
Aborted (core dumped)
[gazebo_gui-3] process has died [pid 2350, exit code 134, cmd /opt/ros/melodic/lib/gazebo_ros/gzclient __name:=gazebo_gui __log:=/home/yahboom/.ros/log/558bb39a-5509-11ee-86fb-000c294b0b84/gazebo_gui-3.log].
log file: /home/yahboom/.ros/log/558bb39a-5509-11ee-86fb-000c294b0b84/gazebo_gui-3*.log
错误的意思是没有证书与这个主机名匹配,实质原因是主机名变更了,所以我们修改配置文件,修改如下:
gedit ~/.ignition/fuel/config.yaml
将里面的
url: https://api.ignitionfuel.org
修改为
url: https://fuel.ignitionrobotics.org
还需要追加一个环境变量,解决VMware: vmw_ioctl_command error Invalid argument.这个错误问题
echo "export SVGA_VGPU10=0" >> ~/.bashrc
source ~/.bashrc
再次启动:roslaunch turtlebot3_gazebo turtlebot3_world.launch
当然上面是带障碍物的场景,也可以是空地图的场景:
roslaunch turtlebot3_gazebo turtlebot3_empty_world.launch
还有很多其余地图,同样双击tab键可以看到:roslaunch turtlebot3_gazebo
multi_map_merge.launch turtlebot3_house.launch
multi_turtlebot3.launch turtlebot3_simulation.launch
multi_turtlebot3_slam.launch turtlebot3_stage_1.launch
turtlebot3_autorace_2020.launch turtlebot3_stage_2.launch
turtlebot3_autorace.launch turtlebot3_stage_3.launch
turtlebot3_autorace_mission.launch turtlebot3_stage_4.launch
turtlebot3_empty_world.launch turtlebot3_world.launch
turtlebot3_gazebo_rviz.launch
1.3、 操作机器人
我们可以直接通过命令行发送话题来操作里面的机器人,那么这个话题与类型又是怎么知道的呢?我们可以通过前面的知识来理解
首先查看话题列表:rostopic list
/clock
/cmd_vel
/gazebo/link_states
/gazebo/model_states
/gazebo/parameter_descriptions
/gazebo/parameter_updates
/gazebo/set_link_state
/gazebo/set_model_state
/imu
/joint_states
/odom
/rosout
/rosout_agg
/scan
/tf
这里可以看到主要是关于gazebo的话题,以及一些惯性测量单元IMU,关节话题等,里面的数据类型很多都属于传感器定义的类型。
我们查看其中的/cmd_vel信息:rostopic info /cmd_vel
Type: geometry_msgs/Twist
Publishers: None
Subscribers:
* /gazebo (http://YAB:38191/)
发送消息的数据类型是geometry_msgs/Twist,然后我们可以继续查看这个类型的详细信息:rosmsg show geometry_msgs/Twist
geometry_msgs/Vector3 linear
float64 x
float64 y
float64 z
geometry_msgs/Vector3 angular
float64 x
float64 y
float64 z
可以看到是由两个向量组成,一个是线速度,另一个是角速度。
有了这些之后,我们就可以通过输入命令,发布消息直接操作机器人:
rostopic pub /cmd_vel geometry_msgs/Twist -- '[1.0, 0.0, 0.0]' '[0.0, 0.0, 0.0]'
2、创建话题cmd_vel
有了前面命令行操作机器人的知识铺垫,我们重新来定义一个话题,名称就是上面这个cmd_vel
2.1、创建工作空间
mkdir -p ~/mywanderbot_ws/src
cd mywanderbot_ws/src
catkin_init_workspace
2.2、创建依赖包
cd ~/mywanderbot_ws/src
catkin_create_pkg mywanderbot rospy geometry_msgs sensor_msgs
这样就创建好了一个mywanderbot包,以及让ROS构建系统需要知道的依赖包:rospy geometry_msgs sensor_msgs,这些依赖包是保证当依赖发生更改时,重新编译这个mywanderbot包到最新版本,以及在发布软件包时生成依赖。
我们可以查看下里面的情况:ls ~/mywanderbot_ws/src/mywanderbot
CMakeLists.txt package.xml src
关于这块代码,更多详情,有兴趣的可以查阅:ROS新建工作区(workspace)与包(package)编译的实践(C++示例)
2.3、自定义话题
我们试着来做一个让机器人每隔三秒进行行驶和暂停的周期性切换,这里给出两个示例
2.3.3、示例1
cd ~/mywanderbot_ws/src/mywanderbot/src
gedit red_green.py
#!/usr/bin/env python
import rospy
from geometry_msgs.msg import Twistmycmd_vel_pub = rospy.Publisher('cmd_vel',Twist,queue_size=1)
rospy.init_node('red_green')red_light_twist = Twist()
green_light_twist = Twist()
green_light_twist.linear.x = 0.5driving_forward = True
rate = rospy.Rate(10)
light_change_time=rospy.Time.now()while not rospy.is_shutdown():#print(light_change_time,rospy.Time.now())if light_change_time < rospy.Time.now():driving_forward = not driving_forwardlight_change_time= rospy.Time.now()+rospy.Duration(3)if driving_forward:mycmd_vel_pub.publish(green_light_twist)else:mycmd_vel_pub.publish(red_light_twist) rate.sleep()
这个网上例子比较多,很多都是书上的原内容是错误的,原例内容是:
if light_change_time > rospy.Time.now():
这样的话,永远不会执行到这个位置也就不会做切换了,需要将>修改为<,一开始小,所以就当前时间加3秒,比当前时间大,这个时间段就是等待当前时间一直累加,累加到小于当前时间,再次切换,加3秒,继续等待......
2.3.4、示例2
cd ~/mywanderbot_ws/src/mywanderbot/src
gedit red_green.py
#!/usr/bin/env python
import rospy
from geometry_msgs.msg import Twistmycmd_vel_pub = rospy.Publisher('cmd_vel',Twist,queue_size=1)
rospy.init_node('red_green')red_light_twist = Twist()
green_light_twist = Twist()
green_light_twist.linear.x = 0.5driving_forward = True
rate = rospy.Rate(1)while not rospy.is_shutdown():if int(rospy.get_time()) % 3 == 0:driving_forward = not driving_forward#print(rospy.Time.now().to_sec(),int(rospy.get_time())%3,driving_forward)if driving_forward:mycmd_vel_pub.publish(green_light_twist)else:mycmd_vel_pub.publish(red_light_twist) rate.sleep()
这种方式也不错,需要注意的是这里的频率不能是10了,需要每秒只发送一次,如果频率很快,试想下,在一秒钟里面进行取余会多次是一样的值,如果是0,那么会在这一秒钟内切换很多次,这肯定不可以。
2.4、编译与执行
添加可执行权限:chmod +x red_green.py
cd ~/mywanderbot_ws
catkin_make
echo "source ~/mywanderbot_ws/devel/setup.bash" >> ~/.bashrc
source ~/.bashrc
执行上述节点:rosrun mywanderbot red_green.py
可以看到机器人行驶了起来,也可以查看话题发布的输出信息:rostopic echo cmd_vel
linear:
x: 0.5
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.0
---
linear:
x: 0.5
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.0
2.5、rqt_graph
我们除了使用rostopic info cmd_vel查看这个话题的类型,发布者和订阅者之外,还可以用到前面章节介绍的rqt_graph来查看
发布的red_green节点通过cmd_vel话题,由gazebo机器人或其他订阅者订阅的关系图:
3、避障测试
我们打开的是带障碍物的地图,所以机器人遇到障碍物的时候会被迫停止,我们可以使用激光雷达LaserScan来测距,进行避障,这里用到实质是Turtlebot上的Kinect深度相机产生的数据。
3.1、激光雷达LaserScan
我们先来看下激光雷达的相关信息:
scan话题:rostopic info scan
Type: sensor_msgs/LaserScan
Publishers:
* /gazebo (http://YAB:36357/)Subscribers: None
可以看到类型是sensor_msgs/LaserScan
查看sensor_msgs/LaserScan消息类型:rosmsg show sensor_msgs/LaserScan
std_msgs/Header header
uint32 seq
time stamp
string frame_id
float32 angle_min
float32 angle_max
float32 angle_increment
float32 time_increment
float32 scan_time
float32 range_min
float32 range_max
float32[] ranges
float32[] intensities
查看scan的输出信息:rostopic echo scan -n 1
header:
seq: 1
stamp:
secs: 2537
nsecs: 462000000
frame_id: "base_scan"
angle_min: 0.0
angle_max: 6.28318977356
angle_increment: 0.0175019223243
time_increment: 0.0
scan_time: 0.0
range_min: 0.119999997318
range_max: 3.5
ranges: [0.377697229385376, 0.38358184695243835, 0.3849789798259735, 0.4099400043487549, ...]
intensities: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,...]
3.2、避障代码
前面的机器人实现了行驶和暂停的功能,接下来使用激光扫描来测距,并进行避开测试。
cd ~/mywanderbot_ws/src/mywanderbot/src
gedit mybot.py
#!/usr/bin/env python
import rospy
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScandef scan_cb(msg):global g_range_ahead#g_range_ahead = msg.ranges[len(msg.ranges)/2]g_range_ahead = msg.ranges[0]print(g_range_ahead)g_range_ahead = 1
cmd_vel_pub = rospy.Publisher('cmd_vel',Twist,queue_size=1)
scan_sub = rospy.Subscriber('scan',LaserScan,scan_cb)rospy.init_node('mybot')
state_change_time = rospy.Time.now() + rospy.Duration(30)
driving_forward = True
rate = rospy.Rate(10)while not rospy.is_shutdown():if driving_forward:if(g_range_ahead<0.8 or rospy.Time.now()>state_change_time):print(rospy.Time.now(),state_change_time)driving_forward = Falsestate_change_time = rospy.Time.now() + rospy.Duration(5)else:if(g_range_ahead>0.8 or rospy.Time.now()>state_change_time):driving_forward = Truestate_change_time = rospy.Time.now() + rospy.Duration(30)twist = Twist()if deriving_forward:if g_range_ahead>0.8:twist.linear.z=0.0twist.linear.x=0.5else:twist.linear.x=-0.2twist.angular.z=0.5else:if g_range_ahead>0.8:twist.linear.z=0.5twist.linear.x=0.0else:twist.linear.x=-0.2twist.angular.z=0.5 cmd_vel_pub.publish(twist)rate.sleep()
加个执行权限:chmod +x mybot.py
启动带障碍物的地图:roslaunch turtlebot3_gazebo turtlebot3_world.launch
执行:rosrun mywanderbot mybot.py
可以看到机器人在行驶中,遇到障碍物(小于0.8米)会进行避开,Nice~
这里使用一个全局变量g_range_ahead来存储激光扫描器检测到的最小距离,这使得回调函数变得简单,直接复制最小距离到我们的全局变量中,当然对于复杂的程序来说,这是一种不好的习惯,影响性能。
查看下激光扫描:rostopic info scan
Type: sensor_msgs/LaserScan
Publishers:
* /gazebo (http://YAB:44733/)Subscribers:
* /mybot (http://YAB:37459/)
可以看到订阅者就是我们定义的mybot机器人节点。
也可以使用rqt_graph来查看下节点之间的关系,如下图:
相关文章:

ROS仿真软件Turtlebot-Gazebo的安装使用以及错误处理[机器人避障]
很多时候由于机器人价格比较贵,而且会因为环境因素、操作失误或者摔坏等,所以我们可以先在仿真软件上做测试,也可以避免这些问题,虽然没有那么真实感,可毕竟是免费的嘛。我们可以在这些仿真的机器人身上去学习如何控制…...
把excel文件内容转化为json文件
js如何把excel文件转化为json文件呢?可以通过xlsx还有循环遍历来做 第一步:安装xlsx包 npm install xlsx这里我需要转化为这种类型 {key:value,key:value,key:value, }如果需要转化为其他格式,到时候需要在循环遍历的时候灵活运用 上代码…...
前端 - 将非 jpeg 格式图片转为 jpeg 格式、压缩图片
目录 将非 jpeg 格式图片转为 jpeg 格式压缩图片使用 将非 jpeg 格式图片转为 jpeg 格式 /common/compressImage/convertImage // 思路是创建一个图片,将file等于这个图片,然后创建一个canvas图层 ,将canvas等比例缩放, //然后用…...

随着 ChatGPT 凭借 GPT-4V(ision) 获得关注,多模态 AI 不断发展
原创 | 文 BFT机器人 在不断努力让人工智能更像人类的过程中,OpenAI的GPT模型不断突破界限GPT-4现在能够接受文本和图像的提示。 生成式人工智能中的多模态表示模型根据输入生成文本、图像或音频等各种输出的能力。这些模型经过特定数据的训练,学习底层模…...

Zookeeper-JavaApI操作
JavaApI操作 JavaApI操作1) Curator 介绍2) Curator API 常用操作a) 建立连接与CRUD基本操作b) Watch事件监听c) 分布式锁c.1) 介绍c.2) Zookeeper分布式锁原理c.3) 案例:模拟12306售票 JavaApI操作 1) Curator 介绍 Curator 是 Apache ZooKeeper 的Java客户端库。…...
Vue2.0打包指定路由前缀
【1】修改vue.config.js 如下修改publicPath: module.exports {publicPath:/concert,lintOnSave: false }【2】修改router/index.js base指定路由前缀: const router new VueRouter({mode: history,base: /concert, //指定路由前缀// base: process.env.BASE_…...

[vxe-table] 合并行后滚动错位
使用vxe-table的属性:span-method合并行,之后下拉后会错位 原因:缺少配置 scroll-y"{enabled: false}"参考: vxe-table合并行后错位...
动态规划:05不同路径
动态规划:05不同路径 62. 不同路径 五部曲 确定dp数组含义:到达第i,j位置的路径条数为d[i][j] 确定递归公式:d[i][j]d[i-1][j]d[i][j-1] 我们发现,想要到d[i][j],只能从d[i-1][j]或者d[i][j-1]达到 dp数…...

html与css知识点
html 元素分类 块级元素 1.独占一行,宽度为父元素宽度的100% 2.可以设置宽高 常见块级元素 h1~h6 div ul ol li dl dt dd table form header footer section nav article aside 行内元素 1.一行显示多个 2.不能设置宽高,宽高由元素内容撑开 常见行内…...
spring boot simple类型cache使用
注意:这里用的不是 redis 的缓存,simple 的缓存默认用的是 java 的 ConcurrentHashMap, 单纯的 simple 缓存,只需要引入下面的 pom 依赖即可:spring-boot-starter-cache <dependency><groupId>org.springf…...
springboot-aop-redis-lua 实现的分布式限流方案
1.自定义限流注解 Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Inherited Documented public interface Limit {/*** 名字*/String name() default "";/*** key*/String key() default "";/*** Key的前缀*/String…...
C++ realloc()用法及代码示例
C realloc()用法及代码示例 C 中的realloc() 函数重新分配先前分配但尚未释放的内存块。realloc() 函数重新分配先前使用 malloc() 、 calloc() 或 realloc() 函数分配但尚未使用 free() 函数释放的内存。如果新大小为零,则返回的值取决于库的实现。它可能会也可能…...
【Go】gin框架生成压缩包与下载文件
在没有加入下面这串代码之前,下载的压缩包一直为空。遂debug了两个小时。。。 可以在服务端本地创建压缩包。单独将服务端本地的压缩包发送给客户端也是没问题的。但是两个合起来,客户端接收到的压缩包内容就为空了。 期间也尝试了 zipFile.Close() zipW…...

iOS 面试题以及自我理解答案
1、简述push原理,push的证书和其他的有什么不一样? 第 一阶段:BeejiveIM服务器把要发送的消息、目的iPhone的标识打包,发给APNS。 第二阶段:APNS在自身的已注册Push服务 的iPhone列表中,查找有相应标识的iP…...

vue实现自定义滚动条
vue实现自定义滚动条 具体效果如下,这边我用的rem单位,比例是1:40, 先写下页面布局,把原生的滚动条给隐藏掉,给自定义的滑块增加transition: marginLeft 1s linear;可以使左边距过度的更顺滑 .top-box-2::-webkit-scr…...

基于Qt C++的工具箱项目源码,含命令行工具、桌面宠物、文献翻译、文件处理工具、医学图像浏览器、插件市场、设置扩展等工具
一、介绍 1. 基本信息 完整代码下载地址:基于Qt C的工具箱项目源码 TBox是一款基于Qt C的工具箱。用户可以自行选择安装所需的工具(以插件的形式),将TBox打造成专属于自己的效率软件。TBox基本界面展示如下: 2. 使用…...

C# AnimeGANv2 人像动漫化
效果 项目 下载 可执行程序exe下载 源码下载 其他 C# 人像卡通化 Onnx photo2cartoon-CSDN博客...
gateway接口参数加解密
上篇介绍了多种加解密的使用java加密使用 本篇主要介绍在gateway网关中使用对参数解密和返回数据进行加密的操作 原理 下面使用的是AES加密 SHA1withRSA加签 1-用户使用拿到的AES秘钥和RSA私钥。对数据进行加密和加签 2-进行验签和时间的检验 3-将解密的数据返回到具体的调用…...

WorkPlus定制化的局域网会议软件,提供安全稳定的会议体验
在现代商业环境中,迅速而高效的沟通是企业成功的关键要素之一。而在传统的会议模式下,时间成本和地理限制往往给企业带来不小的困扰。针对这一问题,WorkPlus推出了一款创新的局域网会议软件——WorkPlus Meet,旨在为企业创造高效的…...

干货|小白也能自制电子相册赶紧码住~
你是否想拥有一个独一无二的电子相册,却又苦于不知道如何下手?今天教你一个简单的方法,即使你是小白,也能轻松自制电子相册! 一、选择合适的工具 首先,你需要选择一个合适的工具来制作电子相册。有很多工具…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...