Bootstrap

Matlab入门 (2 )编程基础

MATLAB编程基础

#htext/orange 本章主要介绍关于M文件、函数定义、类型、参数传递方法、程序控制结构、文件操作、代码优化方法和程序调试方法

M文件

概述

  • MATLAB脚本文件
    • (Ⅰ)脚本文件(scripts):系列代码的封装
        (1)多条命令综合体
        (2)没有输入输出变量
        (3)使用matlab基本工作区(只有clear all 或关闭matlab才会清除变量)
        (4)没有函数声明行
      
    • (Ⅱ)函数文件(function):自建的子函数-第一行以function开头,标记此文件为一个函数
        (1)一般用于扩充matlab函数库
        (2)可以包含输入输出变量,输出变量用中括号,输入变量用小括号
        (3)运算中生成的所有变量都存放在函数工作区(只在函数运行时间内存在)
        (4)包含函数声明行
        示例:
        	function[x,y,z]=sphere(theta,phi,rho) %该语句定义了一个sphere函数
        	function printresults(x)
        	function[]=printresults(x)            %以上两行为无输出函数,可以省略[]或者空置;
      

#htext/green (1)M文件命名最好由数字字母下划线复杂构成,避免与matlab内部函数重名,出现莫名其妙的错误

#htext/green (2)ctrl+R:整体注释 ; ctrl+i:代码整理

函数变量:局部、全局(global)、永久(persistent)

  • 全局变量是在不同函数工作区以及基本工作区中可以被共享的变量

  • 永久变量(类似静态局部变量):

      (1)只能在M函数文件内部定义;
      (2)只有该变量从属的函数能够访问该变量;
      (3)当函数运行结束后,该变量的值保留在内存内,因此该函数再次被调用时可以再次利用这些变量;
    

函数参数

  • 确定函数参数数量(’nargin‘ and ’nargout‘)
    • 具体应用如下:
	% nargin函数确定输入变量个数,nargout函数确定输出变量个数
	function c=testarg1(a,b)
	if(nargin==1)
	c=a.^2;
	elseif(nargin==2)
	c=a+b;
	end
  • 传递可变数量的参数
    • varargin和varargout函数允许传入或传出可变数量参数,matlab默认封装的函数采用该模式,具体应用如下:
    	function [xmin,ymin]=testvar(varargin)
    	for k=1:length(varagin)
    		x(k)=varargin{k}(1);% 单元function=[xmin,ymin]=testvar(varargin)
    	for k=1:length(varagin)
    		x(k)=varargin{k}(1);
    		y(k)=varargin{k}(2);
    	end
    	xmin*=min(x);
    	ymin =max(y);数组索引
    		y(k)=varargin{k}(2);
    	end
    	xmin =min(x);
    	ymin =max(y);
    
    • tesevar函数可以调用具有不同数量的输入变量,如下:
>>[a,b]=testvar([2 3],[1 5],[4 8],[6 5],[4 2],[2 3]);%函数共有6个输入
a=
	1
b=
	8

函数执行完毕则varargin释放

  • varargout:
    function[varargout]=testvar2(arrayin)
    		for k=1:nargout
    			varargout{k}=arrayin(k,:);  %Cell arrayingnment
    		end		
    

