上一课标准答案:
*p = 10因为p指向a[0]。
*(p + 2) = 30因为:
p → a[0] p + 1 → a[1] p + 2 → a[2]所以:
*(p + 2)就是访问a[2]。
*p = 10因为:
int a[3] = {5, 10, 15}; int* p = a;开始时:
p → a[0] → 5执行:
p++;以后:
p → a[1] → 10因此:
*p得到10。
- 在这种数组访问情况下:
a[2]和:
*(a + 2)访问的是同一个元素,也就是数组第 3 个元素。
你现在可以先记:
a[i] ↔ *(a + i)第 19 课:std::vector
这一课开始接触现代 C++ 中非常常用的容器。
前面我们学了数组:
double joint[6];数组很好用,但是它有一个明显特点:
长度通常在创建时就确定了。
例如:
double joint[6];就是 6 个。
但现实程序经常遇到:
我现在还不知道最终会有多少数据。
例如机械臂轨迹:
第1个轨迹点 第2个轨迹点 第3个轨迹点 ……可能今天是 20 个点,明天是 500 个点。
这种情况下:
std::vector非常好用。
1.vector最直觉的理解
你可以先把:
std::vector理解成:
长度可以变化的数组。
普通数组:
int a[3];像固定长度的一排盒子:
┌────┬────┬────┐ │ │ │ │ └────┴────┴────┘ 固定 3 个而vector更像:
开始: ┌────┬────┐ │ │ │ └────┴────┘ 后来: ┌────┬────┬────┬────┐ │ │ │ │ │ └────┴────┴────┴────┘它可以动态增加元素。
2. 使用vector要先准备什么?
和:
std::cout需要:
#include <iostream>类似,
使用vector通常需要:
#include <vector>所以程序可能写:
#include <iostream> #include <vector>其中:
<vector>给我们提供std::vector。
3. 创建第一个vector
例如:
std::vector<int> data;拆开:
std::vector ↓ 我要创建一个 vector <int> ↓ 里面存 int data ↓ 名字叫 data所以:
std::vector<int> data;可以理解成:
创建一个叫
data的动态整数数组。
4. 为什么有<int>?
这是一个新写法。
std::vector<int>意思是:
这个
vector专门存int。
例如:
std::vector<double> angles;表示:
angles里面保存double。
机械臂角度就很适合:
std::vector<double> joint_angles;5. 创建时直接放数据
例如:
std::vector<int> data = {10, 20, 30};可以想成:
data [0] [1] [2] ┌────┬────┬────┐ │ 10 │ 20 │ 30 │ └────┴────┴────┘和数组很像。
访问方式也很像:
data[0] data[1] data[2]所以:
std::cout << data[1] << std::endl;输出:
206.vector最重要的能力:push_back
比如:
std::vector<int> data;刚创建时里面没有元素:
data 空然后:
data.push_back(10);意思是:
在 vector 最后增加一个
10。
现在:
[10]再:
data.push_back(20);变成:
[10, 20]再:
data.push_back(30);变成:
[10, 20, 30]7.push_back怎么读?
data.push_back(10);这里出现:
.我们以后学类的时候会正式解释。
现在先理解为:
对
data这个 vector 执行一个操作。
所以:
data.push_back(10);大致读作:
data,往最后塞进去一个10。
8. 完整例子
#include <iostream> #include <vector> int main() { std::vector<int> data; data.push_back(10); data.push_back(20); data.push_back(30); std::cout << data[0] << std::endl; std::cout << data[1] << std::endl; std::cout << data[2] << std::endl; return 0; }输出:
10 20 309. 怎么知道vector里有多少元素?
使用:
data.size()例如:
std::vector<int> data = {10, 20, 30};那么:
data.size()得到:
3所以可以:
std::cout << data.size() << std::endl;输出:
310.size()为什么有括号?
因为:
size()本质上也是一个函数。
你之前学过:
calculateError()现在:
data.size()可以先理解成:
调用属于
data的size()函数,询问它现在有多少个元素。
11.vector + for非常常用
例如:
std::vector<double> joint_angles = { 10.0, 20.0, 30.0, 40.0, 50.0, 60.0 };我们可以:
for (int i = 0; i < joint_angles.size(); i++) { std::cout << joint_angles[i] << std::endl; }逻辑就是:
i = 0 ↓ 访问 joint_angles[0] i = 1 ↓ 访问 joint_angles[1] ……12. 为什么不用直接写i < 6?
当然可以:
for (int i = 0; i < 6; i++)但如果 vector 后来增加了一个元素:
joint_angles.push_back(70.0);现在已经有 7 个元素。
如果还写:
i < 6就只能处理前 6 个。
而:
i < joint_angles.size()会自动根据当前元素数量变化。
这就是更灵活的地方。
13. 机械臂轨迹点例子
假设你不断生成 Joint1 的轨迹角度:
std::vector<double> trajectory;开始为空。
然后:
trajectory.push_back(10.0); trajectory.push_back(15.0); trajectory.push_back(20.0); trajectory.push_back(25.0);现在:
trajectory [0] [1] [2] [3] 10.0 15.0 20.0 25.0也就是说:
我们不用一开始就知道轨迹到底有几个点。
这对机器人轨迹特别实用。
14. 可以修改元素
和数组一样:
std::vector<double> angle = {10, 20, 30};执行:
angle[1] = 100;以后:
10 100 3015. 一个完整机器人例子
#include <iostream> #include <vector> int main() { std::vector<double> joint_angles; joint_angles.push_back(10.0); joint_angles.push_back(20.0); joint_angles.push_back(30.0); joint_angles.push_back(40.0); joint_angles.push_back(50.0); joint_angles.push_back(60.0); for (int i = 0; i < joint_angles.size(); i++) { std::cout << "Joint " << i + 1 << ": " << joint_angles[i] << std::endl; } return 0; }输出:
Joint 1: 10 Joint 2: 20 Joint 3: 30 Joint 4: 40 Joint 5: 50 Joint 6: 6016.vector仍然从 0 开始
和数组一样:
std::vector<int> data = {10, 20, 30};对应:
data[0] = 10 data[1] = 20 data[2] = 30所以:
第 1 个元素还是
[0]。
17.vector也可能越界
例如:
std::vector<int> data = {10, 20, 30};只有:
[0] [1] [2]如果写:
data[100]仍然是错误访问。
所以用了vector不代表:
再也不用管下标。
18.at():一种更安全的访问方式
vector还可以:
data.at(1)例如:
std::vector<int> data = {10, 20, 30}; std::cout << data.at(1) << std::endl;输出:
20它和:
data[1]都能访问第二个元素。
但是:
data.at(100)发生越界时,通常会明确抛出异常,而:
data[100]通常不会主动帮你检查。
现阶段你可以理解成:
[] 更直接、更常见 at() 带边界检查,更安全一些19.vector和普通数组最大的区别
普通数组:
double q[6];特点:
长度固定,结构简单,底层常见。
vector:
std::vector<double> q;特点:
长度可以动态变化,而且带很多方便的功能。
例如:
q.push_back(...) q.size() q.at(...)20. 机器人里面什么时候用数组?
像六轴机械臂:
永远就是 6 个关节这时候固定大小很自然。
以后现代 C++ 里甚至可能用:
std::array<double, 6>这个我们以后再讲。
21. 什么时候用vector?
例如:
轨迹点数量不固定 视觉检测目标数量不固定 点云数量很多 传感器采样数量变化 规划路径长度变化这时候:
std::vector通常非常合适。
22. 一个非常接近以后机器人的场景
假设路径规划生成若干关节轨迹点:
std::vector<double> trajectory;规划过程中不断:
trajectory.push_back(10.0); trajectory.push_back(12.5); trajectory.push_back(16.0); trajectory.push_back(21.2);执行时:
for (int i = 0; i < trajectory.size(); i++) { double target = trajectory[i]; std::cout << target << std::endl; }意思就是:
按顺序取出每一个目标点。
以后你真正做机械臂轨迹时,就是类似这种思想,只不过每个轨迹点可能不再只有一个double,而是六个关节角度。
23. 后面会发展成这样
未来你会看到:
std::vector<double>表示一组数字。
再以后甚至可能:
std::vector<std::vector<double>>表示:
很多组数字。
例如:
轨迹点1: [j1, j2, j3, j4, j5, j6] 轨迹点2: [j1, j2, j3, j4, j5, j6] 轨迹点3: [j1, j2, j3, j4, j5, j6]这就已经非常接近机械臂轨迹数据了。
不过现在先不学二维 vector。
24. 本课最重要的 4 个写法
创建:
std::vector<int> data;添加:
data.push_back(10);获取数量:
data.size();访问:
data[0];先把这四个吃透。
本课小练习
第 1 题
std::vector<int> data = {10, 20, 30};那么:
data[0] = ? data[2] = ? data.size() = ?第 2 题
std::vector<int> data; data.push_back(5); data.push_back(10); data.push_back(15);现在data里面依次有什么?
第 3 题
std::vector<double> q = {10, 20, 30}; q[1] = 50;最后q是什么?
第 4 题
为什么下面这种写法通常比固定写6更灵活?
for (int i = 0; i < data.size(); i++)下一课我们继续 STL,但先学最常用、也最容易理解的:
第 20 课:std::string
你会搞清楚:
'A'和:
"A"到底有什么区别,以及为什么机器人程序里的:
"CONNECTED" "ERROR" "/dev/ttyUSB0" "joint_state"本质上都是字符串。