机器人操作系统ROS:从入门到放弃(四) C++类,命名空间,模版,CMakeLists介绍(这篇我很推荐,讲得很到位详细!)

本文概述了在ROS中如何理解类(class)、命名空间(namespace)和模版(template)的基本概念,通过实例说明它们在代码中的应用,以及与之前ROS代码的关联。重点在于这些概念如何影响消息的发布和订阅行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

命名空间,模板都讲到了,这些是我最近意识到的,在普罗米修斯代码里。

转载自:机器人操作系统ROS:从入门到放弃(四) C++类,命名空间,模版,CMakeLists介绍 - 简书

机器人操作系统ROS:从入门到放弃(四) C++类,命名空间,模版,CMakeLists介绍

陈瓜瓜_ARPG

机器人操作系统ROS:从入门到放弃(四) C++类,命名空间,模版,CMakeLists介绍

由于下一讲要讲到怎么在类中pub和sub消息.那么考虑到有些同学对类不甚熟悉.我们稍微回顾一下.但关于类网上一查其实一大堆东西,而且都是从入门讲起.所以我这儿肯定不会重复书写那些内容.要介绍的几个东西,其实本来要用得好的话蛮复杂,我们只会涉及到皮毛,重心会放在和之前的代码比较,以了解之前我们之前三讲的很多语法为什么可以那么写.
比如上一讲的geometry_msgs::PoseStamped的对象msg包含成员变量header和pose,heaer包含成员变量stamp等,为什么我们就可以使用msg.header.stamp这种语法来获取类型为time的变量?
再比如std_msgs::Int8这种语法怎么来的,中间那个::表示什么意思,以及它前后的std_msgs和In8有什么区别.
再比如我们定义ROS publisher时

ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

为什么通过<std_msgs::String>这种语法来定义要发送的对象?
这三个比如分别涉及到类,命名空间和模版.对语法熟悉或者不想究其所以然的同学可以跳过这一章直接进入下一章的讲在类中pub和sub消息.

这一讲假设大家对函数,参数,循环等最基本的C++的东西已经掌握了.如果这些不清楚那么用C++操作ROS确实不太合适哈哈.

类(class)

同样类的作用和意义我就不详细阐释了,网上一抓一大把,他们的基本意义大家可以上网搜索.简单来讲,定义了类之后我们可以创建它的对象.对类和其对象直接操作是c++最重要的东西之一.直接开始例子.打开一个terminal,输入下面的内容

mkdir ~/C++Test
cd C++Test
mkdir classTest
mkdir namespaceTest
mkdir templateTest

咱们创建一个叫C++Test的文件夹,再创建三个用于测试三种东西的子文件夹.之后,在classTest文件夹下创建一个叫classBasic.cpp的文件和一个叫CMakeLists.txt的文件.在classBasic.cpp中输入下面内容.

#include <iostream>

class poorPhd{
public:
    /*define constructor*/
    poorPhd(){
        std::cout<<"we create a poor phd class"<<std::endl;
    }

    /*public member variable*/
    int hairNumber = 100;

    /*public member function*/
    int getGirlFriendNumber(){
        return girlFriendNumber;
    }

private:
    /*private member variable*/
    int girlFriendNumber = 0;
};

int main(){
    /*define the object*/
    poorPhd phd;//will use constructor function 
 
    /*call the public memberfunction*/
    std::cout<<"girlFriendnNumber is "<<phd.getGirlFriendNumber()<<std::endl;

    /*change tha value of member variale*/
    phd.hairNumber = 101;

    /*call the member variable*/
    std::cout<<"hairNumber is "<<phd.hairNumber<<std::endl;

    /*define class pointer*/
    poorPhd *phdPointer;

    /*assign the pointer to an object*/
    phdPointer = &phd;

    /*call the member variable*/
    std::cout<<"use pointer, hair number is "<<phdPointer->hairNumber<<std::endl;
}

逐行解说.
1:#include<> 包含头文件,这样可以使用std::cout<<...std::endl;

