ROS2 C++ 常见 rclcpp 写法保姆级教程

目录

摘要

[一、rclcpp 是什么?](#一、rclcpp 是什么?)

[1.1 rclcpp 的基本概念](#1.1 rclcpp 的基本概念)

[1.2 rclcpp 常见写法](#1.2 rclcpp 常见写法)

[二、ROS2 C++ 程序启动相关写法](#二、ROS2 C++ 程序启动相关写法)

[2.1 main() 函数基本结构](#2.1 main() 函数基本结构)

[2.2 rclcpp::init()](#2.2 rclcpp::init())

[2.3 rclcpp::spin()](#2.3 rclcpp::spin())

[2.4 rclcpp::shutdown()](#2.4 rclcpp::shutdown())

[三、rclcpp::Node 节点类](#三、rclcpp::Node 节点类)

[3.1 rclcpp::Node 是什么?](#3.1 rclcpp::Node 是什么?)

[3.2 为什么要继承 rclcpp::Node?](#3.2 为什么要继承 rclcpp::Node?)

[3.3 Node("节点名字") 是什么意思?](#3.3 Node("节点名字") 是什么意思?)

[3.4 this->get_logger()](#3.4 this->get_logger())

[四、rclcpp::Publisher 发布者](#四、rclcpp::Publisher 发布者)

[4.1 Publisher 是什么?](#4.1 Publisher 是什么?)

[4.2 发布者成员变量写法](#4.2 发布者成员变量写法)

[4.3 create_publisher() 创建发布者](#4.3 create_publisher() 创建发布者)

[4.4 publish() 发布消息](#4.4 publish() 发布消息)

[五、rclcpp::Subscription 订阅者](#五、rclcpp::Subscription 订阅者)

[5.1 Subscription 是什么?](#5.1 Subscription 是什么?)

[5.2 订阅者成员变量写法](#5.2 订阅者成员变量写法)

[5.3 create_subscription() 创建订阅者](#5.3 create_subscription() 创建订阅者)

[5.4 订阅者回调函数](#5.4 订阅者回调函数)

[5.5 为什么回调函数里用 msg->?](#5.5 为什么回调函数里用 msg->?)

[六、rclcpp::TimerBase 定时器](#六、rclcpp::TimerBase 定时器)

[6.1 TimerBase 是什么?](#6.1 TimerBase 是什么?)

[6.2 create_wall_timer() 创建定时器](#6.2 create_wall_timer() 创建定时器)

[6.3 500ms 是什么意思?](#6.3 500ms 是什么意思?)

[6.4 定时器常见运行流程](#6.4 定时器常见运行流程)

[七、ROS2 C++ 常见符号总结](#七、ROS2 C++ 常见符号总结)

[7.1 :: 作用域解析符](#7.1 :: 作用域解析符)

[7.2 <> 模板写法](#7.2 <> 模板写法)

[7.3 SharedPtr 智能指针](#7.3 SharedPtr 智能指针)

[7.4 this-> 当前对象指针](#7.4 this-> 当前对象指针)

[7.5 std::bind 和 std::placeholders::_1](#7.5 std::bind 和 std::placeholders::_1)

八、总结


摘要

在 ROS2 C++ 代码中,经常会看到下面这些写法:

cpp 复制代码
rclcpp::Node
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr
rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr
rclcpp::TimerBase::SharedPtr
this->create_publisher()
this->create_subscription()
this->create_wall_timer()
std::bind()

这些内容看起来复杂,其实可以分成两类理解:

cpp 复制代码
ROS2 相关写法
├── rclcpp::Node
├── rclcpp::Publisher
├── rclcpp::Subscription
├── rclcpp::TimerBase
├── create_publisher()
├── create_subscription()
└── create_wall_timer()

C++ 相关写法
├── ::
├── <>
├── SharedPtr
├── this->
└── std::bind

本篇文章主要围绕 ROS2 C++ 中最常见、最基础的 rclcpp 写法进行总结,重点讲解 ROS2 节点如何创建、程序如何启动、发布者和订阅者如何定义、定时器如何工作,以及常见符号应该如何理解。

这篇文章适合作为 ROS2 C++ 代码阅读的入门篇。

学完本篇之后,至少要能看懂下面这些问题:

  1. rclcpp::Node 是什么?
  2. 为什么要继承 rclcpp::Node?
  3. rclcpp::init() 有什么作用?
  4. rclcpp::spin() 为什么不能少?
  5. Publisher 和 Subscription 怎么声明?
  6. create_publisher() 和 create_subscription() 怎么理解?
  7. TimerBase 和 create_wall_timer() 是什么?
  8. this->、::、<>、SharedPtr、std::bind 分别是什么意思?

不过需要说明的是,ROS2 C++ 的常见写法并不只有这些。

本篇主要讲的是最基础的 rclcpp 入门写法,后续还会继续整理:

第二篇:ROS2 C++ 常见写法完整分类

├── Service 服务通信
├── Client 客户端通信
├── Action 动作通信
├── Parameter 参数
├── Logging 日志
├── QoS 通信质量
├── Executor 执行器
├── CallbackGroup 回调组
├── Lifecycle 生命周期节点
├── Component 组件化节点
├── tf2 坐标变换
└── message_filters 多传感器同步

第三篇:ROS2 C++ 中必须掌握的 C++ 语法

├── ::
├── ->
├── .
├── <>
├── auto
├── using
├── SharedPtr
├── std::make_shared
├── std::bind
├── lambda
├── std::chrono_literals
├── std::thread
├── std::mutex
└── std::future

这样整个系列就会形成一个比较完整的学习路线:

复制代码
第一篇
├── 先看懂 rclcpp 基础写法

第二篇
├── 再系统整理 ROS2 C++ 常见模块

第三篇
└── 最后补齐 ROS2 C++ 背后的 C++ 语法

只要把这三篇内容串起来,后面再去看小车控制、AGV 底盘运动控制、导航、SLAM、多传感器融合相关代码时,就不会只停留在"代码能跑",而是能真正知道:

  1. 这个对象是什么?
  2. 这个函数在哪里被调用?
  3. 这个回调什么时候触发?
  4. 这个智能指针为什么这样写?
  5. 这个 ROS2 模块在机器人系统中负责什么?

除此之外,还有 ROS2 与 C++ 相关保姆级教程:

(1)ROS2 C++ 回调函数 保姆级教程:

ROS2 C++ 回调函数 保姆级教程-CSDN博客https://blog.csdn.net/m0_58954356/article/details/162178534?spm=1001.2014.3001.5502


一、rclcpp 是什么?

1.1 rclcpp 的基本概念

rclcpp 是 ROS2 的 C++ 客户端库。

简单理解:

rclcpp = ROS2 C++ 编程接口

如果使用 C++ 写 ROS2 节点,就会大量使用 rclcpp 里面的类和函数。

例如:

复制代码
#include "rclcpp/rclcpp.hpp"

这行代码表示引入 ROS2 C++ 的核心头文件。

只要代码中使用了:

cpp 复制代码
rclcpp::Node
rclcpp::init()
rclcpp::spin()
rclcpp::Publisher
rclcpp::Subscription

一般都需要包含:

复制代码
#include "rclcpp/rclcpp.hpp"

1.2 rclcpp 常见写法

常见写法如下:

写法 含义
rclcpp::init() 初始化 ROS2
rclcpp::shutdown() 关闭 ROS2
rclcpp::spin() 让节点持续运行并处理回调
rclcpp::Node ROS2 C++ 节点基类
rclcpp::Publisher 发布者类型
rclcpp::Subscription 订阅者类型
rclcpp::Service 服务端类型
rclcpp::Client 客户端类型
rclcpp::TimerBase 定时器基础类型

可以这样理解:

复制代码
rclcpp
├── 提供 ROS2 C++ 程序运行能力
├── 提供 ROS2 节点创建能力
├── 提供话题发布和订阅能力
├── 提供服务通信能力
├── 提供定时器能力
├── 提供日志功能
├── 提供参数功能
└── 提供时间相关功能

二、ROS2 C++ 程序启动相关写法

2.1 main() 函数基本结构

ROS2 C++ 程序中,main() 函数一般会出现下面三个核心写法:

cpp 复制代码
rclcpp::init(argc, argv);
rclcpp::spin(node);
rclcpp::shutdown();

完整结构一般如下:

cpp 复制代码
int main(int argc, char * argv[])
{
    rclcpp::init(argc, argv);

    auto node = std::make_shared<LimoTopicSub>();

    rclcpp::spin(node);

    rclcpp::shutdown();
    return 0;
}

2.2 rclcpp::init()

cpp 复制代码
rclcpp::init(argc, argv);

作用是:

初始化 ROS2 C++ 运行环境

可以理解成:

复制代码
rclcpp::init()
├── 启动 ROS2 C++ 程序环境
├── 解析 ROS2 相关命令行参数
├── 为后续创建节点做准备
└── 一般写在 main() 函数最开始

如果没有这行代码,后面的 ROS2 节点通常无法正常运行。


2.3 rclcpp::spin()

cpp 复制代码
rclcpp::spin(node);

作用是:

让节点持续运行,并等待回调函数被触发

比如:

复制代码
rclcpp::spin(node)
├── 等待订阅者收到消息
├── 等待定时器触发
├── 等待服务端收到请求
└── 让节点不会立刻退出

如果没有 spin(),节点创建完之后,程序可能很快就结束了。

  1. 对于订阅者来说,spin() 非常重要。
  2. 因为订阅者需要一直等待消息,如果程序直接结束,就无法接收话题数据。

2.4 rclcpp::shutdown()

cpp 复制代码
rclcpp::shutdown();

作用是:

关闭 ROS2 C++ 运行环境

可以理解成:

复制代码
rclcpp::shutdown()
├── 释放 ROS2 相关资源
├── 结束 ROS2 通信环境
└── 一般写在 main() 函数退出前

三、rclcpp::Node 节点类

3.1 rclcpp::Node 是什么?

cpp 复制代码
rclcpp::Node

表示ROS2 C++ 中的节点的基础类。

封装了一个 ROS2 节点应该具备的基础能力,例如:

复制代码
rclcpp::Node
├── 节点名字
├── 日志功能 get_logger()
├── 创建发布器 create_publisher()
├── 创建订阅器 create_subscription()
├── 创建定时器 create_wall_timer()
├── 创建服务端 create_service()
├── 创建客户端 create_client()
├── 参数功能
├── 时间功能 now()
└── 其他 ROS2 节点基础能力

3.2 为什么要继承 rclcpp::Node?

在 ROS2 C++ 中,我们经常这样写:

cpp 复制代码
class LimoTopicSub : public rclcpp::Node
{
public:
    LimoTopicSub() : Node("limo_topic_sub")
    {
    }
};

其中:

cpp 复制代码
class LimoTopicSub : public rclcpp::Node

意思是:

定义一个自己的节点类 LimoTopicSub,并继承 ROS2 官方提供的节点基类 rclcpp::Node。

也就是说:

复制代码
LimoTopicSub
├── 是我们自己写的 C++ 类
├── 继承了 rclcpp::Node
└── 所以它具备 ROS2 节点能力

3.3 Node("节点名字") 是什么意思?

cpp 复制代码
LimoTopicSub() : Node("limo_topic_sub")

这里的:

cpp 复制代码
Node("limo_topic_sub")

表示给当前节点设置名字。

启动节点后,可以通过命令查看:

复制代码
ros2 node list

如果节点启动成功,正常情况下可以看到:

复制代码
/limo_topic_sub

所以:

复制代码
Node("limo_topic_sub")
├── 创建 ROS2 节点
├── 节点名字叫 limo_topic_sub
└── ros2 node list 中会显示 /limo_topic_sub

3.4 this->get_logger()

在 ROS2 C++ 中,经常会看到:

cpp 复制代码
RCLCPP_INFO(this->get_logger(), "limo_topic_sub node has started.");

其中:

cpp 复制代码
this->get_logger()

表示获取当前节点的日志对象。

可以理解成:

复制代码
this->get_logger()
├── this 表示当前节点对象
├── get_logger() 表示获取当前节点的日志器
└── 配合 RCLCPP_INFO 输出日志

完整含义是:

复制代码
RCLCPP_INFO(this->get_logger(), "xxx")
├── 使用当前节点的日志器
└── 输出一条普通信息日志

四、rclcpp::Publisher 发布者

4.1 Publisher 是什么?

发布者用于向某个 Topic 发布消息。

例如小车速度控制中,常见话题是:

复制代码
/cmd_vel

发布的消息类型是:

复制代码
geometry_msgs::msg::Twist

所以发布者类型可以写成:

复制代码
rclcpp::Publisher<geometry_msgs::msg::Twist>

意思是:

复制代码
这是一个发布 Twist 类型消息的 ROS2 发布者

4.2 发布者成员变量写法

常见写法:

cpp 复制代码
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr pub_vel_;

可以拆开理解:

复制代码
rclcpp::Publisher<geometry_msgs::msg::Twist>
├── 表示发布者类型
├── 发布的消息类型是 geometry_msgs::msg::Twist

::SharedPtr
├── 表示智能指针
├── 用来管理发布者对象

pub_vel_
└── 是我们自己定义的发布者变量名

所以整行代码的意思是:

定义一个发布者智能指针,这个发布者专门发布 Twist 类型消息。


4.3 create_publisher() 创建发布者

创建发布者一般写成:

cpp 复制代码
pub_vel_ = this->create_publisher<geometry_msgs::msg::Twist>(
    "/cmd_vel",
    10
);

可以理解成:

复制代码
this->create_publisher<geometry_msgs::msg::Twist>()
├── this-> 表示当前节点对象
├── create_publisher 表示创建发布器
├── <geometry_msgs::msg::Twist> 表示发布的消息类型
├── "/cmd_vel" 表示发布到这个话题
└── 10 表示队列深度

完整意思是:

  1. 创建一个 /cmd_vel 发布者,
  2. 发布的消息类型是 geometry_msgs::msg::Twist,
  3. 队列深度是 10。

4.4 publish() 发布消息

发布消息时常见写法:

cpp 复制代码
pub_vel_->publish(vel_cmd);

意思是:

通过 pub_vel_ 这个发布者,把 vel_cmd 这条消息发布出去。

可以拆开看:

pub_vel_
├── 发布者对象

->
├── 通过指针调用成员函数

publish()
├── 发布消息的函数

vel_cmd
└── 要发布出去的消息对象

完整示例:

cpp 复制代码
geometry_msgs::msg::Twist vel_cmd;

vel_cmd.linear.x = 0.1;
vel_cmd.angular.z = 0.0;

pub_vel_->publish(vel_cmd);

意思是:

  1. 创建一个 Twist 速度消息,
  2. 设置小车线速度和角速度,
  3. 然后通过 /cmd_vel 发布出去。

五、rclcpp::Subscription 订阅者

5.1 Subscription 是什么?

订阅者用于接收某个 Topic 上的消息。

例如订阅 /cmd_vel 速度话题:

cpp 复制代码
rclcpp::Subscription<geometry_msgs::msg::Twist>

意思是:

这是一个订阅 Twist 类型消息的 ROS2 订阅者


5.2 订阅者成员变量写法

完整写法:

cpp 复制代码
rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr cmd_sub_;

可以拆开理解:

复制代码
rclcpp::Subscription<geometry_msgs::msg::Twist>
├── 表示订阅者类型
├── 订阅的消息类型是 geometry_msgs::msg::Twist

::SharedPtr
├── 表示智能指针
├── 用来管理订阅者对象

cmd_sub_
└── 是我们自己定义的订阅者变量名

所以整行代码意思是:

定义一个订阅者智能指针,这个订阅者接收 Twist 类型消息。


5.3 create_subscription() 创建订阅者

创建订阅者常见写法:

cpp 复制代码
cmd_sub_ = this->create_subscription<geometry_msgs::msg::Twist>(
    "/cmd_vel",
    10,
    std::bind(&LimoTopicSub::cmd_callback, this, std::placeholders::_1)
);

可以理解成:

复制代码
this->create_subscription<geometry_msgs::msg::Twist>()
├── 创建一个订阅者
├── 订阅 /cmd_vel 话题
├── 接收 Twist 类型消息
├── 队列深度是 10
└── 收到消息后自动调用 cmd_callback 回调函数

5.4 订阅者回调函数

回调函数一般这样写:

cpp 复制代码
void cmd_callback(const geometry_msgs::msg::Twist::SharedPtr msg)
{
    RCLCPP_INFO(this->get_logger(),
                "linear.x = %.2f, angular.z = %.2f",
                msg->linear.x,
                msg->angular.z);
}

这里的:

复制代码
msg

表示订阅者收到的消息

复制代码
msg->linear.x
msg->angular.z

表示读取 Twist 消息里面的字段。

可以理解成:

复制代码
cmd_callback()
├── 是订阅者回调函数
├── 不需要我们手动调用
├── ROS2 收到 /cmd_vel 消息后会自动调用
├── msg 表示收到的 Twist 消息
└── msg->linear.x / msg->angular.z 表示读取速度数据

5.5 为什么回调函数里用 msg->?

因为这里的**msg 是一个智能指针。**

复制代码
const geometry_msgs::msg::Twist::SharedPtr msg

所以访问里面的成员变量时,需要使用:

复制代码
msg->linear.x

而不是:

复制代码
msg.linear.x

可以简单记住:

  1. 普通对象访问成员变量:用 .
  2. 指针访问成员变量:用 ->

例如:

复制代码
geometry_msgs::msg::Twist vel_cmd;
vel_cmd.linear.x = 0.1;

这是普通对象,所以用 .

而:

复制代码
geometry_msgs::msg::Twist::SharedPtr msg;
msg->linear.x = 0.1;

这是指针,所以用 ->


六、rclcpp::TimerBase 定时器

6.1 TimerBase 是什么?

定时器用于周期性执行某个函数。

在 ROS2 C++ 中,定时器成员变量常见写法是:

cpp 复制代码
rclcpp::TimerBase::SharedPtr timer_;

意思是:

定义一个 ROS2 定时器智能指针

可以理解成:

复制代码
rclcpp::TimerBase
├── 表示 ROS2 C++ 定时器基础类型
├── 可以周期性触发回调函数
└── 常用于定时发布消息

6.2 create_wall_timer() 创建定时器

创建定时器通常写成:

cpp 复制代码
timer_ = this->create_wall_timer(
    500ms,
    std::bind(&LimoTopicCmd::timer_callback, this)
);

意思是:

每隔 500ms 自动触发一次 timer_callback()

可以拆开理解:

复制代码
this->create_wall_timer()
├── this-> 表示当前节点对象
├── create_wall_timer 表示创建定时器
├── 500ms 表示每 500 毫秒触发一次
└── timer_callback 表示定时器触发后执行的函数

6.3 500ms 是什么意思?

代码中经常会看到:

cpp 复制代码
using namespace std::chrono_literals;

有了这行代码后,就可以直接写:

复制代码
500ms
1s
2s

例如:

cpp 复制代码
timer_ = this->create_wall_timer(
    500ms,
    std::bind(&LimoTopicCmd::timer_callback, this)
);

如果没有:

复制代码
using namespace std::chrono_literals;

那么 500ms 这种写法可能无法识别。


6.4 定时器常见运行流程

定时器最常见的用法是定时发布消息。

例如:

复制代码
每 500ms 发布一次 /cmd_vel 速度消息

流程可以理解为:

复制代码
create_wall_timer()
        ↓
每隔 500ms 触发一次
        ↓
调用 timer_callback()
        ↓
创建 Twist 速度消息
        ↓
通过 pub_vel_->publish() 发布出去

七、ROS2 C++ 常见符号总结

7.1 :: 作用域解析符

在 ROS2 C++ 中,经常看到:

cpp 复制代码
rclcpp::Node
std::bind
geometry_msgs::msg::Twist

这里的:

复制代码
::

作用域解析符

简单理解:

:: 表示"从某个命名空间或者类里面找东西"

例如:

复制代码
rclcpp::Node

意思是:

复制代码
从 rclcpp 里面找到 Node

geometry_msgs::msg::Twist

意思是:

复制代码
从 geometry_msgs 里面找到 msg,
再从 msg 里面找到 Twist

std::bind

意思是:

复制代码
从 std 标准库命名空间里面找到 bind 函数

7.2 <> 模板写法

例如:

cpp 复制代码
rclcpp::Publisher<geometry_msgs::msg::Twist>

这里的:

cpp 复制代码
<geometry_msgs::msg::Twist>

表示模板参数

简单理解:

尖括号 <> 用来指定类型

比如:

复制代码
rclcpp::Publisher<geometry_msgs::msg::Twist>

意思是:

复制代码
创建一个发布 Twist 类型消息的发布者

如果换成:

复制代码
rclcpp::Publisher<std_msgs::msg::String>

意思就是:

复制代码
创建一个发布 String 类型消息的发布者

所以:

复制代码
Publisher<Twist>
├── 发布 Twist 消息

Publisher<String>
├── 发布 String 消息

Subscription<Twist>
└── 订阅 Twist 消息

7.3 SharedPtr 智能指针

ROS2 C++ 中经常看到:

复制代码
::SharedPtr

例如:

cpp 复制代码
rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr pub_vel_;
rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr cmd_sub_;
rclcpp::TimerBase::SharedPtr timer_;

SharedPtr 表示共享智能指针

简单理解:

复制代码
SharedPtr
├── 是一种智能指针
├── 可以自动管理对象生命周期
├── 不需要手动 delete
└── ROS2 C++ 中大量使用

为什么发布者、订阅者、定时器都要用 SharedPtr

因为这些对象需要在节点运行期间一直存在。

例如:

复制代码
rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr cmd_sub_;

如果订阅者对象被释放了,节点就无法继续订阅消息。

所以一般会把它们定义成类的成员变量:

复制代码
private:
    rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr pub_vel_;
    rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr cmd_sub_;
    rclcpp::TimerBase::SharedPtr timer_;

7.4 this-> 当前对象指针

ROS2 C++ 中经常看到:

cpp 复制代码
this->create_publisher()
this->create_subscription()
this->create_wall_timer()
this->get_logger()
this->now()

这里的:

复制代码
this

表示当前对象。

复制代码
this->

表示通过当前对象调用成员函数或成员变量。

因为我们的类继承了:

复制代码
rclcpp::Node

所以当前类对象就拥有了 ROS2 节点能力。

例如:

复制代码
this->create_publisher()

意思是:

复制代码
通过当前节点对象创建发布者

this->get_logger()

意思是:

复制代码
通过当前节点对象获取日志器

this->now()

意思是:

通过当前节点对象获取当前 ROS2 时间


7.5 std::bind 和 std::placeholders::_1

订阅者中经常看到:

复制代码
std::bind(&LimoTopicSub::cmd_callback, this, std::placeholders::_1)

可以拆开理解:

复制代码
&LimoTopicSub::cmd_callback
├── 表示 LimoTopicSub 类里面的 cmd_callback 函数

this
├── 表示当前这个 LimoTopicSub 对象

std::placeholders::_1
└── 表示预留一个参数位置,这个参数就是收到的消息 msg

完整意思是:

  1. 当 /cmd_vel 话题收到 Twist 消息时,
  2. ROS2 会把收到的消息作为第一个参数传入 cmd_callback()。

定时器中也会用到 std::bind

复制代码
std::bind(&LimoTopicCmd::timer_callback, this)

意思是:

  1. 把当前对象 this 里面的 timer_callback() 函数,
  2. 绑定给定时器使用。

八、总结

本篇主要梳理了 ROS2 C++ 中最常见、最基础的 rclcpp 写法。

可以用一句话总结:

rclcpp 是 ROS2 提供给 C++ 使用的开发接口。

在 ROS2 C++ 节点中,经常会看到下面这些核心写法:

复制代码
rclcpp::init()
├── 初始化 ROS2

rclcpp::shutdown()
├── 关闭 ROS2

rclcpp::spin()
├── 让节点持续运行并处理回调

rclcpp::Node
├── ROS2 C++ 节点基类

rclcpp::Publisher<T>
├── 发布 T 类型消息

rclcpp::Subscription<T>
├── 订阅 T 类型消息

rclcpp::TimerBase
└── 定时器基础类型

对于初学者来说,最重要的是先把下面这些内容看懂:

复制代码
节点类
├── class Xxx : public rclcpp::Node

发布者
├── rclcpp::Publisher<T>::SharedPtr
├── create_publisher<T>()
└── publish()

订阅者
├── rclcpp::Subscription<T>::SharedPtr
├── create_subscription<T>()
└── callback()

定时器
├── rclcpp::TimerBase::SharedPtr
└── create_wall_timer()

C++ 辅助语法
├── ::
├── <>
├── SharedPtr
├── this->
├── std::bind
└── std::placeholders::_1

如果把 ROS2 C++ 学习过程比作搭积木,那么本篇讲的内容就是最底层、最常用的积木。

无论后面学习 Topic 通信、Service 通信、Action 通信,还是自定义 msg、自定义 srv、自定义 action,都会反复看到这些写法。

所以,本篇的核心作用就是打基础。

先把 rclcpp::Node、Publisher、Subscription、Timer、this->SharedPtrstd::bind 这些最常见写法理解清楚,后面继续学习 ROS2 C++ 的 Service、Action、QoS、Executor、tf2 和底盘控制代码时,就会轻松很多。