4. CAPL语言基础语法
写程序说白了就是指挥电脑干活。但光有变量和函数还不够——你得告诉它什么情况下干什么事,什么时候重复干,什么时候停下来。这就是流程控制。
我个人习惯把流程控制比作开车:if-else 是路口判断左转还是右转,switch-case 是高速出口选择,循环 是绕圈跑直到里程数达标。今天咱们就把这些方向盘上的操作全捋一遍。
4.1 if-else 条件判断:最基础的分支
if-else 是编程里最常用的判断结构。说白了就是:如果条件成立,做 A;否则做 B。
CAPL 的语法和 C 语言几乎一样:
if (条件表达式) {
// 条件为真时执行
} else if (另一个条件) {
// 第一个条件不成立,但第二个成立时执行
} else {
// 所有条件都不成立时执行
}
举个例子,判断车速并输出不同级别的警告:
if (speed < 30) {
write("低速行驶,注意安全");
} else if (speed < 80) {
write("正常行驶中");
} else if (speed < 120) {
write("超速警告!请减速");
} else {
write("严重超速!立即减速!");
}
逻辑运算符:你可以用 &&(与)、||(或)、!(非)组合多个条件:
if ((speed > 80) && (rpm > 3000)) {
write("高转速超速,建议降档");
}
if ((doorOpen == 1) || (seatbelt == 0)) {
write("请关好车门并系好安全带");
}
4.2 switch-case 多分支:出口选择器
当你有多个固定值需要判断时,if-else 写起来会又长又乱。这时候 switch-case 就派上用场了。
我习惯把它想象成高速出口:根据出口编号决定从哪下高速。
switch (变量) {
case 值1:
// 匹配值1时执行
break;
case 值2:
case 值3:
// 匹配值2或值3时执行
break;
default:
// 所有 case 都不匹配时执行
break;
}
实际例子——根据 CAN 报文 ID 处理不同信号:
switch (msgId) {
case 0x100:
write("收到发动机转速报文");
processEngineSpeed();
break;
case 0x200:
write("收到车速报文");
processVehicleSpeed();
break;
case 0x300:
case 0x301:
write("收到制动系统报文");
processBrake();
break;
default:
write("未知报文 ID: 0x%x", msgId);
break;
}
| 对比项 | if-else | switch-case |
|---|---|---|
| 适用场景 | 范围判断、复杂条件组合 | 固定值匹配、枚举类型 |
| 可读性 | 条件多时变差 | 分支清晰,一目了然 |
| 性能 | 逐条判断 | 跳转表,效率更高 |
| 支持类型 | 任意布尔表达式 | 整数、枚举、char |
4.3 while 循环:先判断,后执行
while 循环是最简单的循环结构:只要条件为真,就一直执行循环体。
while (条件表达式) {
// 循环体
}
举个例子——等待 CAN 总线进入特定状态:
int retryCount = 0;
while ((busState != 1) && (retryCount < 10)) {
write("等待总线就绪... 第%d次尝试", retryCount + 1);
retryCount++;
delay(100); // 等待100ms
}
if (busState == 1) {
write("总线就绪,开始通信");
} else {
write("总线超时,请检查硬件连接");
}
4.4 for 循环:计数循环的利器
for 循环适合已知循环次数的场景。它的结构比 while 紧凑:
for (初始化; 条件; 更新) {
// 循环体
}
三个部分的作用:
- 初始化:循环开始前执行一次,通常用来声明计数器
- 条件:每次循环前判断,为真则继续
- 更新:每次循环结束后执行,通常用来改变计数器
遍历 CAN 报文数组的典型用法:
int i;
for (i = 0; i < 10; i++) {
write("处理第%d条报文", i + 1);
processMessage(messageArray[i]);
}
你还可以在 for 的初始化部分直接声明变量(CAPL 支持 C99 风格):
for (int j = 0; j < 5; j++) {
write("j = %d", j);
}
4.5 do-while 循环:先执行,后判断
do-while 和 while 的区别在于:它至少执行一次循环体,然后再判断条件。
do {
// 循环体
} while (条件表达式);
什么时候用?比如你无论如何都要先发一次报文,然后再看是否需要重发:
int ackReceived = 0;
int attempt = 0;
do {
sendRequestMessage();
attempt++;
delay(50);
if (checkAcknowledge() == 1) {
ackReceived = 1;
}
} while ((ackReceived == 0) && (attempt < 3));
if (ackReceived == 1) {
write("请求成功,尝试次数: %d", attempt);
} else {
write("请求失败,已重试3次");
}
| 循环类型 | 执行时机 | 最少执行次数 | 典型场景 |
|---|---|---|---|
| while | 先判断后执行 | 0 次 | 等待条件成立 |
| for | 先判断后执行 | 0 次 | 固定次数遍历 |
| do-while | 先执行后判断 | 1 次 | 至少执行一次的操作 |
4.6 break 与 continue:循环的刹车和跳过
这两个关键字用来控制循环内部的执行流。
break:立即跳出整个循环,不再执行后续迭代。
for (int i = 0; i < 100; i++) {
if (messageArray[i].id == 0x500) {
write("找到目标报文,索引: %d", i);
break; // 找到了,不用再找了
}
}
continue:跳过本次循环的剩余部分,直接进入下一次迭代。
for (int i = 0; i < 10; i++) {
if (messageArray[i].valid == 0) {
continue; // 无效报文,跳过处理
}
// 只有有效报文才会执行到这里
processMessage(messageArray[i]);
}
4.7 综合示例:CAN 报文接收与重发逻辑
把今天学的串起来,写一个实际场景:接收诊断请求,如果校验失败则重发,最多重试 3 次。
void handleDiagnosticRequest()
{
int retryCount = 0;
int ackFlag = 0;
// do-while 保证至少发一次
do {
// 发送诊断请求
sendDiagRequest();
write("发送诊断请求,第%d次", retryCount + 1);
// for 循环等待应答,最多等 5 次
for (int wait = 0; wait < 5; wait++) {
delay(20);
if (diagResponseReceived == 1) {
// 校验应答
if (checkResponseCRC() == 1) {
ackFlag = 1;
write("应答校验通过");
break; // 跳出 for 循环
} else {
write("CRC 校验失败,准备重发");
break; // 跳出 for 循环,进入下一次重试
}
}
}
retryCount++;
// 如果已经成功,用 break 跳出 do-while
if (ackFlag == 1) {
break;
}
} while (retryCount < 3);
if (ackFlag == 1) {
write("诊断请求成功完成");
} else {
write("诊断请求失败,已重试%d次", retryCount);
}
}
- do-while 保证至少发一次请求
- for 循环等待应答,最多等 5 个周期
- if-else 判断 CRC 校验结果
- break 在成功或失败时跳出 for 循环
- 外层 if 判断成功后用 break 跳出 do-while
嗯,到这里 CAPL 的流程控制就全讲完了。你想想看,其实就三种结构:分支(if/switch)、循环(while/for/do-while)、跳转(break/continue)。任何复杂的程序逻辑,都能用这三板斧拆解清楚。
我个人建议你把这些代码亲手敲一遍,跑一跑。光看是记不住的——我当年学的时候也是边敲边踩坑,踩多了自然就熟了。