#htext/orange 函数调用时必须指定需要的输入和输出变量,varargin和varargout函数可以出现在输入输出变量列表的末尾。

  • 向嵌套函数输入可变变量
    • (1) varargin和varargout:
      • 由于嵌套函数与主函数使用相同的函数工作区,因此,varargin和vararout既可以表示嵌套函数的输入输出变量,也可以是主函数的输入输出变量,具体取决于程序中的变量声明。在哪层函数说明就代表是哪层的输入输出变量。

        % rand() 产生均值为0,方差 σ^2 = 1,标准差σ = 1的正态分布的随机数或矩阵的函数。 用法:Y = randn (n),返回一个n*n的随机项的矩阵。
        	
        	function x=A(y,varargin) %Primary function A
        	B(nargin,y*rand(4))
        		function B(argsIn,z) %Nested function B
        		if argsIn>=2         %如果A的输入变量数>=2,则执行
        			C(z,varargin{1},4.512,1.729)%此时C便可接收到varargin{1}变量
        		end
        			functionC(varargin)% Nested function C
        			if nargin>=2
        				x=varargin{1}%因为此处的varargin在C函数中声明,因此这里的varargin{1}指的就是C的第一个输入参数
        			end
        			end    %End nested function C
        		end    %End nested function B
        	end    %End nested primary function A
        
      • 函数B中调用varargin{1}表示主函数的第二个变量(第一个为y),而在函数C中的varargin{1}表示函数B传递给函数C的第一个参数,即z。
        函数A调用nargin(B(nargin,y* rand(4)))表示函数A的输入变量个数,在函数C中调用nargin表示函数C的输入变量个数。

    • (2)nargin和nargout
      • 当在函数中调用nargin和nargout时,其值为该函数的输入或输出变量,而不需要进行声明。
        • 具体程序如下:
        function meters=convert2meters(miles,varargin)
        		 %Converts MILES(plus optional FEET and INCHES input)
        		 %values to METERS.
        		 if nargin<1||nargin>3
        			 error('1 to 3 input atguments are required');
        		end
        			function feet=convert2Feet(argsIn)
        			%Nested function that converts miles to feet and adds in
        			%optional FEET argument.
        			feet=miles.*5280;
        			if argsIn>=2
        				feet=feet+varargin{1};
        			end
        			end %End nested function convert2Feet
        			function inches=convert2Inches(argsIn)
        			%Nested function that converts feet to inches and adds in
        			%optional INCHES argument.
        			inches=feet.*12;
        			if argsIn==3
        				inches =inches+varargin{2};
        			end
        			end %End nested function convert2Inches
        		feet=convert2Feet(nargin);
        		inches=convert2Inches(nargin);
        		meters=inches.*2.54./100;
        		end %End primary function convert2meters
        

函数句柄(handle)

  • 句柄是MATLAB的标准数据类型之一。利用函数句柄可以实现对函数的间接调用,可将函数句柄传递给其他函数实现对函数的操作,也可以将函数句柄保存在变量中。
    通过@符号创建,语法为:

    fhandle=@functionname
    %fhandle为句柄名称
    

    也可以通过创建匿名函数的方式创建,语法为:

    fhandle=@(arglist)expr     %expr为函数体,arglist为逗号分隔的输入变量列表。
    例如:
    	🤭=@(x)x.^2           %创建了用于计算输入变量平方的匿名函数,句柄为🤭
    (输入变量为空,则arglist为空)
    

    可以使用单元数组同时为多个函数创建各自的句柄,例如:

    trigFun={@sin,@cos,@tan}         %定义了单元数组trigFun,包含三个函数的句柄
    plot(trigFun{2}(-pi:0.01:pi))    %利用句柄调用第二个函数(cos)
    

函数类型

  • 主函数
    • 通常M文件的第一个函数是主函数;
    • 可以被该文件以外的其他函数通过包含他的M文件的文件名调用。
  • 子函数
    • M文件中除主函数之外的函数;

    • 只能被主函数或该文件内的其他子函数调用;

    • 每个子函数从函数定义语句开始,到下一个函数的定义或者文件的结尾为止(即无需每个函数结束都放置end)。

      具体程序如下:

      function[avg,med]=newstats(u)  % 主函数
      %NEWSTATS Find mean and median with internal functions.
      n=length(u);
      avg=mean(u,n);
      med=median(u,n);
      function a=mean(v,n)           %子函数
      %Calculate average.
      a=sum(v)/n;
      function m=median(v,n)         %子函数
      %Calculate median.
      w=sort(v);
      if rem(n,2)==1                 %余数
      	m=w((n+1)/2);
      else
      	m=(w(n/2)+w(n/2+1))/2;
      end
      

    #htext/red 需要注意的是,直接执行上述函数会只给出第一个传出参数ans(也就是avg),必须要给出两个接受传出参数的变量才行

  • 嵌套函数(在函数体内定义其他函数)

    M文件中存在嵌套函数时,所有函数必须以end结束

    • 平级嵌套:…
    • 多层嵌套:…
    • 嵌套函数调用
      
      	function A(x,y)      %Primary function
      	B(x,y);
      	D(y);
      		function B(x,y)  %Nested in A
      		C(x);
      		D(y);
      
      			function C(x) %Nested in B
      			D(x);
      			end
      		end
      		function D(x)     %Nested in A
      		E(x);
      			function E(x) %Nested in D
      			...
      			end
      		end
      	end
      

    该例子中,A可以调用函数B和D,不能调用函数C和函数E。其他以此类推。

  • 私有函数
    • 位于private目录下的M文件函数;
    • 构造与普通M函数完全相同,但只能被private目录的上一级目录下的M函数文件调用(同时也不能被上一级目录下的M脚本文件调用)。
  • 重载函数
    • 允许多个函数使用相同的函数名和不同的输入变量数据类型,函数调用时根据函数输入变量的数据类型选择对应函数(通过varargin/out和nargin/out实现)

MATLAB的程序控制结构

条件控制语句

  • 判断语句(if-else if-end)

    (1)逻辑表达式为一个空数组时,MATLAB认为条件为假;

    (2)逻辑表达式为变量时,变量非0则逻辑真;

    (3)对于矩阵变量,需判断矩阵的所有元素为非0;

  • 分支语句(switch-case)

    具体程序如下:

    
    	switch input_num
    		case -1
    			disp('negative one');
    		case 0
    			disp('zero');
    		case 1
    			disp('positive one');
    		otherwise
    			disp('other value');
    	end
    

#htext/green case命令后面的检测值可以是标量、字符串,还可以是单元数组,当使用单元数组时,会将表达式的值逐个与单元数组中所有元素比较,有真即真。

循环控制语句

  • for
    	for 循环变量=开始值:增量:结束值
    		 循环体
    	end
    

    增量默认为1

  • while
    	while 条件表达式
    		循环体
    	end
    
    

误差控制语句

错误处理语句:try-catch-end,语句结构为:

try
	程序代码块1      %总被执行,若争取,则跳出此结构
catch
	程序代码块2      %仅当程序代码块1出现执行错误时,程序代码块2执行
end

若代码块2运行错误,则程序运行终止,或如果存在其他try-catch-end结构,则运行下一个try-catch-end结构,可以运用lasterr命令查看发生错误的原因
#htext/orange 注意,try和catch的程序代码块中语句之间用逗号隔开
示例如下:

>> n=4;
>> A=pascal(3);
>> try
 A_n=A(n,:),
 catch
 A_end=A(end,:),
 end

 A_end =

	  1     3     6
>> lasterr

 ans =

	'Index in position 1 exceeds array bounds. Index must not exceed 3.'
	
% 试图访问A(4,:);由于size(A)=[3,3],索引超出范围

(Pascal矩阵的第一行元素和第一列元素都为1,其余位置处的元素是该元素的左边元素加起上一行对应位置相加而得,如元素Ai,j=Ai,j-1+Ai-1,j。)

其重要性质包括:对称;正定;而且其逆矩阵的元素也都是整数。

其他流程控制语句

  • continue

结束当次循环,继续执行下一轮循环,一般会与if语句联用,进行条件continue;

%查看matlab自带的magic.m函数文件有多少可执行代码行
fid=fopen('magic.m','r');
count=0;
while ~feof(fid)                        %判断是否到文本的最后一行
	line=fgetl(fid);                     %读取当前文本中当前行的下一行
	if isempty(line)|strncmp(line,'%',1) %判断是否是空行或者注释行(首字符是"%")
		continue                          %是空行或者注释行则退出此次循环
	end
	count=count+1;	
	end
	disp(sprintf('%d lines',count));
  • break

中断循环并跳过,不再执行该循环体的任何操作

  • return

结束正在运行的函数,返回到调用函数,可以设置在某些条件满足时结束正在运行的函数

  • error
    显示出错信息并结束当前函数的运行
    error('message')
    
  • input

提示用户从键盘输入数值、字符串或数组等数据,并接收输入值。

  user_entry=input('prompt')        %屏幕上显示prompt,等待用户输入,并将输入数值或数组赋给变量user_entry
  user_entry=input('prompt','s')    %屏幕上显示prompt,等待用户输入,并将输入字符串赋给变量user_entry
  • keyboard

在程序运行时如果遇到keyboard函数,将停止文件执行并将控制权交给键盘。此时命令提示符变为"K>>",表示一种特殊状态。在M文件中使用该函数,对程序的调试和在程序运行中修改变量都很方便。

  • pause

该命令用于暂时中止程序运行,等待用户按任意键继续运行。该函数在程序调试过程中和用户需要查询中间结果时使用,pause函数语法为:

 pause           %停止M文件执行,按任意键继续
 pause(n)        %中止执行程序n秒后继续,n是任意整数
 pause on        %允许后续的pause命令暂时中止程序运行
 pause off       %禁止后续的pause命令暂时中止程序运行

