1、第六章 数 组【学习目的和要求】1 掌握静态数组的定义和程序设计方法2 掌握动态数组的定义和程序设计方法3 熟悉控件数组的创建和使用4 掌握数组常用算法的程序设计【学习要点】一何时需要使用数组?在程序设计过程中,如果需要对一组同类型的很多数据进行处理时,就必须使用数组进行程序设计。比如求某班级 80 个学生某门课成绩的平均分,再统计所有高于平均分成绩的学生人数。如果不用数组,通过简单变量,要么定义 80 个变量来保存学生的成绩,光赋值语句就得写 80 行,要么采用循环程序设计,定义一个变量,通过 80 次循环来接收每个学生的成绩,并计算平均分。程序段如下:Dim cj As Integer,
2、sum As Integer, ave As SingleDim i As IntegerFor i = 1 To 80cj = InputBox(“请输入第“ NextPrinta = Array(“abc“, “123“, “def“)Print a(1)End Sub分析:根据 Array 函数的功能,赋值语句 a = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)将变体型变量 a 创建成一个整型的一维数组,有 9 个元素,下标从 0 到 8(注意:默认下标的下界为 0) ,其中a(0)的值为 1,a(1)的值为 2,a(8)的值为 9。For 循环的功能是分别输出 a
3、(5)、a(4)、a(3)、a(2)的值,所以第一行输出结果为 6 5 4 3;执行赋值语句 a = Array(“abc“, “123“, “def“)后, a 被创建成一个字符串型的一维数组,有 3 个元素,下标从 0 到 2,a(1) 的值为“123“,所以第二行输出结果为 123。例 6-5 执行以下程序段,输出结果是 _。Private Sub Form_Click()Dim a(9) As VariantDim i As Integera = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)Print a(2)End Sub分析:由于 Array 函数只能给变体型简
4、单变量或变体型动态数组赋值,虽然程序中定义的 a(9)是变体型数组,却是固定大小的数组,所以上述程序运行时会出错。考虑一下:如果将上述程序改成以下程序段,输出结果是多少?Dim a() As VariantDim i As Integera = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)Print a(2)注意:Array 函数只能生成一维数组,不能生成多维数组; Array 函数只能给变体型变量和变体型动态数组赋值,赋值时有多少个数值,生成的一维数组就有多少个元素,数组的类型由数值的类型决定。3数组相关语句对于数组的相关语句,只需要大家掌握 Erase 语句的功能。例
5、6-6 执行以下程序段,输出结果是 _。Option Base 1Private Sub Command1_Click()Dim a, b(5) As Integera = Array(1, 2, 3, 4, 5)b(2) = a(a(1) + 2)Print a(2), b(2)Erase a, bPrint b(2)Print a(2)End Sub分析:程序中定义变量 a 为变体型变量,b 为一维数组,有 5 个元素,下标从 1 到 5。执行语句 a = Array(1, 2, 3, 4, 5)后将 a 创建成一维数组,有 5 个元素,值分别为 1、2、3、4、5;因为 a(1)值为 1
6、,所以语句 b(2) = a(a(1) + 2)等价于将 a(3) 的值赋给 b(2),第一行的输出结果为 2 3 。注意:Erase 语句对动态数组和固定大小数组有不同的功能。若 a 是动态数组,执行语句 Erase a 的功能是释放动态数组 a 的空间,此时 a 没有任何元素,在内存中也没有空间分配,除非用 Redim 重新定义,否则该数组不可访问;若 a 是固定大小数组,执行语句Erase a 的功能是将原来数组的值全部清空,但数组空间仍存在。由于 a 是 Array 函数创建的动态数组,b 是固定大小数组,程序中执行 Erase a, b 后,数组 a 的空间将被撤消,数组 b 的每个
7、元素值将恢复初始值 0,所以运行上述程序后,第二行的结果为 0,但执行到 Print a(2)语句时程序会出现 “下标越界”错,因为此时已经不存在数组元素 a(2)。五数组的输入输出操作数组由于其构成和存储的特殊性,非常适合用循环程序来实现其各种操作。1一维数组的输入输出例 6-7 定义一个包含 20 个元素的整型数组,让用户输入每一个值,并在窗体上打印输出。分析:假设数组名为 a,数组元素下标从 1 变化到 20,即 a(1)到 a(20),每一个数组元素都执行统一的输入和输出操作,同样的操作要执行 20 次,很显然用循环处理更方便。设一个循环控制变量,假设为 i,每个数组元素可用 a(i)
8、来表示,i 的值从 1 变化到 20,在循环体中对每一个 a(i)执行相关操作。程序如下:Private Sub Form_Click()Dim i As Integer, a(1 To 20) As IntegerFor i= 1 To 20Rem 从键盘输入 a(i)的值, i 的值从 1 变化到 20,即输入 a(1)一直到 a(20)的值a(i) = InputBox(“请输入 a(“ Next i Next 语句完成给循环变量 i 增加步长的操作,即 i=i+1End Sub不难发现,输入和输出都是 20 次循环,还可将以上程序简化为:Dim i As Integer, a(1 To
9、 20) As IntegerFor i= 1 To 20a(i) = InputBox(“请输入 a(“ Next i 这样利用一次循环就可以同时实现输入和输出操作。当然,以上程序段用 Do-loop 语句也可实现。程序段如下:Dim i As Integer, a(1 To 20) As Integeri = 1Do While i Sqr(n) Then n 是素数 将计数器加 1,动态数组大小增加一个,并给新增加元素赋值k = k + 1ReDim Preserve a(k)a(k) = nEnd IfNext n 输出数组元素For i = 1 To UBound(a)Print a
10、(i);If i Mod 5 = 0 Then PrintNext i归纳一下:当在程序设计中不能直接确定数组大小时,就可以采用上述的方法来实现,即将动态数组的重定义与计数器配合起来使用,这是通常的做法。七控件数组1控件数组元素的访问如果程序界面上有很多同类型的控件,并且他们需要执行相同或相似的操作,就可以考虑使用控件数组。控件数组中,每个元素都是指定类型的控件,其所有的属性、方法和事件都和简单控件一样,但这时访问控件的标识符以及响应事件的过程框架会有区别,具体表现为:1)简单控件和控件数组元素的访问名称比如,在窗体上有 3 个文本框控件,如果是简单控件,其名称可能是 Text1、Text2
11、和Text3,但如果是控件数组,则它们的名称必须相同(相当于数组名) ,每个控件数组元素必须通过数组名(下标)的方式来访问。假设将 3 个文本框的名称都设置为 Text1,则 3个文本框将分别是 Text1(0)、Text1(1)和 Text1(2),其中元素的下标可以在属性窗口中设置其 Index 属性。注意:普通控件也有 Index 属性,但值为空,不能设置任何数值;控件数组元素的Index 属性代表其下标,值为大于等于 0 的数值。2)属性和方法的使用虽然控件数组的每个元素和简单控件具有相同的属性和方法,要注意引用属性和方法时,所有控件数组元素必须带下标。比如普通的文本框控件 Text1
12、,引用其 Text 属性可以用 Text1. Text,执行设置焦点的方法可以用语句 Text1.Setfocus 实现;而对于文本框控件数组元素 Text1(0),则必须用Text1(0) . Text 和 Text1(0) . Setfocus 实现。3)事件过程的框架对于一个控件数组来讲,数组中的每个元素都响应同一个事件过程,但其事件过程框架会比简单控件对应的事件过程多一个参数 Index。比如一个名称为 Text2 的文本框的 Change 事件过程框架为:Private Sub Text2_Change()End Sub对于上面的文本框控件数组 Text1,它有 3 个元素,分别是
13、Text1(0)、Text1(1)和Text1(2),它们响应的 Change 事件过程框架为:Private Sub Text1_Change(Index As Integer)End Sub因为 3 个控件元素都响应同样的事件过程,所以为了知道在事件发生时,究竟是哪个控件响应了该事件,控件数组的事件过程中都会比简单控件事件过程多一个参数,Index 返回响应事件的数组元素的下标。比如 Index 值为 0 时,表示 Text1(0)元素引发了该事件,Index 值为 2 时,表示 Text1(2)元素引发了该事件。很多时候,在控件数组的事件过程的处理都是和多分支结构的程序配合完成。例 6-
14、14 在窗体上有一个文本框和三个单选按钮,单选按钮构成控件数组 op1,下标分别为 0、1、2,要求实现程序运行时,根据单选按钮的选中情况改变文本框的字体。界面如下图 5-1 所示:图 5-1按上述要求完善以下程序:Private Sub Op1_Click (Index As Integer)Select Case (1) Case 0Text1.FontName = “宋体 “Case 1Text1.FontName = “黑体 “Case (2) Text1.FontName = “隶书 “End SelectEnd Sub分析:3 个单选按钮构成一个控件数组,当单击“宋体”单选按钮 o
15、p1(0),将文本框字体设置为“宋体” ,当单击“黑体”单选按钮 op1(1),将文本框字体设置为 “黑体” ,依次类推。每个单选按钮都响应同一个事件过程,在 Index 参数中记录了响应事件的控件下标。很显然,Select Case 语句中需要根据 Index 识别是哪个控件响应了该事件,所以(1)应该填入Index,填空(2)中应该填入 2 或 Else。考虑一下:上述事件还可以改为以下程序段实现,效果是一样的。Private Sub Op1_Click(Index As Integer)Text1.FontName = Op1(Index).CaptionEnd Sub八数组相关算法程序
16、设计1数组的排序假设已将 n 个数存放在数组中,要求将其按从小到大的顺序排序。排序是程序设计中很重要的算法,排序的算法有很多,这里介绍两种常见的算法:选择法和冒泡法。1)选择法排序选择法的排序思想是:a、 将第一个数依次与后面的 n-1 个数进行比较,如果有一个数比它小,就执行交换操作,经过这一轮比较,产生最小数放在第一个数中。b、 将第二个数再依次与后面的 n-2 个数进行比较,如果有一个数比它小,就执行交换操作,经过这一轮比较,产生第二小的数放在第二个数中。c、 重复执行以上操作,最后是第 n-1 个数和第 n 个数进行比较,如果后者小于前者,执行交换,否则保持原值。从以上的排序过程可以看
17、出:n 个元素排序要进行 n-1 轮的比较,每一轮中要进行若干次比较,所以排序的算法是一个双重循环。若用循环变量 i 表示比较的轮数,则 i 的值从 1 变化到 n-1,当 i=1 时, a(1)分别与 a(2)到 a(n)的每一个元素进行比较,一旦有元素比 a(1)小就执行交换;当 i=2 时, a(2) 分别与 a(3)到 a(n)的每一个元素进行比较,一旦有元素比 a(2)小就执行交换;所以在第 i 轮的比较中, a(i)分别与 a(i+1)到 a(n)的每一个元素进行比较,一旦有元素比 a(i)小就执行交换。若用 j 表示内循环的控制变量,则 j 的值是从 i+1 变化到 n。因为涉及
18、到交换,所以还需定义变量 Temp 作交换时的临时变量。例 6-15 随机产生 10 个三位正整数,用选择法将其按增序排序,并输出原始顺序和排序后的顺序。 (分析见上面)程序如下:Option Base 1Private Sub Form_Click()Dim a(10) As Integer, i As Integer, j As IntegerDim temp As IntegerRandomizePrint “原始顺序:“For i = 1 To 10a(i) = Int(Rnd * 900) + 100 利用随机函数产生数组元素的值Print a(i);Next iPrintFor i
19、 = 1 To 9 10 个元素,外围循环需要 9 次For j = i + 1 To 10 每次 a(i)都和其后面的每个元素作比较If a(i) a(j) Then 如果 a(i) a(j)就执行交换temp = a(i)a(i) = a(j)a(j) = tempEnd IfNext jNext iPrint “排序后的顺序:“For i = 1 To 10Print a(i);Next iEnd Sub优化一下: 从上面排序的执行可以看出,在第 i 轮的比较中,只要有一个数比它小就执行交换,实际上这里有很多交换是无效的,因为每一轮比较中,a(i) 只需与当前的最小元素执行一次交换既可,
20、无需发现比它小的数就执行交换。因此可将选择法优化一下,过程如下:a、 对有 n 个数的序列,从中选出最小的数,与第 1 个数交换。b、 除第 1 个数外,其余 n-1 个数再按 a 的方法选出最小的数,与第 2 个数交换。c、 重复以上操作,直到最后只剩下一个数时结束。假设数组 a 中有 5 个元素,下标从 1 到 5,分别被赋值为 12、23、15、34、9。排序的执行过程如下所示:(下划线部分表示执行交换的两个数组元素)原始数据 12、23 、15、34、9a(1) a(2) a(3) a(4) a(5) 第 1 遍交换后 9 、23 、15、34、12 a(2) a(3) a(4) a(
21、5) 第 2 遍交换后 9 、12 、15、34、23 a(3) a(4) a(5) 第 3 遍交换后 9 、12 、15 、34、23a(4) a(5) 第 4 遍交换后 9 、12 、15 、23 、34 可以看出,优化的选择法执行外层和内层的循环次数完全一样,只不过在每轮比较中,优化的选择法不会频繁执行交换操作,在数据量比较大时可提高运行效率。优化的选择法在每轮比较中,是找出该轮的最小值后再进行交换。若用循环变量 i 表示比较的轮数,在第 i 轮的比较中,只需记录 a(i)到 a(n)中的最小元素所在的下标 k,如果 k 不等于 i,则将 a(k)与 a(i)交换,否则说明 a(i)中就
22、是最小值,不需交换,所以每轮比较中最多执行一次交换。所以上述排序部分的程序段可优化如下:For i = 1 To 9k = i 将本轮第一个元素下标赋给 kFor j = i + 1 To 10只要发现有元素的值比 a(k)小,就用 k 记录最小元素所在的下标If a(k) a(j) Then k = j Next j 如果 a(i)不是本轮最小元素,将 a(i)与本轮最小元素 a(k)交换If k a(j + 1) Then 相邻两数进行比较,如果前者大于后者就交换temp = a(j)a(j) = a(j + 1)a(j + 1) = tempEnd IfNext jNext iPrint
23、 “排序后的顺序:“For i = 1 To 10Print a(i);Next iEnd Sub2数组元素的插入例 6-17 假设有一整型数组 a,已经按递增顺序排好序,现输入一个数据,要求将其插入到数组中,并保证插入后数组仍然有序。分析:在有序数组中插入元素需要两步来完成:1)查找插入的位置将要插入的数与数组元素不断的进行比较,以确定插入的位置2)在指定的位置上插入数据假设数组 a 有 n 个元素,插入的元素放在变量 x 中,要注意数组大小应定义为 n+1,以保证接收插入元素后不会发生溢出。插入算法思想是:从后向前,将 x 与 a(n)、a(n-1)等分别进行比较,查找插入的位置。(设循环
24、变量定义为 i)插入的情况可能有 3 种:1)x 是最大值,即 x=a(n)成立,则将 x 放在元素 a(n+1)中,完成插入2)x 应插在中间数组下标从 n 开始,逐一进行比较,即如果 x= a(9) Then 如果 x 值最大a(10) = xElseFor i = 9 To 1 Step -1If x 28,这时找到 x 的插入位置,x 应该插入到其下一个位置上,这时跳出循环(注意:跳出循环时 i 的值为 5) 。6)将 x 的值赋给 a(6)。考虑一下:上述程序段可否将 For i = 9 To 1 Step -1 循环改成 For i = 1 To 9?答案是不可以,考虑一下为什么?
25、3直接插入排序法插入算法的思想在数组排序处理中应用就是“直接插入排序”算法。如果要将数组按从小到大的顺序来排序,直接插入排序算法的基本思想是:如果数组只有一个元素,那么数组肯定是有序的,在这个前提下,从数组的第 2 个元素开始,按从小到大的顺序在前面的数组元素中找其插入的位置,将该元素插入到指定位置上,由于每插入一个元素都是按从小到大的顺序,所以当所有的元素插完后,数组已经是有序的了。例 6-18 随机产生 10 个三位正整数,用直接插入排序法将其按从小到大排序,并输出原始顺序和排序后的顺序。直接插入排序的程序如下:Private Sub Form_Click()Dim a(10) As In
26、teger, i As Integer, j As IntegerDim t As IntegerRandomizePrint “原始顺序:“For i = 1 To 10a(i) = Int(Rnd * 900) + 100Print a(i);Next iPrintFor i = 2 To 10t = a(i) 每次要插入的元素For j = i - 1 To 1 Step -1 从后向前找插入的位置If t a(mid),说明 x 在区间的右边,让 left =mid+13)如果 x right 为止,当left right,说明 x 在数组中不存在例 6-20 假设在窗体的通用声明段定
27、义数组 a(10),下标从 1 到 10。运行时,单击命令按钮 Command1,实现给数组元素赋值并将数组按从小到大的顺序排序;单击命令按钮Command2,从键盘上输入数值赋给变量 X,用二分法在数组中查找 X,若找到,给出 X 在数组中所在的位置;若找不到,给出“找不到”的信息。程序段如下:Option Base 1Dim a(10) As IntegerPrivate Sub Command1_Click( ) 程序省略,实现给数组元素赋值并将数组按从小到大的顺序排序End SubPrivate Sub Command2_Click() 查找元素Dim x As Integer, Mi
28、d As IntegerDim Left As Integer, Right As IntegerDim f As Booleanx = InputBox(“请输入要查找的数“)设置初始查找的区间Left = 1Right = UBound(a)Do While Left a(Mid) Then 说明 x 在区间的右边Left = Mid + 1Else 说明 x 在区间的左边Right = Mid - 1End IfLoopIf f ThenText2 = “要查找的数“ Next iPrintx = InputBox(“请输入要删除的数 “)For i = 1 To 20If a(i) =
29、 x Then Exit For 找到要删除的元素后跳出循环(删除元素为第 i 个元素)Next iFor j = i To 19 将要删除元素 a(i)后的每个元素依次前移a(j) = a(j + 1) 将元素依次前移,删除第 i 个元素Next jReDim Preserve a(19) 删除元素后,重新定义数组大小For i = 1 To 19Print a(i);Next iEnd Sub2)将数组中所有相同元素全部删除这部分算法分析,书中有详细的说明,见书 P113 的算法说明程序段如下:ub = UBound(a)n = 1Do While n = ubi = n + 1Do Wh
30、ile i = ub If a(n) = a(i) ThenFor j = i To ub - 1a(j) = a(j + 1)Next jub = ub - 1ReDim Preserve a(ub)Elsei = i + 1End IfLoopn = n + 1Loop注意:本题不可以用 ForNext 循环完成!6矩阵的转置二维数组在逻辑上是一个二维矩阵,对矩阵的操作有很多,转置是最常见的操作。矩阵的转置操作是将矩阵的行和列交换,比如原来 4 行 5 列的矩阵经转置后将变成 5 行 4 列,原来的行变成列,列变成行。例 6-22 设有一个 3 行 4 列的矩阵,要求输出原矩阵和转置后矩阵
31、的内容。分析:假设将矩阵内容存储在数组 a(3,4)中,由用户输入,将矩阵转置后存储在数组 b(4,3)中。转置实际上是将行列对调,如:原来 a(1,2)的值应赋给 b(2,1),a(3,4) 的值应赋给b(4,3),即 a(i,j) 的值应赋给 b(j,i)。程序如下:Private Sub Form_Click()Dim a(3, 4) As Integer, b(4, 3) As IntegerDim i As Integer, j As IntegerPrint “原矩阵为:“For i = 1 To 3For j = 1 To 4a(i, j) = InputBox(“输入 a(“
32、Next jPrintNext iPrint “转置后的矩阵为:“For i = 1 To 4For j = 1 To 3Print b(i, j);Next jPrintNext iEnd Sub如果数组是方阵,只要将原来数组中a(i,j)和a(j,i)元素交换,还可以不通过另一个数组直接实现数组元素的转置。程序代码如下:Private Sub Form_Click()Dim a(3, 3) As Integer, temp As IntegerDim i As Integer, j As IntegerPrint “原矩阵为:“For i = 1 To 3For j = 1 To 3a(i, j) = InputBox(“输入a(“ Next j