MATLAB学习笔记3:MATLAB编程基础(后半)

阅读前请注意:

1. 该学习笔记是华中师范大学HelloWorld程序设计协会2021年寒假MATLAB培训的学习记录,是基于培训课堂内容的总结归纳、拓展阅读。博客内容由 @K2SO4钾 撰写、编辑,发布于 @K2SO4钾 的个人投稿与华中师范大学HelloWorld程序设计协会CSDN官方账号 @CCNU_HelloWorld注意,如需转载需得到作者 @K2SO4钾 的同意与授权!

2. 学习笔记基于 《MATLAB R2018a完全自学一本通》(刘浩, 韩晶) 1 ,笔记中增加了很多程序示例和笔者个人的思考。学习笔记面向刚接触MATLAB的新手,内容偏基础。学习前请自行下载安装MATLAB。笔记中示例用MATLAB版本为MATLAB R2019a

3. 请谅解笔记可能会出现的错误,欢迎指正讨论;由于MATLAB更新导致的旧代码无法使用的情况,也欢迎讨论交流。


3.0 MATLAB笔记:传送门汇总


3.0.1 MATLAB学习笔记:传送门汇总


MATLAB学习笔记0:学习须知

MATLAB学习笔记1:MATLAB概述

MATLAB学习笔记2:MATLAB基础知识(上)

MATLAB学习笔记3:MATLAB编程基础(前半)

MATLAB学习笔记3:MATLAB编程基础(后半)


3.0.2 MATLAB拓展学习:传送门汇总


MATLAB拓展学习T1:匿名函数和内联函数

MATLAB拓展学习T2:程序性能优化技术

MATLAB拓展学习T3:histogram函数详解

MATLAB拓展学习T4:数据导入


3.0.3 MATLAB专题博客:传送门汇总


MATLAB-S1:元胞自动机原理及MATLAB实现

MATLAB-S2:图像处理技术


3.0.4 MATLAB应用实例:传送门汇总


MATLAB应用实例:Floyd算法

MATLAB应用实例:简单数字滤波技术的MATLAB实现

MATLAB应用实例:无限脉冲响应数字滤波器


施工中~请耐心等待





3.3 控制程序流辅助指令


控制程序流辅助指令对于6大程序流的编程起到辅助作用,其中break、continue指令应重点掌握。


3.3.1 break指令


break指令通常在循环结构中使用,其作用是退出当前循环体(即终止执行for或while循环)。执行break语句后,后面的内容不再执行

break通常在循环中与if语句结合使用。每一次循环中,判断if语句是否符合,若符合,继续执行循环,若不符合,则使用break语句跳出循环。这样就不需要知道循环的终点是什么,只要if语句判断为不符合,break会自动跳出循环。并且,使用break指令也可以减少代码的冗余运行(即让循环体符合条件就跳出,不执行多余的循环)。

break语句

clc,clear all,close all
i=1;
while(1)
    if i==10
        fprintf('跳出循环\n')
        break;
        fprintf('测试输出') % 这句话永远都不会输出,想一想是为什么
    else
        fprintf('循环执行了%d次\n',i)
    end
    i=i+1;
end

☆ 例3-19:对于数列 a n = l n ( n ) ( n > 0 且 为 整 数 ) a_{n}=ln(n)(n>0且为整数) an=ln(n)(n>0),求符合 a i a i − 1 a i + 1 ≤ 1000 , ( i ≥ 2 ) a_{i}a_{i-1}a_{i+1}\leq 1000 ,(i\geq 2) aiai1ai+11000,(i2)表达式的 i i i的最大值。

分析: 思考一下,为什么结果是i-1而不是i呢?

clc,clear all,close all
n=1:1000000;
array=log(n); % 构造数列an=log(n)
for i=2:length(array)-1 % 循环从i=2一直到n的最大值-1
    m=array(i)*array(i-1)*array(i+1);
    if m>1000
        break; % 不符合条件时跳出循环
    end
end
fprintf('i的最大值为:')
i=i-1 % 思考一下,为什么是i-1而不是i呢?(把代码好好分析一下)

在这里插入图片描述



3.3.2 continue指令


continue指令通常在循环结构中使用,其作用是跳过该循环体的本次循环,执行该循环体的下一次循环(即将控制权传递给 for 或 while 循环的下一迭代)。执行continue语句后,本次循环continue后面的内容不再执行(即跳过当前迭代的循环体中剩余的任何语句)

continue语句

clc,clear all,close all
i=1;
for i=1:20
    if i==10
        fprintf('跳出循环\n')
        break;
    end
    if i==2||i==4||i==6||i==8
        fprintf('执行continue,其后所有语句在本次循环不执行\n')
        continue;
    end
    fprintf('循环体被执行\n')
end

☆ 例3-20:编程实现以下功能,使用continue指令:对矩阵A= [ 1 2 ⋯ 10 11 12 ⋯ 20 ⋮ ⋱ ⋮ 91 92 ⋯ 100 ] \begin{bmatrix} 1 & 2 & \cdots & 10\\ 11 & 12 & \cdots & 20\\ \vdots & & \ddots & \vdots \\ 91 & 92 & \cdots & 100 \end{bmatrix} 11191212921020100,若元素可被3整除,则将其取平方,否则计数至counter,且不对其作操作。

分析: 可以使用循环的方式构造矩阵A,也可以采用10个行向量结合的方式。其编程思路是:使用for循环的嵌套遍历矩阵。若元素不可被3整除,计数后continue;若可被3整除,取平方。

clc,clear all,close all
A=[1:10;11:20;21:30;31:40;41:50;51:60;61:70;71:80;81:90;91:100];
counter=0;
for i=1:10
    for j=1:10
        if mod(A(i,j),3) % 若不能被3整除
            counter=counter+1;
            continue;
        end
        A(i,j)=A(i,j)^2;
    end
end
A

在这里插入图片描述



3.3.3 input指令


该指令让MATLAB控制权暂时交给用户,用户从键盘上输入数值、字符串或表达式,按下Enter键后MATLAB将用户输入内容赋给value并存放于工作区,并且收回控制权。

input指令有两种调用方式:

value=input(‘prompt’) % 将用户输入内容(数值、字符串或表达式)赋值给value
value=input(‘prompt’,‘s’) % 将用户输入的所有内容字符串的形式赋给value

其中,'prompt’作为提醒用户输入内容的字符串(或文本)将会展示在命令行窗口。

input指令

clc,clear all,close all
value1=input('请输入一个矩阵:')
fprintf('\n')
value2=input('请输入一个数:')
fprintf('\n')
value3=input('请输入一个字符串:')
fprintf('\n')
value4=input('请输入一个字符串:','s')

3.3.4 ginput指令


ginput指令把图形窗口控制权交给用户,允许来自用户的鼠标或光标的图形输入(鼠标左键点击,或者在光标处按下键盘任意键)。

常用调用方式为 [x,y]=ginput(n),其作用是从当前轴标识鼠标输入的n个点(n为正整数)并在x和y列向量中返回这些点的x和y坐标。按Return键(即Enter键)可在输入n个点之前终止输入。

可以通过axis([xmin,xmax,ymin,ymax])调整坐标轴大小。


☆ 例3-21:在横坐标(0,10)、纵坐标(40,50)范围内任意选取5个点,并将它们连接起来。


分析:

clc,clear all,close all
axis([0,10,40,50])
[x,y]=ginput(5)
plot(x,y)

ginput

3.3.5 pause指令


pause指令用于控制执行文件的暂停与恢复。调用方式如下:

pause或pause():暂停执行文件,直至用户在命令行窗口输入任意键后恢复
pause(n):暂停执行文件,等待n秒后继续执行文件

pause指令多用于绘图,由于计算机执行速度很快,会导致一些图像用户来不及查看,使用pause指令进行暂停可便于用户查看图像。


3.3.6 return指令


return指令会强制MATLAB在到达函数的末尾前将控制权返回给调用该函数的函数(也称调用函数)。调用函数是调用包含对return的调用的脚本或函数的函数。如果直接调用包含return的函数或脚本,则不存在调用函数,MATLAB会将控制权返回给命令提示符。


☆ 例3-22:将下面一段代码复制入脚本中运行,观察结果的输出。如果把第3行换为“Info1=GetInfo(A)”,MATLAB会报错吗?如果再把第5行换成“Info2=GetInfo(B)”,MATLAB会报错吗?这是为什么?

clc,clear all,close all
A=[1,2,3;4,5,6];
GetInfo(A) % 调用函数GetInfo
B=[]; % 空矩阵
GetInfo(B) % 此调用函数没有执行任何功能

function Info=GetInfo(A) % GetInfo函数,用于返回矩阵A的各项信息
    if isempty(A)
        return % 如果A是空矩阵,则直接返回主函数
    end
    Info=[];
    [L,W]=size(A);
    Max=max(max(A));
    Min=min(min(A));
    Sum=sum(sum(A));
    Avg=mean(mean(A));
    disp('该矩阵的基础信息:')
    Info=[L,W,Max,Min,Sum,Avg];
end

分析: 更改第3行不会报错,但是更改第5行会报错。因为第3行的函数调用中,函数并没有中途执行return指令,函数中输出参数Info被正常赋值后返回主函数,把值赋给了Info1;而第5行的函数调用中,函数中途执行了return指令,函数中输出参数Info未被赋值,将其赋给变量Info2会报错。此时Info1仍可以得到正确结果。

例3-22

3.3.7 keyboard指令


将keyboard函数放入程序中希望MATLAB暂停的位置,keyboard指令会暂停执行正在运行的程序,并允许通过键盘进行控制(也就相当于在第1.2.1.4节中讲到的 断点调试)。当程序暂停时,命令行窗口中的提示符将更改为 K>>,此时MATLAB处于调试模式,可以查看或更改变量的值,以查看新值是否产生预期的结果。调试完成后,在工具栏中点击“继续”即可运行后面的内容。

keyboard指令



3.3.8 与错误和警告相关的指令


MATLAB编程中常出现error(s)或warning(s)。warning在大部分情况下不会影响代码的正常工作,只是对代码中的不规范、影响运行效率的代码段、未被使用的变量或函数、在后续版本中将会移出MATLAB的函数、运算结果出现奇异值等情况进行提醒。但是error会影响代码的正常工作,在MATLAB运行至错误时,MATLAB会在命令行窗口显示错误原因并且终止程序

MATLAB中包含与错误和警告相关的指令。这里只需要掌握以下4个。

msgstr=lasterr:lasterr函数会返回MATLAB软件生成的最后一条错误消息会跨脚本!)。
msgstr=lastwarn:lastwarn函数返回由 MATLAB 生成的最后一条警告消息,无论警告的显示状态是什么。
error(‘message’)显示错误信息’message’,终止程序的执行
warning(‘message’)显示警告信息’message’,程序继续执行

用户可通过warning(‘state’)(state为on或off)来控制是否展示警告;也可使用msgID来区分警告(或错误)并控制在 MATLAB 遇到警告时发生的情况。详情见帮助文档中error和warning参考页,这里不做深入展开。


☆ 例3-23:编写程序实现以下功能。让用户输入两个数,结果输出两个数相除的结果。若被除数为0,则显示警告信息文本“被除数为0”并继续输出结果;若除数为0,则显示错误信息文本“除数为0,已停止运行”并终止程序。

分析:

clc,clear all,close all
a=input('请输入被除数:');
b=input('请输入除数:');
if a==0
    warning('被除数为0')
end
if b==0
    error('除数为0,已停止运行')
end
res=a/b

在这里插入图片描述




3.4 脚本中的函数


通常我们写的一段代码会包括很多功能。当代码中的很多部分是重复而且体量较大时,我们可以将它们整合成一个函数,如果需要就在主函数中调用该函数即可。将具有不同功能的代码段整合为不同的函数,加快我们编程的速度,也减少代码体量,同时也便于将函数打包后发送给他人。函数就像一个黑箱,我们需要使用它的时候,就按照规范把需要处理的数据送入进去,函数执行完成后把执行的结果又返回给我们。


3.4.1 在脚本中设计一个函数


脚本中的函数需要放在主函数的后面(即定义在文件的末尾),并且以function作为开头,以end作为结尾

————↓———— 提 示 ————↓————

MATLAB不像C语言有main()主函数。这里说主函数只是方便大家理解,指的是置于脚本M文件中、函数之前的可执行代码段。(3.4节中出现的所有“主函数”的说法都是指的置于脚本M文件中、函数之前的可执行代码段)

————↑———————————↑————

函数的第一行的格式写作function [y1, … ,yN]=function_name(x1, … ,xM)。这一行即定义了一个名为“function_name”的函数,该函数接受从主函数输入的x1, … ,xM并返回输出y1, … ,yN至主函数。其中的x1, … ,xM我们称为形式参数,也成为输入参数y1, … ,yN我们称为输出参数

在主函数中,我们以 [Y1, … ,YN]=function_name(X1, … ,XM)的格式调用该函数,其中的Y1, … ,YM我们称为实际参数。之前我们讲过,主函数的工作区(命令工作空间)和我们定义函数的工作区是分开的,并且我们定义的函数中的变量是局部变量,其生存期为调用该函数、函数执行过程中的那段时间。因此如果我们需要把主函数中的变量代入到我们定义的函数进行运算,并从这样的运算中得到结果,就需要在两个工作区中传递值。这也就是我们为什么要区分形式参数和实际参数。

以function [y1, … ,yN]=function_name(x1, … ,xM)为例。我们在定义的函数function_name中设置了变量x1, … ,xM、y1, … ,yN。以[Y1, … ,YN]=function_name(X1, … ,XM)调用该函数时,实际参数依次赋值给形式参数(即x1=X1,…,xN=XN,这样写其实不规范,只是方便大家理解)。在该函数所有功能都执行完成后,得到了保存在该函数工作区运算结果y1, … ,yN。为了把这个结果返回到主函数工作区(命令工作空间),函数结束时,会将y1, … ,yN依次赋给主函数中的变量Y1, … ,YN,调用过程结束。

当然,输入参数和输出参数都不是必要的。为实现功能,一些函数只有输出参数而没有输入参数,一些函数只有输入参数而没有输出参数,甚至有的函数二者都没有。这都是按照函数需要实现的功能而决定的。

另外,有效的函数名称以字母字符开头,并且可以包含字母、数字或下划线,不应超过31个字符,输入参数、输出参数的命名请参照变量的命名。

——————↓——— Q & A ———↓———————

※为什么形式参数和实际参数的名字可以相同呢?

在提这个问题之前可以先自己思考思考。这个问题的答案很简单,就是因为主函数的工作区(命令工作空间)和我们定义的函数的工作区是不相同的,因此两个工作区的变量不会互相影响。

———————↑——— Q & A ———↑——————

下面列出了5组常见的函数设计与调用的伪代码。重点看一看注释的部分。

%% 主函数中,1个输入参数,1个输出参数
A=func1(VAR1) % 调用函数
% 1个输入参数,1个输出参数
% AVAR1是主函数工作区的两个变量,VAR1是实际参数
%% 函数func1
function a=func1(var1) % 函数名称为func1
% var1是形式参数,是函数func1工作区中的变量
% 参数a为返回参数,是函数func1工作区中的变量
% 实际参数依次将值赋给形式参数
...
% 函数执行结束后,返回参数把值赋给A
end
%% 主函数中,1个输入参数,2个输出参数
[A,B]=func2(VAR1) % 调用函数
% 1个输入参数,2个输出参数
% ABVAR1是主函数工作区的变量,VAR1是实际参数
%% 函数func2
function [a,b]=func2(var1) % 函数名称为func2
% var1是形式参数,是函数func2工作区中的变量
% 参数a、b为返回参数,是函数func2工作区中的变量
% 实际参数依次将值赋给形式参数
...
% 函数执行结束后,返回参数把值依次赋给AB
end
%% 主函数中,3个输入参数,2个输出参数
[A,B]=func3(VAR1,VAR2,VAR3) % 调用函数
% 3个输入参数,2个输出参数
% ABVAR1VAR2VAR3是主函数工作区的变量,VAR1VAR2VAR3是实际参数
%% 函数func3
function [a,b]=func3(var1,var2,var3) % 函数名称为func3
% var1、var2、var3是形式参数,是函数func3工作区中的变量
% 参数a、b为返回参数,是函数func3工作区中的变量
% 实际参数依次将值赋给形式参数
...
% 函数执行结束后,返回参数把值依次赋给AB
end
%% 主函数中,无输入参数,2个输出参数
[A,B]=func4() % 调用函数
% 无输入参数,2个输出参数
% AB是主函数工作区的变量
%% 函数func4
function [a,b]=func4() % 函数名称为func4
% 无形式参数的赋值
% 参数a、b为返回参数,是函数func4工作区中的变量
...
% 函数执行结束后,返回参数把值依次赋给AB
end
%% 主函数中,无输入参数,无输出参数
func5() % 调用函数
% 无输入参数,无输出参数
%% 函数func5
function func5() % 函数名称为func5
% 无形式参数的赋值
% 无返回参数
...
% 函数执行结束后,不返回值到主函数
end

☆ 例3-24:在脚本文件中设计一个函数,实现求得函数所有元素的和、平均值的功能,并在代码段中调用该函数。


分析:

clc,clear all,close all
A=[1,2,3,4;5,6,7,8;9,10,11,12];
[sum,avg]=func1(A); % 调用函数
fprintf('该矩阵的和')
sum
fprintf('该矩阵的平均值')
avg

function [y1,y2]=func1(matrix)
y1=sum(sum(matrix)); % 求矩阵的和
y2=mean(mean(matrix)); % 求矩阵的平均值
% 因为sum、mean都是求矩阵一列的和、平均值,因此要调用2次
end

在这里插入图片描述



