这一章在干嘛?

前两章你只在「命令行」里和 MATLAB 一问一答地算题。第 3 章开始把多行命令保存成 脚本(script),让程序能反复跑、能从键盘和文件读数据、能画带标签的图,最后写出第一个自己定义的 函数(function)。整个第 6 章会展开,这里只挑最常用的一小块。学完这章,你能把「算圆的面积」从一行交互式命令升级成「输入半径 → 算出面积 → 输出完整句子」的可重用程序。

3.1 算法与脚本:先把思路画出来

3.2 MATLAB 脚本:把命令存到文件里

3.3 输入与输出:让脚本与人对话

3.4 完整的 I/O 脚本示例

3.5 用脚本画图并美化

3.6 文件读写:load 与 save

3.7 用户自定义函数:返回值的那种

3.8 命令与函数的等价写法

3.1 算法与脚本:先把思路画出来

关键词

算法(algorithm)自顶向下设计(top-down design)模块化程序(modular program)

写代码之前,先把要做什么、做到什么程度、按什么顺序做,写成一步一步的描述——这就是「算法」。原书把「算圆的面积」拆成三步:

  1. 输入(input):拿到半径。
  2. 计算(calculate):面积 = π × 半径²。
  3. 输出(output):把结果打印给用户。

MATLAB 编辑器界面:HOME 工具栏下的 "New Script" 按钮、Current Folder 窗口、Editor 窗口的三块区域

图 3.1:MATLAB 编辑器界面

上方是 HOME 工具栏(左侧的「New Script」就是新建脚本的入口),左下角是 Current Folder(当前文件夹),下面就是 Editor(编辑器)。脚本文件必须保存在「当前文件夹」里,MATLAB 才能直接用文件名运行它,不必写完整路径。

这三步对几乎所有程序都适用。即使算法这么简单,每一步还能继续往下拆——比如「输入」就要再拆成「提示用户输入」和「把用户输入读进来」。这就是自顶向下设计:从粗到细、层层拆解,直到每一步都小到能直接写代码。

为什么不在编辑器里直接写代码?

因为写程序 = 把脑子里「怎么算」的思路翻译成「怎么写」的两件事。如果一上来就敲代码、边写边想思路,思路会被零碎的语言细节带跑。养成「先列步骤、再敲代码」的习惯,能在第 5 章之后遇到更难的题时省下大量返工时间。

3.2 MATLAB 脚本:把命令存到文件里

MATLAB 用 脚本文件(.m 文件) 保存一段命令序列。.m 是 MATLAB 默认的文件后缀,俗称「M 文件」。在编辑器里输入下面这段,保存为 script1.m

% 文件:script1.m
% 计算一个圆的面积
radius = 5
area = pi * (radius ^ 2)

保存后,命令行敲 script1(不带 .m)就能运行:

>> script1
radius =
     5
area =
   78.5398

也可以先用 type script1 让 MATLAB 在命令行把文件内容回显出来,再确认要不要执行。修改文件就双击 Current Folder 里的文件名——编辑器和命令行是双向打通的。

3.2.1 注释与 help 命令

% 在 MATLAB 里表示「单行注释(comment)」,从 % 到行尾的所有字符在运行时被完全忽略。注释是给读代码的人看的,机器看不见。

H1 行(H1 line)」:文件第一行连续的注释块,是 help 文件名 唯一会显示的内容。lookfor 命令也只在 H1 行里搜索关键字,所以这一行要写成一句话的功能描述。

% 文件:circlescript.m
% This script calculates the area of a circle
% First the radius is assigned
radius = 5
% The area is calculated based on the radius
area = pi * (radius ^ 2)
>> help circlescript
 This script calculates the area of a circle

注意 help 只显示了第一行——第二行因为前面有空行隔开,被认为不属于「连续的注释块」了。需要更长的多行注释,用 %{ ... %}注释块,comment block),两个标记必须各自独占一行:

%{
  本块用于长注释
  可以写很多行
  直到遇到配对的 %}
%}

常见坑:函数体里忘了写分号

脚本里的赋值语句如果不加分号,每次运行都会把中间结果「喧哗」地打印出来,干扰最终输出。所以函数和脚本里的赋值语句几乎永远要加分号;打印交给专门的 dispfprintf 来做。

3.2.2 文件名的命名规则

