4.7 MATLAB程序的调试
对于程序设计人员而言,在程序试运行的过程中出现各种各样的问题(BUG)都是不可避免的,特别是对于那些规模较大、多组人员共同开发完成的大型应用程序更是如此。因此,熟练掌握程序的调试方法是一个合格的程序设计者必备的基本素质。
本节首先介绍程序出错的两类基本根源——语法错误和逻辑错误,在此基础上讲述程序调试的基本概念;最后介绍MATLAB中程序调试的两种基本方法——直接调试法和工具调试法。
4.7.1 程序调试的基本概念
一般而言,程序出现的错误可以分为以下三大类。
1.语法错误
语法错误是指变量名的命名不符合MATLAB的规则、函数名的误写、函数的调用格式发生错误、标点符号的缺漏、循环中遗漏了“end”等情况。
2.逻辑错误
逻辑错误主要表现在程序运行后,得到的结果与预期设想的不一致,这就有可能是出现了逻辑错误。通常出现逻辑错误的程序都能正常运行,系统不会给出提示信息,所以很难发现。例如在循环过程中没有设置跳出循环的条件,从而导致程序陷入死循环。要发现和改正逻辑错误,需要仔细阅读和分析程序。
3.异常
异常是指在程序执行过程中由于不满足前置条件或后置条件而造成的程序执行错误,例如等待读取的数据文件不在当前的搜索路径内等。
综上所述,程序调试就是在将编制的程序投入实际运行前,用手工或编译程序等方法进行测试、修正语法错误和逻辑错误、解决异常状况的过程,这是保证计算机信息系统正确性的必不可少的步骤。有时调试工作所占用的时间甚至超过程序设计、代码编写所用的时间总和。
调试的目的在于发现其中的错误并及时纠正,所以在调试时应想方设法使程序的各个部分都投入运行,力图找出所有错误。错误多少与程序质量有关。即使调试通过也不能证明系统绝对无误,只不过说明各模块、各子系统的功能和运行情况正常,相互之间连接无误。程序在交付最终用户使用以后,在系统的维护阶段仍有可能发现少量错误,这也是正常的。
MATLAB是一种边解释边执行的程序语言,特别是良好的所见即所得的特性,为程序的调试带来了诸多的方便。MATLAB不仅提供了一系列的调试函数用于调试,而且在M文件编辑器中集成了程序调试器。通过使用程序调试器,用户可以完成调试工作。
4.7.2 直接调试法
对于简单的程序(例如只有单一模块组成的程序),直接调试法是一种简便快捷的方法。直接调试法的基本方法大致有如下几种:
(1)通过分析,将重点怀疑语句后的分号删除,将结果显示出来,然后与预期值比较,从而快速地判断程序执行到该处时是否发生了错误。
(2)在适当的位置添加输出变量值的语句。
(3)在程序的适当位置添加keyboard命令。当程序执行到该处时将暂停,并显示提示符,用户可以查看或变更工作区中显示的各个变量的值。在提示符后输入return指令可以继续执行原文件。
(4)在调试程序时,可以利用注释符号“%”屏蔽函数声明行,并定义输入变量的值,以脚本M文件的方式执行程序,可以方便地查看中间变量,从而有利于找出相应的错误之处。
4.7.3 使用调试函数进行调试
MATLAB提供了一系列的函数可供用户在调试程序时使用。这些函数主要用于程序执行过程相关的显示、执行中断、设置断点、执行单步操作等。在MATLAB命令行窗口中输入下述命令:
系统输出调试函数及其用途简介。这些函数都是以“db”开头的:
下面将分别介绍这些调试函数。
1.断点设置函数dbstop
在程序的适当位置设置断点,使得MATLAB在断点前停止执行,方便程序调试,用户可以检查各个局部变量的值。在命令行窗口中输入下述命令:
MATLAB即列出dbstop函数的相关调用方法:
dbstop函数常用的调用格式主要有以下几种。
● dbstop in file:在文件中的第一个可执行代码行位置设置断点。当运行文件时,MATLAB进入调试模式,在断点处暂停执行并显示暂停位置对应的行。
● dbstop in file at location:在指定位置设置断点。MATLAB 执行会在到达该位置之前立即暂停,除非该位置处是一个匿名函数。如果该位置处是匿名函数,则执行将在断点之后立即暂停。
● dbstop in file if expression:在文件的第一个可执行代码行位置设置条件断点。仅在expression的计算结果为true(1)时暂停执行。
● dbstop in file at location if expression:在指定位置设置条件断点。仅在expression计算结果为true时,于该位置处或该位置前暂停执行。
● dbstop if condition:在满足指定的 condition(如error或naninf)的行位置处暂停执行。与其他断点不同,用户不需要在特定文件中的特定行设置此断点。MATLAB 会在发生指定的condition时在任何文件的任何行暂停执行。
● dbstop(b):用于恢复之前保存到b的断点。包含保存的断点的文件必须位于搜索路径中或当前文件夹中。MATLAB按行号分配断点,因此文件中的行数必须与保存断点时的行数相同。
2.断点清除函数dbclear
dbclear函数可以清除dbstop函数设置的断点。在命令行窗口中输入:
MATLAB即列出dbclear函数的相关调用方法:
dbclear all用来清除所有M文件中设置的所有断点;其余函数分别清除由dbstop函数相应的调用格式产生的各种断点。
● dbclear all:用于删除所有MATLAB代码文件中的所有断点,以及为错误、捕获的错误、捕获的错误标识符、警告、警告标识符和naninf设置的所有断点。
● dbclear in file:将删除指定文件中的所有断点。in关键字是可选的。
● dbclear in file at location:将删除在指定文件中的指定位置设置的断点。at和in关键字为可选参数。
● dbclear if condition:将删除使用指定的condition(例如dbstop if error或dbstop if naninf)设置的所有断点。
3.列出所有断点函数dbstatus
dbstatus函数可以列出所有的断点,包括错误、警告、NAN和INF等。在命令行窗口中输入:
MATLAB即列出dbstatus函数的相关调用方法:
dbstatus函数常用的调用格式主要有以下几种:
● dbstatus:列出所有有效断点,包括错误、捕获的错误、警告和naninfs。对于非错误断点,MATLAB将显示设置断点的行号。每个行号都是一个超链接,点击后可以直接转到编辑器中的该行。
● dbstatus file:将列出对于指定file有效的所有断点。
● dbstatus –completenames:将为每个断点显示包含该断点的函数或文件的完全限定名称。
● dbstatus file –completenames:将为指定文件中的每个断点显示包含该断点的函数或文件的完全限定名称。
● b=dbstatus(___):将以m×1的结构体形式返回断点信息。使用此语法可以保存当前断点以便以后使用dbstop(b)还原。还可以指定文件名和'completenames'。
4.恢复执行函数dbcont
dbcont函数从断点处恢复程序的执行,直到遇到程序的另一个断点或错误。使用时直接在程序中加入该函数即可。
5.调用堆栈函数dbstack
dbstack函数用来显示M文件名和断点产生的行号、调用此M文件的名称和行号等,直到最高级的M文件函数。该函数的使用方法主要有以下几种。
● dbstack:显示行号和导致当前暂停状态的函数调用的文件名,按它们的执行顺序列出。显示内容从当前正在执行的函数开始,一直到顶层函数为止。每个行号都是一个超链接,指向编辑器中对应的行。
● dbstack(n):在显示中省略前n个堆栈帧。此语法很有用,例如从错误处理程序内发出dbstack时。
● dbstack(___,'-completenames'):将输出堆栈中每个函数的完全限定名称。用户可以指定将'-completenames'与上述语法中的任何输入参数结合使用。
● ST=dbstack(___):返回一个m×1的结构体数组ST,ST存储堆栈的相关信息。
● [ST,I]=dbstack(___):结构体数组ST还会返回I,即当前工作区索引。
6.执行一行或多行语句函数dbstep
dbstep函数可以执行M文件中的一行或多行语句。当执行完毕后,返回调试模式。如果在执行过程中遇到断点,程序将终止。该函数的使用方法有以下几种。
● dbstep:该命令执行当前M文件中的下一个可执行语句。
● dbstep in:将从被调用的函数文件的第一个可执行语句开始执行。
● dbstep out:该命令执行函数剩余的部分,在离开函数时停止。
● dbstep nlines:该命令执行当前M文件中由nlines指定数量的行数的可执行语句。
7.列出M文件内容函数dbtype
dbtype函数可以列出M文件的内容,并在每行语句前加上行号,从而方便用户设置断点。该函数的使用方法有以下几种。
● dbtype filename:按照每行前面带有行号的形式显示filename的内容,方便dbstop在程序文件中设置断点。但不能使用该函数显示内置函数的源代码。
● dbtype filename start:end:显示从start行号开始到end行号结束的这部分文件内容,只指定start值时显示单行。
8.切换工作区函数dbup和dbdown
dbup和dbdown函数的使用方法如下。
● Dbup:该命令将当前工作区和函数上下文(断点处)切换到调用M文件或函数的工作区和函数上下文。
● Dbdown:当遇到断点时,该命令将当前工作区切换到被调用的M文件或函数的工作区和函数上下文。
9.退出调试模式函数dbquit
dbquit函数可以结束调试器并返回基本工作区,所有断点仍有效。该函数的使用方法主要有以下两种。
● dbquit:结束当前文件的调试。
● dbquit('all'):结束所有文件的调试。
4.7.4 工具调试法
下面介绍利用MATLAB的M文件编辑器中集成的调试工具对程序进行调试。
单击工作界面中“编辑器”选项卡→“文件”面板中的“新建”按钮,编辑器自动新建一个名为Untitled的M文件,此时工具栏中的大部分按钮高亮显示,如图4-10所示。
图4-10 新建M文件
“编辑器”选项卡集成了各种程序调试命令,这些命令以按钮形式显示。这些按钮的功能与部分调试函数的功能是一样的。下面将分别介绍常见命令。
● 运行:保存并运行当前M文件。
● 断点:设置或清除断点,与调试函数中的dbstop和dbclear函数相对应。
● 断点→全部清除:清除所有的断点,与调试函数中的dbclear all函数相对应。
● 运行并前进:连续执行,与调试函数中的dbcont函数相对应。
4.7.5 程序的性能优化技术
如果在开发程序时不重视性能的优化,虽然实现了功能上的要求,但会造成程序运行效率低下,因此,程序的性能优化是计算机程序开发过程中需要一直关注的重要因素。
本节将分别从程序的执行效率和内存的使用效率两个角度,介绍MATLAB程序的性能优化技术。
1.程序的执行效率
要想提高程序的运行效率,改进算法是最关键的。算法是影响程序运行效率的主要因素,在编写不同程序时要选择适当的算法。
例如求1+2+…+99,可以按照顺序依次相加,也可以采用(1+99)+(2+98)+…的方法来计算。显然,第二种方法更适合人们计算,它的效率比顺序相加快得多,甚至口算就能迅速得到答案。
对于计算机来说,第二种方法并不比第一种方法快,如果程序编制不当,那么反而会降低计算速度。因此,选择适当的算法是提高运行效率的关键。此外,影响程序运行速度之处都是执行次数最多的地方,例如乘法和除法都是相当浪费CPU运算时间的运算。
综上所述,用户在使用MATLAB进行程序设计时,可以考虑使用以下常用方法来提高程序的执行效率。
● 尽可能地使用load函数及save函数,而不是文件I/O操作函数。
● 避免更改变量的数据类型或维数。如果确实需要这么做,则可以预先创建一个新的变量。
● 尽可能地使用实数运算。因此,对于复数的运算可以转换为多个实数运算,由此提升效率。
● 尽可能地避免对实数和复数的相互赋值。
● 在进行逻辑运算时,采用&&等短路逻辑运算具有更高的效率。
● 代码向量化。这是因为MATLAB执行循环的效率比较低,因此,将诸如for循环和while循环转化为矩阵的按位运算,可以提高计算效率。
● 对不可避免且耗时很长的循环操作,可以尝试在MEX文件内实现。
● 在进行for、while等循环前,对于循环过程中不断变化的变量应预先分配足够大的数组,从而避免MATLAB频繁地进行变量数组重生成操作,提高运算速度。
● 尽可能地采用函数M文件而不是脚本M文件,因为函数M文件的执行效率要高于脚本M文件。
2.内存的使用效率
MATLAB提供了一系列的函数帮助用户了解MATLAB对内存的使用情况,并进行相关的操作。比较常用的函数如下。
● whos函数:用于查看当前的内存使用情况。
● clear函数:该函数的调用格式如下。
● save函数:将指定的变量存入磁盘。
● load函数:将save命令存入的变量载入内存。
● quit函数:退出MATLAB,并释放所有分配的内存。
● pack函数:把内存中的变量存入磁盘,再用内存中的连续空间载回这些变量(不要在循环中使用)。
为了节约且更加有效地使用内存,用户在进行程序设计时应该注意以下几点:
(1)尽可能在函数开始处创建变量。
(2)避免生成大的中间变量,并删除不再需要的临时变量。
(3)当使用大的矩阵变量时,预先指定维数并分配好内存,避免每次临时扩充维数。
(4)当程序需要生成大量变量数据时,可以考虑将变量写到磁盘,然后清除这些变量。当需要这些变量时,重新从磁盘加载。
(5)当矩阵中数据极少时,将全矩阵转换为稀疏矩阵。