这一章在干嘛?

第 3 章写的脚本都是「从头跑到尾」一条直线。这一章让程序根据条件选择走哪条路——也就是所有编程语言里最核心的控制流:ifelseswitch。学完之后,你的脚本能「判断输入是否合法」、「按用户选择进入不同分支」、「抛出错误信息」,从而真正具备「与人交互的能力」。这一章也引入了「真 / 假」的底层规则和一组 isxxx 测试函数,为第 5 章的循环、第 6 章的程序结构铺好路。

4.1 if 语句:满足条件才执行

4.2 if-else:二选一

4.3 嵌套 if-else 与 elseif

4.4 switch 语句:值匹配式分支

4.5 is 系列函数

4.1 if 语句:满足条件才执行

关键词

选择语句(selection statement)分支(branching)条件(condition)动作(action)

if 是最基本的条件判断:先看一个条件是真还是假,就执行一段动作,就跳过:

if 条件
    动作    % 可以是一条语句,也可以是多条
end

注意结尾的 end配对的——这和向量下标里的 end 完全不同,虽然写法一样。动作通常缩进两格或四格,让代码一眼看出层级。

4.1.1 第一个例子:把负数变 0

if num < 0
    num = 0
end

试着在命令行连续敲两次,每次给 num 不同的初值:

>> num = -4;
>> if num < 0
    num = 0
    end
num =
     0           % 条件为真,进了 if 内部
>> num = 5;
>> if num < 0
    num = 0
    end
>>               % 条件为假,整个 if 被跳过,啥都没打印

第一次 num 是 -4,条件 num < 0 为真,num 被改成 0;第二次 num 是 5,条件为假,跳过 if 体,直接回到 >> 提示符。

if 也可以直接在命令行输入——敲完一行 if 条件 按 Enter,MATLAB 会进入「等待动作」模式,再敲动作、按 Enter、敲 end、按 Enter,整个 if 才被执行。这对临时调试特别方便,但正式程序里还是放进脚本里更整齐。

4.1.2 脚本里的典型用法:先校验再计算

if 最常见的用法是「先剔除非法输入,再做正常运算」。比如下面这个脚本 sqrtifexamp.m 处理用户输入的数字:负数自动变成 0,再算平方根:

% 文件:sqrtifexamp.m
% 提示用户输入一个数并打印平方根;负数先变成 0
num = input('Please enter a number: ');
if num < 0
    num = 0;
end
fprintf('The sqrt of %.1f is %.1f\n', num, sqrt(num))
>> sqrtifexamp
Please enter a number: -4.2
The sqrt of 0.0 is 0.0          % 负数被改成 0,sqrt(0)=0
>> sqrtifexamp
Please enter a number: 1.44
The sqrt of 1.4 is 1.2

如果想让脚本对负数更「友好」——告诉用户发生了什么,再改用绝对值——只要把 if 体换成多条语句:

if num < 0
    disp('OK, we''ll use the absolute value')   % 两个连续单引号 = 打印一个 '
    num = abs(num);
end
fprintf('The sqrt of %.1f is %.1f\n', num, sqrt(num))
>> sqrtifexampii
Please enter a number: -25
OK, we'll use the absolute value
The sqrt of 25.0 is 5.0

字符串里打单引号:连续两个 ''

MATLAB 字符串用单引号包,打印一个真实的单引号需要写成 ''(两个连续单引号)。这一点在 if 语句的提示语、error 信息里很常见。

4.1.3 临时变量:经典的「交换两个数」

>> a = 3; b = 5;
>> temp = a;     % 先把 a 的值备份到 temp
>> a = b;        % 把 b 拷给 a
>> b = temp;     % 把备份给 b
>> a
a =
     5
>> b
b =
     3

为什么不能直接写 a = b; b = a;?因为 a = b 已经把 a 的原值冲掉了,再 b = a 只是把刚拷过来的 5 再拷给 b,结果两边都变成 5。临时变量(temporary variable) temp 就是用来避免「原值被覆盖」的脚手架。

把这段思路用到函数上,就有了 createvec.m——不管用户先传小的还是先传大的,都保证结果是从小到大排:

