C++ 第 31C 课:Subscriber 到底是什么

我们确实学过一种"比较直白的老写法",但要注意------那种写法主要适用于成员变量;这里的 Node("number_publisher") 是父类初始化,不能简单搬到 {} 里面。

先把这个区别重新捋顺。

一、之前学过的两种初始化写法

假设:

复制代码
class Motor
{
public:
    Motor(int motor_id)
    {
        id = motor_id;
    }

private:
    int id;
};

这里是你最开始学的写法:

复制代码
Motor(int motor_id)
{
    id = motor_id;
}

执行逻辑可以粗略理解成:

复制代码
先创建 id
↓
进入构造函数
↓
再执行
id = motor_id

比如:

复制代码
Motor motor(3);

就是:

复制代码
motor_id = 3

然后:

id = 3

二、后来学了"初始化列表"

更标准的写法:

复制代码
class Motor
{
public:
    Motor(int motor_id)
        : id(motor_id)
    {
    }

private:
    int id;
};

这里:

复制代码
: id(motor_id)

就是:

用 motor_id 直接初始化 成员变量 id。

所以:

复制代码
Motor motor(3);

可以想成:

复制代码
创建 motor
↓
直接让 id 初始就是 3
↓
再进入构造函数 {}

三、这两个简单例子的结果差不多,但过程不完全一样

第一种:

复制代码
Motor(int motor_id)
{
    id = motor_id;
}

更像:

先有 id,然后再给它赋值。

第二种:

复制代码
Motor(int motor_id)
    : id(motor_id)
{
}

更像:

id 出生的时候直接就是这个值。

所以现代 C++ 更常用第二种。

记:

复制代码
构造函数 {} 里面
→ 赋值

构造函数 : 后面
→ 初始化

四、那为什么 ROS2 是:

复制代码
NumberPublisher()
    : Node("number_publisher")
{
}

这里比刚才更重要。

因为:

复制代码
class NumberPublisher : public rclcpp::Node

说明:

复制代码
NumberPublisher
↓
继承了
↓
rclcpp::Node

因此一个 NumberPublisher 对象里面,可以粗略想成:

复制代码
NumberPublisher 对象

┌──────────────────────────┐
│ 父类 Node 部分           │
│                          │
│ NumberPublisher自己的部分 │
└──────────────────────────┘

创建子类的时候:

父类 Node 必须先构造。

所以:

复制代码
: Node("number_publisher")

就是:

调用父类 Node 的构造函数,用 "number_publisher" 初始化父类。


五、不能简单写成"老版本"

你可能想到:

复制代码
NumberPublisher()
{
    Node("number_publisher");
}

这个不等价。

为什么?

因为等进入:

复制代码
{
}

的时候:

当前对象里面真正的父类 Node 部分已经应该初始化完成了。

所以:

复制代码
Node("number_publisher");

放在里面,更像:

临时另外创建一个 Node。

而不是初始化:

当前 NumberPublisher 自己继承来的那个 Node。

所以父类构造这种情况,必须在初始化列表里做:

复制代码
NumberPublisher()
    : Node("number_publisher")
{
}

六、用机械装配再理解一次

假设:

复制代码
Node
=
机械臂的标准底座

而:

复制代码
NumberPublisher
=
在这个底座上安装了发布器的整机

创建整机:

复制代码
NumberPublisher()
    : Node("number_publisher")
{
}

相当于:

复制代码
1. 先装真正属于这台机器的底座 Node
   并给它编号 number_publisher

2. 底座装好

3. 再进入 {}
   安装我们自己的 Publisher 等东西

你不能先把整机装完,再在旁边:

复制代码
Node("number_publisher");

临时造个新底座。

那不是同一个东西。


七、所以你现在这样记最合适

普通成员变量

初学版:

复制代码
Motor(int id)
{
    this->id = id;
}

更推荐:

复制代码
Motor(int id)
    : id(id)
{
}

父类

