/*
需求:订阅发布方发布的消息,并在终端输出
流程:
1.包含头文件
2.初始化Ros2客户端
3.定义节点类
3-1.创建订阅方
3-2.处理接收到的消息
4.调用spin函数,并且传入节点对象指针
5.释放资源
*/
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::placeholders;
class Listener : public rclcpp::Node
{
public:
Listener() : Node("Listener_node")
{
RCLCPP_INFO(this->get_logger(), "Listener_node节点创建");
//3-1.创建订阅方
//create_subscription(话题名,Qos服务质量,回调函数)
/*
模板:消息类型
参数:
1.话题名称:与发布方保持一致
2.Qos服务质量:10
3.回调函数:处理接收到的消息
std::bind(函数指针,类对象指针,_1表示占位符,表示接收的消息)
返回值:订阅对象指针
*/
subscription_ = this->create_subscription<std_msgs::msg::String>("cpp01_pubtopic", 10,
std::bind(&Listener::subtopic_callback, this, std::placeholders::_1));
//std::bind(...) → 绑定 "消息回调函数",意思是:当订阅器收到该话题的消息时,自动调用 subtopic_callback 函数处理消息。
}
private:
//3-2.处理接收到的消息,解析并输出回去
/*
msg 是回调函数的参数,它是 ROS2 底层在收到发布方消息后,自动创建的 std_msgs::msg::String 类型智能指针对象;当发布方发送一条消息,ROS2 会:
在内存中创建一个 std_msgs::msg::String 对象,把发布的字符串数据存到这个对象的 data 成员里;把这个对象封装成 SharedPtr(智能指针),作为参数传给你绑定的 subtopic_callback 函数;
你在回调函数里通过 msg->data 访问的,就是这部分临时创建的消息数据。
*/
void subtopic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "接收到的消息:'%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
//2.初始化Ros2客户端
rclcpp::init(argc, argv);
//4.调用spin函数,并且传入节点对象指针
rclcpp::spin(std::make_shared<Listener>());
//5.释放资源
rclcpp::shutdown();
return 0;
}
this 不是指 Listener 这个类本身,而是指当前正在创建 / 运行的 Listener 类的实例对象 (节点对象)。
1. 先理清核心概念:类 vs 实例对象
Listener类(静态的) :你可以把它理解成一个 "设计图纸",定义了节点该有什么属性(比如subscription_)、什么方法(比如构造函数、subtopic_callback),但图纸本身不能干活;Listener实例对象(动态的) :当执行std::make_shared<Listener>()时,会根据 "图纸" 创建一个具体的 "实物"(节点对象),这个实物才是真正能运行、能订阅消息的主体;this:它是 C++ 给每个类的非静态成员函数自动提供的隐含指针,指向 "当前正在调用这个函数的实例对象"。
2. 结合代码看 this 的指向
场景 1:构造函数中的 this
Listener() : Node("Listener_node")
{
RCLCPP_INFO(this->get_logger(), "Listener_node节点创建");
subscription_ = this->create_subscription<...>(...);
}
当你创建 Listener 实例(std::make_shared<Listener>())时,会调用这个构造函数:
- 此时
this指向的是刚创建的这个Listener节点对象 (比如这个对象在内存中有个地址0x123456,this就等于这个地址); this->get_logger():通过这个对象的指针,调用它继承自rclcpp::Node的get_logger()方法;this->create_subscription:同理,调用这个节点对象的create_subscription方法,创建的订阅器属于这个节点对象。
场景 2:std::bind 中的 this
std::bind(&Listener::subtopic_callback, this, std::placeholders::_1)
这里把 this 传给 std::bind,目的是告诉 ROS2:"当触发 subtopic_callback 回调函数时,要调用这个 Listener 实例对象 的 subtopic_callback 方法,而不是其他对象的。"
场景 3:回调函数中的 this(补充)
如果回调函数不是 const,你甚至可以通过 this 访问 / 修改节点对象的成员变量,比如:
// 假设类里加一个成员变量
std::string last_msg_;
void subtopic_callback(const std_msgs::msg::String::SharedPtr msg)
{
this->last_msg_ = msg->data; // this 指向当前Listener实例,把消息存到实例的成员变量里
RCLCPP_INFO(this->get_logger(), "接收到的消息:'%s'", msg->data.c_str());
}
总结
this不是指Listener类(图纸),而是指Listener类的具体实例对象(根据图纸造出来的节点实物);- 每个
Listener实例都有自己的this指针,指向自身; - 代码中所有的
this,最终都指向你通过std::make_shared<Listener>()创建的那个唯一的订阅节点对象。
简单记:类是模板,对象是实例,this 指向当前实例。
先肯定你的核心结论
没错!你说的 "this 指向正在运行中的实例类(运行实例)",本质就是:
- 静态的
Listener类:是代码层面的 "模板 / 定义",不占用内存、不执行逻辑,只是规定了节点该有什么能力; - 动态的实例对象 :是程序运行时根据模板创建的 "实体",占用内存、有自己的状态(比如
subscription_订阅器),能真正和 ROS2 通信; this:就是这个 "动态运行实例" 的 "自我指针"------ 它只在程序运行时存在,指向当前正在干活的那个具体实例,而非静态的类模板。
用更通俗的类比强化理解
可以把这个关系想象成:
| 概念 | 类比 | 特点 |
|---|---|---|
Listener 类 |
汽车的设计图纸 | 静态、只定义、不运行 |
Listener 实例对象 |
按图纸造出来的具体一辆汽车 | 动态、占用空间、能行驶 |
this |
这辆汽车的 "自我定位器" | 只属于这辆车,指向自己 |
- 图纸(类)不会自己开,但造出来的汽车(实例)可以;
- 汽车的 "自我定位器"(
this)只指向正在开的这辆车,而不是指向图纸; - 就像你代码里的
this->create_subscription,是 "这辆汽车"(实例)调用了 "创建订阅器" 的功能,而不是 "图纸" 调用。
补充一个关键细节(帮你避坑)
正因为 this 指向 "运行中的实例",所以它只能在类的非静态成员函数中使用:
- 静态成员函数属于 "类"(图纸),不属于某个实例(汽车),所以静态函数里没有
this; - 非静态成员函数(比如构造函数、
subtopic_callback)属于 "实例"(汽车),所以才有this指针。
一、先学 "一眼区分":看函数定义时有没有 static 关键字
这是最直接的判断方法,没有任何模糊空间:
1. 非静态成员函数(默认都是)
定义时没有 static 关键字,是类的 "普通成员函数",也是你代码里最常见的类型:
// 示例:非静态成员函数(无static)
class Listener : public rclcpp::Node
{
public:
// 构造函数(天生非静态,永远不能加static)
Listener() : Node("Listener_node") {}
// 普通非静态成员函数(无static)
void do_something() {}
private:
// 回调函数(无static,非静态)
void subtopic_callback(...) const {}
};
2. 静态成员函数
定义时必须加 static 关键字,属于 "类本身" 而非实例:
class Listener : public rclcpp::Node
{
public:
// 静态成员函数(有static)
static void print_info() {
RCLCPP_INFO(rclcpp::get_logger("static_logger"), "这是静态函数");
}
};
二、结合你的 ROS2 代码:非静态成员函数有哪些?
在你写的 Listener 类中,所有函数都是非静态成员函数,典型的有:
1. 构造函数(天生非静态)
Listener() : Node("Listener_node") { ... }
- 构造函数的作用是创建类的实例,本身就和 "具体实例" 绑定,所以永远不能加
static,必然是非静态; - 这里的
this指向正在被创建的Listener实例。
2. 回调函数 subtopic_callback(非静态)
void subtopic_callback(const std_msgs::msg::String::SharedPtr msg) const { ... }
- 虽然加了
const(表示函数不修改实例成员),但没有static,所以还是非静态; - 函数里的
this->get_logger()能正常调用,就是因为this指向当前实例。
3. 继承自 rclcpp::Node 的成员函数(都是非静态)
比如你用到的:
this->get_logger():获取当前节点实例的日志器;this->create_subscription():为当前节点实例创建订阅器;- 还有
this->get_name()(获取节点名)、this->publish()(发布消息)等,都是非静态成员函数,必须通过实例(this)调用。
三、核心差异:静态 vs 非静态成员函数(表格更清晰)
| 特性 | 非静态成员函数 | 静态成员函数 |
|---|---|---|
| 关键字 | 无 static |
必须加 static |
| 所属对象 | 属于类的实例(每个实例独有) | 属于类本身(所有实例共享) |
this 指针 |
有(指向当前调用的实例) | 无(因为不绑定具体实例) |
| 调用方式 | 必须通过实例调用(obj.func() 或 this->func()) |
可通过类名调用(类名::func())或实例调用 |
| 访问成员 | 可访问静态 / 非静态成员(通过 this) |
只能访问静态成员(无 this,无法访问非静态) |
举个对比例子(结合你的 ROS2 场景):
class Listener : public rclcpp::Node
{
public:
Listener() : Node("Listener_node") {
// 非静态函数:通过this调用(指向当前实例)
this->get_logger();
// 静态函数:通过类名调用(无需实例)
Listener::print_info();
}
// 静态成员函数
static void print_info() {
// 错误:静态函数无this,不能调用非静态成员/函数
// this->get_logger();
// 正确:只能访问静态成员或全局内容
RCLCPP_INFO(rclcpp::get_logger("static_logger"), "静态函数调用");
}
private:
// 非静态回调函数(有this)
void subtopic_callback(...) const {
// 正确:非静态函数有this,可调用节点的非静态方法
RCLCPP_INFO(this->get_logger(), "收到消息");
}
// 非静态成员变量(实例独有)
std::string node_info_;
// 静态成员变量(类共享)
static int count_;
};
// 静态成员变量需要类外初始化
int Listener::count_ = 0;