Unicode与字符串对象.docx
《Unicode与字符串对象.docx》由会员分享,可在线阅读,更多相关《Unicode与字符串对象.docx(19页珍藏版)》请在冰豆网上搜索。
![Unicode与字符串对象.docx](https://file1.bdocx.com/fileroot1/2023-4/21/4a526c9a-55a7-46b3-b378-c59290a29252/4a526c9a-55a7-46b3-b378-c59290a292521.gif)
Unicode与字符串对象
Unicode与字符串对象
Unicode与字符串对象1
0.Unicode2
0.1.Unicode简介2
0.2.Unicode与ANSI之间的字符串转换3
0.3.建Unicode的VC++项目的一般步骤4
1.MFC的CString类5
1.1.CString实现的机制.5
1.2.LPCTSTR与GetBuffer(intnMinBufLength)6
1.3.FreeExtra()的作用8
1.4.Format(...)与FormatV(...)9
1.5.LockBuffer()与UnlockBuffer()9
1.6.AllocSysString()与SetSysString(BSTR*)9
2.COM中的字符串BSTR和VARIANT10
2.1.BSTR字符串类型10
2.2.BSTR的包装类_bstr_t10
2.3._variant_t11
1.1.
WINDOWS环境
在1981年秋天IBMPC推出之后不久,MS-DOS(MicrosoftDiskOperatingSystem磁盘操作系统)
1985年11月微软发布window早期版本1.0,1992年4月发布MicrosoftWindows版本3.1
Windows95是在1995年8月发布的
Windows98和WindowsNT都是支持32位优先权式多任务(preemptivemultitasking)及多线程的图形操作系统。
Windows拥有图形使用者接口(GUI)
写一个简单的window程序
几个重要的头文件:
windows.hWINDEF.H基本型态定义WINNT.H支持Unicode的型态定义WINBASE.HKernel函数WINUSER.H使用者接口函数WINGDI.H图形设备接口函数
这些表头文件定义了Windows的所有数据型态、函数呼叫、数据结构和常数标识符,它们是Windows文件中的一个重要部分。
使用Edit菜单中的FindinFiles搜索这些表头文件非常方便
1.2.Unicode简介
如今,Windows操作系统的使用已经遍及世界,为使Windows操作系统及运行在操作系统上的应用软件更容易被世界所有国籍的用户所使用,需要使Windows及运行在其上的应用程序本地化,即使用用户本民族语言的字符集。
字符集的不统一使得本地化变得很困难,这需要对操作系统的源代码根据不同的字符集进行全方位的定制,还要提供API的不同字符集的版本,此外,编写应用软件也要针对不同的字符集开发不同的版本。
在欧美地区,字符串被当作一系列以0结尾的单字节字符,这非常自然。
使用strlen函数时,会返回一个以0结尾的单字节字符数组中的字符数。
但是有些语言,如汉字,字符集的符号很多,而单字节字符集最多只能提供256个符号,这是远远不够的。
因此,创立了双字节字符集DBCS(doublebytecharacterset)来支持这些语言。
在双字节字符集中,字符串中的每个字符由1或2字节组成,因此也叫多字节字符集MBCS(multiplebytecharacterset)。
由于有些字符是1字节宽,而有些是2字节宽,这使得操作多字节字符串变得非常麻烦,使用strlen操作多字节字符串不能得到字符串的真正长度,而只能得到字符的字节数。
strlen(”行”)
ANSI的C运行时库中没有支持多字节字符集的函数。
VC++运行时库中包含有支持操作多字节字符串的函数,这些函数都以_mb开头,比如_mbstrlen。
Win32中提供了一些辅助函数来帮助操作多字节字符串。
如表:
typedefconstchar*LPCTSTR
typedefchar*LPTSTR
Win32中提供的操作多字节字符串的函数
函数
描述
LPTSTRCharNext(LPCTSTRlpszCurrentChar)
返回字符串中下一个字符的地址
LPTSTRCharPrev(LPCTSTRlpszStart,LPCTSTRlpszCurrentChar)
返回字符串中前一个字符的地址
BOOLIsDBcsLeadByte(BYTEbTestChar);
返回该字节是否是一个DBCS字符的第一个字节。
为了更方便地支持软件的国际化,一些国际著名的公司制定了一个宽字节的字符集标准—Unicode。
该标准最早由Apple和Xerox公司在1988年创立,为发展和促进这一个标准,1991年创建了Unicode联盟,成员包括Adobe,Aldus,Apple,Borland,Digital,IBM,Lotus,Metaphore,Microsoft,
Novell,Sun,Taligent,Xerox等。
Unicode这个名称来自三个主要特征:
通用(universal)—它解决了世界语言的需要;统一(uniform)—它为了保证效率而使用固定长度的代码;唯一(unique)—字符代码的重复将到了最低点。
Unicode字符串中所有字符都是16位的(2个字节),没有象MBCS字符那样的特殊字节来指示下个字节是同一字符的一部分还是一个新的字符,这意味着可以简单地增减一个指针来访问字符串中的所有字符。
由于Unicode用16位值来表示每个字符,因此可以表示65536个字符,这使得可对世界上所有的书面语言字符进行编码。
目前,Unicode已经为Abrabic,Chinesebopomofo,Cyrillie(Russian),Greek,
Hebrew,Japanesekane,Koreanhangul和Englishalphabets以及其他蚊子定义了码点(一个符号在字符集中的位置)。
大量的标点符号、数学符号、技术符号、肩头、货币符号、发音符号以及其他符号也包括在这个字符集中。
所有的这些符号总共约有34,000个码点,剩余的码点用于将来的扩展。
这65536个字符分成若干个区间,如表所示。
Unicode字符集空间划分
16位代码
字符
0000-007F
ASCII
0080-00FF
拉丁字符
0100-017F
欧洲拉丁
0180-01FF
扩展拉丁
0250-02AF
标号准音标
02B0-02FF
修改字母
0300-036F
通用发音符号
0370-03FF
希腊字母
0400-04FF
西里尔字母
0530-058F
亚美尼亚字母
0590-05FF
希伯莱字母
0600-06FF
阿拉伯字母
0900-097F
天城文字
3000-9FFF
中文、朝鲜文、日文
Unicode的字符串常量以字符串加前缀L表示,如:
wchar_t*pszInfo=L”It’sunicodestring”;
1.3.Unicode与ANSI之间的字符串转换
多字节字符串转换成宽字节字符串的函数:
intMultiByteToWideChar(
UINTuCodePage,//源字符串的字符集编号(代码页)936
DWORDDWFlags,//是否区分重音符号,一般不用,传0即可
PSTRpMultiByteStr,//双字节的源字符串
intcchMultiByte,//源字符串缓冲区大小,按字节计算
PWSTRpWideCharStr,//宽字符的目标字符串
intcchWideChar);//目标字符串的长度,按字符个数计算
宽字节转换成多字节的函数:
intWideCharToMultiByte(
UINTuCodePage,//代码页
DWORDdwFlags,//一般传0
PCWSTRpWideCharStr,//源宽字符串
intcchWideChar,//源字符串的长度,按字符计算
PSTRpMultiByteStr,//目标双字节缓冲区
intcchMultiByte,//目标缓冲区大小,按字节计算
PCSTRpDefaultChar,//转换失败的替代字符
PBOOLpfUsedDefaultChar//传出参数,表示是否有没转换成功的字符
)
示例:
charszGb[]="大小";
wchar_t*pszUni;
char*pszBig5;
intiLen;
constintCP_GBK=936;//GBK代码页
constintCP_BIG5=950;//繁体中文的代码页
//测试目标缓冲区的大小
iLen=MultiByteToWideChar(CP_ACP,0,szGb,-1,NULL,0);
pszUni=newwchar_t[iLen+1];//iLen把结束标记考虑进去了
//将GB2312字符串转换成UNICODE的
MultiByteToWideChar(CP_ACP,0,szGb,-1,pszUni,iLen);
//测试转换成多字节繁体字符串需要的缓冲区大小
iLen=WideCharToMultiByte(CP_BIG5,0,pszUni,-1,NULL,0,NULL,NULL);
pszBig5=newchar[iLen];
//转换成繁体字符串
WideCharToMultiByte(CP_BIG5,0,pszUni,-1,pszBig5,iLen,NULL,NULL);
TRACE("%s\n",pszBig5);
delete[]pszBig5;
delete[]pszUni;
1.4.建Unicode的VC++项目的一般步骤
1、在项目设置的“菜单—Project—Settings—C/C++--Preprocessor”中,将_MBCS预处理定义修改为_UNICODE;
2、在“菜单—Tools—Debug”中,将“Displayunicodestrings”选项打勾;
3、除非指定要使用char类型的字符串,否则应该将字符串常量用_T括起来,例如
//修改前charszBuf[]="Hello!
";
//修改后TCHARszBuf[]=_T("Hello!
");
4、有些数据类型要进行相应的转换,如
原数据类型
新数据类型
char
TCHAR
char*
LPTSTR
constchar*
LPCTSTR
5、原有字符串处理函数进行相应转换,一般规律为str***转换为_tcs***,例如
原函数
替换后的函数
strlen
_tcslen
strcpy
_tcscpy
strcat
_tcscat
sprintf
_stprintf注意此处有不同
示例:
/*转换前
charszBuf[]="Hello!
";
charszBuf2[16];
char*pszBuf=szBuf;
constchar*pszBuf2=szBuf;
strcpy(szBuf2,szBuf);
intiLen=strlen(szBuf2);
intiLen2=sizeof(szBuf)-1;
sprintf(szBuf2,"stris:
%s",szBuf);
*/
//转换后
TCHARszBuf[]=_T("Hello!
");
TCHARszBuf2[16];
LPTSTRpszBuf=szBuf;
LPCTSTRpszBuf2=szBuf;
_tcscpy(szBuf2,szBuf);
intiLen=_tcslen(szBuf2);
intiLen2=sizeof(szBuf)/sizeof(TCHAR)-1;
_stprintf(szBuf2,_T("stris:
%s"),szBuf);
2.MFC的CString类
2.1.CString实现的机制.
CString是通过“引用”来管理串的,象Window内核对象、COM对象等都是通过引用来实现的。
而CString也是通过这样的机制来管理分配的内存块。
实际上CString对象只有一个指针成员变量,所以任何CString实例的长度只有4字节.即:
intlen=sizeof(CString);//len等于4
这个指针指向一个CStringData结构体类型对象的数据区,即从nAllocLength以后开始的区域:
structCStringData
{
longnRefs;//引用计数
intnDataLength;//字符串长度
intnAllocLength;//已分配的缓冲区大小,应该大于或等于字符串长度
//TCHARdata[nAllocLength]
TCHAR*data()//实际的数据区
{return(TCHAR*)(this+1);}
};
正因为如此,一个这样的内存块可被多个CString所引用,例如下列代码:
CStringstr("abcd");
CStringa=str;
CStringb(str);
CStringc;
c=b;
由于有了这些信息,CString就能正确地分配、管理、释放引用内存块。
如果你想在调试程序的时候获得这些信息。
可以在Watch窗口键入下列表达式:
(CStringData*)((CStringData*)(this->m_pchData)-1)或
(CStringData*)((CStringData*)(str.m_pchData)-1)//str为指CString实例
正因为采用了这样的好机制,使得CString在大量拷贝时,不仅效率高,而且分配内存少。
2.2.LPCTSTR与GetBuffer(intnMinBufLength)
这两个函数提供了与标准C的兼容转换。
在实际中使用频率很高,但却是最容易出错的地方。
这两个函数实际上返回的都是指针,但它们有区别:
1、LPCTSTR它的执行过程其实很简单,只是返回引用内存块的串地址。
它是作为操作符重载提供的,所以在代码中有时可以隐式转换,而有时却需强制转制。
如:
CStringstr;
constchar*p=(LPCTSTR)str;//强制转换
//假设有这样的一个函数,Test(constchar*p);你就可以这样调用
Test(str);//这里会隐式转换为LPCTSTR
2、GetBuffer(intnMinBufLength)它与LPCTSTR类似,也会返回一个指针,不过返回的是LPTSTR
3、一般说LPCTSTR转换后只应该当常量使用,或者做函数的入参;而GetBuffer(...)取出指针后,可以通过这个指针来修改里面的内容,或者做函数的入参。
为什么呢?
也许经常有这样的代码:
CStringsBuf2=sBuf;//指向同一个内存块
//{{错误使用方法
char*pszTemp=(char*)(LPCTSTR)sBuf;
pszTemp[1]='Z';//它会导致sBuf2的值也变化了
//}}错误使用方法
所以LPCTSTR做转换后,你只能去读这块数据,千万别去改变它的内容。
假如我想直接通过指针去修改数据的话,那怎样办呢?
就是用GetBuffer(...):
sBuf="TestCString";
sBuf2=sBuf;//指向同一个内存块
//{{正确的使用方法
//它导致sBuf和sBuf2指向不同的内存块
pszTemp=sBuf.GetBuffer(0);//如果不希望缓冲区变大,直接传0就行了
pszTemp[1]='Z';
sBuf.ReleaseBuffer();
//}}正确的使用方法CStringstr("abcd");
为什么会这样?
其实GetBuffer(20)调用时,它实际上另外建立了一块新内块存,而原来的内存块引用计数也相应减1.所以执行代码后sBuf与sBuf2是指向了两块不同的地方,所以相安无事。
4、不过这里还有一点注意事项:
就是GetBuffer(0)后,新分配的缓冲区长度为原来字符串的长度,即指针pszTemp它所指向的缓冲区,给它赋值时,切不可越界。
另外,当调用GetBuffer(...)后并改变其内容,一定要记得调用ReleaseBuffer(),这个函数会根据串内容来更新引用内存块的头部信息。
5、最后还有一注意事项,看下述代码:
char*p=NULL;
constchar*q=NULL;
{
CStringstr="abcd";
q=(LPCTSTR)str;
p=str.GetBuffer(20);
AfxMessageBox(q);//合法的
strcpy(p,"thisistest");//合法的,
}
AfxMessageBox(q);//非法的
strcpy(p,"thisistest");//非法的
这里要说的就是,当返回这些指针后,如果CString对象生命结束,这些指针也相应
无效。
下面演示一段代码执行过程
voidTest()
{
//str指向一引用内存块(引用内存块的引用计数为1,长度为4,分配长度为4)
CStringstr("abcd");
//a指向一初始数据状态,
CStringa;
//a与str指向同一引用内存块(引用内存块的引用计数为2,长度为4,分配长度为4)
a=str;
//a、b与str指向同一引用内存块(引用内存块的引用计数为3,长度为4,分配长度为4)
CStringb(a);
{
//temp指向引用内存块的串首地址。
//(引用内存块的引用计数为3,长度为4,分配长度为4)
LPCTSTRtemp=(LPCTSTR)a;
//a、b、d与str指向同一引用内存块
//(引用内存块的引用计数为4,长度为4,分配长度为4)
CStringd=a;
//这条语句实际是调用拷贝构造函数。
//b指向一新分配的引用内存块。
//(新分配的引用内存块的引用计数为1,长度为5,分配长度为5)
//同时原引用内存块引用计数减1.a、d与str仍指向原引用内存块
//(引用内存块的引用计数为3,长度为4,分配长度为4)
b="testa";
//由于d生命结束,调用析构函数,导至引用计数减1
//(引用内存块的引用计数为2,长度为4,分配长度为4)
}
//此语句也会导致重新分配新内存块。
temp指向新分配引用内存块的串首地址
//(新分配的引用内存块的引用计数为1,长度为0,分配长度为10)
//同时原引用内存块引用计数减1.只有str仍指向原引用内存块
//(引用内存块的引用计数为1,长度为4,分配长度为4)
LPTSTRtemp=a.GetBuffer(10);
//a指向的引用内存块的引用计数为1,长度为0,分配长度为10
strcpy(temp,"temp");
//注意:
a指向的引用内存块的引用计数为1,长度为4,分配长度为10
a.ReleaseBuffer();
}
//执行到此,所有的局部变量生命周期都已结束。
对象strab各自调用自己的析构构
//函数,所指向的引用内存块也相应减1
//注意,strab所分别指向的引用内存块的计数均为0,这导致所分配的内存块释放
通过观察上面执行过程,我们会发现CString虽然可以多个对象指向同一引用内块存,
但是它们在进行各种拷贝、赋值及改变串内容时,它的处理是很智能并且非常安全的,完全
做到了互不干涉、互不影响。
当然必须要求你的代码使用正确恰当,特别是实际使用中会有
更复杂的情况,如做函数参数、引用、及有时需保存到CStringList当中,如果哪怕有一小
块地方使用不当,其结果也会导致发生不可预知的错误
2.3.FreeExtra()的作用
看这段代码
(1)CStringstr("test");
(2)LPTSTRtemp=str.GetBuffer(50);
(3)strcpy(temp,"thereare22character");
(4)str.ReleaseBuffer();
(5)str.FreeExtra();
上面代码执行到第(4)行时,大家都知道str指向的引用内存块计数为1,长度为22,分配长
度为50.那么执行str.FreeExtra()时,它会释放所分配的多余的内存。
(引用内存块计数为
1,长度为22,分配长度为22)
2.4.Format(...)与FormatV(...)
这条语句在使用中是最容易出错的。
因为它最富有技巧性,也相当灵活。
实际上sprintf(...)怎么用,它就怎么用。
但是有一点需要注意:
就是它的参数的特殊性,由于编译器在编译时并不能去校验格式串参数与对应的变元的类型及长度。
所以你必须要注意,两者一定要对应上,
否则就会出错。
如:
CStringstr;
inta=12;
str.Format("first:
%l,second:
%s",a,"error");//result?
试试
2.5.LockBuffer()与UnlockBuffer()
顾名思议,这两个函数的作用就是对引用内存块进行加锁及解锁。
但使用它有什么作用及执行过它后对CString串有什么实质上的影响。
其实挺简单,看下
面代码:
(1)CStringstr("test");
(2)str.LockBuffer();
(3)CStringtemp=str;
(4)str.UnlockBuffer();
(5)str.LockBuffer();
(6)str="error";
(7)str.ReleaseBuffer();
执行完(3)后,与通常情况下不同,temp与str并不指向同一引用内存块。
你可以在watch
窗口用这个表达式(CStringData*)((CStringData*)(str.m_pchData)-1)看看。
2.6.AllocSysString()与SetSysString(BSTR*)
这两个函数提供了串与BSTR的转换。
使用时须注意一点:
当调用AllocSysString()后,
须调用它SysFreeString(...),例:
CStringsBuf="TestCString";
BSTRbsBuf=sBuf.AllocSysString();
:
:
SysFreeString(bsBuf);
bsBuf=:
:
SysAllocString(L"Hi,I'mBSTR");
sBuf=bsBuf;
TRACE(_T("%s\n"),sBuf);
:
:
SysFreeString(bsBuf);