⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 感悟visualbasic(8).txt

📁 学习(编程技巧_编程知识_程序代码),是学习编程不可多得的学习精验
💻 TXT
字号:
感悟VisualBasic(8)
 

--------------------------------------------------------------------------------
 
广东 张鸿 时间:2003-11-28 10:08:39 
   
第十二话再论字符串
在前面我几次提到VB中对字符串的处理比较特别,但我当时并没有对此进行解释,现在我们已经学到了足够的知识,我可以为你揭示这里的特别之处了。这一期的内容主要是解决前面留下的许多有关字符串知识的疑问的,有点抽象,较难理解,请一定参考我的源程序,并对不懂的地方反复阅读,或者自己写程序反复验证。要有心理准备哦!
第一部分:变量的分配顺序
首选要告诉你的是,VB中的变长字符串变量,其实是一个32位的变量:长型的变量——它只有4个字节,它的内容是某个字符串在内存中的地址。并且我非常肯定的告诉你,这是正确的,即使以前没有人告诉过你。
事实上,C和C++中从来就没有过一种真正可以随时改变大小的字符串变量,一切具有这种功能的变量都是在内部进行了特殊处理,用来保证字符的存放正确,但实际上,C和C++的标准中,它仍然是一个普通的变量。VB也一样,它只是把这种特殊处理集成到内部了,不用我们自己设计方法来实现,然而,这些字符串在VB的内部还是和在C、C++中一样的。
在向你证明这一点之前,我想我需要讲一点VB程序的内存数据的存放知识。
例如我有5个整型的变量,值分别是3345、1024、90、80、70,当我定义之后,它们是按我定义的顺序(如图1)存放的。越先定义的变量,被分配的内存地址就越靠后。而且你可以看到这些地址是连续的,一个整型变量占2个字节的内存,所以内存地址的偏移也是一次偏移2个字节。既然是这样,那就可以理解为什么程序是不能真正改动变量的大小的,因为它们必须保持连续,并且其他变量占内存的大小也是不可以改动的。
那么实际上VB是如何处理变长字符串的这些问题的呢?我现在来做一个实验(图2),让你看看其中的秘密。我先解释图2中各行的含义:在我的程序中,我按这样的顺序定义了这些变量:
  Dim lngHeader As Long,StringA As String,LongB As Long,IntegerC As Integer
  Dim Longer String As String*19,lngStringA As Long
每一行打印的结果是这样的:如果是字符串,则第一、二个数值是变量的地址;如果是其他变量,则第二个数值是空白的,第三、四个数值是字符串的长度或变量所占内存空间。等一会儿再向你解释这些问题。
我们先来看每一行的第一个数值。对照我定义的顺序,你可以看到越是前面定义的变量,它的地址就越靠后,而且基本上都按顺序排列了起来。你看Header的地址和StringA的地址之差,就知道Header是在StringA偏移4字节的之后存放的。我打印了两次这两者的地址,第一次是StringA定义后还没有赋值,第二次是为StringA赋了“asdfggggg”之后(已经超出了4个字节了),结果是两次的地址完全一致,因此可以知道VB中变长字符串变量即使赋了值,变量的大小仍没有变。而定长字符串变量(19个字节)和前一个变量的内存地址差也可以看出VB是为其赋了足够内存空间了,也验证了以前说过的定长字符串变量在定义时就分配空间的说法。
第二部分:字符串变量和字符串
你觉不觉得这个题目有点怪?字符串变量不就是用来存放字符串的吗?还有什么好说的?但在VB中的变长字符串已经不是这样了。
图2中我打印StringA的结果那一行中,我是按顺序打印VarPtr(StringA)、StrPtr(StringA)、Len(StringA)、LenB(StringA)这四个结果的,你也看出来了,上一期我说过的VarPtr和StrPtr以及取变量长度的Len、LenB都在这里出现。可能上一期你还有疑惑,为什么有了VarPtr,还要有StrPtr?这里你将会明白。
第一次打印StringA的结果,StrPtr、Len、LenB都打印出0;第二次(赋值后)却有数值,这就是它们的区别。VarPtr只是得到变量的地址,但是VB中的变长变量不存放字符串,所以StrPtr得到的结果就和VarPtr不同。在赋值之前,VB的变长字符串初始值是NULL——ASCⅡ码为0的字符(该字符在内存中的地址永远是0),长度自然也是0;赋值之后,这个变量的地址没变,但“字符串的地址已经变了”,这个变量已经指向了另一个字符串,所以StrPtr才是真正我们想要得到的字符串的地址。这段话有点难理解,对照程序反复看几次吧。
第一个疑问解决了吗?那么第二个:为什么用Len和LenB得到的字符串类型以外的变量的大小相同?Len是得到变量(或自定义变量)所占内存的大小,LenB是得到变量(或自定义变量)所占内存的实际大小,由于Integer、Long等类型并无特殊之处(不包含隐含的转换之类的),所以Len得到的大小就是它们的实际大小,不会和字符串一样。
第三个:无论定长、变长的字符串,LenB都是Len的双倍大小(第三、四个结果的比较)?对于UNICODE字符(VB内部所有字符的存放形式),这是正确的,因为每个字符都占两个字节的内存,所以它所占内存的大小就是字符总数的两倍。
好了,我想现在你知道如果处理变长字符串变量和它指向的字符串了。那么……你有第四个问题?为什么VarPtr和StrPtr对定长变量的运算的结果不同?我……其实……我也不知道(哎,连这都不知道还来混?)。但是经过我多次的测试,发现VB对定长变量的处理也不是如我们所想的那么简单,这种变量被分配的空间很特别,空间不一定是连续的,并且有一个类似某种文件的文件头(用来记录这个文件的结构)一样的空间,记录了内存中某几个定长变量的的位置、结构等,更奇怪的是当定长变量有多个的时候,甚至取地址运算得到的结果有可能指向同一个内存地址,而即使这种情况下,Len或LenB运算却仍然可以准确判断出我们要得到哪一个变量的大小。你可以看图3,当我定义的变量改为:
  Dim lngHeader As Long,StringA As String,LongB As Long,IntegerC As Integer
  Dim lngHeader2 As Long,Longer String As String*19,lngStringA As Long
  Dim lngHeader3 As Long,Longer String2 As String*30,lngStringB As Long
