以后我们每完成一个阶段,我都会先做一次这种"阶段性总结":覆盖核心知识、配代表代码、不过度展开,然后再继续答案与下一课。
C++ 第一阶段总结:从零基础到 ROS2 前置语法
你目前已经完成的内容,大致可以分成 5 组。
一、变量、数据类型、运算和流程控制
1. 变量
int a = 5;
double angle = 30.5;
bool enabled = true;
char c = 'A';
理解:
int 整数
double 小数
bool true / false
char 单个字符
= 是赋值:
a = 10;
表示把 10 放进 a。
2. 运算
int c = a + b;
double error = target - current;
常见:
+ - * /
整数除法:
5 / 2
结果是:
2
3. 比较和逻辑
a == b
a != b
a > b
a <= b
逻辑:
&& 并且
|| 或者
! 取反
机器人例子:
if (angle >= -90.0 && angle <= 90.0)
{
// 角度合法
}
4. if / else
if (error < 1.0)
{
std::cout << "Reached";
}
else
{
std::cout << "Moving";
}
表示:
根据条件选择执行哪一段代码。
5. switch
适合固定状态:
switch (state)
{
case 0:
break;
case 1:
break;
default:
break;
}
机器人状态机里很常见。
6. 循环
while:
while (running)
{
// 持续控制
}
for:
for (int i = 0; i < 6; i++)
{
std::cout << i;
}
六轴机械臂里:
i = 0 ~ 5
分别对应 6 个关节。
7. break / continue
break;
直接退出当前循环。
continue;
跳过本轮,进入下一轮。
二、函数、引用、指针和内存
1. 函数
double calculateError(double target, double current)
{
return target - current;
}
调用:
double error = calculateError(50, 20);
结果:
30
函数的意义:
把一段功能封装起来反复使用。
2. 局部变量
void test()
{
int a = 5;
}
a 只能在 test() 里面使用。
这涉及:
作用域 scope
生命周期 lifetime
3. 值传递
void change(int a)
{
a = 100;
}
调用:
int x = 5;
change(x);
最终:
x = 5
因为:
a是x的副本。
口诀:
值传递 = 副本
4. 引用
void change(int& a)
{
a = 100;
}
调用:
int x = 5;
change(x);
最终:
x = 100
因为:
a是x的别名。
口诀:
引用 = 别名
5. 指针
int x = 5;
int* p = &x;
关系:
x → 值 5
&x → x 的地址
p → 保存 x 的地址
*p → 访问该地址里的值
所以:
*p = 100;
会让:
x = 100
口诀:
指针 = 地址
6. 对象指针
普通对象:
robot.stop();
指针:
Robot* p = &robot;
p->stop();
记住:
对象 用 .
对象指针 用 ->
而:
p->stop();
基本等价于:
(*p).stop();
三、数组、vector、string、struct
1. 数组
double joint[6] =
{
10, 20, 30, 40, 50, 60
};
访问:
joint[0]
joint[5]
下标从:
0
开始。
必须防止越界:
for (int i = 0; i < 6; i++)
不能写:
i <= 6
2. 数组和指针
int a[4] = {10, 20, 30, 40};
int* p = a;
重要关系:
*p = a[0]
*(p + 1) = a[1]
a[i] ≈ *(a + i)
这是理解底层数组、缓冲区、CAN 数据的重要基础。
3. std::vector
动态数组:
std::vector<double> data;
添加:
data.push_back(10.0);
数量:
data.size();
访问:
data[0];
适合:
轨迹点
传感器数据
视觉结果
可变长度数据
4. std::string
std::string state = "ERROR";
区别:
'A'
是一个 char。
"A"
是字符串。
字符串可以:
state == "ERROR"
比较,也可以:
name.size()
查看长度。
5. struct
把相关数据打包:
struct Joint
{
double angle;
double speed;
bool enabled;
};
创建:
Joint joint1;
访问:
joint1.angle = 30.0;
六轴:
Joint joints[6];
访问第三轴:
joints[2].angle;
四、class 和面向对象基础
1. class
class Joint
{
public:
void setAngle(double angle)
{
this->angle = angle;
}
private:
double angle;
};
可以理解成:
数据 + 操作数据的函数。
2. public / private
public:
外部可以访问。
private:
只能类内部访问。
这样可以防止外部乱改数据。
3. 构造函数
class Motor
{
public:
Motor(int id)
: id(id)
{
}
private:
int id;
};
创建:
Motor motor(1);
会自动调用构造函数。
构造函数:
名字和类名一样
没有返回类型
对象创建时自动执行
4. getter / setter
修改:
void setAngle(double angle)
{
this->angle = angle;
}
读取:
double getAngle() const
{
return angle;
}
5. this
this->angle
表示:
当前这个对象自己的
angle。
this 本身是指向当前对象的指针。
6. const
常量:
const int JOINT_COUNT = 6;
不能修改。
只读引用:
void printJoint(const Joint& joint)
表示:
不复制
joint,也不允许通过这个引用修改它。
成员函数:
double getAngle() const
表示:
这个函数承诺不会修改当前对象。
五、开始进入 ROS2 所需的现代 C++
1. auto
auto x = 10;
编译器自动判断类型。
ROS2 里很常见:
auto node = std::make_shared<MyNode>();
2. shared_ptr
auto robot =
std::make_shared<Robot>();
robot 的类型大致是:
std::shared_ptr<Robot>
它不是直接的 Robot,而是:
管理 Robot 对象生命周期的智能指针。
使用:
robot->run();
多个 shared_ptr 可以管理同一个对象:
auto p1 = std::make_shared<Robot>();
auto p2 = p1;
这里:
p1
p2
指向同一个 Robot。
3. lambda
最简单:
auto f = []()
{
std::cout << "Hello";
};
执行:
f();
可以理解成:
没有名字的小函数。
参数:
auto f = [](int x)
{
std::cout << x;
};
4. lambda 捕获
[x]
按值捕获。
[&x]
按引用捕获。
[this]
捕获当前对象。
ROS2 里特别重要:
[this](const Msg::SharedPtr msg)
{
current_angle = msg->angle;
}
你现在已经能初步拆成:
[this]
↓
可以使用当前对象
msg
↓
收到的消息
msg->angle
↓
通过智能指针读取消息里的 angle
到目前为止,你已经建立的核心思维
现在看一段:
class Robot
{
public:
void update(const Msg::SharedPtr msg)
{
current_angle = msg->angle;
}
private:
double current_angle = 0.0;
};
你已经可以拆出:
class
对象
成员函数
private
double
智能指针
->
消息数据
这已经是进入 ROS2 C++ 的基础了。
上一课练习答案
第 1 题
auto f = []()
{
std::cout << "Hi";
};
这里只是:
定义了 lambda。
还没有执行。
必须:
f();
才执行。
第 2 题
int x = 10;
auto f = [x]()
{
std::cout << x;
};
x = 20;
f();
通常输出:
10
因为:
[x]
是按值捕获,相当于保存了当时 x = 10 的一份副本。
第 3 题
int x = 10;
auto f = [&x]()
{
x = 50;
};
f();
最后:
x = 50
因为:
[&x]
是按引用捕获。
第 4 题
[this]()
{
speed = 100;
}
[this] 表示:
这个 lambda 可以访问当前对象的成员。
所以:
speed = 100;
实际上是在操作当前对象自己的 speed。
第 5 题
msg->data
如果 msg 是智能指针,就是:
通过
msg找到它管理的消息对象,然后访问这个消息对象里的data成员。