第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,少敲几个字符,代码也更紧凑。

执行逻辑是这样的:

  1. 从上往下依次判断每个条件
  2. 遇到第一个为真的条件,执行对应的代码块
  3. 执行完后直接跳到 END IF 后面,不再检查后面的条件
  4. 如果所有条件都不满足,执行 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 知识体系总览

下面这张图总结了本章的核心内容,帮你建立整体认知:

IF-ELIF-ELSE 条件判断知识体系 条件判断 语法结构 条件表达式 嵌套与实战 IF-THEN ELIF ELSE END IF 比较运算符 逻辑运算符 函数返回值 嵌套条件(建议≤3层) 条件顺序优化 调试:PRINT验证 核心原则 先写 END IF 再填内容 · 条件顺序按概率排列 · 嵌套不超过三层 写完条件用 PRINT 验证 · 缩进对齐保持可读性

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 是宏调试最趁手的工具,没有之一。

好了,条件判断的内容就这些。说白了就是「如果...就...否则...」的逻辑,跟日常做决策没什么两样。多写几次,你就能找到感觉。