function outvec = createvec(mymin, mymax)
% createvec 创建一个从最小值到最大值的等差整数向量
% 调用格式:createvec(minimum, maximum)
if mymin > mymax
    temp = mymin;
    mymin = mymax;
    mymax = temp;
end
outvec = mymin:mymax;
end
>> createvec(4,6)
ans =
    4 5 6
>> createvec(7,3)
ans =
    3 4 5 6 7

4.1.4 真与假在 MATLAB 里到底是什么

MATLAB 的逻辑判断和 C 语言有一处关键区别:任何非零值都算「真」,只有 0 算「假」——不只是 1 算真:

>> if 5
    disp('Yes, this is true!')
    end
Yes, this is true!           % 因为 5 是非零

这条规则有时会导致非常隐蔽的 bug。比如程序希望判断「用户输入的是 y 或 Y」:

% 正确写法
letter == 'y' || letter == 'Y'
 
% 错误写法(看似等价,其实恒为真!)
letter == 'y' || 'Y'         % 'Y' 本身就是非零,所以整个 || 永远真

第二行第一段 letter == 'y' 可能是假,但第二段 'Y' 单独存在就是「真」,于是 || 整体永远为真——不管 letter 是啥。这是初学者最常踩、却又不会被报错的雷。

常见坑:漏写 letter ==

误把 letter == 'y' || letter == 'Y' 写成 letter == 'y' || 'Y'——不会报错,但逻辑恒为真,是非常隐蔽的 bug。养成「每个 || 两侧都写完整表达式」的习惯。

4.1.5 if 语句的「ASCII 流程图」

为了把执行路径看清楚,画一个简单的流程图——if 体就是「真才走、假就绕」:

        ┌─────────────┐
        │   开始      │
        └──────┬──────┘
               ▼
        ┌─────────────┐
        │  判断条件    │
        └──────┬──────┘
         真 ───┴─── 假
         │         │
         ▼         ▼
   ┌──────────┐  ┌──────┐
   │  执行动作 │  │ 跳过 │
   └─────┬────┘  └──┬───┘
         └────┬─────┘
              ▼
         ┌─────────┐
         │   结束   │
         └─────────┘

if-else 则是「真走 A、假走 B」:

        ┌──────────┐
        │  判断条件 │
        └─────┬────┘
         真 ──┴── 假
         │      │
         ▼      ▼
      ┌───┐  ┌───┐
      │ A │  │ B │      两条路径有且仅有一条被执行
      └─┬─┘  └─┬─┘
        └──┬───┘
           ▼
        ┌──────┐
        │ 结束 │
        └──────┘

4.2 if-else:二选一

if 只能决定「做不做」;if-else 则决定「做 A 还是做 B」:

if 条件
    动作1       % 条件真时执行
else
    动作2       % 条件假时执行
end

两条动作有且仅有一条会被执行——这一点和「多个独立 if」不同。

下面用随机数示例演示最基本的形式:

>> if rand < 0.5
    disp('It was less than .5!')
    else
    disp('It was not less than .5!')
    end

rand 每次返回一个 [0, 1) 区间的随机数,所以上面这段代码有一半概率打印第一句,另一半打印第二句。

4.2.1 用 if-else 做错误检查

if-else 在工程代码里最常见的用途是输入校验。第 3 章那个「算圆的面积」脚本没检查半径正负,遇到 -4 也会输出一坨无意义的负数。改成 checkradius.m

% 文件:checkradius.m
% 计算圆的面积,并对半径做错误检查
radius = input('Please enter the radius: ');
if radius <= 0
    fprintf('Sorry; %.2f is not a valid radius\n', radius)
else
    area = calcarea(radius);
    fprintf('For a circle with a radius of %.2f,', radius)
    fprintf('the area is %.2f\n', area)
end
>> checkradius
Please enter the radius: -4
Sorry; -4.00 is not a valid radius
>> checkradius
Please enter a radius: 5.5
For a circle with a radius of 5.50, the area is 95.03

if vs. if-else 的取舍

教材的 Style Guideline 写得很明确:只要 else 不需要干任何事,就别写 else。例如只想在 unit == 'i' 时把英寸换算成厘米,没有反方向动作——只用一个 if 就够了。多余的 else len = len; 是噪声。

