流程控制

在划分控制结构所对应的代码块时,编程语言通常采用如下三种做法:

  • 基于大括号{}:例如C/C++,Java
  • 基于end标记:例如FORTRAN,MATLAB
  • 基于缩进:Python

MATLAB 受到 FORTRAN 的影响很大,也采用基于end的代码块标记,并不使用大括号{}来划分代码结构。

if 条件语句

提供例子即可

1
2
3
if x>1
x=1;
end
1
2
3
4
5
if x>1
y=x;
else
y=1;
end
1
2
3
4
5
6
7
if x>10
y=x;
elseif x>0
y=1;
else
y=0
end

switch 条件语句

MATLAB支持基本的switch语句,我们可以判断表达式的值以进入不同的分支,不需要在分支结束使用break,因为不会进入下一个分支,默认分支为otherwise。MATLAB并不要求case后面的结果是常量。

1
2
3
4
5
6
7
8
switch x
case 1
z=1
case 2
z=2
otherwise
z=3
end

对于更复杂的switch情况,我们还可以使用下面的“反转”技巧,判断表达式取为true,在case中加入不同的布尔表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
score = 85;

switch true
case (score >= 90)
disp('Grade: A');
case (score >= 80)
disp('Grade: B');
case (score >= 70)
disp('Grade: C');
case (score >= 60)
disp('Grade: D');
otherwise
disp('Grade: F');
end

for 循环语句

非常不建议基于 for 循环(尤其是多重 for 循环)来遍历矩阵进行计算,因为这种做法的效率非常低下,不能照搬使用 C/C++ 时直接写 for 循环的习惯,使用 MATLAB 提供的向量化运算是更好的选择。

for循环语句的标准格式如下

1
2
3
for index = startValue:step:endValue
% 循环体代码
end

和其他编程语言通常采用的左闭右开区间不同,这里的索引区间实际上是闭区间

例如

1
2
3
4
for i = 1:2:11
disp(i);
end
% 1 3 5 7 9 11

对于步长为1的情况,可以省略步长

1
2
3
4
for i = 1:10
disp(i);
end
% 1 2 3 4 5 6 7 8 9 10

可以用双重循环来遍历一个矩阵

1
2
3
4
5
6
B = [1, 2;3, 4];
for i = 1:size(B, 1)
for j = 1:size(B, 2)
disp(B(i, j));
end
end

和其他语言一样,在循环中支持如下选项:

  • break跳出当前循环;
  • continue跳转进入下一次循环。

注意:

  • 关于循环指标:由于i,j默认是虚数单位,如果将其作为循环变量,虽然在语法上不会报错,但是这会修改它们的值,后续不能将其用作虚数单位。(可以使用i=1i, j=1j来恢复)
  • 如果不使用向量化的语法,那么大规模的 for 循环(尤其是多层循环)很可能就是可行计算程序的性能瓶颈,需要特别注意循环体内部的语句细节,这里不做讨论。

for 遍历语句

我们可以用下面的语法方便地遍历行向量中的每一项

1
2
3
4
A = [1 2 3];
for i=A
disp(i)
end

对于矩阵,遍历语句每次会获取一列

1
2
3
4
5
6
A = [1 2 3
4 5 6];

for i=A
disp(i)
end

输出

1
2
3
4
5
6
7
8
1
4

2
5

3
6

while 循环语句

while循环没什么好说的,和其他语言没什么区别,例如

1
2
3
4
5
w=0;u=0;

while u<10
w=w+u;u=u+1;
end

同样也支持breakcontinue语句。

补充

上述结构可以随意嵌套,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
for c = 1:ncols
for r = 1:nrows

if r == c
A(r,c) = 2;
elseif abs(r-c) == 1
A(r,c) = -1;
else
A(r,c) = 0;
end

end
end

在单条语句的简单情况下,也可以使用单行形式,例如