.m 文件名必须遵守 MATLAB 变量名规则:以字母开头,后面只能跟字母、数字、下划线,不能有空格、连字符或中文。和变量一样,MATLAB 大小写敏感Script1.mscript1.m 是两个完全不同的文件。养成「全程小写 + 下划线分隔单词」的好习惯,例如 circle_io.mCircleIO.m 更耐看。

3.3 输入与输出:让脚本与人对话

光会算不行,还得能接受输入、产生输出,这就是 I/O(input/output)。MATLAB 默认的输入设备是键盘,默认的输出设备是屏幕。

3.3.1 input:键盘录入

input 把用户在命令行敲的内容读成 MATLAB 的变量。最简单的用法是读一个数:

>> rad = input('Enter the radius: ')
Enter the radius: 5
rad =
     5

如果想让用户输入字符串而不是表达式,必须加第二个参数 's'(意思是 “string”):

>> letter = input('Enter a char: ', 's')
Enter a char: g
letter =
    'g'

常见坑: input('prompt', 's') 与裸 input 的本质区别

  • 不加 's' 时,MATLAB 把用户输入当作 MATLAB 表达式:输入 5 得到数 5,输入 t 会去找变量 t,输入 pi 得到 π。
  • 's' 时,用户输入原文照搬地存成字符串,包括空格、引号都被吃进变量里。

漏写 's' 是初学者最常遇到的字符输入 bug。如果用户输错了,MATLAB 还会再抛一次 Undefined function or variable 'xxx' 错误,更让人误以为是自己代码错了。

3.3.2.1 prompt 的写法细节

提示文本末尾通常加一个冒号 + 空格': ')再让用户敲输入,这样光标和提示语之间有一格距离,看起来不那么拥挤。教材里反复出现的 'Enter the radius: ' 就是这个习惯的典型。读向量也一样——input('Enter a vector: ') 让用户输 [3 8 22]1:2:10,MATLAB 都按合法语法解析:

>> v = input('Enter a vector: ')
Enter a vector: [3 8 22]
v =
     3     8    22

3.3.2 disp:最简单的输出

disp 是「display」的缩写,把字符串或表达式的值打到屏幕上,不会自动保存到 ans,也没有格式化能力:

>> disp('Hello')
Hello
>> disp(4^3)
    64

打印矩阵、向量时 disp 特别干净:

>> mat = [15 11 14; 7 10 13];
>> disp(mat)
    15    11    14
     7    10    13

3.3.3 fprintf:格式化输出

要想在输出里夹数字、控制小数位数、对齐字段,就要请出 fprintf。它和 C 语言的 printf 是同一个套路:第一个参数是格式字符串,后面是要打印的值

>> fprintf('The value is %d, for sure!\n', 4^3)
The value is 64, for sure!
>>

格式字符串里的「占位符(conversion character)」决定下一个值怎么被打印:

占位符含义
%d十进制整数(decimal integer)
%f浮点数(real number)
%c单个字符
%s字符串

末尾的 \n 是「换行符(newline character)」——不写它,下一条 >> 提示符会黏在同一行末尾,特别难看。

常见坑:忘了 \n

disp 会自动换行,fprintf 不会。每次 fprintf 末尾都补一个 \n,是写 MATLAB 程序的基本礼貌。

**字段宽度(field width)**让数字对齐:%5d 表示总占 5 列、%6.2f 表示总共 6 列(含小数点和 2 位小数)。宽度太窄会自动撑开:

>> fprintf('%3.2f\n', 1234.5678)   % 宽度 3 不够,MATLAB 会扩成 7
1234.57

更精细的格式:左对齐用 %-5d、字符串截断用 %.2s。常用转义还有 \t(制表符)、\\(打印单个反斜杠)、''(打印单个单引号,因为字符串本身是用单引号包的)。

3.3.4 打印向量和矩阵

fprintf 把向量按列优先顺序「拆开」:

>> vec = 2:5;          % 2 3 4 5
>> fprintf('%d\n', vec)
2
3
4
5

矩阵更明显——按依次打印:

>> mat = [5 9 8; 4 1 10];   % 2×3
>> fprintf('%d %d %d\n', mat)
5 4 9          % 第一列两个元素横着打
1 8 10         % 第二列两个元素横着打

要想保留原本「一行一行」的样子,转置一下再打:

