第六章 过程 Visual Basic 程序设计
引 例 计算100~300之间所有能被3整除的数之和 计算500~800之间所有能被5整除的数之和 2019/4/5 引 例 计算100~300之间所有能被3整除的数之和 计算500~800之间所有能被5整除的数之和 要求编写代码实现100~300之间所有能被3整除的数之和以及500~800之间所有能被5整除的数之和 2019/4/5
b a c d e g f 计算多边形面积,可将多边形分解成若干个三角形 S1 S2 S3 已知五边形的各条边的长度,计算其面积 下面我们看利用Function过程解决一个案例的过程。 求任意多边形的面积。 对于任意多边形求面积,没有对应的公式,通常的解决思路是给多边形添加辅助线,将多边形划分成若干个三角形,通过求三角形的面积,最终获得多边形面积。 2019/4/5
假设过程Triarea(x,y,z)可计算三角形的面积 例 a b d f g c e S1 S2 S3 多边形的面积=S1+S2+S3 假设过程Triarea(x,y,z)可计算三角形的面积 这里要编写代码实现三个三角形面积的求解,为提高代码的重用性,我们将求三角形面积的代码封装到一个Function过程triarea(x,y,z)中,其中x,y,z是三角形的三条边的长度。 那么该多边形的面积就等于三个不同三角形面积之和。 现在我们关心的重点是Triarea这个Function过程是如何编写的,也就是如何定义Function过程的问题。 多边形的面积=Triarea(a,b,c)+Triarea(c,d,e)+Triarea(e,f,g) 2019/4/5
Function过程的定义 Function Triarea(x!, y!, z!) As Single c = (x+y+z)/2 [Private|Public][Static] Function 函数过程名 ([形参表]) [As 类型] …… 函数过程名=表达式 [Exit Function] End Function Function Triarea(x!, y!, z!) As Single c = (x+y+z)/2 Triarea = sqr(c*(c-x)*(c-y)*(c-z)) End Function 下面我们来看与Function过程相关的一些概念。 Function关键字,以Function开头,以End Function结尾。类似于事件过程(Sub…End Sub) Function关键字之后是过程名,过程名的命名规则与变量名的相同,我们这里用Tri表示三角形,area表示面积。在Function中过程名具有特殊意义,除了表示Function过程的名称外,还出现在代码中,兼备获得过程最终结果的功能。 过程名后的括号中是参数,本题要求不同三角形的面积,不同之处在于三角形三边边长不相同。注意参数的格式,变量名紧接着类型说明符,或者变量名 As 数据类型。 参数后As Single是对过程名Triarea的类型说明。 这里x,y,z表示三角形的三条边的长度,大括号中的代码就是利用三条边长求三角形的面积,面积值保存在变量s中。 2019/4/5
Function过程的建立 Function过程可以在标准模块或窗体的通用层中建立 利用“工具”菜单下的“添加过程”命令定义 在代码窗口直接定义 2019/4/5
Function函数过程的举例 【例】编写函数过程计算n! 【例】组合数是求如Cnm的数,组合数的大小为n!/(m!*(n-m)!)
Function函数过程的举例 【例】编写函数过程计算n! 【例】组合数是求如Cnm的数,组合数的大小为n!/(m!*(n-m)!)
函数的特点 函数只能传回一个数 2019/4/5
一般而言,若需多个结果, 通常使用Sub过程 案例 编写实现一元二次方程求解的通用过程。 一般式 ax2+bx+c=0 思考:一元二次方程可能有两个不同的实根, 能否用Function过程实现? 我们再看一个案例,编写实现一元二次方程求解的通用过程。 本题具有一个特殊性,一元二次方程可能有两个实根,能否用Function过程实现?请同学们在课后思考并尝试用Function编写实现本功能。 一般而言,若需多个结果, 通常使用Sub过程 2019/4/5
Sub过程 Sub过程的定义 [Private|Public][Static] Sub 子程序过程名 [(形参表)] …… [Exit Sub] End Sub 2019/4/5
Sub 过程 Call test(n, 10) test n, 10 Sub 过程名(参数列表) … [Exit Sub] End Sub 过程调用 Call 子过程名 [(实参)] 子过程名 [实参] Call test(n, 10) test n, 10 2019/4/5
Sub过程的举例 计算一元二次方程的两个实根 【思路】一元二次方程的一般式为 它的两个根可以由 Sub quad( ) End Sub a!, b!, c! , x1!, x2! x1 = (-b + Sqr(b * b - 4 * a * c)) / (2 * a) x2 = (-b - Sqr(b * b - 4 * a * c)) / (2 * a) Sub Command1_Click() Call quad(a, b, c, x1, x2) root1 = x1: root2 = x2 End Sub 2019/4/5
sub子过程的举例 【例】编写子过程计算n! 【例】组合数是求如Cnm的数,组合数的大小为n!/(m!*(n-m)!)
子过程与函数过程区别 (1)函数过程名有值,有类型,在函数体内至少赋值一次 子过程名无值,无类型,在子过程体内不能对子过程名赋值 (2)调用时,子过程调用是一句独立的语句 函数过程不能作为单独的语句加以调用,必须参与表达式运算 (3)一般当过程有一个函数值,使用函数过程较直观 反之若过程无返回值,或有多个返回值,使用子过程较直观 2019/4/5
传地址:实参为变量、数组、数组元素及记录时,或形参用ByRef声明 参数传递 参数按位置传送有两种方式: 传地址[ByRef]( 也称“引用”) 形参得到的是实参的地址,当形参值的改变同时也改变实参的值 是默认的参数传递方法 传值ByVal 形参得到的是实参的值,形参值的改变不会影响实参的值 传地址:实参为变量、数组、数组元素及记录时,或形参用ByRef声明 传值:实参为常量、表达式、或形参用ByVal声明 2019/4/5
传地址调用:形参获得实参地址,共享存储单元 传地址ByRef a = 1: b = 2: c = 3: d = 4 Call test1(a, b, c, d) Print a; b; c; d … Private Sub test1(b%, c%, d%, e%) e=b+c+d End Sub d 6 4 e(传地址) 传地址调用:形参获得实参地址,共享存储单元 要求:行参和实参必须类型相同 内存单元 传地址调用:形参改变,实参发生对应变化 2019/4/5
传地址ByRef Private Sub f(n%) n = 100 传地址调用 End Sub Private Sub Form_Click() Dim m% m = 10 Print "调用过程前m ="; m Call f(m) Print "调用过程后m ="; m 传地址调用 2019/4/5
传值ByVal 4 6 4 a = 1: b = 2: c = 3: d = 4 Call test2(a, b, c, d) d Print a; b; c; d … Private Sub test2( b%, c%, d%, ByVal e%) e=b+c+d End Sub d 4 6 4 e(传值) 传值调用:形参获得实参的值 内存单元 传值调用:形参改变,不影响实参 2019/4/5
传值ByVal Private Sub f(n%) n = 100 End Sub Private Sub Form_Click() Dim m% m = 10 Print "调用过程前m ="; m Call f((m)) Print "调用过程后m ="; m 实参用括号括起来,传值调用 2019/4/5
例1 Private Sub Form_Click() Dim a as integer,b as integer,c as integer a=1:b=2:c=3 Call subtest(a,b,c) Print a,b,c End sub Private Sub subtest(ByRef x as integer,y as integer,z as integer) x=2*z: y=3*z :z=x+y 2019/4/5
例2 Private Sub Form_Click() Dim a as integer,b as integer,c as integer a=1:b=2:c=3 Call subtest(a,b,c) Print a,b,c End sub Private Sub subtest(ByVal x as integer,y as integer,z as integer) x=2*z: y=3*z :z=x+y 2019/4/5
实例3 Private Sub Command1_Click() Dim a(10) as integer,x as integer For i=1 to 10 a(i)=8+I Next X=2 Print a(f(x)+x) End sub Function f(x as integer) x=x+3 f=x End function 2019/4/5
数组参数的传递 只能通过传址方式传递 在实参和形参中写数组名,忽略维数的定义,但圆括号不能省 Sub Command1_Click() 例:编一函数tim,求任意一维数组中各元素之积。 调用tim,求 和 Function tim(a() As Integer) t = 1 For i = Lbound(a) To Ubound(a) t = t * a(i) Next i tim = t End Function Sub Command1_Click() Dim a%(1 To 5),b%(3 To 8) … t1 = tim(a()) t2 = tim(b()) Print t1, t2 End Sub 2019/4/5
数组倒序存储 Private Sub Form_Click() Call jiaohuan (a()) End Sub Private Sub jiaohuan (a() As Integer) n = UBound(a) For i = 1 To n \ 2 t = a(i): a(i) = a(n - i + 1): a(n - i + 1) = t Next i For i = 1 To n p = p & a(i) & " " Text2.Text = p 2019/4/5
使用过程注意事项 1. 确定自定义的过程是子过程还是函数过程 函数过程名有值,子过程名无值。 2.过程中形参的个数和传递方式的确定 过程中参数的作用是实现过程与调用者的数据通信。 (1)从主调程序获得初值,值传递。 (2)将结果返回给主调程序,地址传递。 3. 实参与形参结合时对应问题 个数、类型、位置、次序一一对应。 形参是地址传递,对应实参可以是简单变量、数组、记录类型、对象。 形参是值传递, 对应实参可以是表达式、常量 。 注意:数组、记录类型、对象只能是地址传递。 2019/4/5
实验 编写程序,计算n!的function过程和sub过程,要求两种过程都必须有,调用这两个过程,计算1!+2!+3!+…+10!之和 2019/4/5
实验 编写程序输出200~500范围内所有素数 利用Function函数过程,判断一个数据是否为素数,若是,返回1,否则返回0。 2019/4/5
实验 编写程序,利用随机函数生成一个6 行6列的整型数组(数据在100内),找出某个指定行内最大元素所在的列号 要求:求指定行内最大元素所在的列号用Function过程实现。然用InputBox函数输入行号,调用Function过程后输出该行最大元素的列号。 2019/4/5
实验 编写程序,把任意十进制整数n转换成二进制数 2019/4/5
过程的嵌套调用 过程的嵌套调用:被调用过程又调用了其他过程 【例】计算组合数。 【思路】利用过程嵌套调用 Private Function fact(n As Integer) Dim i As Integer, f f = 1 For i = 2 To n f = f * i Next i fact = f End Function Private Function comb(n As Integer, m As Integer) comb = fact(n) / (fact(m) * fact(n - m)) Private Sub Command1_Click() Dim m As Integer, n As Integer m = Val(Text1) n = Val(Text2) Text3.Text = comb(n, m) End Sub 过程的嵌套调用:被调用过程又调用了其他过程 2019/4/5
过程的递归调用 递归调用:一个过程直接或间接的调用自己 用自身的结构来描述本身就称为“递归” n! = n (n-1)! VB允许一个自定义子过程或函数过程在过程体的内部调用自己,这样的子过程或函数称为递归子过程或递归函数 递归调用:一个过程直接或间接的调用自己 2019/4/5
求n!的函数 1 n=1 n!=n*(n-1)! fact(n)= n*fact(n-1) n>1 …… Public Function fact(n) As Long If n = 1 Then fact = 1 Else fact = n * fact(n-1) End if End Function 2!=2*1! 1!=1 2019/4/5
递归构成:必须有递归结束的条件及结束的趋势 由于自调用过程在函数内必须设置某些条件,当条件成立时终止自调用过程,并使程序控制逐步从函数中返回。 如: n 等于4,则递归调用过程如下; 3 * fact ( 2) 2 * fact ( 1 ) 1 2 * 1 2 3 * 2 6 fact (4) = 4 * fact ( 3 ) 4 * 6 24 递归构成:必须有递归结束的条件及结束的趋势 2019/4/5
例:求两个正整数的最大公约数 Public Function gcd(m As Integer, n As Integer) As Integer If (m Mod n) = 0 Then gcd = n Else gcd = gcd(n, m Mod n) End If End Function Private Sub Form_Click() Print gcd(10, 4) End Sub 分析:求最大公约数的算法思想: (1)对于已知两数m,n,使得m>n; (2) m除以n得余数r; (3)若r=0,则n为最大公约数结束;否则执行(4); (4)mn,nr,再重复执行(2)。 2019/4/5
函数的功能是___________________ 144 Sub Command1_Click() Print f(100,8) End Sub 本程序,运行的结果是______, 函数的功能是___________________ 144 将100转换为八进制数。 n=12,r=8 n=1,r=8 n=0,r=8 n=100,r=8 Function f(n,r) If n<>0 then f = f(n\r,r) print n Mod r; End if End Function Function f (n,r) If n<>0 then f = f(n\r,r) print n Mod r; End if End Function Function f (n,r) If n<>0 then f = f(n\r,r) print n Mod r; End if End Function Function f (n,r) If n<>0 then f = f(n\r,r) print n Mod r; End if End Function 2019/4/5
过程的作用域 窗体/模块级 Private 全局级 Public 应用程序 .vbp文件 窗体模块 .frm文件 标准模块 .bas文件 事件过程(Sub) 子过程(Sub) 函数过程(Function) 子过程(Sub) 函数过程(Function) 2019/4/5
窗体模块 每个窗体对应一个窗体模块。 窗体模块包含三部分 通用声明部分 事件过程部分 自定义过程部分 2019/4/5
过程作用域 作用域:变量、过程随所处的位置不同,可被访问的范围。 过程的作用域 模块级:加Private关键字的过程,只能被定义的窗体或模块中的过程调用。 全局级:加Public关键字(缺省)的过程,可供该应用程序的所有窗体和所有标准模块中的过程调用。 2019/4/5
本例示意性说明全局级过程的调用 在Form1窗体中建立area函数过程 Public Function area(r As Single) area = 3.14 * r * r End Function 在标准模块中建立perimeter函数过程 Public Function perimeter(r As Single) perimeter = 2 * 3.14 * r 在Form2窗体中调用上面两个函数过程 Text2 = Form1.area(r) Text3 = perimeter(r) 2019/4/5
变量的作用域 变量的作用域 局部变量:在过程内声明的变量,只能在本过程中使用。 模块级变量:在“通用声明”段中用Dim语句或用Private语句声明的变量,可被本窗体/模块的任何过程访问。 全局变量:在“通用声明”段中用Public或Global语句声明的变量,可被本应用程序的任何过程或函数访问。 2019/4/5
例:一个窗体文件中不同级的变量声明 Public Pa As integer ' 全局变量 Private Mb As string *10 ' 窗体/模块级变量 Sub F1( ) Dim Fa As integer ' 局部变量 … End Sub Sub F2( ) Static Fb As Single ' 静态变量 2019/4/5
变量的作用域 若在不同级声明相同的变量名,系统按次序访问如下 : 局部-->模块-->全局 Public Temp As integer ' 全局变量 Sub Form_Load() Dim Temp As Integer ' 局部变量 Temp=10 ' 访问局部变量 Form1.Temp=20 ' 访问全局变量必须加窗体名 Print Form1.Temp, Temp ' 显示 20 10 End Sub 2019/4/5
变量作用域 例1 Option Explicit Dim a As Integer Sub Command1_Click() 变量作用域 例1 请判断该程序运行后输出的结果是 先单击按钮1再单击按钮2: 先单击按钮2再单击按钮1: 单击按钮1两次再单击按钮2: Option Explicit Dim a As Integer Sub Command1_Click() Dim b As Integer a=a+1 b=b+1 End Sub Sub Command2_Click() Print a, b a b 窗体级变量 局部变量 ‘b变量未声明,程序出错 Dim b As Integer 2019/4/5
变量的生存期—动态变量与静态变量 局部变量声明: Dim 声明,随过程的调用而分配存贮单元,每次调用都对变量 初始化;过程体结束,变量的内容自动消失,存储单元释放。 Static 声明,每次调用过程,变量保持原来的值。 声明形式: Static 变量名 [AS 类型] Static Function 函数过程名([参数列表]) [As 类型] Static Sub 子过程名[(参数列表)] 过程名前加Static,表示该过程内的局部变量都是静态变量。 2019/4/5
变量的生存期—动态变量与静态变量 Private Sub Form_Click() Dim i% ,isum% For i = 1 To 5 isum = sum(i) Print isum Next End Sub Private Function sum(n As Integer) Dim j As Integer j = j + n sum = j End Function Static j As Integer,结果? 结果为:1,2,3,4,5 2019/4/5
变量作用域 例2 a b 请判断该程序运行后输出的结果是 Dim a As Integer Sub Command1_Click() 变量作用域 例2 请判断该程序运行后输出的结果是 单击按钮1: 单击按钮1两次: 单击按钮1三次: Dim a As Integer Sub Command1_Click() static b As Integer a=a+1 b=b+1 Print a, b End Sub a b 窗体级变量 静态变量 2019/4/5
下列程序的输出结果是? a b c a b c Dim a As Integer Private Sub Form_Click () dim b,c a=1: b=2 : c=3 For i = 1 to 3 Call test Next i Print a, b, c End Sub Sub test() dim c Static b a = a+1 b = b+1 c = c+1 print a, b, c 窗体级变量 局部变量 a b c 1 2 3 4 2 3 a b c 窗体级变量 静态变量 局部变量 1 2 1 3 2 1 4 3 1 2019/4/5
验证哥德巴赫猜想 验证哥德巴赫猜想:“任何一个大于6的偶数,均可以表示为两个素数之和”,从键盘上输入一个大于6的偶数后,打印出所有分解结果 2019/4/5
常用算法(3) 1、有序数组中元素的插入操作 k [注意] 要执行插入操作的数组在声明的时候要注意数组大小的合法性 条件:操作的是有序数组,插入后该数组仍然有序 实现方法如下: Dim a%(1 To 10) 11 13 27 38 45 49 65 76 97 55 k 11 13 27 38 45 49 55 65 76 97 [注意] 要执行插入操作的数组在声明的时候要注意数组大小的合法性 2019/4/5
常用算法(3) Dim a(10) As Integer For i = 1 To 9 a(i) = Val(InputBox("输入数据")) Text1 = Text1 & a(i) & " " Next x = Val(InputBox("输入要插入的数")) For k = 1 To 9 If x < a(k) Then Exit For Next k For i = 9 To k Step -1 a(i + 1) = a(i) a(k) = x Text2 = Text2 & a(i) & " " 查找x的插入位置 将大于x的数据依次 往后移动一个位置
改进:找位置的同时实现数据移动 Private Sub insert(a%(), x%) For i = 9 To 1 Step -1 If a(i) >= x Then a(i + 1) = a(i) Else Exit For End If Next a(i + 1) = x End Sub 找插入位置 ‘插入数据 2019/4/5
2.有序数列的删除 ReDim a(1 To 9) k 条件:操作的是有序数组,删除后该数组仍然有序 实现方法如下: 4 7 10 13 16 19 22 25 k 1 4 7 10 16 19 22 25 2019/4/5
常用算法(3) Dim a(10) As Integer For i = 1 To 10 a(i) = Val(InputBox("输入数据")) Text1 = Text1 & a(i) & " " Next i x = Val(InputBox("输入要删除的数")) For k = 1 To 10 If x = a(k) Then Exit For Next k For i = k To 9 a(i) = a(i + 1) For i = 1 To 9 Text2 = Text2 & a(i) & " " 查找到预删除数据的位置k 从k+1到10位置上的数据向前移动 2019/4/5
鼠标和键盘 鼠标事件 MouseUp事件: 当鼠标的任意一个键释放时被触发 MouseMove事件: 当鼠标指针移动时被触发 注意:鼠标事件发生在什么对象上,是窗体上还是控件上。 MouseDown事件: 当按下鼠标的任意一个键时被触发 MouseUp事件: 当鼠标的任意一个键释放时被触发 MouseMove事件: 当鼠标指针移动时被触发 以发生在窗体上的事件过程为例: Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) Sub Form_MouseUp(Button As Integer, Shift As Integer, Sub Form_MouseMove(Button As Integer, Shift As Integer, 2019/4/5
Sub Form_MouseMove(Button As Integer, _ Shift As Integer, X As Single, Y As Single) Button 十进制 常 量 鼠 标 键 没有按下任何键 1 vbLeftButton 鼠标左键 2 vbRightButton 鼠标右键 4 vbMiddleButton 鼠标中键 Shift 十进制 常 量 键状态 1 vbShiftMask 按下Shift 2 vbCtrlMask 按下Ctrl 3 vbAltMask 按下Alt 4 vbShiftMask+vbCtrlMask 同时按下Shift和Ctrl 5 vbShiftMask+vbAltMask 同时按下Shift和Alt 6 vbCtrlMask+vbAltMask 同时按下Ctrl和Alt 7 vbShiftMask+vbCtrlMask+vbAltMask 同时按下Shift、Ctrl和Alt 2019/4/5
鼠标和键盘 键盘事件 KeyPress事件过程:用户按下一个ASCII码时被一直触发 (包括数字、大\小写字母、Enter、BackSpace、Esc、Tab键等) Sub Form_KeyPress(KeyAscII As Integer) 参数说明: KeyAscII:与按键相对应得ASCII码。 当控制焦点处于某个对象时,用户按下任一键触发KeyDown事件过程;用户释放该键时被触发KeyUp事件过程。 Sub Form_KeyDown(keycode As Integer, shift As Integer) Sub Form_KeyUp(keycode As Integer, shift As Integer) 参数说明: Shift与鼠标事件过程Shift相同。 KeyCode:键盘扫描码; 2019/4/5
Sub Form_KeyPress(KeyAscII As Integer) …… End Sub 注意:键盘不管处于大小写,字母键的KeyCode是相同的 Sub Form_KeyPress(KeyAscII As Integer) …… End Sub Sub Form_KeyUp(KeyCode As Integer,Shift As Integer) …… End Sub 键(字符) KeyCode KeyAscII A &H41 a &H61 5 &H35 % &H25 1(主键盘) &H31 1(数字键盘) 2019/4/5
鼠标和键盘 窗体的: 控件的: KeyPress KeyPress 当窗体的KeyPreview为True焦点在控件上输入 KeyDown KeyUp 控件的: KeyPress KeyDown KeyUp 当窗体的KeyPreview为True焦点在控件上输入 先触发窗体的以上某个事件 具有焦点控件的以上某个事件再被触发 2019/4/5
鼠标和键盘 假定窗体KeyPreview为True,并有下面事件过程, 则当文本框中输入“1”时,实际上是得到的是“3” Sub Form_KeyPress(KeyAscII AsInteger) KeyAscII = KeyAscII + 1 End Sub Sub Text1_KeyPress(KeyAscII As Integer) KeyAscII = KeyAscII + 1 End Sub ?窗体KeyPreview为False时 在文本框中输入“1”时,实际上得到的是什么。 2019/4/5
编写程序,要求只能在text控件中输入数字 鼠标和键盘 编写程序,要求只能在text控件中输入数字 If Chr(KeyAscii) < "0" Or Chr(KeyAscii) > "9" Then KeyAscii = 0 End If 只能输入大写字母 2019/4/5
重点和难点 ★确定自定义的过程是子过程还是函数过程 函数过程名有值,子过程名无值。 过程有一个返回值,则使用函数过程; 若返回多个值或无返回值,一般使用子过程。 ★过程中形参的个数和传递方式的确定 过程中参数的作用是实现过程与调用者的数据通信。 ☆从主调程序获得初值,值传递。 ☆将结果返回给主调程序,地址传递。 2019/4/5
形参是值传递,对应实参可以是表达式、常量、数组元素。 形参是地址传递,对应实参只能是简单变量。 数组、记录类型、对象只能是地址传递。 ★ 实参与形参结合时对应问题 个数、类型、位置、次序一一对应。 形参是值传递,对应实参可以是表达式、常量、数组元素。 形参是地址传递,对应实参只能是简单变量。 数组、记录类型、对象只能是地址传递。 ★ 变量的作用域问题 局部变量、静态变量、全局变量特点、作用 2019/4/5
实验 编写程序,利用Sub过程实现将任意一个十进制数n分别转换成2进制、8进制和16进制数。 要求:在文本框中得到要转换的十进制数n,在事件过程中调用Sub过程得到转换后的3个结果,在窗体上打印出结果。 2019/4/5
实验 编写sub过程,计算20个数中的最大值、最小值和平均值。并且用单击命令按钮事件过程调用sub过程,并输出计算结果。 2019/4/5
实验 编写程序,利用Sub过程,计算10个学生,3门课的平均成绩,并按平均成绩的高低,输出学生的名次和平均成绩。即先调用sub过程求平均成绩放到一个新的数组中,再将求得的平均成绩用另一个sub过程实现排序操作 2019/4/5