复制代码
class MyNode : public rclcpp::Node

构造时:

复制代码
MyNode()
    : Node("my_node")
{
}

这里不是单纯为了"写得高级"。

而是:

父类必须先初始化。


上一课答案

第 1 题

复制代码
class NumberPublisher : public rclcpp::Node

意思:

定义一个自己的 NumberPublisher 类,并让它继承 ROS2 的 rclcpp::Node,因此拥有 ROS2 节点的基础能力。


第 2 题

复制代码
: Node("number_publisher")

设置的是:

这个 ROS2 节点的节点名称。

这里名字是:

复制代码
number_publisher

第 3 题

复制代码
create_publisher<std_msgs::msg::Int32>(
    "number",
    10
);

其中:

复制代码
std_msgs::msg::Int32

表示:

Publisher 发布的消息类型是整数消息。

而:

复制代码
"number"

表示:

Topic 名称。

也就是往 number 频道发送。


第 4 题

复制代码
std_msgs::msg::Int32 msg;

创建一个 ROS2 整数消息盒子。

复制代码
msg.data = 100;

把 100 放进去。

复制代码
publisher_->publish(msg);

使用 Publisher 把消息发出去。

整体:

复制代码
创建盒子
↓
装100
↓
发送

第 5 题

因为:

复制代码
publisher_

是一个智能指针。

所以:

复制代码
对象本体      .
对象指针      ->

因此:

复制代码
publisher_->publish(msg);

使用 ->。


第 31C 课:Subscriber 到底是什么

这一课先不写完整 ROS2 程序。

Publisher 是:

发消息。

Subscriber 是:

收消息。

还是用微信群类比。

假设:

复制代码
VLA节点
   │
   │ 发布动作
   ▼
 /vla_action
   │
   ▼
机器人控制节点

那么:

复制代码
VLA节点
=
Publisher

/vla_action
=
Topic

机器人控制节点
=
Subscriber

1. Subscriber 不断"监听" Topic

假设有:

复制代码
/number

这个 Topic。

Publisher 发:

复制代码
100

Subscriber 订阅:

复制代码
/number

于是:

复制代码
Publisher
    │
    │ 100
    ▼
 /number
    │
    ▼
Subscriber

但 Subscriber 收到以后:

到底要干什么?

这就需要:

复制代码
callback

2. Subscriber + callback

流程:

复制代码
Subscriber开始监听 /number
↓
平时没消息
↓
有消息到达
↓
ROS2自动执行 callback
↓
callback读取消息

所以以前我们学 callback 是有原因的。


3. 先做一个完全假的版本

暂时不看 ROS2 真语法。

假设可以这样写:

复制代码
subscribe("number", callback);

意思:

订阅 number Topic;有消息的时候执行 callback。

然后:

复制代码
void callback(int data)
{
    std::cout << data;
}

如果 Publisher 发:

复制代码
100

ROS2 可以想象成自动做:

复制代码
callback(100);

注意:

不是我们自己主动写 callback(100)。

而是消息到达以后,ROS2 帮我们调用。


4. 真实 ROS2 不直接给你一个 int

Publisher 之前不是发:

复制代码
int x = 100;

而是发:

复制代码
std_msgs::msg::Int32 msg;

msg.data = 100;

也就是一个消息对象。

因此 Subscriber 收到的也是:

一个 Int32 消息对象。


5. 最核心的 callback 长这样

先只看:

复制代码
void callback(
    const std_msgs::msg::Int32::SharedPtr msg)
{
    std::cout << msg->data << std::endl;
}

这一句确实比较长。

我们分层。


6. 先看变量名

复制代码
msg

就是:

收到的消息。

这是程序员自己起的变量名。

也可以叫:

复制代码
message

只是大家经常写:

复制代码
msg

7. 再看 SharedPtr

复制代码
std_msgs::msg::Int32::SharedPtr

不要全部看。

核心就是:

复制代码
Int32
↓
一个整数消息

