
1
• C++字符串完全指南 - Win32 字符编码(一) (02-11-14)
字符串的表现形式各异,象 TCHAR,std::string,BSTR 等等,有时还会见到怪怪的用_tcs
起头的宏。这 ..
• C++字符串完全指南 - Win32 字符编码(二) (02-11-15)
也许你没有注意到,Win32 的 API 和消息中的字符串处理函数有二种,一种为 MCBS 字
符串,另一种为 Unic ...
• C++字符串完全指南(2) - 各种字符串类(一) (02-11-19)
本文涉及到 Win32 API,MFC,STL,WTL 和 Visual C++运行库中使用到的所有的字符串
类型。说明各个类 ...
• C++字符串完全指南(2) - 各种字符串类- CRT 类 (02-11-20)
我已经说明了字符串的各种类型,现在讨论包装类。本文中我们先来看看 CRT 类。
• C++字符串完全指南(2) - STL 和 ATL 类 (02-11-21)
上一篇文章里我们介绍了 CRT 类,今天我们来讲解 STL 和 ATL 类。…
• C++字符串完全指南(2) - MFC 类 (02-11-22)
本篇继续介绍 MFC 类、WTL 类、CLR 及 VC 7 类。
• C++字符串完全指南(2) - 总结 (02-11-23)
本文介绍了字符串类的打印格式函数,然后总结了前面几篇文章中介绍的所有类。这是本
系列文章的最….
C++字符串完全指南 - Win32 字符编码(一) 2
C++字符串完全指南 - Win32 字符编码(二) 5
C++字符串完全指南(2) - 各种字符串类(一) 8
C 语言字符串与类型定义 9
C++字符串完全指南(2) - 各种字符串类- CRT 类 10
C++字符串完全指南(2) - MFC 类 15
C++字符串完全指南(2) - 总结 18

2
C++字符串完全指南 - Win32 字符编码(一)
作者: 翻译:连波
前言
字符串的表现形式各异,象 TCHAR,std::string,BSTR 等等,有时还会见到怪怪的用_tcs
起头的宏。这个指南的目的就是说明各种字符串类型及其用途,并说明如何在必要时进行类
型的相互转换。
在指南的第一部分,介绍三种字符编码格式。理解编码的工作原理是致为重要的。即使你已
经知道字符串是一个字符的数组这样的概念,也请阅读本文,它会让你明白各种字符串类之
间的关系。
指南的第二部分,将阐述各个字符串类,什么时候使用哪种字符串类,及其相互转换。
字符串基础 - ASCII, DBCS, Unicode
所有的字符串类都起源于 C 语言的字符串,而 C 语言字符串则是字符的数组。首先了解一下
字符类型。有三种编码方式和三种字符类型。
第一种编码方式是单字节字符集,称之为 SBCS,它的所有字符都只有一个字节的长度。ASCII
码就是 SBCS。SBCS 字符串由一个零字节结尾。
第二种编码方式是多字节字符集,称之为 MBCS,它包含的字符中有单字节长的字符,也有
多字节长的字符。Windows 用到的 MBCS 只有二种字符类型,单字节字符和双字节字符。因
此 Windows 中用得最多的字符是双字节字符集,即 DBCS,通常用它来代替 MBCS。
在 DBCS 编码中,用一些保留值来指明该字符属于双字节字符。例如,Shift-JIS(通用日语)
编码中,值 0x81-0x9F 和 0xE0-0xFC 的意思是:“这是一个双字节字符,下一个字节是这
个字符的一部分”。这样的值通常称为前导字节(lead byte),总是大于 0x7F。前导字节后面
是跟随字节(trail byte)。DBCS 的跟随字节可以是任何非零值。与 SBCS 一样,DBCS 字符串
也由一个零字节结尾。
第三种编码方式是 Unicode。Unicode 编码标准中的所有字符都是双字节长。有时也将
Unicode 称为宽字符集(wide characters),因为它的字符比单字节字符更宽(使用更多内存)。
注意,Unicode 不是 MBCS - 区别在于 MBCS 编码中的字符长度是不同的。Unicode 字符串用
二个零字节字符结尾(一个宽字符的零值编码)。
单字节字符集是拉丁字母,重音文字,用 ASCII 标准定义,用于 DOS 操作系统。双字节字符
集用于东亚和中东语言。Unicode 用于 COM 和 Windows NT 内部。
读者都很熟悉单字节字符集,它的数据类型是 char。双字节字符集也使用 char 数据类型(双
字节字符集中的许多古怪处之一)。Unicode 字符集用 wchar_t 数据类型。Unicode 字符串用
L 前缀起头,如:
wchar_t wch = L'1'; // 2 个字节, 0x0031
wchar_t* wsz = L"Hello"; // 12 个字节, 6 个宽字符
字符串的存储
单字节字符串顺序存放各个字符,并用零字节表示字符串结尾。例如,字符串"Bob"的存储
格式为:
Unicode 编码中,L"Bob"的存储格式为:

3
用 0x0000 (Unicode 的零编码)结束字符串。
DBCS 看上去有点象 SBCS。以后我们会看到在串处理和指针使用上是有微妙差别的。字符串
"日本语" (nihongo) 的存储格式如下(用 LB 和 TB 分别表示前导字节和跟随字节):
注意,"ni"的值不是 WORD 值 0xFA93。值 93 和 FA 顺序组合编码为字符"ni"。(在高位优先 CPU
中,存放顺序正如上所述)。
字符串处理函数
C 语言字符串处理函数,如 strcpy(), sprintf(), atol()等只能用于单字节字符串。在标准
库中有只用于 Unicode 字符串的函数,如 wcscpy(), swprintf(), _wtol()。
微软在 C 运行库(CRT)中加入了对 DBCS 字符串的支持。对应于 strxxx()函数,DBCS 使用
_mbsxxx()函数。在处理 DBCS 字符串(如日语,中文,或其它 DBCS)时,就要用_mbsxxx()函
数。这些函数也能用于处理 SBCS 字符串(因为 DBCS 字符串可能就只含有单字节字符)。
现在用一个示例来说明字符串处理函数的不同。如有 Unicode 字符串 L"Bob":
x86 CPU 的排列顺序是低位优先(little-endian)的,值 0x0042 的存储顺序为 42 00。这时如
用 strlen()函数求字符串的长度就发生问题。函数找到第一个字节 42,然后是 00,意味着
字符串结尾,于是返回 1。反之,用 wcslen()函数求"Bob"的长度更糟糕。wcslen()首先找
到 0x6F42,然后是 0x0062,以后就在内存缓冲内不断地寻找 00 00 直至发生一般性保护错
(GPF)。
strxxx()及其对应的_mbsxxx()究竟是如何运作的?二者之间的不同是非常重要的,直接影
响到正确遍历 DBCS 字符串的方法。下面先介绍字符串遍历,然后再回来讨论 strxxx()和
_mbsxxx()。
字符串遍历
我们中的大多数人都是从 SBCS 成长过来的,都习惯于用指针的 ++ 和 -- 操作符来遍历字符
串,有时也使用数组来处理字符串中的字符。这二种方法对于 SBCS 和 Unicode 字符串的操
作都是正确无误的,因为二者的字符都是等长的,编译器能够的正确返回我们寻求的字符位
置。
但对于 DBCS 字符串就不能这样了。用指针访问 DBCS 字符串有二个原则,打破这二个原则就
会造成错误。
1. 不可使用 ++ 算子,除非每次都检查是否为前导字节。
2. 绝不可使用 -- 算子来向后遍历。
先说明原则 2,因为很容易找到一个非人为的示例。假设,有一个配制文件,程序启动时要
从安装路径读取该文件,如:C:\Program Files\MyCoolApp\config.bin。文件本身是正常
的。
假设用以下代码来配制文件名:
bool GetConfigFileName ( char* pszName, size_t nBuffSize )
{

4
char szConfigFilename[MAX_PATH];
// 这里从注册表读取文件的安装路径,假设一切正常。
// 如果路径末尾没有反斜线,就加上反斜线。
// 首先,用指针指向结尾零:
char* pLastChar = strchr ( szConfigFilename, '\0' );
// 然后向后退一个字符:
pLastChar--;
if ( *pLastChar != '\\' )
strcat ( szConfigFilename, "\\" );
// 加上文件名:
strcat ( szConfigFilename, "config.bin" );
// 如果字符串长度足够,返回文件名:
if ( strlen ( szConfigFilename ) >= nBuffSize )
return false;
else
{
strcpy ( pszName, szConfigFilename );
return true;
}
}
这段代码的保护性是很强的,但用到 DBCS 字符串还是会出错。假如文件的安装路径用日语
表达:C:\ヨウユソ,该字符串的内存表达为:
这时用上面的 GetConfigFileName()函数来检查文件路径末尾是否含有反斜线就会出错,得
到错误的文件名。
错在哪里?注意上面的二个十六进制值 0x5C(蓝色)。前面的 0x5C 是字符"\",后面则是字
符值 83 5C,代表字符"ソ"。可是函数把它误认为反斜线了。
正确的方法是用 DBCS 函数将指针指向恰当的字符位置,如下所示:
bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )
{
char szConfigFilename[MAX_PATH];
// 这里从注册表读取文件的安装路径,假设一切正常。
// 如果路径末尾没有反斜线,就加上反斜线。
// 首先,用指针指向结尾零:
char* pLastChar = _mbschr ( szConfigFilename, '\0' );
// 然后向后退一个双字节字符:
pLastChar = CharPrev ( szConfigFilename, pLastChar );
if ( *pLastChar != '\\' )
_mbscat ( szConfigFilename, "\\" );
// 加上文件名:
_mbscat ( szConfigFilename, "config.bin" );
// 如果字符串长度足够,返回文件名:

5
if ( _mbslen ( szInstallDir ) >= nBuffSize )
return false;
else
{
_mbscpy ( pszName, szConfigFilename );
return true;
}
}
这个改进的函数用 CharPrev() API 函数将指针 pLastChar 向后移动一个字符。如果字符串
末尾的字符是双字节字符,就向后移动 2 个字节。这时返回的结果是正确的,因为不会将字
符误判为反斜线。
现在可以想像到第一原则了。例如,要遍历字符串寻找字符":",如果不使用 CharNext()函
数而使用++算子,当跟随字节值恰好也是":"时就会出错。
与原则 2 相关的是数组下标的使用:
2a. 绝不可在字符串数组中使用递减下标。
出错原因与原则 2 相同。例如,设置指针 pLastChar 为:
char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];
结果与原则 2 的出错一样。下标减 1 就是指针向后移动一个字节,不符原则 2。
再谈 strxxx() 与_mbsxxx()
现在可以清楚为什么要用 _mbsxxx() 函数了。strxxx() 函数不认识 DBCS 字符而 _mbsxxx()
认识。如果调用 strrchr("C:\\", '\\')函数可能会出错,但 _mbsrchr()认识双字节字符,
所以能返回指向最后出现反斜线字符的指针位置。
最后提一下 strxxx() 和 _mbsxxx() 函数族中的字符串长度测量函数,它们都返回字符串的
字节数。如果字符串含有 3 个双字节字符,_mbslen()将返回 6。而 Unicode 的函数返回的
是 wchar_ts 的数量,如 wcslen(L"Bob") 返回 3(本文开头示例的出错原因 - 译注)。
下一篇重我们将要讲述 Win32 API 中的 MBCS 和 Unicode。
C++字符串完全指南 - Win32 字符编码(二)
作者: 翻译:连波
Friday, November 15 2002 2:49 PM
Win32 API 中的 MBCS 和 Unicode
API 的二个字符集
也许你没有注意到,Win32 的 API 和消息中的字符串处理函数有二种,一种为 MCBS 字符串,
另一种为 Unicode 字符串。例如,Win32 中没有 SetWindowText()这样的接口,而是用
SetWindowTextA()和 SetWindowTextW()函数。后缀 A (表示 ANSI)指明是 MBCS 函数,后缀
W(表示宽字符)指明是 Unicode 函数。
编写 Windows 程序时,可以选择用 MBCS 或 Unicode API 接口函数。用 VC AppWizards 向导时,
如 果 不 修 改 预 处 理 器 设 置 , 缺 省 使 用 的 是 MBCS 函 数 。 但 是 在 API 接 口 中 没 有
SetWindowText()函数,该如何调用呢?实际上,在 winuser.h 头文件中做了以下定义:
BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString );
BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString );