前几天有个读者给我发了段代码,说面试的时候写的,让我帮忙看看哪里有问题。

int sensor_data[100];
for (int i = 0; i <= 100; i++) {
    sensor_data[i] = readSensor();
}

我一看就笑了——哦不,是心疼。i <= 100,数组越界了。

他问我:"老师,这种低级错误是不是特别减分?"

我说:"减分是肯定的,但没那么致命。致命的是,你写完之后没有检查。"

讲真,数组越界这种问题,不只是新手会犯。我面过工作五年的人,照样在这种地方翻车。不是不会,是写代码的时候没那个意识。

C风格数组 vs std::vector:选哪个?

先说个基础问题:C++里数组有两种写法,C风格的int arr[100]和STL的std::vector<int>。

面试的时候,如果你说"我用C数组",面试官可能会追问:"为什么不用vector?"

老实说,在机器人开发里,C数组用得越来越少了。vector更安全、更灵活,还能动态扩容。唯一的缺点是有一点点性能开销,但在大多数场景下,这点开销可以忽略不计。

C数组什么时候用?嵌入式开发里,内存特别紧张的时候。比如STM32上跑一个控制算法,RAM只有几十KB,你用vector的话,动态内存分配可能会出问题。这时候用C数组更稳妥。

但如果是写ROS2节点、处理点云数据、做SLAM,我强烈建议用vector。代码更安全,也更容易维护。

面试的时候,你能说出这个区别,说明你有实际工程经验。

数组越界:最隐蔽的bug

数组越界是C/C++里最常见的bug之一。

int arr[10];
arr[10] = 42;  // 越界了!

这段代码能编译通过,能运行,但行为是未定义的。可能什么都没发生,可能程序崩溃,可能覆盖了其他变量的值,导致一些莫名其妙的bug。

我见过最离谱的一个case,是之前在某做人形机器人的团队。他们的机器人跑着跑着,关节角度突然跳变,摔了一跤。查了三天,最后发现是某个数组越界,覆盖了关节控制器的参数。

这种bug特别难排查,因为越界的时候不会报错,程序照样跑。等到出问题的时候,你已经不知道是哪里越界了。

怎么避免?

用vector的at()方法,越界的时候会抛异常:

std::vector<int> arr(10);
arr.at(10) = 42;  // 抛出std::out_of_range异常

或者用调试工具,比如AddressSanitizer,编译的时候加-fsanitize=address,越界的时候程序会立即崩溃,告诉你哪里出了问题。

面试的时候,如果你能主动提到这些调试工具,面试官会觉得你有实战经验。真的。

字符串处理:C风格 vs std::string

字符串在C++里也有两种写法:C风格的char*和STL的std::string。

同样的道理,在机器人开发里,我强烈建议用std::string。

C风格字符串的问题在于,你需要手动管理内存,手动计算长度,手动拼接,一不小心就缓冲区溢出。

char str1[20] = "Hello";
char str2[20] = "World";
strcat(str1, str2);  // 如果str1+str2超过20,就溢出了

而std::string会自动管理内存,不用担心溢出:

std::string str1 = "Hello";
std::string str2 = "World";
std::string str3 = str1 + str2;  // 自动扩容,安全

在机器人开发里,字符串用得最多的地方是日志输出、参数配置、通信协议。比如ROS2的topic名字、服务名字,都是字符串。

rclcpp::Node::make_shared("my_robot_node");

这种地方,用std::string省心多了。

不过有个地方C风格字符串还是会用到:嵌入式系统。比如你要跟某个传感器通信,协议规定的是固定长度的字节数组,这时候用char[]更直接。

面试的时候,你能说出这个区别,说明你懂底层,也懂工程实践。

数组作为函数参数:值传递还是引用传递?

这个问题面试的时候特别喜欢问。

void process(int arr[]) {
    // arr是值传递还是引用传递?
}

答案是:数组作为函数参数时,会退化为指针。所以实际上是传递的指针,是"值传递",但传递的是地址。

这意味着,你在函数里修改数组的内容,原数组也会被修改。

void modify(int arr[]) {
    arr[0] = 999;
}

int data[10] = {0};
modify(data);
// data[0]现在是999

这个特性在机器人开发里很有用。比如你要处理一个大的点云数据,不想拷贝一份,就可以直接传数组(指针)进去修改。

但这也带来一个问题:函数内部不知道数组的大小。

void process(int arr[]) {
    // 这里不知道arr有多大
}

所以通常的做法是,把数组大小也传进去:

void process(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        // 处理arr[i]
    }
}

或者用vector,它自带size()方法:

void process(std::vector<int>& arr) {
    for (int i = 0; i < arr.size(); i++) {
        // 处理arr[i]
    }
}

面试的时候,如果你能主动提到数组退化为指针这个特性,面试官会觉得你对C++的理解比较深。

多维数组:机器人数据结构的标配

机器人开发里,多维数组用得特别多。

图像是二维数组,点云是二维数组(每个点有x、y、z),栅格地图是二维数组,关节角度是二维数组(多个关节,每个关节多个采样点)。

C风格的多维数组:

int grid[100][100];  // 100x100的栅格地图
grid[50][50] = 1;    // 标记某个格子为障碍物

vector的多维数组:

std::vector<std::vector<int>> grid(100, std::vector<int>(100, 0));
grid[50][50] = 1;

或者用一维vector模拟二维数组,性能更好:

std::vector<int> grid(100 * 100, 0);
grid[50 * 100 + 50] = 1;  // 手动计算索引

这种写法在性能敏感的场景下用得比较多,比如SLAM里的栅格地图更新。

面试的时候,如果你能说出多维数组的几种实现方式和各自的优缺点,面试官会觉得你有优化意识。

给正在准备面试的你一点建议

数组和字符串,真的是最基础的数据结构了。但基础的东西,往往最能看出一个人的编程习惯。

我面试的时候,看候选人写数组代码,主要看几点:边界条件对不对、有没有越界风险、字符串处理安不安全、函数参数传递方式对不对。这些细节做好了,说明这个人写过规范的代码,有工程素养。

怎么提高?我的建议是:

把C数组和vector的区别搞清楚,知道什么时候用哪个。然后去看看开源的机器人代码,比如PCL、ORB-SLAM,看看人家是怎么处理数组和字符串的。

最的是,养成好习惯。能用vector就不用C数组,能用at()就不用[],能用std::string就不用char*。这些习惯一旦养成,能帮你避免很多低级bug。


如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。

「机器人软件开发面试·从入门到精通」连载系列 上一篇:第4篇 循环结构for/while——机器人数据采集的重复逻辑 下一篇预告:第6篇 函数定义与调用——代码复用的起点

有任何问题欢迎评论区留言,我会尽量回复。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