刚接触VC~程的朋友往往对许多数据类型的转换感到qh不解Q本文将介绍一些常用数据类型的使用? 我们先定义一些常见类型变量借以说明 int i = 100; long l = 2001; float f=300.2; double d=12345.119; char username[]="女侠E佩?; char temp[200]; char *buf; CString str; _variant_t v1; _bstr_t v2; 一、其它数据类型{换ؓ字符? 短整?int) itoa(i,temp,10);///i转换为字W串攑օtemp?最后一个数字表C十q制 itoa(i,temp,2); ///按二q制方式转换 长整?long) ltoa(l,temp,10); 二、从其它包含字符串的变量中获取指向该字符串的指针 CString变量 str = "2008北京奥运"; buf = (LPSTR)(LPCTSTR)str; BSTRcd的_variant_t变量 v1 = (_bstr_t)"E序?; buf = _com_util::ConvertBSTRToString((_bstr_t)v1); 三、字W串转换为其它数据类? strcpy(temp,"123"); 短整?int) i = atoi(temp); 长整?long) l = atol(temp); 点(double) d = atof(temp); 四、其它数据类型{换到CString 使用CString的成员函数Format来{?例如: 整数(int) str.Format("%d",i); 点?float) str.Format("%f",i); 字符串指?char *){已l被CString构造函数支持的数据cd可以直接赋? str = username; 五、BSTR、_bstr_t与CComBSTR CComBSTR、_bstr_t是对BSTR的封?BSTR是指向字W串?2位指针? char *转换到BSTR可以q样: BSTR b=_com_util::ConvertStringToBSTR("数据");///使用前需要加上头文gcomutil.h 反之可以使用char *p=_com_util::ConvertBSTRToString(b); 六、VARIANT 、_variant_t ?COleVariant VARIANT的结构可以参考头文gVC98\Include\OAIDL.H中关于结构体tagVARIANT的定义? 对于VARIANT变量的赋|首先lvt成员赋|指明数据cdQ再对联合结构中相同数据cd的变量赋|举个例子Q? VARIANT va; int a=2001; va.vt=VT_I4;///指明整型数据 va.lVal=a; ///赋? 对于不马上赋值的VARIANTQ最好先用Void VariantInit(VARIANTARG FAR* pvarg);q行初始?其本质是vt讄为VT_EMPTY,下表我们列Dvt与常用数据的对应关系: unsigned char bVal; VT_UI1 short iVal; VT_I2 long lVal; VT_I4 float fltVal; VT_R4 double dblVal; VT_R8 VARIANT_BOOL boolVal; VT_BOOL SCODE scode; VT_ERROR CY cyVal; VT_CY DATE date; VT_DATE BSTR bstrVal; VT_BSTR IUnknown FAR* punkVal; VT_UNKNOWN IDispatch FAR* pdispVal; VT_DISPATCH SAFEARRAY FAR* parray; VT_ARRAY|* unsigned char FAR* pbVal; VT_BYREF|VT_UI1 short FAR* piVal; VT_BYREF|VT_I2 long FAR* plVal; VT_BYREF|VT_I4 float FAR* pfltVal; VT_BYREF|VT_R4 double FAR* pdblVal; VT_BYREF|VT_R8 VARIANT_BOOL FAR* pboolVal; VT_BYREF|VT_BOOL SCODE FAR* pscode; VT_BYREF|VT_ERROR CY FAR* pcyVal; VT_BYREF|VT_CY DATE FAR* pdate; VT_BYREF|VT_DATE BSTR FAR* pbstrVal; VT_BYREF|VT_BSTR IUnknown FAR* FAR* ppunkVal; VT_BYREF|VT_UNKNOWN IDispatch FAR* FAR* ppdispVal; VT_BYREF|VT_DISPATCH SAFEARRAY FAR* FAR* pparray; VT_ARRAY|* VARIANT FAR* pvarVal; VT_BYREF|VT_VARIANT void FAR* byref; VT_BYREF _variant_t是VARIANT的封装类Q其赋值可以用强制类型{换,其构造函C自动处理q些数据cd? 例如Q? long l=222; ing i=100; _variant_t lVal(l); lVal = (long)i; COleVariant的用与_variant_t的方法基本一P请参考如下例子: COleVariant v3 = "字符?, v4 = (long)1999; CString str =(BSTR)v3.pbstrVal; long i = v4.lVal; 七、其? Ҏ(gu)息的处理中我们经帔R要将WPARAM或LPARAM{?2位数?DWORD)分解成两?6位数?WORD),例如Q? LPARAM lParam; WORD loValue = LOWORD(lParam);///取低16? WORD hiValue = HIWORD(lParam);///取高16? 对于16位的数据(WORD)我们可以用同LҎ(gu)分解成高低两?位数?BYTE),例如: WORD wValue; BYTE loValue = LOBYTE(wValue);///取低8? BYTE hiValue = HIBYTE(wValue);///取高8? 后记Q本文匆匆写成,错误之处在所隑օQ欢q来信指正? |友对该文章的评? |友: catch(hw.wh.cn@163.net) 发表? 2003-4-27 20:27:05 我以前大学时学过C语言Q但工作后就一直没用了(大概有两q了)Q现在想从头学,请问我可以直接学习vc++吗?如果可以Q请问我有什么地方要注意的吗Q? 我想h告诉我?zhn)的邮地址和?zhn)的QQLQ这h有问题就可以向?zhn)h了,好吗Q? 如同意,可发到我的邮:hw.wh.cn@163.net 谢谢Q? |友: 匿名 发表? 2003-4-27 20:26:29 我以前大学时学过C语言Q但工作后就一直没用了(大概有两q了)Q现在想从头学,请问我可以直接学习vc++吗?如果可以Q请问我有什么地方要注意的吗Q? 我想h告诉我?zhn)的邮地址和?zhn)的QQLQ这h有问题就可以向?zhn)h了,好吗Q? 如同意,可发到我的邮:hw.wh.cn@163.net 谢谢Q? |友: qlong(zhanglongping@163.com) 发表? 2003-4-25 19:59:09 刚接触VC~程的朋友往往对许多数据类型的转换感到qh不解Q本文将介绍一些常用数据类型的使用? 我们先定义一些常见类型变量借以说明 int i = 100; long l = 2001; float f=300.2; double d=12345.119; char username[]="女侠E佩?; char temp[200]; char *buf; CString str; _variant_t v1; _bstr_t v2; 一、其它数据类型{换ؓ字符? 短整?int) itoa(i,temp,10);///i转换为字W串攑օtemp?最后一个数字表C十q制 itoa(i,temp,2); ///按二q制方式转换 长整?long) ltoa(l,temp,10); 二、从其它包含字符串的变量中获取指向该字符串的指针 CString变量 str = "2008北京奥运"; buf = (LPSTR)(LPCTSTR)str; BSTRcd的_variant_t变量 v1 = (_bstr_t)"E序?; buf = _com_util::ConvertBSTRToString((_bstr_t)v1); 三、字W串转换为其它数据类? strcpy(temp,"123"); 短整?int) i = atoi(temp); 长整?long) l = atol(temp); 点(double) d = atof(temp); 四、其它数据类型{换到CString 使用CString的成员函数Format来{?例如: 整数(int) str.Format("%d",i); 点?float) str.Format("%f",i); 字符串指?char *){已l被CString构造函数支持的数据cd可以直接赋? str = username; 五、BSTR、_bstr_t与CComBSTR CComBSTR、_bstr_t是对BSTR的封?BSTR是指向字W串?2位指针? char *转换到BSTR可以q样: BSTR b=_com_util::ConvertStringToBSTR("数据");///使用前需要加上头文gcomutil.h 反之可以使用char *p=_com_util::ConvertBSTRToString(b); 六、VARIANT 、_variant_t ?COleVariant VARIANT的结构可以参考头文gVC98\Include\OAIDL.H中关于结构体tagVARIANT的定义? 对于VARIANT变量的赋|首先lvt成员赋|指明数据cdQ再对联合结构中相同数据cd的变量赋|举个例子Q? VARIANT va; int a=2001; va.vt=VT_I4;///指明整型数据 va.lVal=a; ///赋? 对于不马上赋值的VARIANTQ最好先用Void VariantInit(VARIANTARG FAR* pvarg);q行初始?其本质是vt讄为VT_EMPTY,下表我们列Dvt与常用数据的对应关系: unsigned char bVal; VT_UI1 short iVal; VT_I2 long lVal; VT_I4 float fltVal; VT_R4 double dblVal; VT_R8 VARIANT_BOOL boolVal; VT_BOOL SCODE scode; VT_ERROR CY cyVal; VT_CY DATE date; VT_DATE BSTR bstrVal; VT_BSTR IUnknown FAR* punkVal; VT_UNKNOWN IDispatch FAR* pdispVal; VT_DISPATCH SAFEARRAY FAR* parray; VT_ARRAY|* unsigned char FAR* pbVal; VT_BYREF|VT_UI1 short FAR* piVal; VT_BYREF|VT_I2 long FAR* plVal; VT_BYREF|VT_I4 float FAR* pfltVal; VT_BYREF|VT_R4 double FAR* pdblVal; VT_BYREF|VT_R8 VARIANT_BOOL FAR* pboolVal; VT_BYREF|VT_BOOL SCODE FAR* pscode; VT_BYREF|VT_ERROR CY FAR* pcyVal; VT_BYREF|VT_CY DATE FAR* pdate; VT_BYREF|VT_DATE BSTR FAR* pbstrVal; VT_BYREF|VT_BSTR IUnknown FAR* FAR* ppunkVal; VT_BYREF|VT_UNKNOWN IDispatch FAR* FAR* ppdispVal; VT_BYREF|VT_DISPATCH SAFEARRAY FAR* FAR* pparray; VT_ARRAY|* VARIANT FAR* pvarVal; VT_BYREF|VT_VARIANT void FAR* byref; VT_BYREF _variant_t是VARIANT的封装类Q其赋值可以用强制类型{换,其构造函C自动处理q些数据cd? 例如Q? long l=222; ing i=100; _variant_t lVal(l); lVal = (long)i; COleVariant的用与_variant_t的方法基本一P请参考如下例子: COleVariant v3 = "字符?, v4 = (long)1999; CString str =(BSTR)v3.pbstrVal; long i = v4.lVal; 七、其? Ҏ(gu)息的处理中我们经帔R要将WPARAM或LPARAM{?2位数?DWORD)分解成两?6位数?WORD),例如Q? LPARAM lParam; WORD loValue = LOWORD(lParam);///取低16? WORD hiValue = HIWORD(lParam);///取高16? 对于16位的数据(WORD)我们可以用同LҎ(gu)分解成高低两?位数?BYTE),例如: WORD wValue; BYTE loValue = LOBYTE(wValue);///取低8? BYTE hiValue = HIBYTE(wValue);///取高8? |
关于CStringȝ
前言Q串操作是编E中最常用也最基本的操作之一?/SPAN> 做ؓVCE序员,无论是菜鸟或高手都曾用过Cstring。而且好像实际~程中很隄得开它(虽然它不是标准E++中的库)。因?/SPAN>MFC中提供的q个cd我们操作字串实在太方便了Q?/SPAN>CString不仅提供各种丰富的操作函数、操作符重蝲Q我们使用起串h更象basic中那L观;而且它还提供了动态内存分配,使我们减了多少字符串数l越界的隐?zhn)。但是,我们在用过E中也体会到CString直太Ҏ(gu)出错了,而且有的不可捉摸。所以有许多高h站过来,抛弃它?/SPAN>
在此Q我个h认ؓQ?/SPAN>CString装得确实很完美Q它有许多优点,如“容易?/SPAN> Q功能强Q动态分配内存,大量q行拯时它很能节省内存资源q且执行效率高,与标准E完全兼容Q同时支持多字节与宽字节Q由于有异常机制所以用它安全方便?/SPAN> 其实Q用过E中之所以容易出错,那是因ؓ我们对它了解得还不够Q特别是它的实现机制。因为我们中的大多数人,在工作中q不那么爱深入地ȝ关于它的文档Q何况它q是英文的?/SPAN>
׃前几天我在工作中遇到了一个本不是问题但却特别手、特别难解决而且莫名惊诧的问题。好来最后发现是׃CString引发的。所以没办法Q我把整?/SPAN>CString的实现全部看了一遍,才慌然大(zhn),q彻底弄清了问题的原?/SPAN>(q个问题Q我已在csdn上开?/SPAN>)。在此,我想把我的一些关?/SPAN>CString的知识ȝ一番,以供他(她)人借鉴Q也许其中有我理解上的错误,望发现者能通知我,不胜感谢?/SPAN>
1Q?/SPAN> CString实现的机?/SPAN>.
CString是通过“引用”来理串的Q“引用”这个词我相信大家ƈ不陌生,?/SPAN>Window内核对象?/SPAN>COM对象{都是通过引用来实现的。?/SPAN>CString也是通过q样的机制来理分配的内存块。实际上CString对象只有一个指针成员变?/SPAN>,所以Q?/SPAN>CString实例的长度只?/SPAN>4字节.
?/SPAN>: int len = sizeof(CString);//len{于4
q个指针指向一个相关的引用内存块,如图: CString str("abcd");
0x04040404 head部,为引用内存块相关信息
str 0x40404040
正因为如此,一个这L内存块可被多?/SPAN>CString所引用Q例如下列代码:
CString str("abcd"); CString a = str; CString b(str); CString c; c = b; 上面代码的结果是Q上面四个对?/SPAN>(str,a,b,c)中的成员变量指针有相同的|都ؓ0x40404040.而这块内存块怎么知道有多个CString引用它呢Q同P它也会记录一些信息。如被引用数Q串长度Q分配内存长度?/SPAN> q块引用内存块的l构定义如下Q?/SPAN> struct CStringData { long nRefs; //表示有多个CString 引用?/SPAN>. 4 int nDataLength; //串实际长?/SPAN>. 4 int nAllocLength; //d分配的内存长度(不计q头部的12字节Q?/SPAN>. 4 }; ׃有了q些信息Q?/SPAN>CStringp正确地分配、管理、释攑ּ用内存块?/SPAN> 如果你想在调试程序的时候获得这些信息。可以在WatchH口键入下列表达式: (CStringData*)((CStringData*)(this->m_pchData)-1)?/SPAN> (CStringData*)((CStringData*)(str.m_pchData)-1)//str为指CString实例 正因为采用了q样的好机制Q?/SPAN>CString在大量拷贝时Q不仅效率高Q而且分配内存?/SPAN>
2Q?/SPAN>LPCTSTR ?/SPAN> GetBuffer(int nMinBufLength)
q两个函数提供了与标?/SPAN>C的兼容{换。在实际中用频率很高,但却是最Ҏ(gu)出错的地斏V这两个函数实际上返回的都是指针Q但它们有何区别呢?以及调用它们后,q后是做了怎样的处理过E呢Q?/SPAN> (1) LPCTSTR 它的执行q程其实很简单,只是q回引用内存块的串地址?/SPAN> 它是作ؓ操作W重载提供的Q所以在代码中有时可以隐式{换,而有时却需强制转制。如Q?/SPAN> CString str; const char* p = (LPCTSTR)str; //假设有这L一个函敎ͼTest(const char* p)Q?/SPAN> 你就可以q样调用 Test(str);//q里会隐式{换ؓLPCTSTR (2) GetBuffer(int nMinBufLength) 它类|也会q回一个指针,不过它有点差?/SPAN>,q回的是LPTSTR (3) q两者到底有何不同呢Q我惛_诉大Ӟ其本质上完全不一P一般说LPCTSTR转换后只应该当常量用,或者做函数的入参;?/SPAN>GetBuffer(...)取出指针后,可以通过q个指针来修攚w面的内容Q或者做函数的出参。ؓ什么呢Q也许经常有q样的代码: CString str("abcd"); char* p = (char*)(const char*)str; p[2] = 'z'; 其实Q也许有q样的代码后Q你的程序ƈ没有错,而且E序也运行得挺好。但它却是非常危险的。再?/SPAN> CString str("abcd"); CString test = str; .... char* p = (char*)(const char*)str; p[2] = 'z'; strcpy(p, "akfjaksjfakfakfakj");//q下完蛋?/SPAN> 你知道此Ӟtest中的值是多少吗?{案?/SPAN>"abzd"。它也跟着改变了,q不是你所期望发生的。但Z么会q样呢?你稍微想惛_会明白,前面说过Q因?/SPAN>CString是指向引用块的,str?/SPAN>test指向同一块地?/SPAN>,当你p[2]='z'后,当然test也会随着改变。所以用它做LPCTSTR做{换后Q你只能去读q块数据Q千万别L变它的内宏V?/SPAN> 假如我想直接通过指针MҎ(gu)据的话,那怎样办呢Q就是用GetBuffer(...).看下qC码: CString str("abcd"); CString test = str; .... char* p = str.GetBuffer(20); p[2] = 'z'; // 执行到此Q现?/SPAN>test中值却仍是"abcd" strcpy(p, "akfjaksjfakfakfakj"); // 执行到此Q现?/SPAN>test中D?/SPAN>"abcd" Z么会q样Q其?/SPAN>GetBuffer(20)调用Ӟ它实际上另外建立了一块新内块存,q分?/SPAN>20字节长度?/SPAN>bufferQ而原来的内存块引用计C相应?/SPAN>1. 所以执行代码后str?/SPAN>test是指向了两块不同的地方,所以相安无事?/SPAN> (4) 不过q里q有一Ҏ(gu)意事:是str.GetBuffer(20)后,str的分配长度ؓ20Q即指针p它所指向?/SPAN>buffer只有20字节长,l它赋值时Q切不可过Q否则灾隄你不q了Q如果指定长度小于原来串长度Q如GetBuffer(1),实际上它会分?/SPAN>4个字节长度(卛_来串长度Q;另外Q当调用GetBuffer(...)后ƈ改变其内容,一定要记得调用ReleaseBuffer(),q个函数会根据串内容来更新引用内存块的头部信息?/SPAN> (5) 最后还有一注意事项Q看下述代码Q?/SPAN> char* p = NULL; const char* q = NULL; { CString str = "abcd"; q = (LPCTSTR)str; p = str.GetBuffer(20); AfxMessageBox(q);// 合法?/SPAN> strcpy(p, "this is test");//合法的, } AfxMessageBox(q);// 非法的,可能完蛋 strcpy(p, "this is test");//非法的,可能完蛋 q里要说的就是,当返回这些指针后Q?/SPAN> 如果CString对象生命l束Q这些指针也相应无效?/SPAN> 3Q拷?/SPAN> & 赋?/SPAN> & "引用内存?/SPAN>" 什么时候释放? 下面演示一D代码执行过E?/SPAN> void Test() { CString str("abcd"); //str指向一引用内存块(引用内存块的引用计数?/SPAN>1,长度?/SPAN>4,分配长度?/SPAN>4Q?/SPAN> CString a; //a指向一初始数据状态, a = str; //a?/SPAN>str指向同一引用内存块(引用内存块的引用计数?/SPAN>2,长度?/SPAN>4,分配长度?/SPAN>4Q?/SPAN> CString b(a); //a?/SPAN>b?/SPAN>str指向同一引用内存块(引用内存块的引用计数?/SPAN>3,长度?/SPAN>4,分配长度?/SPAN>4Q?/SPAN> { LPCTSTR temp = (LPCTSTR)a; //temp指向引用内存块的串首地址。(引用内存块的引用计数?/SPAN>3,长度?/SPAN>4,分配长度?/SPAN>4Q?/SPAN> CString d = a; //a?/SPAN>b?/SPAN>d?/SPAN>str指向同一引用内存块(引用内存块的引用计数?/SPAN>4, 长度?/SPAN>4,分配长度?/SPAN>4Q?/SPAN> b = "testa"; //q条语句实际是调?/SPAN>CString::operator=(CString&)函数?/SPAN> b指向一新分配的引用内存块。(新分配的引用内存块的 引用计数?/SPAN>1, 长度?/SPAN>5, 分配长度?/SPAN>5Q?/SPAN> //同时原引用内存块引用计数?/SPAN>1. a?/SPAN>d?/SPAN>str仍指向原 引用内存块(引用内存块的引用计数?/SPAN>3,长度?/SPAN>4,分配长度?/SPAN>4Q?/SPAN> } //׃d生命l束Q调用析构函敎ͼD引用计数?/SPAN>1Q引用内存块的引用计Cؓ2,长度?/SPAN>4,分配长度?/SPAN>4Q?/SPAN> LPTSTR temp = a.GetBuffer(10); //此语句也会导致重新分配新内存块?/SPAN>temp指向新分配引用内存块的串首地址Q新 分配的引用内存块的引用计Cؓ1,长度?/SPAN>0,分配长度?/SPAN>10Q?/SPAN> //同时原引用内存块引用计数?/SPAN>1. 只有str?/SPAN> 指向原引用内存块 Q引用内存块的引用计Cؓ1, 长度?/SPAN>4, 分配长度?/SPAN>4Q?/SPAN> strcpy(temp, "temp"); //a指向的引用内存块的引用计Cؓ1,长度?/SPAN>0,分配长度?/SPAN>10 a.ReleaseBuffer();//注意:a指向的引用内存块的引用计Cؓ1,长度?/SPAN>4,分配长度?/SPAN>10 } //执行到此Q所有的局部变量生命周期都已结束。对?/SPAN>str a b 各自调用自己的析构构 //函数Q所指向的引用内存块也相应减1 //注意Q?/SPAN>str a b 所分别指向的引用内存块的计数均?/SPAN>0,q导致所分配的内存块释放 通过观察上面执行q程Q我们会发现CString虽然可以多个对象指向同一引用内块存,但是它们在进行各U拷贝、赋值及改变串内Ҏ(gu)Q它的处理是很智能ƈ且非常安全的Q完全做C互不q涉、互不媄响。当然必要求你的代码用正恰当,特别是实际用中会有更复杂的情况Q如做函数参数、引用、及有时需保存?/SPAN>CStringList当中Q如果哪怕有一块地方使用不当Q其l果也会D发生不可预知的错?/SPAN> 5 FreeExtra()的作?/SPAN> 看这D代?/SPAN> (1) CString str("test"); (2) LPTSTR temp = str.GetBuffer(50); (3) strcpy(temp, "there are 22 character"); (4) str.ReleaseBuffer(); (5) str.FreeExtra(); 上面代码执行到第(4)行时Q大安知道str指向的引用内存块计数?/SPAN>1,长度?/SPAN>22,分配长度?/SPAN>50. 那么执行str.FreeExtra()Ӟ它会释放所分配的多余的内存?/SPAN>(引用内存块计Cؓ1,长度?/SPAN>22,分配长度?/SPAN>22) 6 Format(...) ?/SPAN> FormatV(...) q条语句在用中是最Ҏ(gu)出错的。因为它最富有技巧性,也相当灵zR在q里Q我没打对它细l分析,实际?/SPAN>sprintf(...)怎么用,它就怎么用。我只提醒用时需注意一点:是它的参数的特D性,׃~译器在~译时ƈ不能L验格式串参数与对应的变元的类型及长度。所以你必须要注意,两者一定要对应上, 否则׃出错。如Q?/SPAN> CString str; int a = 12; str.Format("first:%l, second: %s", a, "error");//result?试试 7 LockBuffer() ?/SPAN> UnlockBuffer() 思议Q这两个函数的作用就是对引用内存块进行加锁及解锁。但使用它有什么作用及执行q它后对CString串有什么实质上的媄响。其实挺单,看下面代?/SPAN>: (1) CString str("test"); (2) str.LockBuffer(); (3) CString temp = str; (4) str.UnlockBuffer(); (5) str.LockBuffer(); (6) str = "error"; (7) str.ReleaseBuffer(); 执行?/SPAN>(3)后,与通常情况下不同,temp?/SPAN>strq不指向同一引用内存块。你可以?/SPAN>watchH口用这个表辑ּ(CStringData*)((CStringData*)(str.m_pchData)-1)看看?/SPAN> 其实?/SPAN>msdn中有说明Q?/SPAN> While in a locked state, the string is protected in two ways: No other string can get a reference to the data in the locked string, even if that string is assigned to the locked string. The locked string will never reference another string, even if that other string is copied to the locked string. 8 CString 只是处理串吗Q?/SPAN> 不对Q?/SPAN>CString不只是能操作Ԍ而且q能处理内存块数据。功能完善吧Q看q段代码 char p[20]; for(int loop=0; loop<sizeof(p); loop++) { p[loop] = 10-loop; } CString str((LPCTSTR)p, 20); char temp[20]; memcpy(temp, str, str.GetLength()); str完全能够转蝲内存?/SPAN>p到内存块temp中。所以能?/SPAN>CString来处理二q制数据 8 AllocSysString()?/SPAN>SetSysString(BSTR*) q两个函数提供了串与BSTR的{换。用时L意一点:当调?/SPAN>AllocSysString()后,调用它SysFreeString(...) 9 参数的安全检?/SPAN> ?/SPAN>MFC中提供了多个宏来q行参数的安全检查,如:ASSERT. 其中?/SPAN>CString中也不例外,有许多这L参数验,其实q也说明了代码的安全性高Q可有时我们会发现这很烦Q也DDebug?/SPAN>Release版本不一P如有时程?/SPAN>Debug通正常,?/SPAN>Release则程序崩溃;而有时恰相反Q?/SPAN>Debug不行Q?/SPAN>Release行。其实我个h认ؓQ我们对CString的用过E中Q应力求代码质量高,不能?/SPAN>Debug版本中出CQ何断a框,哪?/SPAN>releaseq行g看v来一切正常。但很不安全。如下代码: (1) CString str("test"); (2) str.LockBuffer(); (3) LPTSTR temp = str.GetBuffer(10); (4) strcpy(temp, "error"); (5) str.ReleaseBuffer(); (6) str.ReleaseBuffer();//执行到此ӞDebug版本会弹出错?/SPAN> 10 CString的异常处?/SPAN> 我只惛_调一点:只有分配内存Ӟ才有可能D抛出CMemoryException. 同样Q在msdn中的函数声明中,注有throw( CMemoryException)的函数都有重新分配或调整内存的可能?/SPAN> 11 跨模块时?/SPAN>Cstring。即一?/SPAN>DLL的接口函C的参CؓCString&Ӟ它会发生怎样的现象。解{我遇到的问题。我的问题原来已l发_地址为: http://www.csdn.net/expert/topic/741/741921.xml?temp=.2283136 构造一个这?/SPAN>CString对象Ӟ?/SPAN>CString strQ你可知道此时的str所指向的引用内存块吗?也许你会认ؓ它指?/SPAN>NULL。其实不对,如果q样的话Q?/SPAN>CString所采用的引用机制管理内存块׃有麻烦了Q所?/SPAN>CString在构造一个空串的对象Ӟ它会指向一个固定的初始化地址Q这块数据的声明如下Q?/SPAN> AFX_STATIC_DATA int _afxInitData[] = {-1,0,0,0}; 要描q概括一下:当某?/SPAN>CString对象串置I的话,?/SPAN>Empty(),CString a{,它的成员变量m_pchData׃指向_afxInitDataq个变量的地址。当q个CString对象生命周期l束Ӟ正常情况下它会去Ҏ(gu)指向的引用内存块计数?/SPAN>1Q如果引用计Cؓ0(x有Q?/SPAN>CString引用它时)Q则释放q块引用内存。而现在的情况是如?/SPAN>CString所指向的引用内存块是初始化内存块时Q则不会释放M内存?/SPAN> 说了q么多,q与我遇到的问题有什么关pdQ其实关pd着呢?其真正原因就是如?/SPAN>exe模块?/SPAN>dll模块有一个是static~译q接的话。那么这?/SPAN>CString初始化数据在exe模块?/SPAN>dll模块中有不同的地址Q因?/SPAN>staticq接则会在本模块中有一份源代码的拷贝。另外一U情况,如果两个模块都是shareq接的,CString的实C码则在另一个单独的dll中实玎ͼ?/SPAN>AFX_STATIC_DATA指定变量只装一ơ,所以两个模块中_afxInitData有相同的地址?/SPAN> 现在问题完全明白了吧Q你可以自己LCZ下?/SPAN> __declspec (dllexport) void test(CString& str) { str = "abdefakdfj";//如果?/SPAN>staticq接Qƈ且传入的str为空串的话,q里出错?/SPAN> } 最后一Ҏ(gu)法:写得q里Q其?/SPAN>CString中还有许多技巧性的好东东,我ƈ没去解释。如很多重蝲的操作符、查扄。我认ؓq是详细看看msdnQ这样也怼比我讲的好多了。我只侧重那些可能会出错的情c当Ӟ如我上面叙述中有错误Q敬请高手指点,不胜感谢Q?/SPAN>
C++字符串完全指引之一 —?Win32 字符~码
原著QMichael Dunn
译Q?A href="mailto:cjsun@insun.hit.edu.cn">Chengjie Sun
原文出处Q?FONT color=#4a664d size=2>CodeProjectQThe Complete Guide to C++ Strings, Part I 引言
毫无疑问Q我们都看到q像 TCHAR, std::string, BSTR {各U各L字符串类型,q有那些?_tcs 开头的奇怪的宏。你也许正在盯着昄器发愁。本指引ȝ引进各种字符cd的目的,展示一些简单的用法Qƈ告诉(zhn)在必要Ӟ如何实现各种字符串类型之间的转换?BR> 在第一部分Q我们将介绍3U字W编码类型。了解各U编码模式的工作方式是很重要的事情。即使你已经知道一个字W串是一个字W数l,你也应该阅读本部分。一旦你了解了这些,你将对各U字W串cd之间的关pL一个清楚地了解?BR> 在第二部分,我们单独讲qstringc,怎样使用它及实现他们怺之间的{换?BR> 字符基础 -- ASCII, DBCS, Unicode
所有的 string c都是以C-style字符串ؓ基础的。C-style 字符串是字符数组。所以我们先介绍字符cd。这里有3U编码模式对?U字W类型。第一U编码类型是单子节字W集Qsingle-byte character set or SBCSQ。在q种~码模式下,所有的字符都只用一个字节表C。ASCII是SBCS。一个字节表C的0用来标志SBCS字符串的l束?BR> W二U编码模式是多字节字W集Qmulti-byte character set or MBCSQ。一个MBCS~码包含一些一个字节长的字W,而另一些字W大于一个字节的长度。用在Windows里的MBCS包含两种字符cdQ单字节字符Qsingle-byte charactersQ和双字节字W(double-byte charactersQ。由于Windows里用的多字节字W绝大部分是两个字节长,所以MBCS常被用DBCS代替?BR> 在DBCS~码模式中,一些特定的D保留用来表明他们是双字节字符的一部分。例如,在Shift-JIS~码中(一个常用的日文~码模式Q,0x81-0x9f之间?0xe0-oxfc之间的DC?q是一个双字节字符Q下一个子节是q个字符的一部分?q样的DUC"leading bytes",他们都大?x7f。跟随在一个leading byte子节后面的字节被UC"trail byte"。在DBCS中,trail byte可以是Q意非0倹{像SBCS一PDBCS字符串的l束标志也是一个单字节表示??BR> W三U编码模式是Unicode。Unicode是一U所有的字符都用两个字节编码的~码模式。Unicode字符有时也被UC宽字W,因ؓ它比单子节字W宽Q用了更多的存储空_。注意,Unicode不能被看作MBCS。MBCS的独特之处在于它的字W用不同长度的字节~码。Unicode字符串用两个字节表C的0作ؓ它的l束标志?BR> 单字节字W包含拉丁文字母表,accented characters及ASCII标准和DOS操作pȝ定义的图形字W。双字节字符被用来表CZ亚及中东的语a。Unicode被用在COM及Windows NT操作pȝ内部?BR> 你一定已l很熟?zhn)单字节字W。当你用charӞ你处理的是单字节字符。双字节字符也用charcd来进行操作(q是我们会看到的关于双子节字符的很多奇怪的地方之一Q。Unicode字符用wchar_t来表C。Unicode字符和字W串帔R用前~L来表C。例如:
wchar_t wch = L''1''; // 2 bytes, 0x0031 wchar_t* wsz = L"Hello"; // 12 bytes, 6 wide characters
字符在内存中是怎样存储?/B>
单字节字W串Q每个字W占一个字节按序依次存储Q最后以单字节表C的0l束。例如?Bob"的存贮Ş式如下:
42 | 6F | 62 | 00 |
B | o | b | BOS |
Unicode的存储Ş式,L"Bob"
42 00 | 6F 00 | 62 00 | 00 00 |
B | o | b | BOS |
使用两个字节表示?来做l束标志?BR>
一眼看上去QDBCS 字符串很?SBCS 字符Ԍ但是我们一会儿看?DBCS 字符串的微妙之处Q它使得使用字符串操作函数和永字W指针遍历一个字W串时会产生预料之外的结果。字W串" " ("nihongo")在内存中的存储Ş式如下(LB和TB分别用来表示 leading byte ?trail byteQ?/P>
93 FA | 96 7B | 8C EA | 00 |
LB TB | LB TB | LB TB | EOS |
![]() |
![]() |
![]() |
EOS |
值得注意的是Q?ni"的g能被解释成WORD型?xfa93Q而应该看作两个?3和fa以这U顺序被作ؓ"ni"的编码?BR> 使用字符串处理函?/B>
我们都已l见qC语言中的字符串函敎ͼstrcpy(), sprintf(), atoll(){。这些字W串只应该用来处理单字节字符字符丌Ӏ标准库也提供了仅适用于Unicodecd字符串的函数Q比如wcscpy(), swprintf(), wtol(){?BR> 微Yq在它的CRT(C runtime library)中增加了操作DBCS字符串的版本。Str***()函数都有对应名字的DBCS版本_mbs***()。如果你料到可能会遇到DBCS字符Ԍ如果你的软g会被安装在用DBCS~码的国Ӟ如中国,日本{,你就可能会)Q你应该使用_mbs***()函数Q因Z们也可以处理SBCS字符丌Ӏ(一个DBCS字符串也可能含有单字节字W,q就是ؓ什么_mbs***()函数也能处理SBCS字符串的原因Q?BR> 让我们来看一个典型的字符串来阐明Z么需要不同版本的字符串处理函数。我们还是用前面的Unicode字符?L"Bob"Q?/P>
42 00
6F 00
62 00
00 00
B
o
b
BOS
因ؓx86CPU是little-endianQ?x0042在内存中的存储Ş式是42 00。你能看出如果这个字W串被传lstrlen()函数会出C么问题吗Q它?yu)先看到W一个字?2Q然后是00Q?0是字W串l束的标志,于是strlen()会q回1。如果把"Bob"传给wcslen()Q将会得出更坏的l果。wcslen()会先看?x6f42Q然后是0x0062Q然后一直读C的缓冲区的末,直到发现00 00l束标志或者引起了GPF?BR> 到目前ؓ止,我们已经讨论了str***()和wcs***()的用法及它们之间的区别。Str***()和_mbs**()之间的有区别区别呢?明白他们之间的区别,对于采用正确的方法来遍历DBCS字符串是很重要的。下面,我们先介绍字符串的遍历Q然后回到str***()与_mbs***()之间的区别这个问题上来?BR> 正确的遍历和索引字符?/B>
因ؓ我们中大多数人都是用着SBCS字符串成长的Q所以我们在遍历字符串时Q常怋用指针的++-?操作。我们也使用数组下标的表CŞ式来操作字符串中的字W。这两种方式是用于SBCS和Unicode字符Ԍ因ؓ它们中的字符有着相同的宽度,~译器能正确的返回我们需要的字符?BR> 然而,当碰到DBCS字符串时Q我们必L弃这些习惯。这里有使用指针遍历DBCS字符串时的两条规则。违背了q两条规则,你的E序׃存在DBCS有关的bugs?/P>
我们先来阐述规则2Q因为找C个违背它的真实的实例代码是很Ҏ(gu)的。假设你有一个程序在你自q目录里保存了一个设|文Ӟ你把安装目录保存在注册表中。在q行Ӟ你从注册表中d安装目录Q然后合成配|文件名Q接着d该文件。假设,你的安装目录是C:\Program Files\MyCoolAppQ那么你合成的文件名应该是C:\Program Files\MyCoolApp\config.bin。当你进行测试时Q你发现E序q行正常?BR> 现在Q想象你合成文g名的代码可能是这LQ?/P>
bool GetConfigFileName ( char* pszName, size_t nBuffSize ) { char szConfigFilename[MAX_PATH]; // Read install dir from registry... we''ll assume it succeeds. // Add on a backslash if it wasn''t present in the registry value. // First, get a pointer to the terminating zero. char* pLastChar = strchr ( szConfigFilename, '''' ); // Now move it back one character. pLastChar--; if ( *pLastChar != ''\\'' ) strcat ( szConfigFilename, "\\" ); // Add on the name of the config file. strcat ( szConfigFilename, "config.bin" ); // If the caller''s buffer is big enough, return the filename. if ( strlen ( szConfigFilename ) >= nBuffSize ) return false; else { strcpy ( pszName, szConfigFilename ); return true; } }q是一D很健壮的代码,然而在遇到 DBCS 字符时它?yu)会出错。让我们来看看ؓ什么。假设一个日本用户用了你的E序Q把它安装在 C:\
43 | 3A | 5C | 83 88 | 83 45 | 83 52 | 83 5C | 00 |
LB TB | LB TB | LB TB | LB TB | ||||
C | : | \ | ![]() |
![]() |
![]() |
![]() |
EOS |
当?GetConfigFileName() 查尾部的''\\''Ӟ它寻扑֮装目录名中最后的?字节Q看它是{于''\\''的,所以没有重新增加一?'\\''。结果是代码q回了错误的文g名?BR> 哪里出错了呢Q看看上面两个被用蓝色高量显C的字节。斜?'\\''的值是0x5c?' ''的值是83 5c。上面的代码错误的读取了一?trail byteQ把它当作了一个字W?BR> 正确的后向遍历方法是使用能够识别DBCS字符的函敎ͼ使指针移动正的字节数。下面是正确的代码。(指针Ud的地方用U色标明Q?
bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize ) { char szConfigFilename[MAX_PATH]; // Read install dir from registry... we''ll assume it succeeds. // Add on a backslash if it wasn''t present in the registry value. // First, get a pointer to the terminating zero. char* pLastChar = _mbschr ( szConfigFilename, '''' ); // Now move it back one double-byte character. pLastChar = CharPrev ( szConfigFilename, pLastChar ); if ( *pLastChar != ''\\'' ) _mbscat ( szConfigFilename, "\\" ); // Add on the name of the config file. _mbscat ( szConfigFilename, "config.bin" ); // If the caller''s buffer is big enough, return the filename. if ( _mbslen ( szInstallDir ) >= nBuffSize ) return false; else { _mbscpy ( pszName, szConfigFilename ); return true; } }上面的函C用CharPrev() API使pLastChar向后Ud一个字W,q个字符可能是两个字节长。在q个版本里,if条g正常工作Q因为lead byte永远不会{于0x5c?BR> 让我们来惌一个违背规?的场合。例如,你可能要一个用戯入的文g名是否多ơ出C'':''。如果,你?+操作来遍历字W串Q而不是用CharNext()Q你可能会发Z正确的错误警告如果恰巧有一个trail byte它的值的{于'':''的倹{?BR>与规?相关的关于字W串索引的规则:
2a. 永远不要使用减法dC个字W串的烦引?/PRE>q背q条规则的代码和q背规则2的代码很怼。例如,
char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];q和向后Ud一个指针是同样的效果?BR>
回到关于str***()和_mbs***()的区?/B>
现在Q我们应该很清楚Z么_mbs***()函数是必需的。Str***()函数Ҏ(gu)不考虑DBCS字符Q而_mbs***()考虑。如果,你调用strrchr("C:\\ ", ''\\'')Q返回结果可能是错误的,然而_mbsrchr()会认出最后的双字节字W,q回一个指向真?'\\''的指针?BR> 关于字符串函数的最后一点:str***()和_mbs***()函数认ؓ字符串的长度都是以char来计的。所以,如果一个字W串包含3个双字节字符Q_mbslen()会q回6。Unicode函数q回的长度是按wchar_t来计的。例如,wcslen(L"Bob")q回3?BR>Win32 API中的MBCS和Unicode
两组 APIsQ?
管你也总来没有注意过QWin32中的每个与字W串相关的API和message都有两个版本。一个版本接受MBCS字符Ԍ另一个接受Unicode字符丌Ӏ例如,Ҏ(gu)没有SetWindowText()q个APIQ相反,有SetWindowTextA()和SetWindowTextW()。后~A表明q是MBCS函数Q后~W表示q是Unicode版本的函数?BR> 当你 build 一?Windows E序Q你可以选择是用 MBCS 或?Unicode APIs。如果,你曾l用qVC向导q且没有改过预处理的讄Q那表明你用的是MBCS版本。那么,既然没有 SetWindowText() APIQ我们ؓ什么可以用它呢?winuser.h头文件包含了一些宏Q例如:BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString ); BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString ); #ifdef UNICODE #define SetWindowText SetWindowTextW #else #define SetWindowText SetWindowTextA #endif当用MBCS APIs来buildE序ӞUNICODE没有被定义,所以预处理器看刎ͼ#define SetWindowText SetWindowTextAq个宏定义把所有对SetWindowText的调用都转换成真正的API函数SetWindowTextA。(当然Q你可以直接调用SetWindowTextA() 或?SetWindowTextW()Q虽然你不必那么做。)
所以,如果你想把默认用的API函数变成Unicode版的Q你可以在预处理器设|中Q把_MBCS从预定义的宏列表中删除,然后dUNICODE和_UNICODE?你需要两个都定义Q因Z同的头文件可能用不同的宏? 然而,如果你用char来定义你的字W串Q你会陷入一个尴的境地。考虑下面的代码:HWND hwnd = GetSomeWindowHandle(); char szNewText[] = "we love Bob!"; SetWindowText ( hwnd, szNewText );在预处理器把SetWindowText用SetWindowTextW来替换后Q代码变成:
HWND hwnd = GetSomeWindowHandle(); char szNewText[] = "we love Bob!"; SetWindowTextW ( hwnd, szNewText );看到问题了吗Q我们把单字节字W串传给了一个以Unicode字符串做参数的函数。解册个问题的W一个方案是使用 #ifdef 来包含字W串变量的定义:
HWND hwnd = GetSomeWindowHandle(); #ifdef UNICODE wchar_t szNewText[] = L"we love Bob!"; #else char szNewText[] = "we love Bob!"; #endif SetWindowText ( hwnd, szNewText );你可能已l感受到了这样做会使你多么的头疹{完的解决Ҏ(gu)是用TCHAR.
使用TCHAR
TCHAR是一U字W串cdQ它让你在以MBCS和UNNICODE来buildE序时可以用同L代码Q不需要用繁琐的宏定义来包含你的代码。TCHAR的定义如下:#ifdef UNICODE typedef wchar_t TCHAR; #else typedef char TCHAR; #endif所以用MBCS来buildӞTCHAR是charQ用UNICODEӞTCHAR是wchar_t。还有一个宏来处理定义Unicode字符串常量时所需的L前缀?/P>
#ifdef UNICODE #define _T(x) L##x #else #define _T(x) x #endif##是一个预处理操作W,它可以把两个参数q在一赗如果你的代码中需要字W串帔RQ在它前面加上_T宏。如果你使用Unicode来buildQ它会在字符串常量前加上L前缀?/P>
TCHAR szNewText[] = _T("we love Bob!");像是用宏来隐藏SetWindowTextA/W的细节一Pq有很多可以供你使用的宏来实现str***()和_mbs***(){字W串函数。例如,你可以用_tcsrchr宏来替换strrchr()、_mbsrchr()和wcsrchr()。_tcsrchrҎ(gu)你预定义的宏是_MBCSq是UNICODE来扩展成正确的函敎ͼ像SetWindowText所作的一栗?BR> 不仅str***()函数有TCHAR宏。其他的函数如, _stprintfQ代替sprinft()和swprintf()Q?_tfopenQ代替fopen()和_wfopen()Q?MSDN?Generic-Text Routine Mappings."标题下有完整的宏列表?BR>
字符串和TCHAR typedefs
׃Win32 API文档的函数列表用函数的常用名字Q例如,"SetWindowText"Q,所有的字符串都是用TCHAR来定义的。(除了XP中引入的只适用于Unicode的APIQ。下面列Z些常用的typedefsQ你可以在msdn中看C们?/P>
type | Meaning in MBCS builds | Meaning in Unicode builds |
WCHAR | wchar_t | wchar_t |
LPSTR | zero-terminated string of char (char*) | zero-terminated string of char (char*) |
LPCSTR | constant zero-terminated string of char (const char*) | constant zero-terminated string of char (const char*) |
LPWSTR | zero-terminated Unicode string (wchar_t*) | zero-terminated Unicode string (wchar_t*) |
LPCWSTR | constant zero-terminated Unicode string (const wchar_t*) | constant zero-terminated Unicode string (const wchar_t*) |
LPTSTR | zero-terminated string of TCHAR (TCHAR*) | zero-terminated string of TCHAR (TCHAR*) |
LPCTSTR | constant zero-terminated string of TCHAR (const TCHAR*) | constant zero-terminated string of TCHAR (const TCHAR*) |
何时使用 TCHAR ?Unicode
到现在,你可能会问,我们Z么要使用Unicode。我已经用了很多q的char。下?U情况下Q用Unicode会使你受益Q?/P>
Windows 9x 中大多数?API 没有实现 Unicode 版本。所以,如果你的E序要在windows 9x中运行,你必M用MBCS APIs。然而,׃NTpȝ内部都用UnicodeQ所以用Unicode APIs会加快你的E序的运行速度。每ơ,你传递一个字W串调用MBCS APIQ操作系l会把这个字W串转换成Unicode字符Ԍ然后调用对应的Unicode API。如果一个字W串被返回,操作pȝq要把它转变回去。尽这个{换过E被高度优化了,但它寚w度造成的损失是无法避免的?BR> 只要你用Unicode APIQNTpȝ允许使用非常长的文g名(H破了MAX_PATH的限ӞMAX_PATH=260Q。用Unicode API的另一个优Ҏ(gu)你的E序会自动处理用戯入的各种语言。所以一个用户可以输入英文,中文或者日文,而你不需要额外编写代码去处理它们?BR> 最后,随着windows 9x产品的E出,微Yg正在抛弃MBCS APIs。例如,包含两个字符串参数的SetWindowTheme() API只有Unicode版本的。用Unicode来build你的E序会化字W串的处理,你不必在MBCS和Unicdoe之间怺转换?BR> 即你现在不使用Unicode来build你的E序Q你也应该用TCHAR及其相关的宏。这样做不仅可以的代码可以很好地处理DBCSQ而且如果来你想用Unicode来build你的E序Q你只需要改变一下预处理器中的设|就可以实现了?BR>
C++字符串完全指引之?—?字符串封装类 |
C++字符串完全指引之一 —?Win32 字符~码 |
VC++中进E与多进E管理的Ҏ(gu) |
解析#pragma指o |
用ATL建立轻量U的COM对象Q一Q?/A> |
C++字符串完全指引之?—?字符串封装类
Rule #1 of string classes 使用cast来实现类型{换是不好的做法,除非有文档明指U{换可以用?BR>促我写q两文章的原因是字W串cd转换中经帔R到的一些问题。当我们使用cast把字W串从类型X转换到类型Z的时候,我们不知道ؓ什么代码不能正常工作。各U各L字符串类型,其是BSTRQ几乎没有在M一个地方的文档中被明确的指出可以用cast来实现类型{换。所以我想一些h可能会用cast来实现类型{换ƈ希望q种转换能够正常工作?BR> 除非源字W串是一个被明确指明支持转换操作W的字符串包装类Q否则cast不对字符串做M转换。对帔R字符串用cast不会起到M作用Q所以下面的代码Q? void SomeFunc ( LPCWSTR widestr ); main() { SomeFunc ( (LPCWSTR) "C:\\foo.txt" ); // WRONG! }肯定会失败。它可以被编译,因ؓcast操作会撤消编译器的类型检查。但是,~译可以通过q不能说明代码是正确的?BR> 在下面的例子中,我将会指明cast在什么时候用是合法的?PRE>C-style strings and typedefs 正如我在W一部分中提到的Qwindows APIs 是用TCHARs来定义的Q在~译Ӟ它可以根据你是否定义_MBCS或者_UNICODE被编译成MBCS或者Unicode字符。你可以参看W一部分中对TCHAR的完整描qͼq里Z方便Q我列出了字W的typedefs
一个增加的字符cd是OLETYPE。它表示自动化接口(如word提供的可以你操作文档的接口Q中使用的字W类型。这U类型一般被定义成wchar_tQ然而如果你定义了OLE2ANSI预处理标讎ͼOLECHAR会被定义成charcd。我知道现在已经没有理由定义OLE2ANSIQ从MFC3以后Q微软已l不使用它了Q,所以从现在h把OLECHAR当作Unicode字符?BR>q里l出你将会看到的一些OLECHAR相关的typedefsQ?/P>
q有两个用于包围字符串和字符帔R的宏定义Q它们可以同样的代码被用于MBCS和Unicode builds Q?/P>
在文档或例程中,你还会看到好多_T的变体。有四个{h(hun)的宏定义Q它们是TEXT, _TEXT, __TEXT和__TQ它们都起同L做用?BR>
注意字符串的长度是如何被加到字符串数据中的。长度是DWORDcd的,保存了字W串中包含的字节敎ͼ但不包括l束标记。在q个例子中,"Bob"包含3个Unicode字符Q不包括l束W)Qd6个字节。字W串的长度被预先存储好,以便当一个BSTR在进E或者计机之间被传递时QCOM库知道多数据需要传送。(另一斚wQ一个BSTR能够存储L数据块,而不仅仅是字W,它还可以包含嵌入在数据中?字符。然而,׃q篇文章的目的,我将不考虑那些情况Q?BR> ?C++ 中,一?BSTR 实际上就是一个指向字W串中第一个字W的指针。它的定义如下: BSTR bstr = NULL; bstr = SysAllocString ( L"Hi Bob!" ); if ( NULL == bstr ) // out of memory error // Use bstr here... SysFreeString ( bstr );自然的,各种各样的BSTR装cMؓ你实现内存管理?BR> 另外一个用在自动化接口中的变量cd是VARIANT。它被用来在无类型(typelessQ语aQ如Jscript和VBScriptQ来传递数据。一个VARIANT可能含有很多不同cd的数据,例如long和IDispatch*。当一个VARIANT包含一个字W串Q字W串被存成一个BSTR。当我后面讲到VARIANT装cLQ我会对VARIANT多些介绍?BR> ![]() 到目前ؓ止,我已l介l了各种各样的字W串。下面,我将说明装cR对于每个封装类Q我展C怎样创徏一个对象及怎样把它转换成一个C语言风格的字W串指针。C语言风格的字W串指针对于API的调用,或者创Z个不同的字符串类对象l常是必需的。我不会介绍字符串类提供的其他操作,比如排序和比较?BR> 重复一遍,除非你确切的明白l果代码会做什么,否则不要盲目C用cast来实现类型{换?BR> ![]() _bstr_t _bstr_t是一个对BSTR的完整封装类Q实际上它隐藏了底层的BSTR。它提供各种构造函数和操作W来讉K底层的C语言风格的字W串。然而,_bstr_t却没有访问BSTR本n的操作符Q所以一个_bstr_tcd的字W串不能被作出参Cl一个COMҎ(gu)。如果你需要一个BSTR*参数Q用ATLcCComBSTR是比较容易的方式?BR> 一个_bstr_t字符串能够传l一个接收参数类型ؓBSTR的函敎ͼ只是因ؓ下列3个条件同时满뀂首先,_bstr_t有一个向wchar_t*转换的{换函敎ͼ其次Q对~译器而言Q因为BSTR的定义,wchar_t*和BSTR有同L含义Q第三,_bstr_t内部含有的wchar_t*指向一片按BSTR的Ş式存储数据的内存。所以,即没有文档说明Q_bstr_t可以转换成BSTRQ这U{换仍然可以正常进行? // Constructing _bstr_t bs1 = "char string"; // construct from a LPCSTR _bstr_t bs2 = L"wide char string"; // construct from a LPCWSTR _bstr_t bs3 = bs1; // copy from another _bstr_t _variant_t v = "Bob"; _bstr_t bs4 = v; // construct from a _variant_t that has a string // Extracting data LPCSTR psz1 = bs1; // automatically converts to MBCS string LPCSTR psz2 = (LPCSTR) bs1; // cast OK, same as previous line LPCWSTR pwsz1 = bs1; // returns the internal Unicode string LPCWSTR pwsz2 = (LPCWSTR) bs1; // cast OK, same as previous line BSTR bstr = bs1.copy(); // copies bs1, returns it as a BSTR // ... SysFreeString ( bstr );注意_bstr_t也提供char*和wchar_t*之间的{换操作符。这是一个值得怀疑的设计Q因为即使它们是非常量字W串指针Q你也一定不能用这些指针去修改它们指向的缓冲区的内容,因ؓ那将破坏内部的BSTRl构?BR> _variant_t _variant_t是一个对VARIANT的完整封装,它提供很多构造函数和转换函数来操作一个VARIANT可能包含的大量的数据cd。这里,我将只介l与字符串有关的操作? // Constructing _variant_t v1 = "char string"; // construct from a LPCSTR _variant_t v2 = L"wide char string"; // construct from a LPCWSTR _bstr_t bs1 = "Bob"; _variant_t v3 = bs1; // copy from a _bstr_t object // Extracting data _bstr_t bs2 = v1; // extract BSTR from the VARIANT _bstr_t bs3 = (_bstr_t) v1; // cast OK, same as previous line注意Q?BR> 如果cd转换不能被执行,_variant_tҎ(gu)能够抛出异常Q所以应该准备捕获_com_error异常?BR> q需要注意的?/B>Q?BR> 没有从一个_variant_t变量C个MBCS字符串的直接转换。你需要创Z个(f)时的_bstr_t变量Q用提供Unicode到MBCS转换的另一个字W串cL者用一个ATL转换宏?BR> 不像_bstr_tQ一个_variant_t变量可以被直接作为参C递给一个COMҎ(gu)。_variant_t l承自VARIANTcdQ所以传递一个_variant_t来代替VARIANT变量是C++语言所允许的?BR> ![]() STL只有一个字W串c,basic_string。一个basic_string理一个以0做结束符的字W串数组。字W的cd是basic_string模般的参数。ȝ来说Q一个basic_stringcd的变量应该被当作不透明的对象。你可以得到一个指向内部缓冲区的只L针,但是M写操作必M用basic_string的操作符和方法?BR> basic_string有两个预定义的类型:包含char的stringcd和包含wchar_t的wstringcd。这里没有内|的包含TCHAR的类型,但是你可以用下面列出的代码来实现? // Specializations typedef basic_string不像_bstr_tQ一个basic_string变量不能在字W集之间直接转换。然而,你可以传递由c_str()q回的指针给另外一个类的构造函敎ͼ如果q个cȝ构造函数接受这U字W类型)。例如: // Example, construct _bstr_t from basic_string _bstr_t bs1 = str.c_str(); // construct a _bstr_t from a LPCSTR _bstr_t bs2 = wstr.c_str(); // construct a _bstr_t from a LPCWSTR ![]() CComBSTR CComBSTR ?ATL 中的 BSTR 装c,它在某些情况下比_bstr_t有用的多。最引h注意的是CComBSTR允许讉K底层的BSTRQ这意味着你可以传递一个CComBSTR对象lCOM的方法。CComBSTR对象能够替你自动的管理BSTR的内存。例如,假设你想调用下面q个接口的方法: // Sample interface: struct IStuff : public IUnknown { // Boilerplate COM stuff omitted... STDMETHOD(SetText)(BSTR bsText); STDMETHOD(GetText)(BSTR* pbsText); };CComBSTR有一个操作符--BSTRҎ(gu)Q所以它能直接被传给SetText()函数。还有另外一个操?-&Q这个操作符q回一个BSTR*。所以,你可以对一个CComBSTR对象使用&操作W,然后把它传给需要BSTR*参数的函数? CComBSTR bs1; CComBSTR bs2 = "new text"; pStuff->GetText ( &bs1 ); // ok, takes address of internal BSTR pStuff->SetText ( bs2 ); // ok, calls BSTR converter pStuff->SetText ( (BSTR) bs2 ); // cast ok, same as previous lineCComBSTR有和_bstr_t怼的构造函敎ͼ然而却没有内置的向MBCS字符串{换的函数。因此,你需要用一个ATL转换宏? // Constructing CComBSTR bs1 = "char string"; // construct from a LPCSTR CComBSTR bs2 = L"wide char string"; // construct from a LPCWSTR CComBSTR bs3 = bs1; // copy from another CComBSTR CComBSTR bs4; bs4.LoadString ( IDS_SOME_STR ); // load string from string table // Extracting data BSTR bstr1 = bs1; // returns internal BSTR, but don''t modify it! BSTR bstr2 = (BSTR) bs1; // cast ok, same as previous line BSTR bstr3 = bs1.Copy(); // copies bs1, returns it as a BSTR BSTR bstr4; bstr4 = bs1.Detach(); // bs1 no longer manages its BSTR // ... SysFreeString ( bstr3 ); SysFreeString ( bstr4 );注意在上个例子中使用了Detach()Ҏ(gu)。调用这个方法后QCComBSTR对象不再理它的BSTR字符串或者说它对应的内存。这是bstr4需要调用SysFreeString()的原因?BR> 做一个补充说明:重蝲?amp;操作W意味着在一些STL容器中你不能直接使用CComBSTR变量Q比如list。容器要?amp;操作W返回一个指向容器包含的cȝ指针Q但是对CComBSTR变量使用&操作W返回的是BSTR*Q而不是CComBSTR*。然而,有一个ATLcd以解册个问题,q个cLCAdapt。例如,你可以这样声明一个CComBSTR的listQ?PRE>std::list< CAdapt CAdapt提供容器所需要的操作W,但这些操作符对你的代码是透明的。你可以把一个bstr_list当作一个CComBSTR的list来用?BR> // Constructing CComVariant v1 = "char string"; // construct from a LPCSTR CComVariant v2 = L"wide char string"; // construct from a LPCWSTR CComBSTR bs1 = "BSTR bob"; CComVariant v3 = (BSTR) bs1; // copy from a BSTR // Extracting data CComBSTR bs2 = v1.bstrVal; // extract BSTR from the VARIANT不像_variant_tQ这里没有提供针对VARIANT包含的各U类型的转换操作W。正如上面介l的Q你必须直接讉KVARIANT的成员ƈ且确保这个VARIANT变量保存着你期望的cd。如果你需要把一个CComVariantcd的数据{换成一个BSTRcd的数据,你可以调用ChangeType()Ҏ(gu)? CComVariant v4 = ... // Init v4 from somewhere CComBSTR bs3; if ( SUCCEEDED( v4.ChangeType ( VT_BSTR ) )) bs3 = v4.bstrVal;像_variant_t一PCComVariant也没有提供向MBCS字符串{换的转换操作。你需要创Z个_bstr_tcd的中间变量,使用提供从Unicode到MBCS转换的另一个字W串c,或者用一个ATL的{换宏?BR> ![]() ATLQ{换宏是各U字W编码之间进行{换的一U很方便的方式,在函数调用时Q它们显得非常有用。ATL转换宏的名称是根据下面的模式来命名的[源类型]2[新类型]或者[源类型]2C[新类型]。据有第二种形式的名字的宏的转换l果是常量指针(对应名字中的"C"Q。各U类型的U如下: A: MBCS string, char* (A for ANSI) W: Unicode string, wchar_t* (W for wide) T: TCHAR string, TCHAR* OLE: OLECHAR string, OLECHAR* (in practice, equivalent to W) BSTR: BSTR (used as the destination type only) 所以,W2A()宏把一个Unicode字符串{换成一个MBCS字符丌ӀT2CW()宏把一个TCHAR字符串{转成一个Unicode字符串常量?BR> Z使用q些宏,需要先包含atlconv.h头文件。你甚至可以在非ATL工程中包含这个头文g来用其中定义的宏,因ؓq个头文件独立于ATL中的其他部分Q不需要一个_Module全局变量。当你在一个函C使用转换宏时Q需要把USES_CONVERSION宏放在函数的开头。它定义了{换宏所需的一些局部变量?BR> 当{换的目的cd是除了BSTR以外的其他类型时Q被转换的字W串是存在栈中的。所以,如果你想让字W串的生命周期比当前的函数长Q你需要把q个字符串拷贝到其他的字W串cM。当目的cd是BSTRӞ内存不会自动被释放,你必Lq回Dl一个BSTR变量或者一个BSTR装cM避免内存泄漏?BR> 下面是一些各U{换宏的用例子: // Functions taking various strings: void Foo ( LPCWSTR wstr ); void Bar ( BSTR bstr ); // Functions returning strings: void Baz ( BSTR* pbstr ); #include正如你所看见的,当你有一个和函数所需的参数类型不同的字符串时Q用这些{换宏是非常方便的? ![]() CString 因ؓ一个MFC CStringcȝ对象包含TCHARcd的字W,所以确切的字符cd取决于你所定义的预处理W号。大体来_CString 很像STL stringQ这意味着你必L它当成不透明的对象,只能使用CString提供的方法来修改CString对象。CString有一个string所不具备的优点QCStringh接收MBCS和Unicode两种字符串的构造函敎ͼ它还有一个LPCTSTR转换W,所以你可以把CString对象直接传给一个接收LPCTSTR的函数而不需要调用c_str()函数? // Constructing CString s1 = "char string"; // construct from a LPCSTR CString s2 = L"wide char string"; // construct from a LPCWSTR CString s3 ( '' '', 100 ); // pre-allocate a 100-byte buffer, fill with spaces CString s4 = "New window text"; // You can pass a CString in place of an LPCTSTR: SetWindowText ( hwndSomeWindow, s4 ); // Or, equivalently, explicitly cast the CString: SetWindowText ( hwndSomeWindow, (LPCTSTR) s4 );你可以从你的字符串表中装载一个字W串QCString的一个构造函数和LoadString()函数可以完成它。Format()Ҏ(gu)能够从字W串表中随意的读取一个具有一定格式的字符丌Ӏ // Constructing/loading from string table CString s5 ( (LPCTSTR) IDS_SOME_STR ); // load from string table CString s6, s7; // Load from string table. s6.LoadString ( IDS_SOME_STR ); // Load printf-style format string from the string table: s7.Format ( IDS_SOME_FORMAT, "bob", nSomeStuff, ... );W一个构造函数看h有点奇怪,但是q实际上是文档说明的装入一个字W串的方法?注意Q对一个CString变量Q你可以使用的唯一合法转换W是LPCTSTR。{换成LPTSTRQ非帔R指针Q是错误的。养成把一个CString变量转换成LPTSTR的习惯将会给你带来伤宻I因ؓ当你的程序后来崩溃时Q你可能不知道ؓ什么,因ؓ你到处都使用同样的代码而那时它们都恰y正常工作。正的得到一个指向缓冲区的非帔R指针的方法是调用GetBuffer()Ҏ(gu)。下面是正确的用法的一个例子,q段代码是给一个列表控件中的项讑֮文字Q? CString str = _T("new text"); LVITEM item = {0}; item.mask = LVIF_TEXT; item.iItem = 1; item.pszText = (LPTSTR)(LPCTSTR) str; // WRONG! item.pszText = str.GetBuffer(0); // correct ListView_SetItem ( &item ); str.ReleaseBuffer(); // return control of the buffer to strpszText成员是一个LPTSTR变量Q一个非帔R指针Q因此你需要对str调用GetBuffer()。GetBuffer()的参数是你需要CString为缓冲区分配的最长度。如果因为某些原因,你需要一个可修改的缓冲区来存?K TCHARsQ你需要调用GetBuffer(1024)。把0作ؓ参数ӞGetBuffer()q回的是指向字符串当前内容的指针?BR> 上面划线的语句可以被~译Q在q种情况下,甚至可以正常起作用。但qƈ不意味着q行代码是正的。通过使用非常量{换,你已l破坏了面向对象的封装,q对CString的内部实C了某些假定。如果你有这L转换习惯Q你l将会陷入代码崩溃的境地。你会想代码Z么不能正常工作了Q因Z到处都用同L代码而那些代码看h是正的?BR> 你知道h们L抱怨现在的软g的bug是多么的多吗QY件中的bug是因为程序员写了不正的代码。难道你真的惛_一些你知道是错误的代码来ؓ所有的软g都满是bugq种认识做A(ch)献吗Q花些时间来学习使用CString的正方法让你的代码在Q何时间都正常工作把?BR> CString 有两个函数来从一?CString 创徏一?BSTR。它们是 AllocSysString() 和SetSysString()? // Converting to BSTR CString s5 = "Bob!"; BSTR bs1 = NULL, bs2 = NULL; bs1 = s5.AllocSysString(); s5.SetSysString ( &bs2 ); SysFreeString ( bs1 ); SysFreeString ( bs2 );COleVariant COleVariant和CComVariant.很相伹{COleVariantl承自VARIANTQ所以它可以传给接收VARIANT的函数。然而,不像CComVariantQCOleVariant只有一个LPCTSTR构造函数。没有对LPCSTR 和LPCWSTR的构造函数。在大多数情况下q不是一个问题,因ؓ不管怎样你的字符串很可能是LPCTSTRsQ但q是一个需要意识到的问题。COleVariantq有一个接收CString参数的构造函数? // Constructing CString s1 = _T("tchar string"); COleVariant v1 = _T("Bob"); // construct from an LPCTSTR COleVariant v2 = s1; // copy from a CString像CComVariant一P你必ȝ接访问VARIANT的成员。如果需要把VARIANT转换成一个字W串Q你应该使用ChangeType()Ҏ(gu)。然而,COleVariant::ChangeType()如果p|会抛出异常,而不是返回一个表C失败的HRESULT代码? // Extracting data COleVariant v3 = ...; // fill in v3 from somewhere BSTR bs = NULL; try { v3.ChangeType ( VT_BSTR ); bs = v3.bstrVal; } catch ( COleException* e ) { // error, couldn''t convert } SysFreeString ( bs ); ![]() CString WTL的CString的行为和MFC?CString完全一P所以你可以参考上面关于MFC?CString的介l?BR> ![]() System::String是用来处理字W串?NETcR在内部Q一个String对象包含一个不可改变的字符串序列。Q何对String对象的操作实际上都是q回了一个新的String对象Q因为原始的对象是不可改变的。String的一个特性是如果你有不止一个String对象包含相同的字W序列,它们实际上是指向相同的对象的。相对于C++的用扩展是增加了一个新的字W串帔R前缀SQS用来代表一个受控的字符串常量(a managed string literalQ? // Constructing String* ms = S"This is a nice managed string";你可以传递一个非受控的字W串来创Z个String对象Q但是样会比使用受控字符串来创徏String对象造成效率的微损失。这是因为所有以S作ؓ前缀的相同的字符串实例都代表同样的对象,但这寚w受控对象是不适用的。下面的代码清楚地阐明了q一点: String* ms1 = S"this is nice"; String* ms2 = S"this is nice"; String* ms3 = L"this is nice"; Console::WriteLine ( ms1 == ms2 ); // prints true Console::WriteLine ( ms1 == ms3); // prints false正确的比较可能没有用S前缀的字W串的方法是使用String::CompareTo() Console::WriteLine ( ms1->CompareTo(ms2) ); Console::WriteLine ( ms1->CompareTo(ms3) );上面的两行代码都会打?Q?表示两个字符串相{?String和MFC 7 CString之间的{换是很容易的。CString有一个向LPCTSTR的{换操作,而String有两个接收char* ?wchar_t*的构造函敎ͼ因此你可以把一个CString变量直接传给一个String的构造函数? CString s1 ( "hello world" ); String* s2 ( s1 ); // copy from a CString反方向的转换也很cM String* s1 = S"Three cats"; CString s2 ( s1 );q也怼使你感到一点迷惑,但是它确实是起作用的。因ZVS.NET 开始,CString 有了一个接收String 对象的构造函数? CStringT ( System::String* pString );对于一些快速操作,你可能想讉K底层的字W串Q? String* s1 = S"Three cats"; Console::WriteLine ( s1 ); const __wchar_t __pin* pstr = PtrToStringChars(s1); for ( int i = 0; i < wcslen(pstr); i++ ) (*const_cast<__wchar_t*>(pstr+i))++; Console::WriteLine ( s1 );PtrToStringChars()q回一个指向底层字W串的const __wchar_t* Q我们需要固定它Q否则垃圾收集器或许会在我们正在理它的内容的时候移动了它? ![]() 当你在printf()或者类似的函数中用字W串装cL你必d分小心。这些函数包括sprintf()和它的变体,q有TRACE和ATLTRACE宏。因些函数没有对d的参数的cd查,你必d心,只能传给它们C语言风格的字W串指针Q而不是一个完整的字符串类?BR> 例如Q要把一个_bstr_t 字符串传lATLTRACE()Q你必须使用昑ּ转换(LPCSTR) 或?LPCWSTR)Q?PRE>_bstr_t bs = L"Bob!"; ATLTRACE("The string is: %s in line %d\n", (LPCSTR) bs, nLine); 如果你忘了用{换符而把整个_bstr_t对象传给了函敎ͼ会昄一些毫无意义的输出Q因为_bstr_t保存的内部数据会全部被输出?BR>
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() Michael DunnQ?BR> Michael Dunn居住在阳光城市洛杉矶。他是如此的喜欢q里的天气以致于想一生都住在q里。他?q时开始编E,那时用的?sh)脑是Apple //e?995q_在UCLA获得数学学士学位Q随后在Symantec公司做QA工程师,?Norton AntiVirus l工作。他自学?Windows ?MFC ~程?999-2000q_他设计ƈ实现?Norton AntiVirus的新界面?BR> Michael 现在?NapsterQ一个提供在U订阅音乐服务的公司Q做开发工作,他还开发了UltraBarQ一个IE工具栏插Ӟ它可以ɾ|络搜烦更加Ҏ(gu)Q给?googlebar 以沉重打击;他还开发了 CodeProject SearchBarQ与人共同创Z Zabersoft 公司Q该公司在洛杉矶和丹麦的 Odense 都设有办事处?BR> 他喜Ƣ玩游戏。爱玩的游戏?pinball, bike ridingQ偶还?PS, Dreamcasth ?MAME 游戏。他因忘了自己曾l学q的语言Q法语、汉语、日语而感到?zhn)哀?BR> Nishant S(Nish)Q?BR> Nish是来自印?Trivandrum,?Microsoft Visual C++ MVP。他?990q开始编码。现在,NishZ为合同雇员在安?CodeProject 工作。 他还写了一部浪漫戏剧?TCHAR> |
C++字符串完全指引之?—?字符串封装类 |
C++字符串完全指引之一 —?Win32 字符~码 |
VC++中进E与多进E管理的Ҏ(gu) |
解析#pragma指o |
用ATL建立轻量U的COM对象Q一Q?/A> |