解释一下这一段
[this](const Msg::SharedPtr msg)
{
current_angle = msg->angle;
}
你暂时不要把它当成“一整句代码”,而要看成四块:
[this] (const Msg::SharedPtr msg)
↓ ↓
我是谁? 消息是什么?
{
current_angle = msg->angle;
↓ ↓
存到哪里? 从消息里取什么?
}
一、先重新理解 this
假设我们有两个机器人对象:
Robot robot1;
Robot robot2;
每一个对象都有自己的:
current_angle
可以想象成:
robot1
┌──────────────────┐
│ current_angle=10 │
└──────────────────┘
robot2
┌──────────────────┐
│ current_angle=50 │
└──────────────────┘
现在执行:
robot1.update();
进入 robot1 的成员函数时,C++ 需要知道:
“现在到底是在操作 robot1,还是 robot2?”
于是 C++ 内部有一个特殊的指针:
this
你可以把它理解成:
“当前这个对象的地址。”
所以当:
robot1.update();
正在执行时:
this
│
▼
robot1
而如果:
robot2.update();
那么:
this
│
▼
robot2
所以一句最形象的记法:
this= “我自己”
类里面的代码在说:
this
=
现在正在执行这个函数的“我”
二、为什么 [this]?
现在 lambda 是:
[this]()
{
}
lambda 就像我们临时雇了一个“小助手”。
问题来了:
这个小助手怎么知道它是在为哪个 Robot 对象工作?
所以我们把:
this
交给它。
[this]
可以形象理解成:
“小助手,你把‘当前对象是谁’这个地址带进去。”
于是 lambda 里面就知道:
我现在服务的是 robot1
或者:
我现在服务的是 robot2
所以现阶段你看到:
[this]
就直接翻译成:
这个 lambda 允许操作当前对象自己的成员。
比如:
[this]()
{
current_angle = 30;
}
如果当前对象是 robot1,实际上就是:
robot1.current_angle = 30
三、再重新理解 ->
这部分其实和你之前学的指针完全一样。
假设有一个普通对象:
Robot robot;
直接访问:
robot.angle;
因为:
你手里直接拿着对象。
所以使用:
.
但是如果你手里不是对象,而是:
对象的地址
例如:
Robot* p = &robot;
可以画成:
p
┌──────────────┐
│ robot的地址 │
└───────┬──────┘
│
▼
robot
┌────────────────┐
│ angle = 30 │
│ speed = 10 │
└────────────────┘
现在想访问 angle:
p->angle;
-> 可以非常形象地理解成:
顺着这个地址箭头找到对象,然后拿它里面的东西。
所以:
p->angle
读成人话:
顺着
p指向的地址找到 Robot,然后取 Robot 里的angle。
因此这个箭头符号其实画得非常形象:
p ---------> Robot对象
│
└── angle
四、. 和 -> 最形象区别
假设 Robot 是一间房。
情况 1:你就在房子里
robot.angle
相当于:
“robot 房间里的 angle。”
所以直接:
.
情况 2:你只有房子的地址
Robot* p = &robot;
你必须:
地址
↓
找到房子
↓
进去找 angle
所以:
p->angle
也就是:
沿着地址指过去,再访问成员。
记住:
对象本体 .
对象的地址/指针 ->
五、那 msg->angle 是什么?
现在回到:
msg->angle
因为:
msg
不是普通 Msg 对象,而是:
Msg::SharedPtr
它本质上是一个:
管理消息对象的智能指针。
你可以先画成:
msg
┌───────────────────┐
│ 消息对象的管理/地址 │
└─────────┬─────────┘
│
▼
Msg消息对象
┌────────────────────┐
│ angle = 45.0 │
│ speed = 10.0 │
└────────────────────┘
所以:
msg->angle
意思是:
顺着
msg找到收到的消息对象,然后取里面的angle。
如果消息是:
angle = 45
那么:
msg->angle
得到:
45
六、现在看完整一行
current_angle = msg->angle;
假设收到:
Msg
┌──────────────┐
│ angle = 45 │
└──────────────┘
那么:
msg->angle
先得到:
45
然后:
current_angle = 45;
把这个值保存到当前对象自己的成员变量里。
七、为什么这里需要 [this]?
因为:
current_angle
通常是类里面的成员变量。
例如:
class RobotController
{
private:
double current_angle = 0.0;
};
lambda 想修改:
current_angle
本质是在修改:
this->current_angle
所以需要知道:
“当前是哪一个 RobotController 对象?”
这就是:
[this]
的作用。
八、整个过程画出来
假设:
RobotController controller;
ROS2 收到一条消息:
Msg消息
angle = 45
lambda:
[this](const Msg::SharedPtr msg)
{
current_angle = msg->angle;
}
执行过程:
ROS2收到消息
│
▼
msg 智能指针
│
▼
┌──────────────────┐
│ Msg │
│ angle = 45 │
└──────────────────┘
│
│ msg->angle
▼
45
│
▼
[this]
知道当前对象是 controller
│
▼
controller.current_angle = 45
这就是整段代码干的事情。
九、你可以直接翻译成人话
以后看到:
[this](const Msg::SharedPtr msg)
{
current_angle = msg->angle;
}
不要逐符号硬背。
直接读:
“收到一条消息
msg后,这个小函数可以访问当前对象,然后从消息中读取angle,保存到当前对象的current_angle。”
这样就够了。
十、再给你一个更生活化的例子
假设你是仓库管理员。
你自己:
this
=
当前这个仓库管理员
快递员送来一个包裹:
msg
=
包裹地址/包裹引用
包裹里面有:
angle = 45
你做:
msg->angle
相当于:
顺着包裹信息找到包裹,打开,拿出
angle。
然后:
current_angle = msg->angle;
就是:
把包裹里的角度,抄到自己手里的“当前角度记录表”上。
而:
[this]
就是告诉这个临时小助手:
“你操作的是我这个管理员自己的记录表。”
十一、再补一个非常重要的对比
普通对象:
Msg msg;
msg.angle;
因为 msg 本身就是对象。
使用:
.
智能指针:
Msg::SharedPtr msg;
使用:
msg->angle;
因为 msg 是指向/管理 Msg 对象的智能指针。
所以以后判断用哪个,先问自己:
左边这个变量,是对象本身,还是指针?
上一课 Callback 标准答案
第 1 题
普通函数:
hello();
是:
我们自己的代码主动调用。
第 2 题
回调函数最大的特点:
先把函数交给系统,等未来某个事件发生时,由系统调用它。
第 3 题
subscriber 订阅:
/joint_states
那么 callback 一般在:
新的
/joint_states消息到达时执行。
第 4 题
lambda 很适合 callback,因为:
可以直接在需要注册回调的位置写一个短函数,不需要额外单独定义函数,而且还能通过
[this]等方式访问当前对象的数据。
第 5 题
VLA 发布新动作后,控制节点 callback 最可能负责:
接收动作消息
↓
读取目标动作
↓
转换/检查
↓
更新机械臂控制目标
↓
进一步发给控制器或仿真
第 30 课:ROS2 Node 与继承
现在正式开始看一句真正非常典型的 ROS2 C++:
class MyNode : public rclcpp::Node
第一次看主要有三个陌生点:
MyNode
:
public rclcpp::Node
我们一个一个拆。
1. 先不用 ROS2,理解“继承”
假设我们有一个基础机器人:
class Robot
{
public:
void stop()
{
std::cout << "Stop" << std::endl;
}
};
现在我们想设计:
CleaningRobot
扫地机器人。
它本质上:
也是 Robot。
所以它应该自动拥有:
stop()
同时再增加:
clean()
可以写:
class CleaningRobot : public Robot
{
public:
void clean()
{
std::cout << "Cleaning" << std::endl;
}
};
2. : 在这里是什么意思?
注意,这里的:
:
不是初始化列表那个冒号的具体用途。
这里出现在:
class CleaningRobot : public Robot
表示:
CleaningRobot继承自Robot。
可以画成:
Robot
│
│ 原本有 stop()
│
▼
CleaningRobot
├── stop() ← 继承来的
└── clean() ← 自己新增的
3. “继承”最形象怎么理解?
可以理解成:
在已有机器的基础上继续改装。
比如:
Robot
=
基础底盘
它已经有:
stop
power
move
现在:
CleaningRobot
不是从零重新造一台。
而是:
把 Robot 的能力拿过来,再加清扫功能。
这就是 inheritance。
4. 为什么叫父类 / 子类?
例如:
class Robot
叫:
基类 / 父类。
而:
class CleaningRobot : public Robot
叫:
派生类 / 子类。
可以画:
Robot
父类
│
▼
CleaningRobot
子类
子类会继承父类的一部分能力。
5. public Robot 是什么意思?
class CleaningRobot : public Robot
这里:
public
表示一种:
公有继承。
现在先不要深入 C++ 各种继承权限。
对 ROS2 现阶段直接理解成:
CleaningRobot公开继承Robot,保留“它是一种 Robot”的关系。
就够了。
6. 使用起来是什么样?
CleaningRobot robot;
然后:
robot.stop();
虽然 CleaningRobot 里面没有重新写 stop(),
但它从:
Robot
继承到了。
同时:
robot.clean();
也能调用自己新增的功能。
7. 现在回到 ROS2
典型写法:
class MyNode : public rclcpp::Node
翻译:
定义一个叫
MyNode的类,它继承 ROS2 提供的rclcpp::Node。
也就是说:
rclcpp::Node
=
ROS2已经给你造好的“基础节点”
里面已经包含很多 ROS2 能力,比如:
创建 publisher
创建 subscriber
创建 timer
参数
日志
节点名称
我们不想自己重新写。
于是:
MyNode
=
在 ROS2 Node 基础上继续扩展
8. 为什么 ROS2 不让我们从零写 Node?
因为一个 ROS2 节点需要很多基础功能。
如果从零开始,你可能要自己处理:
ROS通信
节点注册
消息系统
定时器
日志
参数
执行器
……
太复杂。
所以 ROS2 已经给你:
rclcpp::Node
你只需要继承它。
相当于:
ROS2 把“通用底盘”造好了,你只负责安装自己的设备。
9. VLA 控制节点例子
比如以后你想写:
VLAControlNode
负责:
接收 VLA Action
↓
转换成机器人目标
↓
发布控制命令
那么:
class VLAControlNode : public rclcpp::Node
{
};
表示:
创建一种 ROS2 节点,名字叫 VLAControlNode。
它本身拥有 ROS2 Node 的基础能力,同时我们往里面增加 VLA 逻辑。
10. 第一个 ROS2 节点骨架
你以后会看到:
class MyNode : public rclcpp::Node
{
public:
MyNode()
: Node("my_node")
{
}
};
这里现在先拆第一层。
class MyNode
定义自己的类。
: public rclcpp::Node
继承 ROS2 Node。
MyNode()
是:
MyNode 的构造函数。
你已经学过。
: Node("my_node")
这是:
调用父类
Node的构造函数,把节点名字设置成"my_node"。
11. 这一句为什么又有 :
完整:
MyNode()
: Node("my_node")
{
}
这个 : 是你之前学过的:
构造函数初始化列表。
也就是:
创建 MyNode
↓
先初始化它的父类 Node
↓
给 Node 名字 "my_node"
12. 两个冒号别混
你现在已经碰到两种 :。
类定义后面的:
class MyNode : public rclcpp::Node
表示:
继承。
构造函数后面的:
MyNode()
: Node("my_node")
表示:
初始化列表。
位置不同,意义不同。
13. 整个 Node 骨架翻译
class MyNode : public rclcpp::Node
{
public:
MyNode()
: Node("my_node")
{
}
};
翻译成人话:
“我要定义一种自己的 ROS2 节点
MyNode。它继承 ROS2 提供的Node。当创建这个节点对象时,把它在 ROS2 系统里的名字初始化为my_node。”
这就是你以后几乎每个 ROS2 C++ 节点的起点。
14. 和 VLA 联系起来
以后可能写:
class VLAController : public rclcpp::Node
{
public:
VLAController()
: Node("vla_controller")
{
}
};
含义:
这是一个 ROS2 Node
名字叫 vla_controller
以后里面可以:
├── subscribe VLA action
├── subscribe joint_states
├── publish joint commands
└── 做动作转换
这已经开始真正进入你的目标了。
本课小练习
第 1 题
class Dog : public Animal
这里谁是父类?
谁是子类?
第 2 题
一句话解释:
class MyNode : public rclcpp::Node
第 3 题
下面:
MyNode()
: Node("my_node")
{
}
MyNode() 是什么?
第 4 题
Node("my_node")
在这里大概是在做什么?
第 5 题
为什么 ROS2 C++ 节点常常要继承:
rclcpp::Node
而不是我们从零自己写通信功能?
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/qq_56657939/article/details/167039143



