☰
C++ 第 31A 课:先把 ROS2 Publisher 讲成人话
2026/10/5 15:18:38 网站建设 项目流程

上一课答案

第 1 题

class Dog : public Animal

其中:

Animal → 父类 Dog → 子类

第 2 题

class MyNode : public rclcpp::Node

表示:

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


第 3 题

MyNode()

是:

MyNode的构造函数。

创建MyNode对象时会自动调用。


第 4 题

Node("my_node")

表示:

调用父类Node的构造函数,并把节点名称设置为"my_node"。


第 5 题

因为rclcpp::Node已经提供了 ROS2 节点所需的大量基础能力,比如:

Publisher Subscriber Timer Parameter Logger Service

我们继承它,就不需要自己从零实现 ROS2 通信系统。

上一课跳得太快了。问题不在 Publisher/Subscriber 本身,而是我一口气把ROS2、模板< >、::、智能指针、this->、lambda、消息类型全塞进了一段代码里。我们先不继续下一课,把第 31 课拆成几节微课。今天只搞懂Publisher 到底是什么,以及一条 ROS2 发布代码怎么读。

第 31A 课:先把 ROS2 Publisher 讲成人话

先完全忘掉这句:

rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr publisher_;

现在不看它。

你先只理解一个现实场景。

假设我们的机械臂程序里,有两个程序:

程序 A:VLA 程序 B:机械臂控制器

VLA 算出了:

“机械臂第 1 轴转到 30°”

问题是:

程序 A 怎么把这个消息告诉程序 B?

ROS2 提供了一套通信系统。

最简单可以想象成微信:

VLA程序 │ │ 发消息 ▼ 【机器人动作群】 │ │ 收消息 ▼ 机械臂控制程序

这里:

发消息的人 Publisher 微信群 Topic 收消息的人 Subscriber 发送的内容 Message

今天我们只学发消息这一半:

Publisher ↓ Topic ↓ Message

1. 如果不用 ROS2,最简单的“发消息”应该长什么样?

假设我自己设计一个超级简单的程序:

Publisher publisher;

意思:

创建一个发布器。

然后:

publisher.publish(100);

意思:

发布数字 100。

就这么简单。

你先把 ROS2 Publisher 的本质理解成:

publisher.publish(消息);

也就是:

让发布器把一条消息发出去。


2. ROS2 为什么看起来复杂?

因为 ROS2 还必须知道两件事:

第一:

你发到哪个频道?

第二:

你发送的是什么类型的数据?

比如我们说:

频道:number 数据类型:整数

所以概念上相当于:

创建发布器: 我要发送: 整数 发送到: number频道

真实 ROS2 才写成:

create_publisher<std_msgs::msg::Int32>( "number", 10 );

别急着整句理解。

我们开始拆。


3. 先看"number"

这个最简单:

"number"

只是一个字符串。

你已经学过:

std::string name = "Robot";

所以:

"number"

没什么特殊的。

在这里它表示:

Topic 的名字。

也就是频道名称。

可以想成:

微信群名字: number

所以:

"number"

≈

我要往 number 这个频道发东西

4. Topic 到底是什么?

Topic 不是什么神秘对象。

你现在先把它理解成:

ROS2 中用名字标识的消息频道。

比如机器人里可能有:

/joint_states /camera/image /vla_action /cmd_vel

分别可以理解成不同频道。

例如:

/joint_states

里面专门传:

机器人关节状态。

而:

/vla_action

里面以后可以传:

VLA 产生的机器人动作。


5. 再看这一坨

std_msgs::msg::Int32

第一次看肯定很烦。

我们拆成:

std_msgs :: msg :: Int32

你以前学过:

std::cout

其中:

::

可以先理解成:

“里面的”

所以:

std::cout

可以粗略读:

std里面的cout

一样的:

std_msgs::msg::Int32

可以一层层读:

std_msgs 里面的 msg 里面的 Int32

你现在不用关心为什么要分这么多层。

只需要知道最终的:

Int32

表示:

一种 ROS2 整数消息。


6. 为什么不能直接发普通int?

你可能会问:

我已经有:

int x = 100;

为什么 ROS2 还要搞:

std_msgs::msg::Int32

因为 ROS2 在不同程序、甚至不同电脑之间传数据。

它需要一种大家都认识的统一“快递包装”。

普通:

int x = 100;

可以理解成:

桌子上的数字 100。

而:

std_msgs::msg::Int32 msg;

可以理解成:

一个符合 ROS2 快递标准的盒子。

这个盒子里面有:

msg.data

于是:

msg.data = 100;

就是:

┌─────────────┐ │ ROS2消息盒子 │ │ │ │ data = 100 │ └─────────────┘

这样 ROS2 才方便把整个盒子送出去。


7. 所以这两句一定要理解

std_msgs::msg::Int32 msg;

意思:

创建一个 ROS2 整数消息对象,名字叫msg。

然后:

msg.data = 100;

意思:

把数字100放进这个消息对象的data成员。

