Presentation is loading. Please wait.

Presentation is loading. Please wait.

第四章 MATLAB编程基础 4.1 概述 4.2 流程控制 4.3 脚本文件 4.4 函数文件 4.5 M文件调试 4.6 M文件性能分析

Similar presentations


Presentation on theme: "第四章 MATLAB编程基础 4.1 概述 4.2 流程控制 4.3 脚本文件 4.4 函数文件 4.5 M文件调试 4.6 M文件性能分析"— Presentation transcript:

1 第四章 MATLAB编程基础 4.1 概述 4.2 流程控制 4.3 脚本文件 4.4 函数文件 4.5 M文件调试 4.6 M文件性能分析
4.1 概述 4.2 流程控制 4.3 脚本文件 4.4 函数文件 4.5 M文件调试 4.6 M文件性能分析 4.7 本章小结

2 4.1 概 述 MATLAB提供了完整的编写应用程序的能力,这种能力通过一种被称为M语言的高级语言来实现。这种编程语言是一种解释性语言,利用该语言编写的代码仅能被MATLAB接受,被MATLAB解释、执行。其实,一个M语言文件就是由若干MATLAB的命令组合在一起构成的,这些命令都是在前面章节中介绍的合法的MATLAB命令。和C语言类似,M语言文件都是标准的纯文本格式的文件,其文件的扩展名为.m。

3 使用M文件最直接的好处就是可以将一组MATLAB命令组合起来,通过一个简单的指令就可以执行这些命令。这些命令可以完成某些MATLAB的操作,也可以实现某个具体的算法。其实,MATLAB产品族中包含的工具箱就是由世界上在相应专业领域内的顶尖高手,利用M语言开发的算法函数文件集合。读者也可以结合自己工作的需要,为自己的MATLAB开发具体的算法和工具箱。 MATLAB的函数主要有两类,一类被称为内建(Build-in)函数,这类函数是由MATLAB的内核提供的,能够完成基本的运算,例如三角函数、矩阵运算的函数等。另外一类函数就是利用高级语言开发的函数文件,这里的函数文件既包括用C语言开发的MEX函数文件,又包含了M函数文件。有关MEX函数文件的内容已经超出了本书的内容,将在《MATLAB外部编程接口》一书中详细讲述。

4 如前所述,MATLAB的M语言文件是纯文本格式的文件,利用任何一种纯文本编辑器都可以编写相应的文件,例如Windows平台下的记事本、UltraEdit等软件,或者Unix平台下的Emacs软件等。同样,为了方便编辑M文件,MATLAB也提供了一个编辑器,叫作meditor,它也是系统默认的M文件编辑器。 运行meditor的方法非常简单,在MATLAB命令行窗口中键入下面的指令就可以打开meditor: >> edit 这时MATLAB将启动meditor,然后创建一个未命名的空白文件,如图4-1所示。

5 图4-1 meditor的运行界面

6 这时用户就可以直接在编辑器中键入MATLAB指令,开发M语言文件了。
此外,运行meditor还可以通过“File”菜单中“New”子菜单下的“M-File”命令来实现,或者直接单击MATLAB用户界面工具栏上的新建按钮完成同样的工作。 M语言文件可以分为两类,其中一类是脚本文件,另外一类叫作函数文件。本章将分别介绍这两类文件的编写方法。

7 4.2 流 程 控 制 选择结构 如前所述,当人们判断某一条件是否满足,根据判断的结果来选择不同的解决问题的方法时,就需要使用选择结构。和C语言类似,MATLAB的条件判断可以使用if语句或者switch 语句。

8 if语句 if语句的基本语法结构有三种,分别如下: (1) if (关系运算表达式) MATLAB语句 end 这种形式的选择结构表示,当关系运算表达式计算的结果为逻辑真的时候,执行MATLAB语句,这里的MATLAB语句可以是一个MATLAB表达式,也可以是多个MATLAB表达式。在MATLAB语句的结尾处,必须有关键字end。

9 (2) if(关系运算表达式) MATLAB语句A else MATLAB语句B end 这种选择结构表示,当关系运算表达式的计算结果为逻辑真的时候,执行MATLAB语句A,否则执行MATLAB语句B,在语句B的结尾必须具有关键字end。

10 (3) if (关系运算表达式a) MATLAB语句A elseif (关系运算表达式b) MATLAB语句B else (关系运算表达式c) end 这种选择结构可以判断多条关系运算表达式的计算结果,然后按照执行的逻辑关系执行相应的语句。读者可以根据类似的C语言知识或者前面两种选择结构的介绍判断这种结构的执行方式。

11 例子4-1 if语句的使用——if_examp.m。
读者通过本例子将同时了解meditor的基本使用方法。打开meditor,然后键入下面的指令: 001 clear all 002 003 I=1; 004 J=2; 005 006 if I == J 007 A(I,J) = 2; 008 elseif abs(I-J) == 1 009 A(I,J) = ?1; 010 else 011 A(I,J) = 0; 012 end

12 注意: 在键入程序时,不要将行号(001~012)也敲进去,在这里设置行号的主要目的是为了便于讲解和分析程序。 所有的指令键入完毕后,将文件保存,读者可以将其保存为任何名字,不过文件名必须由英文字符和数字组成,将文件的扩展名设置为.m,并且将文件保存在MATLAB的搜索路径下,例如MATLAB当前的工作路径。 然后在MATLAB的命令行中,键入刚才保存的文件名,不过,这时不要将扩展名也一同键入,MATLAB就会依次执行这些指令。

13 运行例子4-1的方法和效果如下: >> if_examp A = 例子4-1代码的核心是006~012行的部分,这部分展示了if-elseif-else-end语句组合的使用方法。请读者仔细察看,并且通过修改程序003和004行中对I和J的赋值来察看整个语句的执行情况。 和C语言类似,if-elseif-else的语句结构也可以嵌套地使用,也就是可以存在这样的语句结构:

14 if(关系表达式a) if(关系表达式b) MATLAB语句A else MATLAB语句B end else if(关系表达式c) MATLAB语句C else MATLAB语句D

15 注意: 在使用嵌套的选择结构时,需要小心if语句和end关键字的配对。 例子4-2 嵌套使用的if结构——if_examp2.m。 001 clear all 002 003 if 1 disp('Is 1') 005 else disp('Not 1') 007 end 008

16 009 I = 1; 010 if I if I < 2 disp('I is bigger than 0 but less than 2') end 014 else if I > -2 dis('I is less than 0 but bigger than -2') end 018 end

17 该程序的运行方法和效果如下: >> if_examp2 Is 1 I is bigger than 0 but less than 2 在例子4-2中,主要说明了嵌套的if结构和在关系表达式中使用常量的方法。在代码的003行,if语句的关系表达式为常数1,这个时候if语句将始终认为非零值为逻辑真,所以,程序执行了004行的代码。同样,在程序的009行,if语句的关系表达式为变量I,若I的数值为非零值,则if语句判断其为逻辑真,所以,代码的016行只有在I为0时,才可能被执行。