文件操作

文件分为文本(ASCII)文件和二进制文件。文本文件一个字节存一个ASCII代码,便于对字符处理,但占空间较多,用时需要转换;二进制文件用二进制输出,不能直接输出字符形式。

  • ![[主要文件操作函数|150]]
    ![[主要文件操作函数2.jpg|]]
  • ![[文件打开方式|150]]程序如下:
    fid=fopen(filename)
    fid=fopen(filename,mode)
    [fid,message]=fopen(filename,mode,machineformat)
    fids=fopen('all')
    [filename,mode,machineformat]=fopen(fid)

其中fid为调用文件时返回的文件句柄,打开方式表示所打开文件的种类及使用文件的权限。

当不指定文件类型时,MATLAB默认为二进制文件,文件打开方式后加一个’t’,比如’rt’,'wt’等。打开文件成功会返回一个值为正整数的句柄,失败则返回句柄值-1,还可以在fopen语句中增加一个输出变量来存储错误信息(ferror函数也可以提供一些错误信息)。

读写完成后,用 fclose关闭文件,如下:

status=fclose(fid)          %关闭句柄为fid的文件

status=fclose('all')        % 关闭所有文件

#htext/green 文件关闭成功则ststus为0,关闭失败时为-1.关闭MATLAB时打开的所有文件都将关闭,但文件用完之后用fclose关闭可以增加系统资源

文件I/O

  • save函数

    将工作区中的变量形成文件保存到硬盘上,语法为:

    save                                    %将工作区中所有变量保存带名为MATLAB.mat的二进制格式文件中,该文件可通过load命令重载入工作区
    
    save('filename')                        %保存为文件,文件名自定,包含路径则按路径建文件,不包含则存放在当前路径
    
    save('filename','var1','var2',...)      %保存指定变量在文件filename.mat中
    
    save('filename','-struct','s')          %保存结构体s中全部域作为单独的变量
    
    save('filename','-struct','s','f1''f2'...)%保存结构体s中指定的域( s.f1,s.f2)
    
    save(...,'format')                      %保存指定文件保存格式为mat或ascii,format格式自查
    
    save filename var1 var2 ...
    
  • load函数

    从磁盘中重载变量内容到工作区中,语法为:

    load
    
    load filename
    
    load filename X Y Z ...   %显而易见的语法
    
    load -ascii filename
    
    load -mat filename
    
  • fread函数

    从文件中读取二进制数据,语法为:

    A=fread(fid)
    
    A=fread(fid,count)
    
    A=fread(fid,count,precision)
    
    A=fread(fid,count,precision,skip)
    
    A=fread(fid,count,precision,skip,machineformat)
    
    [A,count]=fread(...)
    

    fread函数从指定文件fid中读取二进制数据,将数据写入矩阵A;

    可选输出count返回成功读入元素个数;

    fid为文件标识符,其值由fopen函数得到;

    可选参数count确定读入多少数据,若不指定count则读完文件,count合法选择如下:

    n,读取n个元素到一个列向量
    inf,读到文件结束,返回一个与文件数据元素相同的列向量
    [m,n],读取元素填充一个m行n列的矩阵,填充按列进行,如果文件读入数据不够,则填充0。
    

    precision表示读入数据精度的字符串(类似:int8、uchar…);

    参数skip确定每次读操作跳过的字节数。

  • fwrite函数

    向文件中写入二进制数据,语法为:

    [count,errmsg]=fwrite(fid,A,precision)
    
    [count,errmsg]=fwrite(fid,A,precision,skip)
    
    

    fwrite函数将矩阵A中的元素写入指定文件fid中,将其值转化为指定的精度。,count返回成功写入文件的元素个数。参数skip指定每次写操作跳过指定字节。

  • fscanf函数

    按指定格式从文件中读取数据,并根据格式字符进行转换,同时返回给矩阵A。参数size指定数据长度,参数count返回成功读入的数据长度,语法为:

    A=fscanf(fid,format)
    
    [A,count]=fscanf(fid,format,size)
    
  • fprintf函数

    向文件写入格式化数据,语法为:

    count=fprintf(fid,format,A...)
    
    

    将矩阵A或其他矩阵的实部数据按"格式字符串"指定的形式进行格式化,并将其写入指定的文件fid中,count返回值为写入的数据长度。

  • fgets函数

    以字符串形式返回文件中的下一行内容,包含行结束符,语法为:

    tline=fgets(fid)
    
    tline=fgets(fid,nchar)
    

    返回文件标识符fid的文件中下一行内容,如果遇到EOF(结尾),则返回-1。nchar指定返回的字符个数,在遇到行结束符时不追加字符。(与此相对的fgetl函数不返回行结束符)

  • ferror函数

    报错。语法为:

    message=ferror(fid)
    

    把已打开文件的错误信息返回给message。

  • feof函数

    测试指定文件是否设置了文件结尾EOF。返回1则设置了,返回0则未设置,语法为:

    eofstat=feof(fid)
    

MATLAB程序优化

Profile

  • 通过Profiler进行程序运行分析,计算各个部分所消耗的时间以进行程序优化,一般从以下几方面提供程序运行信息:

(1)避免由于疏忽造成的非必要操作;

(2)替换运行较慢的算法,选择快速算法;

(3)通过存储变量的方式避免重复计算。

  • 通过Profiler工具进行程序运行分析通常按照以下步骤进行:

(1)查看Profiler工具生成的通体报告,查找运行时间最多的函数或调用最频繁的函数;

(2)查看这些函数的详细报告,查找其中运行时间最多的语句或调用最频繁的语句;

(3)确定运行时间最多的函数或代码是否存在改进的可能;

(4)单机界面链接,打开相应文件进行修改;

(5)重复进行上述分析、修改,直至得到满意结果

tic函数与toc函数

  • tic开始计时器,toc关闭计时器,并计算程序运行总时间,语法为:
tic
   any statements  %所需计时的程序代码
toc
t=toc              %保存计时时间 

程序优化常用方法

  • 循环向量化

    MATLAB的一个缺点是当对矩阵的单个元素做循环时运算速度很慢。编程时,把循环向量化,不但能缩短程序长度,更能提高程序执行效率。由于MATLAB的基本数据类型为矩阵和向量,所以编程时应该对向量和矩阵编程,而不是对矩阵元素进行编程。示例如下:

    %loop.m
    tic
    x=1;
    for k=1:1001
    	y(k)=log10(x)
    	x=x+0.01;
    end
    toc
    

loop

-----------------------------------------------------------------------------------------------------------------------------
时间已过0.019843秒。
具体程序如下:
-----------------------------------------------------------------------------------------------------------------------------
% vector_loop.m
tic
x=1:0.01:10;
y=log10(x);
toc

vector_loop

-----------------------------------------------------------------------------------------------------------------------------
时间已过0.003630秒。
```
  • 数组内存预分配

    在for循环或while循环中,如果数组大小随着循环而增加,则会严重影响内存的使用效率。如下代码:

    x=0;
    for k=2:1000
    	x(k)=x(k-1)+5;
    end
    

    该代码在for循环中将x扩展成长度为1000的一维数组。在每次拓展时,系统需要寻找更大的连续内存区域,用于存放该数组,并将其移到新地址,该代码可有如下改进:

    x=zeros(1,1000);
    for k=2:1000
    	x(k)=x(k-1)+5;
    end
    

    预先分配好数组内存,全部赋0,节约重新分配内存的时间。
    #htext/red A=zeros(100, ‘int8’)->内存分配并赋0,还可以用cell函数进行预分配

  • 其他方法

    (1)对数组赋值时避免改变数组类型或数组大小;

    (2)对实数进行操作,尽量避免复数;

    (3)合理使用逻辑运算符;

    (4)避免重载MATLAB的内置函数和操作符;

    (5)通常函数运行效率高于脚本文件;

    (6)load和save函数效率高于文件输入输出函数。

调试

直接调试

(1)可以把可能出错语句后面的分号删掉,显示结果进行观察;

(2)利用disp函数显示中间变量的值;

(3)适当位置添加keyboard命令,程序执行到此处时将会暂停等待用户反应,命令行窗口显示k>>提示符,可以查看工作区变量,可以改变变量的值,输入return返回程序继续执行;

(4)调试一个单独函数时,可以将该函数编写为脚本文件,此时可以直接对输入变量赋值,以脚本形式运行该M文件。这样可以保存中间变量。运行完成后查看中间变量值进行检查。

调试工具(断点操作)

;