4.2.2 error 函数:让错误更显眼

fprintf 只能把错误信息用普通文本打印出来。MATLAB 提供专门的 error 函数,它把信息用红字打印出来,并在脚本里直接终止当前运行:

if radius <= 0
    error('Sorry; %.2f is not a valid radius\n', radius)
end

error('msg', ...)fprintf('msg', ...) 用法一模一样,差别就在它会抛异常(throwing an error)。在脚本里这样用最有效——一旦走到这行就停,不会让无效数据继续污染后面的计算。

4.2.3 经典范例:把 sin 角度模式串起来

下面这个脚本 printsindegorrad.m 把第 3 章的 I/O 和第 4 章的 if-else 串起来:先问角度、再问单位、最后按规则调用 sinsind

% 文件:printsindegorrad.m
angle = input('Enter the angle: ');
mode = input('(r)adians (default) or (d)egrees: ', 's');
if mode == 'd'
    val = sind(angle);              % sind 接受「度」
else
    val = sin(angle);               % sin 接受「弧度」
end
fprintf('The sin is %.2f\n', val)
>> printsindegorrad
Enter the angle: 45
(r)adians (default) or (d)egrees: d
The sin is 0.71
>> printsindegorrad
Enter the angle: pi
(r)adians (default) or (d)egrees: r
The sin is 0.00

注意 mode == 'd' 这种字符串相等比较——左右都用单引号包起来才是字符串字面量。如果一不小心写成 mode == d(没加单引号),MATLAB 会去找变量 d 的值;变量 d 不存在就直接报错。

4.2.4 字符串比较的两种姿势:== vs. strcmp

mode == 'd' 当 mode 是单个字符时能用;如果 mode 是字符串(如 'degree'),就要换 strcmp(mode, 'degree')

if strcmp(mode, 'degree')
    val = sind(angle);
elseif strcmp(mode, 'radian')
    val = sin(angle);
else
    disp('Unknown mode; treating as radians')
    val = sin(angle);
end

字符串比较速查

  • 单字符c == 'y'
  • 字符串strcmp(s1, s2),相等返回 1
  • 不区分大小写strcmpi(s1, s2)
  • 判断前缀startsWith(s, 'pre')
  • 判断包含contains(s, 'sub')

4.2.5 if vs. if-else 的「何时用哪个」

教材 Style Guideline 里有一条特别值得反复看:只有 else 真的需要做点啥时才写 else。看下面的反例:

% 错误示范:else 啥也没做,写了等于噪声
if unit == 'i'
    len = len * 2.54;
else
    len = len;       % 把值赋给自己,等于啥也没干
end
 
% 正确写法:省掉无意义的 else
if unit == 'i'
    len = len * 2.54;
end

同样的道理,elseif 后面的条件也不该写冗余判断:

% 错误示范:第二个条件完全多余
if number == 5
    disp('It is a 5')
elseif number ~= 5
    disp('It is not a 5')
end
 
% 正确写法:else 已经覆盖所有非 5 的情况
if number == 5
    disp('It is a 5')
else
    disp('It is not a 5')
end

一条经验法则

写完一个 if-else,问自己:「else 分支真的要做点什么吗?」如果答案是「不」,就删掉它。代码越短,bug 越少。

4.3 嵌套 if-else 与 elseif

当分支超过两个,比如下面这个分段函数:

用「三个独立 if」是可行的,但每条 if 都要把所有条件都判断一遍,效率低:

if x < -1
    y = 1;
end
if x >= -1 && x <= 2      % 注意:必须写两个子条件
    y = x^2;
end
if x > 2
    y = 4;
end

常见坑:链式比较 -1 <= x <= 2

if -1 <= x <= 2   % 错!这段代码永远为真
    ...
end

MATLAB 把 -1 <= x <= 2 当成「(-1 <= x) <= 2」——先算 -1 <= x 得到 0 或 1,再和 2 比较,结果总是真。必须写成「x >= -1 && x <= 2」,用 && 把两个独立条件显式连起来。这条规则和数学写法不一样,初学者极易栽跟头。

嵌套 if-else 能把互斥的分支「短路」起来——命中一条就退出,后面的条件不再判断:

if x < -1
    y = 1;
else
    % 走到这里意味着 x >= -1
    if x <= 2
        y = x^2;
    else
        % 走到这里意味着 x > 2
        y = 4;
    end
end

嵌套写法看起来像楼梯(cascading if-else),越写越长。多分支时 MATLAB 推荐用**elseif 子句**——结构清晰、只有一个 end

if 条件1
    动作1
elseif 条件2
    动作2
elseif 条件3
    动作3
else
    动作n
end

上面分段函数的最简版本:

if x < -1
    y = 1;
elseif x <= 2
    y = x^2;
else
    y = 4;
end

封装成函数 calcy.m,可以直接被脚本调用:

function y = calcy(x)
% calcy 根据 x 的范围计算 y
% y = 1     if x < -1
% y = x^2   if -1 <= x <= 2
% y = 4     if x > 2
if x < -1
    y = 1;
elseif x <= 2
    y = x^2;
else
    y = 4;
end
end
>> x = 1.1;
>> y = calcy(x)
y =
    1.2100

4.3.1 elseif 的实际威力:判断变量类型

下面这个 findargtype.m 函数用 size 拿到输入的行列数,再用 elseif 把「标量 / 向量 / 矩阵」三种情况分开。这是 elseif 最常见的应用——一组互斥的可能值

function outtype = findargtype(inputarg)
% findargtype 判断输入参数是标量、向量还是矩阵
% 调用格式:findargtype(inputArgument)
[r c] = size(inputarg);
if r == 1 && c == 1
    outtype = 'scalar';
elseif r == 1 || c == 1
    outtype = 'vector';
else
    outtype = 'matrix';
end
end
>> findargtype(33)
ans =
scalar
>> disp(findargtype(2:5))
vector
>> findargtype(zeros(2,3))
ans =
matrix

三种写法怎么选?

  • 互斥分支(如分段函数、变量类型)→ if-elseif-else,最干净。
  • 互不相关的多个判断(如「是字母且是元音」「偶数且大于 10」)→ 写多个独立 if,每个 if 独立判断。
  • 遍历每一个可能取值 → 多个 if 都可以,但通常换成 switch 更直观(下一节)。

4.3.2 把成绩映射成字母:letgrade 函数

下面 letgrade.m 把 0–10 的整数成绩翻译成 A/B/C/D/F,外加错误标志 X:

function grade = letgrade(quiz)
% letgrade 把整数 quiz 成绩转成字母等级
% 调用格式:letgrade(integerQuiz)
% 先做错误检查
if quiz < 0 || quiz > 10
    grade = 'X';
elseif quiz == 9 || quiz == 10
    grade = 'A';
elseif quiz == 8
    grade = 'B';
elseif quiz == 7
    grade = 'C';
elseif quiz == 6
    grade = 'D';
else
    grade = 'F';
end
end
>> quiz = 8;
>> lettergrade = letgrade(quiz)
lettergrade =
B
>> letgrade(4)
ans =
F
>> lg = letgrade(22)
lg =
X

注意第二个例子:letgrade(4) 落进 else 分支返回 F——所有 <= 5 的合法分数都映射到 F。

4.3.3 嵌套 vs. 级联的对照图

嵌套(nested)              级联(cascading)             elseif(推荐)
──────────────            ────────────────            ────────────────
if 条件1                  if 条件1                     if 条件1
  动作1                     动作1                        动作1
else                      elseif 条件2                 elseif 条件2
  if 条件2                  动作2                        动作2
    动作2                  elseif 条件3                 else
  else                     动作3                        动作3
    if 条件3              else                         end
      动作3                动作n
    else                  end
      动作n
    end
  end
end

常见坑: elseif 中间漏空格

else if(带空格)和 elseif(无空格)是两件不同的事!前者会被解析成「else + 单独的 if」,导致 end 不匹配;后者才是 elseif 子句。每次敲完都看一眼:「我想要的是哪一种?」

4.3.4 嵌套 / 级联 / elseif 的 ASCII 对照图

        嵌套 if-else(每一层都是一个完整的 if)       elseif(线性一条龙)
        ──────────────────────────────          ─────────────────────
        if 条件1                                if 条件1
        ┌─────┐                                 ┌─────┐
        │ 动作1│                                 │ 动作1│
        └──┬──┘                                 └──┬──┘
        else│                                    elseif 条件2
           ▼                                        ┌─────┐
        if 条件2                                      │ 动作2│
        ┌─────┐                                       └─────┘
        │ 动作2│                                    elseif 条件3
        └──┬──┘                                       ┌─────┐
        else│                                          │ 动作3│
           ▼                                            └─────┘
        if 条件3                                     else
        ┌─────┐                                       ┌─────┐
        │ 动作3│                                       │ 动作n│
        └──┬──┘                                       └─────┘
        else│                                        end
           ▼                                       ↑ 整个结构只有一个 end
        动作 n
        end
        ↑ 每一层都要单独写 end,容易漏

工程实践:分支数 ≤ 3 时用 if-elseif-else;分支数多或只是「等值映射」用 switch;判断之间相互嵌套或有先后约束才用嵌套 if-else。

4.3.5 区间归属:为什么 elseif x <= 2 已经够了?

初学者常问:「elseif x <= 2 难道不会和前面 if x < -1 冲突吗?比如 x = -2 既满足 x < -1,又满足 x <= 2?」

答案是:不会,因为 if-elseif-else 是「短路」的——一旦某个分支被命中,整条 if-elseif-else 就结束了,后面的条件不会再被判断。x = -2 会在第一行 x < -1 就被截走,永远到不了 elseif x <= 2

这种「短路」机制让 elseif 链的判断顺序很重要:把范围小的、特殊的条件放前面,把范围大的、兜底的条件放后面。上面 calcy.m 就是这个顺序:先判 x < -1、再判 x <= 2、最后兜底(else)。

等价改写:用 && 把区间写完整

同样的逻辑也可用「多个完整区间条件 + 独立 if」实现:

if x < -1
    y = 1;
elseif x >= -1 && x <= 2
    y = x^2;
else
    y = 4;
end

两种写法结果相同。elseif x <= 2 更简洁,但要求你清楚每个分支互斥;写完整区间更啰嗦,但更不容易出错。新手推荐先写完整区间,等熟练了再「偷懒」用 elseif 的短路。

4.4 switch 语句:值匹配式分支

switch 用「变量值是否等于某一组可能值」来分支,适合互斥的等值判断

switch 表达式
    case 值1
        动作1
    case 值2
        动作2
    otherwise
        动作n
end

与 if-elseif 的关键差别:switch 把 switch 表达式的结果只计算一次,然后依次和每个 case 比对;匹配到第一个就执行对应动作并退出整个 switch。

letgradeswitch 改写:

function grade = switchletgrade(quiz)
% switchletgrade 用 switch 把整数成绩转成字母
if quiz < 0 || quiz > 10
    grade = 'X';
else
    switch quiz
        case 10
            grade = 'A';
        case 9
            grade = 'A';
        case 8
            grade = 'B';
        case 7
            grade = 'C';
        case 6
            grade = 'D';
        otherwise
            grade = 'F';
    end
end
end

9 和 10 都对应 'A' 这种「多值映射到同一动作」的情况,用花括号把多个值包成一个 case

switch quiz
    case {10, 9}      % 多个值用 {} 包起来
        grade = 'A';
    case 8
        grade = 'B';
    % ... 其余类似
end

otherwise 子句是「前面所有 case 都不匹配时」的兜底分支——可以省略,但教材强烈建议保留(除非真的什么也不做)。otherwise 经常被当作「错误信息」的归集地:

choice = input('Enter a 1, 3, or 5: ');
switch choice
    case 1
        disp('It''s a one!!')
    case 3
        disp('It''s a three!!')
    case 5
        disp('It''s a five!!')
    otherwise
        disp('Follow directions next time!!')
end
>> switcherror
Enter a 1, 3, or 5: 4
Follow directions next time!!

4.4.1 switch vs. if-elseif 对照

维度switchif-elseif
判断依据表达式是否等于一组离散值任意逻辑表达式
典型用途菜单选择、状态机映射区间判断、复杂条件组合
多值合并case {a, b, c} 一次写完多条 elseif a || b || c
兜底分支otherwise(可选)else(可选)
switch_expression 类型限制必须是标量或字符串任意

switch 表达式必须是标量或字符串

switch [1 2 3]    % 错!向量不能当 switch 表达式
    ...
end

想对向量的每个元素分别判断,得用第 5 章的循环或向量化写法。

4.4.2 用 switch 重写随机风力的脚本

原书练习 22 给了一段用 switch 把蒲福风力等级(0–12)翻译成自然语言描述的代码——这是 switch 的教科书级应用:

ranforce = randi([0, 12]);     % 在 [0,12] 里随机取一个整数
switch ranforce
    case 0
        disp('There is no wind')
    case {1, 2, 3, 4, 5, 6}
        disp('There is a breeze')
    case {7, 8, 9}
        disp('This is a gale')
    case {10, 11}
        disp('It is a storm')
    case 12
        disp('Hello, Hurricane!')
end

这道题的姊妹练习是「把这段 switch 反过来改写成一条嵌套 if-else」,原书给的答案是嵌套 if-else 完全可以等价:

if ranforce == 0
    disp('There is no wind')
elseif ranforce <= 6
    disp('There is a breeze')
elseif ranforce <= 9
    disp('This is a gale')
elseif ranforce <= 11
    disp('It is a storm')
else
    disp('Hello, Hurricane!')
end

注意第二个写法利用了「ranforce 已经在 [0,12]」这一前提,省掉了上界检查。

4.4.3 if vs. switch 的「触发词」

经验上,读需求时如果出现下面这些词,思路应该不一样:

需求关键词建议结构
「如果…则…否则…」if / if-else
「满足以下任一条件…」if + ||
「都不满足…」if-else 的 else 分支
「当 x 等于 a/b/c/d」switch
「在 A 区间内…在 B 区间内…」嵌套 if-else / elseif
「不属于以上任何情况」otherwise / else

4.4.4 menu 函数:图形化的「switch 触发器」

R2015b 之前的 MATLAB 提供 menu 函数——它弹出一个带按钮的窗口,用户点哪个按钮就返回哪个数字,再交给 switch 处理:

choice = menu('Pick a shape', 'Circle', 'Rectangle', 'Triangle');
switch choice
    case 1
        disp('You picked Circle')
    case 2
        disp('You picked Rectangle')
    case 3
        disp('You picked Triangle')
    otherwise
        disp('No shape picked')
end

教材特意提醒:从 R2015b 起 menu 不再推荐使用,新的 GUI 方案在第 13 章讲 App Designer 时介绍。把它当历史常识记一下即可。

不要用 menu 写新项目

menu 在新版本里依然能跑,但官方不再维护。要做交互界面,第 13 章的 App Designer 是正路。

4.5 is 系列函数

MATLAB 内置了一堆名字以 is 开头的函数,统一返回逻辑值 1 或 0,用来快速判断「某个性质是不是真的」。它们可以直接当成 if 的条件,因为非零就是真。

函数用途
isletter(c)c 是字母吗?
isempty(v)v 是空数组吗?
iskeyword(s)s 是 MATLAB 保留字吗?
isa(x, 'double')x 是不是指定的类型?
isequal(a, b)a 和 b 完全相等吗?
isnumericisintegerislogicalisreal各种数值类型判断
ischarisstring字符/字符串类型判断
isvectorisrowiscolumn数组形状判断

任何 isxxx 函数都可以直接放进 if

if isletter(mychar)        % 简洁
    disp('Is a letter')
else
    disp('Not a letter')
end

不需要写成 if isletter(mychar) == 1——后面这种写法啰嗦且冗余。教材 Common Pitfalls 专门点名这一点:「(x < 5) == 1==1 是多余」。

4.5.1 自己写一个 myisletter

isletter 的内部实现其实就是「字符的 ASCII 编码在 azAZ 之间」。自己写一个练练手:

function outlog = myisletter(inchar)
% myisletter 如果输入字符是字母返回 1,否则返回 0
% 调用格式:myisletter(inputCharacter)
outlog = inchar >= 'a' && inchar <= 'z' ...
    || inchar >= 'A' && inchar <= 'Z';
end

inchar >= 'a' 这种写法之所以合法,是因为 MATLAB 字符可以按 ASCII 编码自动转成整数比较,字符排序与 ASCII 排序一致。

4.5.2 isempty:判断「有没有值」

isempty 检查一个变量是不是空数组。这在「判断用户是否真的输入了内容」时非常方便:

>> clear
>> isempty(evec)
Undefined function or variable 'evec'.
>> evec = [];
>> isempty(evec)
ans =
     1                    % 空数组 → 真
>> evec = [evec 5];
>> isempty(evec)
ans =
     0                    % 有元素 → 假

注意 isempty不存在的变量会报错(不像 Python 的 None)。要安全判断,得先 exist 或者用 try/catch(第 6 章详述)。

字符串版本同样适用——用户直接回车得到的就是空串:

istr = input('Please enter a string: ', 's');
if isempty(istr)
    disp('You entered nothing!')
else
    fprintf('You entered: %s\n', istr)
end

4.5.3 isa 与 iskeyword

isa(x, '类型名') 判断 x 是不是指定类型。常见用法:

>> num = 11;
>> isa(num, 'int16')
ans =
     0                       % num 是 double,不是 int16
>> isa(num, 'double')
ans =
     1

iskeyword(s) 判断字符串是不是 MATLAB 保留字——也就是不能用作变量名的那批字(ifelseendfunctionswitch 等)。空参的 iskeyword 会列出所有关键字:

>> iskeyword('sin')
ans =
     0                       % sin 是函数名,可以被覆盖(不建议)
>> iskeyword('switch')
ans =
     1                       % switch 是关键字,不能用作变量

sinlength 这种函数名不是关键字

因为它们不是关键字,理论上你可以写 sin = 5 把内置的 sin 函数覆盖掉——然后所有用到正弦的代码都会出错**。** 用 clear sin 才能救回来。这条坑教材里没单独列,但属于「MATLAB 给人最大自由度带来的副作用」,要小心。

4.5.4 综合实战:写一个「安全输入」包装函数

把所有学到的 is 函数串起来,可以做一个 getnumber.m:反复提示用户输入数字,直到他输入合法为止:

function n = getnumber(prompt)
% getnumber 反复提示,直到用户输入合法数字为止
% 调用格式:getnumber('promptString')
prompt = [prompt ': '];      % 拼上冒号空格
while true
    s = input(prompt, 's');   % 当字符串读进来,避开变量名陷阱
    if isempty(s)
        disp('Nothing entered, try again')
        continue
    end
    n = str2double(s);        % 字符串转数字
    if isnan(n)
        disp('Not a number, try again')
        continue
    end
    break                     % 输入合法,跳出循环
end
end

里面用了:

  • isempty 检查空输入;
  • str2double 把字符串解析成数字;
  • isnan 判断「不是数字」的结果(str2double('hello') 返回 NaN);
  • 第 5 章会学的 while true 无限循环加 break 跳出。
>> n = getnumber('Please enter a number')
Please enter a number: hello
Not a number, try again
Please enter a number: t
Not a number, try again
Please enter a number: 42
n =
    42

这种「重试 + 校验」的模式

是工程程序最常用的输入处理套路。MATLAB 的 inputdlgvalidateattributes 等内置函数,本质上也是把这段逻辑封装得更通用。先手动写一次,能更深刻理解输入校验为何要这么繁琐。

4.5.5 try/catch:另一种「选择结构」

教材最后提到了一个特殊的条件处理工具 try/catch:先把可能出错的代码塞进 try,出错就让 catch 接住,避免整个脚本崩溃:

try
    x = input('Enter a number: ');
    y = sqrt(x);
    fprintf('sqrt is %.2f\n', y)
catch
    disp('Something went wrong; using default value 0')
    y = 0;
end

try/catch 本质上是一种自动错误检测版的 if-else——只要 try 体里抛了任何 error,就跳到 catch 体执行。教材原话是「may be a bit complicated to understand at this point, but keep them in mind for the future」,意思是「现在不懂没关系,第 6 章还会回来」。

不要用 try/catch 替代正常 if

  • try/catch 是用来兜住意料之外的崩溃,不是用来判断「用户输入是否合法」。合法输入校验该用 if,否则会失去错误信息的可读性。

本章小结