18 switch语句 另外一种构成选择结构的关键字就是switch。在处理实际问题的时候,往往要处理多个分支,这时如果使用if-else语句处理多分支结构往往使程序变得十分冗长,从而降低了程序的可读性。switch语句就可以用于处理这种多分支的选择,它的基本语法结构如下:

19 switch(表达式) case 常量表达式a:MATLAB语句A case 常量表达式b:MATLAB语句B case 常量表达式m:MATLAB语句M otherwise :MATLAB语句N end

20 在switch语句之后的表达式可以是一个数值类型表达式或者是一个数值类型的变量,当这个表达式的值同case后面的某一个常量表达式相等时,则执行该case后面的常量表达式后面的语句。
注意: MATLAB的switch和C语言的switch语句结构不同。在C语言中,每一个case后面的语句中必须包含类似break语句的流程控制语句,否则程序会依次执行符合条件的case语句后面的每一个case分支。但是在MATLAB中就不必如此,程序仅仅执行符合条件的case分支。

21 例子4-3 switch结构使用示例——switch_examp.m。
001 clear all 002 003 algorithm = input('Enter an algorithm in quotes (ode23, ode15s, etc:) '); 004 005 switch algorithm 006 case 'ode23' 007 str = '2nd/3rd order'; 008 case {'ode15s', 'ode23s'} 009 str = 'stiff system'; 010 otherwise 011 str = 'other algorithm'; 012 end 013 disp(str);

22 该文件的运行方法和效果如下: >> switch_examp Enter an algorithm in quotes (ode23, ode15s, etc:) 'ode23' 2nd/3rd order Enter an algorithm in quotes (ode23, ode15s, etc:) 'ode4' other algorithm

23 例子4-3中需要用户在执行程序的过程中输入一个字符串,switch语句根据用户的输入判断执行相应的case分支。若没有符合条件的case分支,则switch执行otherwise后面的语句。若switch结构中没有定义otherwise及其相应的代码,则程序不会进行任何操作,而是直接退出switch结构。 提示: 在处理以字符串变量或者常量参与的关系判断操作时,使用switch结构要比if-else结构效率高一些。 由于MATLAB的switch结构没有C语言的fall-through特性,所以,如果需要针对多个条件而使用同一个case分支的时候,需要使用元胞数组与之配合,参见例子4-4。

24 例子4-4 switch结构使用示例——switch_examp2.m。
001 clear all 002 003 var = input('Input a Numer:'); 004 switch var case 1 disp('1') case {2,3,4} disp('2 or 3 or 4') case 5 disp('5') otherwise disp('something else') 013 end

25 例子4-4运行的方法和效果如下: >> switch_examp2 Input a Numer:1 1 Input a Numer:3 2 or 3 or 4 Input a Numer:7 something else

26 例子4-4代码的核心部分为007行,这里使用元胞数组增加判断条件的个数,当输入的数字为2、3或者4时,switch结构将使用同一个case分支进行判断、计算。
注意: 从代码的完整性和可靠性角度出发,在使用switch语句时,一定要包含otherwise分支,这是一种良好的编程习惯。

27 循环结构 在解决很多问题的时候需要使用循环结构,例如求解数列的和或者进行某种迭代法求解数值方程时,都需要循环结构配合完成计算。 在MATLAB中,包含两种循环结构,一种是循环次数不确定的while循环,而另一种是循环次数确定的for循环。

28 1.while循环结构 while语句可以用来实现“当”型的循环结构,它的一般形式如下: while(表达式) MATLAB语句 end 当表达式为真时,循环将执行由语句构成的循环体,其特点是先判断循环条件,如果循环条件成立,即表达式运算结果为“真”,再执行循环体。循环体执行的语句可以是一句也可以是多句,在MATLAB语句之后必须使用关键字end作为整个循环结构的结束。另外,在循环过程中一定要能够改变关系表达式或者布尔类型变量的值,或者使用其他方法来跳出循环,否则会陷入死循环(无法正常退出的循环叫作死循环)。

29 例子4-5 使用while语句求解 。 001 i = 1; 002 sum = 0; 003 while ( i <= 1000 ) 004 sum = sum+i; 005 i = i+1; 006 end 007 str = ['计算结果为:',num2str(sum)]; 008 disp(str)

30 例子4-5的运行结果为 >> while_example 计算结果为:500500 例子4-5的002~006行使用了while循环结构,在循环结构中进行了累加的操作。需要注意的是,在MATLAB中没有类似C语言的++或者+=等运算操作符,因此在进行诸如累加或者递减的运算时,不得不给出完整的表达式。另外,例子4-5求数列和的算法的运算效率很低,在MATLAB中不要使用这样的结构完成类似的运算,而需要采用向量化的计算。

31 注意: while循环结构的关系表达式可以是某个数据变量或者常量,这时,将按照非零值为逻辑真进行相应的操作。另外,在进行上述操作时,若数据变量为空矩阵,则while语句将空矩阵作为逻辑假处理,也就是说,在while A MATLAB语句S1 end结构中,若A为空矩阵,则MATLAB语句S1永远不会被执行。

32 2.for循环结构 使用for语句构成循环是最灵活、简便的方法,不过,使用for语句循环需要预先知道循环体执行的次数,所以这种循环一般叫作确定循环。在MATLAB中for循环的基本结构如下: for index = start:increment:end MATLAB语句 end 其中,index的取值取决于start和end的值,一般地,这里通常使用等差的数列向量,参见例子4-6。

33 例子4-6 使用for语句求解 。 001 sum = 0; 002 for i = 1:1000 sum = sum+i; 004 end 005 str = ['计算结果为:',num2str(sum)]; 006 disp(str)

34 例子4-6运行的结果为 >> for_example 计算结果为:500500 在例子4-6中,002行的代码使用了确定次数的for循环结构,循环次数使用行向量进行控制,而且索引值i按照默认的数值1进行递增。 在for循环语句中,不仅可以使用行向量进行循环迭代的处理,也可以使用矩阵作为循环次数的控制变量,这时循环的索引值将直接使用矩阵的每一列,循环的次数为矩阵的列数,参见例子4-7。

35 例子4-7 for循环示例。 001 A = rand(3,4); 002 003 for i = A sum = mean(i) 005 end 例子4-7运行的结果为 >> for_matrices sum = 0.2728 0.6649 0.4275 0.5220

36 例子4-7尽管只有短短的几行,但是在003行使用了一个矩阵作为循环的索引值,于是,循环结果就分别计算矩阵的每一列元素的均值。
和其他高级语言类似,MATLAB的循环结构也可以进行嵌套使用,使用嵌套的循环需要注意for关键字和end关键字之间的配对使用,请读者根据高级语言的一般特性来推断其运行的方式,这里就不再赘述了。

37 break语句和continue语句 在循环结构中还有两条语句会影响程序的流程,这就是break语句和continue语句,这两条语句的基本功能如下: ● 当break语句使用在循环体中的时候,其作用是能够在执行循环体的时候强迫终止循环,即控制程序的流程,使其提前退出循环,它的使用方法是 break; ● continue语句出现在循环体中的时候,其作用是能够中断本次的循环体运行,将程序的流程跳转到判断循环条件的语句处,继续下一次的循环,它的使用方法是 continue;

