VB中String的⽤法及原理
转载:VB中String的⽤法及原理
2008-05-07 09:02
在各种不同开发语⾔中,字符串类型显然是最常见,也是最常⽤的。
常⽤代表它最易⽤,是这样吗?未必,越简单,越普通,你会忽视,内⾥隐藏着的陷井更容易使你中招。它往往是绊脚⽯,或者程序中性能的瓶颈。
本⾝,我对VB语⾔及相关应⽤并不太熟,只不过近期编码⽤到,有些体会。
⼀: 先来总结⼀下,常⽤编程语⾔的字串表达⽅式:
C:      char(wchat_t) * 或 []:  字符数组来表⽰字符串,以0结尾,⽆长度标识。
配⼀堆操作函数,不好记,不好⽤。
C++:  std::string  basic_string<>的特化版本. 注意:其内在的数据区由默认的内存管理器分配。
对象哈,提供还算好⽤的⽅法。
MFC: CString 标准的C++类. 数据项:LPTSTR ⼀堆可⽤的⽅法。
Windows普通API DLL:        LPTSTR 采⽤C标准的0结束字串
Windows扩展COM中:          BSTR  OLE标准, 头上带长度标识的字符串.
Java中:                            java.lang.string
VB中:                              String    实际上就是OLE标准的BSTR
好了,我们关⼼的是BSTR。
看看BSTR到底是什么东东。
1.0:看看在Windows SDK中的定义:
typedef OLECHAR* BSTR;
typedef WCHAR OLECHAR;
typedef wchar_t WCHAR;
所以,它实际上是宽字符指针。
1.1:它的内存结构呢?很容易查到资料。
前置四字节,内置字串的长度,后⾯是字串内容,原则上并不以'/0'结尾,长度由前置值决定。
所以,它⼜不简简单单就是宽字符指针。但从基本类型定义上来看,它与宽字符指针是可以划等号的。
1.2:那VB中的String也就明⽩了。实际上是地址,是字串的⾸地址(注意:不是长度的⾸地址)
另外,String是可以装载中间带'/0'字符的字串的,只不过,⼀些显⽰函数可能将其省略。
如:dim str as string
str = "ab" & chr(0) & "cd"
MsgBox str  '输出:ab
Debug.Print str '输出 ab cd
1.3:可以看出,它很特珠,Windows提供⼏个函数,⽤来分配和释放它。
分配:SysAllocStringLen      SysAllocString
释放:SysFreeString
取长度:SysStringLen
注意:分配得到的BSTR,实际上仍然是以'/0'结尾。SysStringLen似乎有点⼉英雄⽆⽤武之地,呵。
⽐如:SysAllocStringLen 的说明⽂档:
Allocates a new string, copies cch characters from the passed string into it, and then appends a null character.
⽐如:SysAllocString,我们可以通过例⼦:
BSTR bstrVal = ::SysAllocString(L"abcd");
for(int n=0;n<::SysStringLen(bstrVal)+1;n++)
{
TRACE(_T("/n%d"),*(bstrVal+n));
}
::SysFreeString(bstrVal);
输出:
97
97
98
99
100
仍然有0。
1.4:VC中的⽤法:
⽆论⽤MFC,还是ATL,实际上,由于BSTR并不是基本类型,⽽它的相关操作函数也是沿⽤的以'/0'
结尾的函数,所以,虽然它在字串中可以带'/0',但实际上,经过相关操作后,'/0'后的内容会丢掉,这要⼩⼼。⽐如,我们看看CString的构造:
CString::CString(LPCWSTR lpsz)
{
Init();
int nSrcLen = lpsz != NULL ? wcslen(lpsz) : 0;
if (nSrcLen != 0)
{
AllocBuffer(nSrcLen*2);
_wcstombsz(m_pchData, lpsz, (nSrcLen*2)+1);
ReleaseBuffer();
}
}
⼩结:BSTR有长度标识,但是MS在实现时,为了兼容以前的字串操作,再加上BSTR并不是基本类型,所以,它的分配函数和⼀些操作函数都是把它当作'/0'结尾来处理。千万要注意。最好也不要⽤它来装载中间带'/0'的字串,因为,说不定什么时候,它就丢掉了。
⼆:  VBr的应⽤中将⿇烦的地⽅,应该集中在以下⼏个⽅⾯:
2.1: VB调⽤Windows API DLL
2.1.1: 字串作为输⼊参数
这还⽤说吗?
在API中如果是LPTSTR,在VB中直接转为Byval s as String
原因?  为什么 BSTR可以直接转为 LPTSTR ?
前⾯其实已经讲了,再说⼀次吧。
BSTR实际上是指针,指向字串头.所以,采⽤ByVal指向的刚好是字串的⾸地址,也就是LPSTR,嘿
嘿,刚刚好,符合需要,真是巧啊。
2.1.2: 字串作为输出参数
这个⿇烦⼀点,举个例⼦吧.
如:
UINT GetWindowsDirectory(
LPTSTR lpBuffer,
UINT uSize
);
使⽤API Viewer得到声明
Public Declare Function GetWindowsDirectory Lib "kernel32" Alias "GetWindowsDirectoryA" _
(ByVal lpBuffer As String, ByVal nSize As Long) As Long
代码:
Dim sTemp As String * MAX_PATH
Dim lsize As Long
Dim str As String
lsize = GetWindowsDirectory(sTemp, MAX_PATH)
str = Left(sTemp, lsize)
MsgBox "Windows路径:" & str & "长度:" & Len(str)
注意⼀下:
A: 引⽤的Windows API是A版,⽽不是W版,如果是W版会有错误.
原因,我的猜测是:    VB的API调⽤机制中,字串强制做了UNICODE到ANSI的转换.API的DLL没法告知调
编程语言vb是什么⽤端,它是Unicode版还是ANSI版.如果是OCX或COM就没问题了,因为它们有标识。没办法,只能⽤⼀种了, MS选了A版。
B: 参数是ByVal
B: 参数是ByVal
道理同作为输⼊时,实际上传的指针,所以,指向的内容是可以修改地,也就可以返回了。
C: 根据API的使⽤说明,可得到
[out] Pointer to the buffer to receive the null-terminated string containing the path.
需要调⽤⽅分配空间,因此, sTemp需要给⾜够长度的空间.
D: 返回的0结尾的字串,可以⽤left达到⽬的.
因为:VB的String对应的是BSTR类型,长度由前置字节决定,⽽不是0字符决定.
2.1.3: 字串作为Return值
这种API是不是存在呢?系统级的没看见,⼀般不会这样⽤,因为这存在内存分配的问题.
但我⾃已好像写过,处理⽅式是:
VB声明中可以使⽤long返回,然后⽤lstrcpy进⾏转换,
说起lStrCpy就不得不说说它的作⽤了,它实在是很关键,它很神奇,它可以把
地址指向的字符串拿出来。
我们来看看原理:
lStrcpy本⾝只是简单的复制字符串的函数:
Public Declare Function lstrcpy Lib "kernel32" Alias "lstrcpyA" (ByVal lpString1 As String, ByVal lpString2 As String) As Long 如果按照API View中的声明,是不⾏的.它确实就是字串拷字串,没办法把字址指向的字串拷到另⼀串.
好了,需要⼩⼩改动⼀下:
⾸先:⽬的地没错,确实应该是指向字串的⾸地址.⽤Byval String就可以.第⼀个参数OK。
源串呢? 因为你传⼊的实际上不是String,⽽是long啊(上例返回的是long噢),那当然应该将声明改成lon
g,否则,传⼊的String会变成啥,啥都不是呢?就这么简单。
2.1.4: LPTSTR 都可以⽤VB的byval String替代吗?
如果在结构体⾥,可就不是这样了.
如:EnumForms  枚举某个打印机的所有打印纸型。
Public Declare Function EnumForms Lib "winspool.drv" Alias "EnumFormsA" (ByVal hPrinter As Long, _ ByVal Level As Long, ByRef pForm As FORM_INFO_1, ByVal cbBuf As Long, ByRef pcbNeeded As Long, _
ByRef pcReturned As Long) As Long
对于 FORM_INFO_1的定义:
Public Type FORM_INFO_1
Flags As Long
pName As String
Size As SIZEL
ImageableArea As RECTL
End Type
如果按上述的⽅法调⽤,会出现异常.估计是在拷贝数据时,String实际上是不同于LPTSTR的,因为它的前⾯还有四字节是标识长度的,如果定义为String,那在构造pName之前的长度,必将动到它不应该动到的地⽅,这样,就错了.想来不会异常退出的,但却退出了,不知原因(我没弄明⽩)
所以,此种情况,应该将pName变为long,然后再⽤2.1.3提到的⽅法,将字串拷贝出来使⽤.
这样,就OK了。
引申⼀下:
对于⼀些API中⼤的结构体中的指针类型,我们完全可以⽤long来取代它,然后传递0&,因为,⼤多数情况,我们是不需要传⼊任何参数的,避免定义很多我们并不需要的类型.这样就降低了API使⽤的复杂性。
2.2 : VB调⽤COM组件(或OCX控件)
这似乎没啥可说的,COM组件的接⼝⼀般都是标准的BSTR,与VB的String完全兼容.
VB调⽤端没啥问题,倒是书写COM的程序需要注意:
2.2.1: 应⽤端可能会传⼊空指针.⽐如传⼊vbNullString,这需要留意.
2.2.2: BSTR的释放与普通LPTSTR可不同,使⽤前需要初始化,使⽤后要释放,当然,VB⽆此烦恼.
3: VB字串的连接操作.
当我们要序列化⽣成⽂本⽂件时,⼤都喜欢先将内容写⼊字串,然后再⼀次写了⽂件.否则多次写⼊⽂件,似乎有效率低之嫌,但如果字串为多次累加⽽成,那么使⽤VB字串的连接操作,将是效率极差的做法,这也变成性能低下的重要原因.
多次累加⽽成,那么使⽤VB字串的连接操作,将是效率极差的做法,这也变成性能低下的重要原因.
分析⼀下原因:
对于String的连接,系统的做法⼀定是重分配空间,搬移到新空间,拷贝进新内容.如果连接次数较多,且较细,那将是灾难性的。(类似MFC 的CString的做法,假设我们要写⼀个100K长度的字串,但每次添加⼀个字符,那你可以试试它的速度)
如何解决?
很简单,采⽤预分配内存块, 当内存块不够⽤时,⾃动增加指定块的增量(当然每次增长的内存块这个阀值需要慎重考虑)
这样,减少搬移操作,效率⾃然提升。
具体实现:可以使⽤内存操作API实现.VB会⿇烦⼀点(需要⽤API⽣成全局内存,⾃⾏完成搬移,拷贝),
VC可以直接使⽤CMemFile简单搞定.
4: VB字串多语⾔化
4.1: ⼀般的做法是: 将原⽣串与多语⾔串做成Key.Value Pair结构。多语⾔信息为Value,然后做⼀个简单的Hashc ode来索引,或者⼲脆⽤Hash Map来实现 (为了更⾼效,更容易使⽤已有容器,最好借助于C++编写相关⽀持)。
4.2: 为了使UI上的字串也能多语⾔化,需要保证字串信息为动态加载。
4.3: 对于控件的字体编码字符集也需要处理,以保证正确显⽰。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。