glog记录程序崩溃及扩展
abstract Welcome to my blog, enter password to read.
Read more
ROS2的package.xml和cmake

创建包,相关文件

创建C++包: ros2 pkg create --build-type ament_cmake --node-name node_name package_name

指明依赖项: ros2 pkg create --build-type ament_cmake --node-name node_name package_name --dependencies rclcpp std_msgs

创建python包: ros2 pkg create --build-type ament_python --node-name node_name package_name

如果在创建包时,忘了指定依赖项,需要手动修改package.xmlCMakeLists.txt

比如在package.xml<buildtool_depend> 之后添加

1
2
<depend>rclcpp</depend>
<depend>std_msgs</depend>

CMakeLists.txt添加
1
2
3
4
find_package(rclcpp  REQUIRED)
find_package(std_msgs REQUIRED)

ament_target_dependencies(my_node rclcpp std_msgs)

两个依赖项不能都放进find_package里面,否则报错

工作空间

  • build目录放的是中间文件.例如调用CMake时,每个包都会生成一个子文件夹.

  • install目录是放包的安装文件.默认情况下,每个包会被安装到一个独立的子目录.

  • log目录包含每次执行colcon的各种日志信息.

对比catkin,没有了devel目录


package.xml中包含该功能包的依赖信息,它可以帮助编译工具colcon确定多个功能包编译的顺序。

CMakeList.txt当中,必须有

1
2
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

ament_cmake是cmake的增强版

find_package(rclcpp_lifecycle REQUIRED)用于生命周期管理,平时可以不添加

例如package名称为project(first_node),必须修改的部分如下

1
2
3
4
5
add_executable(test_node src/test.cpp)
ament_target_dependencies(test_node rclcpp std_msgs)
install(TARGETS test_node DESTINATION lib/install_node)

target_link_libraries(test_node behaviortree_cpp_v3)

  • add_executable不必解释

  • ament_target_dependencies是官方推荐的方式去添加依赖项。它将使依赖项的库、头文件和自身的依赖项被正常找到。如果不添加库名称,include头文件时会找不到

  • install是安装库的语句。它将在工作空间生成文件 install/first_node/lib/install_node/test_node

  • ament_package() 最后一句,不要修改。项目安装是通过ament_package()完成的,并且每个软件包必须恰好执行一次这个调用。ament_package()会安装package.xml文件,用ament索引注册该软件包,并安装CMake的配置(和可能的目标)文件,以便其他软件包可以用find_package找到该软件包。由于ament_package()会从CMakeLists.txt中收集大量信息,因此它应该是CMakeLists.txt中的最后一个调用。

可选项

ament_export_dependencies(${dependencies})

这句会将依赖项导出到下游软件包。这样该库使用者也就不必为那些依赖项调用find_package了。


行为树安装和配置

尽量不要用行为树,除非公司里有个人,本来就很擅长行为树,那么可以交给他,其他人没必要参与了。行为树入门难,原因不是它本身多么复杂,而是网上的资料太少,尤其groot的资料更少,全靠自己摸索,一个并不复杂的问题,摸索一整天。有不少资料是游戏设计方面的,用于机器人领域的开源方案目前只有ROS2。资料这么少,肯定是有原因的。

最近从行为树版本3切换到版本4,发现有一些变化,又走了一遍坑之后,我还是认为最好不要用行为树。

准确地说,是BehaviorTree.CPPGroot太垃圾,行为树本身在游戏行业已经用了很多年,不需要讨论其可用性。

行为树的优缺点

优点:

  • 行为逻辑和状态数据分离(减少了耦合),节点写好以后可以反复利用
  • 线性的方式扩展,所以扩展性好。添加或删除一个功能,可能只需添加或删除一个节点
  • 黑板变量用起来很灵活
  • 官方提供了与ROS2的接口,适合机器人的业务开发,而且是两套接口。
  • 使用groot编辑,直观容易理解。但是Groot又有一大堆缺点,我在另一篇文章有总结