2:class poorPhd 定义了一个叫poorPhd的类.类后跟这宗括号{}.宗括号中的内容为类的内容.

3:public 加冒号之后的内容,即为公有.公有范围内定义的函数为公有成员函数,变量为公有成员变量.

4:poorPhd(). 这个函数称为构造函数(constructor function).在类创建时,会自动调用.构造函数的名字和类的名字必须一样并且没有返回值.

5:int hairNumber = 100. 定义了一个int类型公有成员变量,赋值100.

6:int getGirlFriendNumber(). 定义了一个返回值为int的函数,该函数会返回私有成员变量girlFriendNumber.

7:private加冒号之后的内容,即为私有.私有范围内定义的函数为私有成员函数,变量为私有成员变量.

8: int girlFriendNumnber=0. 定义了一个int类型的私有成员变量girlFriendNumber并赋值为0

main函数中
9: poorPhd phd  创建了一个类的对象(object),名字叫phd.每一个类,要想实际被使用,都需要创建一个对象.对象会拥有之前我们在类中定义的所有东西.所谓拥有,即是可以调用他们.对象的数量是没有限制的,并且他们之间不会干扰.你还可以用类似方法创建一个名字加abc的对象,它也会拥有poorPhd这个类的全部东西.
对象在创建时,会自动调用构造函数.

10:std::cout....phd.getGirlFriendNumber()<<std::endl;
类对象调用成员函数或者成员变量的方法是对象名.成员公有成员可以在类的定义外使用这种方式直接调用,私有成员是不可以被直接调用的.所以如果我们使用phd.girlFriendNumber就会报错.因为在类外,不可以直接调用私有成员变量.那有时候我们仍然想看到或者修改私有成员变量怎么办呢?那么我们可以写类似于这个gerGirlFriend的公有成员函数.公有成员函数定义在类中,所以它可以使用私有成员变量,并把变量的值作为返回值,这样我们就得到可私有成员变量的值.
为什么要分私有公有呢?有时候我们写了一个类,并不想其中所有东西都被使用者使用,比如我们有了造车相关技术,所有这些技术和在一起,就是类.具体实现,就是我们造了一辆辆车子.每一辆车子就称为对象.每一辆车子都有相同的内容,但是他们互不干扰.我们只想用户了解刹车,油门等东西.并不想用户了解车子内部构造.那刹车油门在这儿就是公有,而车子内部构造就是私有.如果用户实在想获取内部构造,用户可以去汽车销售店了解些相关资料,销售店就相当于咱们写的那个get...函数接口,架起用户和类私有成员友谊的桥梁.当然,如果有些内容特别私密,我们并不想用户了解它的相关资料,就不写那个get...函数就行了.

  1. phd.hairNumber = 101;
    为公有成员变量赋值101.

12.std::cout<<...phd.hairNumber...
调用公有成员并print出来.

13.poorPhd *phdPointer 创建一个类的指针.类的指针被创建时不会调用构造函数.它需要指向一个对象.

14.phdPointer = &phd 刚才创建的对象的地址赋值给指针,这个指针就有了phd对象的所有内容.

  1. ...phdPointer->hairNumber... 类指针调用类的成员的唯一不同之处就是使用指针名->成员调用而不是对象名.成员调用.

和之前写的ROS代码的联系: 之前我们定义过std_msgs::Int8 msg,msg即是类Int8的对象.我们通过查看roswiki std_msgs/Int8 Documentation 得知Int8包含类型为int8的成员变量data,所以我们通过msg.data使用这个成员.

写好文件后退出保存,打开之前建立的CMakeLists.txt文件.输入以下内容.

project(class_test)

cmake_minimum_required(VERSION 2.8)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAG} -std=c++11 -Wall")

add_executable(classBasic classBasic.cpp)

这基本上算是一个最简单的CMakeLists.txt文件了.CMakeLists.txt是用来编译C++文件的.
第一行表明了项目名称.
第二行输入CMake使用的最小版本号,一般是2.8以及以上.
第三行设定编译器.使用c++11.虽然我们的项目没用到c++11但是考虑到如今c++已经被普遍使用了,所以最好加上.我们在ROS的CMakeLists里注释过这个内容add_compile_options(-std=c++11)达到的也是使用c++11编译的效果.
第四行指定要编译的文件.要编译的文件是classBasic.cpp,编译后的可执行文件名字叫classBasic.
写完上面的内容后,保存退出.
在terminal中cd 到classTest这个文件夹输入下面的内容

mkdir build
cd build
cmake ..
make

第一二行命令创建一个叫build的文件夹并进入
第三行命令使用是使用cmake命令并通过..表示使用上一个文件夹的CMakeLists.txt.执行这行命令之后我们写的CMakeLists就会产生一系列的文件在build中,其中一个是Makefile.其他的这里不作介绍
第四行命令是使用makefile.makefile的作用就是直接编译你在CMakeLists里设定好的文件了.
建立一个build文件夹不是必须的但是推荐,因为你看到build里有一系列编译CMakeLists.txt里产生的文件,你以后要删除或者修改他们会比较方便,不至于和其他文件混在一起.
执行完上面的命令后,你会看到多了一个叫classBasic的文件没有后缀,这就是我们的可执行二进制文件了.使用./classBasic执行后得到下面的输出

we create a poor phd class
girlFriendnNumber is 0
hairNumber is 101
use pointer, hair number is 101

请对应源代码一行行查看输出为何如此.

上一章我们说过有这样一段话msgs_header.stamp调用stamp,stamp.sec调用sec得到epoch的时间,那么msgs_header.stamp.sec就可以获取当前的时间,秒为单位.写段话之前我们创建了Header的对象msg_header,并通过ros wiki知道了该对象包含数据成员stamp,stamp包含数据成员sec,然后我们我们可以用这种msg_header.stamp.sec来调用sec这个数据成员.这种数据之间看起来的连续性具体是怎么实现的呢?
咱们在之前创建的classTest文件夹下再创建一个新的文件叫 classBasic2.cpp.并输入下面的内容.

#include <iostream>

class poorPhd{
public:
    /*define constructor*/
    poorPhd(){
        std::cout<<"we create a poor phd class"<<std::endl;
    }

    /*public member variable*/
    int hairNumber = 100;

    /*public member function*/
    int getGirlFriendNumber(){
        return girlFriendNumber;
    }

private:
    /*private member variable*/
    int girlFriendNumber = 0;
};

class master1 {
public:
    /*define constructor*/
    master1(){
        std::cout<<"we create a master class"<<std::endl;
    }
    /*member variable*/
    poorPhd future;
};


int main(){
    /*define the object*/
    master1 mStudent1;

    /*use inheritance*/
    std::cout<<"hairNumber of master student 1 is "<<mStudent1.future.hairNumber<<std::endl;
}

poorPhd类和上一个文件完全一样,我们新添加了一个类叫master1.master1同样有一个构造函数.另外它有一个成员变量,这个成员变量是poorPhd类型的对象future.那么在main函数中,定义了master1的对象mStudent1.咱们就可以用mStudent1.future调用变量future,再由于future是poorPhd类型的变量,所以可以用future.hairNumber调用hairNumber.连在一起就可以通过定义msater1的对象却最终调用了poorPhd的成员变量了.
保存退出后,在CMakeLists.txt中添加下面的内容.

add_executable(classBasic2 classBasic2.cpp)

terminal中进入classTest/build文件加输入

cmake ..
make

这时候就多了一个二进制文件classBasic2,执行该二进制文件你会看到

we create a poor phd class
we create a master class
hairNumber of master student 1 is 100

从这个输入可以看出,创建master1的对象mStudent1的时候c++会首先初始化它的成员变量,所以咱们先得到的是create a poor phd class,之后再调用了构造函数.
类还有很多很多的内容,就靠大家自己取学习了,咱们这儿只是简单地介绍了和前面的代码联系的部分.

命名空间(namespace)