是不是一下简单很多?

其实和你学过的 struct 很像。

完全可以把它暂时想象成:

struct Int32 { int data; };

然后:

Int32 msg; msg.data = 100;

ROS2 的Int32消息你现在就可以这样理解。


8. 再看< >

我们之前学过:

std::vector<double>

什么意思?

你已经知道:

一个装double的 vector。

这里同样:

create_publisher<std_msgs::msg::Int32>

你可以理解成:

创建一个专门发送 Int32 消息的 Publisher。

所以:

<...>

暂时就翻译成:

“我要处理什么类型的数据?”

比如:

std::vector<double>

→ 装double

而:

create_publisher<Int32>

→ 发布Int32

先到这个深度就够。


9. 现在重新看这半句

create_publisher<std_msgs::msg::Int32>( "number", 10 );

现在你应该至少能翻译出:

创建一个 Publisher,它专门发送Int32整数消息,发送到"number"这个 Topic。

至于:

10

今天甚至可以先不管。

暂时把它记成:

ROS2 通信参数,目前先默认写 10。

后面我们专门学 QoS 时再解释。


10. 那this->又来了

完整一点:

this->create_publisher<std_msgs::msg::Int32>( "number", 10 );

还记得:

this

是什么意思吗?

就是:

当前这个对象自己。

现在我们的类是:

class MyNode : public rclcpp::Node

所以当前对象就是:

当前这个 ROS2 节点。

那么:

this->create_publisher(...)

就可以翻译成:

让当前这个 ROS2 节点创建一个 Publisher。

你甚至可以把:

this->

脑子里先翻译成:

“我这个节点的……”

所以:

this->create_publisher(...)

≈

“我这个节点创建发布器。”


11. 为什么这里是->?

因为:

this

是一个指针。

我们前面已经学过:

普通对象 . 指针 ->

所以:

this->create_publisher(...)

用的是:

->

因为this是“当前对象的地址”。

可以画成:

this │ │ 地址 ▼ 当前 MyNode 对象 │ └── create_publisher()

所以:

this->create_publisher()

就是:

顺着this找到当前 Node 对象,然后调用它的create_publisher()。


12. 我们现在已经把右边读完了

this->create_publisher<std_msgs::msg::Int32>( "number", 10 );

整句话:

当前 ROS2 节点创建一个发布器;这个发布器发送Int32整数消息,使用"number"Topic。

很好。

先到这里。


13. 创建出来以后怎么办?

我们需要把这个 Publisher 保存下来。

就像:

int x = 5;

右边:

5

产生一个值。

左边:

x

保存它。

一样:

publisher_ = this->create_publisher<...>();

左边:

publisher_

就是:

保存创建好的 Publisher。

所以你可以暂时把整句理解成:

publisher_ = 我刚刚创建的发布器

14.publisher_之后怎么用?

比如已经有消息:

std_msgs::msg::Int32 msg; msg.data = 100;

然后:

publisher_->publish(msg);

又出现->。

为什么?

因为:

publisher_

是一个智能指针。

所以:

publisher_ │ ▼ Publisher对象 │ └── publish()

于是:

publisher_->publish(msg);

翻译:

找到 Publisher 对象,调用它的publish(),把msg发出去。

最终:

msg.data = 100 ↓ publisher_->publish(msg) ↓ number Topic ↓ 100 被发送出去

15. 你今天只需要真正看懂这四步

先别管整份 ROS2 代码。

只看:

std_msgs::msg::Int32 msg;

创建一个整数消息盒子。

然后:

msg.data = 100;

往盒子里放 100。

然后 Publisher 已经创建好了:

publisher_

最后:

publisher_->publish(msg);

让 Publisher 把这个盒子发出去。

整个逻辑就是:

创建盒子 ↓ 装入100 ↓ 找到发布器 ↓ 发送盒子

16. 和 VLA 联系起来其实也一样

现在我们发的是:

100

以后 VLA 发的是:

机械臂动作

例如概念上:

VLA输出: Joint1 = 30° Joint2 = 15° ...

依旧是:

创建ROS2消息 ↓ 把VLA动作装进去 ↓ Publisher发布 ↓ Topic ↓ 机器人控制节点收到

只是消息盒子变复杂而已。


17. 现在先别学 Subscriber

上一课的问题就是:

Publisher 还没吃透,我马上又加 Subscriber + lambda + SharedPtr。

所以我们现在把课程拆开:

  • 31A:Publisher 是什么——现在这节
  • 31B:Publisher 的完整最小程序
  • 31C:Subscriber 是什么
  • 31D:Publisher → Topic → Subscriber 完整链路

等这四小节真正搞懂,我们再继续spin()。


先检查你有没有抓住核心

你现在只需要能回答下面 4 个很小的问题:

std_msgs::msg::Int32 msg;

是什么意思?

msg.data = 100;

是什么意思?

this->create_publisher<...>()

大概是什么意思?

publisher_->publish(msg);

大概是什么意思?

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询