第4章 CAPL运算符与表达式

运算符这东西,说白了就是让变量干活的工具。CAPL里的运算符种类不少,但别怕,咱们一个一个捋清楚。我刚开始学的时候也觉得多,后来发现其实就那几类,用熟了自然就记住了。

4.1 算术运算符

算术运算符就是做数学运算的。加减乘除,跟小学数学差不多,但有几个坑要注意。

运算符 含义 示例
+ 加法 a + b
- 减法 a - b
* 乘法 a * b
/ 除法 a / b
% 取模(求余数) a % b
++ 自增1 a++ 或 ++a
-- 自减1 a-- 或 --a
注意:整数除法会截断小数部分。比如 5 / 2 结果是 2,不是 2.5。我当年刚做项目时就踩过这个坑,算出来的车速总是偏小,查了半天才发现是整数除法的问题。

自增自减运算符,++a 和 a++ 有区别。++a 是先加再用,a++ 是先用再加。举个例子:

int a = 5;
int b = ++a;  // a先变成6,然后赋值给b,b=6
int c = a++;  // 先把a的值赋给c,c=6,然后a变成7

我个人习惯在循环里用 a++,因为更直观。但在复杂表达式里,我建议你尽量拆开写,别搞得太花哨,容易把自己绕晕。

4.2 关系运算符

关系运算符用来比较两个值的大小或是否相等。结果只有两种:真(1)或假(0)。

运算符 含义 示例
== 等于 a == b
!= 不等于 a != b
> 大于 a > b
< 小于 a < b
>= 大于等于 a >= b
<= 小于等于 a <= b
最容易犯的错误:把 == 写成 =。一个等号是赋值,两个等号才是比较。我曾经在项目里写了个 if(a = 5),结果条件永远为真,程序跑飞了。这种bug最难查,因为语法上没错。

4.3 逻辑运算符

逻辑运算符用来组合多个条件。你想想看,实际项目中哪有那么简单的条件?经常是「车速大于30且小于80」这种复合条件。

运算符 含义 示例
&& 逻辑与(且) a && b
|| 逻辑或(或) a || b
! 逻辑非(取反) !a

逻辑与有个特点:短路求值。如果第一个条件已经是假,第二个条件就不会执行了。比如:

if (ptr != NULL && ptr->value == 5) {
  // 如果ptr是NULL,第二个条件根本不会执行
  // 这样就避免了空指针访问
}

这个特性其实很有用。我在写CAN报文解析时经常用,先判断报文是否存在,再判断里面的信号值,安全又高效。

4.4 位运算符

位运算符直接操作二进制位。做汽车电子的人,位运算几乎是家常便饭。因为CAN报文里的信号经常是按位定义的。

运算符 含义 示例
& 按位与 a & b
| 按位或 a | b
^ 按位异或 a ^ b
~ 按位取反 ~a
<< 左移 a << 2
>> 右移 a >> 2

举个例子,假设你要从CAN报文里提取第3到第5位:

byte rawData = 0xAB;  // 二进制: 10101011
// 提取第3-5位(从0开始计数)
byte result = (rawData >> 3) & 0x07;
// 先右移3位,变成 00010101
// 再与0x07(00000111)按位与,得到 00000101 = 5
小技巧:左移1位相当于乘以2,右移1位相当于除以2。但位运算比乘除法快得多。我在写实时性要求高的代码时,能用移位就绝不用乘除。

4.5 赋值运算符

赋值运算符就是把右边的值赋给左边。除了基本的 =,还有复合赋值运算符。

运算符 含义 等价写法
= 赋值 a = b
+= 加后赋值 a = a + b
-= 减后赋值 a = a - b
*= 乘后赋值 a = a * b
/= 除后赋值 a = a / b
%= 取模后赋值 a = a % b
&= 按位与后赋值 a = a & b
|= 按位或后赋值 a = a | b
^= 按位异或后赋值 a = a ^ b
<<= 左移后赋值 a = a << b
>>= 右移后赋值 a = a >> b

复合赋值的好处是代码更简洁。比如计数器累加:

int counter = 0;
counter += 5;  // 等价于 counter = counter + 5

我个人习惯在循环里用 += 和 -=,看着清爽。但别滥用,如果逻辑复杂,还是拆开写更清晰。

4.6 运算符优先级

运算符优先级决定了表达式里谁先算谁后算。优先级高的先执行。我直接给你一张表,建议收藏:

优先级 运算符 结合性
1(最高) () [] . -> 左到右
2 ! ~ ++ -- +(正号) -(负号) 右到左
3 * / % 左到右
4 + - 左到右
5 << >> 左到右
6 < <= > >= 左到右
7 == != 左到右
8 &(按位与) 左到右
9 ^(按位异或) 左到右
10 |(按位或) 左到右
11 && 左到右
12 || 左到右
13 ?:(三目运算符) 右到左
14 = += -= 等赋值运算符 右到左
15(最低) ,(逗号运算符) 左到右
避坑指南:我曾经在项目里写了个表达式:a << 2 + 3,本意是 a 左移 2 位再加 3。结果因为 + 的优先级比 << 高,实际执行的是 a 左移 5 位。从那以后,我养成了一个习惯:拿不准优先级的时候,就用括号。括号永远是最保险的。

举个例子,看看加了括号和没加括号的区别:

int a = 5;
int b = 3;
int c = 2;

// 不加括号
int result1 = a + b * c;  // 先算 b*c=6,再加 a,result1=11

// 加括号
int result2 = (a + b) * c;  // 先算 a+b=8,再乘 c,result2=16

嗯,这里要注意:三目运算符 ?: 的优先级很低,比赋值运算符还低。所以:

int x = a > b ? a : b;  // 这样写没问题
// 等价于 int x = (a > b ? a : b);

但如果你在复杂表达式里用三目运算符,最好加括号。我见过有人写:

int y = a > b ? a : b + 1;  // 实际是 a > b ? a : (b + 1)
// 而不是 (a > b ? a : b) + 1

这种代码容易让人误解。我的建议是:三目运算符尽量用在简单场景,复杂逻辑还是用 if-else 吧,可读性更好。

总结一下我的经验:
  • 算术运算:注意整数除法截断
  • 关系运算:小心 == 和 = 搞混
  • 逻辑运算:利用短路求值特性
  • 位运算:做信号提取时特别好用
  • 赋值运算:复合赋值让代码更简洁
  • 优先级:拿不准就加括号,别逞能

运算符这块内容不算难,但细节多。你写代码的时候多留个心眼,慢慢就熟练了。下一章咱们聊控制语句,if、while、for 这些,到时候这些运算符就派上大用场了。