☆ 例3-25:代码段已给出,现在需要你来设计一个函数,在两行百分号中输入你的代码,以实现表达式 y = { 3 x 3 + 4 , x ⩽ − 1 1 , − 1 < x < 1 2 x 2 − 1 , x ⩾ 1 y=\left\{\begin{matrix} 3x^{3}+4, x\leqslant -1\\ 1, -1<x<1 \\ 2x^{2}-1, x\geqslant 1 \end{matrix}\right. y=3x3+4,x11,1<x<12x21,x1,并输出y的计算结果。

clc,clear all,close all
func(-3);
func(0.5);
func(2);

function func(x)
%%%%%%%%%%%%%%%%%%%%%%%%%%
% 在这里输入你的代码
%%%%%%%%%%%%%%%%%%%%%%%%%%
end

分析: 由于函数并没有设计输出参数,因此直接在函数中运算完成后进行输出即可。

clc,clear all,close all
func(-3);
func(0.5);
func(2);

function func(x)
%%%%%%%%%%%%%%%%%%%%%%%%%%
if x<=-1
    y=3*x^3+4;
elseif x>=1
    y=2*x^2-1;
else
    y=1;
end
disp('将x代入表达式,其输出为')
disp(y)
%%%%%%%%%%%%%%%%%%%%%%%%%%
end

在这里插入图片描述



☆ 例3-26:代码段已给出,现在需要你来设计多个函数,在两行百分号中补全代码,和例3-25一样,实现表达式 y = { 3 x 3 + 4 , x ⩽ − 1 1 , − 1 < x < 1 2 x 2 − 1 , x ⩾ 1 y=\left\{\begin{matrix} 3x^{3}+4, x\leqslant -1\\ 1, -1<x<1 \\ 2x^{2}-1, x\geqslant 1 \end{matrix}\right. y=3x3+4,x11,1<x<12x21,x1,并输出y的计算结果。

clc,clear all,close all
x=-3; % 任设一值
fprintf('输入x,输出:')
if x<=-1
    y=func1(x)
elseif x>=1
    y=func2(x)
else
    y=func3(x)
end

%%%%%%%%%%%%%%%%%%%%%%%%%%
% 在这里输入你的代码
%%%%%%%%%%%%%%%%%%%%%%%%%%

分析: 这里需要设计func1、func2、func3三个函数。三个函数置于代码段之后即可。

clc,clear all,close all
x=3; % 任设一值
fprintf('输入x,输出:')
if x<=-1
    y=func1(x)
elseif x>=1
    y=func2(x)
else
    y=func3(x)
end

%%%%%%%%%%%%%%%%%%%%%%%%%%
function y=func1(x)
    y=3*x^3+4;
end
function y=func2(x)
    y=2*x^2-1;
end
function y=func3(x)
    y=1;
end
%%%%%%%%%%%%%%%%%%%%%%%%%%

在这里插入图片描述



3.4.2 函数的嵌套


为保证实现函数功能,我们会在函数中再次调用函数。例如下面这段代码。

嵌套示例代码

clc,clear all,close all
% 这段代码没有实际功能,只是展示函数的嵌套调用
x=3;
y=func1(x)

function func2(a,b)
fprintf('已调用func2')
end

function y=func1(x)
fprintf('已调用func1\n')
a=2;
func2(x,a);
y=4;
end

示例中,脚本中的代码段调用了func1,在func1执行过程中又调用了func2。调用函数的规则同3.4.1节所述,每个函数的工作区独立,因此可以按照自己的需要设计函数的嵌套。

函数多次嵌套时,MATLAB运行流程如下图。

多次嵌套调用
除了上述的常规嵌套,还有一些看起来很硬核高级的嵌套。例如结合函数句柄的嵌套,见例3-27。


☆ 例3-27(拓展内容):下面这一段代码是用于绘制函数 y = x 3 + a x 2 + b x + c , x ∈ [ x 1 , x 2 ] y=x^{3}+ax^{2}+bx+c, x\in [x_{1},x_{2}] y=x3+ax2+bx+c,x[x1,x2]的图像和该函数在 ( x 1 , x 2 ) 的 最 小 值 (x_{1},x_{2})的最小值 (x1,x2)。把代码段2 复制并运行,分析代码,试着给代码加注释。

clc,clear all,close all
[x,y]=funmin(3,1,2,-3,3)

function [x0,y]=funmin(a,b,c,x1,x2)
options=optimset('Display','off')
[x0,y]=fminbnd(@poly3,x1,x2,options);
function y=poly3(x)
y=x.^3+a*x.^2+b*x+c;
end
fplot(@poly3,[x1,x2]);
hold on
plot(x0,y,'r.')
end

分析:

clc,clear all,close all
[x,y]=funmin(3,1,2,-3,3) % 调用函数

function [x0,y]=funmin(a,b,c,x1,x2) % 函数funmin声明
options=optimset('Display','off') % 编辑优化options结构体,不用管这一行是干啥的
[x0,y]=fminbnd(@poly3,x1,x2,options); % @poly3相当于是函数句柄,这一行的含义是
% (x1,x2)之间找到函数fun,即在函数poly3中定义的x^3+a*x^2+b*x+c的局部最小值
function y=poly3(x)
y=x.^3+a*x.^2+b*x+c; % 表达式,用于生成函数句柄
end
fplot(@poly3,[x1,x2]); % 利用函数句柄绘图,x限定在[x1,x2]内
hold on % 图像窗口保留,等待下一次绘图
plot(x0,y,'r.') % 画出局部最小值的点
end

在这里插入图片描述



☆ 例3-28:设计用于生成矩阵的函数,以满足以下功能:

  1. 函数名称为func。输入参数为两个双精度数和一个字符串,两个双精度数必须大于0,否则调用报错函数err1以输出错误原因并终止程序执行;字符串应为以下三个字符串之一:‘rand’,‘zero’,‘magic’,否则调用报错函数err2以输出错误原因并终止程序执行。
  2. 函数func输出为一个矩阵。该矩阵的大小由两个双精度数向下取整数决定。如果输入参数为’zero’,则输出的矩阵是以zeros函数构造的一个矩阵;如果输入参数为’rand’,则输出的矩阵是以rand函数构造的一个矩阵;如果输入参数为’magic’,则输出的矩阵是以magic函数构造的一个矩阵。
  3. 如果输入参数为’magic’,需要使两个双精度数向下取整的值相同(因为magic函数生成的是长宽相同的方阵,且输入参数只有一个),否则调用报错函数err3以输出错误原因并终止程序执行。

在代码段中调用函数func来验证功能。(提示,比较字符串时使用strcmp函数)


分析:

clc,clear all,close all
matrix_1 = func(3,4,'rand') % 调用函数,可任意更改输入参数
matrix_2 = func(2,3,'zero')
matrix_3 = func(3,3,'magic')
matrix_4 = func(3,3,'other') % 报错

function A=func(a,b,str)
if a<=0||b<=0
    err1();
end
if strcmp(str,'rand')
    A=rand(floor(a),floor(b));
elseif strcmp(str,'zero')
    A=zeros(floor(a),floor(b));
elseif strcmp(str,'magic')
    if floor(a)==floor(b)
        A=magic(floor(a));
    else
        err3();
    end
else
    err2();
end
end

function err1()
error('输入的两个双精度数应都大于0')
end

function err2()
error('输入字符串应为rand,zero,magic三者之一')
end

function err3()
error('如果输入参数为magic,需要使两个双精度数向下取整的值相同')
end

在这里插入图片描述



3.4.3 函数的递归调用


函数的递归调用是一种特殊的嵌套调用递归调用指的是某个函数调用它自己。递归调用、递归的思想能帮助我们解决很多问题,也可以把原本解决过程复杂的问题简单化

在递归调用中,代码段调用函数,而在函数执行过程中又调用了该函数,反复调用,直至实现功能后又一层一层退出函数,把输出参数一层一层向上返回,直至返回给命令空间工作区。每一层调用该函数都会生成一个新的工作区,因此各调用的工作区不会重叠。函数的递归调用过程与嵌套类似(因为递归本身就是一种特殊的嵌套),如下图。

递归调用

☆ 例3-29:下面这个函数用于实现累加 ∑ i = 1 a i , a ⩾ 2 \sum_{i=1}^{a}i,a\geqslant 2 i=1ai,a2。分析一下函数递归调用的过程。(可以把a设置小一点,然后在草稿纸上一步一步跟着算)

clc,clear all,close all
a=10;
s=func(a) % 调用函数实现1到a累加

function s=func(n)
if n==1
   s=1; % 如果n已经为1,就把输出参数赋予1
else
   s=n+func(n-1); % 如果n还没有减到1,就需要赋予
   % 输出参数n的值和调用func(n-1)得到的值之和
end
end

在这里插入图片描述



☆ 例3-30:编程,使用函数的递归调用实现斐波那契(Fibonacci)数列:

F ( 0 ) = 0 , F ( 1 ) = 1 , F ( n ) = F ( n − 1 ) + F ( n − 2 ) , ( n ≥ 2 , n ∈ N ∗ ) F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2), (n\geq 2, n\in N^{*}) F(0)=0,F(1)=1,F(n)=F(n1)+F(n2),(n2,nN)

将该数列存放于矩阵F中,该数列最大值不超过30000。


分析: 下面给出两段参考代码,二者思路稍有不同,读者可自行分析。

clc,clear all,close all
fib=[];
for i=1:1000
    fib(i)=fibonacci(i);
    if fib(i)>30000
        break
    end
end
fib=fib(1:length(fib)-1) % 去掉最后一个

function a=fibonacci(n)
    if n==1
         a=0;
         return
    elseif n==2
         a=1;
         return
    else
         a=fibonacci(n-1)+fibonacci(n-2);
         return
    end
end
clc,clear all,close all
global fib; % 设置全局变量,存放斐波那契数列的矩阵
fib(1)=0;
global m; % 全局变量,用于对于fib矩阵的索引
m=2;
fibonacci(0,1);
fib=[fib(1),fliplr(fib(2:length(fib)))]; % 倒置数组fib(使用fliplr函数)
% 上面这一行等效于下面注释部分
% n=length(fib);
% for i=2:ceil(length(fib)/2)
%     tem=fib(i);
%     fib(i)=fib(n);
%     fib(n)=tem;
%     n=n-1;
% end
fib

function fibonacci(F_n1,F_n2)
    global fib;
    global m;
    F_n=F_n1+F_n2;
    if F_n<=30000
        fibonacci(F_n,F_n1);
        fib(m)=F_n;
        m=m+1;
    end
end

在这里插入图片描述



3.4.4 匿名函数


匿名函数,就如其名,它没有函数名,是一个只包含输入输出参数一个表达式函数句柄,通常来说只有一行。

匿名函数以函数句柄的方式创建,伪代码如下。

fun=@(var1,var2, …) expression

这其中,函数句柄的名称为fun,var1、var2等变量可以理解为形式参数,后面的表达式expression包含这些变量(也可以不包含这些变量,有些函数可以不需要输入,写作fun=@() expression即可,依照函数要实现的功能决定)。定义该函数句柄后,以如下方式调用:

res=fun(input1,input2, …)

调用该匿名函数时,MATLAB会对依照输入input1,input2, …依次对形式参数var1,var2, …赋值,赋值后运算表达式expression,将结果返回到res中。

输入除了是标量,也可以是一个数组。多个变量时,每一个input的输入数组需要长度相等。MATLAB会依次把数组中的数字代入匿名函数,返回一个与输入数组等长的输出数组。

表达式expression中也可以加入已经赋值的变量。在创建匿名函数之后,不论对该变量进行如何操作,都不会影响该匿名函数。例如下面这段代码,运行后可以用whos查询变量的详细信息(你会发现fun是一个函数句柄)。

匿名函数的使用

clc,clear all,close all
a=1;b=1;
fun=@(x,y) a*x^2+b*y^2;
a=3;b=3; % 对a、b重新复制对函数句柄没有影响
res=fun(3,4)

如果想要查看匿名函数的基本信息,可以使用functions函数,调用方式是functions(fun),fun是函数名称。

查看匿名函数
匿名函数在学习笔记中不做过多展开。如果想要深入了解,详见拓展学习:MATLAB拓展学习T1:匿名函数和内联函数




3.5 函数文件中的函数


3.5.1 在函数文件中设计一个函数


MATLAB中有大量的函数文件,其中定义着很多函数,这不仅为用户提供了很多强大的、功能丰富的可调用函数,也能让用户自己设计函数。

从工具栏的“编辑器”中可以新建一个函数文件。函数文件与脚本文件一样是以.m后缀结尾,不同的是函数文件的第一个可执行语句都是function函数文件必须在MATLAB的搜索路径中

任意打开一个函数文件
函数文件中函数的设计与脚本中函数的设计相同。函数输入参数输出参数的设计、参数的传递、函数的调用、函数命名的规范等设计函数的基础知识,详见3.4.1-3.4.3节,与脚本函数的相同点这里不再赘述,这里只说不同点。


3.5.2 函数文件的结构


不规范的函数文件中函数的写法就按照脚本中函数的写法即可。需要注意的是函数文件的文件名必须与文件中主函数的函数名一致

而如果想要规范函数文件,使这个函数文件中的函数能像MATLAB中定义的函数那样可以使用help、lookfor来查询函数的基本信息,就需要按照下面的格式来写:

  1. 函数声明行:位于首行,以function作为开头,定义函数名和输入输出参数。
  2. H1行(摘要行):紧随函数声明行之后的以%开头的第一行注释行,包含大写的函数文件名、运用关键词简要描述的函数功能,提供lookfor关键词查询和help在线帮助的文本
  3. 在线帮助文本区(详细注释行):H1行及后连续以%开头的注释行,包含函数功能的详细介绍、输入输出变量的含义、函数调用的说明等。提供help在线帮助的文本
  4. 编写和修改记录:与在线帮助文本区以一个空行相隔,以%开头,包含了编写、修改该M文件的所有作者、日期、版本号,以便于后来者查询、修改、使用。
  5. 函数主体:与编写和修改记录以一个空行相隔,包含实现该函数文件功能的MATLAB指令、接收输入变量、进行程序流控制。以end结尾。
  6. 子函数在主函数后编写,以供主函数调用、子函数之间相互调用,不可被外部调用。

☆ 例3-31:将下面一段代码复制进函数文件中保存,并在任一脚本文件中调用该函数。尝试在命令行窗口中查询该函数的帮助文档。

function spir_len=spirallength(d,n,lcolor)
% CIRCLE plot a circle of radius as r in the provided color and calculate its area
% 输入参数d是螺旋的旋距,是双精度类型
% 输入参数n是螺旋的圈数,是双精度类型
% 输入参数lcolor规定线型,是字符串类型
% 输出参数spir_len是螺旋的周长
%
% 常用调用方式:
% spirallength(d,n) 用蓝色线型绘制以输入参数为基础的螺旋线
% spirallength(d,n,lcolor) 用lcolor线型绘制以输入参数为基础的螺旋线
% spir_len=spirallength(d,n) 用蓝色线型绘制以输入参数为基础的螺旋线,并计算螺旋线的周长
% spir_len=spirallength(d,n,lcolor) 用lcolor线型绘制以输入参数为基础的螺旋线,并计算螺旋线的周长
%
% 编写于202181711:14   程序员:K2SO4% 修改于202181711:31   程序员:K2SO4if nargin>3 % 如果输入变量多于3error('输入参数过多!')
elseif nargin<=1
    error('输入参数过少!')
elseif nargin==2
    lcolor='b';
end
j=sqrt(-1);
phi=0:pi/1000:n*2*pi;
amp=0:d/2000:n*d;
spir=amp.*exp(j*phi);
if nargout==1
    spir_len=sum(abs(diff(spir)));
    plot(spir,lcolor)
elseif nargout==0 % 如果有输出变量
    plot(spir,lcolor)
else
    error('输出参数过多!')
end
axis('square')
grid on
end

分析:

例3-35

3.5.3 函数文件中的主函数与子函数


当多个函数同时写入同一个函数文件时,第一个函数是主函数,其它均为子函数。主函数与子函数有如下特点:

  1. 函数文件应按照主函数的名称来命名
  2. 函数文件中主函数永远在文件首子函数可以任意调换顺序
  3. 函数文件中子函数仅能被主函数和其它子函数调用,不可被外部程序调用
  4. 函数文件中主函数和子函数都有独立的工作区,它们的变量不会相互影响。如果需要参数的传递,可以使用函数的输入输出变量或全局变量。
  5. 函数文件中,以函数名调用函数时,子函数的优先级仅次于MATLAB内置函数
  6. 以help、lookfor查询某函数文件信息时不会展示子函数的任何相关信息。

可以理解为,子函数相当于是为主函数服务的工具,以帮助主函数在实现其功能的同时代码段不会过于冗长,相同功能的执行以子函数的方式调用也减少程序员编程消耗的时间。




3.6 伪重载函数与函数参数的可调性


重载函数是C++中使用的一种特殊的函数。C++中允许构造同名的且形式参数的个数、类型或输出参数不同的函数,它们构成重载函数,在用户以不同的输入输出参数调用该函数时执行对应的函数功能。

MATLAB中,一个函数在输入参数数目不同、输出参数数目不同时,可以完成不同的功能。这样的函数具有类似C++中重载函数的功能,因此可以理解为伪重载函数。MATLAB用户可以调整函数的输入输出参数,因此这一节我们讨论函数参数的可调性。


3.6.1 以nagrin、nargout调整参数


nargin函数针对当前正在执行的函数,返回函数调用中给定函数输入参数的数目,nargout函数针对当前正在执行的函数,返回该函数调用中指定的函数输出参数的数目。二者均 仅可在函数体内使用。因此可以通过nargin和nargout来判断输入参数、输出参数的数量,以此来执行不同的功能。

下面这个例子将详细说明,这个例子能帮助你理解nagrin、nargout以及参数的可调性。


☆ 例3-32:运行下面一段代码,观察nagrin、nargout的使用。

clc,clear all,close all
func(1)
fprintf('------换行,避免混淆------\n')
i=func(1,2)
fprintf('------换行,避免混淆------\n')
[i,j]=func(1,2,3)
fprintf('------换行,避免混淆------\n')
[i,j,k]=func(1,2,3)
fprintf('------换行,避免混淆------\n')
func()

function [x,y,z]=func(a,b,c)
if nargin==1
    fprintf('只有1个输入参数a\n')
elseif nargin==2
    fprintf('有2个输入参数a、b\n')
elseif nargin==3
    fprintf('有3个输入参数a,b,c\n')
elseif nargin==0
    fprintf('无输入参数\n')
else
    error('输入参数过多或过少\n')
end
if nargout==1
    fprintf('只有1个输出参数x\n')
    x=a;
elseif nargout==2
    fprintf('有2个输入参数x、y\n')
    x=a;y=b;
elseif nargout==3
    fprintf('有3个输入参数x,y,z\n')
    x=a;y=b;z=c;
elseif nargout==0
    fprintf('无输出参数\n')
else
    error('输出参数过多或过少')
end
end

在这里插入图片描述


需要注意几点:

  1. 函数输入、输出参数的数量根据用户调用该函数的方式决定。调用函数时,主函数中的实际参数会依次对函数的形式参数赋值,实际参数的数量不得多于函数规定输入参数数量的最大值
  2. 函数中的输出参数的数量由调用函数的形式决定。调用函数的代码段中规定了有几个输出参数,在函数中就必须对输出参数进行赋值
  3. 函数参数的可调性是通过nagrin、nargout选择结构来实现的。
  4. nagrin、nargout本身是函数,用户不可对其进行赋值

3.6.2 以varagrin、varargout调整参数


MATLAB中,很多函数可以输入任意多的变量,也可以输出任意多的变量,例如绘图函数plot。这里就需要用到varagrin、varargout来调整函数参数。

以varagrin、varargout调整函数参数时,需要在定义函数的时候将输入参数设置为varagrin,输出参数设置为varargout。

function varargout=func(varargin)
function [普通输出参数名,varargout]=func(普通输入参数名,varargin)

在调用函数时,用户可以输入任意多的输入和输出变量。varagrin、varargout均作为元胞数组,在调用时使用varagrin{n}、varargout{n}来表示元胞数组的第n个数据,即第n个输入或输出变量(注意是花括号)。

该函数被调用时,输入变量依次顺序地赋给输入参数列表中被明确定义变量名称的普通输入参数,再将剩余的输入变量赋给元胞数组varargin。输出时,调用时的输出参数依次分配给被明确定义变量名称的普通输出参数,再把剩余的输出变量赋给元胞数组varargout


☆ 例3-33:运行下面一段代码,总结varagrin、varargout的使用方式。

clc,clear all,close all
a=func1(3)
fprintf('------换行,避免混淆------\n')
[a,b,c]=func1(3,4,5)
fprintf('------换行,避免混淆------\n')
[a,b]=func2(3,4)
fprintf('------换行,避免混淆------\n')
[a,b,c,d,e]=func2(3,4,5,6,7)

function varargout=func1(varargin)
for i=1:nargin
    varargout{i}=varargin{i}*2;
end
end

function [x,y,varargout]=func2(a,b,varargin)
x=a*2;
y=b*2;
for i=1:nargin-2
    varargout{i}=varargin{i}*2;
end
end

在这里插入图片描述




思考题


☆ 思考题3-3:回忆一下控制程序流的几个重要命令,回顾一下函数的构造方法、函数参数的可调性,将本章的15个例题动手做一下。相信你对这一章一定会掌握得很好。




MATLAB编程基础 · 拓展学习:传送门汇总


这里是MATLAB编程基础这一章节的拓展学习汇总。

MATLAB拓展学习T1:匿名函数和内联函数

MATLAB拓展学习T2:程序性能优化技术




撰写:邓云泽、林耀
审核:华中师范大学HelloWorld程序设计协会工作人员


  1. 刘浩, 韩晶. MATLAB R2018a 完全自学一本通[M]. 北京:电子工业出版社. 2019. ↩︎

  2. MATLAB函数嵌套[EB/OL]. doi: https://download.csdn.net/download/crazy31415926/9655248?utm_medium=distribute.pc_aggpage_search_result.none-task-download-2aggregatepagefirst_rank_v2~rank_aggregation-2-9655248.pc_agg_rank_aggregation&utm_term=matlab%E4%B8%AD%E5%87%BD%E6%95%B0%E5%B5%8C%E5%A5%97%E8%B0%83%E7%94%A8&spm=1000.2123.3001.4430 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

K2SO4钾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值