在各種不同開發(fā)語言中,字符串類型顯然是最常見,也是最常用的。
常用代表它最易用,是這樣嗎?未必,越簡單,越普通,你會忽視,內(nèi)里隱藏著的陷井更容易使你中招。它往往是絆腳石,或者程序中性能的瓶頸。
本身,我對VB語言及相關(guān)應(yīng)用并不太熟,只不過近期編碼用到,有些體會。
一: 先來總結(jié)一下,常用編程語言的字串表達(dá)方式:
C: char(wchat_t) * 或 []: 字符數(shù)組來表示字符串,以0結(jié)尾,無長度標(biāo)識。
配一堆操作函數(shù),不好記,不好用。
C++: std::string basic_string<>的特化版本. 注意:其內(nèi)在的數(shù)據(jù)區(qū)由默認(rèn)的內(nèi)存管理器分配。
對象哈,提供還算好用的方法。
MFC: CString 標(biāo)準(zhǔn)的C++類. 數(shù)據(jù)項(xiàng):LPTSTR 一堆可用的方法。
Windows普通API DLL: LPTSTR 采用C標(biāo)準(zhǔn)的0結(jié)束字串
Windows擴(kuò)展COM中: BSTR OLE標(biāo)準(zhǔn), 頭上帶長度標(biāo)識的字符串.
Java中: java.lang.string
VB中: String 實(shí)際上就是OLE標(biāo)準(zhǔn)的BSTR
好了,我們關(guān)心的是BSTR。
看看BSTR到底是什么東東。
1.0:看看在Windows SDK中的定義:
typedef OLECHAR* BSTR;
typedef WCHAR OLECHAR;
typedef wchar_t WCHAR;
所以,它實(shí)際上是寬字符指針。
1.1:它的內(nèi)存結(jié)構(gòu)呢?很容易查到資料。
前置四字節(jié),內(nèi)置字串的長度,后面是字串內(nèi)容,原則上并不以'/0'結(jié)尾,長度由前置值決定。
所以,它又不簡簡單單就是寬字符指針。但從基本類型定義上來看,它與寬字符指針是可以劃等號的。
1.2:那VB中的String也就明白了。實(shí)際上是地址,是字串的首地址(注意:不是長度的首地址)
另外,String是可以裝載中間帶'/0'字符的字串的,只不過,一些顯示函數(shù)可能將其省略。
如:dim str as string
str = "ab" & chr(0) & "cd"
MsgBox str '輸出:ab
Debug.Print str '輸出 ab cd
1.3:可以看出,它很特珠,Windows提供幾個函數(shù),用來分配和釋放它。
分配:SysAllocStringLen SysAllocString
釋放:SysFreeString
取長度:SysStringLen
注意:分配得到的BSTR,實(shí)際上仍然是以'/0'結(jié)尾。SysStringLen似乎有點(diǎn)兒英雄無用武之地,呵。
比如: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,實(shí)際上,由于BSTR并不是基本類型,而它的相關(guān)操作函數(shù)也是沿用的以'/0'結(jié)尾的函數(shù),所以,雖然它在字串中可以帶'/0',但實(shí)際上,經(jīng)過相關(guān)操作后,'/0'后的內(nèi)容會丟掉,這要小心。比如,我們看看CString的構(gòu)造:
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();
}
}
小結(jié):BSTR有長度標(biāo)識,但是MS在實(shí)現(xiàn)時,為了兼容以前的字串操作,再加上BSTR并不是基本類型,所以,它的分配函數(shù)和一些操作函數(shù)都是把它當(dāng)作'/0'結(jié)尾來處理。千萬要注意。最好也不要用它來裝載中間帶'/0'的字串,因?yàn)?,說不定什么時候,它就丟掉了。
二: VBr的應(yīng)用中將麻煩的地方,應(yīng)該集中在以下幾個方面:
2.1: VB調(diào)用Windows API DLL
2.1.1: 字串作為輸入?yún)?shù)
這還用說嗎?
在API中如果是LPTSTR,在VB中直接轉(zhuǎn)為Byval s as String
原因? 為什么 BSTR可以直接轉(zhuǎn)為 LPTSTR ?
前面其實(shí)已經(jīng)講了,再說一次吧。
BSTR實(shí)際上是指針,指向字串頭.所以,采用ByVal指向的剛好是字串的首地址,也就是LPSTR,嘿嘿,剛剛好,符合需要,真是巧啊。
2.1.2: 字串作為輸出參數(shù)
這個麻煩一點(diǎn),舉個例子吧.
如:
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調(diào)用機(jī)制中,字串強(qiáng)制做了UNICODE到ANSI的轉(zhuǎn)換.API的DLL沒法告知調(diào)
用端,它是Unicode版還是ANSI版.如果是OCX或COM就沒問題了,因?yàn)樗鼈冇袠?biāo)識。沒辦法,只能用一種了, MS選了A版。
B: 參數(shù)是ByVal
道理同作為輸入時,實(shí)際上傳的指針,所以,指向的內(nèi)容是可以修改地,也就可以返回了。
C: 根據(jù)API的使用說明,可得到
[out] Pointer to the buffer to receive the null-terminated string containing the path.
需要調(diào)用方分配空間,因此, sTemp需要給足夠長度的空間.
D: 返回的0結(jié)尾的字串,可以用left達(dá)到目的.
因?yàn)?VB的String對應(yīng)的是BSTR類型,長度由前置字節(jié)決定,而不是0字符決定.
2.1.3: 字串作為Return值
這種API是不是存在呢?系統(tǒng)級的沒看見,一般不會這樣用,因?yàn)檫@存在內(nèi)存分配的問題.
但我自已好像寫過,處理方式是:
VB聲明中可以使用long返回,然后用lstrcpy進(jìn)行轉(zhuǎn)換,
說起lStrCpy就不得不說說它的作用了,它實(shí)在是很關(guān)鍵,它很神奇,它可以把
地址指向的字符串拿出來。
我們來看看原理:
lStrcpy本身只是簡單的復(fù)制字符串的函數(shù):
Public Declare Function lstrcpy Lib "kernel32" Alias "lstrcpyA" (ByVal lpString1 As String, ByVal lpString2 As String) As Long 如果按照API View中的聲明,是不行的.它確實(shí)就是字串拷字串,沒辦法把字址指向的字串拷到另一串.
好了,需要小小改動一下:
首先:目的地沒錯,確實(shí)應(yīng)該是指向字串的首地址.用Byval String就可以.第一個參數(shù)OK。
源串呢? 因?yàn)槟銈魅氲膶?shí)際上不是String,而是long啊(上例返回的是long噢),那當(dāng)然應(yīng)該將聲明改成long,否則,傳入的String會變成啥,啥都不是呢?就這么簡單。
2.1.4: LPTSTR 都可以用VB的byval String替代嗎?
如果在結(jié)構(gòu)體里,可就不是這樣了.
如:EnumForms 枚舉某個打印機(jī)的所有打印紙型。
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
如果按上述的方法調(diào)用,會出現(xiàn)異常.估計(jì)是在拷貝數(shù)據(jù)時,String實(shí)際上是不同于LPTSTR的,因?yàn)樗那懊孢€有四字節(jié)是標(biāo)識長度的,如果定義為String,那在構(gòu)造pName之前的長度,必將動到它不應(yīng)該動到的地方,這樣,就錯了.想來不會異常退出的,但卻退出了,不知原因(我沒弄明白)
所以,此種情況,應(yīng)該將pName變?yōu)閘ong,然后再用2.1.3提到的方法,將字串拷貝出來使用.
這樣,就OK了。
引申一下:
對于一些API中大的結(jié)構(gòu)體中的指針類型,我們完全可以用long來取代它,然后傳遞0&,因?yàn)椋蠖鄶?shù)情況,我們是不需要傳入任何參數(shù)的,避免定義很多我們并不需要的類型.這樣就降低了API使用的復(fù)雜性。
2.2 : VB調(diào)用COM組件(或OCX控件)
這似乎沒啥可說的,COM組件的接口一般都是標(biāo)準(zhǔn)的BSTR,與VB的String完全兼容.
VB調(diào)用端沒啥問題,倒是書寫COM的程序需要注意:
2.2.1: 應(yīng)用端可能會傳入空指針.比如傳入vbNullString,這需要留意.
2.2.2: BSTR的釋放與普通LPTSTR可不同,使用前需要初始化,使用后要釋放,當(dāng)然,VB無此煩惱.
3: VB字串的連接操作.
當(dāng)我們要序列化生成文本文件時,大都喜歡先將內(nèi)容寫入字串,然后再一次寫了文件.否則多次寫入文件,似乎有效率低之嫌,但如果字串為多次累加而成,那么使用VB字串的連接操作,將是效率極差的做法,這也變成性能低下的重要原因.
分析一下原因:
對于String的連接,系統(tǒng)的做法一定是重分配空間,搬移到新空間,拷貝進(jìn)新內(nèi)容.如果連接次數(shù)較多,且較細(xì),那將是災(zāi)難性的。(類似MFC的CString的做法,假設(shè)我們要寫一個100K長度的字串,但每次添加一個字符,那你可以試試它的速度)
如何解決?
很簡單,采用預(yù)分配內(nèi)存塊, 當(dāng)內(nèi)存塊不夠用時,自動增加指定塊的增量(當(dāng)然每次增長的內(nèi)存塊這個閥值需要慎重考慮)
這樣,減少搬移操作,效率自然提升。
具體實(shí)現(xiàn):可以使用內(nèi)存操作API實(shí)現(xiàn).VB會麻煩一點(diǎn)(需要用API生成全局內(nèi)存,自行完成搬移,拷貝),
VC可以直接使用CMemFile簡單搞定.
4: VB字串多語言化
4.1: 一般的做法是: 將原生串與多語言串做成Key.Value Pair結(jié)構(gòu)。多語言信息為Value,然后做一個簡單的Hashc ode來索引,或者干脆用Hash Map來實(shí)現(xiàn) (為了更高效,更容易使用已有容器,最好借助于C++編寫相關(guān)支持)。
4.2: 為了使UI上的字串也能多語言化,需要保證字串信息為動態(tài)加載。
4.3: 對于控件的字體編碼字符集也需要處理,以保證正確顯示。
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請
點(diǎn)擊舉報(bào)。