>> fprintf('%d %d %d\n', mat')
5 9 8
4 1 10

经验之谈

临时调试矩阵用 disp(mat);要写文件、固定位数才用 fprintf

3.3.5 在 prompt 里用换行

input 的提示字符串里可以塞 \n 实现换行——这是唯一一个被 input 接受的格式字符,其它 %d\t 都会被原样打印:

>> x = input('Enter the \nx coordinate: ');
Enter the
x coordinate: 4

3.3.6 vsprintf 家族:fprintf 的两个近亲

除了 fprintf(打印到屏幕或文件),MATLAB 还提供两个不常被提到但用起来很顺手的姊妹:

  • sprintf:把格式化结果塞回一个字符串变量,不打印任何东西。常用于生成文件名、拼接 SQL、构造图表标题。
  • disp:不接受格式串,但会自动在末尾加换行、且不会污染 ans,适合一次性把矩阵或字符串怼到屏幕上。

举例感受一下:

>> s = sprintf('Result = %.3f', sqrt(2));
>> s
s = 'Result = 1.414'
>> disp(['Hello, ' 'world'])      % disp 接受字符串拼接
Hello, world

3.4 完整的 I/O 脚本示例

把上面所有东西拼起来,就是一个真正有用的脚本 circleIO.m

% 文件:circleIO.m
% 这个脚本计算并打印一个圆的面积
% 它会向用户询问半径,然后输出包含单位和说明的完整句子
fprintf('Note: the units will be inches.\n')
radius = input('Please enter the radius: ');
area = pi * (radius^2);
% 按句子格式打印所有变量
fprintf('For a circle with a radius of %.2f inches,\n', radius)
fprintf('the area is %.2f inches squared\n', area)

运行一次:

>> circleIO
Note: the units will be inches.
Please enter the radius: 3.9
For a circle with a radius of 3.90 inches,
the area is 47.78 inches squared

注意:每个赋值语句末尾的 ; 把中间结果压住,最终输出完全由两条 fprintf 控制——这就是「脚本控制输出格式」的范本。

常见坑: % 到底是注释还是占位符?

在 MATLAB 代码行首的 % 是注释;在 fprintf格式字符串内部%d%f 里的 % 是占位符——这两件事毫无关系。新手看到 fprintf('%d', x) 时常怀疑自己写错了,其实完全正确。

3.5 用脚本画图并美化

MATLAB 自带强大的绘图能力,把多步绘图保存成脚本才是日常用法。plot(x, y, '...') 是最基础的命令:第三个参数是 LineSpec 字符串,描述颜色、线型、点型,三类属性可以自由组合。

3.5.1 颜色、线型、点型

类别取值(单字符)
颜色r 红、g 绿、b 蓝、k 黑、y 黄、m 品红、c 青、w
点型. 实点、* 星号、o 圆圈、+ 加号、x 叉号、s 方块、d 菱形、^ 上三角、v 下三角
线型- 实线、: 点线、-. 点划线、-- 虚线,不写则只画点

例如 'r*' 表示「红色星号」,'ko' 表示「黑色圆圈」。

3.5.2 第一个画图脚本:单点

% 文件:plotonepoint.m
% 一个非常简单的「只画一个点」的脚本
% 创建坐标,画一颗红星
x = 11; y = 48;
plot(x, y, 'r*')
% 改坐标轴范围并打标签
axis([9 12 35 55])
xlabel('Time')
ylabel('Temperature')
% 加标题
title('Time and Temp')

axis([xmin xmax ymin ymax]) 一次性设置坐标范围,默认坐标轴没有任何标签,所以 xlabelylabeltitle 三件套是标配。

红色星号 (11, 48) 落在坐标轴中央,标题「Time and Temp」

图 3.2:用 plot 画出的一个点

注意坐标轴是 axis([9 12 35 55]) 手动指定的,否则 MATLAB 会按数据范围自动收缩,第一/最后点会贴在边框上看不清。

3.5.3 折线图:传两个等长向量

>> x = 1:6;
>> y = [1 5 3 9 11 8];
>> plot(x, y)

(1,1)(2,5)(3,3)(4,9)(5,11)(6,8) 6 个点用折线连起来

图 3.3:plot(x, y) 把两组数据点连成折线

默认没有 LineSpec 时画的是「实线连接的折线」,坐标轴范围是数据范围自动算出来的。如果 x 就是 1:n,可以直接 plot(y),MATLAB 会用索引当 x 轴。

3.5.4 多图与图例

下面这个脚本一次开两个 Figure,第一个放柱状图,第二个把两条曲线叠在一起,并加网格和图例:

% 文件:plot2figs.m
% 演示多窗口、hold、legend、grid
clf
x = 1:5;
y1 = [2 11 6 9 3];
y2 = [4 5 8 6 2];
% Figure 1:柱状图
figure(1)
bar(x, y1)
% Figure 2:两条曲线叠在一起
figure(2)
plot(x, y1, 'k')
hold on
plot(x, y2, 'ko')
grid on
legend('y1', 'y2')

Figure 1:蓝色柱状图展示 y1 在 x=1..5 上的取值

图 3.4 (A):bar 函数画柱状图

bar(x, y1) 是柱状图的标配,barh 是横过来的柱状图。

Figure 2:黑色折线 + 黑色圆点叠加显示 y1 和 y2,网格 + 图例齐备

图 3.4 (B):hold on 把两条曲线叠在同一坐标系

hold 是「冻结」切换开关:hold on 之后再 plot,新曲线叠在旧曲线上而不替换;hold off 关闭。legendplot 的调用顺序把字符串和曲线配对,grid 加底纹。

3.5.5 画图脚本里其它常用小函数

除了 plot,脚本里还经常配合下面几个帮手:

  • clf:清空当前 Figure 窗口的所有内容,从头再来。
  • figure(n):打开编号为 n 的 Figure(不存在就创建)。多窗口并存时用 figure(1)figure(2) 切换。
  • hold on/off:冻结/解冻当前图,新 plot 是否叠加。
  • legend('A','B', ...):在角落加图例框,字符串顺序必须对应 plot 调用顺序
  • grid on/off:开关网格线。
  • bar(x, y):柱状图;barh 是横向版本。
  • axis([xmin xmax ymin ymax]):硬性指定坐标范围(解决边点贴边的问题)。

画图脚本的标准范式:先准备 xy 数据 → plotaxis 调范围 → xlabel/ylabel/title 三件套 → 有多条曲线就 legend。每幅图都按这个流程写,画出来的图就「能直接放进报告」。

把连续函数画出来更直观。MATLAB 的便利之处在于:把向量传给函数(如 sin)时,函数会对向量的每个元素分别计算——这就是所谓「向量化(vectorization)」:

% 文件:sinncos.m
% 在 [0, 2π] 范围内同时画 sin 和 cos
clf
x = 0: 2*pi/40: 2*pi;
y = sin(x);
plot(x, y, 'ro')
hold on
y = cos(x);
plot(x, y, 'b+')
legend('sin', 'cos')
xlabel('x')
ylabel('sin(x) or cos(x)')
title('sin and cos on one graph')

sin 与 cos 在同一窗口对比,红色圆点是 sin,蓝色加号是 cos

图 3.5:sin 与 cos 同图对比

步长 2*pi/40 表示每 1/20 个周期取一个点,已经足够平滑。也可以一行写完:plot(x, sin(x), x, cos(x)),免去 hold on

3.6 文件读写:load 与 save

实际工程数据不会让人一行一行敲,最常见的是从 .dat.txt 文件里读数字,或者把计算结果写出去。

3.6.1 save:把变量写入文件

>> mymat = rand(2,3)
mymat =
   0.4565   0.8214   0.6154
   0.0185   0.4447   0.7919
>> save testfile.dat mymat -ascii

文件 testfile.dat 里写的是文本形式的数字(-ascii 强制用 ASCII),可以用 type testfile.dat 看:

4.5646767e-001 8.2140716e-001 6.1543235e-001
1.8503643e-002 4.4470336e-001 7.9193704e-001

常见坑:save 默认是二进制

不带 -ascii,save 会写二进制 .mat 文件——人能读懂的是文本数字,机器读的是结构化数据。给同伴共享或用其他软件读时,几乎一定要 -ascii

3.6.2 -append:在文件尾追加

>> save testfile.dat mat2 -ascii -append

追加写入仍然受「每行列数相同」这条潜规则约束——以后才能再 load 回矩阵。行列数不一致的数据需要第 9 章的低级 I/O 函数。

3.6.3 load:把文件读成变量

>> load testfile.dat
>> who                % 列出当前工作区所有变量
Your variables are:
testfile             % load 自动用「文件名(不含扩展名)」作为变量名
>> testfile
testfile =
   0.4565   0.8214   0.6154
   0.0185   0.4447   0.7919
   0.9218   0.4057   0.4103
   0.7382   0.9355   0.8936
   0.1763   0.9169   0.0579

如果想把读出来的数据放到指定变量名,用函数式:points = load('pointcoords.dat')

常见坑: load 不是万能文件读取器

它要求文件里每一行的元素个数完全一致,否则会按「字符串」或者乱七八糟的结构读进来。读混合类型(既有数字又有表头文字)的 CSV、读非矩形的实验记录、读带分隔符的 JSON 之类,都得用第 9 章的 fopen/fscanf/fgetl 低级 I/O——load 只适合规规矩矩的「数字矩阵文件」。

3.6.4 实例 —— 读温度文件并画图

假设 timetemp.dat 里两行数据:第一行是时间(小时),第二行是对应温度:

0   3    6    9    12   15   18   21
55.5 52.4 52.6 55.7 75.6 77.7 70.3 66.6

下面这个脚本读入数据、按行拆成时间和温度两个向量、用 k*(黑色星号)画出散点:

% 文件:plottemp.m
% 从文件读入下午的时间和温度数据,画图
load timetemp.dat
% 第一行是时间,第二行是温度
time = timetemp(1, :);
temp = timetemp(2, :);
% 画图 + 标注
plot(time, temp, 'k*')
xlabel('Time')
ylabel('Temperature')
title('Temperatures one afternoon')

下午温度随时间变化:黑色星号散点,0 点的值紧贴 y 轴不易看见

图 3.6:从文件读取并绘制的温度散点图

数据本身就是文本,直接用编辑器新建文件、键入数字、保存为 timetemp.dat 即可——不必先在 MATLAB 里建矩阵再 save 出去。

更典型的应用是「两家公司、两组数据,画到同一张图上做对比」。假设 salesfigs.dat 里两行数据分别存 A、B 两个部门四个季度的销售额:

1.2 1.4 1.8 1.3
2.2 2.5 1.7 2.9

下面的脚本把它读成矩阵,按行拆成两条曲线,再用不同的点型区分:

% 文件:plotsales.m
load salesfigs.dat
% 第一行是部门 A,第二行是部门 B
divA = salesfigs(1, :);
divB = salesfigs(2, :);
quarter = 1:4;
plot(quarter, divA, 'ko', quarter, divB, 'k*')
legend('Division A', 'Division B')
xlabel('Quarter')
ylabel('Sales (billions)')
title('ABC corporation sales: 2013')

ABC 公司 2013 年两部门季度销售额对比,圆圈是 A,星号是 B

图 3.7:双部门销售额散点对比

注意 plot(x, y1, 'ko', x, y2, 'k*') 这种「多组数据一次画」的写法比 hold on 链式叠两次更紧凑,也是工业代码里最常见的范式。

文件顺序乱了怎么办?

如果文件的行序和你想要的顺序相反,flipud(矩阵) 可以把矩阵上下翻转,列顺序也跟着翻——这是处理实验数据时最常用的「数据纠错」函数:matlab correctorder = flipud(expresults); save neworder.dat correctorder -ascii

3.7 用户自定义函数:返回值的那种

到现在为止我们只用 MATLAB 自带的函数(sinpilength 等)。这一节学自己写函数。

关键词

函数(function)函数头(function header)输入参数(input argument)输出参数(output argument)函数体(function body)局部变量(local variable)作用域(scope of variables)

3.7.1 函数定义的四要素

function 输出参数 = 函数名(输入参数1, 输入参数2, ...)
% 紧贴函数头的注释块 = help 命令显示的内容
% 也叫 H1 行 + 详细说明
   ...函数体(计算过程)...
   输出参数 = ...   % 必须给输出参数赋值
end

第一行(函数头)约定返回值类型、函数名和入口参数;下面紧跟注释块是 help 的输出;中间是函数体;结尾 end 收口(早期版本必须有,现在省略也能跑,但写上更稳)。

3.7.2 第一个自定义函数

把「算圆的面积」抽成函数 calcarea.m

function area = calcarea(rad)
% calcarea calculates the area of a circle
% Format of call: calcarea(radius)
% Returns the area
area = pi * rad * rad;
end

调用方式与内置函数一致:

>> calcarea(4)               % 不写赋值,结果丢给 ans
ans =
   50.2655
>> area = calcarea(5)        % 自己接住返回值
area =
   78.5398
>> mya = calcarea(5.2);      % 加分号,运行时啥都不打印

命名铁律: 函数名 = 文件名calcarea 函数必须存在 calcarea.m 里,否则 MATLAB 会找不到或猜错同名文件。

3.7.3 函数的「副作用」—— 传向量时记得用 .*

如果传入向量而函数体里用 * 而不是 .*,会触发「矩阵乘法维数不匹配」:

>> calcarea(1:3)
Error using *
Inner matrix dimensions must agree.

* 改成 .* 就能支持向量(元素逐个相乘):

function area = calcareaii(rad)
% calcareaii 返回一个圆的面积
% 输入参数可以是向量
% 调用格式:calcareaii(radiiVector)
area = pi * rad .* rad;   % 标量 × 元素逐乘
end
>> calcareaii(1:3)
ans =
    3.1416   12.5664   28.2743

3.7.4 一个完整的「程序」= 脚本 + 函数

第 3 章专门把「脚本调用函数」叫作一个程序(program)——这是教材的术语约定,本书后文都用这层意思。下面这个 circleCallFn.m 调用 calcarea.m,两个文件必须放在同一当前文件夹:

% 文件:circleCallFn.m
% 输入半径,调用 calcarea 算面积,再打印结果
radius = input('Please enter the radius: ');
area = calcarea(radius);
fprintf('For a circle with a radius of %.2f, ', radius)
fprintf('the area is %.2f\n', area)
>> circleCallFn
Please enter the radius: 5
For a circle with a radius of 5.00, the area is 78.54

脚本调用函数的标准结构:左侧 script.m 三步走 (Get input → Call fn to calculate result → Print result),右侧 fn.m 实现具体计算并 return 结果

图 3.8:一个简单程序的标准结构

脚本负责「输入 → 调用 → 打印」的编排工作,函数只关心算法实现。这种「主线在脚本、细节在函数」的分层写法,是后面所有中等规模程序的骨架。

3.7.5 多输入参数:圆锥体积

圆锥体积 ,需要半径和高度两个参数:

function outarg = conevol(radius, height)
% conevol calculates the volume of a cone
% Format of call: conevol(radius, height)
% Returns the volume
outarg = (pi/3) * radius .^ 2 .* height;
end

调用时实参顺序必须对应形参顺序

>> conevol(4, 6.1)
ans =
  102.2065
>> fprintf('The cone volume is %.2f\n', conevol(3, 5.5))
The cone volume is 51.84

.^.* 同时出现,是为了兼容「传向量」——半径向量平方后再和高度向量元素逐乘,前提是两个向量长度相同。

3.7.6 局部变量与作用域

函数里临时用的中间变量叫局部变量(local variable),只在该函数运行期间存在。看下面这个算封闭圆柱材料成本的函数 cylcost.m

function outcost = cylcost(radius, height, cost)
% cylcost 计算制作封闭圆柱所用材料的总成本
% 调用格式:cylcost(radius, height, cost)
% radius/height 单位是英寸,cost 单位是美元/平方英尺
% 1. 算总表面积(平方英寸)
surf_area = 2 * pi * radius .* height + 2 * pi * radius .^ 2;
% 2. 换算成平方英尺,并向上取整
surf_areassf = ceil(surf_area / 144);
% 3. 乘以单价
outcost = surf_areassf .* cost;
end
>> cylcost(32, 73, 4.50)
ans =
   661.5000

surf_areasurf_areassf 在函数返回后立即消失,在命令行敲会报「未定义」。

常见坑:脚本 vs. 函数的作用域

脚本里的变量和命令行共享同一个工作区base workspace),脚本跑完变量还在;函数有自己的工作区,函数跑完除了返回的输出参数,其它变量都不可见。这点对刚接触 MATLAB 的人最反直觉——函数就像一个密封的工厂,原料(输入参数)从厂门进,成品(输出参数)从厂门出,里面用啥临时设备,外面一概不知。

