Excel精英培训网

 找回密码
 注册
数据透视表40+个常用小技巧,让你一次学会!
查看: 83013|回复: 216

[VBA] VBA入门学习(2011年5月2日更新结束)

  [复制链接]
发表于 2011-4-7 21:33 | 显示全部楼层 |阅读模式
本帖最后由 青城山苦丁茶 于 2011-5-2 13:01 编辑

    在大家的鼓励与支持下终于断断续续地于2011年5月2日更新结束,后面还有内容只好在有空时再另发新贴了。
    欢迎大家发新贴或跟贴交流讨论!
    谢谢大家!
------------------------------------分-------界-------线------------------------------------
    写在前面的废话:

    VBA,他有什么能耐?我们为什么要学习他?是不是真的“除了不会生孩子外,什么都会”?
    不是的,什么都不会是万能的,“寸有所长,尺有所短”。个人常讲的:从A处到B处,步行、乘汽车、乘火车、乘飞机一个比一个先进、一个比一个优越,当A是成都B是北京时肯定如此,然而A和B是一个家属院的两幢楼时大约就不是这样了吧?哪么我们在什么时候使用他?个人认为有以下情况时我们可以考虑看能否用VBA解决:
当我们仅仅在电脑上从事机械的、重复的简单的体力劳动的时候;
当我们在表格中写入的函数太多而致使改动一个数据,去泡了一杯茶过来电脑都还没有运算完的时候;
当我们需要在不同的文件间进行数据交换的时候;
当我们需要进行实时监控的时候
……
    总之就是我们用电脑解决一些有规律的,我们用函数、技巧解决起来却很麻烦,或很耗时,或不太好解决甚至不能解决的问题,就可以考虑用VBA能否方便快捷地完成。
    那么VBA难吗?看到的VBA代码都是些鸟文,俺不懂鸟文,能学会吗?
    VBA难,担不会比E中的函数难,能学会E中的函数的人都应该能学会V,并且绝对比用E中的数组公式简单。懂不懂鸟文与学得会学不会V关系不大,不会有多少人E中的每个函数都能默写出来吧?对E中的函数我是仅仅记住了常用的那几个,其余的要用到时再到帮助中去找。V同样如此,有时整段整段的代码都来自帮助,稍改下就成了自己的了
    在进入下面的学习以前,请暂时忘掉你原有的excel函数、技巧等等吧。此贴是为完全不懂VBA、完全不懂程序设计的人而写的,否则还是请绕道,他会浪费你很多时间和精力。学VBA的要点是动手动脑,然后是再动手动脑,最后还是动手动脑。这里的动手,是指录入代码(哪怕是照抄别人的,都得自己手录入,而绝不能复制粘贴)、改代码( 阅读理解别人代码的基础上进行功能改、扩等的改写)、写代码;动脑除了常规意义上的思考等以外,此处还包括阅读理解程序,阅读的时候大脑模拟电脑执行过程,要知道每到一处的时候各变量、参数的值是什么、处于何种状态。
------------------------------分-------界-------线----------------------------


VBA程序设计程序设计的基础知识:
一、计算机解决问题的步骤
计算机解决问题的步骤:一般来说,在运用计算机程序解决一个具体的实际问题时,大致经过以下几个步骤:
首先建模:分析问题,从具体问题中找出一个适当的数学模型
其次确定算法:然后设计一个解决数学模型的算法,
最后编制程序并进行测试调整,直到得出最终答案。
由于每一步不见得都能得到正确的方向,所以在每一步都可能返回到前一步甚至前几步。图形如下:
可见建立的模型不同(整得这么神秘,什么建模了、算法了,其实就我们对问题的想法,完成问题的具体步骤),最终编制出来的程序也就不同。
我们先来看一个经典的问(鸡兔同笼问题)有若干只鸡兔同在一个笼子里,从上面数,有35个头;从下面数,有94只脚。问笼中各有几只鸡和兔?
分析一:设兔子的数量为X,则鸡的数量为35-X,那么脚的数目就为4X+2(35-X)。我们就可以让X从0开始逐步增加,只要使4X+2(35-X)=94,就得到了兔子和鸡的数目。这就是所谓的建模。
确定算法:
①输入头的数目a,脚的数目b
②x= 0
③如果4X+2(a-X)不等于b,则x增加1,重复③;否则进入④
④输出兔子的数量X,则鸡的数量35-X
⑤结束
Sub jttl()
Dim a AsInteger, b As Integer
Dim x AsInteger
a =InputBox("a=", "请输入头的数目")
b = InputBox("b=","请输入脚的数目")
x = 0
Do Until 4 *x + 2 * (a - x) = b
    x = x + 1
Loop
MsgBox "有兔" & x & "只,有鸡" & 35 - x & "只"
End Sub
分析二(换一个思路,另建个模):设兔子的数量为X,鸡的数量为Y,头的数目为a,脚的数目为b,则有X+Y=a,4X+2Y=b,解方程组可得X=b/2-a,Y=2a-b/2
确定算法:
①输入a和b的值
②计算x= b/2-a
③计算y=2a-b/2
④输出x、y的值
⑤结束
编制程序:
Sub jttl ()
Dim a AsInteger, b As Integer
dim x AsInteger, y As Integer
a =InputBox("a=", "请输入头的数目")
b =InputBox("b=", "请输入脚的数目")
x = b / 2 - a
y = 2 * a - b/ 2
MsgBox "有兔" & x & "只,有鸡" & y & "只"
End Sub
以上的程序可以暂时不必搞清楚(后面我们还要讲此例),只是体现出看待问题的角度不同就有不同的算法,相应的程序也是不同的。同时也反应出程序实际就是一组机器操作的指令或语句的序列,是对算法(解决问题的步骤)的一种描述而已。因此编程就是将算法(解决问题的步骤)翻译成对应的代码,只是注意算法的每一个步骤对应着一个或多个语句,每个语句对应着一个或多个操作。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

评分

参与人数 48 +429 金币 +20 收起 理由
神隐汀渚 + 3 神马都是浮云
ruiping709 + 1 讲的太好了,对还未入门的我来说简单易懂
feedyu + 3
lslly + 3 很给力!
YANG6815475 + 3 赞一个!

查看全部评分

excel精英培训的微信平台,每天都会发送excel学习教程和资料。扫一扫明天就可以收到新教程
 楼主| 发表于 2011-4-7 21:46 | 显示全部楼层
二、VBA语言及开发环境
(一)vba语言概述
由前示例可看出,VBA程序由一条条的语句构成,这些语句大体上可以分为(如下图):
①开始/结束:必须有的,表明程序的开始与结束。Sub是必须的(当然还有function等等,以后遇到再说)。Sub后面是我们给程序的命的名称(此处为jttl,也可以命名为其它,但必须要符合命名规则,命名规则后面讲)。End Sub是自动产生的。
②变量定义:Dim 变量名 as 数据类型,有多个变量要定义时用,分开。一般都具备,但还是要看具体情况而定。
③数据输入/数据输出:输入是给程序某些信息以供运行所需,可以从键盘输入,也可以从文件、扫描仪、鼠标等等一切可以输入数据的途径,它不是必须的,这要看具体情况而定;输出是将程序运行的结果反馈给我们,它是广义的:向显示屏上输出提示信息、向音箱输出信息、向打印机输出信息、向文件输出信息等等,一般来说每个程序都应具备。
④数据处理:我们希望计算机对数据进行的运算、处理等等。
-------------------------------------------------------------------------
1、vba的基本数据类型
描述客观事物的数、字符以及所有能输入到计算机中并被计算机程序加工处理的符号的集合统称为数据。数据既可以是计算机程序处理的对象,也可以是计算机程序处理的结果。为了更好地处理各种数据,vba定义了多种数据类型(书上是这么讲的,大家可以牵强地参照单元格数据类型中的数值、日期、时间、文本等来类似理解),以下介绍几种常用的基本数据类型:
整数:Byte存储为单精度型、无符号整型、8 位(1个字节)的数值形式,范围在 0 至255 之间。Byte 数据类型在存储二进制数据时很有用
integer存储为 16位(2 个字节)的数值形式,其范围为-32768至32767的所有整数,类型声明字符是百分比符号(%)。即dim I as integer 与dimi%等效。下同