1
2
3
4
5
6
7
8
9
10
% if 条件, 语句; end
if a > 0, disp('a is positive'); end
if ~isempty(TitleStr), title(TitleStr); end


% if 条件, 语句1; else 语句2; end
if x > 0, disp('Positive'); else disp('Non-positive'); end

% for 变量 = 范围, 语句; end
for i = 1:5, fprintf('%d ', i); end

虽然MATLAB将字符数组和字符串,字符串数组进行了区分,但是出于兼容性考虑,在下面的各种输入输出方式中无论是使用字符数组还是字符串都是一样的效果。

显示变量的值

disp函数可以用来显示一个变量的值,如果这个变量是字符串的话,也可以达到输出信息的效果,但是会自动添加一个回车。

例如

1
2
disp('hello,world!');
% hello,world!

这里的disp(X)语句加不加;都是一样的。

disp函数只接受一个参数,我们可以将字符数组拼接起来进行显示

1
2
disp(['hello', ',', 'world']);
% hello,world

在很多默认行为中都会调用disp函数,例如一个普通的赋值语句如果不以;结尾,可能会对赋值结果调用disp函数以展示它的值。 对于自定义类型也可以通过定义 disp 方法来达到自定义输出效果的目的。

格式化字符串

MATLAB支持和C语言几乎一样的字符串格式化函数,包括fprintfsprintf

fprintf格式化输出到控制台或文件中,返回值只是输出的字节数,没什么用,例如

1
2
3
4
5
fprintf('Hello, %s!\n', 'World'); % 缺省时输出到控制台
Hello, World!

fileID = fopen('exp.txt','w'); % 打开文件
fprintf(fileID,'Hello, %s!\n', 'World'); % 写入文件中

fileID 是获取的文件句柄(其实就是一个大于2的整数),1代表标准输出流,2代表标准错误输出流。

sprintf格式化输出到一个字符串,返回值就是格式化得到的字符串,例如

1
2
3
4
5
6
name = 'Alice';
age = 30;
height = 5.5;
str = sprintf('Name: %s, Age: %d, Height: %.1f feet', name, age, height);

disp(str);

除此之外,下面几个函数也是支持格式化字符串并输出的,不需要额外生成message(得益于MATLAB对不定参数的支持,不需要像C语言那么麻烦)

1
2
3
4
5
6
7
8
9
10
11
n = 7;

assert(isa(c,'double'),'Product is type %s, not double.',class(c))

if ~ischar(n)
warning('Input must be a character vector, not a %s',class(n))
end

if ~ischar(n)
error('Error. \nInput must be a char, not a %s.',class(n))
end

文件读写

在 MATLAB 中,文件打开模式与 C 语言非常类似。

使用fopen函数打开文件,除了文件名之外,还可以指定不同的模式来控制文件的读写行为,常见的文件打开模式包括:

  • r:只读模式。文件必须存在,否则会出错。
  • w:写入模式。如果文件存在,将覆盖文件;如果文件不存在,将创建新文件。
  • a:追加模式。如果文件存在,数据将写入文件末尾;如果文件不存在,将创建新文件。
  • r+:读写模式。文件必须存在,否则会出错。
  • w+:读写模式。如果文件存在,将覆盖文件;如果文件不存在,将创建新文件。
  • a+:读写模式。如果文件存在,数据将写入文件末尾;如果文件不存在,将创建新文件。

此外,还可以指定文本模式或二进制模式:

  • t:文本模式(默认)。
  • b:二进制模式。

fopen函数返回的是文件ID,后续对这个文件的操作都需要传入文件ID,包括最后的关闭文件fclose

下面列举几个常见的文件操作:

逐行读取文本并展示

1
2
3
4
5
6
7
8
fileID = fopen('example.txt', 'r');

while ~feof(fileID)
line = fgetl(fileID);
disp(line);
end

fclose(fileID);

逐行写入文本

1
2
3
4
5
6
fileID = fopen('output.txt', 'w');