你肯定使用过命名空间,基本上每一个写c++的人都会用过using namespace std这条语句.这条语句代表使用命名空间std.达到的效果是,例如你要使用cout语句在屏幕上打印什么东西,如果没有std,你需要输入的是

std::cout<<"....."<<std::endl;

如果你使用了using namespace std这条语句,那么你就只需要下面的内容打印语句

cout<<"...."<<endl;

但是你如果没写过大型程序的话,可能没有机会自己写过命名空间命名空间一般是用来避免重命名的.大型的库里面一般定义了很多类,无数的函数.不同的大型的库之间很可能会有函数甚至类的命名重复,这会造成很大的麻烦.
namespace的命名语法也很简单

namespace name{
    //内容
}

下面这个程序简单地展示了两个命名空间里定义相同名字的类,并分别使用两个类的简单程序.

#include <iostream>

/*define a phd namespace*/
namespace phd {

    /*define a student class in phd namespace*/
    class student{
    public:
        student(){
            std::cout<<"create a student class in phd namespace"<<std::endl;
        }
        int graduateYear = 5;
        int hairNumber   = 100;
    };
}

/*define a master namespace*/
namespace master{

    /*define a student class in master namespace*/
    class student{
    public:
        student(){
            std::cout<<"create a student class in master namespace"<<std::endl;
        }
        int graduateYear = 2;
        int hairNumber   = 10000;
    };
}

int main(){

    /*create an object of student class, in phd namespace*/
    phd::student     phdStudent;

    /*create an object of student class, in master namespace*/
    master::student  masterStudent;

    std::cout<<"phd normally graduate in "<<phdStudent.graduateYear<<" years"<<std::endl;

    std::cout<<"master normally graduate in "<<masterStudent.graduateYear<<" years"<<std::endl;
}

上面的这个程序定义了两个命名空间,一个叫phd,一个叫master,这两个命名空间拥有一个类,类名都叫student
定义命名空间中的类的对象的方法是命名空间名::类名 对象名::被称为作用域符号(scope resolution operator).在main函数中我们定义了phd命名空间下的student类的对象phdStudent和master命名空间下的类student的对象masterStudenrt. 后面的两行各自输出了成员变量graduateYear
在我们之前的ros程序中,遇到了两个命名空间,一个是std_msgs,另一个是geometry_msgsInt8, Float64等都是std_msgs这个命名空间下的类,PoseStamped等是geometry_msgs这个命名空间下的类.
回到上面的程序我们在定义完phd这个命名空间后,可以使用using namespace phd,这样在main函数中我们可以不使用phd::来定义一个phd下的student类的对象,直接student phdStudent即可.同样,如果我们添加using namespace master,我们也可以直接使用student masterStudent来定义msater命名空间下student类的对象.
但是如果在程序中同时添加了

using namespace phd;
using namespace master;

这时候你在main函数中写student object_name就肯定会报错.因为电脑无法知道你要使用的student类是属于哪个命名空间的.所以一般为了图方便,在我们确定没有类名会重复时,我们添加using namespace ...这一行在定义完头文件之后,这样我们就可以省去在定义类时一直使用namespace_name::类名这种格式命名.但是有些时候如果两个库很有可能有相同的类名,就不要使用using namespace ...,不然很有可能造成程序的误解.

写好上面的程序后和咱们写classBasic.cpp的过程完全一样的步骤,创建CMakeLists.txt和一个build文件夹进行编译.

可能有的读者会问那如果命名空间的名字都重复了呢?你就删掉其中一个程序把 = = ....
同样,命名空间有的是学问,有兴趣的同学自行研究.

模版(Template)

模版这个东西,你如果是c++的使用者,那必定也接触过.为什么这么说呢?当你定义一个std::vector的时候,你就已经使用了模版了.但是你可能没自己写过模版(这种情况好像和namespace有点相似).
模版是为了避免重复定义同样功能的函数而开发的.
打个比方,你现在要实现平方一个数的函数.很简单,类似于下面这样

#include <iostream>

int square(int a){
    return a*a;
}

