在各种不同开发语言中,字符串类型显然是最常见,也是最常用的。
常用代表它最易用,是这样吗?未必,越简单,越普通,你会忽视,内里隐藏着的陷井更容易使你中招。它往往是绊脚石,或者程序中性能的瓶颈。
本身,我对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
98
99
100
0
仍然有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没法告知调
用端,它是unicode版还是ansi版.如果是ocx或com就没问题了,因为它们有标识。没办法,只能用一种了, ms选了a版。
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噢),那当然应该将声明改成long,否则,传入的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字串的连接操作,将是效率极差的做法,这也变成性能低下的重要原因.
分析一下原因:
对于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: 对于控件的字体编码字符集也需要处理,以保证正确显示。
文章整理:站长天空 网址:http://www.z6688.com/
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!