本章通关标准:

  • 能用 if / if-else / elseif / switch 写出至少三种典型分支结构。
  • 能解释「MATLAB 里非零就是真」这条规则,并知道它会引出什么隐蔽 bug。
  • 知道 elseif(无空格)和 else if(带空格)的本质区别。
  • 至少用过 isletterisemptyisa 中的两个,能自己写一个简单的 isxxx

常见坑:把这一章最容易栽的几个雷汇总

  1. 链式比较 a < x < b:MATLAB 不支持,必须拆成 a < x && x < b
  2. letter == 'y' || 'Y' 恒为真:每一段 || 都得是完整的逻辑表达式。
  3. elseif 中间加了空格 变成 else if:导致 end 对应错误。
  4. ifelseif 后写 ==1:冗余,写 if isletter(c) 就好。
  5. 用 if 而不是 if-else 做错误检查:报错信息还会打印,但程序依然执行了非法计算。

下一章预告

第 5 章讲「循环结构(loop)」——forwhile,还有 breakcontinue 这些控制循环走向的小开关。把第 3 章的脚本、第 4 章的选择结构、第 5 章的循环三件套拼起来,就能写「重复做某件事直到条件满足」的真正程序了。

附录:选择结构速查表

一图看懂 if / if-else / 嵌套 / elseif / switch

想做的事推荐结构
仅在条件成立时做点什么,其它情况啥也不干单个 if ... end
条件成立做 A、不成立做 Bif-else
互斥的多档区间(如分段函数)if-elseif-elseswitch
互斥的多档「等于」判断switch-case
多种独立条件,可同时成立多个独立 if
包含复杂区间判断的复合条件if + &&/||

拿到一道题时先看:

  1. 分支之间是否互斥?(→ 走 if-else 系列)
  2. 判断条件是不是「值等于某几个常量」?(→ 走 switch
  3. 有没有「合法输入先校验」的需求?(→ 走 if-else + error

附录:三个「课后练习」级综合实例

下面三个例子把本章所有元素串在一起。建议先自己想 5 分钟再看答案。

例 1:风力强度判断

蒲福风力(Beaufort Scale)把风力分成 0–12 共 13 档。原书 Practice 21 给的是「按平均风速判断风暴类型」:

风速(mph)类型
< 38热带低压
39–73热带风暴
≥ 74飓风
% 文件:stormtype.m
wind = input('Enter the wind speed in mph: ');
if wind < 38
    disp('Tropical depression')
elseif wind <= 73
    disp('Tropical storm')
else
    disp('Hurricane')
end

注意 elseif 的区间是开闭方向要写对:第一档 <、第二档 <=、第三档兜底。MATLAB 的 elseif 短路保证了不管 wind 是 0 还是 1000,都会精确归入一档。

例 2:流体力学连续方程

流体力学里有个连续方程:对不可压缩流体,。给两个截面面积 ,可以判断第二点的速度是「增加 / 减少 / 不变」:

% 文件:flowcompare.m
A1 = input('Enter area at point 1 (sq ft): ');
A2 = input('Enter area at point 2 (sq ft): ');
if A1 > A2
    disp('Velocity at point 2 increases')
elseif A1 < A2
    disp('Velocity at point 2 decreases')
else
    disp('Velocity at point 2 remains the same')
end

物理直觉:管子越细,水流越快——A1 更大 → A2 更细 → 速度上升。

例 3:判定数字奇偶

最简单但最能体现「理解 if 含义」的练习:随机生成一个整数,判断奇偶。

n = randi([1, 100]);       % [1, 100] 的随机整数
if rem(n, 2) == 0          % rem = remainder(余数)
    fprintf('%d is even\n', n)
else
    fprintf('%d is odd\n', n)
end

rem(n, 2)n / 2 的余数——余 0 是偶数,余 1 是奇数。这道题虽然小,但「随机生成 + 条件判断 + 格式化输出」三件套是从命令行到真实程序的标志性跨越。

把这一章做成可携带的「速查卡片」

写完代码后,把这一章末尾的速查表和你自己总结的常见错误清单贴到编辑器旁边的便签里。以后写任何 if 之前先看一眼:分支是不是互斥?判断是不是等值?要不要带 error?三个问题答完,选哪种结构基本就不会选错。