行为树的缺点

  • 一个简单的操作都需要定义Action或Condition节点,行为树很容易做的复杂化
  • 每次从根节点开始逻辑,若树的很庞大,效率将变得低下,占用CPU更多,注意行为树的刷新频率
  • 黑板变量,有的人在XML文件里赋值,有的人在C++里赋值,我如果用groot2看行为树,无法看到C++里赋值的情况
  • switch默认最多6个case,不能直接修改xml文件里的Switch,C++不识别。要想实现更多case,只能自己实现新的 Control 节点
  • C++的逻辑风格是大多数人熟悉的,比如if else, while, 设计模式等等,一个人看另一个人写的代码并不难上手。但是行为树的逻辑类型很多,有的人还会自己定义控制节点和修饰节点。同样的功能,不同人实现的逻辑会很不同,互相难理解。我之前看两个人写的行为树,风格大为不同,很难理清思路。
  • Timeout节点疑似不能生效

网上找不到修饰节点Timeout的使用方法,从API里找到了说明:The TimeoutNode will halt() a running child if the latter has been RUNNING for more than a give time. The timeout is in milliseconds and it is passed using the port “msec”.

但是我反复试验,发现这个节点总不生效,它修饰的节点运行时间早就超过Timeout规定时间了,但是不会停止

状态机的缺点

  1. 在项目中可能增加新状态、减少状态或者改变状态之间的迁移关系,如果状态越来越多,一点小修改都会产生很大的工作量,代码中会出现大量的判断跳转,耦合性太强。代码的逻辑会变得臃肿
  2. 与行为树最显著的区别:状态与执行内容是绑定在一起的。当执行内容需要在多个状态中执行时,各个状态下都需要放置执行内容的逻辑。当业务逻辑代码分散在各处时就不太好维护了

行为树与FSM的权衡

  1. 状态机是事件驱动的,对于状态机来说,每个时刻它都处于某种“状态”中,等待某个事件(转换)的发生。如果事件没有发生,那么继续保持在这个状态;
  2. 行为树都是自顶向下采用轮询的方式实现的。不太适合实现事件驱动的行为。例如,机器人在行走过程中陷入困境了,这明显是发生了事件,用状态机表示更合适。其实通过并行节点和修饰节点结合能够处理这种情况。

安装

安装依赖项,然后去github的release里下载最新的版本

1
2
3
4
5
sudo apt-get sqlite3
sudo apt-get install libzmq3-dev libboost-dev
sudo apt-get install libboost-coroutine-dev # 需要用到协程

sudo apt-get install qtbase5-dev libqt5svg5-dev libzmq3-dev libdw-dev

cmake老三样编译安装。

普通的cmake设置如下

1
2
3
4
5
6
7
8
9
10
cmake_minimum_required(VERSION 3.10.2)

project(simple_bt)