3.8 命令与函数的等价写法

MATLAB 有些命令是「命令式(command syntax)」——直接敲名字+参数,不加括号、不接返回值,比如 typesaveload。它们的本质其实是函数,MATLAB 允许两种写法等价:

>> type script1                 % 命令式
>> type('script1')              % 函数式

上面两条完全等价。

特别提一下 load:用命令式时,MATLAB 会默认用文件名做变量名。想自己起名,用函数式最方便:

>> points = load('pointcoords.dat')   % 直接存到 points
points =
    3.3000    1.2000
    4.0000    5.3000

命令式 vs. 函数式的取舍

命令式短,敲着省事,但没有返回值没有变量名可指定。任何需要把结果送进后续计算的场合,都优先用函数式。教材后文凡是要把文件内容塞进特定变量的,都会写成 data = load('xxx.dat')info = type('xxx.m') 这种形式。

本章回顾:常见的小题目类型

学完这一章,你应该有能力做下面这几类小题。每一题都对应了原书 3.x 节后的 Practice 题目,先看思路再写代码:

① 输入 + 计算 + 输出三件套

例如求三角形面积、球的体积、抛物线在某个 x 处的 y 值。关键套路:先把所有变量从键盘收齐 → 算 → fprintf 一句话输出,保留合适的小数位。

② 一组输入走两条分支

例如「输入英尺或米,统一输出米」:先用 input(..., 's') 收单位字符,再 if strcmp(unit, 'm') 决定要不要换算。这是第 4 章选择结构的预演。

③ 读数据文件 → 拆向量 → 画图

例如从 .dat 读两列数据画成散点图。注意先验证数据load 完用 disp(data)size(data) 看看行列数对不对,再继续画图,否则调试时根本不知道是哪一步出问题。

④ 写一个返回单值的小函数

例如把摄氏度转华氏度、mwh_to_gj(兆瓦时转吉焦)、已知长宽求矩形面积。函数体常常只有一行加 end,但H1 注释块和命名一致性要做到位,这是后面所有大程序的「基本工」。

本章小结

本章通关标准:

  • 能把「输入 → 计算 → 输出」三步法写成一个有 input + fprintf 的完整脚本。
  • 看见 %d / %f / %s / %c / %5d / %.2f 就知道怎么选。
  • 知道 scriptfunction 的差别:脚本共享 base workspace,函数有自己的工作区。
  • 能写出 function out = foo(in) 形式、文件名与函数名一致的函数,并能用 .* 兼容向量入参。

常见坑:把 3 条最容易踩雷总结一下

  1. 字符与字符串输入忘了加 's':导致用户输入被当作 MATLAB 表达式解读,字母就报错,变量名就会被替换。
  2. fprintf 末尾忘 \n:下一次提示符黏在上一行末尾,屏幕看起来乱七八糟。
  3. 函数名和文件名不一致:脚本能跑,函数调用就报「Undefined function」。

进阶坑:input 输入变量名时被「吃掉」

>> t = 11;
>> num = input('Enter a number: ')
Enter a number: t
num =
    11

因为没加 's',MATLAB 把 t 当变量名解读,把它的值 11 取进来。这其实是一个合法但易混的副作用:想要「字符串」必须显式 's',否则用户输入永远是「表达式」。

进阶坑: dispfprintf 混用错位

  • disp 不需要 \n——它每次调用都自动换行。
  • fprintf 必须靠 \n 换行;混用容易出现「disp 完后留下空白行」「fprintf 不换行导致下一条命令黏住」的怪现象。
  • 一段输出里全用 fprintf 最稳;disp 只在你只想要「最快打印一个东西」时再用。

,下面两种写法哪个对?为什么?

A = input('Enter vec: ')        % 写法 1
B = input('Enter vec: ', 's')   % 写法 2

写法 1 正确:用户输入 [1 3 5 7] 被当作 MATLAB 表达式解析,A 得到 [1 3 5 7] 数值向量;写法 2 把整段字符(包括方括号和空格)当成字符串 '[1 3 5 7]',不能直接当数值用。

下一章预告

第 4 章开始学「选择结构(selection statements)」——ifif-elseelseifswitch、以及一堆 isxxx 函数。掌握了这一章的脚本和函数基础,下一章才能真正写出「根据输入决定做什么」的程序。