int main(){
   double x = 5.3;
   std::cout<<"the square of "<<x <<" is "<<square(x)<<std::endl;
}

这个程序有个很明显的缺点,编写函数或者使用变量时,都必须先指定类型,由于c++函数形参类型和返回值已经指定为int类型了,你只能传int类型进去,如果传double类型的变量进去,变量会被强制转换截断为int类型.而且只能return整型的变量.所以你只能得到25.
基本的解决方法是函数的重载,即我可以命名相同的函数但是变量类型或者个数不同以实现对不同输入的处理.类似于下面这样

#include <iostream>

int square(int a){
    return a*a;
}

double suqare(double a ){
    return a*a;
}

int main(){
   double x = 5.3;
   std::cout<<"the square of "<<x <<" is "<<square(x)<<std::endl;
}

这样调用square(x)时会自动匹配形参相同的函数.我们可以得到5.3的平方.但是可以想象,如果我有很多不同类型的变量要传入,我就得写好多不同的除了变量类型不同,其他的一模一样的函数了!有没有一种方法,形参什么类型都是可以的呢?
模版应运而生.模版的定义方式是

template <typename T>

或者

template <class T>

定义完之后后面紧跟要实现的函数或者是类.这个class不是我们之前理解的那种class了.这儿的class和typename作用完全一样,表示定义了一个新的类型T.这个新的类型具体是什么不知道,要等我们具体使用时程序根据传入的类型自行判断.
咱们先上代码,实现数字平方相同的功能.

#include <iostream>

template <typename T>
T square(T a){
    return a*a;
}

int main(){
    double x = 5.3;
    std::cout<<"square of "<<x<<" is "<<square(x)<<std::endl;
}

现在你无论传什么类型的数据进去,都会得到它的平方.sqaure指定的函数形参和返回值类型都为T.可以这样理解,现在当我们传入一个double类型的变量时,T就会自动变成double,传入int时,T就自动变为int.
下面来一个稍微复杂一点的例子的.实现两个向量的相加(好像也不怎么复杂...). 向量在c++里是不能直接相加的.我们定义向量时要指定向量元素的类型.比如std::vector<int> astd::vector<double> b等.和上一个例子一样,为了避免传入重载函数,我们使用模版.代码如下

#include <iostream>
#include <vector>

template <typename T, typename U>
U addVector(T vec1, U vec2){
    
    U result;

    if(vec1.size()!=vec2.size()){
        std::cout<<"cannot add two vector, they must be the same length. Return a null vector"<<std::endl;
        return result;
    }

    for(int i = 0; i<vec1.size(); i++){
        result.push_back(vec1[i]+vec2[i]);
    }
    return result;
}

int main(){
    std::vector<int> vec1 = {1,2,3};
    std::vector<double> vec2 = {4.0,5.0,6.0};

    auto addVec = addVector(vec1,vec2);

    for(auto i:addVec)
        std::cout<<i<<",";

    std::cout<<std::endl;
}

我们的tempalte定义了两个类型,一个叫U,一个叫T.为什么要定义两个呢?因为前面说过模板定义的具体类型在使用时确定的,在主函数中我们要加两个vector,一个是int类型的,作为第一个参数传入addVector,那么T就会是std::vector<int>,而第二个参数是double类型的向量,作为第二个参数传入函数后U就会相当于std::vector<double>,函数返回的类型也是U.
程序主函数第三行使用了auto这个关键字.使用c++11编译才可使用auto.这个是很有用的关键字.auto会自动分配被它定义的对象的类型,根据赋值的变量的类型.addVector返回的是U,在这个程序里也就是std::vector<double>了.那么auto会自动让addVec称为dpuble类型的vector.
主函数第四行的for循环采用的是有别于我们常用的for循环的形式.

for(auto i:addVec)

其中i:addVec的作用是把addVec中的元素依次赋值给i,这就要求i的类型得和addVec中的元素的类型相同,不过有auto的帮助,我们也就不用管这么多了,把i的类型定义为auto,那么程序会自动让i成为addVec中要赋值给i的元素的类型,这儿也就是double了.
说了这么多,还没到我们最初想讲的,那就是类似于std::vector<int>和我们使用ros的时候定义的advertise<std_msgs::String>这种类型的语法是怎么来的?首先根据命名空间那儿的学习我们知道std肯定是代表命名空间的名字了,vector是一个类,而<int>则来源于模版.如果我们使用模版定义了一个类,则会出现类似的内容.还是用简单的square函数来举例.我们来建立一个简单的sqaure类.

#include <iostream>

template <typename T>
class square{
public:
    T a;
    /*constructor function will store _a*_a as public member a*/
    square(T _a){
        a = _a*_a;
    }
};


int main(){
    double x = 5.5;
    square<double> test(x);
    std::cout<<"the square of "<<x<<" is "<<test.a<<std::endl;
}

在声明了模版之后紧接着我们声明了一个类,类的公有成员函数是一个类型为T的值a.主函数中,在我们声明模版下定义的类的对象时,我们需要在<>之中表明T的类型.再这之后才能定义对象.即普通的类的对象的定义格式如下

类名 对象名(构造函数参数)

模版下的类的对象定义的格式就是

类名<模版变量类型> 对象名(构造函数参数)

main函数第二行的这种定义方法,就类似于我们std::vector<int> ABC这种定义方法了,后者多的不过是在命名空间下定义了模版.然后再在模版下定义类.

总结

这一讲我们粗略地涉及到c++中几个简单又庞杂的系统,类,命名空间和模版,在我们平常使用的语法中多多少少都出现过他们的影子.只是我们自己不经常定义罢了.学会使用他们对建立庞杂的代码系统很有帮助.我们还介绍了最简要的CMakeLists的需要包含的内容.下一讲咱们回到ROS.在这一讲的基础之上,讲解在ros的类中发布/接收消息

