首页 《老兵新传:Visual Basic核心编程及通用模块开发》样章043360-01

《老兵新传:Visual Basic核心编程及通用模块开发》样章043360-01

举报
开通vip

《老兵新传:Visual Basic核心编程及通用模块开发》样章043360-0120 老兵新传:Visual Basic核心编程及通用模块开发 第1章 万丈高楼平地起,一劳永逸打地基——知识准备 19 第1章 万丈高楼平地起,一劳永逸 打地基——知识准备 如何利用VB编写出功能更“强大”的程序,如何让自己不再做重复的编程劳动,如何在短时间内开发出一个软件……,本章就为这一切做好准备!本章是全书内容的铺垫。在本章中读者将了解到增强VB程序功能所必须的Windows API函数的调用、模块化和代码重用的编程思想、以及从一开始就需要养成的良好的编程风格,本章还将介绍条件编译和如何查找...

《老兵新传:Visual Basic核心编程及通用模块开发》样章043360-01
20 老兵新传:Visual Basic核心编程及通用模块开发 第1章 万丈高楼平地起,一劳永逸打地基——知识准备 19 第1章 万丈高楼平地起,一劳永逸 打地基——知识准备 如何利用VB编写出功能更“强大”的程序,如何让自己不再做重复的编程劳动,如何在短时间内开发出一个软件……,本章就为这一切做好准备!本章是全书内容的铺垫。在本章中读者将了解到增强VB程序功能所必须的Windows API函数的调用、模块化和代码重用的编程思想、以及从一开始就需要养成的良好的编程风格,本章还将介绍条件编译和如何查找资料,这一切都是进行更深入VB编程所必须的知识,让我们开始吧! 1.1 使用Windows API函数 Windows API(Application Programming Interface),即Windows应用程序编程接口,是Windows提供的一整套编程函数库,包括大量函数。任何在Windows中运行的程序包括Windows本身都是靠调用这些函数运行的。Windows程序的开发语言有很多种,用各种语言编写的程序源代码可以不同,但在被编译后生成的可执行文件中最终都要被转换成这些API函数的调用命令,Win32汇编语言如此,Visual C++如此,Visual Basic也不例外。因此不同的编程语言或编译系统实际上都是Windows编程的“外壳”,“外壳”——编程语言可以五花八门,但实质——调用API都是一样的。通常使用Visual Basic编写程序不需要调用API函数,程序员也察觉不到API,这是因为Visual Basic在封装上做得很好,它几乎完全封装和隐藏了API。因此,如果说VB提供的功能少,只能说VB这个“外壳”封装和隐藏的API少;当要实现更多、更强大的功能时,我们完全可以通过直接调用API函数来实现所需要的功能。 API函数大部分都位于Windows系统目录下的库文件中,这些库文件大多是.dll文件,比较常用的有kernel32.dll、gdi32.dll、user32.dll等。这些文件是随Windows操作系统的安装而被安装到系统中的,而且它们都很重要,Windows本身的运行也需要调用它们。因此即使在我们的VB程序中调用了API函数,在发布时也不必再把库文件打包到安装程序中;因为可以肯定的是,用户的机器上一定存在这些文件,否则他的Windows根本就不可能启动,更谈不上在一台不能启动的机器上有机会运行我们的VB程序了。所以库文件的存在与否是不必关心的,我们可以尽情地调用API函数,享受Windows带给我们的无穷强大功能吧! 1.1.1 API函数的声明 在VB中调用API函数,首先需要用Declare语句声明。声明的含义就是“告诉”VB,我要调用哪个dll文件中的哪个函数,方便让VB能够找到这个函数去执行函数中的代码。声明后,就能像调用自己编写的Function或Sub过程一样,来调用API函数。声明的方式是: 声明有返回值的API函数时使用类似Function过程的声明: [Public/Private] Declare Function 使用函数名 Lib "库文件名" [Alias "库文件中函数名"] [([[ByVal] variable [As type] [,[ByVal] variable [As type]]...])] As Type 声明没有返回值的API函数时使用类似Sub过程的声明: [Public/Private] Declare Sub 使用函数名Lib "库文件名" [Alias "库文件中函数名"] [([[ByVal] variable [As type] [,[ByVal] variable [As type]]...])] API函数的声明与VB的Function或Sub过程最大的不同之处是必须有一个“库文件名”,即指定函数来自于哪个“库文件”。“库文件”一般是dll文件,声明中可给出dll文件的全路径文件名,也可省略路径只给出文件名。如果省略路径,Windows将依次在程序运行目录、当前目录、系统目录和PATH环境变量中指定的目录中查找文件。如果dll文件属于核心库(即kernel32.dll、gdi32.dll、user32.dll三者之一),还可以省略“.dll”扩展名;而如果是其他dll文件则不能省略扩展名。 下面再介绍API函数的声明中其他一些需要注意的地方。 1.1.1.1 用公有(Public)还是私有(Private) 在 标准 excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载 模块、窗体模块或类模块中都可以声明API函数。VB系统规定,在标准模块中声明的API函数默认是公有的(加Public关键字或默认),可以在工程中的任何模块中调用它;也可以声明为私有的(在声明中加Private关键字),表示API函数只能在本模块中被调用。在窗体模块或类模块中声明的API函数只能是私有的(必须加Private关键字),也就是在窗体模块或类模块中声明的API函数只能在本模块中使用。 在很多介绍API函数调用的书籍或网络资料中,通常都喜欢把一个工程中要使用的所有API函数“统统”声明在一个标准模块中,且声明为公有的(Public),以便工程中所有模块的程序都可以随意调用这些API函数。这种做法是不好的,因为工程中所有调用了API函数的模块(标准模块、窗体模块、类模块)都需要依赖这个声明API函数的标准模块才能运行,这增加了模块之间的联系(在软件工程中称“耦合性”),降低了模块的独立性,它将会带来程序结构的不清晰,尤其在设计大型软件项目时这种弊端将非常明显。在本书中不采用这种做法。本书的做法是:如果要在一个窗体模块或类模块中调用API函数,就在窗体模块或类模块内部将之声明为私有的(Private);如果要在一个标准模块中调用API函数,就在标准模块中声明仅本模块要调用的API函数,且也声明为私有的。这样,每个模块都能够“独立地”实现自己的功能,而很少或不依赖其他模块,这正是软件工程中所倡导的增强模块“内聚性”的方法,在后续章节中读者还会看到这种方式的优越性。 1.1.1.2 声明中为什么有两个名字 函数声明中,有“使用函数名”和Alias子句后面的“库文件中函数名”,为什么要有两个名字呢?我们知道,要在VB中调用API函数,就要“告诉”VB函数的名字和所在的库文件,这是说明函数名字的一个用途。此外,说明函数名字还有一个用途,那就是在VB程序中调用函数时,在代码中还要“书写”函数名。对于前者,函数名是在库文件中定义好的,不能由我们来改变;而对于后者,则必须使用符合VB语法规则的函数名。在一般情况下,库文件中的函数名与代码中要书写的函数名相同,这时只需在“使用函数名”中指定这个名字即可,而不需指定Alias子句后面的“库文件中函数名”。然而,在另一些情况下,库文件中的函数名与代码中要书写的函数名是不同的,这时就需要用到两个名称,这些情况主要有以下两类: (1)由于Windows本身是用C或C++开发的,库文件也是用C或C++开发的,因此库文件中的某些函数的函数名符合C或C++的语法规则,但不一定符合VB的语法规则:如果使用了VB中不允许使用的特殊字符,或者函数名和VB的保留字重名,或者和程序员自己定义的函数名重名等。这时,在Alias子句后面写上库文件中定义的函数的真实名称,而在“使用函数名”中就可以任意地为函数起另外一个名字,在VB程序中仅使用这个名字调用该函数。 例如,API函数“_lopen”的函数名是以下划线开头的,而VB的语法规则规定函数名不允许以下划线开头。这时需将“_lopen”函数改名为“lopen”,然后在Alias子句后面写上函数真实的名字“_lopen”: Private Declare Function lopen Lib "kernel32" Alias "_lopen" (ByVal lpPathName As String, ByVal iReadWrite As Long) As Long (2)故意为函数起别名:为了使用方便,故意不采用库文件中定义的函数的名字,而为函数起另外的名字。例如,有时库文件中定义的函数名称太长或不便记忆,或者库文件定义的函数名称包含字符集后缀标志等,为使用方便,都需要重新命名。 在Windows的API函数中,涉及字符串的函数一般都有两个版本,ANSI版和Unicode版。ANSI版使用单字节字符集,即每个英文、数字和符号都占1个字节,每个汉字占2个字节;Unicode版使用Unicode字符集,即所有字符都占2个字节。对ANSI版的函数,在函数名的最后都有一个后缀字母“A”;对Unicode版的函数,在函数名的最后都有一个后缀字母“W”。例如同时存在GetComputerNameA和GetComputerNameW两个函数,这两个函数的功能相同,都是获得当前系统的计算机名称,它们的区别是版本不同,分别是ANSI版和Unicode版。为了让程序的兼容性更好,一般建议使用ANSI版的函数。这样,如果要获得计算机名称,就要调用GetComputerNameA函数,在函数名称的最后都要多写一个字母“A”,使用起来不方便,于是可在声明时将此函数改名为“GetComputerName”,并在Alias子句后面说明真实的名称“GetComputerNameA”: Private Declare Function GetComputerName Lib "kernel32" Alias "GetComputerNameA" (ByVal lpBuffer As String, nSize As Long) As Long 此外,通过这种方法,还可以实现对同一API函数的多个声明,以便在调用时可以传递不同类型的参数。例如声明创建文件夹的CreateDirectory函数: Private Declare Function CreateDirectorySecu Lib "kernel32" Alias "CreateDirectoryA" (ByVal lpPathName As String, lpSecurityAttributes As SECURITY_ATTRIBUTES) As Long Private Declare Function CreateDirectoryLong Lib "kernel32" Alias "CreateDirectoryA" (ByVal lpPathName As String, ByVal lpSecurityAttributes As Long) As Long 通过以上两个声明可以在VB程序中使用两个函数,CreateDirectorySecu和CreateDirectoryLong。这两个函数的最后一个参数的类型不同,在CreateDirectorySecu的最后一个参数中要传递一个SECURITY_ATTRIBUTES自定义类型的数据,而在CreateDirectoryLong函数的最后一个参数中可以直接传递一个长整数。但这两个函数实际是同一个函数,都是库文件kernel32.dll中的函数“CreateDirectoryA”。 注意,Alias子句后面的库文件中的函数名是区分大小写的,而在VB程序中调用的函数的函数名是不区分大小写的。因此当存在Alias子句时,只需将Alias子句后面的函数名的大小写书写正确,而“使用函数名”可任意书写;当省略Alias子句时,只有“使用函数名”,且同时它也是库文件中的函数名,这时“使用函数名”的大小写就必须书写正确。 1.1.1.3 声明中为什么大量使用“Long” 读者可能已经发现,在API函数的声明中,大量使用“As Long”:很多参数都是“As Long”,函数的返回值大都也是“As Long”,为什么API函数对“Long”型变量这么“情有独钟”呢?原因是这样的,在Win32环境下,一个整数占4个字节,一个指针也占4个字节;在C或C++中,大量使用的整型(int型)变量也占4个字节。API函数中经常使用4个字节的指针或整数,许多函数的返回值也是4个字节的指针或整数,因此在VB中要调用这些API函数,也必须使用占4个字节的整型数据与之对应,即Long型数据;而不能使用占2个字节的Integer型数据。 1.1.1.4 声明中为什么大量使用“ByVal” 函数调用的参数传递,有“值传递”和“引用传递”两种传递方式。如果采用“值传递”,在函数内部改变了参数的值,主调程序的对应变量的值不会改变;而如果采用“引用传递”,在函数内部改变了参数的值,主调程序的对应变量的值也会发生相应的改变。“值传递”的实质是传递一个变量的“值拷贝”,“引用传递”的实质是传递一个变量的“地址”。当传递一个变量的“值拷贝”时,在函数中修改的是变量的“副本”拷贝,当然不会影响主程序的变量值;而当传递一个变量的“地址”时,函数中的参数和主程序的变量是同一块内存空间,自然在函数中修改值后主程序的变量值也被修改了。采用“值传递”或“引用传递”时,所传递的数据大小也有所不同:“值传递”时,所传递的数据大小与变量类型有关,变量占多少个字节,就传递多少个字节的数据;而“引用传递”时,只传递变量的地址,地址都是占4个字节的,也就是说无论变量是什么类型的,全部只传递4个字节的数据。 在VB中,在函数的参数前面加“ByVal”关键字表示采用“值传递”,在函数的参数前面加“ByRef”关键字表示采用“引用传递”;如果不加任何关键字,VB将默认采用“引用传递”,即ByRef。而在C或C++语言中,默认是采用“值传递”的。由于要调用的API函数大多是用C或C++语言开发的,函数的参数很多都被定义为“值传递”的方式,因此我们在声明API函数时必须在这些参数前添加“ByVal”关键字以声明为“值传递”;否则,如果省略“ByVal”,就会被VB默认按“引用传递”处理,函数就不能被正确调用。 1.1.1.5 声明这么长,如何能记住 API函数的声明中语法较多,书写的代码也较长,一般程序员是无法准确记住的。其实,根本不需要我们记住这些函数的声明,即使记住了,书写起来也会很容易出错。我们可以利用VB提供的一个程序——API浏览器,来轻松地在程序中“粘贴”API函数的声明。 “API浏览器”程序可以从开始菜单中的Visual Basic程序组中找到,图1-1是该程序的运行界面。通过该程序,可以自动将所需API函数的声明复制到剪贴板,然后再粘贴到我们自己的VB程序代码中。“API浏览器”除提供了大量API函数的声明外,还提供了API函数所需要的类型定义(Type类型)和常量定义,这些内容也可以通过同样的方式被粘贴到我们自己的VB程序代码中。 具体操作方法是首先选择要加载的文件,单击【文件】菜单的【加载文本文件】或者【加载数据库文件】均可,“加载数据库文件”原则上比“加载文本文件”速度快,但目前的计算机硬件配置已经很高,而API函数声明的文本信息量相对来说并不是很大,所以在目前主流配置的计算机上其速度的差别并不明显,所以读者完全也可以选择“加载文本文件”。在文件浏览对话框中,选择Visual Basic安装目录下的“COMMON\Tools\Winapi”目录中的WIN32API.TXT文件,该文件中包含了很多常用的API函数的声明、类型定义和常数定义。加载文件后,还可以选择【视图】菜单的【加载最后文件】选项,以便下次使用时“API浏览器”能自动加载该文件。 文件被加载后,先选择声明范围中的“公有”或“私有”,然后在“API类型”下拉列表中选择需要的内容,有“声明”、“常数”和“类型”三种,分别对应于API函数的声明、API函数所用到的常数定义以及类型定义。在下面的文本框中输入所需内容(如API函数名)的前几个字母,程序可自动在下面的“可用项”列表中选中此项。找到需要的内容后单击【添加】按钮,或双击此项,即可将声明或定义的代码添加到“选定项”的文本框中。可将多个API函数的声明、常数或类型定义添加到“选定项”的文本框中,然后单击【复制】按钮,将文本框中的内容一次性地复制到剪贴板,再在VB的代码窗口中粘贴即可。 1.1.2 使用API函数的注意事项 API函数是Windows的底层代码,直接调用API函数就是绕开了VB的“外壳”,直接和Windows操作系统打交道。虽然功能无限广大,但却脱离了VB的保护,危险也是不容忽视的。在VB环境下,如果程序出错,VB会给出提示并允许调试和修改;而直接调用API函数如果出错,Windows是不会提供任何调试和修改的机会的,这时VB的调试机制也无能为力,Windows会弹出一个“帅气”的出错消息框,毫不留情地把程序甚至整个VB“杀”死(如图1-2所示)。如果此时你的程序还没有保存,那么损失就大了。所以使用API函数,调试调用API函数的程序时,首先要注意的是一定及时存盘。单击VB【工具】菜单的【选项】命令,在【选项】对话框中的【环境】选项卡中,也可选择【启动程序时:保存改变】选项,使程序在每次运行之前VB都能自动保存程序,以免程序崩溃造成损失。 变量在使用之前定义与否,在编程中是非常重要的。无论读者在以前使用变量时是定义变量还是不定义变量,建议读者从现在开始一定在使用变量之前定义变量。这是非常重要的,尤其是在做大型软件项目的开发时,使用变量之前强制定义变量真的能避免许多错误。仍可单击VB【工具】菜单的【选项】命令,在【选项】对话框中的【编辑器】选项卡中,勾选【要求变量声明】选项,强制变量在使用之前必须定义。另外,还应尽量少用“As Any”的数据类型,以打开VB的变量类型自动检查功能。这样在很大程度上可以减少由于变量名拼写或变量类型不一致导致的错误。 在实际编程时,还要注意养成良好的编程习惯,就像在日常生活中“吃完饭要刷碗”、“借东西要归还”一样:如申请了内存空间,在使用后就要释放空间;用API函数打开了某种资源,在程序退出前就必须将它关闭;子类操作时,改变了窗口程序的地址,就要在程序退出前恢复窗口程序的地址等。这样可以大大减少API函数调用出错的概率。 此外,绝对禁止在程序代码中用End语句结束程序,更不能通过单击VB调试工具栏的结束按钮结束程序(或单击【运行】菜单的【结束】命令)。不论读者以前是不是这样做,但从现在开始请你不要再这样做了,因为这样会剥夺许多对象的资源释放操作,极易导致程序崩溃。应该通过卸载所有程序中的窗体和释放资源来结束程序,比如在用户单击【退出】命令或在主窗体卸载时用以下代码来结束程序: On Error Resume Next Dim frmEach As Form For Each frmEach In Forms Unload frmEach Next 在调试调用API函数的程序时,还要慎用VB调试工具栏的暂停按钮或按Ctrl+Break键(或单击“运行”菜单的“中断”命令),尤其是当程序中使用了“回调函数”时,因为这极易导致整个程序甚至整个VB系统被“挂起”,程序和VB就都没有响应了,如“死机”一般。如果遇到这种情况,靠单击菜单和工具栏解决已经无济于事了,可以按键盘上的F5键试一试,如果运气好,程序还可能能够继续运行;否则,只有用Windows的“任务管理器”强制关闭VB。出于同样的原因,在程序中也不要随便使用stop语句暂停程序的运行。 1.2 模块化编程 1.2.1 我能在一天之内做出一个软件吗 本书的初衷是面向实际编程,不仅要给出解决实际问题的方法,还要让实现方法的代码能被重复使用,尽量做到“一劳永逸”。编写程序和调试程序是一件复杂的工作,当编写和调试成功一段程序后,如果以后遇到同样或类似的问题还要重新编写和调试类似的程序段,那就是对自己劳动的极大浪费。所以,从一开始,我们就要重视编写“可重用”的代码,要考虑这段代码以后如何能被重复使用,而且能够被“非常方便地”使用。 要开发的一个软件对应于VB的一个工程,它是由一个个模块组成的,每个模块都是一个独立的程序单位,用以完成某个特定的功能。VB中的模块主要有窗体模块(文件扩展名为.frm)、标准模块(文件扩展名为.bas)、类模块(文件扩展名为.cls)等几种类型。那么, 遵循模块化编程的思路,把程序中经常需要的实现某个特定功能的代码结合在一起组成一个模块,并且将模块设计为“通用的”,即在任何VB工程中都可以使用的模块;这样以后如果需要这项功能时,就不必重复编程了,直接把该模块添加到工程中,再通过简单调用模块中的几个函数就可以达到目的。这是一个非常合理的编程开发思路,也就是模块化的自我代码积累。一个程序员也许在做第一个程序、第二个程序时工作量很大,许多代码都要自己编写;而如果他遵循了这种代码积累的思路,随着编写的程序越来越多,积累的代码也越来越多时,以后的开发就会变得越来越容易,开发的速度也会越来越快。当代码积累到一定程度时,再为开发一个新软件所做的工作量就会很少,开发速度也会变得非常迅速。因为他只要把所需功能的相关模块添加到工程中,再编写少量针对特殊需求的代码即可完成一个项目,所以在一天之内做出一个软件真的不奇怪! 1.2.2 用标准模块还是类模块 在VB中,程序员既可以使用标准模块(文件扩展名为.bas),也可以使用类模块(文件扩展名为.cls)。标准模块是一个纯代码性质的文件,它不属于任何窗体。在标准模块中可以定义若干过程或函数,其中被定义为公有(Public)的过程或函数可被工程中的任何窗体或其他模块调用。类模块同样是个纯代码性质的文件,也不属于任何窗体。类模块用于VB程序员定义自己的类,在程序中要通过创建类模块的对象实例来调用类模块中的代码。无论是标准模块还是类模块,在VB工程中,都既可以使用新建的标准模块或类模块,也可以把以前设计好的标准模块或类模块添加进来直接使用,而后者正是我们所倡导的,因为它可以大大提高程序的开发效率。 那么在编程时,究竟应该使用标准模块还是类模块呢?这不能千篇一律,要根据具体问题确定。类模块是“类”,它与标准模块的最大区别在于类模块中不仅可以包含过程或函数的代码,还可以包含数据,以及能够产生事件。类模块中的过程和函数还都不能被直接调用,而必须先创建一个类模块的对象实例,然后通过对象实例才能调用。 建立类模块是一种面向对象的方法,这也是一种“时髦”的方法,但我们的程序不能以追求“时髦”为目的,而要根据具体问题的情况,以解决问题的快捷和方便为宗旨。比如要增强滚动条控件,因为在一个程序中可能要同时使用多个滚动条控件,那么使用类模块,就可以分别创建类模块的多个对象实例,让每个对象实例去增强和管理一个滚动条控件,程序代码清晰,程序运行起来效率也会提高。 而如果要编写一个字符串内容替换的函数,把函数放到标准模块中,再设为公有(Public)就可以了,在程序中需要时直接通过函数名调用即可。而如果要把函数放到类模块中,在使用时就必须首先创建一个类模块的对象实例,然后再通过对象实例调用函数,最后还必须用“Set 对象=Nothing”释放引用,这无疑多此一举。而且建立对象、释放对象的操作也要占用很多系统资源,程序的运行效率也会因此受到影响。这未免就得不偿失了。 此外,需要获得函数地址的函数,也要放到标准模块中,因为VB的AddressOf运算符只能获取标准模块中函数的地址。 在实现一些涉及用户界面的程序功能时,有时还需要直接把代码放到窗体模块中。这样整个窗体(包括窗体上的控件、控件的属性和窗体的代码)都将作为一个可重用的通用模块,这也是通用模块的一种类型。今后在编程需要时,直接把窗体添加到工程中即可。 1.2.3 怎样创建类模块 创建窗体和创建标准模块都非常简单,只要单击VB【工程】菜单的【添加窗体】或【添加模块】即可。其实创建类模块也不难,同样是单击【工程】菜单的【添加类模块】。创建类模块后还要在属性窗口中给新类模块命名,默认名称为“Class1”,这个名称是创建对象实例时“类”的名称(就像变量类型的名称),所以一定要命名正确,且便于记忆。 1.2.3.1 创建“方法” 假如已经创建了一个名为“Class1”的类模块,在类模块的代码中编写一个过程如下: Public Function Test() As Boolean MsgBox "Test!", vbInformation Test = True End Function 在类模块中创建的过程,无论是Sub过程还是Function过程都称为“方法”,可以通过以下方式在标准模块或窗体模块中调用这个Test方法: Dim objClass1 As Class1 Set objClass1 = New Class1 Debug.Print objClass1.Test Set objClass1 =Nothing 上述程序首先定义了一个Class1类型的对象变量:objClass1,其定义方式与定义普通变量类似。然而定义对象变量并不等于创建对象实例,系统还没有为objClass1分配内存,这与定义普通变量(如定义一个整数)是不同的。所以执行“Dim objClass1 As Class1”后并不能立即使用objClass1,还要再执行“Set objClass1 = New Class1”,才能真正创建一个对象实例,objClass1才能被使用。使用时通过“objClass1.Test”调用类模块中的Test方法的代码,屏幕将显示一个消息框;返回到主程序后,在立即窗口中显示“True”。 如果没有“立即窗口”,可单击VB【视图】菜单下的“立即窗口”命令,将该窗口显示出来。本书后面的程序还要频繁地使用这个窗口,所以现在就让它一直显示,不要关闭它。 最后一条语句“Set objClass1 =Nothing”表示释放对象引用,以便销毁对象和释放对象所占用的系统资源。一个对象在使用后,应该用“Set 对象名=Nothing”释放对象引用。就像日常生活中“借东西要还”、“吃完饭要刷碗”一样,既然通过“Set objClass1 = New Class1”创建了对象,使用后就要再释放它。尽管在对象变量生存期结束时,VB会自动释放对象,但通过Set语句显式释放对象是个很好的编程习惯,尤其是在程序中对象很多且关系很复杂时,它不仅能及时地释放资源,更能消弭许多错误于无形。 1.2.3.2 添加“属性” 类模块还有一个不同于标准模块的特点是,在类模块中允许定义属性。属性是通过Property Get过程和Property Let(或Property Set)过程来定义的,分别对应于同一个属性的“取值”和“设置值”。在这两个过程中还可以编写在“取值”和“设置值”时需要被执行的代码。一般地,在类模块中可定义一个私有(Private)的模块级变量,通过此模块级变量保存属性值。需要获取属性值时,直接返回此变量的值;需要设置属性值时,通过该变量保存新值。例如在类模块中定义一个Boolean类型的AutoClose属性的代码可以是: Dim lAutoClose As Boolean '此行要在类模块的声明部分 Public Property Get AutoClose() As Boolean AutoClose = lAutoClose End Property Public Property Let AutoClose(ByVal vNewValue As Boolean) lAutoClose = vNewValue End Property 上述例子中首先定义了一个类模块的私有模块级变量lAutoClose,然后通过Property Get过程和Property Let过程分别定义了获取属性值和设置属性值的代码。注意Property Get过程和Property Let过程的过程名要一致,这里都是AutoClose,该名称也是所定义的属性的名称。Property Get过程的返回值类型就是属性的类型,在过程代码中,只返回了模块级变量lAutoClose的值。Property Let过程没有返回值,但多出一个参数vNewValue,该参数就是主调程序设置属性时的设置值,该参数的类型也要和属性的类型保持一致,本例中都是Boolean。在Property Let过程的代码中,只是简单地将模块级变量lAutoClose赋值为属性的设置值vNewValue。如果要定义一个只读属性,那就只编写Property Get过程,而不编写Property Let过程。 在本书中,除特殊需要外,在类模块中定义属性基本上都是沿用上述这种命名约定和定义方式:即在属性名的前面加一个小写的字母“l”前缀(为local的首字母),作为模块级变量名;在Property Get过程和Property Let过程中,使用此模块级变量来分别返回数据和保存数据。在本书的随书光盘中,还提供了一个笔者编写的小程序“类属性代码生成工具”,通过它可自动生成属性的Property Get过程和Property Let过程的代码,然后再“粘贴”到VB中就可以了。 在主调程序中要使用类的属性时,直接用“.属性名”引用,像使用许多控件的属性一样,例如: objClass1.AutoClose = True '设置属性 Debug.Print objClass1.AutoClose '获取属性 定义类模块的属性时,还可以定义带参数的属性,如下面例子: Private lStates(1 To 10) As Double Public Property Get States(ByVal idxState As Long) As Double States = lStates(idxState) End Property Public Property Let States(ByVal idxState As Long, ByVal vNewValue As Double) lStates(idxState) = vNewValue End Property 这里定义了一个带参数的属性States,它包含一个参数idxState。同时定义了模块级数组lStates(1 To 10)用于保存属性值,根据参数idxState的不同,将返回或设置数组lStates()的不同下标元素的值。定义带参数的属性时,要保证Property Get过程和Property Let过程的参数一致,即在两个过程中都要包含同样类型、同样个数的参数(本例中为一个Long型的idxState参数),而Property Let过程总是再多出一个vNewValue参数。 在主调程序中使用带参数的属性时,在属性名的后面要同时给出参数,例如: objClass1.States(2) = 2.5 Debug.Print objClass1.States(2) States属性可以有1~10的10种不同的参数,上述例子中给出的是2,实际设置和返回的是类模块的模块级数组lStates(2)元素的值。 1.2.3.3 引发“事件” 类模块的对象实例还能引发事件,就像VB中的各种控件的事件一样:如命令按钮CommandButton可以引发Click事件、文本框TextBox可以引发Change事件。要使自己编写的类模块的对象也引发事件,需要首先在类模块的声明部分定义事件的名称和参数,例如: Public Event MyEvent(arg As Long) 在类模块的代码中,事件是通过调用RaiseEvent语句引发的。这需要根据具体程序的需要,在代码运行到适当时候引发事件,例如在类模块内当读写完毕一个文件时,就可以用RaiseEvent语句引发文件读写完毕的事件。如要引发MyEvent()事件,可在类模块的任意过程或函数中添加如下代码: Dim lngTest As Long lngTest = 1 RaiseEvent MyEvent(lngTest) 参数arg是做什么用的呢?这也要依据实际问题的具体需要而决定,例如在文件读写完毕的事件中,可以通过此参数传递已读写的字节数。本例中只是将局部变量lngTest的值传递到参数中。 在主调用程序中使用类模块对象实例的事件时,与使用VB控件的事件是类似的(如命令按钮的Click事件)。但首先要定义类模块的对象变量,且在定义时一定要添加“WithEvents”关键字。这类似于在窗体中画出一个命令按钮,只不过在这里不是画出,而是通过代码定 义的: Private WithEvents obj As Class1 然后就可以像使用VB控件的事件一样,在代码窗口上方的“对象”下拉列表中选择对象变量obj,再在“过程”下拉列表中选择对象变量obj的事件MyEvent,如图1-3所示。VB系统会自动为我们生成事件子程序的代码框架: Private Sub obj_MyEvent(arg As Long) …… End Sub 然后就可以在其中编写处理事件的代码了。 图1-3 在窗体代码中使用自定义类模块对象的自定义事件 1.2.3.4 类模块的Initialize和Terminate事件过程 类模块的Initialize和Terminate事件与上文提到的对象实例的事件不同,这是类模块本身的内置事件,它不是由程序员定义的,也不是用RaiseEvent语句引发的,它们将分别在对象被创建和销毁时自动引发。在Initialize事件中可以包含对象在被创建时所需执行的代码,在Terminate事件中可以包含对象在被销毁后进行清理的代码。这两个事件有些类似于C++语言中类的构造函数和析构函数。 这两个事件的处理代码也应该被编写在类模块本身的代码中,而不能被编写在窗体模块或其他模块中。例如,可以在类模块中编写下列代码: Private Sub Class_Initialize() Debug.Print " Class_Initialize()事件:对象被创建" End Sub Private Sub Class_Terminate() Debug.Print " Class_Terminate()事件:对象被销毁" End Sub 在对象被创建和销毁时,在VB的立即窗口中都会输出一行文字。 这两个事件很有用,Initialize事件可以用于对象刚刚被创建时各个变量的初始化、内存空间的预分配和其他准备工作;Terminate事件可以用于对象被销毁时的资源释放工作,如释放占用内存、恢复指针、释放API函数的对象句柄等。在以后的章节中,读者还会看到本书中的许多类模块都充分使用了这两个事件。 1.2.3.5 实现类模块的“继承”和“多态” 在面向对象编程中,“继承”和“多态”是两个重要的概念。虽然VB的类模块不直接支持“继承”和“多态”,但这并不影响我们的编程,在后面的章节中将介绍如何在VB中“变通地”实现“继承”和“多态”。这将结合具体实例讲解,特别是这其中还要用到第二章要介绍的对象“弱引用”的知识。具体实现方法还请读者参阅后续章节。 1.3 编程风格 高效的程序和良好的编程风格是分不开的。当编写一个简单的程序时,不大注意编程风格和程序的效率似乎对程序运行也没太大影响;然而当开发大型软件时,编程风格就不能被忽视。即使计算机的硬件配置很高,一个设计糟糕的程序运行起来也会“慢得像蜗牛一样”,程序效率严重低下,甚至导致错误频发。 因此首先,我们要养成良好的程序设计风格,尤其是在开发通用模块时。因为这些模块将来很有可能要被大量调用,并且也有可能要被以后新开发的其他通用模块调用。必须把模块内可能出现的错误降到最低,否则模块中在当时一个不起眼的错误在将来就极有可能被“扇形放大”,错误发生而不可收拾,最终导致整个软件项目的失败。 优秀编程风格所要遵循的原则非常多,本节仅总结了以下至少应该遵循的几个方面。 1.3.1 变量在使用前一定要定义 VB允许变量不必预先定义就可以直接使用,这给初学者带来了很大的方便,但也使他们养成了不定义变量就直接使用的习惯,这是一个非常不好的习惯。因为这避开了VB的变量名和类型的自动检查,会导致许多不该发生的错误发生。所以,我们必须强制自己在使用变量之前先定义变量。方法是在每个模块代码的起始部分添加如下语句: Option Explicit 这样就能在本模块内强制变量必须先定义后使用。也可单击VB【工具】菜单的【选项】命令,在【选项】对话框的【编辑器】标签中勾选“要求变量声明”选项,这样在新建的模块中,VB会自动添加上述语句,但在已经创建的模块内不会自动添加,需要我们手工输入。 1.3.2 尽量不要用Variant数据类型 所有没有被显式声明为其他数据类型的变量,或者被直接声明Variant的变量,都是Variant 类型的,这是一种可以被赋值为任何类型数据的类型。Variant 数据类型给编程带来了许多方便,然而它也是程序出错的一个“罪魁祸首”,因为Variant同样避开了VB的数据类型自动检查功能。此外,Variant 数据类型的效率极低,因为无论存储哪种类型的数据,都要占用16个字节的存储空间,不仅浪费内存,而且CPU运算起来也很“费劲”,需要进行大量的数据转换工作。程序中应该根据实际需要,使用Integer、Long、Single、Double、Boolean等基本的数据类型或通过Type语句定义的自定义类型,而少用或不用Variant 数据类型。 1.3.3 代码的缩进和换行 坚持代码缩进的编程风格,编写出的程序会显得思路清晰、严谨规范,便于调试和维护,也降低了出错的概率。代码缩进包括If、Select Case等分支语句的缩进,for、While、Do-Loop等循环体语句的缩进,函数名和函数体之间的缩进等。要将数行程序同时向右或向左缩进一个层次,不必为每一行分别输入跳格(Tab)或空格,只要同时选中这些行,再按一次Tab或Shift+Tab键即可。 此外,代码的换行也很重要,要尽量将各行的代码控制在一屏宽度能够显示的范围内,使查看代码时能够尽量不必拉动水平滚动条。如果语句很长,一屏宽度不够显示,要用“续行符”(“空格”+“_”)把代码分别写在几行中。在书写注释的时候也要沿用这个风格,使注释能够在一屏的宽度内显示完全。 在定义函数时,如果函数的参数很多,也要用“续行符”将每个参数写在一行内,并保持各参数的名称对齐,这样可使函数的定义更加清晰,例如: Public Function CVMyWndProc(ByVal hwnd As Long, _ ByVal Msg As Long, _ ByVal wParam As Long, _ ByVal lParam As Long) As Long 1.3.4 避免重复用对象名称做一串调用 很多时候可能要连续使用同一个对象的几个不同的属性或方法,很多VB程序员喜欢重复使用对象的名称来做一串调用。例如,窗体上有一个文本框控件Text1,编写代码如下: Text1.Text = "abcd" Text1.Enabled = True Text1.SelStart = 0 Text1.SelLength = Len(Text1.Text) Text1.SetFocus 这段代码看上去没有什么问题,但它会导致最终可执行文件的体积大大增加,而且使程序的运行效率非常低。因为在每次调用“Text1.”时VB都会进行一次很复杂的操作,以“绑定”到Text1对象,然后才会调用Text1对象的某个属性或方法。这个绑定操作比实现Text1对象的属性或方法的代码还要庞大许多倍,这无疑是一种巨大的浪费!当你的程序很庞大,需要调用的对象属性和方法很多时,程序的效率将会严重低下。正确的做法是使用With…End With语句结构,例如上述代码应该被修改为: With Text1 .Text = "abcd" .Enabled = True .SelStart = 0 .SelLength = Len(.Text) .SetFocus End With 这样除使代码简短、清晰、易于维护外,更重要的是使程序的运行效率大大提高。因为上述代码将复杂的对象“绑定”操作只执行了一次,然后一气呵成地调用了所有需要调用的属性和方法。 使用With…End With语句结构的缺点是一次With语句只能简化一个对象的操作,如果要在一段代码中同时用到两个或更多的对象,With语句就无能为力了。那么在这种情况下应该如何做呢?这时应该定义一个局部的对象类型的变量,然后用Set语句将要操作的对象赋值给该对象变量,之后可通过该对象变量做连续的属性或方法调用。例如: Dim objText1 As TextBox Set objText1 = Text1 objText1.Text = "abcd" objText1.Enabled = 1 objText1.SelStart = 0 objText1.SelLength = Len(objText1.Text) objText1.SetFocus Set objText1 = Nothing 这段代码看起来和第一段很类似,然而它的运行效率要高很多。因为复杂的对象“绑定”在这里也只被执行了一次,即是“Set objText1 = Text1”的时候;而通过变量objText1调用属性和方法时则没有绑定操作。 又如,下面的代码笔者每次看到时都会感到非常“畏惧”: ListView1.View = lvwReport ListView1.ListItems.Add , "L1", "Text1" ListView1.ListItems("L1").ToolTipText = "ToolTip1" ListView1.ListItems("L1").SubItems(1) = "Sub1_1" ListView1.ListItems("L1").SubItems(2) = "Sub1_2" ListView1.ListItems("L1").Selected = 1 这些代码运行起来速度会非常慢,且难于维护。虽然实现这些功能在VB中所编写的代码并不多,但这并不意味着代码执行起来速度会很快。正确的做法是使用With…End With语句块以绑定ListView1对象,再定义一个ListItem型的对象变量用于指向新添加的一个ListItem项目,即: Dim lsItem As ListItem With ListView1 .View = lvwReport Set lsItem = .ListItems.Add(, "L1", "Text1") lsItem.ToolTipText = "ToolTip1" lsItem.SubItems(1) = "Sub1_1" lsItem.SubItems(2) = "Sub1_2" lsItem.Selected = 1 Set lsItem = Nothing End With 在使用With…End With语句块时,还要注意的是显式或隐式地跳出With块所带来的“负面影响”,例如下面的程序段会产生错误: Private Type ArrType long1 As Long single2 As Single End Type Private Sub Command1_Click() Dim arr() As ArrType ReDim arr(1 To 10) With arr(10) .long1 = 1 .single2 = 2.5 GoTo Finish End With Finish: ReDim arr(1 To 5) '该语句会产生错误 End Sub 程序中用With语句绑定了数组元素arr(10),但在执行End With之前又用Goto语句跳出了With块(这是显式跳出,也有可能当在程序中指定“On Error Goto Finish”后,在With块内、End With之前出现错误,程序也会跳转到Finish),在Finish后面的语句中试图改变数组arr 的大小,这时就会引发错误,如图1-4所示。在执行“With arr(10)”后,数组被锁定,只有在执行“End With”后才能被解锁;而在没有被解锁之前重新改变数组大小自然会引发错误,这在实际编程时要注意。 1.3.5 Boolean型条件表达式的写法 下面再来看一段笔者经常见到的程序: If Command1.Enabled = True Then Text1.Text = "OK" 在If语句后面的条件表达式为“真”时就会执行Then后面的语句,所以这里完全没有必要再判断“真是否为真”。高效的写法是: If Command1.Enabled Then Text1.Text = "OK" 又如,下列程序也是低效的写法: If lngFlag <> 0 Then Text1.Text = "OK" 高效的写法是: If lngFlag Then Text1.Text = "OK" 因为非0值都表示“真”,只有0值才表示“假”。lngFlag不为0,本身就表示“真”。 1.3.6 字符串为空串的判断 判断一个字符串是否为空串时,有不少程序员喜欢用字符串是否等于空串("")来判断: If strFile = "" Then Exit Sub 这种方式效率是比较低的,因为系统必须做一个字符串的比较。比较高效的写法是用字符串的长度是否为0来判断: If Len(strFile) = 0 Then Exit Sub 因为在VB中存储字符串数据的机制与C/C++不同,有一个专门的内存 单元 初级会计实务单元训练题天津单元检测卷六年级下册数学单元教学设计框架单元教学设计的基本步骤主题单元教学设计 用来保存字符串的长度。因此在VB中获取一个字符串的长度是一件非常容易的事。我们要利用这个特点,善于使用获取字符串长度的函数Len,其效率要比直接做字符串比较提高许多倍。 1.3.7 能用常量就不要用函数求值 下面的代码在每次运行时都要调用两次chr$函数并做一次字符串的连接操作: If strKey = Chr$(13) & Chr$(10) Then strText = strText & " " 其实VB中有一个常量:vbCrLf,其值就是Chr$(13) & Chr$(10),因此上述代码完全可以简化为: If strKey = vbCrLf Then strText = strText & " " 1.3.8 数组下标从1开始 VB的数组下标可以从任何数值开始,笔者这里提倡的是要让数组下标从1开始。这也有悖许多程序设计语言的规定,例如C++、C#甚至VB.Net中都规定了数组下标应该从0开始。从0开始是因为下标为0的元素也占用内存空间,不能浪费。然而,我们在日常生活中许多事物的编号都是从1开始的,如第1个文件、第1个学生、第1台电脑……,如果在程序中也能从1开始编号数组下标将会非常方便,这使数组下标值与实际生活中的事物编号互相统一,即使牺牲一个下标为0的空间也是值得的。我们真的没有必要为了节省一个存储空间,而不得不在程序中使用i+1来引用数组元素,并记得用for
本文档为【《老兵新传:Visual Basic核心编程及通用模块开发》样章043360-01】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_136668
暂无简介~
格式:doc
大小:271KB
软件:Word
页数:20
分类:
上传时间:2013-04-03
浏览量:28