38 例子4-8 break语句示例——break_example.m。
001 i = 0; 002 j = 0; 003 k = 0; 004 for i = 1:2 for j = 1:2 for k = 1:2 if(k == 2) disp('退出循环'); break; end

39 011 str = sprintf('I = %d , J = %d , K = %d',i,j,k);
disp(str); end end 015 end 016 disp('程序运行结束');

40 例子4-8 的运行结果如下: >> break_example I = 1 , J = 1 , K = 1 退出循环 I = 1 , J = 2 , K = 1 I = 2 , J = 1 , K = 1 I = 2 , J = 2 , K = 1 程序运行结束

41 break语句的作用是退出当前的循环结构运行,所以在例子4-8中,位于最内层循环的break语句执行的结果是退出了最内层的循环k,位于外层的循环i和j还是都运行完毕了。

42 例子4-9 continue语句示例。 001 i = 0; 002 for i =1:6 if(i>3) continue else str = sprintf('I = %d',i); disp(str); end 009 end 010 str = sprintf('循环结束 I = %d',i); 011 disp(str);

43 例子4-9的运行结果如下: >> continue_example I = 1 I = 2 I = 3 循环结束 I = 6 continue语句的作用在例子4-9中得到了充分说明,该语句终止当前的循环,然后继续下一次循环运算,直到所有的循环迭代运算结束为止。

44 提高运算性能 M语言和其他的高级语言不同,由于采用了解释型语言,所以M语言的执行效率肯定低于编译型语言(例如C语言)。然而,随着MATLAB版本的不断升级,再加之合理利用MATLAB向量运算等特点可以较大幅度地提高M语言代码的执行效率。在本小节结合一些具体的例子来讲述M语言编程以及MATLAB软件本身在提高程序执行效率方面的一些特性。

45 1.向量化运算 首先,希望读者牢记这样一点,MATLAB最初的目的是提供便利的矩阵数据操作能力。所以在大多数的应用程序中,不要使用循环结构操作矩阵的元素,应直接使用矩阵元素的索引或者矩阵运算的函数,这样做不仅能够提高代码的执行效率,而且还能够提高程序的可读性,这就是所谓的向量化的运算,也就是说,尽量将使用while循环或者for循环的语句结构转换成等价的向量或者矩阵运算,以提高程序的运算速度,参见例子4-10。

46 例子4-10 向量化运算——array_vs_loops.m。
001 Mass = rand(5,10000); 002 Length = rand(5,10000); 003 Width = rand(5,10000); 004 Height = rand(5,10000); 005 006 [rows, cols] = size(Mass); 007 008 disp([char(10), '使用数组运算:']) 009 tic 010 Density = Mass./(Length.*Width.*Height); 011 toc

47 012 013 disp([char(10), '使用循环结构:']) 014 tic; 015 for I = 1:rows for J = 1:cols Density(I) = Mass(I,J)/(Length(I,J)*Width(I,J)*Height(I,J)); end 019 end 020 toc

48 例子4-10比较了循环结构和数组运算的执行效率,程序中分别在010行使用数组运算和在015~019行使用循环结构完成了同样的工作。程序的运行结果如下:
>> array_vs_loops 使用数组运算: elapsed_time = 使用循环结构: 0.0100

49 2.预分配存储空间 另外一种能够提高运算效率的方法就是进行内存变量存储空间的预分配,首先察看例子4-11。 例子4-11 内存预分配的例子—— pre_allocate.m。 001 disp([char(10), '使用内存预分配:']) 002 pre_allo = zeros(10000,1); 003 tic; 004 for I = 1:10000 pre_allo(I) = rand(1); 006 end

50 007 toc 008 009 disp([char(10), '不使用内存预分配:']) 010 tic; 011 for J = 1:10000 not_pre_allo(J) = rand(1); 013 end 014 toc

51 例子4-11的执行结果如下: >> pre_allocate 使用内存预分配: elapsed_time = 0.0900 不使用内存预分配: 0.3410

52 上面两种不同的运算惟一的区别就是程序002行,执行这行语句之后,MATLAB自动分配了10 000个连续的内存空间用于存储数据,MATLAB将一次创建足够的存储空间,然后依次赋值。而后者not_pre_alloc变量没有进行相应的操作,所以带来了两次运算结果的不同。 在不使用内存预分配的运算中,MATLAB是如何进行操作的呢? 当I=1时,MATLAB将使用一小块长度为一个单元大小的内存保存一位随机数。 当I=2时,MATLAB寻找一块两单元大小的内存区,一个单元放第一个随机数,第二个放另外一个随机数。

53 当I = 10000 时,MATLAB 寻找一块容纳10000单元的内存区存放以前的9999个随机数,同时把最新的一个随机数加入进去。代码运行的结果造成了存储空间的浪费,降低了程序的执行速度。
所以,在编写M语言程序的时候需要尽量使用内存的预分配,而少使用或者不使用数组内存空间的自动扩充方式。MATLAB针对不同的数据类型有不同的内存预分配函数,见表4-1。

54 表4-1 内存预分配函数

55 表4-1中说明了不同数据类型所要使用的内存预分配函数,其中结构类型的数组需要两个函数配合使用,利用struct函数构造结构,而使用repmat函数创建数组。
对于非双精度类型的数据,例如整数类型或者单精度类型,进行内存的预分配时,需要使用相应的构造函数或者类型转换函数,例如: Y = int16(zeros(1:10000)); 在上面的表达式中创建了连续的10 000个16位整数的存储空间。 当预先分配的内存空间无法容纳数据时,则可以通过repmat函数来扩充数组的存储空间。

56 3.MATLAB加速器 尽管利用了上述的特性可以明显提高M语言的运行效率,但是在同等条件下,M语言的效率还是无法和第四代高级编程语言的执行效率相媲美,于是,在MATLAB 6.5版本中,新增加了MATLAB性能加速器这样一个新特性。MATLAB性能加速器能够明显改善循环结构、内建函数调用等方面的运行效率,特别是在处理循环迭代次数确定的for循环结构中,其运算速度较早期的MATLAB版本有很大的提高,在表4-2中总结了MATLAB加速器能够发挥作用的M语言元素。

57 表4-2 加速器能够影响的M语言元素

58 表4-3 性能加速器不起作用的M语言元素

59 例子4-12 性能加速器说明——jitaccel.m。
001 tic 002 A = rand(500000,1); 003 B = zeros(500000,1); 004 B(1) = A(1); 005 for i = 2:500000 B(i) = B(i?1) + A(i); 007 end 008 toc

60 在MATLAB 6.1中: >> jitaccel elapsed_time = 5.2780 在MATLAB 6.5中: 0.0900 若直接使用内建函数,则运行的效果为 >> tic;B = cumsum(A,1);toc 0.0200

61 通过比较,可以看出MATLAB性能加速器明显提高了程序的执行效率,在有些情况下,加速器使执行速度提高10~100倍。
注意: 例子4-12代码运算得到的时间和具体运行MATLAB的计算机的性能有关,不同的计算机环境得到的计算结果不尽相同。另外,MATLAB性能加速器在MATLAB 6.5中的默认设置为开启(On)状态。

62 4.3 脚 本 文 件 脚本文件是最简单的一种M语言文件,在本章前面章节的例子中都使用了脚本文件。所谓脚本文件,就是由一系列的MATLAB指令和命令组成的纯文本格式的M文件,执行脚本文件时,文件中的指令或者命令按照出现在脚本文件中的顺序依次执行。脚本文件没有输入参数,也没有输出参数,执行起来就像早期的DOS操作系统的批处理文件一样,而脚本文件处理的数据或者变量必须在MATLAB的公共工作空间中。

63 例子4-13 脚本文件示例。 001 % 注释行 002 % M脚本文件示例 003 % "flower petal" 004 % 以下为代码行 005 % 计算 006 theta = -pi:0.01:pi; 007 rho(1,:) = 2*sin(5*theta).^2; 008 rho(2,:) = cos(10*theta).^3; 009 rho(3,:) = sin(theta).^2; 010 rho(4,:) = 5*cos(3.5*theta).^3;

64 for k = 1:4 % 图形输出 subplot(2,2,k) polar(theta,rho(k,:)) 015 end 016 disp('程序运行结束!') 在MATLAB命令行中运行该脚本文件: >> script_example 程序运行结束!

65 图4-2 例子4-13脚本文件的运行结果

66 仔细察看例子4-13的脚本文件,在脚本文件中,主要由注释行和代码行组成。M文件的注释行需要使用%定义符,在%之后的所有文本都认为是注释文本,不过,M文件的注释定义符仅能影响一行代码,类似于C++语言中的“//”。然而在M语言中,没有类似C语言的注释定义符“/*”和“*/”,所以无法一次定义多行注释。给程序添加适当的注释是良好的编程习惯,希望读者能够在日常编程中多多使用。

67 脚本文件中的代码行都是一些简单的MATLAB指令或者命令,这些命令可以用来完成相应的计算处理数据、绘制图形结果的操作,也可以在脚本文件中调用其他的函数完成复杂的数学运算,在例子4-13中就完成了这些工作。另外,在MATLAB中还有一些指令用来处理程序和用户之间的交互,在表4-4中进行了总结。

68 表4-4 脚本文件中常用的MATLAB指令

69 MATLAB一般使用脚本文件作为某种批处理文件,其中,有两个批处理文件经常被MATLAB自动调用,这两个脚本文件分别为startup
MATLAB一般使用脚本文件作为某种批处理文件,其中,有两个批处理文件经常被MATLAB自动调用,这两个脚本文件分别为startup.m和finish.m。 startup.m文件在MATLAB启动时自动被执行,用户可以自己创建并定义编写该文件,例如在文件中添加物理常量的定义、系统变量的设置或者MATLAB搜索路径的设置。当用户安装MATLAB之后,在<MATLABROOT>\toolbox\local路径下有一个M文件,名为Starupsav.m,该文件可以看作是startup.m文件的模板,可以修改该文件,然后将其以文件名startup.m的形式保存在<MATLABROOT>\toolbox\local路径下。

70 与startup. m文件相对应的是finish
与startup.m文件相对应的是finish.m文件,该文件在MATLAB退出时自动执行,用户可以自己创建并定义编写该文件,例如在文件中添加保存数据等指令,这样可以将每次退出前的工作结果进行保留。同样,在<MATLABROOT>\toolbox\local路径下有两个文件,分别为finishsav.m和finishdlg.m,这两个文件可以用来作为finish.m文件的模板,相关的具体内容介绍请读者自己察看相应的文件和帮助文档。

71 4.4 函 数 文 件 基本结构 M函数文件和脚本文件不同,函数文件不仅有自己特殊的文件格式,不同的函数还分别具有自己的工作空间。同其他高级语言类似,M函数文件也有局部变量和全局变量。读者首先需要了解的是函数文件的基本结构,参见例子4-14。

72 例子4-14 函数文件示例——average.m。
001 function y = average(x) 002 % AVERAGE 求向量元素的均值 003 % 语法: 004 % Y = average(X) 005 % 其中,X 是向量,Y为计算得到向量元素的均值 006 % 若输入参数为非向量则出错 007 008 % 代码行 009 [m,n] = size(x); 010 % 判断输入参数是否为向量

73 011 if (~((m == 1) | (n == 1)) | (m == 1 & n == 1))
% 若输入参数不是向量,则出错 error('Input must be a vector') 014 end 015 % 计算向量元素的均值 016 y = sum(x)/length(x);

74 在MATLAB命令行中,键入下面的指令运行例子4-14的代码:
>> z = 1:99; >> y = average(z) y = 50

75 M语言函数文件具有下面的不同部分: * 函数定义行。 * 在线帮助。 * 注释行。 * M语言代码。

76 下面结合例子4-14分别说明这些部分的构成。 函数定义行,例子4-14的函数定义行为代码的001行: 001 function y = average(x)

77 这一行代码中包括关键字function、函数输出参数y、函数的名称average和函数的输入参数x。需要读者注意的是函数的名称,函数的名称定义要求必须以字符开头,后面可以用字符、数字和下划线的组合构成函数名称。MATLAB对函数名称的长度有限定,读者可以在自己的MATLAB中,通过执行namelengthmax函数获取相应的数值。假设该函数返回的数值为N,若函数的名称长度超过了N,则MATLAB使用函数名称的前N个字符作为函数名称。 一般推荐将函数名称用小写的英文字符表示,同时函数的M文件名称最好和函数名称保持一致,若文件名称和函数名称不一致,则调用函数的时候需要使用文件名称而非函数名称。

78 M函数文件的在线帮助为紧随在函数定义行的注释行。在例子4-14中,average函数的在线帮助为002~006行的注释行。若在MATLAB命令行中键入下面的指令:
>> help average 在MATLAB的命令窗口中就会出现: AVERAGE 求向量元素的均值 语法: Y = average(X) 其中,X是向量,Y为计算得到向量元素的均值 若输入参数为非向量则出错

79 其中,在线帮助中比较重要而且特殊的是在线帮助的第一行,在MATLAB中将这行注释称为H1帮助行,它是在线帮助的第一行,若使用lookfor函数查询函数时,仅查询并显示函数的H1帮助行,例如,在MATLAB命令行中键入下面的指令: >> lookfor average 在MATLAB的命令窗口中就会出现: AVERAGE 求向量元素的均值 MEAN Average or mean value.

80 由于H1帮助行的特殊作用,所以在用户自己定义M函数文件时,一定要编写相应的H1帮助行,对函数进行简明、扼要的说明或者解释。
例子4-14的008、010、012、015行代码分别是程序具体的注释行,这些注释行不会显示在在线帮助中,主要原因就是这些注释行没有紧随在H1帮助行的后面,其中008行的注释与在线帮助之间有一个空行。其实从008行开始一直到文件的结尾都是M函数文件的代码行,这些代码行需要完成具体的算法,实现用户的具体功能。代码行就是用户开发的算法M语言的实现。

81 输入输出参数 M语言函数文件的输入、输出参数和其他高级语言的输入、输出参数不同,在定义这些输入、输出参数的时候不需要指出变量的类型,因为MATLAB默认这些参数都使用双精度类型,这样可以简化程序的编写。而且在定义参数时,也没有确定输入参数的维数或者尺寸,也就是说,直接从参数上无法判断输入来的是标量、向量还是矩阵,只有通过程序内部的具体代码来加以判断。 M语言的函数文件不仅可以有一个输入参数和一个返回值,还可以为M语言函数文件定义多个输入参数和多个输出参数,见例子4-15。

82 例子4-15 多个输入、输出参数的M函数。 001 function [avg, stdev, r] = ourstats(x,tol) 002 % OURSTATS 多输入输出参数示例 003 % 该函数计算处理矩阵,得到相应的均值、 004 % 标准差和矩阵的秩 005 [m,n] = size(x); 006 if m == 1 m = n; 008 end 009 % Average 010 avg = sum(x)/m;

83 011% Sandad deviation 012 stdev = sqrt(sum(x.^2)/m - avg.^2); 013 % Rank 014 s = svd(x); 015 r = sum(s > tol); 运行例子4-15,在MATLAB命令行中,键入下面的指令: >> A = [ 1 2 3; 4 5 6] A =

84 >> [a,s,r] = ourstats(A,0.1)
s = r = 2 >> ourstats(A,0.1) ans = >> [a,s] = ourstats(A,0.1)

85 例子4-15的M代码具有两个输入参数、三个输出参数,所以在使用该函数的时候,需要将必要的输入、输出参数写明。注意调用该函数时的语法,将输出参数依次写在一个向量中,若输出参数的个数与函数定义的输出参数个数不一致,则在例子4-15中,将计算得到的前几个输出参数作为返回值,个数等于用户指定的输出参数个数。计算的结果依次赋值给不同的变量。 在使用多个输入、输出参数的时候,往往需要判断用户写明的输入、输出参数的个数,若个数与函数定义不符合的时候,将给出错误或者警告信息,这个时候,需要使用函数nargin和nargout来获取函数的输入、输出参数个数,见例子4-16。

86 例子4-16 nargin和nargout示例。
001 function c = testarg(a,b) 002 %TESTARG 检测输入输出参数个数 003 % 该函数根据不同的输入输出参数个 004 % 数进行相应的操作 005 if (nargout ~= 1) disp('使用该函数必须指定一个输出参数!'); return 008 end 009 switch nargin case 0

87 011 disp('使用该函数至少需要一个输入参数!');
c = []; return case 1 c = a.^2; case 2 c = a+b; 018 end

88 运行例子4-16,在MATLAB命令行窗口中,键入下面的指令:
>> testarg(A,B) 使用该函数必须指定一个输出参数! >> C = testarg 使用该函数至少需要一个输入参数! C = [ ] >> C = testarg(A)

89 >> C = testarg(A,B)
>> C = testarg(A,B,C) ??? Error using ==> testarg Too many input arguments.

90 运行例子4-16的代码时,使用不同的输入、输出参数,函数本身和MATLAB系统将自动检测参数的个数,在最后一次调用时,由于使用的输入参数个数超过了函数定义的个数,所以MATLAB给出了错误信息。
MATLAB的M函数文件还可以具有个数不确定的输入、输出参数,也就是说,在定义M函数文件的时候,不指明输入、输出参数的个数,而是在程序中通过编写程序完成具体参数的确定,完成该功能主要依靠varargin和varargout函数。

91 当函数的定义具有以下形式的时候 function y = function_name(varargin) 函数function_name可以接受任意个数的输入参数;而当函数具有下面的形式时 function varargout = function_name(n) 函数function_name可以输出任意个数的输出参数。 可以将varargin函数和varargout函数结合在同一个M文件函数中使用。

92 例子4-17 不确定的输入参数个数。 001 function y = varargin_example(varargin) 002 %VARARGIN_EXAMPLE 不确定输入参数例子 003 str = sprintf('输入参数的个数 := %d',length(varargin)); 004 disp(str); 005 y = 0; 006 % varargin的类型 007 class(varargin) 008 for i = 1:length(varargin) %varargin为元胞数组

93 010 if(isnumeric(varargin{i}))
% 将每个为数值数组的输入参数 % 的第一个元素累加求和 y = y + varargin{i}(1); end 015 end

94 运行例子4-17,在MATLAB命令行中键入下面的指令:
>> varargin_example 输入参数的个数 := 0 ans = cell >> varargin_example('a',[1 2 3],3,rand(2,5)) 输入参数的个数 := 4 4.0153

95 通过例子4-17的运行结果和例子的代码可以看出varargin函数的使用方法。它相当于在MATLAB的函数入口处开辟了足够大的空间,用于接受各种用户的输入。在使用这个函数的时候,需要在程序中判别函数输入参数的类别,并且从元胞数组中正确提取变量,在MATLAB中将这一过程称之为unpacking。 与之相对应的,将函数的输入参数传递给varargout函数被称之为packing,在这一过程中,需要将所有必要的输出参数传递给varargout元胞数组,在传递参数的时候,还需要注意参数的顺序,在例子4-18中介绍了这一过程。

96 例子4-18 不确定的输出参数。 001 function varargout=varargout_example(varargin) 002 %VARARGOUT_EXAMPLE不确定个数的输出参数 003 004 % 判断输出参数的个数 005 % 下面注释行中的代码执行有错误 006 % str = sprintf('输出参数的个数:=%d',length(varargout)); 007 % 必须使用nargout 008 str = sprintf('输出参数的个数 :=%d',nargout); 009 disp(str); 010 if(nargout <= nargin) for k=1:nargout varargout{k} = varargin{nargin-k+1}; end 014 end

97 运行例子4-18的代码,在MATLAB命令行中,键入下面的指令:
>> [a b] = varargout_example(1,2,3,4) 输出参数的个数 :=2 a = 4 b = 3

98 例子4-18的第006行代码,若将注释行符号“%”删除,则程序运行会出现错误。在程序中,若需要判断输出参数的个数不能使用length函数,而需要使用nargout函数。另外,在操作输出参数时,需要判断输出参数的个数,根据输出参数的个数完成相应的操作。 在使用不确定的输入、输出参数时,还可以像下面的代码行一样使用这两个参数: function [out1,out2] = example1(a,b,varargin) function [i,j,varargout] = example2(x1,y1,x2,y2,flag) 若使用varargout和varargin参数,除了必须给定的参数以外,其余的参数是任意数量可变的,具体的操作参阅例子4-19。

99 例子4-19 可变的输入、输出参数。 001 function [x,y,varargout] = vararginout(a,b,c,d,varargin) 002 %VARARGINOUT 可变的输入输出参数 003 str = sprintf('输入参数的个数:=%d',nargin); 004 disp(str); 005 str = sprintf('输出参数的个数:=%d',nargout); 006 disp(str); 007 if(nargin <=4) error('输入参数必须多于4个'); 009 end 010 % 处理输入输出参数

100 011 x = a+b+c+d; 012 y = a-b+c-d; 013 if(nargout >2 && nargin>4) for(i =1:nargout-2) % 这里也许会出错,小心! varargout{i} = varargin{end-i-1}; end 018 end

101 运行例子4-19的代码,在MATLAB命令行中,键入下面的指令:
>> vararginout(1, 2, 3, 4, 5) 输入参数的个数: =5 输出参数的个数: =0 ans = 10 >> [a,b,c]=vararginout(1, 2, 3, 4, 5, 6, 7, 8) 输入参数的个数: =8 输出参数的个数: =3 a = b = -2 c = 6

102 子函数和私有函数 同一个M函数文件中可以包含多个函数。如果在同一个M函数文件中包含了多个函数,那么将出现在文件中的第一个M函数称为主函数(primary function),其余的函数称为子函数(subfunction)。M函数文件的名称一般与主函数的名称保持一致,其他函数都必须按照函数的基本结构来书写,每一个函数的开始都是函数定义行,函数的结尾是另一个函数的定义行的开始或者整个M文件的结尾(最后一个子函数的结尾就是文件结束符)。不过,子函数不像主函数,一般子函数没有在线帮助,子函数的作用范围有限,它只能被那些在定义子函数的M文件中定义的函数(包括主函数和其他子函数)调用,不能被其他M文件定义的函数调用。

103 例子4-20 子函数应用例子。 001 function [avg,med] = newstats(u) % 主函数 002 % NEWSTATS 计算均值和中间值 003 n = length(u); 004 avg = mean(u,n); % 调用子函数 005 med = median(u,n); % 调用子函数 006 007 function a = mean(v,n) % 子函数 008 % 计算平均值 009 a = sum(v)/n; 010

104 011 function m = median(v,n) % 子函数
012 % 计算中间值 013 w = sort(v); 014 if rem(n,2) == 1 m = w((n+1)/2); 016 else m = (w(n/2)+w(n/2+1))/2; 018 end

105 运行例子4-20,在MATALB命令行窗口中,键入下面的指令:
>> x = 1:11; >> [mean,mid] = newstats(x) mean = 6 mid =

106 在MATLAB中有一类函数被称为私有函数,这类函数被放置在名称为private的子目录中。每一个函数文件都是标准的M语言函数文件,没有特殊的关键字。但是,这些函数仅能被那些位于private子目录的上一层目录中的函数调用。例如,假设在MATLAB的搜索路径中包含路径\ProjectA ,那么所有位于 \ProjectA\private路径下的函数,只能在其上一层路径 \ProjectA中的函数文件中调用。由于私有函数作用范围的特殊性,不同父路径下的私有函数可以使用相同的函数名。由于MATLAB搜索函数时优先搜索私有函数,所以如果同时存在私有函数名func1.m和非私有函数名func1.m,则私有函数func1.m被优先执行。 创建私有函数的方法非常简单,只要将那些需要设置为私有的函数都拷贝到一个private子目录中,则这些函数就能被那些位于父层目录中的M函数调用了。

107 在表4-4中总结了子函数和私有函数的区别。 表4-4 私有函数和子函数比较

108 例子4-21 私有函数的例子。 创建一个新的函数文件,代码如下: 001 function x = pmean(v,n) 002 %MEAN 私有函数例子 003 % 将该函数文件保存在pivae子目录中, 004 % 则该函数仅能在上层目录的函数文件 005 % 中调用 006 disp('私有函数 mean'); 007 x = sum(v)/n;

109 图4-3 将pmean.m文件保存在private目录下

110 接着,修改newstats函数,并将其另存为newstats1.m。
001 function [avg,med] = newstats1(u) % 主函数 002 % NEWSTATS 计算均值和中间值 003 n = length(u); 004 avg = mean(u,n); % 调用子函数 005 avg1 = pmean(u,n) % 调用私有函数 006 med = median(u,n); % 调用子函数 007 008 function a = mean(v,n) % 子函数 009 % 计算平均值 010 disp('子函数 mean'); 011 a = sum(v)/n;

111 然后在MATLAB命令行中,执行newstats1.m函数:
子函数 mean avg = 5.5000 私有函数mean avg1 =

112 局部变量和全局变量 同C语言类似,在M语言函数中也存在局部变量和全局变量。所谓局部变量,就是那些在M函数内部声明并使用的变量。这些变量仅能在函数调用执行期间被使用,一旦函数结束运行,则这些变量占用的内存空间将自动被释放,变量的数值也就不存在了。这是由于MATLAB的解释器在解释执行函数的时候,为不同的函数创建不同的工作空间,函数彼此的工作空间相互独立,一旦函数执行完毕,则函数的工作空间就不存在了。 在本章前面的例子中,每个例子的函数内部声明使用的变量都是局部变量,所以函数执行完毕后,MATLAB的基本工作空间中没有这些变量存在,参见例子4-22。

113 例子4-22 局部变量的例子。 001 function local 002 %LOCAL 察看局部变量的例子 003 x = rand(2,2); 004 y = zeros(2,2); 005 z = '函数中的变量'; 006 u = {x,y,z}; 007 disp(z) 008 whos

114 运行例子4-22,在MATLAB命令行中,键入下面的指令:
>> local 函数中的变量 Name Size Bytes Class u x cell array x x double array y x double array z x char array Grand total is 31 elements using 332 bytes >> whos

115 通过运行local函数可以看到,所有在函数中创建的变量在函数运行结束后就不存在了。也就是说,局部变量的生存周期仅在函数的活动期间内。
与局部变量相对应的就是全局变量。MATLAB将全局变量保存在特殊的工作空间进行统一维护、管理,而将变量声明为全局变量的方法就是在使用变量前,用关键字global声明,例如声明全局变量gXY: >> global gXY >> whos Name Size Bytes Class gXY x double array (global) Grand total is 0 elements using 0 bytes

116 需要强调一点,MATLAB管理、维护全局变量和局部变量使用了不同的工作空间,所以使用global关键字创建全局变量的时候有三种情况:
(1) 若声明为全局的变量在当前的工作空间和全局工作空间都不存在,则创建一个新的变量,然后为这个变量赋值为空数组,该变量同时存在于局部工作空间和全局工作空间。 (2) 若声明为全局的变量已经存在于全局工作空间中,则不会在全局工作空间创建新的变量,其数值同时赋值给局部工作空间中的变量。 (3) 若声明为全局的变量存在于局部工作空间中,而全局工作空间不存在,则系统会提示一个警告信息,同时将局部的变量“挪”到全局工作空间中。

117 例子4-23 全局变量的例子。 在MATLAB命令行窗口中,键入下面的指令: >>% 创建全局变量并赋值 >> global myx >> myx = 10; >>% 变量的信息 >> whos Name Size Bytes Class myx x double array (global) Grand total is 1 element using 8 bytes

118 >>% 清除变量 >> clear myx >>% 察看信息 >> whos >> whos global Name Size Bytes Class myx x double array (global) Grand total is 1 element using 8 bytes >>% 在局部工作空间再次创建变量 >> myx = 23 myx = 23 >>% 变量的信息

119 >> whos Name Size Bytes Class myx x double array Grand total is 1 element using 8 bytes >>% 将其修改为全局变量(注意警告信息) >> global myx Warning: The value of local variables may have been changed to match the globals. Future versions of MATLAB will require that you declare a variable to be global before you use that variable. >>%看看变量的数值 >> myx myx = 10

120 >>% 清除当前的工作空间 >> clear >> whos global Name Size Bytes Class myx x double array (global) Grand total is 1 element using 8 bytes >>% 清除所有的内存空间 >> clear all

121 使用全局变量时,需要小心留意,因为全局变量可以在任何的函数中进行读写,这样,可能在比较复杂的程序中查找全局变量错误的时候就非常的麻烦。
在MATLAB中还有一类变量被声明为persistent,本书将其称之为保留变量,这类变量类似于C语言函数中被声明为static类型的变量。这类变量在函数退出的时候不被释放,当函数再一次被调用的时候,这些变量保留上次函数退出时的数值。被声明为persistent的变量具有以下特征: * 变量仅能在声明变量的函数内使用,其他函数不能直接使用这些变量。 * 函数执行退出后,MATLAB不清除这些变量占用的内存。 * 当函数被清除或者重新编辑后,保留的变量被清除。

122 例子4-24 persistent关键字。 001 function y = persistent_example(x) 002 %PERSISTENT_EXAMPLE 保留变量使用示例 003 for i = 1:x y = myfun; 005 end 006 007 function y = myfun 008 % 子函数 009 % persistent关键字的使用 010 persistent count;

123 011 % count记录函数调用的次数 012 if( isempty(count)) count = 1; 014 else count = count + 1; 016 end 017 str = sprintf('第%d次调用该函数',count); 018 disp(str); 019 y = count;

124 在MATLAB命令行窗口中,执行该函数:
>> persistent_example(2) 第1次调用该函数 第2次调用该函数 ans = 2 >> persistent_example(3) 第3次调用该函数 第4次调用该函数 第5次调用该函数 5

125 从例子4-24的执行情况可以看出变量count记录了函数被调用的次数,如果在MATLAB命令行中键入如下指令:
>> clear all 则所有的变量都会被清除,此时再次执行例子4-24: >> persistent_example(3) 第1次调用该函数 第2次调用该函数 第3次调用该函数 ans = 3 count数值又重新计数了。

126 由于使用全局变量有这样那样的危险性,所以建议读者尽量使用函数参数传递的方式来完成函数之间的数据共享,或者可以使用persistent关键字将必要的变量保护起来。另外,isglobal 命令可以用来测试本地变量与全局工作区中的变量是否存在联系,该命令并不能判断全局工作区中是否存在该变量。如果全局工作区中存在某个变量,但与本地工作区中的相应变量没有联系,isglobal函数返回值为0 (假)。

127 函数执行规则 到这里,读者应该能够创建自己的算法函数,并且能够执行任何M语言函数了。只要在MATLAB的命令行窗口中键入函数的名称,并且提供足够的输入、输出参数就会得到正确的结果。如前文所述,M语言的函数被MATLAB的解释器解释、执行,所以,在本小节中将简要讨论一下解释器解释执行程序的问题。 当用户在MATLAB命令行窗口键入一条命令或者执行M语言文件中包含的一条语句或者指令时,MATLAB解释器就负责解析用户的输入,并且给出相应的答案。MATLAB解释器解析命令按照一定优先级进行:

128 * 首先判断输入的命令是否为变量。 * 若不是内存中的变量,判断输入的命令是否为MATLAB的内建函数。 * 若不是内建函数,则判断输入的命令是否为子函数。 * 若不是子函数,则判断输入的命令是否为私有函数。 * 若不是私有函数,则判断输入的命令是否为MATLAB搜索路径中所包含的某个文件或函数。 * 若在同一个路径下发现同名的三种类型的文件MEX文件、P代码文件和M代码文件,则优先执行MEX文件,其次是P代码文件,最后执行的是M语言文件。

129 提示: 若需要了解具体调用的是哪一个对象,则可以使用which命令获取相应的信息。 这里需要注意的一点就是,MATLAB内存中的变量比函数具有较高的优先级,如例子4-25所示。

130 例子4-25 MATLAB命令解析的优先级。 在MATLAB命令行窗口中,键入下面的指令: >> cos = 'This is a string!' cos = This is a string! >> cos(4) ans = s >> which cos cos is a variable.

131 >> clear all >> cos(4) ans = >> which cos cos is a built-in function. 通过例子4-25说明,MATLAB命令解释器在解释、执行MATLAB指令时,将变量置于第一位,所以读者在进行M语言编程的时候注意自己定义的变量名称,避免命名冲突。

132 提示: P代码(伪代码)文件是从M 文件用pcode命令生成的。伪代码是经过预编译的,无论何时函数被调用,MATLAB都能访问的现成代码。因为 P 文件是预编译过的,所以它们的实际内容对用户而言是不可读的,而且在一般情况下它们比相应的 M 文件运行速度快。将M语言函数文件转变为P代码文件的方法是 >> pcode fun1 fun MEX文件是一种特殊的文件格式,主要使用C语言进行开发,并且经过编译后在MATLAB环境中使用,关于MEX文件的详细信息将在《MATLAB外部接口编程》一书中详细讨论。

133 4.5 M 文 件 调 试 M语言文件的编辑器——meditor不仅仅是一个文件编辑器,同时还是一个可视化的调试开发环境。在M文件编辑器中可以对M脚本文件、函数文件进行调试,以排查程序的错误。M文件的调试不仅可以在文件编辑器中进行,而且还可以在命令行中结合具体的命令进行,但是过程相对麻烦一些,所以本小节将重点讲述在M文件编辑器中进行可视化调试的过程。 一般地来说,应用程序的错误有两类,一类是语法错误,另外一类是运行时的错误。其中,语法错误包括了词法或者文法的错误,例如函数名称的拼写错误等。而运行时的错误是指那些程序运行过程中得到的结果不是用户需要的情况。但是,由于M文件是一种解释型语言,语法错误和运行时的错误都只有在运行过程中才能发现,所以程序的调试往往是在程序无法得到正确结果时进行程序修正的惟一手段。

134 为了能够有效地处理各种情况,M语言的断点类型除了类似C语言的用户定义的断点外,还有几种自动断点,分别为
* Stop if Error。 * Stop if Warning。 * Stop if NaN or Inf。 * Stop if All Errors。 这些自动断点可以在程序中设置,当程序运行过程中发生了错误或者警告,则程序运行中断,进入调试状态。

135 图4-4 M文件编辑器的Breakpoints菜单

136 例子4-26 M文件调试代码——stats_error.m。
001 function [totalsum,average] = stats_error (input_vector) 002 % STATS_ERROR - Calculates cumulative total & average 003 totalsum = sum(input_vector); 004 average = ourmean(input_vector); 005 006 function y = ourmean (x) 007 % OURMEAN - Calculates average 008 [m,n] = size(x); 009 if m == 1 m = n; 011 end 012 y = sum(input_vector)/m;