set(CMAKE_CXX_SaTANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(behaviortree_cpp)

add_executable(${PROJECT_NAME} "simple_bt.cpp")
target_link_libraries(${PROJECT_NAME} BT::behaviortree_cpp)

ROS2环境的设置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
cmake_minimum_required(VERSION 3.8)
project(test_node)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

set(CMAKE_CXX_STANDARD 17)

add_compile_options(-Wextra -Wpedantic -Wno-unused-parameter -g)

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(ament_index_cpp)
find_package(behaviortree_cpp)

INCLUDE_DIRECTORIES(/home/user/catkin_ws/src/test_node/include)

add_executable(test_node src/test.cpp)

ament_target_dependencies(test_node
rclcpp
behaviortree_cpp
${BTCPP_LIBRARY}
)

install(TARGETS
test_node
DESTINATION lib/${PROJECT_NAME}
)

ament_package()

package.xml如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>test_node</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="user@todo.todo">zzp</maintainer>
<license>TODO: License declaration</license>

<buildtool_depend>ament_cmake</buildtool_depend>

<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>behaviortree_cpp</depend>

<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>

<export>
<build_type>ament_cmake</build_type>
</export>
</package>

ROS2行为树动态库默认安装在/opt/ros/galactic/lib/libbehaviortree_cpp_v3.so,头文件在/opt/ros/foxy/include/behaviortree_cpp_v3


clang-format的使用

统一的代码规范对于整个团队来说十分重要,通过git/svn在提交前进行统一的 ClangFormat 格式化,可以有效避免由于人工操作带来的代码格式问题。

1
2
3
4
5
6
7
// 以 LLVM 代码风格格式化main.cpp, 结果输出到 stdout
clang-format -style=LLVM main.cpp
// 以 LLVM 风格格式化 main.cpp, 结果直接写到 main.cpp
clang-format -style=LLVM -i main.cpp

// 以 Google 风格格式化 main.cpp, 结果直接写到 main.cpp
clang-format -style=google -i main.cpp

除了LLVMGoogle外,还有 Chromium, Mozilla, WebKit

  • 批量格式化代码

find . -path '*/src/*.cpp' -o -path '*/include/*.h' ! -name 'sigslot.h' | xargs clang-format -style=file -i

在使用时,出现一个奇怪的bug,clang-format把我修改的文件的所有者都改成了root,再修改时还得先把用户改回来,极其不方便,不过我是在VMware里遇到的bug

参考: clang-format的介绍和使用


colcon编译

安装 colcon : sudo apt install python3-colcon-common-extensions。 ROS2的build没有了ROS1中的devel概念

与ROS1不同,如果一个包不放在src目录,而放在工作空间目录,colcon也会编译它。

  • colcon build     编译所有包

  • colcon build —packages-select pkg     只编译一个包

colcon build --packages-select 指令并不会编译该包的依赖,往往会报错。可以用下面这条指令进行包和其依赖编译

1
colcon build --packages-up-to   package_name
  • colcon build —cmake-args -DCMAKE_BUILD_TYPE=Release    

不编译测试单元

colcon test --packages-select YOUR_PKG_NAME --cmake-args -DBUILD_TESTING = 0

运行编译的包的测试

colcon test

  • 允许通过更改src下的部分文件改变install,这样每次修改Python脚本时不必重新 build

colcon build --symlink-install

  • colcon build —symlink-install pkg   

  • colcon build —symlink-install —packages-ignore pkg    

减小colcon的CPU占用

指定并行的线程数

1
colcon build --symlink-install --parallel-workers 1

不使用并行编译

1
colcon build --symlink-install  --executor sequential

两种编译不要混合用,否则每次都会重新编译

显示CMake中的MESSAGE

colcon build —event-handlers console_direct+ —packages-select test_node

console_direct 换成 console_cohesion 也可以

如果不想编译特定的包,在该包目录里面创建一个名为COLCON_IGNORE文件,这样子这个包就不会被索引到了

问题

  1. 编译时报错 CMake Error at /opt/ros/jazzy/share/rosidl_typesupport_c/cmake/get_used_typesupports.cmake:35 (message):
    No ‘rosidl_typesupport_c’ found

执行source后再编译

  1. 截图 2025-07-16 09-49-13.png

一开始我以为是/opt里的安装有问题,后来发现是ament_target_dependencies里没有添加nav_msgs,导致include头文件时会找不到

  1. failed to create symbolic link ‘/home/xxx/learning_interfaces2’ because existing path cannot be removed: Is a directory

把对应的目录删掉

  1. CMake Warning: Manually-specified variables were not used by the project: CATKIN_INSTALL_INTO_PREFIX_ROOT

在相应的包里加入

1
2
3
<export>                               
<build_type>ament_cmake</build_type>
</export>

  1. 如果需要在包的package.xml中添加依赖关系。比如sample包依赖common包,那么就在sample包的package.xml中添加
1
<depend>common</depend>

    告诉编译器,编译B包的时候依赖A包,A包需要先编译出来。

  1. 没有那个文件或目录
1
2
3
4
Starting >>> test_node
--- stderr: test_node
cc1plus: fatal error: /home/user/catkin_ws/src/test_node/src/Action.cpp: 没有那个文件或目录
compilation terminated.

删掉build目录中的test_node文件夹

  1. 编译自定义的msg包时,报错 找不到rosidl_typesupport_c

一般都安装了相应的库:

1
sudo apt install ros-jazzy-rosidl-typesupport-c

source /opt/ros/jazzy/setup.bash 即可

  1. CMake Warning: Manually-specified variables were not used by the project: CATKIN_INSTALL_INTO_PREFIX_ROOT

相应的包里明明没有用CATKIN相关的东西,不知是哪里报警,只好在CMake里加一句 unset( CATKIN_INSTALL_INTO_PREFIX_ROOT )

  1. 编译时找不到 ament_cmake

source /opt/ros/jazzy/setup.bash

  1. 报警: WARNING:colcon.colcon_ros.prefix_path.catkin:The path ‘/home/user/catkin_ws/install/nav2_map_server’ in the environment variable CMAKE_PREFIX_PATH doesn’t exist

即使删除了install, build, log也无法解决,要想彻底解决只能重建一个工作空间。或者用临时方法,但新终端又会失效

1
2
unset AMENT_PREFIX_PATH
unset CMAKE_PREFIX_PATH

  1. 包的依赖问题

在路径 /build/my_package/ament_cmake_core 可以看到编译一个包后生成的cmake文件,比如my_packageConfig.cmakemy_packageConfig-version.cmake

如果包A需要包B,编译时报错找不到BConfig.cmake,在工作空间执行 source


visual studio code的配置

不搜索指定的文件夹

可以使用以下JSON格式的示例进行配置:

1
2
3
4
5
6
7
8
9
{
"search.exclude": {
"**/node_modules": true,
"**/build": true,
"**/dist": true,
"**/.git": true,
"**/.vscode": true
}
}

上面的示例中,我们配置了五个排除规则:

"**/node_modules": true - 这将排除项目中的node_modules文件夹,通常包含依赖库。

"**/build": true - 这将排除build文件夹,如果您的项目使用构建工具生成构建文件,可以排除它。

"**/dist": true - 这将排除dist文件夹,如果您的项目包含编译后的分发文件,可以排除它。

"**/.git": true - 这将排除.git文件夹,以防止搜索Git版本控制文件。

"**/.vscode": true - 这将排除.vscode文件夹,以防止搜索Visual Studio Code配置文件。

  • VS Code 按快捷键 ctrl+p 可以弹出一个小窗,在上面的 输入框输入文件名,下拉框点击一个文件

  • 设置VsCode 多文件分行(栏)排列显示,打开vscode的设置,搜索wrap tabs,勾选上就可以了

  • 修改鼠标滚轮效果: VSCode 设置页面搜索 mouseWheelScrollSensitivity,放大前两个系数

配置vscode 远程开发+ 免密登录

VSCODE 设置特定文件不显示在文件列表中


ROS2常用的命令

pkg

1
2
3
4
5
6
7
8
9
10
# 列出某个包的所有可执行文件
ros2 pkg executables pkg_name
# 列出所有的包
ros2 pkg list

# 某个包所在路径的前缀
ros2 pkg prefix pkg_name

# 查看包对应的 package.xml 文件
ros2 pkg xml pkg_name

node 命令

ros2 node 不识别kill命令,只有infolist

topic

1
2
3
4
5
6
ros2 topic list                # 查看话题列表
ros2 topic info <topic_name> # 查看话题信息
ros2 topic hz <topic_name> # 查看话题发布频率
ros2 topic bw <topic_name> # 查看话题传输带宽
# 发布话题消息
ros2 topic pub <topic_name> <msg_type> <msg_data>
  • ros2 topic info topic_name

现在必须加/

1
2
3
4
5
6
7
user@robot:~$ ros2 topic info imu
Unknown topic 'imu'

user@robot:~$ ros2 topic info /imu
Type: sensor_msgs/msg/Imu
Publisher count: 1
Subscription count: 1

  • 输出话题的详细信息,包括发布订阅者: ros2 topic info /odom -v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Type: nav_msgs/msg/Odometry

Publisher count: 1

Node name: turtlebot3_diff_drive
Node namespace: /
Topic type: nav_msgs/msg/Odometry
Endpoint type: PUBLISHER
GID: 01.0f.ea.0f.41.6a.0c.34.01.00.00.00.00.00.a5.03.00.00.00.00.00.00.00.00
QoS profile:
Reliability: RMW_QOS_POLICY_RELIABILITY_RELIABLE
Durability: RMW_QOS_POLICY_DURABILITY_VOLATILE
Lifespan: 2147483651294967295 nanoseconds
Deadline: 2147483651294967295 nanoseconds
Liveliness: RMW_QOS_POLICY_LIVELINESS_AUTOMATIC
Liveliness lease duration: 2147483651294967295 nanoseconds

Subscription count: 1

Node name: my_node
Node namespace: /
Topic type: nav_msgs/msg/Odometry
Endpoint type: SUBSCRIPTION
GID: 01.0f.ea.0f.0d.6a.5d.f0.01.00.00.00.00.00.13.04.00.00.00.00.00.00.00.00
QoS profile:
Reliability: RMW_QOS_POLICY_RELIABILITY_RELIABLE
Durability: RMW_QOS_POLICY_DURABILITY_VOLATILE
Lifespan: 2147483651294967295 nanoseconds
Deadline: 2147483651294967295 nanoseconds
Liveliness: RMW_QOS_POLICY_LIVELINESS_AUTOMATIC
Liveliness lease duration: 2147483651294967295 nanoseconds
  • ros2 interface show msg_name

显示 Topic 发送的消息定义 ros2 interface show sensor_msgs/msg/Imu

  • ros2 topic find msg_name

获知某个消息类型是谁在用

1
2
user@robot:~$ ros2 topic find sensor_msgs/msg/Imu
/imu

  • 手动发消息到话题
1
ros2 topic pub /chatter std_msgs/msg/String 'data:"123"

ros_map_editor

用GIMP等工具来进行地图的常规清理和编辑是一项费时费力的任务,为了解决这种情况,项目ros_map_editor提供一个强大而灵活的工具,使机器人领域的从业者和研究人员能够更轻松地编辑导航地图,以满足他们的特定碧求。

特点:

  1. 添加整行区域: 用户可以轻松地添加禁行区域,以确保机器人避开潜在危险区域
  2. 地图对齐:工具允许将导航地图与真实世界地图对齐,以便进行精确的导航
  3. 一般地图清理: 与传统工具相比,使用ros map editor进行地图清理更加高效

C++的新类型 pair, tie, array

std::pair

需要 #include <utility>

std::pair主要的作用是将两个数据组合成一个数据,两个数据可以是同一类型或者不同类型。pair实质上是一个结构体,其主要的两个成员变量是firstsecond,这两个变量可以直接使用。初始化一个pair可以使用构造函数,也可以使用std::make_pair函数

一般make_pair都使用在需要pair做参数的位置,可以直接调用make_pair生成pair对象。另外pair可以接受隐式的类型转换,这样可以获得更高的灵活度。

1
2
3
4
5
6
7
pair <string,double> product1 ("tomatoes",3.25);
pair <string,double> product2;
pair <string,double> product3;

product2.first = "lightbulbs"; // type of first is string
product2.second = 0.99; // type of second is double
product3 = make_pair ("shoes",20.0);

pair是将2个数据组合成一个数据,当需要这样的需求时就可以使用pair。另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。

由于pair类型的使用比较繁琐,因为如果要定义多个pair类型的时候,可以使用typedef简化声明

std::tie

std::tie 是在 <tuple> 头文件中定义的。std::tie 是一个函数模板,用于创建一个元组并将多个变量绑定到该元组的元素上。它通常用于同时获取或设置多个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <tuple>

int main() {
int x = 10;
int y = 20;
int z = 30;

// 将变量 x, y, z 绑定到元组中
std::tie(x, y, z) = std::make_tuple(z, x, y);

std::cout << "x: " << x << std::endl; // 输出: 30
std::cout << "y: " << y << std::endl; // 输出: 10
std::cout << "z: " << z << std::endl; // 输出: 20

return 0;
}

三个整数变量 x、y 和 z,分别初始化为 10、20 和 30。然后,我们使用 std::tie 将这些变量绑定到一个元组中,并使用 std::make_tuple 创建了一个新的元组,其中元素的顺序为 z, x, y。通过将该元组赋值给 std::tie(x, y, z),变量 x、y 和 z 的值发生了重新排列。

最后,我们输出变量 x、y 和 z 的值,可以看到它们已经按照元组的顺序进行了重新赋值。这种方式可以方便地交换变量的值或同时获取多个返回值。

std::array

需要include <array>

std::array仍然是有固定大小的数组,可以随机访问。不像vector那样支持添加或删除元素等改变大小的操作,没有内存重新分配的行为。当定义一个array时,除了指定元素类型,还要指定容器大小。

之所以加入了std::array,就是为了解决内置数组的老问题,比如无法直接对象赋值,无法直接拷贝等等,同时内置的数组又有很多比较难理解的地方,比如数组名是数组的起始地址等等。

简单地说,std::array作为固定大小的数组,又拥有vector的一些特点,比如迭代器访问、获取容量、获得原始指针等高级功能,但它又不会退化成指针给开发人员造成困惑。

1
2
3
std::array<int, 5> a0 = {0, 1, 2, 3, 4};    //正确
std::array<int, 5> a1 = a0; //正确
std::array<int, m> a3; //错误,array不可以用变量指定

std::array不要和数组混合使用。

std::array提供了[]、at、front、back、data的方式来访问元素。array还提供了迭代器的方式进行元素遍历和访问,比如begin, end。以及其它一些函数,比如empty, size, fill,但没有capacity函数。


std重要库函数

std::exchange

std::exchage未出现之前, 我们交换两个变量的值,需要先定义一个临时的中间变量,这是经典老方法了。但是std::exchange让我们优雅地解决这个问题。

它的主要作用是替换一个对象的值,并返回该对象的旧值。这个函数在 C++14 中引入,主要用于简化和优化代码。原型是

1
2
template< class T, class U = T >
T exchange( T& obj, U&& new_value );

在C++ 20里给函数加上了constexpr

1
2
3
4
std::string name = "Alice";
std::string new_name = "Bob";

std::string old_name = std::exchange(name, new_name);

还可以用于容器

1
2
3
4
5
6
7
std::vector<int> v;
std::exchange(v, {1,2,3,4});
cout << v.size() << endl;
for (auto a : v)
{
cout << a << " ";
}

std::exchange在处理移动语义时非常有用。

std::exchange是原子操作的,所以在多线程环境下是安全的,如果对程序性能有严格要求,可以换std::swap或临时变量的方式。

std::transform

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int multiply(int num)
{
return 3*num;
}

vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
vec.push_back(5);

for(const auto &it: vec)
{
cout << it << endl;
}

// 将 vec 元素范围 ( 起始迭代器 和 末尾迭代器 ) 作为输入容器 , 将 vec 的起始迭代器作为输出容器起始点 , 也就是将输入容器的元素进行修改, 再次放回到该容器
std::transform(vec.begin(), vec.end(), vec.begin(), multiply );

for(const auto &it: vec)
{
cout << it << endl;
}

// 使用lambda表达式作为转换方式,如果输出容器的起始点改为 vec.end(),vec不会有变化
std::transform(vec.begin(), vec.end(), vec.begin(), [](int num){return 2*num; } );

for(const auto &it: vec)
{
cout << it << endl;
}

再看一个配合std::back_inserter使用的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vector<int>  vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
vec.push_back(5);

vector<int> new_vec;
std::transform(vec.begin(), vec.end(), std::back_inserter(new_vec), [](int v){ return 3*v; } );

cout << "new vec size: " << new_vec.size() << endl;
for(auto it : new_vec)
{
cout << it << " ";
}

运行结果
1
2
new vec size: 5
3 6 9 12 15