ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 122460|回复: 168

[分享] VBA高级教程之基础篇:文本编码和字符串处理(包括指针),ADODB.Stream转换文本编码

  [复制链接]

TA的精华主题

TA的得分主题

发表于 2013-3-31 17:52 | 显示全部楼层 |阅读模式
本帖已被收录到知识树中,索引项:文本处理和正则
本帖最后由 liucqa 于 2014-8-15 22:05 编辑

一、理解什么是文本
       文本是一种由若干行字符构成的信息表达方式。提到文本(也就是我们常说的字符串),不得不说,这是一个艰深的问题。常写代码的童鞋,几乎都曾被莫名的字符串乱码搞的火冒三丈。

       为什么会出现文本乱码?   
       答:是因为计算机不认识这个信息,所以不能在屏幕上画出来你认识的文字。


       为什么我堂堂中华大国的文本,计算机会不认识?
       答:计算机键盘只有26个字母+10个数字,所以,理所当然计算机就只认识这几十个字符。谁让电脑是西方人发明的呢!想让电脑认识汉字文本,我们就得通过计算机认识的规则来做某种实现约定的变换,并告知计算机这种变换的规则和对应显示的东东,然后计算机就能认识汉字文本了。这就是所谓的字符编码。

      所以,想理解什么是文本(字符串),你需要理解以下知识:
      1、计算机先天就认识的字母+数字,是怎么编码和显示的?
      2、汉字(或者其他国家的文字)是怎么编码和显示的,常用的编码有多少种?
      3、这种用来显示人类能够看懂的符号信息(就是通常所说的文本)的编码,在计算机内部是如何存储的,在互联网上又是如何传输的呢?
      4、不同的操作系统和不同的编程语言版本,其支持的编码种类和编码在计算机内部存储的方式一样吗?
      5、不同的编码(甚至同一个编码在不同的计算机语言里面)之间如何识别?如何转换?


       如果你想让计算机显示你需要的信息,就要告诉计算机你打算采用什么种类的编码,并按照编码约定,提供二进制数字给计算机。如果你的计算机能够识别这种编码的话,你就会看到期盼已久的文本了!


说明:本文面向初学者简单介绍文本编码知识,如果你希望向专业方向发展,或者想了解更详细的信息,请浏览各编码标准的官方网站。
          或者参考 http://www.crifan.com/files/doc/ ... /char_encoding.htmlhttp://blog.csdn.net/pcxjx/article/details/5375097
         
          如何判断CJK汉字,参考 http://club.excelhome.net/thread-906973-1-1.html,此贴内容是本文的更详细知识扩展介绍。         

          此外,本文会略微涉及VB的指针知识,这并不说明本人赞成在VBA中使用指针,涉及指针知识,只是为了更好的说明VBA字符串在内存中的存储格式而已。

          本贴核心知识点在七、八、九楼。编码转换的参考代码在十二楼~十五楼。


char_encoding 2.2.rar (832.64 KB, 下载次数: 1934)


补充内容 (2013-6-26 22:21):
http://club.excelhome.net/thread-1026277-1-1.html  这个工具箱里面带有14种编码工具,供学习本贴参考

点评

非常不错的资料。  发表于 2013-4-1 16:06

评分

18

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-3-31 17:53 | 显示全部楼层
本帖最后由 liucqa 于 2013-4-1 14:22 编辑

二、ASCII编码---ASCII字符集

      所有在正规院校学习过计算机课程的童鞋,恐怕最早接触的就是这个编码了。这是除了二进制编码以外,在计算机中最为基础的编码。
      ASCII全称是:American Standard Code for Information Interchange (美国信息交换标准代码)。     
      标准ASCII字符集使用7位二进制数来表示所有的大写和小写字母,数字0到9、标点符号,以及在美式英语中使用的特殊控制字符(好多是从当年的打字机控制字符中继承过来的)。这个7位的字符集,对应的就是十进制的0~127。
      ASCII字符集包括标准ASCII字符集和扩展ASCII字符集

我们先来看一下ASCII的编码标准字符集(0~127)
ASCII.png

在这个标准字符集中,明确规定了只认识二进制的计算机其所存储的数字与字符之间的关系。例如:1000001代表A(十六进制的41/十进制的65)。所以,对计算机而言,如果你想让它显示A,只要告诉计算机显示编码为1000001的字符就行了,也就是告诉计算机显示十进制的65/16进制的41对应的字符即可。

ASCII编码,就是用来让计算机能够认识内存中存储的二进制数字与键盘上的26个字母+数字字符之间的关系的最基本的编码!

ASCII编码,是大部分计算机系统都支持的编码! ASCII标准使得只含有ASCII字符的文本文件可以在Unix、Macintosh、Microsoft Windows、DOS和其它操作系统之间自由交互,而其它格式的文件是很难做到这一点的。但是要注意的是,在这些操作系统中,换行符并不相同,处理非ASCII字符的方式也不一致。








评分

3

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-3-31 17:54 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
本帖最后由 liucqa 于 2013-4-5 09:42 编辑

三、ANSI编码(ISO8859、GB2312、Big5、JIS等等)

      在二楼的介绍中,我们讲解了计算机是如何显示键盘字符的(通常我们称为ASCII字符)。那么,我们伟大的中华民族的汉字多达十万个,常用的也有两三万,这又该如何显示呢?ASCII标准编码只有0~127,扩展编码128~255,完全放不下这么多汉字嘛。

      莫非,电脑发明的时候,就是给英语国家用的?  是的,你猜对了,最早电脑发明的时候,完全没有考虑这个世界上还会有其他的民族也会用到电脑,而这些民族的字符集会多达数万!有人问了,为什么当年的科学家不考虑地球的其他民族呢?俺猜想,这与当年存储器的容纳能力有关,如果采用一个字节存储英文字符,完全足够了。如果考虑其他民族,就要用两个甚至更多的存储空间来存放英文字符(以便兼容多民族字符),而为了这种兼容需要多支付的存储空间,其代价是当年的技术所不能承受的。

      后来,随着技术的进步,单位存储空间的费用已经降低到了一个可以接受的地步,为地球多民族的字符集做编码,终于变为了可能。ANSI(美国国家标准学会)在这样的背景下,出台了一系列字符集,这些字符集再加上其他国家为本国语言所设计的早期字符集,统称为ANSI编码。

      所以,提到ANSI字符集,你一定要知道,这是一个由许多不同国家的字符集所构成的一个字符集集合的统称,而不是一个特定的字符集。同一个操作系统在不同的国家的ANSI编码,就是这个国家的字符集编码。例如,欧洲国家用ISO8859(包括15个不同国家的子集)、中国大陆用GB2312、台湾用Big5、日本用JIS等等

      ANSI的子集,在不同的操作系统下是不一样的,这取决于操作系统所支持的字符集。例如,同样是简体中文,Win32下的ANSI是GB2312,Windows95是GBK。Windows7是GB18030(完整支持需要安装扩展字体)等





补充内容 (2013-4-27 00:08):
GB2312、GBK、 GB18030等都属于双字节字符集 (DBCS double-byte character set)。 DBCS是亚洲字符集,包括简中、繁中、日、韩。在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码。

评分

1

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-3-31 17:55 | 显示全部楼层
本帖最后由 liucqa 于 2013-4-2 11:53 编辑

四、中文编码的国家标准(GB2312、GBK、GB18030)

      在1980年我国颁布了第一个汉字编码字符集标准,即GB 2312-80《信息交换用汉字编码字符集基本集》。该标准共收了6763个汉字及常用符号,奠定了中文信息处理的基础。区位码是国标码的另一种表现形式,把国标GB2312--80中的汉字、图形符号组成一个94×94的方阵,分为94个“区”,每区包含94个“位”,其中“区”的序号由01至94,“位”的序号也是从01至94。94个区中位置总数=94×94=8836个,其中7445个汉字和图形字符中的每一个占一个位置后,还剩下1391个空位,这1391个位置空下来保留备用。

      GBK编码,是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从8140至FEFE(剔除xx7F),共23940个码位,共收录了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。GBK编码方案于1995年10月制定, 1995年12月正式发布。MS Win95简体中文版开始提供支持。这是一个过度标准。

      GB18030-2000是GBK的取代版本,它的主要特点是在GBK基础上增加了CJK统一汉字扩充A的汉字。GB18030-2005的主要特点是在GB18030-2000基础上增加了CJK统一汉字扩充B的汉字(目前到扩展D)。


      很明显,这里有一个致命的问题,上述的编码都是中国国内使用的编码,只在简体中文上使用。而地球上的其他国家是不认识这个编码的。
      与之类似,其他国家的ANSI标准的地区性编码也存在同样的问题。

      在国际化交流日益紧密的今天,能否有一个组织提供一个可以供全球不同国家的人共同使用的一套字符集编码标准呢(能包括目前所有的地区性字符集)?
      这就是ISO10646/UNICODE。

      

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-3-31 17:56 | 显示全部楼层
本帖最后由 liucqa 于 2013-4-10 20:54 编辑

五、UNICODE编码(ISO/IEC 10646 / Unicode字符集)

      Unicode和ISO 10646起初是由两个不同的国际组织开发并推广的,后来在1991年前后,他们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。
      需要注意的是,这两个标准尽管相互兼容,但依然存在一些区别。例如,ISO/IEC 10646标准,是一个简单的字符集表。它定义了一些编码的别名,指定了一些与标准有关的术语,并包括了规范说明。Unicode标准,额外定义了许多与字符有关的语义符号学。Unicode详细说明了绘制某些语言(如阿拉伯语)表达形式的算法,处理双向文字(比如拉丁文和希伯来文的混合文字)的算法,排序与字符串比较所需的算法,等等。

      对于非文字研究领域的人来说,知道这两者的编码相同就行了。此外,还要记住,这两个标准都不是只面向Windows系统,而是面向所有的应用环境!

    术语

      UCS:(Universal Multiple-Octet Coded Character Set  通用多字节编码字符集) 是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的标准字符集。UCS规定了怎么用多个字节表示各种文字。而怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16。
      UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。   
      在ISO 10646中,UCS-2标准规定必须用两个字节(也就是16位)来统一表示所有的字符。对于ASCII里的那些英文字符,UCS-2包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他地区字符集则全部重新统一编码。由于ASCII的英文符号只需要用到低8位,所以其高 8位永远是0。由于这种大气的方案在保存英文文本时会多浪费一倍的空间,在网络传输中是不可接受的,因此又提出了UTF(UCS Transformation Format UCS转换格式)。在Unicode协会中,这个用于传输的转换格式叫做(Unicode Transformation Format  Unicode转换格式),两者是一致的。也可以统称叫做 Universal Transformation Format,通用转换格式。目前存在的UTF格式有:UTF-7,UTF-7.5,UTF-8,UTF-16,以及 UTF-32

      UTF-16:UNICODE组织为 了支持某些特殊应用 ( 古代文字,象形文字,数学和音乐排版等 ) ,必需的字符数超出了 64K 的容量。因此, 原先暗示与UCS-2对应的UTF-16编码被修改为一种 21-bit 的字符 集, 支持的码值范围为从 U+00000000 至 U+0010FFFF 。 BMP 为此定义了 2 x 1024 个特殊字符 (U+D800 至 U+DFFF) ,称为辅助平面字符 (surrogate character); 把两个 16-bit 的辅助平面字符连用,可以用来表示 1024 x 1024 个 non-BMP 字符。这样就产生了 UTF-16 ,它向后兼容 UCS-2 ,并能用于扩展后 21-bit 的 Unicode 的编码。 UTF-16可看成是UCS-2的父集(这里的UTF-16与之前说的转换格式的UTF-16不是一个概念,不可搞混)。Unicode的编码方式与UCS概念相对应,在没有辅助平面字符(surrogate code points)前,UTF-16与UCS-2所指的是同一的意思(虽然前者部分编码定义未使用)。当UTF-16引入辅助平面字符后,就称为UTF-16了。(UTF16变成了一个变长的标准)。
      BMP(基本多文种平面):Unicode协会定义的的码空间从U+0000到U+10FFFF,共有1,112,064个码位可用来映射字符. Unicode的码空间可以划分为17个平面(plane)。第一个平面成为基本多文种平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0)。或者说在四字节编码中,高两个字节为0的码位被称作BMP。将四字节的BMP去掉前面的两个零字节就得到了UCS-2。  

参考下面的英文,你会理解UCS和UTF-16之间的关系。
注意UCS的传输标准缩写也叫UTF(包括UTF-8、UTF-16等),这和本段说的UTF-16不是一个概念(前者是传输编码,后者是文字编码)
  1. UTF16 and USC2 are one and the same.
  2. The mess comes from the fact that there are two different groups
  3. dealing with the Unicode standard:
  4. - The Unicode Consortium
  5. - ISO 10646

  6. They work a lot to keep the two standards in sync
  7. (don't ask me why they don't merge)
  8. But they don't fight so much to keep the same terminology.
  9. The UTF is the Unicode Consortium terminology (Unicode Transformation Format)
  10. UCS is the ISO terminology (Universal Character Set)

  11. On the Unicode web site, in the glossary, the UTF entries now mention UCS:
  12. "UTF-16. Unicode (or UCS) Transformation Format, 16-bit encoding form."
  13. (http://www.unicode.org/glossary/index.html#UTF_16)

  14. The one thing to keep in mind is that both standard are moving.
  15. UCS and UTF are just encodings of the character tables.
  16. Windows NT likes to use the ISO terminology to show the compliance with the
  17. international standards.
复制代码
     UTF-16分大尾序(big endian,高位在前)和小尾序(little endian,低位在前),这两种储存形式目前都在用。一般来说,以Macintosh制作或储存的文字使用大尾序格式,以Microsoft或Linux制作或储存的文字使用小尾序格式。
       "endian"这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,一个皇帝送了命,另一个丢了王位。我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。

      UTF-32 :这个是 Unicode 组织中提出的用于表示 21-bit Unicode 的 4 字 节编码方式。 UTF-32 和 UCS-4 两种编码方法其实是相同的,唯一的区别在于 UTF-32 从不会用于表示码值大于 U+ 0010FFFF 字符,而 UCS-4 可用于表示不超过 U+7FFFFFFF 的 231 个字符。 ISO 10646 工作小组已承诺保证不会为任何字符分配超过 U+00107FFF 的码值,保证 UCS-4 和 UTF-32 这两种编码方法在实际应用中完全相同。

      UNICODE编码方式包括:
      UTF-16BE和UTF-16LE,  UTF-16与高位在前或低位在前的编码。UTF-16BE有时缩写为UTF-16。
      UCS-2BE和UCS-2LE, UCS-2与高位在前或低位在前的编码。UCS-2BE有时缩写为UCS-2。
      UCS-4BE和UCS-4LE, UCS-4与高位在前或低位在前的编码。UCS-4BE有时缩写为UCS-4。
      UTF-32BE和UTF-32LE, UTF-32与高位在前或低位在前的编码。UTF-32BE有时缩写为UTF-32。

     编码及BOM(Byte-Order Mark)说明:
     名称              编码次序                      BOM
     UTF-16LE      低位在前                      无
     UTF-16BE      高位在前                      无
     UTF-16         低位在前,包含BOM     FFFE
     UTF-16         高位在前,包含BOM     FEFF

      Unicode 编码系统可分为编码方式和实现方式两个层次。 UTF-16/UCS-2或者 UTF-32/UCS-4标准是编码方式。UTF-8、UTF-16、UTF-32是编码的实现方式(这里的UTF-16/UTF-32指的是编码的实现方式,也就是传输标准,与前面的UTF-16/UTF-32含义不同)。

      
    几点说明:
      区分Unicode是大尾序还是小尾序的通用方法是:通过BOM识别。此外,从Windows 2000系统开始,支持UTF-16,码序是小尾序,低位在前。

      自WinNT发布之后 ,Windows内核的API全部改成使用UTF-16的编码方式,以更好地支持多语言。但是由于历史原因,Windows仍然保留多字节编码的API。Win32 API中带字符串的API一般都有两个版本,例如CreateFileA和CreateFileW。A代表ANSI代码页,W是宽字符,即Unicode字符。
      此外,需要注意的是,尽管操作系统支持UTF-16,但是很多API函数只支持UCS-2,也就是说,不能识别超过2字节的UTF-16编码。

      一个功能完备的编码转换器必须提 供以下 13 种 Unicode 和 UCS 编码变种的支持:
      UCS-2, UCS-2BE, UCS-2LE, UCS-4, UCS-4LE, UCS-4BE, UTF-8, UTF-16, UTF-16BE, UTF-16LE, UTF-32, UTF-32BE, UTF-32LE

      UNICODE目前已发布6.1版(2012年1月31日)。在unicode联盟网站上可以查看完整的6.1的核心规范。http://www.unicode.org/



TA的精华主题

TA的得分主题

 楼主| 发表于 2013-3-31 17:57 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
本帖最后由 liucqa 于 2013-4-1 00:50 编辑

六、VBA中字符串表示方式(Unicode内码)

      前文已经说过,从Windows95系统开始,操作系统支持Unicode编码(UTF-16)。为了确保VB程序的通用性,从VB4.0开始支持Unicode作为存储文本的内码。
我们来看一段代码运行的结果
捕获.JPG

在这段程序中,我们将一个字符串转换成一个字节类型的数组,以查看其在内部的存储情况。字符A的Unicede内码是十六进制的0041,把对应的字节转成十进制就是00,65。从截图中,我们可以看到,字符A在内部的存储顺序是65,00。
由此可见,VB内部的Unicode编码是低位在前(小尾序)。每个字符占两个字节(UCS-2/UTF-16)。


TA的精华主题

TA的得分主题

 楼主| 发表于 2013-3-31 17:57 | 显示全部楼层
本帖最后由 liucqa 于 2014-2-8 00:38 编辑

七、字符串在VB内部的存储方式(BSTR,Basic String),本贴知识仅涉及32位操作系统

      要搞清楚字符串的编码转换,我们还要知道字符串在计算机内部的存储方式,了解这一点,对于掌握通过API函数传递字符串变量有着重要意义。

一般来说,字符串在内存中的存储方式有三种,C风格、Pascal风格和BSTR风格。
C风格:字符串以0结尾,可以无限长。判断字符串长度只能从头遍历到字符\0为止,进行计数。
Pascal风格:字符串前面有长度标记字节,不可以无限长。但可以快速获取字符串长度。
BSTR风格:长度标记+字符串+\0结尾


      在目前的VB/VBA中,定义一个String类型的字符串变量,实际上定义了一个指向我们称之为BSTR类型的数据类型的指针。

      BSTR数据类型,是一个带有4字节前缀和2字节0结尾的,以Unicode编码字符的字节数组(一个字符=2个字节)。

微软的 ActiveX/COM 技术为了做到支持任何语言,定义了一系列通用的数据类型,微软称之为自动化对象类型(Automation data types),其中之一就是 BSTR。
      BSTR 在 WTypes.h 中定义:
          typedef wchar_t WCHAR;
          typedef WCHAR OLECHAR;
          typedef OLECHAR *BSTR;
从定义可以看出,BSTR 是指向 wchar_t 类型(也就是 C 语言中的 Unicode)的指针,但是 BSTR 并不是普通的 wchar_t 指针。


标准 BSTR 指向一个有长度前缀和 NUL 结束符的 wchar_t 数组。BSTR 的前4字节是一个表示字符串长度的前缀。BSTR 长度域的值是字符串的字节数,并且不包括 NUL 结束符。BSTR用2字节空字符(就是二进制的00000000 00000000),表示到达了字符串的结尾。为什么以2个字节0结尾呢?因为在Unicode下,一个空字符是占用两个字节的空间,所以这两个字节就都是0。

      一个BSTR字符串变量实际上是一个指针变量。它本身占用32bit即4个字节。其指向的字符串数组必须由4个字节的保留字开始(保存字符串数组的字节数而不是字符数),由2个空字符结束。指向BSTR字符串的指针指向第一个字符,而非开头的字串长度。

     在VB中,一个字符串变量,是一个指向字符数组的指针,而不是字符数组本身。这一点很关键。

     我们来看一段代码
  1. Private Type myType
  2.     astring As String
  3. End Type
  4. Private Type myType2
  5.     astring As String
  6.     bstring As String
  7. End Type

  8. Sub test()
  9.     Dim s As String
  10.     s = "ABC"
  11.     MsgBox Len(s)           '=3,字符串长度

  12.     Dim Test1 As myType
  13.     Dim Test2 As myType2
  14.     Test1.astring = "ABC"
  15.     Test2.astring = "ABC"
  16.     MsgBox Len(Test1)       '=4,自定义类型myType占用空间长度
  17.     MsgBox Len(Test2)       '=8,自定义类型myTyp2占用空间长度
  18. End Sub
复制代码
从这段代码中,Len函数返回的是字符串数组的字符个数。所以3个字符的字符串“ABC”返回3。对自定义类型的结构变量myType、myType2来说,Len函数返回的是该结构占用的内存空间。所以返回值4、8清楚地表明了每一个BSTR变量在内存中占用4个字节。因为BSTR是一个Win32的指针!

      现在我们明白了,在VBA中,为什么将字符串转成字符数组之后,会发现每个字母对应的是两个字节(Unicode是2个字节表示一个字符);为什么判断字符串是否为空,要采用Len函数效率才最高,而不是用=“”(因为字符串长度是现成的数据)。

      之所以在这里要讲解BSTR的结构,是因为我们在API函数中要用到的许多函数只支持C风格的字符串参数,而BSTR由于兼容C风格字符串,所以可以使用大部分C风格的API函数。同时,BSTR又能够快速判断字符串的长度,提高字符串操作的运行效率。

      所以我们可以理解下面的操作:      
      在VBA中,使用API中传递字符串变量,要通过StrPtr函数,该函数返回的是BSTR字符串指针指向的Unicode字符串的首地址


顺便简单介绍一下VBA中的几个特殊关键字及常数的区别
  1. Sub testNULL()
  2.     Dim var As Variant

  3.     var = Null                    '关键字,Variant 子类型,表示变量不包含有效数据。
  4.     var = vbNull                  'VarType 常数,值为1  表示不含任何有效数据的类型
  5.     var = vbNullChar              'Miscellaneous 常数, Chr(0) 值为 0 的字符
  6.     var = vbNullString            'Miscellaneous 常数, 表示未分配内存;与长度为零的字符串 ("") 不同(后者已分配内存)

  7.     var = Empty                   '关键字,Variant 子类型,表示变量未初始化。
  8.     var = vbEmpty                 'VarType 常数,值为0  表示未初始化 (缺省值)类型

  9.     MsgBox VarPtr(vbNullString)   '有存放指针的实际内存
  10.     MsgBox VarPtr("")             '有存放指针的实际内存
  11.     MsgBox StrPtr(vbNullString)   'BSTR未分配内存
  12.     MsgBox StrPtr("")             'BSTR已分配内存
  13. End Sub
复制代码
识别NUll、vbNull、vbNullChar、vbNullString、Empty、vbEmpty这几个变量和常数,需要对字符串及其内存分配机制有深刻理解。

此外,还有IsEmpty和IsNull两个函数,用于判断Variant变量是否为Empty和NULL

Empty 值
有时需要知道是否已将一个值赋予所创建的变量。在赋值之前,Variant 变量具有值 Empty。值 Empty 是异于0、零长度字符串 ("") 或 Null 值的特定值。可用 IsEmpty 函数测试 Empty 值。将任何值(包括 0、零长度字符串或 Null)赋予 Variant 变量,Empty 值就会消失。而将关键字 Empty 赋予 Variant 变量,就可将 Variant 变量恢复为 Empty。

Null 值
Variant 数据类型还可包含一特定值:Null。Null通常用于数据库应用程序,表示未知数据或丢失的数据。 由于在数据库中使用 Null 方法,Null 具有某些唯一的特性:
对包含 Null 的表达式,计算结果总是 Null。于是说 Null 通过表达式“传播”;如果表达式的部分之值为 Null,那么整个表达式的值也为 Null。
将 Null 值、含 Null 的 Variant 变量或计算结果为 Null 的表达式作为参数传递给大多数函数,将会使函数返回 Null。
如果将 Null 值赋予 Variant 以外的任何其它类型变量,则将出现可以捕获的错误。而将 Null 值赋予 Variant 则不会发生错误,Null 将通过包含 Variant 变量的表达式传播。除非明确将 Null 赋予变量,否则变量不会设置成 Null 值,所以,如果不在应用程序中使用 Null,就不必书写测试 Null 和处理 Null 的程序。

只有Variant类型的变量才可能有Empty和Null值。

再说一下字符串连接的问题。
具体看代码,不再赘述
  1. Sub testStrJoin()   '字符串连接的效率
  2.     Dim t As Date, oDic As Object, i&, str$, arr()

  3.     '直接连接
  4.     t = Timer
  5.     For i = 1 To 100000
  6.         str = str & CStr(i)
  7.     Next
  8.     MsgBox Format(Timer - t, "0.00")      '约33秒
  9.    
  10.     '字典做动态数组
  11.     t = Timer
  12.     Set oDic = CreateObject("scripting.dictionary")
  13.     For i = 1 To 100000
  14.         oDic.Add i, CStr(i)
  15.     Next
  16.     str = Join(oDic.Items, "")
  17.     MsgBox Format(Timer - t, "0.00")      '约0.5秒

  18.     '数组生成字符串
  19.     t = Timer
  20.     ReDim arr(100000)
  21.     For i = 0 To 100000
  22.         arr(i) = i
  23.     Next
  24.     str = Join(arr, "")
  25.     MsgBox Format(Timer - t, "0.00")     '约0.05秒
  26. End Sub
复制代码
原因与大字符串的内存分配有关


关于Split的效率,看代码
  1. Sub testSplit()
  2.     Dim arr(999999)
  3.     For i = 0 To 999999
  4.         arr(i) = i
  5.     Next

  6.     Dim str As String
  7.     t = Timer
  8.     str = Join(arr, ",")
  9.     MsgBox Format(Timer - t, "0.00")

  10.     t = Timer
  11.     a = Split(str, ",")
  12.     MsgBox Format(Timer - t, "0.00")

  13.     t = Timer
  14.     str = str & ","
  15.     Set regex = CreateObject("vbscript.regexp")
  16.     regex.Global = True
  17.     regex.Pattern = "([^,]*),"
  18.     Set ms = regex.Execute(str)
  19.     Dim b()
  20.     ReDim b(ms.Count - 1)
  21.     For i = 0 To ms.Count - 1
  22.         b(i) = ms.Item(0).Submatches(0)
  23.     Next
  24.     MsgBox Format(Timer - t, "0.00")
  25. End Sub
复制代码
大字符串下,Split的效率没有正则高。



评分

3

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-3-31 17:58 | 显示全部楼层
本帖最后由 liucqa 于 2013-4-10 10:06 编辑

八、关于StrConv;Asc、AscB、AscW;Chr、ChrB、ChrW等一些转换函数,以及MsgBox的问题

先说说StrConv
这个函数是用来在大小写和Unicode/ANSI之间进行转换的。这里我们只讲解后者功能。
如果你有一个ANSI格式字符串的数组(数组的来源可能是gb2312编码的网站数据),使用StrConv函数,可以将数组转成Unicode字符串。因为在VBA中,Excel单元格和Msgbox等函数只认识Unicode字符串,只有将ANSI转成Unicode才能显示出来。

请运行下面的代码
  1. Sub testStrConv()
  2.     Dim str As String
  3.     Dim b(1) As Byte
  4.     b(0) = &HD6             'D6D0是"中"的ANSI/gb2312编码
  5.     b(1) = &HD0
  6.     MsgBox StrConv(b, vbUnicode, &H804)        '"中"
  7.    
  8.     b(0) = &H41             '41是"A"的ANSI/gb2312编码,十六进制
  9.     b(1) = &H42             '42是"B"的ANSI/gb2312编码,十六进制
  10.     MsgBox StrConv(b, vbUnicode, &H804)        '"AB"
  11. End Sub
复制代码
第一次给字节数组b赋值成“中”的ANSI/GB2312编码(D6D0),这样b就成了一个ANSI的字符数组,通过StrConv的转换,变成文本“中"。
第二次,将十六进制的41和42连续赋值给字节数组b,b就成了一个ANSI的字符数组,通过StrConv的转换,变成文本“AB"。注意,StrConv转换之后的文本是以Unicode编码存在的。&H804是GB2312的LocaleID

顺便讲一下Locale(
Locale ID)、CodePages、AppLocale的含义

Locale 和 Locale ID (区域标志符):Locale是指特定于某个国家或地区的一组设定,包括字符集,数字、货币、时间和日期的格式等。在Windows中,每个Locale可以用一个32位数字表示,记作LCID(Locale ID)。例如,简体中文是&H0804。美国英语是 &H0409。繁体中文是&H0404。

代码页(Code Pages):又叫做字符集。每个Locale都联系着很多信息,可以通过GetLocalInfo函数读取。其中最重要的信息就是字符集了,即Locale对应的语言文字的编码。Windows将字符集称作代码页。每个Locale可以对应一个ANSI代码页和一个OEM代码页。Win32 API使用ANSI代码页,底层设备使用OEM代码页,两者可以相互映射。(早期操作系统直接使用BIOS里面的VGA功能来显示字符,操作系统的编码支持也就依靠BIOS的编码。这些BIOS代码页现在被称为OEM代码页。)。例如English (US)的ANSI和OEM代码页分别为“1252 (ANSI - Latin I)”和“437 (OEM - United States)”。 Chinese (PRC)的ANSI和OEM代码页都是“936 (ANSI/OEM - Simplified Chinese GBK)”。  Chinese (TW)的ANSI和OEM代码页都是“950 (ANSI/OEM - Traditional Chinese Big5)”。
早期IBM和微软内部使用数字来标记不同的编码字符集,不同的厂商对同一个字符集编码使用各自不同的名称。例如,UTF-8在IBM称作代码页1208, 在微软称作代码页65001, 在SAP称作代码页4110,这点请大家注意!

ANSI代码页
在Windows中,通过控制面板可以为系统和用户分别设置Locale。系统Locale决定代码页,用户Locale决定数字、货币、时间和日期的格式。使用GetSystemDefaultLCID函数和GetUserDefaultLCID函数分别得到系统和用户的LCID。有很多材料将这两个函数和另外两个函数混淆:GetSystemDefaultUILanguage和GetUserDefaultUILanguage。GetSystemDefaultUILanguage和GetUserDefaultUILanguage得到的是您当前使用的Windows版本所带的UI资源的语言。
用户程序缺省使用的代码页是当前系统Locale的ANSI代码页,可以称作ANSI编码,也就是A版本的Win32 API默认的字符编码。对于一个未指定编码方式的文本文件,Windows会按照ANSI编码解释。

AppLocale
如果一个文本文件采用BIG5编码,系统当前的ANSI代码页是GBK。打开这个文件,就会显示乱码。例如“中文”在BIG5中的编码是A4A4、A4E5,这两个编码在GBK中对应的字符是“いゅ”。这是日文的两个平假名。
在Windows XP平台有一个AppLocale程序,可以以指定的语言运行非Unicode程序。用Win32dsm打开看一看,其实它只是在运行程序前设置了两个环境变量。我们可以用个批处理文件模仿一下:
@ECHO OFF
SET __COMPAT_LAYER=#ApplicationLocale
SET ApplocaleID=0404
start notepad.exe
在简体中文平台,用这个批处理文件启动的记事本可以正确显示BIG5编码的文本文件。用它打开GBK编码的文本文件会怎么样?“中文”会被显示为“笢恅”。设置这两个环境变量会作用于当前进程和其子进程。Windows 2000平台不支持这个方法。


再来看看Asc、AscB、AscW、Chr、ChrB、ChrW等转换函数

先看代码
  1. Sub testAscChr()
  2.     Dim s As String, w As String
  3.     Dim sb() As Byte, wb() As Byte
  4.     s = "A"
  5.     sb = s
  6.     w = "国"
  7.     wb = w
  8.     Debug.Print Asc(s)      '=65           返回首字母的ANSI代码
  9.     Debug.Print Asc(w)      '=-10544 D6D0  返回首字母的ANSI代码
  10.     Debug.Print AscB(s)     '=65           返回第一个字节的ANSI代码
  11.     Debug.Print AscB(w)     '=45           返回第一个字节的ANSI代码
  12.     Debug.Print AscW(s)     '=65           返回 Unicode 字符代码
  13.     Debug.Print AscW(w)     '=20013(4E2D)  返回 Unicode 字符代码
  14.     Dim b() As Byte

  15.     Debug.Print Chr(&HD6D0)       '中
  16.     Debug.Print ChrW(&H4E2D)      '中
  17.     Debug.Print StrConv(ChrB(65), vbUnicode)    'ChrB(65)返回一个数值为65的字节,通过StrConv转成Unicode
  18.     Debug.Print ChrB(65)               '不是Unicode,无显示字符
  19.     Debug.Print ChrB(65) & ChrB(0)     '使用两个ChrB构成一个Unicode字符串
  20.    
  21.     MsgBox ChrB(65)               '把ANSI转成Unicode了
  22. End Sub
复制代码
在这段代码中,可以看到Asc转换的是ANSI编码字符,AscB转换的是第一个字节的ANSI字符,AscW转换的是UniCode编码的字符。      
      也就是说,不管传递过来的参数是什么,这三个函数都只按照固有的规则进行转换。因此,如果你传递一个ANSI字符串给AscW或者传递一个Unicode汉字字符串给Asc,都可能会变成乱码。
      Chr和ChrW函数分别可以将ANSI和Unicode编码的数值转成字符。ChrB的接受数值范围在0~255之间,超过会显示溢出!


顺便说一下AscW返回负数的解决办法   :   
CLng("&H" & Hex(AscW("")))

另一个十六进制不显示负数的技巧
H = &
HFFFF&

看一下MsgBox函数的问题
  1. Sub TestMSG()
  2.     Dim b() As Byte, str As String
  3.     str = "AB" & Chr(0) & "CD"
  4.     b = str
  5.     MsgBox "字符串长度是:" & Len(str) & vbCrLf & "字符串是" & str
  6. End Sub
复制代码
由于字符串中间有Chr(0)字符,导致MsgBox的输出被截断,这是因为 MsgBox 在内部调用了 MessageBox  API函数,而该函数是以 NUL 作为字符串结束符的。这是MsgBox输出BSTR字符串时,需要注意的地方。



评分

1

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2013-3-31 17:59 | 显示全部楼层
本帖最后由 liucqa 于 2013-10-21 13:52 编辑

讲指针之前,先讲讲CopyMemory吧。注意,本楼内容不适合初学者,看不懂可以跳过!。

以下是百度搜到的内容,请自行学习:
http://blog.csdn.net/slowgrace/archive/2009/09/14/4549926.aspx

函数声明:
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory"  (pDest As Any, pSource As Any, ByVal byteLen As Long)  

这个函数的功能是从pSource拷贝byteLen个字节的数据到pDest,其中源地址和目标地址都是声明为Any类型。下面是CopyMemory对不同形式参数的理解:
(1) 传一个变量给pSource,那么源地址就是变量所在的地址
(2) 以ByVal形式传一个变量给pSource,那么源地址就是变量的值
(3) 字符串变量的值是个指针,指向字符串缓冲区的地址,也就是StrPtr(String1)。因此,以ByVal形式传一个字符串变量给pSource,那么源地址就是字符串变量的值,也就是字符串缓冲区的地址


下表总结了几种常见的传参数给CopyMemory的形式:
cpm.jpg

注:
CpoyMemoey在复制前,VB会自动对Unicode字符串做UA转换,转成ANSI字符。这可能会带来一些麻烦。



TA的精华主题

TA的得分主题

 楼主| 发表于 2013-3-31 18:00 | 显示全部楼层
本帖最后由 liucqa 于 2013-10-21 13:35 编辑

九、关于指针:谈谈VarPtr和StrPtr

这两个函数在微软的文档中是找不到帮助信息的。微软并不推荐我们使用这两个函数,因为在VB公开的文档中,这个编程语言并不支持指针。
然而,活用这两个函数,可以让VB在一定程度上具备指针的功能。特别是在做API函数调用的时候,我们必须通过这两个函数传递参数的指针。

下面看一个例子
  1. Sub testStrPtr()
  2.     Dim lng As Long
  3.     Dim str As String, str1 As String
  4.     Dim b(1 To 10) As Byte
  5.     Dim sp As Long, vp As Long

  6.     str = "ABC"
  7.     sp = StrPtr(str)                   'sp是字符串首地址即字符A在内存中的地址
  8.     Debug.Print "字母A的内存地址:" & sp

  9.     vp = VarPtr(str)                   '保存str指针变量的内存地址(str是指向BSTR字符串"ABC"的指针)
  10.     Debug.Print "指向BSTR字符串""ABC""的指针的内存地址" & vp

  11.     CopyMemory lng, ByVal vp, 4        '将vp指向的内容拷贝4个字节到lng。
  12.     Debug.Print vp & "指向的内容是否是""A""在内存中的实际地址" & sp & ":" & (lng = sp)
  13. End Sub
复制代码
在这个例子中,我们很容易通过CopyMemory函数来检查BSTR风格的字符串在内存中的存储方式。
   
    首先,我们通过StrPtr得到字符串"ABC”在内存中的实际存放地址。然后,我们通过VarStr得到str变量的指针地址(由于Dim定义的BSTR类型实际是一个指向BSTR字符数组的指针,所以这个Str变量本身应该有一个地址,而这个地址里面存放的数据才是指向"ABC"的实际地址)。最后,我们通过CopyMemory函数,将Str指向的地址复制到变量lng里面,通过与之前StrPtr获得的地址相比较,可以发现BSTR变量是一个指针,而这个指针指向的是字符串"ABC"的实际地址。

    这里注意CopyMemory函数的使用,ByVal vp指的是拷贝vp变量里面的数值指向的地址中的数据。如果去掉ByVal就变成了拷贝变量vp在内存中的地址了。也就是说,我们的目的是拷贝vp变量记录的内存地址给lng,而不是拷贝vp变量本身在内存中的地址给lng。

再看两个例子,体会指针操作的奇妙之处!

第一个例子:
  1. Sub TestBSTRRef()
  2. '替换BSTR变量指向BSTR字符串的指针,从而修改BSTR变量的字符串
  3.     Dim vpstr As Long, vpbyte As Long, spstr As Long, str$, b(13) As Byte
  4.     b(0) = 88         '随便写的数字,当作BSTR的字符串长度
  5.     b(1) = 0
  6.     b(2) = 0
  7.     b(3) = 0
  8.     b(4) = 66         'B
  9.     b(5) = 0
  10.     b(6) = 66         'B
  11.     b(7) = 0
  12.     b(8) = 65         'A
  13.     b(9) = 0
  14.     b(10) = 65        'A
  15.     b(11) = 0
  16.     b(12) = 0         'BSTR结束标记
  17.     b(13) = 0         'BSTR结束标记
  18.     str = "MNP"
  19.     spstr = StrPtr(str)                                        '记录指针
  20.     CopyMemory ByVal VarPtr(str), VarPtr(b(4)), 4              '把b(4)的地址,拷贝到BSTR变量里面,当作新的指针
  21.     'CopyMemory ByVal VarPtr(str), (VarPtr(b(0)) + 4), 4
  22.     MsgBox "长度是:" & Len(str) & vbCrLf & "字符串是" & str      '输出长度44,字符串BBAA
  23.     CopyMemory ByVal VarPtr(str), spstr, 4                     '还原,避免内存泄漏,否则在回收内存时会有一定的概率崩溃
  24. End Sub
复制代码
这个例子,先构建了一个符合BSTR规范的字节数组,然后把指向str字符串的BSTR变量里面的指针,改成指向构建的字节数组,我们就会看到一个4个字符的字符串显示的长度却是44(88/2),这就非常准确的解释了BSTR字符串的内存结构。这段代码因为抛弃了之前分配的字符串,如果不还原,可能会导致Excel崩溃关闭。

'CopyMemory对不同形式参数的理解:
'(1) 传一个变量给pSource,那么源地址就是变量所在的地址
'(2) 以ByVal形式传一个变量给pSource,那么源地址就是变量的值
'(3) 字符串变量的值是个指针,指向字符串缓冲区的地址,也就是StrPtr(String1)。
'      因此,以ByVal形式传一个字符串变量给pSource,那么源地址就是字符串变量的值,也就是字符串缓冲区的地址。


另一个例子:
  1. Sub TestBSTRVal()
  2. '修改BSTR变量指向的存储BSTR字符串的内存中的数据,从而修改BSTR变量的字符串
  3.     Dim str$, b(13) As Byte
  4.     b(0) = 88       '随便写的数字
  5.     b(1) = 0
  6.     b(2) = 0
  7.     b(3) = 0
  8.     b(4) = 66        'B
  9.     b(5) = 0
  10.     b(6) = 66        'B
  11.     b(7) = 0
  12.     b(8) = 65        'A
  13.     b(9) = 0
  14.     b(10) = 65       'A
  15.     b(11) = 0
  16.     b(12) = 0
  17.     b(13) = 0
  18.     str = "MNP"
  19.     CopyMemory ByVal (StrPtr(str) - 4), b(0), 14                '从b(0)的地址开始的内存拷贝14个字节到BSTR的字节数组内存地址开始的内存里面
  20.     MsgBox "长度是:" & Len(str) & vbCrLf & "字符串是" & str       '输出长度44,字符串BBAA
  21. End Sub
复制代码
这个例子,先构建一个BSTR标准的字节数组,然后把这个字节数组的内容拷贝到BSTR变量指向的存放str字符串的内存中,从而替换掉了原来的字符串内容,显示的是字节数组构建的BSTR字符串。这个操作实际上破坏了内存原有的数据(从占用3个字符空间的内存变成了占用4个字符空间的内存),如果在存放str字符串内存的后面有其他有用的数据的话,这个数据就会被破坏。所以,此类操作极易造成数据丢失,严重会导致程序崩溃!这正是指针的危险之处!


交换两个字符串最快的方法
  1. Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory"  (Destination As Any, Source As Any, ByVal Length As Long)
  2. Sub testSwapStr()
  3.     Dim StrA As String, StrB As String, pTmp As Long
  4.     StrA = "abcd"
  5.     StrB = "efghjik"
  6.     'CopyMemory pTmp, ByVal VarPtr(StrA), 4                 '把StrA变量的指针(字符串的实际内存地址),赋值给pTmp
  7.     'CopyMemory ByVal VarPtr(pTmp), ByVal VarPtr(StrA), 4   '临时变量的第二种赋值方法
  8.     pTmp = StrPtr(StrA)                                     '临时变量的第三种赋值方法,这三种方法效果相同
  9.     CopyMemory ByVal VarPtr(StrA), ByVal VarPtr(StrB), 4    '把StrB变量的指针(字符串的实际内存地址),赋值给StrA变量,作为字符串的指针
  10.     CopyMemory ByVal VarPtr(StrB), pTmp, 4                  '把临时变量记录的StrA的内存地址,赋值给StrB变量,作为字符串的指针
  11. End Sub
复制代码
理解VB中的指针,可以参考下面的文章
http://www.cnblogs.com/xw885/articles/105264.html
http://blog.csdn.net/slowgrace/article/details/4549926


顺便说一下,与BSTR类似,我们在VB中定义一个数组,其实定义的是COM里的SafeAraay结构指构的指针
  1. Private Type SAFEARRAY
  2.        cDims As Integer         ''这个数组有几维?
  3.        fFeatures As Integer     ''这个数组有什么特性?
  4.        cbElements As Long       ''数组的每个元素有多大?
  5.        cLocks As Long           ''这个数组被锁定过几次?
  6.        pvData As Long           ''这个数组里的数据放在什么地方?
  7.        ''rgsabound() As SFArrayBOUND
  8.     End Type
复制代码


在VB中使用指针,可能涉及的函数包括:VarPtr、StrPtr、ObjPtr、VarPtrArray 和 VarPtrStringArray 函数
具体不在本贴讲解


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

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-12-12 05:13 , Processed in 0.057347 second(s), 10 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

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