第 8 章 过程
教学要求 掌握VB的两种过程:事件过程和通用过程 掌握Sub过程的定义 掌握Function过程的定义 掌握过程的调用 掌握参数的传递——按值传递和按地址传递 掌握递归算法 掌握变量的作用域 2
教学内容 8.1 Sub过程 8.2 Function过程 8.3 过程调用 8.4 参数传递 8.5 递归过程 8.6 变量的作用域 8.3 过程调用 8.4 参数传递 8.5 递归过程 8.6 变量的作用域 8.7 程序示例 3
模块化程序设计 基本思想:将一个大的程序按功能分割成一些小模块 特点: 各模块相对独立、功能单一、结构清晰、接口简单 控制了程序设计的复杂性 提高元件的可靠性 缩短开发周期 避免程序开发的重复劳动 易于维护和功能扩充 开发方法: 自上向下,逐步分解,分而治之 4
过程 事件过程 通用过程 子程序过程 Sub 函数过程 Function VB中过程的分类 5
8.1 Sub过程 8.1.1 事件过程 当对象识别某事件后,进行的操作处理——以代码的形式存储在事件过程中。 分类: 窗体事件过程 控件事件过程 6
窗体的事件过程名中不使用其具体名称,而固定用Form 窗体事件固定有“Private”关键字,说明它是模块级的 1 定义事件过程 [形式]: Private Sub Form_事件名([参数列表]) [局部变量和常数声明] 语句块 End Sub 完全由系统决定,用户无权修改 窗体的事件过程名中不使用其具体名称,而固定用Form 窗体事件固定有“Private”关键字,说明它是模块级的 7
2 窗体的Initialize、Load、 Activate、 Gotfocus事件 执行次序: Initialize Load Activate Gotfocus 8
注 意 窗体的Initialize、Load事件发生在窗体被显示之前,其中可放置系统初始化命令,但其中语句有所限制; 注 意 窗体的Initialize、Load事件发生在窗体被显示之前,其中可放置系统初始化命令,但其中语句有所限制; 窗体加载后,只要不被卸载,就不会再执行Initialize、Load事件,但Activate事件会多次发生; 当访问另一窗体上的“非可视”数据或调用其中定义的全局过程时,只会触发该窗体的Initialize事件,而Load事件不触发; 当访问另一窗体上的“可视”数据时,会同时触发该窗体的Initialize和Load事件; 9
控件事件固定有“Private”关键字,说明它是模块级的 3 定义控件的事件过程 [一般形式]: Private Sub 控件名_事件名([参数列表]) [局部变量和常数声明] 语句块 End Sub 由系统决定,用户无权修改 控件的事件过程名中必须使用其具体名称 控件事件固定有“Private”关键字,说明它是模块级的 10
4 建立事件过程 打开“代码编辑器”窗口; 先选定某对象,再选定事件过程; 编辑代码; 过程 对象 11
c 计算: n! = m!(n-m)! Private Sub Command1_Click() n = Val(Text1.Text) m = Val(Text2.Text) k = n t = 1 For i = 1 To k t = t * i Next i s = t k = m s = s / t k = n - m Text3.Text = Str(s) End Sub c n m = m!(n-m)! n! 共同代码 计算k! 能不能简化?
使用函数 Private Sub Command1_Click() Dim m As Integer, n As Integer Dim i As Integer, k As Integer Dim t As Long, s As Long n = Val(Text1.Text) m = Val(Text2.Text) s = fact(n) / (fact(m) * fact(n - m)) Text3.Text = Str(s) End Sub Private Function fact(k As Integer) As Long Dim i As Integer fact = 1 For i = 1 To k fact = fact * i Next i End Function 使用函数 函数调用(三次) 函数定义 计算k! 13
过程的引入: 在以下两种情况下,经常使用自定义过程: (1)应用程序中出现较频繁的处理,仅仅是每次处理的数据不同而已; (2)程序中比较复杂的算法,独立出来,增强程序的可读性; “过程”的引入使得整个程序的结构更加清晰,模块化更强。 14
8.1.2 通用过程 完成某一特定功能的程序段 ——通用过程(自定义) 必须显式调用方可执行; 分为: 公有过程(Public) 8.1.2 通用过程 完成某一特定功能的程序段 ——通用过程(自定义) 必须显式调用方可执行; 分为: 公有过程(Public) 私有过程(Private) 15
1 通用Sub过程的定义 [一般形式]: [Private|Public] [static] Sub <过程名>([<参数列表>]) <过程体> End Sub [说明]: (1) 以Sub开头,End Sub结束,,中间是过程体——包括变量声明和语句块; (2) 以Private为前缀的过程是模块级的,以Public为前缀的过程是应用程序级的,缺省默认是Public; (3) Static 选项说明过程中的局部变量是静态变量; (4) 过程名的命名规则和变量名相同,在同一个模块中,过程名必须是唯一的; 16
说 明 (5) 参数列表中的参数称为形式参数,可以没有,但无参数时圆括号不能省略;有多个参数时,参数之间用逗号间隔; (6)参数说明格式: 说 明 (5) 参数列表中的参数称为形式参数,可以没有,但无参数时圆括号不能省略;有多个参数时,参数之间用逗号间隔; (6)参数说明格式: [Optional][ByVal|ByRef] <变量名>[()][As <数据类型>] 其中: a) 若参数是数组,则在变量名后面加一对圆括号,但无维界定义 b) ByVal:指明参数传递方式是传值; c) ByRef:指明参数传递方式是传地址,为缺省值; d) 若参数是字符型的,必须是不定长字符串; e)Optional:参数是可选的,必须定义在必选参数后面。 (7) 当过程调用结束,即执行到End Sub语句,系统自动返回调用程序的调用语句处,执行调用语句的下一条语句; (8) 过程不能嵌套定义,但可嵌套调用; (9)Exit Sub语句的功能是提前退出过程调用,返回调用语句。 17
该通用过程实现交换功能,包含两个参数,均是ByRef形式的参数。 例: Private Sub Exchange(x As Integer,y As Integer) Dim Temp As Integer Temp=x : x=y : y=Temp End Sub 该通用过程实现交换功能,包含两个参数,均是ByRef形式的参数。 18
2 建立通用Sub过程 方法一:在“代码编辑窗口”的“通用”中自行输入; 方法二:进入代码编辑器窗口,执行“工具”菜单的“添加过程”,在“添加过程”对话框中输入定义过程的名称、类型和范围后单击“确定”按钮,系统会自动在代码窗口添加自定义过程的框架。 19
3 Sub过程调用 注意 :一个过程或函数可以被调用多次; 必须在事件过程或其它通用过程中显示调用。 Private Sub sub2(形参表) …… 过程语句 End Sub Private Sub sub1() …… Call Sub2(实参表) End Sub ② ③ ① ④ ⑤ 主调程序 被调过程 注意 :一个过程或函数可以被调用多次; 20
Call Fact(x) Fact x Sub过程调用 P164 [格式二]: <过程名> [<实在参数表>] [功能]:对已定义的过程进行调用。 如: P164 Call Fact(x) Fact x 21
说 明 (1)调用的过程必须是已经定义的,否则系统会出现“子程序或函数未定义”的信息提示; (2)实在参数可以是常量、变量或表达式; (3)实在参数的数目及类型要和定义时必选参数保持一致,否则系统会出现“参数不可选”的信息提示,参数之间用逗号间隔; (4)若子程序没有参数,则格式一中的括号可以省略; (5)格式一和格式二的区别在于:格式二的参数表无须括号,而是和过程名之间用空格隔开; 22
例8-2 试编写一个找出任意一个正整数的因子的程序 例8-2 试编写一个找出任意一个正整数的因子的程序 P165 23
程序说明: 定义子程序factor求因子,该子程序定义了两个参数,来传递求因子实现前的原始数据和返回功能实现后的结果数据; Option Explicit Private Sub Command1_Click() Dim inta As Integer, st As String inta = Text1 Call factor(inta, st) Text2 = st End Sub 调用过程factor Private Sub factor(ByVal n As Integer, s As String) Dim i As Integer For i = 1 To n - 1 If n Mod i = 0 Then s = s & Str(i) Next i End Sub 定义过程 优点:程序即Sub过程的使用不仅可以缩短程序的长度,还能够使程序的结构更加清楚。 24
例:定义一个通用过程用以求一维数组中的最小值 Private Sub value(a() As Integer, min As Integer) Dim i As Integer min = a(1) For i = 2 To UBound(a) If a(i) < min Then min = a(i) Next i End Sub 25
8 . 2 Function 过程 Function过程的特点是返回一个值,因此我们通常也称之为自定义函数。通常我们利用Function过程得到一个数值或一个字符串或一个逻辑值。 [格式]: [Private|Public] Function <函数名>([参数列表]) [As <数据类型>] <函数体> End Function 函数返回值的类型 函数名=表达式 26
说 明 (1) 以Function开头,以End Function结束,中间是函数体; 说 明 (1) 以Function开头,以End Function结束,中间是函数体; (2)函数名命名规则、参数列表的表示都和Sub过程相同; (3)As 数据类型:函数过程将由函数名返回一个值,值的类型由[As 数据类型]定义; ★ (4) 函数体中一定要有对函数名赋值的语句——函数名=表达式,否则返回相应类型的初值; ★ (5)函数体内可有Exit Function语句——无条件退出函数过程,返回主程序。 (6)Function过程不能嵌套定义,但可嵌套调用。 27
例:定义函数: Private Function f(ByVal x As Integer) As Integer 函数返回值的类型 Private Function f(ByVal x As Integer) As Integer f = 3*x^3-2*x^2+6*x-1 End Function 函数名=表达式 28
传值的参数 例8-1:编写一个求n!的函数。 Private Function Fact(Byval n As Integer) As Long Dim K As Integer Fact=1 If n=0 Or n=1 Then Exit Function Else For K=1 To N Fact=Fact*K Next K End If End Function 函数类型 函数名=表达式 29
2 调用 Function 过程 Call fact(x) Fact x 这两种方法均放弃函数的返回值 [格式]:<函数名>([实在参数表]) [功能]:返回一个函数值。 [说明]: (1)一般情况下,函数的调用出现在赋值语句中,并且在赋值号的右侧; (2)若函数没有参数,函数名后的括号不能省略; (3)虽然VB允许象调用Sub过程那样调用Function过程,但这样调用时系统不返回函数值,建议大家不要使用这种调用方法。 Call fact(x) Fact x 这两种方法均放弃函数的返回值 例如: 30
例:用函数实现求一维数组中的最小值,对比前例(Sub) Private Function min( a( ) As Integer ) As Integer Dim i As Integer min = a(1) For i = 2 To UBound(a) If a(i) < min Then min = a(i) Next i End Sub 函数类型 函数名=表达式 31
可省略,因Prime的初始值就是false 例:定义函数用以判断一个数是否是素数 Private Function Prime(N As Integer) As Boolean Dim i As Integer Prime = False For i = 2 To N-1 If N Mod i = 0 Then Exit For Next i If i =N then Prime = True End Function 可省略,因Prime的初始值就是false 函数名=表达式 32
[例]:S=1!+2!+…+10! 函数调用: S = S + Fact(i) 函数名=表达式 对比p171 Private Sub Form_Click() Dim S As Long, i As Integer For i = 1 To 10 Next i Print ”S="; S End Sub 函数调用: S = S + Fact(i) Private Function Fact(n As Integer) As Long Dim i As Integer Fact = 1 For i = 1 To n Fact = Fact * i Next i End Function 函数名=表达式 33
例8-3:利用函数过程编写一个求两个正整数的最大公约数的程序 P166 Private Function Gcd(ByVal A As Integer, ByVal B As Integer) As Integer Dim R As Integer R = A Mod B Do While R <> 0 A = B B = R Loop Gcd = B End Function Private Sub Form_Click( ) ‘ 主调过程 Dim N As Integer, M As Integer, G As Integer N = InputBox("输入N") M = InputBox("输入M") G = Gcd(N, M) Print N; "和"; M; "的最大公约数是:"; G End Sub 使用赋值语句调用函数 34
8.3.4 调用其他模块中的过程 1、调用其他窗体模块中的公有过程 在Form2中调用 8.3.4 调用其他模块中的过程 1、调用其他窗体模块中的公有过程 [格式]:Call <窗体名>.<过程名>[(<实在参数表>)] [功能]:调用其他窗体模块中定义的公有过程。 Private Sub Form_Click() Dim a As Integer a = 10 Print Form1.Fact(a) End Sub 在Form2中调用 35
2、调用标准模块中的公有过程 [格式]:Call [<标准模块名>.]<过程名>[(<实参表>)] [功能]:调用其他标准模块中定义的公有过程。 [说明]: 若公有过程唯一,则直接调用,不加模块名。 若存在同名的公有过程,则: 调用本模块中过程:直接调用,不加模块名 调用其它模块中过程:必须加模块名。 被调用的函数和过程必须是公有的; 函数也可以这样调用。 36
调用类模块中的过程 不做要求 [格式]: dim democlass as new class1 call democlass.clssub ( [实参表] )] [注意]: 调用类模块中的共有过程,须用该类的某一实例修饰过程。 不做要求 37
8.4 参数的传递 8.4.1 形式参数和实在参数 形式参数: 过程定义时,在过程名后面的圆括号里的一系列变量; 8.4 参数的传递 8.4.1 形式参数和实在参数 形式参数: 过程定义时,在过程名后面的圆括号里的一系列变量; 过程被调用执行时,系统才给形参分配存储空间; 可以是除定长字符串外的任一简单变量; 可以是数组,变量名后接括号; 简称“形参” St As String *3 × St( ) As String *3 √ St As String √ 38
实在参数 主调程序中,调用语句中,出现在过程名后面圆括号里的变量,是实在参数,可以是常量、变量或表达式; 过程调用传递参数时,实参按“位置”和形参结合; 实在参数和形式参数,要求个数一样,位置对应,类型一致,否则会出错; 定长字符串变量可以作为实在参数; 简称“实参” 39
参数结合: 形参 实参 变量 变量、常量、表达式、数组元素 数组 40
举例: Private Sub Form_Click() Dim x as single , st as sting*5 Private sub test(a as single,loc as booleaan,array1() as integer,chr1 as string) … End sub Private Sub Form_Click() Dim x as single , st as sting*5 Dim a(5) as integer Call test (x^2 ,true, a, st ) End sub 定长字符串 表达式 常量 数组 41
举例:写出下列程序执行的结果 Private Sub ExamSub( x as integer,y as integer) x=x+10 : y=y-10 End Sub 过程的先后顺序无所谓 Private Sub Form-click() Dim x as integer, y as integer x=10:y=100 Call ExamSub( y ,x ) Print “x=” ; x ; “y=” ; y End Sub 调用子过程时,进行的实际操作是y=y+10,x=x-10 42
8.4.2 按值传递参数 [格式]:ByVal <变量说明> [传递方式 ]: 8.4.2 按值传递参数 [格式]:ByVal <变量说明> [传递方式 ]: 调用时,系统为形参分配一个临时存储单元,并将实参 的值存储到该临时单元中。 若在被调用的过程体中改变了形参的值,只是改变了临时 存储单元中的数据,对实参的值无任何影响。 43
[参数传值传递]举例: Private Sub Form_Click() Dim M As Integer, N As Integer M = 15: N = 20 Call Value_change(M, N) Print "M="; M, "N="; N End Sub Private Sub Value_change(ByVal x As Integer, ByVal y As Integer) x = x + 20 y = y + 20 Print "X="; x, "Y="; y End Sub 44
传值是单向的: 实在参数 形式参数 调用时,实参将值传递给形参,两者就无任何关联。过程中形参的值发生变化,对实参无任何影响。 45
8.4.3 按地址传递参数 [格式]: ByRef <变量说明> [传递方式]: 系统在调用执行过程时,为形参分配临时存储单元,并将实参的内存单元地址传送给形参,存储在临时存储单元中 在被调用的过程体中一切对形参的操作,都是直接对地址保存在形参中的内存单元中的数据进行的,而实参就是保存在此内存单元中的数据,所以任何对形参的操作也就是对实参的操作; 按地址传递时,形式参数和实在参数共用同一“内存单元”。 46
[参数传址传递]举例:(对比前例) Private Sub Form_Click() Dim M As Integer, N As Integer M = 15: N = 20 Call Value(M, N) Print "M="; M, "N="; N End Sub Private Sub Value(x As Integer, y As Integer) x = x + 20 y = y + 20 Print "X="; x, "Y="; y End Sub 47
传地址是双向的: 实在参数 形式参数 48
举例:计算5!+4!+3!+2!+1! P171 Private Sub Form_Click() Dim Sum As Integer, I As Integer For I = 5 To 1 Step -1 Sum = Sum + Fact(I) Next I Print "Sum="; Sum End Sub Private Function Fact(n As Integer) As Integer Fact = 1 Do While n > 0 Fact = Fact * n n = n - 1 Loop End Function 修改: 方法一:在形参n前加ByVal 方法二:将调用语句中fact(i)改为fact( ( i ) ) 49
注 意 参数有两种传递方式:传值(ByVal)和传地址(ByRef)。 注 意 参数有两种传递方式:传值(ByVal)和传地址(ByRef)。 若调用时实参为常量或表达式,这两种方式没有区别,无论形参定义的是按值传递还是按地址传递,系统都是按传值方式传递 系统在调用时为形参分配一个临时存储单元,将表达式的值计算出来,存储到该临时单元;调用结束,系统收回临时单元。 调用函数或过程时,将单个变量转换成表达式的方法:将变量放在一对括号中。如用Fact((I))的方式调用函数,系统会按传值来处理。 50
注 意 按地址传递时,当实参是变量时,实参与形参必须类型完全一致; 注 意 按地址传递时,当实参是变量时,实参与形参必须类型完全一致; 按地址传递时,当实参是常量或表达式时,VB会自动进行类型转换,然后再传递相应的值(即类型无须完全一致); 在算术表达式中,函数的优先级最高,若表达式中有函数的实参,而函数的参数又是按地址传递的,则函数中有可能改变了实参的值,即改变了表达式中变量的值,会引起混淆。 见例 见例 51
实际的参数传递方式是按值传递,强制转换后, 举例: Private Sub Form_Click() Dim S As Single S=125.5 Call Convert((S),”12”+”.5”) End Sub Private Sub Convert(Inx As Integer,Sing As Single) Inx=Inx*2 Sing=Sing+23 Print “Inx=”;Inx,”Sing=”;Sing 实际的参数传递方式是按值传递,强制转换后, inx =126 sing=12.5 52
举例: a=a+10 : b=b+10 : c=c+10 fun_add=a+b+c End function Private function fun_add(a as integer,b as integer,c as integer) as integer a=a+10 : b=b+10 : c=c+10 fun_add=a+b+c End function 问题出在哪? Private sub command1_click() Dim v1 as integer,v2 as integer,v3 as integer V1=2 : v2=3 : v3=4 Debug.print v1+v2+v3*fun_add(v1,v2,v3) End sub 53
练 习 (1)写出程序运行结果(2)若改为按值传递,结果又如何? 练 习 (1)写出程序运行结果(2)若改为按值传递,结果又如何? Dim p As integer, q As integer, r As integer Private Sub Form_Click() p = 2: q = 3 Print p, q, r Call pq(p, q, r) End Sub Private Sub pq(x As integer, y As integer, z As integer) x = p + q y = p - q z = x + y 54
练 习 写程序运行的结果: Option Explicit Private Sub command1_click() 练 习 写程序运行的结果: Option Explicit Private Sub command1_click() Dim i As Integer, j As Integer Dim k As Integer i = 1: j = 2 k = fun(i, fun(i, j)) + i + j Print "I="; i; "J="; j; "K="; k End Sub Private Function fun(a As Integer, ByVal b As Integer) As Integer a = a + b b = b + a fun = a + b End Function 55
8.4.4 数组参数 VB允许把数组作为形式参数,声明数组的格式是: <数组名>() As <数据类型> [注意]: (1)数组参数只能按地址传递,即不能用ByVal来修饰数组参数 (2)定义数组参数时无须说明数组的维数和下标变化范围; (3)调用过程时,对应的实在参数也必须是数组,但只需要数组名,无须后跟括号,且数据类型也要一致。` (4)在过程体或函数体中无须对数组参数再次说明; (5)若实参是动态数组,在过程体或函数体中可以使用重定义语句修改数组的维界; 56
举例 Private Sub Form_Click() Dim a() As Integer, i As Integer Dim n As Integer n = InputBox("请输入数组的初始大小") ReDim a(n) For i = 1 To n a(i) = i Next i Call PrintArray(a) Call Array1(a) End Sub Private Sub PrintArray(x() As Integer) Dim i As Integer For i = LBound(x) To UBound(x) Print x(i); Next i Print End Sub Private Sub Array1(a() As Integer) Dim m As Integer, n As Integer n = UBound(a) m = InputBox("请输入新大小") ReDim Preserve a(m) Dim i As Integer For i = n + 1 To m a(i) = 0 Next i End Sub 输入5和8,程序执行结果为: 57
举例:自定义一个将一维数组按从小到大排序的通用过程。 Private Sub sort(a() As Integer) Dim i As Integer, j As Integer For i = 1 To UBound(a) - 1 For j = i + 1 To UBound(a) If a(i) > a(j) Then temp = a(i) a(i) = a(j) a(j) = temp End If Next j Next i End Sub Private Sub Command1_Click() Dim i As Integer, j As Integer Dim a(10) As Integer For i = 1 To 10 a(i) = Int(8 * Rnd) Picture1.Print a(i); Next i Call sort(a) Picture2.Print a(i); End Sub 58
若不希望实参随形参改变,就将参数传递方式定义为传值 练 习 完善程序:本程序的功能是找出100~200之间的所有素数。 Private Sub Form_Click() Dim i As Integer, x As Integer For i = 100 To 200 If Then Print i Next i End Sub Private Function prime( ) As Boolean Dim i As Integer For i = 2 To Sqr(n) If n Mod i = 0 Then Next i prime = True End Function 若不希望实参随形参改变,就将参数传递方式定义为传值 Prime(i) ByVal n As Integer Exit Function 59
8.4.5 对象参数 不做要求 在VB中,支持过程的形参是对象: 当传递窗体时,定义形参类型是Form; 8.4.5 对象参数 在VB中,支持过程的形参是对象: 当传递窗体时,定义形参类型是Form; 当传递控件时,定义形参类型是Control. 不做要求 60
错误的调用:fact(n)=n*fact(n-1) 8.5 递归过程 所谓递归,就是在过程定义中,调用过程本身。 例:使用递归函数求N!。 [提示]:N!=N*(N-1)! 表示为:fact(n)=n*fact(n-1) Private Sub Form_Click() Dim n As Integer n = InputBox("输入一个正整数") Print n; "!="; fact(n) End Sub 错误的调用:fact(n)=n*fact(n-1) Private Function fact(ByVal n As Integer) As Long If n = 1 Then fact = 1 Else fact = n * fact(n - 1) End If End Function 函数调用,调用自身 61
使用递归算法,最重要的是要有一个结束递归的条件,可以使递归得以返回,即终止条件或边界条件。 执行过程分析(以输入3为例): 逐层返回 逐层调用 1 Fact(3) 3*Fact(2) Fact(2) 2*Fact(1) Fact(1) 6 2 总结: 使用递归算法,最重要的是要有一个结束递归的条件,可以使递归得以返回,即终止条件或边界条件。 62
例:使用递归算法计算裴波拉挈数列。 Private Sub Form_Click() Dim n As Integer n = InputBox("求数列的第几项") Print “数列中第”; n; “项是”; Cal(n) End Sub Private Function Cal(n As Integer) If n = 1 Or n = 2 Then Cal = 1 Else Cal = Cal(n - 1) + Cal(n - 2) End If End Function 63
上例的非递归解法——例5-7(P109)。 递推法:利用未知项与已知项之间存在的某种关系,从已知项逐项推出未知项的方法。 Private Sub Form_Click() Dim fb() As Integer Dim i As Integer,, n as integer n = InputBox(“求数列中第几项的值”) Redim fb(n) fb(1)=1:fb(2)=1 For i = 3 To n fb(i)=fb(I-1)+fb(I-2) Next i Print "数列中的第"; n; "项是"; fb(n) End Sub 64
[例8-9]:编写一个递归函数,求任意两个整数的最大公约数。 P190 Private Sub Command1_Click() Dim Gcdvalue As Long, M As Long, N As Long If M < > 0 And N < > 0 Then Gcdvalue = Gcd(M, N) Text3.Text = CStr(Gcdvalue) End If End Sub Private Function Gcd(ByVal X As Long, ByVal Y As Long) Dim R As Long R = X Mod Y If R = 0 Then Gcd = Y Else X = Y :Y = R :Gcd = Gcd(X, Y) End If End Function Private Function Gcd(ByVal a As Long, ByVal b As Long) Dim r As Long r = a Mod b Do While r<>0 a=b b=r r=a Mod b Loop Gcd = b End Function 65
2、返回调用程序的调用语句,接着执行下面的语句。 练 习 请注意: 1、逐层调用, 逐层返回。 2、返回调用程序的调用语句,接着执行下面的语句。 写出下列程序运行的结果: Private Sub Command1_Click() Dim a As Integer a = 2 Call sub1(a) End Sub Private Sub sub1(x As Integer) x = x * 2 + 1 If x < 10 Then Call sub1(x) End If Print “x=”;x 66
8.6 变量的作用域 关键字 使用位置 作用域 Public 在标准模块的声明段中 全局 Private和Dim 在模块的声明段中 模块 在过程中 局部|过程 Static 局部、静态 67
8.6.1 过程级变量 例: Private Sub Command1_Click() 问: Dim i As Integer 在一个过程内部声明的变量,只在过程内部有效,又称为局部变量; 例: Private Sub Command1_Click() Dim i As Integer i = i + 1 Print i End Sub Private Sub Command2_Click() i = i - 1 问: (1)单击两次按钮1,结果是什么? (2)先单击按钮1,再单击按钮2,结果又是什么? (1) 1 1 (2) 1 -1 过程级变量,过程开始执行时,系统分配给其存储单元,而一旦过程运行结束,系统就收回存储空间。在其它过程中,不可访问。 68
8.6.2 模块级变量 Dim i As integer Private Sub Command1_Click() i = i + 1 8.6.2 模块级变量 在模块的“通用”声明段中用Private和Dim声明的变量,模块中任何一个过程都可以访问; Dim i As integer Private Sub Command1_Click() i = i + 1 Print i End Sub Private Sub Command2_Click() i = i - 1 问: (1)单击两次按钮1,结果是什么? (2)先单击按钮1,再单击按钮2,结果又是什么? (1) 1 2 (2) 1 0 模块级变量,在模块加载时,即分配给其内存存储单元,模块中任意过程均可对该变量进行操作,直到模块卸载时,系统才收回存储空间。但在其它模块中,不可访问。 69
8.6.3 全局变量 在模块的通用部分用Public声明的变量,在整个应用程序中都可访问,即在其他模块也可以访问。全局变量分为两种: 8.6.3 全局变量 在模块的通用部分用Public声明的变量,在整个应用程序中都可访问,即在其他模块也可以访问。全局变量分为两种: 第一种——在窗体模块(.frm)中定义: 窗体1中的程序代码 Public I as Integer Private Sub Command1_Click() I=I+1 End Sub Private Sub Command2_Click() Form2.Show 窗体2中的程序代码: Private Sub Form_Click() Print Form1.I End Sub 问:单击十次按钮1,再单击按钮2,单击窗体2,结果是什么? 变量I是在窗体1中的通用部分定义的公有变量,其他模块可以访问它,但必须在变量前加上所属窗体的名称。 70
Private Sub Command1_Click() I=I+1 End Sub 第二种——在标准模块(.bas)中定义: 窗体1中的程序代码 Private Sub Command1_Click() I=I+1 End Sub Private Sub Command2_Click() Form2.Show 窗体2中的程序代码: Private Sub Form_Click() Print I End Sub 标准模块1中的程序代码: Public I as Integer 变量I是在标准模块1中的通用中定义的公有变量,其他模块可以直接访问,即无须在变量前加任何修饰。 71
8.6.4 关于同名变量 当变量的作用域不同时,允许变量的名称相同。 例如: 问: (1)单击两次按钮1,再单击按钮2,结果是什么? 8.6.4 关于同名变量 当变量的作用域不同时,允许变量的名称相同。 例如: 问: (1)单击两次按钮1,再单击按钮2,结果是什么? Dim i As integer Private Sub Command1_Click() i = i + 1 Print i End Sub Private Sub Command2_Click() i = i + 10 1 2 10 说明:当变量名称相同而作用域不同时,优先访问局限性大的变量,即作用范围小的变量屏蔽作用范围大的变量。 72
举例: Option explicit Public x as integer,y as integer,z as integer Private sub form_activate() Conflict_x Debug.print “X,Y和Z是:”,x,y,z End sub Private sub form_load() X=10 : y=20 : z=35 Private sub conflict_x() Dim x as integer x=135 思考:当把X改成 form1.x后,运行结果是什么? 73
8.6.5 静态变量 在过程内的变量定义时,用Static 关键字定义的变量。 8.6.5 静态变量 在过程内的变量定义时,用Static 关键字定义的变量。 所谓静态变量,是指仅在过程第一次执行时,分配给该变量内存单元,到过程执行结束时,并不象其它局部变量一样收回存储空间,系统并不收回分配给静态变量的存储空间,其内的内容得以保留,当再次调用执行该过程时,静态变量中保留了上次运行时的数据,本次执行可直接访问。 静态变量只在过程内有效,即是局部变量。 例 Private Sub Command1_Click() Static i As integer i = i + 1 Print i End Sub 问:(1)单击两次按钮1,结果是什么? (2)如果将Static改为Dim,结果又如何? (1)1 2 (2)1 1 74
举例: Option explicit Private sub command1_click() Dim k as integer K=5 Call static-variable(k) Debug.print “第一次调用:k=”;k Debug.print “第二次调用:k=”;k End sub 说明: 因二次过程调用时,STA的初值不同,所以虽然实参一样,但运行结果不同。 Private sub static_variable(n as integer) Static sta as integer Sta=n+sta N=sta+n End sub 75
练 习 写程序运行结果: Dim a As Integer, b As Integer ‘模块级变量 练 习 写程序运行结果: Dim a As Integer, b As Integer ‘模块级变量 Private Sub Form_click() Dim x As Integer, y As Integer a = 5: b = 3 x = x + a: y = y + b Print fun1(x, y) End Sub Private Function fun1(m As Integer, n As Integer) As Integer Static k As Integer ‘静态变量 Dim a As Integer, b As Integer ‘同名的过程级变量 a = a + m b = b - n k = k + a + b fun1 = k End Function 77
8.7 程序示例 例 8-5 例 8-6 例 8-7 ★ 例 8-11 78
[例8-5] 利用级数法编程求反正弦函数值 [分析]:求解这一类累加和的题目,关键是两点:一是通项的表示,二是求解的精度,即当通项小于等于给定的误差值时,就停止累加。 [一般形式]如下两种: Do … tx=… s=s+tx n=n+1 Loop While tx>eps Do … tx=… if tx <=eps then exit do s=s+tx n=n+1 Loop 79 [形式一] [形式二]
Private Sub Command1_Click() Dim x As Single, n As Integer, eps As Single Dim s As Single, a As Single, temp As String temp = InputBox(“请输入一个绝对值小于等于1的数与允许误差:” , “求函数值” , “0, 1e - 5“ ) temp = Trim(temp) ‘调用函数,删除前后空格 n = InStr(temp, “, ” ) ’找出分隔符,的位置 x = Left(temp, n - 1) ’取X eps = Right ( temp, Len ( temp) – n ) ’取允许误差 s = x : n = 1 Do a = afun(x, n) ’调用自定义函数,求通项 If a <= eps Then Exit Do s = s + a n = n + 1 Loop Label2.Caption = "arcsin(" & CStr(x) & ")=" Text1.text = s End Sub 80
Private Function afun(ByVal x As Single, ByVal n As Integer) As Single Dim i As Integer, p As Single p = 1 For i = 1 To n p = p * (2 * i - 1) / (2 * i) Next i afun = p * x ^ (2 * n + 1) / (2 * n + 1) End Function 81
[例8-6] 改进的冒泡排序法 分析: 传统的冒泡排序法,共进行N-1轮扫描,每轮依次进行A(1)与A(2), A(2)与A(3),…直至A(10-i)与A(10-i+1)的比较,一旦前者比后者大,即交换他们的值。 如果有数据序列:1,2,3,4,5,7,6;其实它只要进行一轮扫描,即排好序;但传统的冒泡排序法,不管它已排好序,还继续进行5轮扫描,只是不交换任何元素,这其实毫无意义。 因此,在改进的冒泡排序法中,我们增设了一个开关Switch在比较过程中,若发生了数据交换,则设为True,而若一轮比较下来,没有发生数据交换,即此序列已排好序,就设为False,在每轮比较结束时,判断开关Switch的值,若为False,退出循环,结束扫描。 82
[改进的冒泡排序程序] ub=Ubound(a) Switch=True Do While swtich Switch=False For j=1 to ub ’n-I If a(j) > a(j+1) Then TEMP=A(j) : A(j)=A(j+1):A(j+1)=TEMP End If Next j Loop 83
例8-7 把一个任意十进制转换成N进制数(N<=16) E 78 4 14 16 余数 商 84
十六进制的基本元素是0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F。 二进制的基本元素是0和1 ; 八进制的基本元素是0、1、2、3、4、5、6、7 ; 十六进制的基本元素是0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F。 可以看出十六进制的基本元素包含其他两种进制的基本元素,因此,我们定义数组Char(15),依次存放最大进制——十六进制的基本元素。 以整除N的余数作为下标,取数组Char(15)中的元素就是需要的结果。 85
Dim N As integer, Num As Long Private Sub Command2_Click() End End Sub Option Explicit Dim N As integer, Num As Long Private Sub Command2_Click() End End Sub Private Sub Text1_Change() N = Val(Text1) Label4.Caption = Str$(N) + "进制数" Private Sub Text2_Change() Num = Val(Text2) N进制 需转换的数 86
调用过程trans,实参是数组,只需数组名 Private Sub Command1_Click() Dim Ch As String, i As integer Dim Char(15) As String Dim Bin() As String For i = 0 To 9 Char(i) = Str$(i) Next i For i = 0 To 5 Char(10 + i) = Chr$(Asc("A") + i) Print ReDim Bin(1) Call trans(Bin, Char) For i = UBound(Bin) To 1 Step –1 Ch = Ch + Bin(i) Text3 = Ch End Sub 数组 Char 元素0 1 2 …9 A B C D E F 下标0 1 2…..9 10 11 12 13 14 15 调用过程trans,实参是数组,只需数组名 逆序输出结果数组Bin元素 87
Private Sub trans(vary() As String, st() As String) Dim R As integer Bin Char Private Sub trans(vary() As String, st() As String) Dim R As integer Dim K As integer K = 0 Do Until Num = 0 R = Num Mod N K = K + 1 ReDim Preserve vary(K) vary(K) = st(R) Num = int(Num / N) Loop End Sub 结束条件商为0 不断增加动态数组上界,以存放新元素 注意语句 的次序 以余数为下标,找出数组中对应的字符 如:14“E” 或Num \N 将余数依次存放在动态数组vary中 88
[例8-11]:直接插入排序法——从小到大的顺序。 将待排序的N个数据存放在数组SORT中,首先将SORT(1)认为已排序子序列,然后依次将SORT(2)、 SORT(3)、… SORT(N)插入到已排好序的子数列中。每插入一个元素都依次进行以下三个操作。 1.先将待插元素SORT(I)放入变量Temp中; 2.寻找插入位置:将temp依次与SORT(I-1)、 SORT(I-2)、… 进行比较,一旦SORT(k)< temp,则停止比较,插入位置即是SORT(K)之后; 3.空出位置,将元素插入:将SORT(k+1) 至SORT(I-1)的元素依次后移一位,再将变量temp的值放入SORT(K+1)中;注意:为避免元素值丢失,必须先将SORT(I-1)赋给SORT(I), SORT(I-2)赋给 SORT(I-1),…,直至 SORT(K+1)赋给 SORT(K +2 ). … … 重复上述操作,将每个元素都插入到指定位置中。 89
直接排序示例 排序前 3 6 1 5 4 2 1 3 2 3 6 3 6 5 4 4 6 5 5 6 6 排序后 Temp 6 1 5 4 2 90
寻找第一个比temp小的元素的位置 取待排序的元素 一边比较 一边后移 K指向前一个元素 为temp的插入位置 Private Sub insertion(sort( ) As Integer) Dim k As Integer, i As Integer, temp As Integer,Ub As Integer Ub = UBound(sort) For i = 2 To Ub temp = sort (i) k = i - 1 Do While temp < sort(k) sort(k + 1) = sort(k) k = k - 1 If k <= 0 Then Exit Do Loop sort (k + 1) = temp Next i End Sub 寻找第一个比temp小的元素的位置 取待排序的元素 一边比较 一边后移 K指向前一个元素 temp比已排序的所有元素都小则结束 为temp的插入位置 91