4.5 脚本和函数
4.5.1 脚本
对于比较简单的运算过程,从命令行窗口中直接输入指令并运行计算是非常方便的。随着指令行的增加,或是运算逻辑复杂度的增加,以及重复计算要求的提出,再直接从命令行窗口中进行运算就十分不明智了。
在这种情况下,使用脚本文件最为适宜。“脚本”本身反映这样一个事实:MATLAB是按照文件中所输入的指令执行的,这种文件的构成比较简单,其主要特点如下:
● 文件只是一串按照用户意愿排列而成的MATLAB指令集合。
● 脚本文件运行后,其运算过程中所产生的所有变量都自动保留在MATLAB工作区(Base工作区)中,除非用户关闭MATLAB运行界面,或是使用clear指令对工作区中的变量加以清理,否则这些变量将一直保存在基本的工作区中。基本空间随着MATLAB的启动而产生,只有关闭MATLAB界面时,该基本空间才会被删除。
● 当调用一个脚本时,MATLAB会简单地执行文件中找到的命令。脚本可以运行工作区中存在的数据,或者创建新数据来运行。
● 虽然脚本不能返回输出变量,但是所有创建的变量都将保留在工作区中,供后面的计算使用。另外,脚本能提供图形输出,就像使用图形输出函数plot()一样。
例4-15:脚本文件的简单运用示例。
创建M文件并命名为ex4_15.m,利用M文件编辑器在M文件中输入:
运行M文件,结果如图4-4所示。
图4-4 脚本文件运行结果
4.5.2 函数
如果M文件的第一个可执行语句以function开始,该文件就是函数文件,每一个函数文件都定义一个函数。事实上,MATLAB 提供的函数命令大部分都是由函数文件定义的,这足以说明函数文件的重要性。
从使用的角度来看,函数是一个“黑箱”,把一些数据送进去,经加工处理,把结果送出来。从形式上看,函数文件区别于脚本文件之处在于脚本文件的变量为命令工作空间变量,在文件执行完成后保留在命令工作空间中;而函数文件内定义的变量为局部变量,只在函数文件内部起作用,当函数文件执行完后,这些局部变量将被清除。
例4-16:编写函数average()用于计算向量元素的平均值。
创建M文件并命名为average.m(文件名与函数名相同),利用M文件编辑器在M文件中输入:
保存M文件,函数average()接收一个输入参数并返回一个输出参数,该函数的用法与其他MATLAB函数一样。在MATLAB命令行窗口中运行以下语句,便可求得1~9的平均值。
通常函数文件由以下几个基本部分组成。
(1)函数定义行:函数定义行由关键字function引导,指明这是一个函数文件,并定义函数名、输入参数和输出参数。函数定义行必须为文件的第一个可执行语句,函数名与文件名相同,可以是MATLAB中任何合法的字符。
函数文件可以带有多个输入参数和输出参数,如:
也可以没有输出参数,如:
(2)H1行:H1行就是帮助文本的第一行,是函数定义行下的第一个注释行,是供lookfor查询时使用的。一般来说,为了充分利用MATLAB的搜索功能,在编制M文件时,应在H1行中尽可能多地包含该函数的特征信息。由于在搜索路径上包含average的函数很多,因此用lookfor average语句可能会查询到多个有关的命令。如:
(3)帮助文本:在函数定义行后面,连续的注释行不仅可以起到解释与提示作用,更重要的是为用户自己的函数文件建立在线查询信息,以供help命令在线查询时使用。如:
函数average_2(x)用以计算向量元素的平均值。
输入参数x为输入向量,输出参数y为计算的平均值。非向量输入将导致错误。
(4)函数体:函数体包含了全部的用于完成计算及给输出参数赋值等工作的语句,这些语句可以是调用函数、流程控制、交互式输入/输出、计算、赋值、注释和空行。
(5)注释:以%起始到行尾结束的部分为注释部分,MATLAB的注释可以放置在程序的任何位置,可以单独占一行,也可以在一个语句之后。如:
4.5.3 M文件的一般结构
从结构上来看,脚本文件只比函数文件少了一个“函数声明行”,除此之外,二者的语法及构架等均相同。于是将典型规范的M文件函数的结构总结如下。
● 函数声明行:位于函数文件的首行,以MATLAB关键字function开头,定义函数名及函数的输入/输出变量。脚本文件无须函数声明行。
● H1行:紧随函数声明行之后的以“%”开头的第一注释行。H1行包括大写的函数文件名和运用关键词简要描述的函数功能。该行将提供lookfor命令作为关键词时查询使用。
● 在线帮助文本区:H1行及其后的连续以“%”开头的注释行,通常包括函数输入/
输出变量的含义调用说明。
● 编写和修改记录:与在线帮助文本区应以一个“空”行相隔;该行以“%”开头,记录了编写及修改M文件的所有作者、日期及版本号,以方便后来的使用者查询、修改和使用。
● 函数主体:规范化的写法应与编写和修改记录以一个“空”行相隔;这部分内容包括所有实现该M函数文件功能的MATLAB指令、接收输入变量、进行程序流控制。
说明:
(1)函数定义名应和文件保存名一致。当两者不一致时,MATLAB将忽视文件首行的函数定义名,而以文件保存名为准。
(2)MATLAB中的函数文件名必须以字母开头,可以是字母、下画线及数字的任意组合,但不可以超过31个字符。
(3)建议读者在编写H1行注释时,尽量采用英文表述方式,这是为了之后在使用过程中关键词检索方便。
例4-17:完整的M文件示范。
创建M文件并命名为ex4_17.m,利用M文件编辑器在M文件中输入:
在命令行窗口中输入:
输出结果如图4-5所示。
图4-5 spirallength.m运行结果图
M文件函数参数指令集如表4-5所示。
表4-5 M文件函数参数指令集
4.5.4 匿名函数、子函数、私有函数与私有目录
1.匿名函数
匿名函数没有函数名,也不是函数M文件,只包含一个表达式和输入/输出参数。用户可以通过在命令行窗口中输入代码来创建匿名函数。匿名函数的创建方法为:
f为创建的函数句柄。函数句柄是一种间接访问函数的途径,可以使用户调用函数过程变得简单,减少了程序设计中的繁杂,而且可以在执行函数调用过程中保存相关信息。
例4-18:当给定实数x、y的具体数值后要求计算表达式的结果,可以通过创建匿名函数的方式来解决。
在命令行窗口中输入:
MATLAB将创建一个名为Fxy的函数句柄:
调用whos函数,可以查看变量Fxy的信息:
分别求当x=2、y=5及x=1、y=9时表达式的值:
2.子函数
在MATLAB中,多个函数的代码可以同时写到一个M函数文件中。其中,出现的第一个函数称为主函数(Primary Function),该文件中的其他函数称为子函数(Sub Function)。保存时所用的函数文件名应当与主函数定义名相同,外部程序只能对主函数进行调用。
子函数的书写规范有如下几条:
(1)每个子函数的第一行是其函数声明行。
(2)在M函数文件中,主函数的位置不能改变,但是多个子函数的排列顺序可以任意改变。
(3)子函数只能被处于同一M文件中的主函数或其他子函数调用。
(4)在M函数文件中,在任何指令通过“名称”对函数进行调用时,子函数的优先级仅次于MATLAB内置函数。
(5)同一M文件的主函数、子函数的工作区都是彼此独立的。各个函数间的信息传递可以通过输入/输出变量、全局变量或跨空间指令来实现。
(6)help、lookfor等帮助指令都不能显示一个M文件中的子函数的任何相关信息。
例4-19:M文件中的子函数示例。
创建M文件并命名为ex4_19.m,利用M文件编辑器在M文件中输入:
3.私有函数与私有目录
所谓私有函数,是指位于私有目录private下的M函数文件,它的主要性质有如下几条。
(1)私有函数的构造与普通M函数完全相同。
(2)关于私有函数的调用:私有函数只能被private直接父目录下的M文件所调用,而不能被其他目录下的任何M文件或MATLAB指令窗中的命令所调用。
(3)在M文件中,任何指令通过“名称”对函数进行调用时,私有函数的优先级仅次于MATLAB内置函数和子函数。
(4)help、lookfor等帮助指令都不能显示一个私有函数文件的任何相关信息。
4.5.5 重载函数
“重载”是计算机编程中非常重要的概念,经常用于处理功能类似但变量属性不同的函数。例如实现两个相同的计算功能,输入的变量数量相同,不同的是其中一个输入变量类型为双精度浮点数类型,另一个输入变量类型为整型。这时,用户就可以编写两个同名函数,分别处理这两种不同的情况。当用户实际调用函数时,MATLAB就会根据实际传递的变量类型选择执行哪一个函数。
MATLAB的内置函数中就有许多重载函数,放置在不同的文件路径下,文件夹通常命名为“@+代表MATLAB数据类型的字符”。例如@int16路径下的重载函数的输入变量应为16位整型变量,而@double路径下的重载函数的输入变量应为双精度浮点数类型。
4.5.6 eval和feval函数
1.eval函数
eval函数可以与文本变量一起使用,实现有力的文本宏工具。其调用格式如下。
● eval(s):该指令的功能为使用MATLAB的注释器求表达式的值或执行包含文本字符串s的语句。
例4-20:eval函数的简单运用示例。
本例展示了利用eval函数分别计算4种不同类型的语句字符串,分别是:
● “表达式”字符串。
● “指令语句”字符串。
● “备选指令语句”字符串。
● “组合”字符串。
(1)创建M文件并命名为eval_exp1.m,利用M文件编辑器在M文件中输入:
输出结果:
(2)创建M文件并命名为eval_exp2.m,利用M文件编辑器在M文件中输入:
运行M文件,得到结果:
(3)创建M文件并命名为eval_exp3.m,利用M文件编辑器在M文件中输入:
运行M文件,得到结果:
(4)创建M文件并命名为eval_exp4.m,利用M文件编辑器在M文件中输入:
运行M文件,得到如下结果:
2.feval函数
feval函数的具体句法形式如下:
该指令的功能为用变量arg1,arg2,…来执行FN函数指定的计算。
说明:
(1)在此FN为函数名。
(2)在eval函数与feval函数通用的情况下(使用这两个函数均可以解决问题),feval函数的运行效率比eval函数高。
(3)feval函数主要用来构造“泛函”型M函数文件。
例4-21:feval函数的简单运用示例。
(1)示例说明:feval和eval函数运行区别之一是feval函数的FN不可以是表达式。
创建M文件并命名为feval_exp1.m,利用M文件编辑器在M文件中输入:
运行M文件,得到结果:
(2)示例说明:feval函数中的FN只接收函数名,不能接收表达式。
创建M文件并命名为feval_exp2.m,利用M文件编辑器在M文件中输入:
运行M文件,结果如图4-6所示。
图4-6 feval_exp2.m文件的运行结果
4.5.7 内联函数
内联函数(Inline Function)的属性和编写方式与普通函数文件相同,但相对来说,内联函数的创建简单得多。其调用格式如下。
● inline('CE'):其功能为把字符串表达式“CE”转化为输入变量自动生成的内联函数。本语句将自动对字符串CE进行辨识,其中除了“预定义变量名”(如圆周率pi)、“常用函数名”(如sin、rand等),其他由字母和数字组成的连续字符辨识为变量,连续字符后紧接左括号的,也不会被识别为变量,例如array(1)。
● inline('CE',arg1,arg2,…):其功能为把字符串表达式“CE”转换为arg1、arg2等指定的输入变量的内联函数。本语句创建的内联函数最为可靠,输入变量的字符串用户可以随意改变,但是由于输入变量已经规定,因此生成的内联函数不会出现辨识失误等错误。
● inline('CE',n):其功能为把字符串表达式“CE”转化为n个指定的输入变量的内联函数。本语句对输入变量的字符是有限制的,其字符只能是x,P1,…,Pn等,其中P一定为大写字母。
说明:
(1)字符串表达式CE中不能包含赋值符号“=”。
(2)内联函数是沟通eval和feval两个函数的桥梁,只要是eval函数可以操作的表达式,都可以通过inline指令转化为内联函数,这样,内联函数总可以被feval函数调用。MATLAB中的许多内置函数就是通过被转换为内联函数,从而具备了根据被处理的方式不同而变换不同函数形式的能力。
MATLAB中关于内联函数的属性的相关指令如表4-6所示,读者可以根据需要使用。
表4-6 内联函数属性指令集
例4-22:内联函数的简单运用示例。
(1)示例说明:内联函数的第一种创建格式是使内联函数适用于“数组运算”。
在命令行窗口中输入:
输出结果:
在命令行窗口中输入:
输出结果:
在命令行窗口中输入:
输出结果:
(2)示例说明:第一种内联函数创建格式的缺陷在于不能使用多标量构成的向量进行赋值,而使用第二种内联函数创建格式则可以。
在命令行窗口中输入:
输出结果:
在命令行窗口中输入:
输出结果:
(3)示例说明:产生向量输入、向量输出的内联函数。
在命令行窗口中输入:
输出结果:
在命令行窗口中输入:
输出结果:
在命令行窗口中输入:
输出结果:
在命令行窗口中输入:
输出结果:
(4)示例说明:以最简练的格式创建内联函数;内联函数可被feval指令调用。
在命令行窗口中输入:
输出结果:
在命令行窗口中输入:
输出结果:
4.5.8 向量化和预分配
1.向量化
要想让MATLAB最高速地工作,重要的是在M文件中把算法向量化。其他程序语言可以用for或DO循环,MATLAB则可用向量或矩阵运算。下面的代码用于创立一个算法表:
同样代码的向量化翻译如下:
对于更复杂的代码,矩阵化选项不总是那么明显。当速度重要时,应该想办法把算法向量化。
2.预分配
若一条代码不能向量化,则可以通过预分配任何输出结果已保存在其中的向量或数组以加快for 循环。例如,下面的代码用zeros函数把for循环产生的向量预分配,这使得for循环的执行速度显著加快。
上例中若没有使用预分配,则MATLAB的注释器利用每次循环扩大r向量。向量预分配排除了该步骤以使执行加快。
4.5.9 函数的函数
一种以标量为变量的非线性函数称为“函数的函数”,即以函数名为自变量的函数。这类函数包括求零点、最优化、求积分和常微分方程等。
MATLAB通过M文件的函数表示该非线性函数。
例如,下例为一个简化的humps函数(humps函数可在路径MATLAB\demos下获得)。
例4-23:函数的函数的简单运用示例。
创建M文件并命名为ex4_23.m,利用M文件编辑器在M文件中输入:
运行文件,输出图像如图4-7所示。
图4-7 运行结果
图像表明函数在x=0.6附近有局部最小值。接下来用函数fminsearch可以求出局部最小值及此时x的值。函数fminsearch第一个参数是函数句柄,第二个参数是此时x的近似值。
在命令行窗口中输入:
输出结果:
在命令行窗口中输入:
输出结果:
4.5.10 P码文件
一个M文件首次被调用(包括在M文件编辑器中被打开或者在命令行窗口中运行文件名)时,MATLAB将首先对该M文件进行语法分析,并把生成的相应内部伪代码(Psedocode,P码)文件存放在内存中。
当M文件再次被调用时,将直接调用该M文件在内存中的P码文件,而不会再对原M文件进行重复的语法分析。需要注意的是,MATLAB的分析器(Parser)总是把M文件连同在该M文件中被调用的所有函数文件一起转变成P码文件。
P码文件与原码文件具有相同的文件名,但是其扩展名为“.p”;P码文件的运行速度高于原码文件,但对于小规模的文件而言,用户一般体会不到这种速度上的差异。
在MATLAB环境中,假如存在同名的P码文件和原码文件,那么当该文件名被调用时,被执行的一定是P码文件。
P码文件并不是仅当M文件被调用时才能生成的,用户也可以使用MATLAB中的内设指令在M文件中生成P码文件,其调用格式如下。
● pcode FunName:该指令的功能为在当前文件夹中生成FunName.p。
● pcode FunName –inplace:该指令的功能为在Filename.m所在的目录下生成FunName.p。
说明:P码文件相对于原码文件来说有以下两个优点。
(1)运行速度快。
(2)P码文件中的数据为二进制保存,阅读困难,增加了程序的保密性。
MATLAB中还内置了P码文件的相关操作指令,如表4-7所示。
表4-7 P码文件的相关操作指令
例4-24:查询内存中所有的P码文件名,并清除指定名称的P码文件。
首先调用inmem函数,查询当前内存中所有的P码文件名,得到如下结果:
然后使用clear函数,清除名为path的P码文件:
再次调用inmem函数,查看当前内存中所有的P码文件,发现名为path的P码文件不存在,得到如下结果: