470 likes | 618 Views
第 7 章 过程. 教学目标 掌握过程的定义及其引用 熟悉异常处理机制 熟悉递归算法. 7.1 过程. 在已经学习过的事件过程中,事件过程是当发生某个事件时,如单击命令按钮,对该事件作出响应的程序段。有些情况下,多个不同的事件过程可能要执行一段相同的程序代码,那么可以将这一段代码抽出来,建立一个独立的过程,这样的过程称为 通用过程 ,供事件过程或其他通用过程调用。 在 Visual Basic 中, 通用过程分为两类 : 子程序过程和函数过程, 前者也称为 Sub 过程 ,后者称为 Function 过程 。. 7.1.1 Sub 过程的定义.
E N D
第7章过程 教学目标 掌握过程的定义及其引用 熟悉异常处理机制 熟悉递归算法
7.1 过程 在已经学习过的事件过程中,事件过程是当发生某个事件时,如单击命令按钮,对该事件作出响应的程序段。有些情况下,多个不同的事件过程可能要执行一段相同的程序代码,那么可以将这一段代码抽出来,建立一个独立的过程,这样的过程称为通用过程,供事件过程或其他通用过程调用。 在Visual Basic中,通用过程分为两类:子程序过程和函数过程,前者也称为Sub过程,后者称为Function过程。
7.1.1 Sub过程的定义 下面是通用过程的建立和使用。 • Sub过程即子程序,是由Sub... End Sub定义的过程。定义过程的格式如下: [Static ] [Private ] [Public] Sub 过程名([参数表]) 语句序列 End Sub • Sub过程定义以Sub(除前面的Static、Private和Public外)开始,以End Sub结束,中间是描述过程功能的语句序列,称为过程体。 • Sub前面的Static、Private和Public指定过程或其中定义的变量的有效范围,有关细节后面介绍。 • 过程名与变量的命名规则相同,不要与Visual Basic中的关键字重名,也不要与同级的变量同名。 • 参数表指定在调用该过程时,应该传递的参数的个数和类型。参数表中可以包含多个参数项,相邻的两个参数项之间用逗号隔开。每个参数项的形式如下: ByVal | ByRef 参数名[( ) ] [ As 类型] • 这里的参数名是一个合法的变量名,如果参数做为数组来使用,在其名的后面应该加一对括号。
类型指定参数的数据类型,可以是Integer,Long,Single,Double,String,Variant或用户定义的其他类型。 如果省略“As 类型名”则默认为Object。变量名前的ByVal表明参数是按照值来传递,ByRef按照地址来传递。有关细节,本章后面将做详细介绍。这里的参数在定义时并没有分配存储单元,只有在运行该过程时才分配,所以也称为形式参数。 • End Sub表明过程的结束。每个Sub过程应该有一个End Sub语句,当程序执行到End Sub时,将结束该过程的运行。在过程体中,还可以包含一个或多个Exit Sub语句,当程序运行到Exit Sub语句时,也结束过程的运行。 • Sub过程不能嵌套,即不能在Sub过程中再定义其他的Sub或Function过程。
例如,定义计算和打印矩形面积的Sub过程如下:例如,定义计算和打印矩形面积的Sub过程如下: Sub area(ByRef intA As Integer, ByRef intB As Integer) Dim intS As Integer intS = a * b TxtOutput.Text = "The area is " + intS.ToString() End Sub 该过程的名字为area,包含两个参数a和b,均为Integer型。过程可以没有参数,下面是一个不带参数的过程定义: Sub sums( ) Dim intA As Integer Din intB As integer intA = 3 intB = 7 TxtOutput.Text = intA & " + " & intB & " = " & intA + intB End Sub
7.1.2 调用Sub过程 调用Sub过程,即执行该过程中的代码。调用Sub过程的形式如下: 过程名([实际参数表]) 其功能是:运行该过程名对应的过程。 • 实际参数表是传递给该过程的诸参数,实际参数也简称为实参。实参可以是常量、变量或表达式。相邻的两个实参之间用逗号隔开。实参的个数、顺序、类型和形参要一一对应。 • 调用的执行过程是:首先将实参传递给形参,然后执行过程体。当过程运行结束后,从调用该过程的语句的下一句处继续执行。例如,要调用前面定义的过程area,使用如下的程序段: Dim intX As Integer Dim intY As Integer intX = 5 intY = 7 area(intX, intY) • 当执行该程序段时,首先给变量x和y赋值,接着以x和y为实参,调用过程area。 • 调用过程是首先将实参x传递给形参a,将实参y传递给形参b,然后执行过程area的过程体,即该过程中的诸语句。当执行到End Sub时,过程的运行结束,返回到调用该过程的语句的下一句,从该处继续程序的运行。要调用前面定义的无参过程sums,可用如下的语句: sums( )
7.2 Function过程 Sub过程不返回值,且以语句的形式调用。Function过程要返回一个值,调用方式是以表达式的形式出现。 7.2.1 Function过程的定义 Function过程的定义格式如下: [ Static ][ Private ][ Public ] Function 过程名 ([参数表])[As 类型名 ] 语句序列 End Function Function过程以Function(除前面的Static、Private和Public外)开始,以End Function结束,中间是描述过程功能的语句序列,称为过程体或函数体。 过程体中至少有一条Return语句,形式为 Return 表达式 • 当调用该过程时,过程的返回值即此表达式的值。 • “As 类型名”指定Function过程返回值的数据类型。 • 其他部分同Sub过程的定义。
例如,定义计算阶乘的Function过程如下: Function facts( intN As Integer) As Long Dim i As Integer Dim intResult As Long intResult = 1 For i = 1 To intN intResult = intResult * i Next Return intResult End Function 该函数过程包含一个Integer型的形参,其返回值为Long型。可以看到,过程体中包含Return语言,返回值等于参数intN的阶乘。
7.2.2 Function过程的调用 • 由于Function过程返回一个值,可以像其它函数一样来调用。将它作为单独的语句来调用,就无法得到函数的返回值。因此一般它作为表达式或表达式的一部分出现。其在表达式中出现的形式为: 过程名([参数表]) • 参数表的使用形式同调用Sub过程。例如,要调用前面定义的计算阶乘的Function过程facts,可以采用下面的程序段: Dim intM As Integer = 12 Dim lngY As Long lngY = facts(intM) 当执行到lngY = facts(intN)时,将调用函数过程facts。首先将实参intM传递给形参intN,然后执行该函数过程的过程体,当执行到End Function时,完成过程的运行,将Return后面表达式的值带回调用处,即赋给变量lngY。然后从调用处继续执行。
7.3 参数传递 在调用一个过程时,必须为过程提供实际参数,完成实际参数与形式参数的结合,称为参数传递,然后用实际参数执行所调用的过程体。 7.3.1 参数传递的方式 • 在Visual Basic .NET中,参数传递的方式有两种:传值和传址。所谓传值是将实际参数的值传递给形式参数,而传址是将实际参数的引用(地址)传递给形式参数。 • 如果要按照传址方式进行参数传递,在定义过程时,在形式参数的前面加ByVal;如果按照传址方式进行参数传递,在形式参数的前面加ByRef。
7.3.1.1传值 • 参数传递的传值方式是将实际参数的值传递给形式参数,而不是将实际参数的地址传递给形式参数。在这种方式下,系统将要传送的变量复制到一个临时单元中,然后把临时单元的地址传送给被调用的过程,即系统为形式参数重新分配存储单元。由于被调用过程没有访问实际参数,因而在改变形式参数值时,并没有改变实际参数的值。 • 要使得参数的传递按照传值方式进行,需在定义过程时,在形式参数的前面加ByVal关键字。
下面是一个简单的例子。新建一个项目,取名为Swap,在窗体上放置一个TextBox和Button控件。属性的设置和第5章中第一个例子一样。在窗体上单击右键,选择查看代码从而打开代码窗口。在End Class前的空白处编写swap过程如下: Sub swap(ByVal intX As Integer, ByVal intY As Integer) Dim intTemp As Integer TxtOutput.Text += "交换前intX和intY的值" + vbCrLf TxtOutput.Text += "intX=" + intX.ToString() + " " + "intY=" + _ intY.ToString() + vbCrLf intTemp = intX intX = intY intY = intTemp TxtOutput.Text += "交换后intX和intY的值" + vbCrLf TxtOutput.Text += "intX=" + intX.ToString() + " " + "intY=" + _ intY.ToString() + vbCrLf End Sub 该函数将两个形参intX和intY交换。由于其前面都有ByVal,所以他们都是按照值传递的参数。
随后为Button的Click事件编写代码,以调用swap过程。随后为Button的Click事件编写代码,以调用swap过程。 Private Sub BtnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnStart.Click Dim intA As Integer = 30 Dim intB As Integer = 23 TxtOutput.Text += "调用swap前intA和intB的值" + vbCrLf TxtOutput.Text += "intA=" + intA.ToString() + " " + "intB=" + _ intB.ToString() + vbCrLf swap(intA, intB) TxtOutput.Text += "调用swap后intA和intB的值" + vbCrLf TxtOutput.Text += "intA=" + intA.ToString() + " " + "intB=" + _ intB.ToString() + vbCrLf End Sub
Button的Click事件过程中,变量intA和intB的值分别为30和23。在输出“调用swap前intA和intB的值”之后,输出“intA = 30 intB = 23”。接着,调用swap,两个实际参数分别为intA和intB。由于是传值,所以为形式参数intX和intY重新分配存储单元,并将实际参数intA的值30传递给形式参数intX,将intB的值23传递给intY。接着执行swap的过程体,在输出“交换前intX和intY的值”之后,输出“intX = 30 intY = 23”。通过3条赋值语句,将形式参数intX和intY的值交换,所以intX的值变为23,intY的值变为30。但是实际参数intA和intB的值没有发生任何变化。在输出“调用swap后intA和intB的值”后,输出“intA = 30 intB = 23”。在Swap过程的执行中,遇到End Sub时结束该过程的运行,系统收回为形式参数intX和intY分配的存储单元,返回到调用语句的下一句处,即 TxtOutput.Text += "调用swap后intA和intB的值" + vbCrLf 语句处,继续事件过程的运行。 • 过程swap实现了交换变量intX和intY的值的功能,但对实际参数intA和intB的值没有产生任何影响。
7.3.1.2 引用 • 参数传递的另一种方式是引用,也称为传地址。定义过程时,如果在形式参数的前面加ByRef,参数的传递方式是引用。 • 在引用方式下,参数的传递是将实际参数的地址传递给形式参数,所以形式参数和实际参数共享相同的存储单元。当在过程中对形式参数的值更改时,实际参数的值也进行相应的更改。所以可以通过引用的方式,将被调用过程的处理结果带回调用过程。 • 前面定义的过程swap采用了传值参数,仅交换了形式参数的值,并没有改变实际参数的值。为了能改变实际参数的值,对该过程进行改造,将传值参数改为传址参数,改变swap过程的定义如下: Sub swap(ByRef intX As Integer, ByRef intY As Integer)
Button的Click事件过程中,变量intA和intB的值分别为30和23。在输出“调用swap前intA和intB的值”之后,输出“intA = 30 intB = 23”。接着,调用swap,两个实际参数分别为intA和intB。由于是引用,形式参数intX和实际参数intA共享同样的存储单元,其值为30,形式参数intY和实际参数intB共享同样的存储单元,其值为23。接着执行swap的过程体,在输出“交换前intX和intY的值”之后,输出“intX = 30 intY = 23”。通过3条赋值语句,将形式参数intX和intY的值交换,所以intX的值变为23,intY的值变为30。由于实际参数intA和形式参数intX共享同样的存储单元,所以intA的值也变为23,同样的道理,intB的值变为100。
7.3.1.3传递方式的比较 通过下面例子大家可以再次看到传值和引用的区别。过程change的定义如下: Sub change(ByRef intX As Integer, ByVal intY As Integer) intX = 2 * intX intY = 2 * intY End Sub 该过程有一个引用的参数intX和一个传值的参数intY。Button的Click事件代码 如下: Private Sub BtnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnStart.Click Dim intA As Integer = 10 Dim intB As Integer = 100 TxtOutput.Text += "调用Change前" + vbCrLf TxtOutput.Text += "intA = " + intA.ToString() + " intB = " + _ intB.ToString() + vbCrLf change(intA, intB) TxtOutput.Text += "调用Change后" + vbCrLf TxtOutput.Text += "intA = " + intA.ToString() + " intB = " + _ intB.ToString() + vbCrLf End Sub
Button的Click事件过程中,给变量intA赋的值为10,给变量intB赋的值为100。所以在 TxtOutput.Text += "调用Change前" + vbCrLf 输出“调用Change前”之后, 执行 TxtOutput.Text += "intA = " + intA.ToString() + " intB = " + _ intB.ToString() + vbCrLf 显示“intA = 10 intB = 100”。后面调用过程change,传递的两个实际参数分别为intA和intB。实际参数intA和形式参数intX结合,采用引用的方式,所以intA和intX共享同一存储单元,intX的值为10;实际参数intB和形式参数intY结合,采用传值的方式,所以intY被分配一个新的存储单元,其值为100。 在change的过程体中,将intX的值改为20,intA的值也变为20;之后将intY的值改为200,但intB的值没有发生变化。 可见传值的参数不能将被调过程中的结果带回调用过程,而传址参数能将被调过程中的结果带回调用过程。
7.3.1.4 传递方式的选择 定义过程时,是选择传值参数还是选择引用方式,没有统一的标准,下面几条原则可供参考: 如果要将被调过程中的结果带回,采用引用参数,否则采用传值参数。 对于整型、长整型或单精度型参数,如果不希望过程修改实参的值,采用传值参数;反之可使用引用参数。 前面介绍过,Function过程能通过返回值直接带回过程的结果,而Sub过程由于没有返回值,不能直接带回过程的结果。但是通过传址方式的参数,Sub过程也能间接带回过程的结果。所以在编写程序时,通过Function过程能解决的问题,同样也能通过Sub过程完成,反之亦然。不过,如果过程的结果要以表达式的形式使用,采用Function过程为佳。 例如,要计算n!,既可以使用Function过程,也可以使用Sub过程。使用Function过程: Function facts(ByVal intN As Integer) As Long Dim i As Integer Dim lngS As Long = 1 For i = 1 To n lngS = lngS * i Next Return s End Function 调用该Function过程的程序段如下: TextBox1.Text = facts(4) 使用Sub过程: Sub facts(ByVal intN As Integer, ByRef lngY As Long) Dim i As Integer lngY = 1 For i = 1 To n lngY = lngY * i Next End Sub 调用该Sub过程的程序段如下: Dim lngY As Long facts(5, lngY) TextBox1.Text = lngY.ToString()
7.3.1.5 值变量和引用变量与参数传递 Visual Basic .NET中的变量可以分为2类:值类型和引用类型。如果某个数据类型在自己的内存分配中包含数据,则该数据类型是“值类型”。“引用类型”含有指向包含数据的其他内存位置的地址(指针)。 以下变量是值类型: • 所有 numeric 数据类型 • Boolean、Char 和 Date • 所有结构(自定义数据类型),即使其成员是引用类型 以下变量是引用类型: • String • 所有数组,即使其元素是值类型 • 类类型,如 Form(将在第8章学习) 对于值类型,前面已经看到,在数或过程中如果通过ByVal方式传递参数,则在函数和过程中对形参做的任何改变不会影响实参的值。如果通过ByRef传递参数,则对形参的改变将导致实参同时改变。 对于引用类型,则有所不同。在引用类型的变量中,本身就是存放的变量的地址。因此无论是通过ByVal和ByRef方式传递参数,在函数或过程中,对变量中内容的改变都将导致调用代码中实际参数的改变。区别在于,以ByVal方式传递参数时,只能改变变量的内容,变量本身的改变不会影响实际参数。以ByRef方式传递参数时,既可以改变变量的内容,也可以改变变量本身。例如,以ByVal方式传递数组参数时,改变数组中的内容时,调用代码中的实际数组内容也会改变。但是如果在函数或过程中重定义数组的大小,则调用代码中的数组不会改变。如果以ByRef方式传递的数组,则这两者都会跟着改变。
7.3.2 数组参数 在Visual Basic的过程中,允许参数是数组。在使用数组参数时,应该注意在形式参数表和实际参数表中,数组名后的括号不能省略,需要指明数组的维数,但不需要维数的下标界限。例如,定义以下过程: Sub f(ByVal intA() As Integer) … End Sub 过程f有一个一维数组参数intA,在形式参数表中,只需列出数组参数intA的名字以及后面的括号,不需给出维数的上界。如果是2维以上的数组,则表明维数的逗号不能省略。 如果已经定义了数组,可以通过以下的语句调用该过程: f(intB) 在调用该过程时,就将数组intB传递给数组intA。由于数组本身是引用类型的变量,所以无论以何种方式传递数组,当在过程f中,对数组intA的某一元素值进行了更改,也是对数组intB的元素进行了修改。当过程结束后,数组intB将修改的结果带回到调用过程。
例如,编写Function一过程,求数组各元素的和。例如,编写Function一过程,求数组各元素的和。 Function ArraySum(ByVal intX() As Integer) As Integer Dim intSum As Integer = 0 Dim i As Integer For i = 0 To intX.GetUpperBound(0) intSum += intX(i) intX(i) = 0 Next Return intSum End Function
在过程ArraySum中,有一个数组参数intX,它的元素的数据类型为Integer。过程的功能是计算每个元素值的和。在过程中,通过数组对象本身的方法GetUpperBound获取数组上界。和前面的例题同样的界面,在Button的Click事件过程调用Function过程ArraySum: Private Sub BtnStart_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles BtnStart.Click Dim intA(9) As Integer Dim j As Integer Dim intSum As Integer For j = 0 To intA.GetUpperBound(0) intA(j) = j * j TxtOutput.Text += intA(j).ToString() + " " Next intSum = ArraySum(intA) TxtOutput.Text += vbCrLf + "总和" + intSum.ToString() + vbCrLf For j = 0 To intA.GetUpperBound(0) TxtOutput.Text += intA(j).ToString() + " " Next End Sub 通过上面的程序求得了0到9的平方和。下图是程序运行结果:
可以看到,使用ByVal方式传递数组,当在ArraySum过程中将intX的每一项清为0后,intA中的值也跟着改变了。将ArraySum过程中intX的参数传递改为ByRef,运行结果和现在是一样的。 下面对ArraySum过程做一些改变: Function ArraySum(ByVal intX() As Integer) As Integer Dim intSum As Integer = 0 Dim i As Integer ReDim Preserve intX(12) For i = 10 To 12 intX(i) = i * i Next For i = 0 To intX.GetUpperBound(0) intSum += intX(i) intX(i) = 0 Next Return intSum End Function
可以看到,在ByVal方式下重新定义intX的大小后,并不改变intA。同时重新定义的intX得到了新的和intA不再相同的空间,因此对intX赋值为0时,intA不再随之变化。可以看到,在ByVal方式下重新定义intX的大小后,并不改变intA。同时重新定义的intX得到了新的和intA不再相同的空间,因此对intX赋值为0时,intA不再随之变化。 数组对象被改变后的运行结果 下面将ByVal方式改为ByRef方式再次运行程序,结果如右图:而此时我们看到,intX的改变完全反应到了intA上。intA的所有元素成为了0值,并且大小也被改变的和intX一样(共有13个0)。 以ByRef方式运行的结果
7.3.3 变量的作用域 在Visual Basic中,变量由于定义位置的不同,可被访问的范围也不同,变量可以被访问的范围称为变量的作用域。Visual Basic中作用域有以下几级: 块范围:块是由 End、Else、Loop 或 Next 语句终止的语句集合,例如 For...Next 或 If...Then...Else...End If 结构内的语句。在某块内声明的元素只能在该块内使用。在下面的示例中,整数变量 Cube 的范围是 If 和 End If 之间的块,在执行超出该块后便不再引用 If N < 1291 Then Dim Cube As Integer Cube = N ^ 3 End If
过程范围 在某过程内声明的元素在该过程外不可用。只有包含元素声明的过程才能使用该元素。该级别的元素也称为“局部”元素 。使用 Dim 语句声明这些元素,使用或不使用 Static 关键字。过程范围和块范围密切相关。如果在过程内但在该过程的任何块外声明元素,则可将该元素看作具有块范围,其中块就是整个过程。 不能在过程中使用 Public 关键字声明元素。 • 模块范围 为方便起见,单个术语“模块级别”同等地应用于模块、类和结构。可以通过将声明语句放在模块、类或结构中的任一过程或块的外部来声明该级别的元素。当在模块级声明时,由所选的可访问性确定范围。包含模块、类或结构的命名空间也影响范围。为其声明 Private 可访问性的元素可用于引用该模块内的每个过程,但不能用于引用其他模块中的任何代码。如果不使用任何可访问性关键字,则模块级 Dim 语句默认为 Private。但是,通过在 Dim 语句中使用 Private 关键字可以使范围和可访问性更明显。 • 命名空间范围 如果使用 Friend 或 Public 关键字声明模块级元素,则该元素变为可用于整个命名空间(在其中声明该元素)内的所有过程。命名空间范围包括嵌套命名空间。可从命名空间内使用的元素同样可从该命名空间中的任何嵌套命名空间内使用。如果项目中不包含任何 Namespace 语句,则该项目全部都在同一命名空间中。这种情况下,可将命名空间范围看作是项目范围。模块、类或结构中的 Public 元素也可用于任何引用它们的项目的项目。
当在程序中定义了有不同作用域的同名变量时,作用域小的变量有较高的优先访问权。例如,定义了全局变量a,在某个过程中也定义了局部变量a。局部变量a只能在该过程中使用,作用域比全局变量小。但在该过程中,全局变量被“屏蔽”掉,如果使用变量a,将被认为是局部变量的a。要想在过程中使用同名的全局变量,必须在变量名的前面加模块名。当在程序中定义了有不同作用域的同名变量时,作用域小的变量有较高的优先访问权。例如,定义了全局变量a,在某个过程中也定义了局部变量a。局部变量a只能在该过程中使用,作用域比全局变量小。但在该过程中,全局变量被“屏蔽”掉,如果使用变量a,将被认为是局部变量的a。要想在过程中使用同名的全局变量,必须在变量名的前面加模块名。
7.3.4 静态变量 • 只有当调用一个过程时,才建立该过程内定义的局部变量以及参数,并为其分配内存,进行变量的初始化,在该过程内进行数据的存取,而在过程结束后,清除这些局部变量,释放他们占用的内存。如果再调用该过程,则重新建立这些变量,他们原来的值不复存在。 • 有些情况下,希望当过程结束时能保留局部变量的值,可以使用Static将其定义为静态变量。定义静态变量的格式如下: • Static 变量名 As 类型 • 可以看到,定义静态变量的方式同定义局部变量的方式相同,只是将Dim用Static替换。由于Static用于定义局部变量,只能出现在事件过程、Sub过程或Function过程中。在过程中的Static变量是局部变量,只能在该过程中使用,但当该过程结束时,它的值仍然保留。当再次调用该过程时,可以继续使用它的值。正由于Static变量的值在过程结束后继续保留,所以可以通过该种变量记录事件的触发次数或进行变量值的切换。
例如,要显示单击按钮的次数,可以新建一个和上一例相同的工程,为Button的Click事件编写代码如下:例如,要显示单击按钮的次数,可以新建一个和上一例相同的工程,为Button的Click事件编写代码如下: Private Sub BtnStart_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles BtnStart.Click Static intCount As Integer intCount += 1 TxtOutput.Text = "You have pressed " + intCount.ToString() + " times" End Sub 当首次单击该按钮而调用该过程时,intCount的初始值是0,执行count +=1使其值变为1,当过程结束后,intCount仍然存在,并保留其值。当第二次单击该按钮而调用该过程时,intCount的初始值是1,执行count += 1使其值变为2,当过程结束后,intCount保留其新值2,依此类推,每按一次该按钮,intCount的值就增加1,从而记录按钮的单击次数。图7-8所示为单击Button按钮4次后的结果。
7.3.5 递归 递归:是用自身的结构来描述自身,最典型的例子是对阶乘运算可作如下的定义: n!=n*(n-1)! (n-1)!=(n-1)*(n-2)! 显然,用阶乘本身来定义阶乘,这样的定义就称为递归定义。 在Visual Basic中,允许一个过程在自身定义的内部调用自己,这样的过程称为递归过程。在编写递归过程时,只要知道递归定义的公式,再加上递归终止的条件就能容易地写出相应的递归过程。例如采用递归算法求n!。 根据阶乘的概念,可以写出其递归定义: fac(1)=1 fac(n)=n*fac(n-1) 新建工程fac2,在Form窗体上放置一个TextBox和一个Button,属性设置同上一例。函数Fac用来完成递归。
代码如下: Function fac(ByVal intN As Integer) As Long If intN = 1 Then Return 1 Else Return (intN * fac(intN - 1)) End If End Function Private Sub BtnStart_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles BtnStart.Click Dim intN As Integer = 5 TxtOutput.Text = intN.ToString() + "!=" + (fac(intN)).ToString() End Sub 在过程fac的定义中,当n>1时,连续调用自身共n-1次,直到n=1为止。
运行该程序段时,intN的值是5,求fac(5)的值变为求5*fac(4);求fac(4)又变为求4*fac(3);求fac(3)又变为求3*fac(2),依此类推,当n=1时,递归调用结束,其执行过程为:5*4*3*2*1,即5!。如果将第一次调用过程fac称为0级调用,以后每调用一次,级别增加1,过程参数n减1,则递归调用过程如下所示: • 将递归调用分解为两个阶段。第一个阶段是“递推”,即将求n!分解为求(n-1)!的过程,而(n-1)!仍然不知道,还要递推到(n-2)!,依此类推,直到求1!。由于1!已经知道,其值为1,不需要再递推了。然后开始第二个阶段,采用“回推”方式,从1!(其值为1)推算出2!,从2!(值为2)推算出3!(值为6),依此类推,直到推算出5!(其值为120)为至。 • 可见,递归调用可以分解为递推和回推两个阶段,需要经过许多步才能求出最后的结果。 注意:要使递归过程在适当的时候结束,必须提供递归结束的条件。在该例子中,结束递归的条件是fac(1)=1。
一个递归的例子,著名的梵塔(Hanoi)问题。据古引度神话,在贝拿勒斯的圣庙里安放着一块铜板,板上有3根宝石针。梵天(印度教的主神)在创造世界的时候,在其中的一根针上摆放了由小到大共64片中间有孔的金片。无论白天和黑夜,都有一位僧侣负责移动这些金片。移动金片的规则是:一次只能将一个金片移动到另一根针上,并且在任何时候以及任意一根针上,小片只能在大片的上面。当64个金片全部由最初的那根针上移动到另一根针上时,这世界就在一声霹雳中消失。 编写模拟该过程的程序。假定用A、B和C分别表示3根针,可以看到,要将n个金片由A针移动到C针,可以分解为以下几个步骤: (1)将A上的n-1个金片借助C针移动到B针上。 (2)将A针上剩下的一个金片由A针移动到C针上。 (3)将最后剩下的n-1个金片借助A针由B针移动到C针上。 步骤(1)和(3)与整个任务类似,但涉及的金片只有n-1个,是一个典型的递归算法。 梵塔
将一个金片由其中一个针移动到另一针的模拟过程如下: Sub moves(ByVal strStart As String, ByVal strEnd As String) TxtOutput.Text += "将金片从" + strStart + "针移动到" + _ strEnd + "针" + vbCrLf End Sub 完成全部移动的程序如下,其中Else到End If之间的3条语句刚好和上面分析的3个步骤一一对应: Sub hanoi(ByVal intN As Integer, ByVal A As String, ByVal B As String, _ ByVal C As String) If (intN = 1) Then moves(A, C) Else hanoi(intN - 1, A, C, B) moves(A, C) hanoi(intN - 1, B, A, C) End If End Sub
再从Button的Click中调用hanoi,当只有3个金片时,程序及运行结果如下:再从Button的Click中调用hanoi,当只有3个金片时,程序及运行结果如下: Private Sub BtnStart_Click(ByVal sender As System.Object, ByVal e As _ System.EventArgs) Handles BtnStart.Click Dim intN As Integer = 3 hanoi(intN, "A", "B", "C") End Sub 梵塔运行结果
从程序的运行结果可以看到:当金片数为3时,只需7步就可以将3个金片由A移动到C针上。但是随着金片数量的增加,所需要的步数急剧增加。要将64个金片全部由A针移动到C针,共需264-1步。这个步数是一个相当庞大的数字,将耗费相当漫长的时间来完成。假定寺庙里的僧侶以每秒一次的速度移动金片,日夜不停,则需要58万亿年方能完成。采用每秒钟运算100万次的计算机来模拟该过程,也需要5千8百万年。
7.3.6 快速排序 快速排序是对冒泡排序的一种改进。 基本思想:通过一趟排序将待排序的数分割成独立的两个部分。其中一部分的数值均比另一部分小。随后采用同样的方法,分别对这两部分排序,直到整个序列有序。在排序的时候,可以任意取一个数值,以此数值为分界,将待排序的数分开,如取第一个数作为分界线。
假设要对数组r中第s个元素到第t个元素进行一趟快速排序的伪代码如下: 假设要对数组r中第s个元素到第t个元素进行一趟快速排序的伪代码如下: Sub qkpass(ByRef r() As Object, ByVal s As Integer, ByVal t As Integer, ByRef i As Integer) Dim i As Integer Dim j As Integer Dim x As Object i = s j = t x = r(s) While i<j While (i < j) AND (r[j] >= x ) j = j – 1 r(i) = r(j) End While While (i < j) AND (r[j] >= x ) i = i +1 r(j) = r(i) End While End While r(i) = x End Sub
整个快速排序的算法可以用递归的方法实现: Sub qksort(ByRef r() As Object, ByVal s As Integer, ByVal t As Integer) If s<t Then qkpass(r, s, t, k) qksort(r, s, k-1) qksort(r, k+1, t) End If End Sub
小结 过程 • Sub过程的定义 • 调用Sub过程 Function过程 • Function过程的定义 • Function过程的调用 参数传递 • 参数传递的方式 • 数组参数 • 变量的作用域 • 静态变量 • 递归 • 快速排序
作业 P122: 3、6 谢谢大家!