fprintf(fileID, 'This is a test.\n');
fprintf(fileID, 'The value of pi is approximately %.4f\n', pi);

fclose(fileID);

写入二进制文件

1
2
3
4
5
6
7
fileID = fopen('binaryoutput.bin', 'wb');

data = [1.1, 2.2, 3.3];

fwrite(fileID, data, 'double'); % 以double格式写入

fclose(fileID);

读取二进制文件,这里重新读取出来的data变成列向量了。

1
2
3
4
5
6
7
fileID = fopen('binaryoutput.bin', 'rb');

data = fread(fileID, 'double'); % 以double格式读取

disp(data);

fclose(fileID);

写入文件前确保目录存在

1
2
3
4
5
[filePath, ~, ~] = fileparts(fileName);
if ~isempty(filePath) && ~exist(filePath,'dir')
mkdir(filePath);
end
fileID = fopen(fileName, 'w');

MAT文件读写

MATLAB 专门提供 saveload 函数,用于保存和加载 .mat 文件,这是一种 MATLAB 特有的二进制文件格式,可以完整存储工作空间中的变量,包括变量的类型、大小和其他元数据。

保存变量

使用save函数可以直接把当前工作区的所有变量保存到MAT文件中

1
save('all_data.mat')

我们也可以将指定的部分变量保存到MAT文件中

1
2
3
p = rand(1,10);
q = ones(10);
save("pqfile.mat","p","q")

可以以追加形式把数据添加到MAT文件中

1
2
3
D = ones(2);

save("mydata.mat", "D", "-append")

注意:

  • 如果文件已经存在,默认会直接覆盖原始文件,除非加上-append选项;
  • load命令其实需要一个格式选项,默认是-mat,也支持-ascii等其他格式,但是对文本格式的操作不够灵活,建议使用fprintf;对格式的判断与文件后缀名无关。

可以使用whos命令查看MAT文件当前存储的变量列表,得到的是一个结构体数组

1
2
fileInfo = whos("-file", "mydata.mat");
disp(fileInfo);

可以加上-struct选项解包一个结构体数组,将其中的所有字段作为单独变量存储到MAT文件中

1
2
3
4
s1.a = 12.7;
s1.b = {"abc",[4 5; 6 7]};
s1.c = "Hello!";
save("newstruct.mat","-struct","s1")

可以加上MAT文件的格式版本,默认7.0版本,但是只有7.3版本可以支持超过2GB的数据

1
2
3
A = rand(5);
B = magic(10);
save("example.mat","A","B","-v7.3")

可以使用-nocompression选项,这个选项会阻止存储过程中的压缩,优点是存储过程更快,缺点是MAT文件变大

1
2
3
A = rand(5);
B = magic(10);
save("example.mat","A","B","-v7.3","-nocompression")

一个常见的需求是,MAT文件中已经存储了我们关注的若干变量,我们希望使用工作区中的同名变量更新MAT文件中的数据,但是不引入工作区中的其他变量

1
2
existingVars = whos("-file","mydata.mat");
save("mydata.mat",existingVars.name)

加载变量

使用load函数可以直接把MAT文件中的所有变量加载到工作区中

1
load('mydata.mat');

也可以指定加载MAT文件中的部分变量到工作区中(这里甚至支持通过正则匹配加载满足的部分变量)

1
load('mydata.mat', 'var1', 'var2', ...);

注意:

  • 如果当前工作区的变量和MAT文件中的变量重复,那么MAT文件中的变量值就会直接覆盖它!
  • 在加载MAT文件时我们可能需要传递文件格式,默认格式是-mat,也支持-ascii等选项,格式的判断与文件后缀无关;

我们可以使用变量接受load的返回值,这样会把所有变量加载到一个结构体数组中,作为结构体数组的字段,而不是直接暴露在工作区中

1
2
3
4
data = load('mydata.mat');

disp(data.A);
disp(data.B);