之后,LongerString和LongString2处于同一个地址,但VB仍然可以正确识别它们。并且它不按定义的顺序,而是总是在最前面,由于它的介入,变量的排列显得有点乱。
也是因为这种原因,我尝试通过对StrPtr的深入研究希望从中看出关键所在也失败了。但是先停住,我们真的需要深入研究下去吗?我很怀疑实际使用中遇到这个问题的可能性(没有一个API要求你传递由自己运算的定长字符串变量所指向的字符串的地址),既然不会用到,那么我们不如不去理它。而从我的猜测出发,对于这两者的结果不同,大概就是因为变长字符串变量含有变长字符的地址,而定长字符串变量的结构比较复杂,StrPtr取到的不是正确的地址。也许只有这样理解,才可以解释这个问题吧。
第三部分:小心使用变长字符串地址
回到图2,最后的两行我是这样写的:
  CopyMemory lngStringA,StringA,4
  Print "lngString & StrPtr(StringA):1-",lngStringA,StrPtr(StringA)
  CopyMemorylngStringA,ByValVarPtr(StringA),4
  Print "lngString & StrPtr(StringA):2-",lngStringA,StrPtr(StringA)
这段程序我尝试通过CopyMemory把StringA中记录着的字符串地址复制到lngStringA中,如果你仔细思考,你会觉得我上面第一行的程序并没有问题,但是在图中反映出来的结果却是错误的(应该和StrPtr(StringA)相同才对),但是当我直接把变长字符串变量地址传递给CopyMemory却能得到正确结果,从理论上讲,CopyMemory是按引用传递值的,它应该可以得到StringA的地址,但也许由于VB内部对字符串的特殊处理,我们反而无法把StringA当成一个长型值进行直接处理,也可能因为如此,定长字符串变量虽然结构复杂,却在总是可以不必担心传递给API时出错,而当成简单变量直接使用。这一点,我们只需要在如上面这种场合中多加注意即可,即取回“变长字符串变量指向的字符串的指针”时,直接使用StrPtr,或自己先运算变量的地址再复制到长型变量中。
第十三话按值传递与按引用传递
我曾几次提到VB中的按值传递和按引用传递,现在我就在这里讲清楚。
按值传递,就是在调用函数或子程序的时候,把一个变量的内容复制到另一个新变量中,再在被调用的函数或子程序中使用这个新变量,因此不会影响到原有的变量;而按引用传递就像为变量定义一个假名一样,通过这个假名对变量的值更改,实际等于对原变量的值更改。从理论上讲,VB中的引用传值应该是不会新定义一个变量再分配值的,所以速度应该比按值传递快,特别是在字符串传递方面。
在图4的第一对结果中,第一行是我分别按值和按引用传递一个长型变量给子程序(被调用函数),并在子程序中取两个变量的地址,而第二行是在调用函数中取到的变量地址,可以看到按值传递的变量和原变量是不同变量,而按引用传递和原变量是同一个变量。子程序如下:
  Sub pTest (ByVal lVal As Long,ByRefp Val As Long)
  Print "VarPtr(lVal):",VarPtr(lVal),"pVal:",VarPtr(pVal)
 End Sub
第二对结果中,是对变长字符串变量按引用传递的比较,可以看到被调用函数中的变量和原变量是同一个变量,并且指向的字符串也是同一个。子程序如下:
  Sub pTestStr(ByRefp Val As String)
  Print "pTestStr",VarPtr(pVal),StrPtr(pVal)
 End Sub
第三对结果和第二对一样,只是传递的是定长变量,虽然可以知道被调用函数中的变量和原变量是同一个变量,但通过StrPtr得到的结果是不同的,可以印证上一话我们的猜测。
好了,这一期的内容就这些,第十二话的内容比较难,如果无法一下子理解,就多写程序,多比较吧,写这一话的内容其实我也挺累的,VB的内部实在很复杂。虽然这一期没有什么新的函数介绍,但不仅在字符串方面,对其他方面的问题,这些知识也是有用的(如果你想更好地使用CopyMemory等可操作内存的函数的话)。以后要是遇到这种情况,相信靠这些理论可以帮上很大忙。
文中程序在Win98/Win2000+VB6下调试通过。源程序下载地址是:http://www.cfan.net.cn/qikan/cxg/0208gwv.zip。
 
 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -