运算符与表达式:面试官问的位运算,我当年也栽过跟头
昨天有个读者私信我,说看完上篇关于数据类型的文章后,去面试被问了个运算符的问题,当场懵了。
他问我:"老师,面试官问我'unsigned int减1等于多少',我说是最大值减1,他说不对。这到底怎么回事?"
我一看就知道问题出在哪了——他对运算符的理解还停留在表面,没搞清楚类型转换的坑。
说实话,这种问题在机器人面试里太常见了。你以为自己懂了,但真到了面试官追问细节的时候,才发现自己根本没吃透。
算术运算符:不只是加减乘除那么简单
加减乘除谁都会,对吧?但在机器人开发里,事情没那么简单。
举个例子,你要计算电机的转速,公式是 rpm = (pulse_count * 60) / (encoder_resolution * sample_time)。看起来很简单,但你要是直接用int类型计算,很可能就掉坑里了。
我见过一个实习生写的代码,pulse_count是1000,encoder_resolution是4096,sample_time是0.1秒。他这么写:
int rpm = (1000 * 60) / (4096 * 0.1);
结果你猜怎么着?rpm算出来是0。
为什么?因为4096乘以0.1的时候,0.1是double类型,整个表达式被提升为double计算,结果是2441.40625。但赋值给int的时候直接截断,变成了2441。等等,这也不对啊,应该是2441才对。
不对,我再算一遍。1000乘以60是60000,4096乘以0.1是409.6,60000除以409.6应该是146.48。所以最后结果是146。
你看,就这么一个简单的公式,里面藏着类型转换、整数除法截断、浮点精度好几个坑。面试官就喜欢问这种看似简单实则暗藏玄机的问题。
我的建议是,在机器人代码里,涉及物理量计算的,一律用double。别省那点内存,现在又不是上世纪八十年代。
关系运算符:浮点数比较的坑
a == b,这看起来再简单不过了。但在机器人领域,这么写很可能出bug。
为什么?因为浮点数有精度问题。
你算出来的两个坐标,理论上应该相等,但实际计算结果可能是:
- a = 1.000000001
- b = 0.999999999
这时候 a == b 返回false,但你觉得它们应该相等。
怎么办?
正确的做法是判断它们的差值是否小于一个很小的数,叫epsilon:
const double EPSILON = 1e-6;
if (std::abs(a - b) < EPSILON) {
// 认为a和b相等
}
这个EPSILON取多少合适?取决于你的应用场景。如果是处理激光雷达数据,1e-6可能就够了;如果是做高精度定位,可能需要1e-9。
面试的时候,如果你能主动提到浮点数比较的这个问题,面试官会觉得你有实际工程经验。真的,我面试别人的时候,听到候选人提到这个,心里就会加分。
逻辑运算符:短路求值的妙用
&&和||这两个运算符,大家都知道是"与"和"或"。但你知道它们有个特性叫"短路求值"吗?
什么意思呢?看这个例子:
if (ptr != nullptr && ptr->isValid()) {
// 安全地使用ptr
}
如果ptr是空指针,ptr != nullptr返回false,后面的ptr->isValid()根本不会执行。这就避免了解引用空指针导致的崩溃。
这个特性在机器人代码里特别有用。比如你要检查传感器数据是否有效:
if (sensor_data != nullptr && sensor_data->timestamp > last_timestamp) {
process(sensor_data);
}
先判断指针是否为空,再访问它的成员。如果反过来写,程序就可能崩溃。
我见过一个初级工程师写的代码,他把条件写反了:
if (sensor_data->timestamp > last_timestamp && sensor_data != nullptr) {
结果程序时不时就崩溃,查了半天才发现是这个问题。
面试的时候,面试官可能会问你:"短路求值有什么好处?"你就拿这个例子回答,绝对比他听一百遍"可以提高效率"要强。
位运算符:嵌入式开发的必备技能
如果你要做嵌入式机器人开发,位运算符是必须要掌握的。&、|、^、~、<<、>>,这些符号看起来简单,但用起来有很多讲究。
举个例子,你要从传感器读取一个16位的寄存器值,高8位是温度,低8位是湿度。怎么提取?
uint16_t reg_value = read_register();
uint8_t temperature = (reg_value >> 8) & 0xFF;
uint8_t humidity = reg_value & 0xFF;
这种操作在驱动开发里太常见了。你要是不会位运算,连传感器数据都读不出来。
还有一个常见的用法是设置标志位。比如机器人的状态可以用一个uint8_t表示,每一位代表一个状态:
const uint8_t STATUS_CONNECTED = 0x01; // 0000 0001
const uint8_t STATUS_MOVING = 0x02; // 0000 0010
const uint8_t STATUS_ERROR = 0x04; // 0000 0100
// 设置移动状态
status |= STATUS_MOVING;
// 清除移动状态
status &= ~STATUS_MOVING;
// 检查是否在移动
if (status & STATUS_MOVING) {
// 机器人正在移动
}
这种写法在嵌入式代码里到处都是。面试的时候,如果问你"怎么设置/清除/检查某个位",你要能脱口而出。
说到位运算,还有一个经典的面试题:怎么判断一个数是2的幂?
答案是:n > 0 && (n & (n - 1)) == 0
为什么?因为2的幂在二进制里只有一个1,比如8是1000,7是0111,它们相与结果是0。
这种题目考的不是你会不会,而是你对位运算的理解深不深。
运算符优先级:那些容易被忽略的细节
a + b * c,先算哪个?
小学生都知道先算乘法。但在C++里,运算符优先级有十几个等级,谁能全记住?
老实说,我也记不住。所以我的习惯是,只要涉及到不同运算符,就加括号。
比如 if (a & b == c),你觉得是先算&还是先算==?
答案是先算==。因为==的优先级比&高。所以这个表达式实际上是a & (b == c),很可能不是你想要的结果。
正确的写法应该是 (a & b) == c。
我见过太多因为优先级问题导致的bug了。有个同事写的代码,判断某个标志位是否设置:
if (status & 0x01 == 0x01) {
结果这个条件永远不成立。因为==先算,0x01 == 0x01是true,也就是1,然后status & 1,只有status的最低位是1的时候才成立。
他本来想写的是 (status & 0x01) == 0x01。
这种bug特别隐蔽,因为代码能编译通过,逻辑上也说得通,但结果就是不对。
我的建议是,不要依赖优先级,多加括号。代码是给人看的,不是给编译器看的。你写得清楚一点,后面维护的人(包括三个月后的你自己)会感谢你的。
给正在准备面试的你一点建议
运算符和表达式,看起来是最基础的东西,但面试里考的就是这些基础。
我面试的时候,最喜欢问候选人运算符相关的问题。不是因为这些问题难,而是能看出一个人的基本功扎不扎实。
你能把浮点数比较的坑讲清楚,说明你写过真正的代码;你能把位运算的用法说得头头是道,说明你懂嵌入式;你能主动提到优先级的问题,说明你踩过坑、长过记性。
怎么复习这部分?我的建议是:
把C++的运算符表过一遍,重点看类型转换、浮点比较、位运算、优先级这几个地方。然后去找一些开源的机器人代码,看看别人是怎么用这些运算符的。
最的是,自己动手写一写。纸上得来终觉浅,绝知此事要躬行。
如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。
「机器人软件开发面试·从入门到精通」连载系列 上一篇:第1篇 变量与数据类型——机器人程序中每个数据的"身份证" 下一篇预告:第3篇 条件判断if/else——面试中最容易写错的分支逻辑
有任何问题欢迎评论区留言,我会尽量回复。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)