long
存储为 32 位(4 个字节)有符号的数值形式,其范围从-2,147,483,648 到 2,147,483,647的所有整数。类型声明字符为和号 (&)
小数single存储为 IEEE 32 位(4 个字节)浮点数值的形式,它的范围在负数的时候是从 -3.402823E38 到 -1.401298E-45,而在正数的时候是从 1.401298E-45 到 3.402823E38。但它只有七位有效数字。Single 的类型声明字符为感叹号 (!)。
double存储为 IEEE 64 位(8 个字节)浮点数值的形式,它能表示绝对值小于10308的数。有十五位有效数字。Double 的类型声明字符是数字符号 (#)。
字符串:string有两种:变长(最多可包含大约 20 亿 ( 2^31)个字符)与定长(可包含 1 到大约 64K ( 2^16 ) 个字符)。 String 之字符码的范围是 0 到255。字符集的前 128 个字符(0 到 127)对应于标准的 U.S. 键盘上的字符与符号。这前 128 个字符与 ASCII 字符集中所定义的相同。后128 个字符(128 到 255)则代表特殊字符,例如国际字符,重音符号,货币符号及分数。String 的类型声明字符为美元号 ($)。
逻辑型(布尔型)Boolean存储为 16 位(2个字节)的数值形式,但只能是 True 或是 False。Boolean 变量的值显示为 True 或 False(在使用 Print 的时候),或者 #TRUE# 或 #FALSE#(在使用 Write # 的时候)。使用关键字 True 与 False 可将 Boolean 变量赋值为这两个状态中的一个。当转换其他的数值类型为 Boolean 值时,0 会转成False,而其他的值则变成 True。当转换 Boolean值为其他的数据类型时,False 成为 0,而 True 成为 -1。
Variant 是一种特殊的数据类型,除了定长 String 数据及用户定义类型外,可以包含任何种类的数据(似乎是万能数据类型,从某种程度上可看作类似于单元格格式中的常规)。Variant
也可以包含 EmptyErrorNothing Null等特殊值。
其它数据类型dateCurrency等等可参考帮助文件。
-------------------------------------------------------------------------
2、vba的字符集与关键字
VBA中的字符集是由英语字母、数字和专用字符等组成,几乎包含了ASCII码字符集中的所有字符:二十六个英语字母的大小写、0至9的数字,专用字符是指在VBA中具有特殊含义的字符,如算术运算符(加+、减-、乘*、除/、整除\、乘方^)、关系运算符(>、<、>=、<=、<>、=)及各种标点符号。
关键字是VBA系统规定了一些固定的并且具有特殊含义的字符串。用户不可以改变他们,在命名宏、变量时也不可用这些关键字,如sub、dim、or等等。特征是在VBE窗口中自动将首字母变成大写、其余字母大部份是小写的就是。
-----------------------------------------------
3、常量与变量及其命名规则:
常量:声明定义时确定其值,程序在执行不始终不能更改的量,通常用于保存固定的数据。
声明方式为:const 常量名=表达式[,常量名=表达式]……
例:const pi=3.14
即声明了一个值为3.14的常量pi
变量:程序在处理数据时,对于输入 数据、参加运算的数据、运算过程中的临时性数据、运行结果等,通常需要把它们暂时存储在计算机的内存单元中,变量就是用于命名这些内存单元的。其声明方式有dim、static、public等,以下介绍dim,其余的可参考帮助。
声明方式:dim 变量名 as 数据类型[,变量名 as 数据类型]……
例:dim i as integer 或dim i%
声明一个取值范围在-3276832767的整数变量i
dim n as long,s as string 或dim n&,s$
声明一个取值范围在-2,147,483,648到 2,147,483,647的整数变量n和一个字符串变量s
注意:
①当仅用dim 变量名,即省略as 数据类型和类型声明字符时,vba会自动将其默认为Variant。当一个变量的类型为Variant时他可以存放任何类型的数据,并且该变量的实际类型随其存入的值的类型变化而变化。如
Sub test()
Dim v
‘v
Variant
v = "你好!"
‘v为
string
v = 123
‘v
integer
v = True
‘v
boolean
End Sub
输入代码后按f8键来单步执行这个代码,在本地窗口中可看到v的类型变化。
VBA提供了VarType函数来测定变量的实际数据类型,格式VarType(变量名)
②不要试图用dim i1,i2,i3as integer就将i1、i2、i3都声明为integer型。他的结果是i1、i2为Variant型,只有i3为integer型③
命名规则:程序名、变量名、常量名都需遵循以下命名规则:
①由字母、数字、下划线组成,不可以包括空格。字母不区分大小写,即i与I是相同的。
②第一个字符必须是字母或下划线
③不能与关键字和保留字相同,但允许加前缀或后缀
④尽量简明清晰,避免使用容易混淆的字母和数字
⑤尽量使用有明确含义的英语单词或汉语拼音
----------------------------------------------------------------
4、运算符、函数与表达式:
算术运算符:加(+)、减(-)、乘(*)、除(/)、整除(\)、取余(mod)、乘方(^)
说明:整除与取余运算,其操作数都必须是整数,当操作数不是整数时,系统会将其自动四舍五入转化为整数后,再进行运算。即9.8 \ 3.2与10 \ 3等效,结果都为3
比较运算符:等于(=)、大于(>)、小于(<)、大于或等于(>=)、小于或等于(<=)、不等于(<>)、对象比较(is )、字符串比较(like)
逻辑运算符:逻辑与(and)、逻辑或(or)、逻辑非(not)、逻辑异或(xor)、逻辑等价(eqr)、逻辑蕴涵(imp)
字符串运算:& 和 +,都可以将两个字符串连接起来成一个字符串。
函数:有内部函数和自定义函数两类。内部函数如trim、mid、left等字符处理函数,date、time、day等日期时间函数,val、cstr等类型转换函数,array、split、join等数组处理函数,sqr、int、sin等数学三角函数,等等,具体可以参考帮助或相关文档。自定义函数是根据需要我们自己编写的函数。调用形式都是:函数名(参数列表),其中参数列表根据不同的函数而不同,有的有多个参数,有的只有一个函数,还有的没有参数。
表达式:是由运算符、配对的括号将变量、常数、函数等以合理的形式连接起来的式子。其中括号只能是小括号,而不能象数学上用中括号、大括号。如数学上的式子                              在程序中写为(-b+sqr(b^2-4*a*c))/(2*a)。注:sqr是求平方根的内部函数,^表示乘方(指数更恰当点)运算
-----------------------------------------------------------------------
(二)开发环境
这方面的介绍浩如烟海,不再多说,仅仅给出一种启动方法:启动excel后按[alt]+[f11]即可看到vbe界面。
以上内容可以跳过,在后面的学习中有不明白的,可以返回来查看可得到原因。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

 楼主| 发表于 2011-4-7 21:48 | 显示全部楼层
本帖最后由 青城山苦丁茶 于 2011-4-7 22:44 编辑

三、三大基本程序结构
1、顺序结构:
1(再来看前面的例子):有若干只鸡兔同在一个笼子里,从上面数,有35个头(数据存放于A1单元格);从下面数,有94只脚(数据存放于A2单元格)。问笼中各有几只鸡和兔(将结果数据存放于B1B2单元格)?
前面已写了平时我们是如何解决这个问题的:
分析:设兔子的数量为X,鸡的数量为Y,头的数目为a,脚的数目为b,则有X+Y=a,4X+2Y=b,通过数学知识很容易解得X=b/2-a,Y=2a-b/2,这样一来,我们只需要完成:
①获取a和b的值
②计算x= b/2-a
③计算y=2a-b/2
④输出x、y的值
⑤结束
就这么简单。这当中①至⑤在专业的书上就称为确定算法。其中①的获取也称输入,④的输出也称返回。为更贴近程序设计,我们再将①至⑤换一种表达方式:
这种图称为流程图,看得懂嘛?下面是流程图中几种图形的意思:
表示算法的开始与结束:
表示算法中表示输入输出:
表示算法中运算与赋值:
表示算法中条件判断:
流程线:
大家要学会画流程图。后面的无论例子还是练习,都请大家先画流程图。
不是说讲VBA吗?这都是些啥子哟?不要急,把上面的搞清楚了,我们再来翻译翻译:
启动excel,按[alt]+[f11]进入VBE环境,点菜单中的插入———模块,按流程图中所示一一翻译如下:
Subjttl()  '开始
Dim a As Integer, b As Integer  '
定义变量
Dim x As Integer, y As Integer
a = Range("a1")    '获取a的值
b = Range("a2")    '
获取a的值
x = b / 2 - a      '
计算X的值
y = 2 * a - b / 2  '
计算y的值
Range("b1") = "
有兔" & x & "只"   '输出X的值
Range("b2") = "
有鸡" & y & "只"   '输出y的值
End Sub     '结束
    光标位于这段代码的任一位置,按f5,看excel表中就有结果了(当然需要先在A1和A2单元格中输入头和脚的数目)。
    就这样就完了?是的,完成了!我们再来解释一下:
    第一句:sub是必需的,我们手工输入就可以了;jttl是给我们的这段代码取的名儿,只要符合命名规则(请参考前面的内容),怎么取都可以;这两个(中间的空格不能少哈)输入好后按回车,代码名称自动被加上了一对括号、下一行自动出现end sub。括号是有作用的,但此处我们可暂时不必管他,等他空起。接下来我们在sub和end sub之间分别输入其他语句。
    第二句和第三句:就是各定义两个整型变量。程序中用到几个就定义几个。但这里的变量名取得不好,虽然不影响运行但不能见名知意。以后的代码我们得改正。
    第四句:为赋值语句,完整的写法是let变量=表达式(对应到这句应该是let a =Range("a1"),其中let可省略,故在通常情况下我们所看到的都只是 变量=表达式 ,而没有了前边的let),其功能就是将表达式的值赋给变量(因此这句就是将单元格A1的值赋给变量a)。这里的=不是等号,是赋值号,他与数学上的=完全不同(数学中的=表示相等),如i=i+1在赋值语句中是常见的(数学上这么写则是错的),他表示将变量i的值增加1后再赋给变量i,即如果运行前i的值为0则运行后i的值变成了1;特别强调赋值语句中不能将=两边的内容互换(即写成形如 b / 2 - a=x,这在数学上可以,但赋值语句中却绝不容许)。另外对这句还要说一点的是VBA对单元格值的引用方法是Range(单元格).value(如要引用单元格A1的值就写成Range("a1").value),由于默认的是取value,所以在通常情况下我们将.value也省了,就成了句子中的a = Range("a1");除了用range来引用外,还有cells(行,列) .value(.value同样可省略),如a1单元格为第一行第一列,要引用a1单元格就写成cells(1,1)或cells(1, "a")都行,f10单元格为第十行第六列就写成cells(10,6)或cells(10, "f")。第三种引用方法是[单元格],如要A1就是[a1],f10就是[f10]。也就是说a =Range("a1")、a = [a1]、a=cells(1,"a")、a = cells(1,1)这几种形式的结果是完全相同的。
    第五句与第四句解释相同。
    第六句第七句也都是赋值语句。其中的表达式应该看得懂吧?
    第八句还是赋值语句,他将表达式的值赋给单元格b1,对此的解释与第四句相同。需要啰嗦一下的可能是表达式:"有兔" & x & "只",这当中x为变量,在前面的计算中他已获得了值,到了这里他就是用&运算符将他们三者按顺序连接起来。
    第九句与第八句相同
    第十句是规定动作。在输入代码的第一句时就自动产生了这一句。
    还有一个没提到:每行后面以’开头的是注释部份,也可以单独放在一行,反正程序是不执行他的。
    怎么样?还是晕晕的?这个例子不太好,从数学角度上难点了,不适合入门,重新来个简单的:

例2:根据我们输入的单元格名称,将当前的日期时间输入到该单元格中。
    分析:VBA为我们提供了inputbox来让用户输入内容、函数now来获取当前日期和时间。需要我们做的是把这些组合起来,获取用户输入的单元格名称、获取当前日期和时间、将日期和时间写入该单元格。
为此就有了以下流程图:
剩下的事情就是将上面的流程图翻译成VBA代码:
Sub srrq()
Dim s_dz As String, d_rq As Date    '
定义变量s_dz和d_rq
s_dz = InputBox("
请输入单元格名称", , "a1") '输入地址赋给变量s_dz
d_rq = Now    '
获取当前的日期时间存入变量d_rq
Range(s_dz) = d_rq   '
将d_rq的值赋给s_dz代表的单元格
MsgBox "
日期与时间已写入" & s_dz & "单元格中"   '没实际价值,仅给个提示
End Sub
    什么?还是晕?应该是晕在两处:一是不知道输入可用inputbox、获取日期时间可用now、MsgBox可以给出提示信息,二是这三个究竟怎么个用法?其实这不是重点, VBA内置的函数是死的,常用的就那么些,都可以查阅相关的资料来获得,要不了多久就能掌握,而重点是我们要掌握编程的思想方法。在后面的介绍中我们也会沿着这一思路进行。
    练习1:已知圆柱的体积为底面积乘以高,底面积为底面圆半径的平方乘以3.14。请写一程序用于输入任意圆柱的高和半径(用两句inputbox)并计算出其体积,将半径、高、体积分别存入单元格A1至A3(输入的半径为10、高为5,则A1至A3中依次显示:半径10、高5、圆柱的体积为1570。要求画出流程图、写出代码)。
    练习2:给两个变量分别输入值,然后将这两个变量的值互换后,用msgbox显示出来。同样要求画出流程图、写出代码
    这就是书上讲的程序三大基本结构之一的顺序结构,就是按照我们写的代码的顺序,从上到下依次执行。
我们再看一例
----------------------------------------------------------------------
2、选择结构
例3:输入一个整数,显示出他是偶数还是奇数
    分析:由前面知识可知,输入整数、显示都不成问题了,但问题在于要显示出他是偶数还是奇数,这就需要我们判断一下。如何判断呢?由数学知识知道除以2余数为0的就是偶数,否则为奇数。我们还是先别管程序如何,而是写出我们日常情况下是如何做的:
①输入整数
②判断他除以2的余数,如果是0进入③,不是0则进入④
③输出“你输入的是偶数”,进入⑤
④输出“你输入的是奇数”,进入⑤
⑤结束
    看出与前面的区别了吗?画出流程图再看看:
    这下就非常明显了。与前面不同就是红色部份

我们看看翻译成VBA代码是怎样的:
Sub jopd()
Dim i AsInteger
i =InputBox("请输入一个整数", ,0)
If i Mod 2 = 0 Then
    MsgBox "
你输入的是" & i & ",他是一个偶数"
Else
    MsgBox "
你输入的是" & i & ",他是一个奇数"
EndIf
End Sub
这段代码中红色的是我们所不熟悉的,其他的前面都见过。红色部份就是我们流程图中的红色部份,这就是选择结构(有选择地执行)。下面我们来详细说说。
If 条件 then
   语句块1
Else
   语句块2
End if
    这就是选择结构的格式之一,意思是如果条件成立(书上讲是条件为真)那么程序就执行语句块1(注意此处说的是语句块,也就是说他可以只有一句也可以有多句),语句块1执行完后进入到end if 的下一行继续执行;如果条件不成立(书上讲是条件为假)那么程序就执行语句块2,语句块2执行完后进入到end if 的下一行继续执行。换句话说,在执行一次这种选择结构时语句块1和语句块2必然执行一个且只执行一个。
    验证下:点VBE菜单栏中的视图---本地窗口来调出本地窗口来。光标位于这段代码的任意位置,按键盘上的f8键。每按一下代码执行一行,这样可以很清楚地看到程序执行的情况。同时从本地窗口中可看到变量的值的情况。特别提醒:这是调试代码常用的方式之一。
    特别地:如本例中,语句块1和语句块2都只有一句,就可以写在一行当中,变成了(有的地方称之为行if语句):
If 条件 Then语句1 Else 语句2
    再说下这里的条件:这里的i Mod2是取变量i除以2的余数,=与前边讲的又不同了(前面讲的=是赋值号,功能是将=右边的值赋给=右边的变量,但在赋值语句中才这样),这是在条件语句中,在此的功能是判断=两边的值是否相等,如果相等,则条件成立(书上讲的是条件为真),否则条件不成立(条件为假)。提醒一句:条件语句,=两边的内容可以互换,互换后的不影响结果。
    补充:条件可以是单一的逻辑值,也可以是一个比较表达式(例3就是),还可以是用逻辑运算符把几个比较表达式、逻辑值连接起来的复杂表达式。如:i_age代表年龄、str_name代表姓名,则表示年龄在20以上或10以下、姓张的条件表达式为: (i_age >= 20 Or i_age<= 10) And (str_name Like "张*")
    巩固一下:写一段代码,输入一个整数,判断他是不是5的倍数。
    这个题条件是用=比较得到,比较运算符除=外,还有>、<、<>、>=、<=,分别是大于、小于、不等于、大于或等于、小于或等于。那么我们再变通出一个题:手工在B2单元格输入一个学生的成绩,写一段代码,要求运行后用msgbox来提示该成绩是及格还是不及格(60分及格)。
    选择语句除了上面提到的以外,还有以下形式(每例一格式哈):
例4:还是例3,但改为输入的是奇数则提示,否则不作任何提示。
这里就只给出核心部分,其它与前面相同:
If i Mod 2 = 1 Then
   MsgBox "
你输入的是" & i & ",他是一个奇数"
Endif
也就是说可以不要else 和语句块2,而成为:
If 条件 then
   语句块
End if
与前面还有个相同的是,如果语句块只是一句的话,可写成一行:

If
条件 then 语句
例5:输入一个整数,显示出他是正整数、负整数还是0.
将例3稍改下就可以了,看大家能理解不?
Sub zfpd()
Dim i AsInteger
i =InputBox("请输入一个整数", ,0)
Ifi > 0 Then
   MsgBox "
你输入的是" & i & ",他是正整数"
Elseif  i<0 then
   MsgBox "你输入的是" & i & ",他是负整数"
Else
   MsgBox"你输入的是零"
EndIf
End Sub
   这种称为多重选择。这里是两个条件三选择,要是喜欢的话,还可以再整几个条件:
If 条件1 then
   语句块1
ElseIf 条件2 then
   语句块2
ElseIf 条件3 then
   语句块4
……
ElseIf 条件n then
   语句块n
else
   语句块n+1
End if
   大家理解起来应该没问题吧?其中else语句块n+1可以没有。提醒一下:在这种结构中假如有多个条件成立的时候,他只按从上到下的顺序,执行第一个条件为真的那段语句块,后出现的都不执行;在没有else 语句块n+1且有所的条件都不成立时,那么所有的语句块都不执行。
    但是这样一来,感觉上有点不舒服,还好,小比同学为我们提供了另一个选择用于取代if语句的多重选择(帮助中的程序):
Sub test()
Dim Number AsInteger
Number =InputBox("请输入一个整数", ,0)   '输入变量初值。
Select CaseNumber    ' 判断 Number 的值。
   Case 1 To 5       ' Number 的值在 1 到 5 之间,包含1 和 5 。
      MsgBox "
在 1 和 5之间"
   Case 6, 7, 8   ' Number
的值是6 、7、8。
      MsgBox "
在 6 和 8之间"
   Case Is >= 9   ' Number 的值为>= 9。
      MsgBox "大于 8"
   Case Else   '
其他数值。
      MsgBox "小于1"
End Select
End Sub
具体格式是:
Select case 测试表达式
   Case 表达式1
      语句块1
   Case 表达式2
     语句块2
   ……
   Case 表达式n
     语句块n
   Case else
      语句块n+1
End select

    与前面的if多重选择一模一样,不解释了嘛。只是上面的例程中给出的表达式古里古怪的,不过还好,他仅为了显示可有这么几种,实际中根据需要写就行,不必整得那么花哨。
   立即模仿练习:输入一个学生的分数,如果是40以下则显示“差生”,40至60则显示“不及格”,60至80显示“中等”,80以上显示“优生”,不在0至100之间则显示“输入成绩超界”。用If和select各写一个吧。
从例4起就没画流程图了,是因为多重选择的流程图画起来不方便。
    还要再次说明的是,凡是提到语句块,就是说可以是一句、也可以是多句。选择语句是可以嵌套的,但必需要注意“配对”:嵌套可以,绝不能交叉,如例5的多重选择我们可以改成以下的if嵌套:
    大家自行理解去吧。
    同时注意:这里该意做成图片并划上了线,目的是为了表明前面提到的必需要注意“配对”:嵌套可以,绝不能交叉。这里的外层if结构的语句块2是另一个if结构。
    还有一点,与例1例2不同的是后面的代码排得非常非常的sexy,大家一定要习惯并且也这么来干,这都是为“配对”的需要:不是程序的要求,而是方便我们写代码、读代码时清楚其层次结构。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

 楼主| 发表于 2011-4-7 21:49 | 显示全部楼层
本帖最后由 青城山苦丁茶 于 2011-4-8 22:47 编辑

四、数组
为什么要学习数组?换句话说,他有什么优势?
那我们先来一个直观的感受:
例9:请向当前表格的a1至j30000单元格区域中填入1至100间的随机整数。
由前面的知识,画出流程图:
VBA提供了Rnd 函数返回小于 1 但大于或等于 0 的随机数,如果要生成某区间的随机整数可用Int((上限 –下限 + 1) * Rnd + 下限),由流程图我们很容易写出以下代码:
Sub test1()               '名称为test1的程序
Dim i1%, i2%, t As Single    ' 定义两个整型变量和一个单精度型变量
t = Timer                 '记下当前的时刻
Cells.Clear               '清空当前表的所有单元格
For i1 = 1 To 10          'i1从1循环到10,步长为1
     For i2 = 1 To 30000  'i2
从1循环到30000,步长为1
         Cells(i2, i1) = Int(Rnd * 100 + 1)   '
向单元格中填入随机整数数
    Next                  '
下一个i2
Next                      '下一个i1
MsgBox Timer - t          '显示当前时刻与t的差
End Sub                   '结束
    代码中我们增加了t = Timer和MsgBox Timer – t两句用于检查此段代码花费了多少时间,应该能明白吧?
    现在我们思路完全不变,改成用数组来做:
Sub test2()
Dim i1%, i2%, t As Single
Dim arr1(1 To 30000, 1 To 10) As Integer    ‘定义数组arr1
t = Timer
Cells.Clear
For i1 = 1 To 10
    For i2 = 1 To 30000

        arr1(i2, i1) = Int(Rnd * 100 + 1)  ‘
生成的随机数写入数组arr1
    Next
Next
Range("a1:j30000") = arr1       '将数组arr1的元素值填入单元格a1至j30000区域中
MsgBox Timer - t
End Sub
比较一下两段代码,基本上一样,是吧?可是他们花费的时间呢?
    还有更夸张的:记得以前刚开始用VBA时写一个类似于vlookup功能的代码,运行了一个小时还没完成实在等不下去了只好强行中断了它,后来改成数组大约是3秒左右,最后改成数组+字典,就没超过1秒了。怎么样?激发起数组兴趣没有?
    数组为什么快?究其原因,第一段代码直接向单元格中写入数据,写了10*30000次;第二段代码使用数组进行过渡,他先将数据写入数组,最后通过一句Range("a1:j30000") = arr1一次性将数组中的数据写入到单元格区域,从而达到的提高速度的目的。这就是空间换时间(数组是要占空间的哟)。
    从这个示例我们可以得到以下知识:
    其一,利用数组来减少对单元格的操作,可以大大提高速度,这就是我们为什么要学数组的原因之一。
    其二,又见到了三个函数:取整函数int(返回不大于其参数的最大整数,即int(5.9)返回5,int(-5.1)返回-6)、随机数函数rnd(返回大于等于0且小于1的随机小数)、时间函数timer(返回从午夜开始到现在经过的秒数)。如果要生成某区间的随机整数可用Int((上限 –下限 + 1) * Rnd + 下限)(如要生成10到20的随机整数就用int(11*rnd+10)
    见识了数组的作用,那么什么是数组?如何使用呢?下面我们从头来讲:
---------------------------------------------------------------------------
    前面讲了变量,变量就是用于存放数据的内存单元。一个变量存放一个数据。
    而数组,书上讲是多个类型相同的元素组成的集合,他用一个统一的数组名称和下标来唯一确定数组中的元素。
    太深奥了,不便于老百姓理解!数组数组,就是数据的组合嘛。说简单点:
    1、一维数组:
    公交车在出厂时安放了从1号编到50号的50个座位。王美女上车来司机把她安到了5号座位,这样5号座位就是王美女,下一站王美女下车了,上来了张帅哥、刘大头,司机把张帅哥、刘大头依次安到5号和10号位,这样5号就变成了张帅哥、10号位成了刘大头。这就就是个数组,而且是大小为50个元素的一维数组:安放了从1号编到50号的50个座位就是dim arr1(1 to 50),王美女坐到5号位就是arr1(5)= "王美女",后来张帅哥、刘大头依次坐到5号和10号位就是arr1(5)= "张帅哥"(张帅哥占了5号位王美女就消失了)、arr1(10)= "刘大头"……
这样的例子还有很多很多:银行里面办理业务排的号;足球比赛时运动员背上还是有大大的数字:一个序号……
    象这样,他们就是一维数组:只有一个下标。在VBA中使用
       Dim 数组名(n1 to n2) as 数据类型
    来定义一维数组。其中as 数据类型与前面的讲的变量完全一样,此处不在解释;n1和n2都是整数(即正整数、负整数、0都可以,而不见得如上面的都从1开始),但需要满足n1<=n2,定义出的元素个数为n2-n1+1。要引用其中的元素时,就用数组名(整型数字),这个整型数字的取值范围就从n1到n2的范围内。
    以dim arr1(-10 to 10)为例:数组名为arr1;元素个数为21个(为什么是21而不是20?),依次为arr1(-10)、arr1(-9)、arr1(-8)、……arr1(-1)、arr1(0)、arr1(1)、……arr1(9)、arr1(10),括号中的-10、-9等等数字就叫数组的下标(由于每个数组元素的下标只有一个,叫一维数组;这些下标都是整数,编程中常用整数或整型变量来表示这些下标);且都为Variant型(因为省略了as 类型)。其实质是在内存中开辟了一段连续的21个内存单元,分别用arr1(-10)、arr1(-9)、arr1(-8)、……来指向这些内存单元(相当于定义了21个变量)。
----------------------------------------------------------------------------
    2、二维数组及多维数组
    除了这种只有一个下标的外,还有两个下标的:我们的大会议室中安放了很多排、每排很多个座位,这时我们常常要给他编上号:1排、2排……,每一排又有1号、2号……要指定某个座位时就可用3排5号、10排8号等等这样的两个数字来唯一确定。对应到VBA中这就是一个二维数组:
    Dim 数组名(n1 to n2,n3 to n4) as 数据类型
    含义与一维数组基本雷同。其中n1<=n2,n3<=n4,元素个数为(n2-n1+1) *(n4-n3+1)
   
    前例中的Dim arr1(1 To 30000, 1 To 10) As Integer就定义了一个大小为30000*10的integer类型的数组。对应的元素的引用就是arr1(1,1)、arr1(1,2)、……arr1(2,1)、arr1(2,2)、……arr1(30000,9)、arr1(30000,10)。看到了吧,每个元素都是两个下标,中间用,分开。
-----------------------------------------------------------------------
    多维数组:还是前面的会议室,共有30排每排40个座,这是二维的;可是我们有5个这样完全一样的会议室,分别编号的一会议室、二会议室……如果要指定三会议室12排6号我们就可用会议室(3,12,6),这不就是三维了吗?要是我们有很10层楼,每层楼都是这样5个完全一样的会议室,如果要指定7楼三会议室12排6号我们就可用会议室(7,3,12,6),这又是几维呢?……还有很多很多。不过VBA提供的数组最多可达64维,不能超过64哈,实际编程中用得最多的还是一维和二维。


    在实际编程中,有时我们在设计代码时没法确定数组的大小,而需要在运行过程中根据实际需要才能定,这时我们常常先定义为动态数组(即不确定他的大小)然后再修改他的大小:
Sub test()
Dim arr1()              '定义数组arr1为动态数组
Dim i1%, i2%
i1 = InputBox("输入数组的最小下标", , 0)
i2 = InputBox("输入数组的最大下标", , 100)
ReDim arr1(i1 To i2)    '重新定义数组arr1的大小
End Sub
    对于redim大家可以去认真看看帮助文件,在这里我们暂时不多说他,在后面的实例中我们再讲。
   

    至此,我们将一些堆积木的规则及个别积木块展示出来了,下面,我们将再增加一些积木块,运用前面讲的规则来堆我们希望得到的“珍品”,共同努力吧!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

 楼主| 发表于 2011-4-7 21:49 | 显示全部楼层
本帖最后由 青城山苦丁茶 于 2011-4-15 23:22 编辑

    坛子中有很多讲排序的代码,但同时也有很多在问排序的,都说排序代码不好理解。其实排序和我们前面讲的也一样,就是把我们平时日常生活中是如何做的翻译成代码而已。下面我们就来说说教科书上讲的几个排序算法(下面讲的时候以3   5    4    6    2    1这六个数最终排为1    2   3   4    5    6为例,但代码中需排序的数据源在当前表的a1a100单元格中,结果依次放在b列、c列……):
一、选择排序:
    我们在日常生活中,常用的办法是先从一堆数中找出最小的放在第一位,再在余下的数中找出最小的放在第二位……以此类推,这种排序就叫选择排序。我们详细来说说其过程:
第一遍:用第一个数依次与第二个数、第三个数等等比较,只要是比第一个数小,则将它与第一个数交换,这样第一个数就是最小的了,其过程如下图:
    第二遍:用第二个数依次与第三个数、第四个数等等比较,只要是比第二个数小,则将它与第二个数交换,这样第二个数就是次小的了。过程中显然54交换、46不换、43交换、32交换,结果为1   2   5   6   4   3  
        第三遍:用第三个数……
    以此类推,直到所有的数都排好为止。现在我们来将他翻译成相应的代码:
Sub xzpx1()   '选择排序
Dim ar()
ar = Range("a1:a100").Value
For i& = 1 To 99
    For j& = i + 1 To 100
      If ar(i, 1) > ar(j, 1) Then
         tmp% = ar(i, 1)
         ar(i, 1) = ar(j, 1)
         ar(j, 1) = tmp
      End If
    Next
Next
[b1:b100] = ar
End Sub
    但这样一来,交换的次数有点多,而且其思路还是有点不好理解,可以稍改一下(我们就省略了,只写第一遍的,其余类推):记下第一个数的值和他处的位置(第一个数当然是第一了),用这个数依次与后面的数比较,当有比他小的数出现时,记下这个小的数及其所处的位置,然后用这个小的数与后面的比较,反复执行直到所有的数都比较完成了,这样得到两个东西:最小的数和他所处的位置,这时我们就将第一个数和这个最小的数进行交换。这样第一个数就排好了,然后再类似地排第二个数、第三个数。代码如下:
Sub xzpx2()   '选择排序2
ar = Range("a1:a100").Value
For i& = 1 To 99
   n_min% = ar(i, 1)     '用n_min记下当前的数值

   mrow = i          '用mrow记下n_min所处的位置
      For k% = i + 1 To 100
         If n_min > ar(k, 1) Then '如果有比n_min小的数
            n_min = ar(k, 1) '则将该数赋给n_min
            mrow = k '同时用mrow记下该数所处的位置
         End If
      Next
    If mrow <> i Then '不相等说明循环过程中给n_min赋过新值
       ar(mrow, 1) = ar(i, 1) '则进行交换
       ar(i, 1) = n_min
    End If
Next
[c1:c100] = ar
End Sub
这个代码引入了新的变量n_min,其实可以不用它都可以,而直接用以下代码完全一样:
Sub xzpx3()  '选择排序3
Dim ar()
ar = Range("a1:a100").Value
For i& = 1 To 99
    mrow& = i  '用mrow记下n_min所处的位置
    For j& = i + 1 To 100
      If ar(mrow, 1) > ar(j, 1) Then '用mrow指向的数来作比较
          mrow = j '用mrow记下较小数的位置
      End If
    Next
    If mrow <> i Then 'mrow与i值不同说明上面的循环中有比第i个数小的数,交换
       tmp% = ar(i, 1)
       ar(i, 1) = ar(mrow, 1)
       ar(mrow, 1) = tmp
    End If
Next
[d1:d100] = ar
End Sub
    选择排序与我们日常生活非常接近,是最易理解的排序方法。同时缺点也非常明显:数据量小还行,数据稍多点,速度太慢太慢。
--------------------------------------------------------------------------------------
为此程序语言中人们又想出了以下的排序方法:
二、插入排序:就是把数据插入到前面已排好的数据中。这种方法是从第二个数开始:
直接插入排序:第一个数认为是排好了的(只有一个数,当然是排好了的),所以就从第二个数开始:把第二个数插入到前面已排好的序列中,由于此时前面只有一个数,所以就这两数比较来决定插入到第一个数的前面还是后面,这样第一、第二个数就排好了;把第三个数插入到前面已排好的序列中……直到所有的数都排好为此。具体过程见下图所示:
Sub crpx1()     '直接插入排序
Dim ar()
ar = Range("a1:a100").Value
For i% = 2 To 100  '注意与前面的选择排序的不同,是从第二个数到最后一个
   tem% = ar(i, 1)
   For k% = i - 1 To 1 Step -1  '这里用上了倒循环。也可改成do循环
     If tem < ar(k, 1) Then
        ar(k + 1, 1) = ar(k, 1)
     Else
        Exit For  '这是异常跳出for循环,而执行next后面的语句
     End If
  Next
  ar(k + 1, 1) = tem
Next
[e1:e100] = ar
End Sub
    可见直接插入排序是在排第k个数时,前边的k-1个是已排好的,只需要将第k个数插入到前面k-1个数的合适位置就行了,采取的办法是将第k个数与前面的k-1个数倒起来比较,比他大的都往后移,当找到比他小的数的时候则终止比较(即找到他应该处的位置了)并将他写入。
    对他的改进方法是:采用二分法来查找他该处的位置,其余与直接插入相同。具体方法是:在排第k+1个数时,前边的k个数已排好,我们不必要从后往前一个一个的来比较,而是拿这个第k+1个数与已排好的k个数中的中间那个数(即第k/2个数)进行比较,根据其大小来确定这个数是插入到排好的k个数的前半区还是后半区,这样比较一次其范围就缩小一半,然后'再对这个半区再做前面的操作来再缩小一半……直到确定唯一位置后再将其插入。
Sub crpx2()    '二分法直接插入排序
Dim ar()
ar = range(“a1:a100”).Value
For i% = 2 To 100
   tem% = ar(i, 1)

   lo% = 1

   u% = i - 1

   Do While lo <= u‘
此循环用于折半(称二分法)查找,请注意变量的设置、处理的技巧
        m% = Int((lo + u) / 2)‘求中间数
        If tem <ar(m, 1) Then‘确定下一个查找的区域
             u = m - 1
       Else
            lo = m + 1
       End If
    Loop
    For k = i - 1 To lo Step -1‘此循环用于将大数依次往后移动,以便腾出空位让新数插入。
         ar(k + 1, 1) = ar(k, 1)
    Next
   ar(lo, 1) = tem ‘插入新数
Next
[f1:f100] = ar
End Sub
    这一方法的难点是二分法部份(当中的do循环部份),仔细体会。
----------------------------------------------------------------------------------
三、冒泡排序
    也称大数下沉。第一遍的过程:第一、二两个数比较,如果第一个数大,则交换否则不换;第二、三个数比较,如果第二个数大,则交换否则不换;第三、四个数比较,如果第三个数大,则交换否则不换;……最后两个数比较,倒数第二个数大则交换否则不换。这样最大的数就被一步步的换到了最后一个位置。第二遍的过程:与第一遍相同,只是到比较的个数少一个(最后一个不比较,因为他已经是最大的了),这样次大的数就被一步步的换到了倒数第二的位置。以此类推,直到所有的都进行完成则数就排好了。特别是:如果某遍比较过程中,如果没有进行过数据交换,则说明数据已提前排好,可以结束比较了。这一思路的代码如下:
Sub mppx()  '冒泡排序1
Dim ar(),fla as boolean
ar = range(“a1:a100”).Value
For i% = 1 To 99
    fla = True   ‘这一变量用来做标志,初值为真
    For k% = 1 To 100-i
          If ar(k, 1) >ar(k + 1, 1) Then‘如果需要进行交换
                fla = False‘则标志变量为假
               tem% = ar(k, 1)‘进行交换
               ar(k, 1) = ar(k + 1, 1)
               ar(k + 1, 1) = tem
          End If
     Next
     If fla Then Exit For‘如果标志变量为真,说明这一遍循环没有数据进行交换,则退出
Next
[g1:g100] = ar
End Sub
    设置了标志变量后,当待排数据大部有序时起到了非常大的作用。但同时,待排数据大部有序时内层循环还是嫌执行多了,为此又有了改进做法:再设置一个变量,用于记录内层循环最后一个进行过数据交换的位置,此位置之后的数据没进行过交换,说明他们都已排好了,为此下一回进行数据比较交换时已排好的就不需要再进行比较了嘛,也就是说循环只需到上回最后一个进行过数据交换的位置就行了。实现这一思想的代码如下:
Sub mppx2()    '冒泡排序2
Dim ar(),fla as boolean
ar = range(“a1:a100”).Value
mro% = 99
Do While mro> 1
     fla = True
     i% = mro
     For k% = 1 To i
           If ar(k, 1) >ar(k + 1, 1) Then
               fla = False
               tem% = ar(k, 1)
              ar(k, 1) = ar(k + 1, 1)
              ar(k + 1, 1) = tem
              mro = k   ‘记录进行交换的位置,其最后一个值作为下一回for循环的终值
          End If
     Next
     If fla Then mro = 0
Loop
[h1:h100] = ar
End Sub
     还有其它很多很多的排序:归并排序、希尔排序、快速排序等等,大家可以去参考相关资料。所有这些排序(包括前面写到的)都各有优缺点,不见得改进的就一定比未改进的好(主要是数据源情况不同时其表现就不同,特定情况下甚至会使改进成为负担)。
   
     所有的这些方法,都是教科书上的,仅供训练思维使用。其实excel自带的排序就非常非常好,如前面的是排a1:a100只需Range("a1:a100").Sort ([a1])就行,而且速度远比前面写的要快很多(据说有能写出比excel自带的排序更快代码的,但是我不能)


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

发表于 2011-4-7 21:52 | 显示全部楼层
感谢山哥,为我们这种完全不懂V的人带来如此详细的解说,加强学习!!!不懂再多问,只是现在还看不懂代码,慢慢来吧!!!
回复

使用道具 举报

 楼主| 发表于 2011-4-7 22:52 | 显示全部楼层
本帖最后由 青城山苦丁茶 于 2011-4-24 16:15 编辑

    前面说了堆积木的种种规则,但那是程序设计的。Excel中的vba必然要与excel打交道,这就有相应的规则在里面,同时还有他所独有的一些积木块,这些都是学VBA所不容回避的。规则我们要记住,但是那些积木块却太多太多,又都是英语的,要全部记住几乎是不可能的(反正我只记住了很少很少的一部份),那怎么办?没关系,有几种方法:录制宏,到那里面去找;查帮助;网上搜索;收录一些文件,如http://www.excelpx.com/thread-58127-1-4.html就是前人收集在一起的一个较全的集子;等等方式。下面我们就用个实例来看看如何从录制宏中找出我们想要的东西并恰当地运用到我们的程序中去:
实例一:当前excel文件中的sheet1中有形如下图的成绩表,
需要在sheet2中做出如下图所示的成绩条以便打印出来发给学生:
    (当然你也可以将它改成单位工资表打印工资条)此问题用函数、技巧我们是可以完成,但学了VBA,我们当然希望用VBA来做。为此我们还是按贯例先来看手工下是如何做的:
    第一个人:1. A1:J1合并并居中。 2. A3J4设置所有框线。3.输入数据
    第二个人:与第一个人的雷同
    第三个人:与第一个人的雷同
    ……
    这不就是循环吗?这我们是有经验的,为此我们只要将第一个人的完成了,在外层增加个循环,就可将所有的人都做完。那么如何来做这第一个学生的呢?下面我们来慢慢完成:
A1:J1合并居中,在手工下就是选中A1:J1,然后点合并居中按钮,现在我们用录制宏将这一手工过程记录下来:
Sub 1()
'
' 1
'
'Range("A1:J1").Select
With Selection

    .HorizontalAlignment = xlCenter

    .VerticalAlignment = xlCenter

    .WrapText = False

    .Orientation = 0

    .AddIndent = False

    .IndentLevel = 0

    .ShrinkToFit = False

    .ReadingOrder = xlContext

    .MergeCells = False

End With
Selection.Merge
End Sub
    简单的一个合并居中所录制的宏代码就是这么一大堆?对不懂鸟文的人来说,V就这么难呀?怕怕(这里面绝大部份鸟文是他不认识我我更不认识他)。
    其实他实现的不仅仅是合并居中,因为是录制宏,他要将将方方面面的东西都记录下来,而我们所需要的只是合并居中这么一点。那如何从中找出我们所需要的呢?我是这样干的:
sheet2中的内容全部删除(不是清除内容,而是选中整张表-------点右键------点删除),这样就将整张sheet恢复到初始状态,在A1中输入任意内容,将VBE窗口缩小并移到合适的位置,只要能看到sheet中的A1:J1就可以了。这时单步执行刚录的宏来观察每一行所起的作用,这时可看到Range("A1:J1").Select是选中单元格区域A1:J1.HorizontalAlignment = xlCenter是水平居中,如果行高有点高的话还可以明显看到.VerticalAlignment = xlCenter是垂直居中,后面的看不出什么变化,直到Selection.Merge时可看到A1:J1合并了。可见,要合并单元格核心的语句就是Selection.Merge,其它的都是此处所不需要的(注意,仅仅是此处合并所不需要的),为了验证这一想法,我们试试将其它语句都注释掉(或干脆删掉):
Sub 1()
Selection.Merge
End Sub
    再执行一下看是不是。错了错了,没合并呢?原来是Range("A1:J1").Select没了,仅有Selection.Merge,而当前只选中了一个单元格,一个单元格怎么合并?那我们就任意选中几个单元格,再执行一下看。怎么样?合并了吧?哈哈,找到了,合并就是他:Merge
    可是还有点不对的地方:要不就有单元格区域. Select,要不就是当前选中有单元格区域,才能Selection.Merge,可见是先选中再合并,能不能再简单点呢?为此,经试验合并a1:j1的代码就成了:
Sub 1()
Range(“A1:j1”).Merge
End Sub
那么长一段代码,最后就整出这么三行,简单吧?就这么简单!
可是,没对,还没对!还有居中呢?居中嘛,再加不就完了?仿合并的:
Sub 1()
Range(“A1:j1”).Merge
Range(“A1:j1”).HorizontalAlignment = xlCenter   ‘这是水平居中的
Range(“A1:j1”).VerticalAlignment = xlCenter       ‘这是垂直居中的
End Sub
    如果还要其它功能就再加就是了,看你的需要而定嘛,哈哈
    大家去试试通过录制宏,实现整张表格所有内容清除、整张表格内容水平居中和垂直居中、行高25A1:j1合并、D2:E2合并的功能,我们这里仅给出最终代码:
Sub 1()
Cells.clear    ‘整张表格所有内容清除
Cells.HorizontalAlignment = xlCenter    '整张表格所有内容水平居中
Cells.VerticalAlignment = xlCenter        '
整张表格所有内容垂直居中
Cells.RowHeight = 25                             '整张表格行高为25
Range(“A1:j1”).Merge                           ‘合并A1:j1
Range(“D2:E2”).Merge                          ‘合并D2:E2
End Sub
    注意:录的宏中其它东东都不是无用的,有兴趣的可以选中鸟文后按f1从帮助中看他们是做何用的。对于所有的这些东西,你能够把他们都记住、能独立写出来固然非常好,但是如果象我一样实在记不住,那也没什么大不了的,只要大至有个印象,到要用的时候任意录制个宏,从宏中能把他们选出来就行了(我就是这样干的,嘿嘿)。
    下面我们来给A3:J4设置边框:同样录个宏,得到的代码如下:
Sub 2()
'
' 2
'
'
Range("A3:J4").Select

Selection.Borders(xlDiagonalDown).LineStyle = xlNone

Selection.Borders(xlDiagonalUp).LineStyle = xlNone

With Selection.Borders(xlEdgeLeft)

    .LineStyle = xlContinuous

    .ColorIndex = 0

    .TintAndShade = 0

    .Weight = xlThin

End With

With Selection.Borders(xlEdgeTop)

    .LineStyle = xlContinuous

    .ColorIndex = 0

    .TintAndShade = 0

    .Weight = xlThin

End With

With Selection.Borders(xlEdgeBottom)

    .LineStyle = xlContinuous

    .ColorIndex = 0

    .TintAndShade = 0

    .Weight = xlThin

End With

With Selection.Borders(xlEdgeRight)

    .LineStyle = xlContinuous

    .ColorIndex = 0

    .TintAndShade = 0

    .Weight = xlThin

End With

With Selection.Borders(xlInsideVertical)

    .LineStyle = xlContinuous

    .ColorIndex = 0

    .TintAndShade = 0

    .Weight = xlThin

End With

With Selection.Borders(xlInsideHorizontal)

    .LineStyle = xlContinuous

    .ColorIndex = 0

    .TintAndShade = 0

    .Weight = xlThin

End With
End Sub
    越整越多了,这么长,真晕!别急,有前面的经验,慢慢来:还是先将A3:J4的所有东西都删了,然后一边单步执行代码,一边仔细观察A3:J4的变化。可以看到:
Range("A3:J4").Select与前面的相同,就是选择该区域Selection.Borders(xlDiagonalDown).LineStyle = xlNone 没什么明显变化(查帮助方知是去除斜线),下一句Selection.Borders(xlDiagonalUp).LineStyle = xlNone也一样(斜线有两个方向嘛)后面的六个With语句分别是加左、上、下、右、中间的竖、中间的橫边框,而且加框线就.LineStyle = xlContinuous,另几个是线型颜色什么的。对这个进行精减要稍动点脑筋,在结合帮助的情况下我最终将他整成了(只有一句,下面把他与前面的内容合在一起了):
Sub 1()
Cells.clear       ‘
整张表格所有内容清除
Cells.HorizontalAlignment = xlCenter     '整张表格所有内容水平居中
Cells.VerticalAlignment = xlCenter         '
整张表格所有内容垂直居中
Cells.RowHeight = 25                             '整张表格行高为25
Range(“A1:j1”).Merge                           ‘合并A1:j1
Range(“D2:E2”).Merge                         ‘合并D2:E2
Range("A3:J4").Borders.LineStyle = xlContinuous          ‘
设置单元格区域A3:J4的边框为实线
End Sub
    下面我们来填数据:
    要填数据,得先把数从sheet1中取出来。虽然可以取一个填一个,但这样速度很慢,为速度计我们采取两个措施:
    1、先将sheet1中的数据取出来放到一数组中。要用数时就从这个数组中来取
    2、学生数据不是直接写入单元格,而是先写入一目的数组中,然后再一次性写入单元格区域
    为此我们先定义一个动态数组(dim arr1(),必须是动态数组,要是固定数组的话就不能一次性将数据全放进去了),然后用数组名=单元格区域.value的形式一次性将数据放进数组中。
Sub 1()
Dim arr1(), arr2(1 To 4, 1 To 10)

Dim i1%,  i2%,   l&

arr1 = Sheets("sheet1").UsedRange.Value   
'sheet1的所有数据存入数组arr1,这样取来的第一行表头也在里面了
Sheets("sheet2").Select     ‘选中sheet2
Cells.Clear                         '
清除当前表格的所有内容(包括格式)

Cells.HorizontalAlignment = xlCenter    '
整张表格所有内容水平居中   

Cells.VerticalAlignment = xlCenter       '
整张表格所有内容垂直居中

Cells.RowHeight = 25                           '
整张表格行高为25

'
以下生成目的数组。暂时还没有学生信息

arr2(1, 1) = "*
学校*学年度下期成绩通知单"

arr2(2, 1) = "
姓名"

arr2(2, 3) = "
学号"

arr2(2, 8) = "
班级"

For i1 = 1 To 10      ‘
这个循环用于写科目名称到目的数组的第三行中去

      arr2(3, i1) = arr1(1, i1 + 3)        '
为什么要i1+3呢?

Next

以下填入第一个学生的信息到目的数组中去:

arr2(2, 2) = arr1(2, 3)      ‘
写姓名

arr2(2, 4) = arr1(2, 2)      ‘
写学号

arr2(2, 9) = arr1(2, 1)      ‘
写班级

For i1 = 1 To 10               ‘
这个循环用于写各科成绩到目的数组的第三行中去

      arr2(4, i1) = arr1(2, i1 + 3)       '
为什么要i1+3呢?

Next

Range("A1:j4") = arr2       '
将数据写入表格,这样第一个学生的信息就填好了

'下面来设置格式
Range("A1:j1").Merge        '
这是合并

Range("d2:e2").Merge      '
这是合并

Range("A3:J4").Borders.LineStyle = xlContinuous      '
这是设置边框
End Sub
        这样第一个学生的信息就填完整了。单步执行一下,看情况是否如我们所料。
        如果是只到这里,那么V就太麻烦了,小小的一个事情整得这么复杂,还不如手工来得爽。但是我们的目的不仅仅是做出第一个学生的,而是要将很多很多的学生都做出来。有了第一个学生的,其他的就变得非常简单:只需我们在外加个循环就成了。现在我们来看如何加循环:
        循环应该包括两层意思:循环取数和循环写数。
        循环取数,我们已经将数据取到数组arr1中了,在数组arr1中,从第二行起每个学生占一行,固只须从第二行起一行一行地读下去就可以了。可见这是个初值为2、终值为数组arr1最大下标、步长为1的循环。
        循环写数,要稍麻烦点,由示例要求得知,在sheet2中是从第一行起,第一个学生占第14行,然后空1行;第二个学生占第69行,然后空1行……也就是说从第一个学生起每个学生的起始行依次是1611……看出规律了吗?这是个初值为1、步长为5、循环次数与取数相同,是不?
        那么如何将这两个整合在一起呢?这里直接给出结果,大家自己体会吧:
Sub 1()
Dim arr1(), arr2(1 To 4, 1 To 10)

Dim str1$, i1%, i2%, l&

arr1 = Sheets("sheet1").UsedRange.Value
Sheets("sheet2").Select
Cells.Clear    '
清除当前表格的所有内容(包括格式)

Cells.HorizontalAlignment = xlCenter    '
整张表格所有内容水平居中

Cells.VerticalAlignment = xlCenter    '
整张表格所有内容垂直居中

Cells.RowHeight = 25    '
整张表格行高为25

'
以下生成目的数组。暂时还没有学生信息

arr2(1, 1) = "*
学校*学年度下期成绩通知单"

arr2(2, 1) = "
姓名"

arr2(2, 3) = "
学号"

arr2(2, 8) = "
班级"

For i1 = 1 To 10

    arr2(3, i1) = arr1(1, i1 + 3)

Next

l = 1     ‘
初值为1,因为第一个学生是从第一行开始填

For i1 = 2 To UBound(arr1)      '
循环初值从2开始,循环到arr1的最大下标,步长为1.
可是这个循环为什么要加在此处呢?能否前后移动呢?
    arr2(2, 2) = arr1(i1, 3)

    arr2(2, 4) = arr1(i1, 2)

    arr2(2, 9) = arr1(i1, 1)

    For i2 = 1 To 10

        arr2(4, i2) = arr1(i1, i2 + 3)
    Next
    Range("A" & l & ":j" & l + 3) = arr2    '
将数据写入表格,这样第一个学生的信息就填好了,下面来设置格式

    Range("A" & l & ":j" & l).Merge    '
这是合并

    Range("d" & l + 1 & ":e" & l + 1).Merge    '
这是合并

    Range("A" & l + 2 & ":j" & l + 3).Borders.LineStyle = xlContinuous     '
这是设置边框
    ‘上面为什么有寻么多的&+呢?结果是什么?可以在上面加一行tmp="A" & l + 2 & ":j" & l + 3并单执行,就可看到"A" & l + 2 & ":j" & l + 3的作用是啥子了,其它雷同

    l = l + 5    ‘
下一个学生开始的位置

Next
End Sub
    这样这个工程就差不多了。收拾下思绪,对这个问题我们再提点东东:
    为速度计,其一我们还必须在程序开始位置再加一句:Application.ScreenUpdating = False,作用是关闭屏幕更新,从而达到提速的目的;其二,这个代码数组用得还不够彻底,向表中写数据时,一个学生写一次,在数据量大的情况下速度肯定快不起来,还可以考虑将所有学生的数据全写到一个数组中后再一次性写进表格;其三,每个学生都进行两次合并单元格、一次设置边框,同样可以优化思路,以达到提速的目的。
    说这么几点,实际就是提速提速再提速。
    不过,这不是这部份内容的主题,这部份内容的主题是录制宏,在帮助的帮助下运用一些手段从录的宏中准确地找出我们所需要的东西并恰当地运用到我们的代码中去,因此只要这个目的达到了就成了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

 楼主| 发表于 2011-4-7 22:52 | 显示全部楼层
本帖最后由 青城山苦丁茶 于 2011-5-2 12:49 编辑

    我们再来看看坛子中经常而且反复出现的一个问题:删除空行
    对于这个问题,用技巧等等方式非常好解决,这里我们不去谈他,现在仅试图用VBA来解决看看
    问题描述:如下图所示,当前表格中的B2B25001区域中,有的地方是由函数产生的空(即假空),有的地方确实是没内容的真空(f2f3两个单元格公式结果可看出这个情况),如何用VBAB列的这些真空和假空所在的行整行删除呢?
    先复习下录制宏:删除一行、连续的几行、不连续的几行的宏代码,经修改后的结果是:
    删除单行:Rows(3).Delete Range("3:3").Delete  ’将第三行删除
    删除连续几行:Rows("4:10").DeleteRange("4:10").Delete  ’ 将第四至第十行删除
    删除不连续几行:Range("8:8,12:15,23:33").Delete
还有以下形式:
Range("B4").EntireRow.Delete’B4所在的行删除
Range("B4:b10").EntireRow.Delete’B4B10所在的行删除
Range("B4,b6:b10").EntireRow.Delete’B4b6b10所在的行删除
    有了这些积木块后,再加上以前的循环、判断等等积木块,下面我们来实现删除空行的目的。
    还是先来看看我们人工做这个活路时是如何来做的(贯穿始终的思想:编代码其实就是将我们的想法翻译成对应的VBA代码):
    对B列单元格,从上往下一个一个地来看,只要他是为空,就删除该行,直到所有的都完成为止。
    因此,很容易就得出以下代码(为了方便我们暂时只做到第10行):
Sub delblankrow()
For i% = 2 To 10
    If Cells(i, 2) = "" Then Rows(i).Delete
Next
End Sub
    这个代码应该是非常容易看明白的,也可将Cells(i, 2) = ""改成len(cells(I,2))<1。真假空都能删除。
    可是,还是有点没对呢?当有连续空单元格时,运行一次是删除不完的,为什么会这样?探究一下原因(我们把B列内容都清除了,并在代码中增加一行):
Sub delblankrow()
For i% = 2 To 10
    If Cells(i, 2) = "" Then
       Rows(i).select  ‘增加这么一行,先选中这行后再删除。
       Rows(i).Delete
    endif
Next
End Sub
    增加了Rows(i).select这么一行,先选中这行后再删除,很清楚地看到:i=4时,判断Cells(4, 2)为空,选中了这行然后删除了,i增加1,成了5,就去判断Cells(5, 2)是否为空去了,这里产生了遗漏!因为Cells(4, 2)为空在删除第四行时,第五行自然上移成了第四行(后面的行都依次上移了一行),下一个判断Cells(5, 2)是否为空时这个Cells(5, 2)实际上是以前的第六行,以前的第五行(现在的第四行)就没有去判断了。这就是这段代码出错的原因所在!
    明白了产生错误的原因,那么如何来改呢?先给出个简单的、间接的改法,倒循环:
    前面的错误是因为删除一行后下面的行上移而造成漏判,因此我们可以改成从最后一行开始往上进行判断删除(为什么这样就没遗漏了呢?):
Sub delblankrow2()
For i% = 10 To 2 step -1
    If Cells(i, 2) = "" Then
        Rows(i).select‘增加这么一行,先选中这行后再删除。实际代码中去除它
        Rows(i).Delete
    endif
Next
End Sub
    那么只能用倒循环吗?再来看看:第一段代码的错误是在于某行删除了后,下面的行依次上移,代码没有考虑到上移这一情况。我们现在将这一情况考虑进去:删除某行、下面的行自动依次上移后再对这行进行判断:
Sub delblankrow3()
r%=2
For i% = 2 To 10
    If Cells(r, 2) = "" Then
        Rows(r).Delete
    else
        r=r+1
    endif
Next
End Sub
    这样,多用一个变量来表示当前需要判断的行就达到目的了。对于以下代码,请大家自己来理解理解其思路:
Sub delblankrow4()
r% =10
i%=2
Do While i <=r
    If Cells(i, 2) = "" Then

        Rows(i).Delete

        r=r-1

    Else

        i = i + 1

    End If
Loop
End Sub
    后面这两个虽然都可以达到目的,但都不如delblankrow2简洁明了,看来还是用delblankrow2这样的倒循环要好些。delblankrow2完整的代码应该如下:
Sub delblankrow5()
Application.ScreenUpdating = False '关闭屏幕刷新
For i% =10 To 2 Step -1
    If Cells(i, 2) = "" Then Rows(i).Delete
Next
End Sub
    说到这里,看似这个问题就结束了。
    可是,这只做到了210行,要是改到如开头时讲的到25001行或者更多行,用这个代码那慢得要死!不信你试试。慢的原因是什么?慢就慢在两个方面:读单元格的次数太多(每次判断都要读一次单元格)、删除的次数太多(每有一个空就删除一次)。要想提速,必须从这两方面入手!对于读单元格的次数太多可以如以前所用的办法:定义一个数组,将需要判断的B列数据事先一次性读入数组,循环判断时只读取数组而不是去读单元格,从而达到提速的目的。而对于删除的次数太多的问题,就要用到点技巧了,我们可以这样来想:在一开始时回顾了录制宏,并得到了删除一行、连续的几行、不连续的几行的宏代码,如删不连续几行的Range("8:8,12:15,23:33").Delete,要是我们能将需要删除的行先找出来,然后按规则构造成形如"8:8,12:15,23:33"的字符串存入变量delstr中,然后再用Range(delstr).Delete,行不?要是可行的话,多行不是只删一次就可以了吗?马上试验,将delblankrow2按这个思路来改看行不:
Sub delblankrow6()
Dim arr1(), delstr$
arr1 = [b1:b10].Value'B列数据读入数组
For i% = UBound(arr1) To LBound(arr1) + 1 Step -1'从数组的最大下标循环到最小下标+1,步长为-1
    If arr1(i, 1) = "" Then delstr = delstr & "," & i & ":" & i'
构造字串
Next
If Len(delstr) > 1 Then'delstr有没有内容
    delstr = Right(delstr, Len(delstr) - 1)'
构造的字串delstr的第一个字符为,得去掉

    Range(delstr).Delete'
删除delstr所指定的行
End If
End Sub
    执行一下,是可行的。这样,删除多行只执行了一次delete,达到了提速的目的。
    我们把它扩展到25001行,怎么错了呢?出现了错误提示:
    为什么少的时候可行而多了就不行了呢?而多少恰巧是delstr的内容的多少的问题。为此我们可以将delstr分成几段来分次删除,以下实现这一思想:
Sub delblankrow7()
Dim arr1(), delstr$
Application.ScreenUpdating = False '关闭屏幕刷新
arr1 = [b1:b25001].Value'B列数据读入数组
For i% = UBound(arr1) To LBound(arr1) + 1 Step -1'从数组的最大下标循环到最小下标+1,步长为-1
    If arr1(i, 1) = "" Then

        delstr = delstr & "," & i & ":" & i'
构造字串

        If Len(delstr) > 240 Then‘
字串达到一定长度执行删除,经试验240是个合适的值

            delstr = Right(delstr, Len(delstr) - 1)'
构造的字串delstr的第一个字符为,得去掉

            Range(delstr).Delete'
删除delstr所指定的行

            delstr = ""'
将字串delstr清空

        End If

    End If
Next
If Len(delstr) > 1 Then'delstr还有没有内容
    delstr = Right(delstr, Len(delstr) - 1)'
构造的字串delstr的第一个字符为,得去掉

    Range(delstr).Delete'
删除delstr所指定的行
End If
End Sub
    至此,大部完成了,基本可以接受。
    还有一点:连续空行!目前的代码对于如本例中的连续空行4567其生成的字串是”4:4,5:5,6:6,7:7”对不?而我们前面的录制的宏Range("8:8,12:15,23:33").Delete中,这样的连续空行表示成”4:7”就成了,如何才能达到这个目的呢?需要我们增加个连续空行的,那就再来改嘛:
Sub delblankrow8()
Dim arr1(), delstr$
Application.ScreenUpdating = False '关闭屏幕刷新
arr1 = [b1:b25001].Value'B列数据读入数组
For i% = UBound(arr1) To LBound(arr1) + 1 Step -1'从数组的最大下标循环到最小下标+1,步长为-1
    If arr1(i, 1) = "" Then

        tmpi = i - 1

        Do While tmpi > 1

            If arr1(tmpi, 1) <> "" Then

                delstr = delstr & "," & i & ":" & tmpi + 1 '
构造字串

                i = tmpi

                tmpi = 1

            Else

                tmpi = tmpi - 1

            End If

        Loop

        If Len(delstr) > 240 Then

            delstr = Right(delstr, Len(delstr) - 1)  '
构造的字串delstr的第一个字符为,得去掉

            Range(delstr).Delete  '
删除delstr所指定的行

            delstr = ""   '
将字串delstr清空

        End If
    End If
Next
If Len(delstr) > 1 Then  'delstr有没有内容
    delstr = Right(delstr, Len(delstr) - 1)  '
构造的字串delstr的第一个字符为,得去掉

    Range(delstr).Delete  '
删除delstr所指定的行
End If
End Sub
    这样一来,相对来说较为满意了。
    但同时这个代码给出了一个很不好的范例:for循环中对循环变量进行了更改!
    可是还是有点不满意:示例中的空,有的是真空有的是假空,对于真空而言,e提供了个定位空值的功能,为什么我们不先定位真空并删除真空所在的行,再用上面的代码来删除假空所在的行呢?这里我们仅给出定位B列空值并删除其所在行的录制宏经修改后的代码:
Columns("B:B").SpecialCells(xlCellTypeBlanks).EntireRow.Delete
要实现上述思想大家自行去组合吧。
--------------------------------------------------------------------------------------------------------------
    上面的代码全部都是按循环判断是否为空来决定是否删除这一思路来做的。其实我们还可以换换思路:在循环判断后,不删除他,而是在右边增加个辅助列,来记录是否为空,完成后利用E自带的排序,将为空的(不管真空还是假空)都排在一起,然后一次性删除就行了。为此我们将Sub delblankrow6()改成这一思想:
Sub delblankrow9()
Dim arr1()
arr1 = [b1:b25001].Value   'B列数据读入数组
For i% = 2 To UBound(arr1)   '从数组的最大下标循环到最小下标+1,步长为-1
    If arr1(i, 1) <> "" Then

        arr1(i, 1) = 1  '
这是随意赋的值

    Else   '
对于此例,elsearr1(i,1)=""都可以不要

        arr1(i, 1) = ""

    End If
Next
[d1].Resize(i - 1) = arr1'写入一个空列,为辅助列
[a2].Resize(i - 2, 4).Sort ([d1])'按辅助列排序,空值就都被排到后面去了
Range("25001:" & 1 + Cells(i, 4).End(xlUp).Row).Delete'删除后面的空值所在的行
Range("D:D").Clear'清空辅助列
End Sub
    还有没有其它方法呢?如筛选等等?我想,肯定有,大家自己去试吧。
    “思路决定出路”,确实不假,只要思路正确,把他翻译成VBA代码就成了,堆积木嘛,啷个堆都成,哈哈……

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

发表于 2011-4-7 22:59 | 显示全部楼层
学习过程中啊,以前学习C语言,看来还要段时间过度呢!
回复

使用道具 举报

发表于 2011-4-7 23:21 | 显示全部楼层
本帖最后由 天籁友 于 2011-4-7 23:22 编辑

啊!前排没位了?还有!来学习,谢谢版主!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|Archiver|Excel精英培训 ( 豫ICP备11015029号 )

GMT+8, 2024-4-30 01:59 , Processed in 0.290991 second(s), 8 queries , Gzip On, Yac On.

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表