SharedPtr
↓
这个消息通过智能指针交给你

所以:

复制代码
msg

不是消息对象本身,而是:

指向/管理收到的消息对象的智能指针。

可以画成:

复制代码
msg
 │
 ▼
┌────────────────┐
│ Int32消息      │
│ data = 100     │
└────────────────┘

8. 所以为什么:

复制代码
msg->data

因为:

复制代码
msg

是智能指针。

因此:

复制代码
msg
↓
顺着指针找到消息
↓
访问消息里的 data

于是:

复制代码
msg->data

如果收到的是 100:

复制代码
结果 = 100

9. const 现在怎么理解?

完整:

复制代码
const std_msgs::msg::Int32::SharedPtr msg

现在先简单理解成:

这个回调主要负责读取收到的消息,不希望随便修改它。

严格的 const/智能指针细节我们之后再区分,现在不用钻。

这里先建立工程直觉:

ROS2 把消息交给 callback,我们读取它。


10. 整个 callback 翻译

复制代码
void callback(
    const std_msgs::msg::Int32::SharedPtr msg)
{
    std::cout << msg->data << std::endl;
}

翻译成人话:

定义一个回调函数。ROS2 收到一条 Int32 消息后,把这个消息通过智能指针 msg 传进来,然后我们读取其中的 data 并打印。


11. 整个通信链终于可以画出来了

Publisher:

复制代码
msg.data = 100;

publisher_->publish(msg);

然后:

复制代码
Publisher
   │
   │ Int32:
   │ data=100
   ▼
 /number Topic
   │
   ▼
Subscriber
   │
   │ 消息到达
   ▼
callback(msg)
   │
   ▼
msg->data
   │
   ▼
100

这就是 ROS2 Topic 通信最核心的逻辑。


12. 和 VLA 完全一样

以后不再是:

复制代码
Int32
data = 100

而可能是:

复制代码
VLA Action
├── x
├── y
├── z
├── gripper
└── ...

Subscriber 收到后依然:

复制代码
消息来了
↓
callback
↓
msg->某个字段
↓
取出VLA动作
↓
给机械臂执行

所以你现在真正需要学的不是死记 API,而是先建立:

Publisher 发 → Topic 传 → Subscriber 收 → callback 处理

这条脑回路。


本课小练习

1

Subscriber 最核心的作用是什么?

2

callback 是我们在主程序里不停主动调用的吗?

3

复制代码
msg->data

为什么用 ->?

4

如果:

复制代码
Publisher发送 data=50

Subscriber callback 中:

复制代码
std::cout << msg->data;

会打印什么?

5

目前用一句话描述完整 ROS2 Topic 通信:

复制代码
Publisher → ? → Subscriber → ?
相关推荐
weixin_440730501 小时前
内置函数、json文本、pickle二进制
开发语言·python·json
殷色玫瑰1 小时前
C++ string类详解:常用接口、字符串操作与模拟实现
java·linux·c语言·开发语言·数据结构·c++
李游Leo1 小时前
HarmonyOS 7 + Spatial Recon Kit-C++:3DGS 高斯参数的非有限值隔离与可渲染性门禁【鸿蒙心迹】
开发语言·c++·3d·harmonyos
Evand J2 小时前
【MATLAB例程】三维RRT+APF路径规划与AOA-TDOA融合定位算法。附代码的下载链接
算法·matlab·路径规划·rrt·tdoa·apf·aoa
时间的拾荒人2 小时前
Qt 文件操作详解:从基础类到读写实战
开发语言·qt·面试
Wang's Blog2 小时前
Java 项目实战: 外卖平台优化-YApi接口管理平台与文档导入导出
java·开发语言·yapi
海绵宝宝转agent2 小时前
leetCode100算法开源笔记分享
笔记·算法
jimy12 小时前
基类没有虚析构函数,基类指针删除派生类对象,行为是未定义的
开发语言·c++
迅猛龙办公室3 小时前
python获取星期字符串
开发语言·python