第4章 流程控制(一):IF-ELIF-ELSE 条件判断,让宏学会做决定
写宏跟写故事有点像。故事有情节分支,宏也有执行路径。
没有条件判断的宏,就像一条单行道,从头走到尾,不会拐弯。但实际光学设计哪有这么简单?镜片厚度超差了要报警,像质达标了要跳过后处理,材料换掉了要重新计算...这些「如果...就...」的逻辑,全靠 IF-ELIF-ELSE 来实现。
我个人习惯把条件判断叫做「宏的决策中枢」。你想想看,一个不会做决定的宏,跟一个只会背台词的机器人有什么区别?
4.1 为什么需要条件判断?
先看一个真实场景。
我在项目中写过这样一个宏:自动优化一组变焦镜头。每个焦段跑完优化后,需要检查 MTF 是否达标。达标了就输出「Pass」,不达标就自动调整权重重新跑。
没有 IF 语句的话,你得手动盯着屏幕,一条条看日志,然后手动改参数。有了条件判断,宏自己就能做这个决定。
核心思想:条件判断让宏具备「感知状态 → 做出选择 → 执行对应动作」的能力。这是宏从「死脚本」进化为「智能工具」的第一步。
4.2 IF-ELIF-ELSE 的基本语法
Code V 的宏语言里,条件判断的写法很直观。先看一个最简单的例子:
! 检查系统是否包含非球面
IF (ASP(1) .GT. 0) THEN
PRINT '系统中包含非球面'
ELSE
PRINT '系统中无非球面'
END IF
这段代码在做什么?
ASP(1) 返回系统中非球面的数量。如果大于 0,打印第一句话;否则打印第二句话。
语法要点:
- IF 后面跟条件表达式,必须用括号括起来
- THEN 不能省略,它是条件块的开始标记
- ELSE 是可选的,处理条件为假的情况
- END IF 是结束标记,必须成对出现(注意中间有空格)
我曾经踩过的坑:刚开始写宏时,我经常忘记写 END IF。Code V 不会报错,但宏会跳过整个条件块,直接执行后面的代码。调试了半天才发现是少了个结尾。后来我养成习惯:写 IF 的同时就把 END IF 敲好,再往中间填内容。
4.3 多分支:ELIF 的用法
现实中的判断往往不止两个分支。比如判断一个镜头的类型:
! 根据焦距判断镜头类型
IF (EFL() .LT. 10) THEN
PRINT '广角镜头'
ELIF (EFL() .LT. 50) THEN
PRINT '标准镜头'
ELIF (EFL() .LT. 200) THEN
PRINT '长焦镜头'
ELSE
PRINT '超长焦镜头'
END IF
注意看,ELIF 是 ELSE IF 的缩写。Code V 两种写法都支持,但我个人更推荐用 ELIF,少敲几个字符,代码也更紧凑。
执行逻辑是这样的:
- 从上往下依次判断每个条件
- 遇到第一个为真的条件,执行对应的代码块
- 执行完后直接跳到 END IF 后面,不再检查后面的条件
- 如果所有条件都不满足,执行 ELSE 块(如果有的话)
小技巧:条件的顺序很重要。把最可能满足的条件放在前面,可以减少判断次数。我在处理大批量数据时,这个习惯能明显提升宏的运行速度。
4.4 条件表达式:比较运算符与逻辑运算符
条件判断的核心是「条件表达式」。Code V 支持以下比较方式:
| 运算符 | 含义 | 示例 |
|---|---|---|
.GT. |
大于 | A .GT. B |
.GE. |
大于等于 | A .GE. B |
.LT. |
小于 | A .LT. B |
.LE. |
小于等于 | A .LE. B |
.EQ. |
等于 | A .EQ. B |
.NE. |
不等于 | A .NE. B |
多个条件还能组合:
! 检查系统是否满足设计规范
IF ((EFL() .GT. 100) .AND. (WFNO() .LT. 2.8)) THEN
PRINT '焦距大于100mm,且F数小于2.8'
END IF
! 或者用 OR
IF ((SI(SI) .LT. 0.1) .OR. (SI(SI) .GT. 1.0)) THEN
PRINT '球差超出合理范围'
END IF
逻辑运算符有三个:.AND.、.OR.、.NOT.。注意它们前后都有点号,这是 Fortran 风格的遗留,别写错了。
避坑指南:我曾经在写 .AND. 时漏掉了前面的点号,写成 AND.。Code V 把它当成了变量名,结果条件永远为真。这种错误很难排查,建议写完条件后,用 PRINT 把条件结果打出来验证一下。
4.5 嵌套条件:条件里面套条件
有时候一个条件不够用,需要在条件内部再做判断。比如:
! 检查镜片是否可加工
IF (CT(1) .GT. 0.5) THEN
IF (CT(1) .LT. 20.0) THEN
PRINT '中心厚度在可加工范围内'
ELSE
PRINT '中心厚度过大,需要减薄'
END IF
ELSE
PRINT '中心厚度过小,无法加工'
END IF
嵌套的层数理论上没有限制,但我建议不要超过三层。超过三层后,代码的可读性会急剧下降。我自己最多用到两层,再多就拆成子函数了。
个人习惯:嵌套条件时,我每层缩进 2 个空格,并且把 END IF 的层级对齐。这样一眼就能看出哪个 IF 对应哪个 END IF。
4.6 知识体系总览
下面这张图总结了本章的核心内容,帮你建立整体认知:
4.7 实战案例:自动判断镜片类型并输出报告
把今天学的知识串起来,写一个实用的宏:
! 自动判断镜片类型并输出加工建议
! 作者:你的名字
! 日期:2024-01-15
PRINT '=== 镜片可加工性分析报告 ==='
PRINT ''
! 获取镜片参数
CUR = CV(1) ! 第一面曲率
CTH = CT(1) ! 第一面中心厚度
DIA = SD(1) ! 第一面口径
! 判断曲率类型
IF (CUR .EQ. 0.0) THEN
SURF_TYPE = '平面'
ELIF (CUR .GT. 0.0) THEN
SURF_TYPE = '凸面'
ELSE
SURF_TYPE = '凹面'
END IF
! 判断厚度是否合理
IF (CTH .LT. 0.3) THEN
THK_STATUS = '过薄,建议增加厚度'
ELIF (CTH .GT. 25.0) THEN
THK_STATUS = '过厚,建议减薄'
ELSE
THK_STATUS = '厚度合理'
END IF
! 判断口径与厚度的比例
RATIO = DIA / CTH
IF (RATIO .GT. 10.0) THEN
RATIO_STATUS = '口径/厚度比过大,加工易变形'
ELSE
RATIO_STATUS = '口径/厚度比合理'
END IF
! 输出报告
PRINT '镜片类型:', SURF_TYPE
PRINT '中心厚度:', CTH, ' mm'
PRINT '厚度状态:', THK_STATUS
PRINT '口径/厚度比:', RATIO
PRINT '比例状态:', RATIO_STATUS
PRINT ''
PRINT '=== 分析完毕 ==='
这个宏做了三件事:判断面型、检查厚度、评估比例。每个判断都是独立的,互不干扰。输出结果一目了然。
扩展思路:你可以把这个宏改造成一个循环,批量检查整个镜头系统中所有镜片。下一章我们会讲循环,到时候再回来升级这个案例。
4.8 常见错误与调试技巧
写条件判断时,有几个错误我几乎每周都会遇到:
- 忘记 END IF:Code V 不会报错,但条件块后面的代码会异常。检查方法:数一下 IF 和 END IF 的数量是否一致。
- 条件表达式没加括号:
IF EFL() .GT. 10 THEN是错的,必须写成IF (EFL() .GT. 10) THEN。 - ELIF 拼写成 ELSEIF:两种写法都支持,但别写成
ELSE IF(中间有空格),那会被解析成 ELSE 后面跟了一个新的 IF。 - 嵌套缩进混乱:不缩进也能运行,但调试时会让你怀疑人生。我建议用 2 或 4 个空格缩进,保持统一。
调试口诀:条件不对就 PRINT,变量不对就 PRINT,逻辑不对还 PRINT。PRINT 是宏调试最趁手的工具,没有之一。
好了,条件判断的内容就这些。说白了就是「如果...就...否则...」的逻辑,跟日常做决策没什么两样。多写几次,你就能找到感觉。