<think>好的,我现在要处理用户提供的这个链接的问题。用户给出的链接是关于《ROS 2机器人开发入门到实践》的1.4.4节,具体是在Linux中编写C++代码的教程视频,来自哔哩哔哩。但根据用户的要求,我需要先确认自己是否能够访问该链接的内容。由于我无法直接访问外部链接或查看视频内容,因此需要基于已有的知识和常见的ROS 2开发流程来推测和解释这部分内容。 首先,用户可能希望了解该视频中涉及的主要内容,或者在学习过程中遇到问题需要帮助。由于视频标题提到了在Linux中使用C++进行ROS 2开发,这通常包括环境配置、创建ROS 2包、编写C++节点、编译和运行等步骤。因此,我的回答需要涵盖这些方面,结构清晰,分步骤解释,确保用户能够理解并解决可能遇到的问题。 接下来,我需要考虑用户可能的疑问点。比如,如何安装ROS 2环境,创建工作区,创建包,编写节点代码,修改CMakeLists.txt和package.xml,编译以及运行节点。这些都是ROS 2开发的基础步骤,但新手可能会在配置环境或编译过程中遇到问题,比如依赖项缺失、编译错误、节点无法运行等。 同时,要注意用户提到的系统级指令,确保数学公式的正确格式,但在这个问题中可能涉及较少数学内容。不过如果有涉及到任何代码块或命令,需要用正确的格式呈现,比如使用代码块标记。但根据用户提供的系统指令,这里可能需要将行内代码用$...$包裹,不过通常代码块在Markdown中使用反引号,但用户的要求可能有所不同,需要仔细检查。 另外,用户要求使用中文回答,结构清晰,逐步解决问题。因此,我需要将ROS 2开发的基本流程分解成几个步骤,每个步骤详细说明,可能遇到的问题及解决方法,并确保内容真实可靠,符合ROS 2的最佳实践。 现在,我需要验证自己对于ROS 2中C++节点编写的流程是否正确。例如,创建工作区使用colcon,创建包使用ros2 pkg create,编写节点时需要继承rclcpp::Node,并在CMakeLists.txt中添加可执行文件和依赖。这些步骤是否正确?是的,ROS 2的常规流程确实如此。 另外,编译时使用colcon build,运行节点需要先source安装文件,然后使用ros2 run。这些步骤是否准确?是的。可能的错误包括没有正确source环境,或者CMakeLists.txt配置错误,比如遗漏ament_target_dependencies或install部分,导致编译后的节点不可执行或未被安装。 用户可能遇到的问题可能包括:找不到包、编译错误(比如缺少依赖声明)、节点无法运行(权限问题或路径问题)。需要针对这些常见问题给出解决方法,例如检查package.xml中的依赖是否正确,确认编译后的install目录是否被正确source,以及确保节点文件有可执行权限。 最后,总结这部分内容,强调实践的重要性,并建议用户参考官方文档或社区资源解决更具体的问题。由于无法观看视频,我的回答需要基于通用知识,但应尽量覆盖视频可能涉及的主题,确保用户得到有价值的指导。</think>关于《ROS 2机器人开发入门到实践》1.4.4节“在Linux中编写C++代码”的内容,由于无法直接访问视频,以下基于ROS 2开发通用流程和常见实践,为你梳理关键步骤和知识点: --- ### **1. 环境准备** - **安装ROS 2** 确保已正确安装ROS 2(如Humble Hawksbill或Foxy Fitzroy),并通过以下命令验证环境配置: ```bash source /opt/ros/<distro>/setup.bash # 如 source /opt/ros/humble/setup.bash ``` - **创建工作区** 创建并初始化ROS 2工作区: ```bash mkdir -p ~/ros2_ws/src cd ~/ros2_ws colcon build ``` --- ### **2. 创建ROS 2包** - **生成包模板** 使用`ros2 pkg create`创建C++包,指定依赖项(如`rclcpp`): ```bash cd ~/ros2_ws/src ros2 pkg create my_cpp_pkg --build-type ament_cmake --dependencies rclcpp ``` --- ### **3. 编写C++节点** - **代码结构** 在`src`目录下创建C++文件(如`my_node.cpp`),内容通常包含: - 继承`rclcpp::Node` - 初始化节点、发布者/订阅者、服务等 - 主函数中初始化ROS 2并运行节点 示例代码框架: ```cpp #include "rclcpp/rclcpp.hpp" class MyNode : public rclcpp::Node { public: MyNode() : Node("my_node") { // 初始化逻辑(如定时器、话题订阅) } }; int main(int argc, char** argv) { rclcpp::init(argc, argv); auto node = std::make_shared<MyNode>(); rclcpp::spin(node); rclcpp::shutdown(); return 0; } ``` --- ### **4. 配置编译文件** - **CMakeLists.txt** 在包的`CMakeLists.txt`中添加可执行文件及依赖: ```cmake add_executable(my_node src/my_node.cpp) ament_target_dependencies(my_node rclcpp) install(TARGETS my_node DESTINATION lib/${PROJECT_NAME}) ``` - **package.xml** 确保声明依赖项(如`rclcpp`): ```xml <depend>rclcpp</depend> ``` --- ### **5. 编译与运行** - **编译包** 在工作区根目录执行编译: ```bash cd ~/ros2_ws colcon build --packages-select my_cpp_pkg ``` - **运行节点** Source环境后启动节点: ```bash source install/setup.bash ros2 run my_cpp_pkg my_node ``` --- ### **6. 常见问题** - **依赖缺失** 若编译报错未找到头文件,检查`package.xml`和`CMakeLists.txt`是否声明依赖。 - **节点未找到** 确保编译后执行`source install/setup.bash`加载环境变量。 - **权限问题** 若节点无法执行,尝试添加可执行权限: ```bash chmod +x ~/ros2_ws/build/my_cpp_pkg/my_node ``` --- ### **总结** 本节核心是掌握**ROS 2 C++节点的开发流程**,重点在于包创建、代码编写、编译配置与调试技巧。建议结合官方文档(如[ROS 2 Tutorials](https://docs.ros.org/en/humble/Tutorials.html))深入学习。若视频中有特定操作细节(如调试工具使用),可进一步补充说明!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值