137 首先在MATLAB环境中启动M文件编辑器,然后选择M文件编辑器中“Breakpoints”菜单下的“Stop if Error”命令。注意,这时不一定需要将stats_error.m文件在文件编辑器中打开。 然后,在MATLAB命令行窗口中键入下面的指令: >> [sum avg] = stats_error(rand(1,50)) ??? Undefined function or variable 'input_vector'. Error in ==> D:\TEMP\ch4\stats_error.m (ourmean) On line 12 ==> y = sum(input_vector)/m;

138 图4-5 处于调试状态的M文件编辑器

139 在M文件编辑器中,第12行代码前有绿色的箭头,表示当前程序运行在此处中断。通过用户界面中的Stack下拉框可以察看当前应用程序使用堆栈的状态,如本例子中Stack下拉框中包含如下的内容:ourmean、stats_error和Base,由下至上,分别为调用者和被调用者之间的关系,同时也显示了当前的工作空间。另外,部分按钮从编辑状态进入调试状态,如图4-6所示。 这些按钮分别执行增加断点、清除断点、单步执行等调试程序的功能。将鼠标光标移动到按钮处并保持几秒钟,MATLAB的文件编辑能够给出相应的提示。

140 图4-6 调试程序的按钮

141 此时,MATLAB命令行窗口也处于调试状态,在这种状态下命令行提示符为“K>>”,在该命令行提示符中可以任意键入MATLAB指令进行运算和处理,不过需要注意,此时的工作空间是函数正在应用的空间,若在命令行窗口中键入的指令影响了工作空间中的变量,则可以直接影响程序运行的结果。 例如,在当前的提示符“K>>”键入下面的指令: K>> whos Name Size Bytes Class m x double array n x double array x x double array Grand total is 52 elements using 416 bytes

142 可以看到,当前的工作空间下没有变量名input_vector,这也是该程序执行出错的原因,将程序中第12行的input_vector修改成为x就能得到正确的答案了。
MATLAB可视化程序调试功能相对于Visual C++的可视化调试功能弱了一些,但是,在调试程序的过程中通过MATLAB命令行窗口的配合,充分利用MATLAB命令行窗口“演算纸”的功能,能够非常方便地调试M语言应用程序。 另外,MATLAB也提供了一些指令用于进行M文件的调试,在表4-5中对这些命令进行了总结。

143 表4-5 应用与调试M文件的指令

144 表4-5中指令的具体使用方法请读者查阅在线帮助或者MATLAB的帮助文档,在本书中就不再赘述了。其中,比较常用的指令是dbquit,在可视化调试过程中,往往会出现没有退出调试状态就关闭了M文件编辑器的情况,这时可以在“K>>”提示符下键入该指令退出调试状态。另外,在startup.m文件中利用dbstop指令预先设置自动断点为有效,这样就不必每次在调试应用程序前设置自动断点了。

145 4.6 M文件性能分析 使用M文件性能分析器有两种方法,其中一种方法是通过命令行,另外一种方法是通过图形用户界面。本小节分别介绍这两种使用M文件性能分析器的方法。 本小节用来分析的程序为例子4-10 向量化运算——array_vs_loops.m的代码。 使用性能分析器的图形用户界面,通过执行MATLAB的“Start”菜单中“MATLAB”子菜单下的“Profiler”命令,得到性能分析器的用户界面,如图4-7所示。

146 图4-7 性能分析器的图形用户界面

147 图4-8 性能分析的总结报告

148 图4-9 性能分析的详细报告——执行次数报告

149 详细分析报告主要按照不同的项目统计了程序的执行,其中包括运行时间(Time)、语句调用次数(Number of Call)、覆盖率(Coverage)、已加速的代码(Acceleration)。从分析报告中就可以得出占用了较多计算处理时间的代码段或代码行。另外,通过性能分析器的报告也能看出使用数组运算大大提高了M文件的执行性能。读者进行到这里可以单击详细报告上的所有超链接,获取关于M文件的详细信息。

150 这里比较重要的就是察看代码的加速信息了,在M语言文件性能分析器中,如果代码的前面具有“x”标识,则说明此行代码没有被MATLAB性能加速器加速,用户可以单击“x”标识,则性能分析器将弹出对话框说明此行代码没有被加速的具体原因。用户可以充分利用该对话框的提示信息修改代码,以提高程序的执行效率。

151 注意: 由于系统的限制,截至笔者截稿前的MATLAB最新版本,图形用户界面的性能分析器都不能处理任何包含中文字符的M文件代码。 另外一种进行性能分析的方法是直接利用命令行的方式进行,这里主要用到一个函数——profile,该函数的主要使用方法为 profile keywords 其中,根据不同的关键字执行不同的功能,而使用的过程大体分为三个步骤: 首先,在MATLAB命令行中键入下面的指令: >> profile on 其中,关键字on的作用是开启性能分析器,并且将前面的统计结果清除。

152 注意: 当性能分析器处于开启状态时,MATLAB的状态栏显示“Profile on”。 其次,运行需要分析的M文件,例如,本小节使用的array_vs_loops.m文件。运行文件的方法和正常运行M文件的方法一致: >> array_vs_loops 第三步,也就是最后一步,在MATLAB命令行中键入下面的指令: >> profile report 这时MATLAB将分析的结果创建为一个超文本格式的文档,该文档就是性能分析的总结文档,如图4-10所示。

153 图4-10 性能分析结果

154 通过单击相应的超链接可以获取相应文件的分析报告,其主要内容为每一行代码执行占用的时间。和使用图形用户界面的性能分析器相比较,这个分析报告的内容相对简单,只有执行时间的报告,但是能够支持包含中文的语句,如图4-11所示。

155 图4-11 性能分析的详细报告

156 表4-6 profile函数的使用方法

157 4.7 本 章 小 结 在本章主要讲述了关于如何利用MATLAB的编程语言——M语言进行编程的方方面面。MATLAB提供了一种高级编程语言——M语言,这种语言的语法结构与C语言非常类似,任何熟悉C语言的用户学习使用M语言都不会有任何障碍。尽管M语言是一种解释性的编程语言,但是随着MATLAB版本的不断升级,以及充分利用MATLAB提供的各种编程技巧,能够有效提高M语言应用程序的执行效率,使M语言成为了工程领域中最适合进行算法开发验证的编程语言。

158 通过本章的学习之后,读者应该能够比较熟练地利用M语言实现自己的想法。MATLAB是灵活、可靠的开发环境,用户不仅可以利用已有的MATLAB的功能,而且还能够利用M语言丰富MATLAB的能力。通过本章的学习,读者还能够充分利用MATLAB新版本特性,以及MATLAB丰富的编程调试工具——调试器和性能分析器,编写出高效率可靠的应用程序。 本章所涉及的内容仅仅是M语言的一部分内容,请读者仔细阅读MATLAB的帮助文档,已获得更加全面的信息。


Download ppt "第四章 MATLAB编程基础 4.1 概述 4.2 流程控制 4.3 脚本文件 4.4 函数文件 4.5 M文件调试 4.6 M文件性能分析"

Similar presentations


Ads by Google