值传递vs引用传递:这道题我面了十次,每次都有人翻车
上周帮朋友面试,来了个硕士应届生,985的,简历上项目经验挺丰富,什么SLAM、路径规划都做过,看着挺有底气的。
我想先热热身,问了个自认为很简单的问题:"你写一个函数,交换两个整数的值,怎么写?"
他刷刷刷写完了,笔都没停:
void swap(int a, int b) { int temp = a; a = b; b = temp; }我问他:"这样写对吗?"
他很自信地说:"对啊,教科书上就是这么写的。"
我说:"你调用一下试试。"
他写了一个测试:
int x = 1, y = 2; swap(x, y); cout << x << " " << y << endl;输出是1 2。没交换。
他愣住了。
讲真,这种问题我见过太多次了。不是不会写,而是没真正理解值传递和引用传递的区别。教科书上写的swap函数,用的是指针,不是值传递。
值传递:复印一份给你
值传递,说白了就是把参数的副本传给函数。你在栈上开辟一块新空间,把原变量的值原封不动地复制过去。函数里怎么折腾这个副本,原变量都不受影响。
打个比方:你有一本书,值传递就是我去复印店复印了一本给你。你在上面画花、做笔记,我的书还是干干净净的。
void modify(int x) { x = 100; } int a = 10; modify(a); // a还是10这种传递方式安全,因为不会意外修改原变量。但有个问题:如果参数是个大的数据结构,比如一个包含100万个点的点云,值传递就要拷贝100万个点,性能开销很大。
我见过一个实习生写的代码,处理激光雷达数据,函数参数是值传递的点云对象。每次调用函数,都要拷贝一次点云。结果程序跑得特别慢,从30fps掉到了8fps。
后来改成引用传递,帧率立刻恢复了。
所以在机器人开发里,对于大的数据结构,值传递要慎用。
引用传递:给你起个别名
引用传递是C++特有的语法,相当于给原变量起了个别名。函数里修改引用,就是在修改原变量。
底层怎么实现的?编译器在底层其实是用指针来做的,引用就是指针的语法糖。但这个语法糖让代码好读太多了。你不用写*和&,直接用变量名就行。
void modify(int& x) { x = 100; } int a = 10; modify(a); // a现在是100这种传递方式高效,因为没有拷贝。但要注意,函数内部可能会修改原变量,所以要小心。
在机器人开发里,引用传递用得特别多。比如你要处理一个大的点云数据,不想拷贝,就用引用传递:
void processPointCloud(PointCloud& cloud) { // 直接修改原数据 }但如果你不想让函数修改原数据,就加个const:
void processPointCloud(const PointCloud& cloud) { // 只读,不能修改 }这样既高效,又安全。
面试的时候,如果你能主动提到const引用,面试官会觉得你有工程经验。真的。
指针传递 vs 引用传递:有什么区别?
指针传递和引用传递效果类似,都能修改原变量。但语法上不一样。
指针传递:
void modify(int* x) { *x = 100; } int a = 10; modify(&a); // 传地址引用传递:
void modify(int& x) { x = 100; } int a = 10; modify(a); // 直接传变量看起来引用传递更简洁,不需要取地址符&,也不需要用*解引用。
但在底层,引用传递其实是用指针实现的。编译器帮你做了那些繁琐的语法。
面试的时候,面试官可能会问:"引用传递和指针传递有什么区别?"
答案是:语法上,引用更简洁;语义上,引用必须绑定到一个有效的变量,不能为空,而指针可以是nullptr;使用上,引用一旦绑定就不能改变指向,指针可以随便指。
在机器人开发里,我更喜欢用引用传递,因为代码更清晰。但有些场景必须用指针,比如参数可能为空的时候。
void processSensor(Sensor* sensor) { if (sensor != nullptr) { // 处理传感器数据 } }这种时候,指针就比引用更合适。
返回值:也是值传递
很多人忽略了,函数的返回值也是值传递。如果返回的是大数据结构,拷贝开销很大。
怎么办?一种方法是用引用参数代替返回值:
void getPointCloud(PointCloud& cloud) { // 直接填充传入的cloud,避免拷贝 }另一种方法是用移动语义(C++11),后面有机会再聊。
const引用的威力——只读又高效
const引用是C++中最实用的参数传递方式,没有之一。它结合了值传递的安全性(不会修改原变量)和引用传递的高效性(没有拷贝)。
void calculateDistance(const Point3D& a, const Point3D& b) { double dx = a.x - b.x; double dy = a.y - b.y; double dz = a.z - b.z; // a和b都不能被修改,编译器会报错 // a.x = 0; // 编译错误! }在机器人开发中,const引用几乎是标配。你去看PCL、ROS2的源码,大数据结构的参数基本都是const引用。
面试时候被问"为什么不用值传递加const",你可以说:值传递的const只保护函数内部的副本,原变量本来就不会被修改。const引用的const保护的是原变量——这才是有意义的。
还有一个容易忽略的点:const引用可以绑定到临时对象上,而普通引用不行。
void print(const std::string& s) { cout << s << endl; } print("hello"); // OK,const引用可以绑定到字符串字面量(临时对象) void modify(std::string& s) { s = "modified"; } // modify("hello"); // 编译错误!普通引用不能绑定到临时对象这个特性在实际开发中非常有用。比如你的函数需要一个默认参数,用const引用就能直接传字面量。
面试中的陷阱题
面试的时候,有些题目特别容易踩坑。
比如这个:
void getString(std::string s) { s = "modified"; } std::string str = "original"; getString(str); cout << str << endl;输出是什么?
答案是"original"。因为参数是值传递,函数里修改的是副本。
但很多人会想当然地认为是"modified"。
还有这个:
void modify(std::string& s) { s = "modified"; } std::string str = "original"; modify(str); cout << str << endl;这次输出是"modified",因为参数是引用传递。
这种题目考的就是你对值传递和引用传递的理解。面试的时候,一定要看清楚参数是值传递还是引用传递。
给正在准备面试的你一点建议
值传递和引用传递,真的是C++最基础的特性之一。但基础的东西,往往最容易出错。
我面试的时候,看候选人写函数,第一眼就看参数是怎么传的。如果是大的数据结构用值传递,我心里就会打个问号——这个人有没有性能优化的意识?
怎么提高?我的建议是:
把值传递、引用传递、指针传递的区别搞清楚,知道什么时候用哪个。然后去看看开源的机器人代码,比如PCL、ROS2,看看人家是怎么传参数的。你会发现,大的数据结构基本都是const引用,只有小的、便宜拷贝的类型才用值传递。
最重要的是,养成好习惯。大的数据结构用const引用,需要修改的时候用引用或指针,小的基本类型用值传递。这些习惯一旦养成,代码质量和性能都会有提升。
对了,还有个实用的小技巧:写函数的时候,先想清楚"这个参数需不需要在函数内部修改?修改的话,调用者需不需要感知到?"想清楚这两个问题,你就知道该用哪种传递方式了。
如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。
「机器人软件开发面试·从入门到精通」连载系列上一篇:第6篇 函数定义与调用——代码复用的起点 下一篇预告:第8篇 递归函数——运动学求解中的常见思路
有任何问题欢迎评论区留言,我会尽量回复。