I.关于Unicode
转自:http://blog.csdn.net/jxluofeng/article/details/9132649
最近因为项目需求,在windows平台下总是碰到一些关于字符串类型的变换,吃了不少苦头,索性花了这个晚上来整理下有关字节的知识,碰到的困难以及自己的一些解决方案。
一,字符的基础 ASCII ,DBCS( MBCS ) ,UNICODE
1.1,ASCII码( SBCS )
ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用7 位二进制数来表示所有的大写和小写字母,数字0 到9、标点符号, 以及在美式英语中使用的特殊控制字符。其中:
0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(响铃)等;通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;ASCII值为8、9、10 和13 分别转换为退格、制表、换行和回车字符。它们并没有特定的图形显示,但会依不同的应用程序,而对文本显示有不同的影响。
32~126(共95个)是字符(32sp是空格),其中48~57为0到9十个阿拉伯数字
65~90为26个大写英文字母( A-Z ),97~122号为26个小写英文字母( a-z )
单字节字符集(single-byte character set or SBCS)。在这种编码模式下,所有的字符都只用一个字节表示。ASCII是SBCS。一个字节表示的0用来标志SBCS字符串的结束。
1.2,DBCS( MBCS )
英语用ascii编码就够了,但是我们知道,世界上很多国家都有自己的文字,ascii肯定是不能满足,在这里我们就讨论下中文字符,对于中文而言,则必须使用两个字节(byte)来代表一个字符,具第一个字节必须大于127(所以我们有许程序判断中文都是以ascii码大于127作为条件)以上用两个字节来表示一个中文的方式,在习惯上称为双字节(即DBCS: Double-Byte Character Set),由于Windows里使用的多字节字符绝大部分是两个字节长,所以MBCS常被用DBCS代替。
我们运行下面这段代码:
#include <stdio.h> #include <string.h> int main() { char *str1="hello world!"; char *str2="世界,你好!"; char *str3="我爱你"; printf("the length of str1=%d\n",strlen(str1)); printf("the length of str2=%d\n",strlen(str2)); 1printf("the length of str3=%d\n",strlen(str3)); return 0; }
我们可以看出中文字符占两个字节,英文字符占一个字节。
1.3,UNICODE
虽然双字节(DBCS)足以解决中英文字符混合使用情况,但对于不同字符系统而言,必须经过字符码转换,非常麻烦。例如:中英文混合情况,日文,韩文等等。 为解决这个问题,Apple,Xerox,Microsoft,IBM,Novell,Borland...很多公司联合起来制订了一套可以适用于全世界所有国家的字符码,就称为Unicode ,java的内核就是16位双字节编码Unicode为基准。
char类型表示Unicode编码方案中的字符。Unicode可同时包含65536个字符,ASCII/ANSI只包含255个字符,实际上是Unicode的一个子集。Unicode字符通常用十六进制编码方案表示,范围在'\u0000'到'\uFFFF'之间。\u0000到\u00FF表示ASCII/ANSI字符。\u表示这是一个Unicode值。
二,windows平台下各种字符串定义
VC6.0默认的是DBCS字符集,VS默认的是UNICODE字符集。
char :单字节变量类型,最多表示256个字符,
wchar_t :宽字节变量类型,用于表示Unicode字符,
它实际定义在<string.h>里:typedef unsigned short wchar_t。
为了让编译器识别Unicode字符串,必须以在前面加一个“L”,定义宽字节类型方法如下:
wchar_t c = 'A' ;
wchar_t * p = L"Hello!" ;
wchar_t a[] = L"Hello!" ;
其中,宽字节类型每个变量占用2个字节,故上述数组a的sizeof(a) = 14
TCHAR / _T( ) :
如果在程序中既包括ANSI又包括Unicode编码,需要包括头文件tchar.h。TCHAR是定义在该头文件中的宏,它视你是否定义了_UNICODE宏而定义成:
定义了_UNICODE: typedef wchar_t TCHAR ;
没有定义_UNICODE: typedef char TCHAR ;
#ifdef UNICODE typedef char TCHAR; #else typede wchar_t TCHAR; #endif
_T( )也是定义在该头文件中的宏,视是否定义了_UNICODE宏而定义成:
定义了_UNICODE: #define _T(x) L##x
没有定义_UNICODE: #define _T(x) x
注意:如果在程序中使用了TCHAR,那么就不应该使用ANSI的strXXX函数或者Unicode的wcsXXX函数了,而必须使用tchar.h中定义的_tcsXXX函数。
以strcpy函数为例子,总结一下:
//如果你想使用ANSI字符串,那么请使用这一套写法: char szString[100]; strcpy(szString,"test"); //如果你想使用Unicode字符串,那么请使用这一套: wchar_t szString[100]; wcscpy(szString,L"test"); //如果你想通过定义_UNICODE宏,而编译ANSI或者Unicode字符串代码: TCHAR szString[100]; _tcscpy(szString,_TEXT("test"));
这样我们用TCHAR,_tcscpy等就可以实现在UNICODE字符集和DBCS字符集兼容,明显地,这样同样的代码可以在VC6.0和VS平台下运行。
2.1、在字符串前加一个L作用:
如 L"我的字符串" 表示将ANSI字符串转换成unicode的字符串,就是每个字符占用两个字节。
strlen("asd") = 3;
strlen(L"asd") = 6;
2.2、_T宏可以把一个引号引起来的字符串,根据你的环境设置,使得编译器会根据编译目标环境选择合适的(Unicode还是ANSI)字符处理方式
如果你定义了UNICODE,那么_T宏会把字符串前面加一个L。这时 _T("ABCD") 相当于 L"ABCD" ,这是宽字符串。
如果没有定义,那么_T宏不会在字符串前面加那个L,_T("ABCD") 就等价于 "ABCD"
2.3、TEXT,_TEXT 和_T 一样的
如下面三语句:
TCHAR szStr1[] = TEXT("str1");
char szStr2[] = "str1";
WCHAR szStr3[] = L("str1");
那么第一句话在定义了UNICODE时会解释为第三句话,没有定义时就等于第二句话。
但二句话无论是否定义了UNICODE都是生成一个ANSI字符串,而第三句话总是生成UNICODE字符串。
为了程序的可移植性,建议都用第一种表示方法。
但在某些情况下,某个字符必须为ANSI或UNICODE,那就用后两种方法。
2.4、_T()函数详解
_T("")是一个宏,他的作用是让你的程序支持Unicode编码,因为Windows使用两种字符集ANSI和UNICODE,前者就是通常使用的单字节方式,但这种方式处理象中文这样的双字节字符不方便,容易出现半个汉字的情况。而后者是双字节方式,方便处理双字节字符。Windows NT的所有与字符有关的函数都提供两种方式的版本,而Windows 9x只支持ANSI方式。如果你编译一个程序为ANSI方式,_T实际不起任何作用。而如果编译一个程序为UNICODE方式,则编译器会把"Hello"字符串以UNICODE方式保存。_T和_L的区别在于,_L不管你是以什么方式编译,一律以UNICODE方式保存。
LPSTR:32bit指针指向一个字符串,每个字符占1字节
LPCSTR:32-bit指针指向一个常字符串,每个字符占1字节
LPWSTR:32bit指针指向一个字符串,每个字符占2字节
LPCWSTR:32-bit指针指向一个常字符串,每个字符占2字节
LPCTSTR:32-bit指针指向一个常字符串,每字符可能占1字节或2字节,取决于Unicode是否定义
LPTSTR:32-bit指针每字符可能占1字节或2字节,取决于Unicode是否定义
L是表示字符串资源为Unicode的。
比如:
wchar_t Str[] = L"Hello World!";
这个就是双子节存储字符了。
_T是一个适配的宏,当#ifdef _UNICODE的时候,_T就是L。没有#ifdef _UNICODE的时候,_T就是ANSI的。比如
LPTSTR lpStr = new TCHAR[32];
TCHAR* szBuf = _T("Hello");
以上两句使得无论是在UNICODE编译条件下都是正确编译的。而且MS推荐你使用相匹配的字符串函数。比如处理LPTSTR或者LPCTSTR 的时候,不要用strlen ,而是要用_tcslen。否则在UNICODE的编译条件下,strlen不能处理 wchar_t*的字符串。T是非常有意思的一个符号(TCHAR、LPCTSTR、LPTSTR、_T()、_TEXT()...),它表示使用一种中间类型,既不明确表示使用 MBCS,也不明确表示使用 UNICODE。那到底使用哪种字符集?编译的时候才决定。
II.使用fstream类打开文件的时候,如果文件名中含有中文或者路径中含有中文的话,文件打开失败!
(1). 在VS2008的“Property Pages”属性页中,选择“Configuration Properties”-->“General”,可以看到当前使用的字符集是“Multi-Byte Character Set”,也就是说程序中使用的是多字节字符集。
(2).接下来看看ifstream打开txt文件的简单代码:
#include <fstream> #include <iostream> using namespace std; int _tmain(int argc, _TCHAR* argv[]) { ifstream infile("d:\\测试.txt"); if(infile.is_open()) { cout<<"Open Success!"; } else { cout<<"Open Fail!"; } return 0; }
(3).运行结果:输出“Open Fail” (打开文件失败!)
那么这两种编码方式有什么样的区别呢?
(1).传统的计算机使用ANSI编码,在ANSI编码模式下,英文字符都用1个字节表示,而某些其它国家的文字(如汉字、日文),无法用单个字节来表示,ANSI便采用多个字节来表示这些字符(汉字是2个字节)。
(2).UNICODE包含UTF-8、UTF-16、UTF-32等多种编码方案(目前windows一般使用UTF-16)。拿UTF-16来说,规定所有字符都使用2个字节表示(不论英文字母还是汉字),对于超出2个字节范围的字符采用代理(采用4个字节表示)。
UNICODE相比ANSI有很多方面的优势(优势体现在哪?),微软非常提倡使用UNICODE编码方式,在MS较新版本的系统中都是采用UNICODE编码的。因此,即便我们在自己写的程序中使用了ANSI编码,系统会将其转换为UNICODE再对其进行处理。
#include <tchar.h> #include <fstream> #include <iostream> using namespace std; int main() { /************************************************************************/ /* 方法1,使用_TEXT()宏定义将字符串常量指定为TCHAR*类型 */ /* 如果是我,首选此类型 */ /************************************************************************/ fstream file; file.open(_TEXT("c:\\测试\\测试文本.txt")); cout<<file.rdbuf(); file.close(); /************************************************************************/ /* 方法2,使用STL中的locale类的静态方法指定全局locale */ /* 使用该方法以后,cout可能不能正常输出中文,十分蹊跷 */ /* 我发现了勉强解决的方法:不要在还原区域设定前用cout或wcout 输出中文 */ /* 否则后果就是还原区域设定后无法使用cout wcout输出中文 */ /************************************************************************/ locale::global(locale(""));//将全局区域设为操作系统默认区域 file.open("c:\\测试\\测试文本2.txt");//可以顺利打开文件了 locale::global(locale("C"));//还原全局区域设定 cout<<file.rdbuf(); file.close(); /************************************************************************/ /* 方法3,使用C函数setlocale,不能用cout输出中文的问题解决方法同上 /************************************************************************/ setlocale(LC_ALL,"Chinese-simplified");//设置中文环境 file.open("c:\\测试\\测试文本3.txt");//可以顺利打开文件了 setlocale(LC_ALL,"C");//还原 cout<<file.rdbuf(); file.close(); }
由于windows提倡使用UNICODE编码,因此,我们在使用VS编写程序的时候,最好也都使用UNICODE字符集。这样有利于避免字符集转换带来的问题,同时,也有利于提高效率(前面提到,windows内部会把ANSI编码转换为UNICODE再处理,这些转换当然也带来了额外的时间消耗)。
III.关于MFC里面一系列指针
LPTSTR
LP:长指针(long pointer)。
T:win32环境中有一个_T宏,用来标识字符是否采用Unicode编码(两字节表示一个字符),若程序中定义了Unicode,该字符/字符串被作为Unicode字符串,否则就是标准的ANSI(单字节表示一个字符)字符串。
STR:表示这个变量是一个字符串。
LPCTSTR
与上面的类似,不过多了一个C,表示常量,即这是一个常指针。(即不允许修改指针指向的字符)
ANSI情况下,LPCTSTR 就是 const char*, 是常量字符串(不能修改的)。而LPTSTR 就是 char*, 即普通字符串(非常量,可修改的)。类似的,在Unicode情况下,LPCTSTR 就是 const wchar_t*, 是常量字符串(不能修改的)。而LPTSTR 就是wchar_t*.
IV. vs 2005调试“没有为任何调用堆栈框架加载任何符号
其实这主要是初学者才会犯的错误,建工程的时候不能直接建空白工程,要先建控制台,再在里面选空项目就可以正常调试了.~~
其实问题在于,在空项目中不生成调试文件pdb,所以无法调试。
要让项目生成pdb文件,需要更改:
项目属性,configuration properties->linker->Generate Debug Info 从 no 改为 yes
但这样还是不够的,还需要更改:
项目属性,configuration properties->c/c++->debug information format为/ZI
项目属性,configuration properties->c/c++->optimization为Disabled
因为为了生成这个文件,需要设定debug信息的格式并关掉O2,还要更改linker生成调试信息的开关
在你的代码编译时会根据编译器选项会生成符号文件,它用于你调试时可以映射到源代码.所以如果没有这个文件,它只能显示汇编码.如果你在调试进程,这种情况是正常的;如果是自己的代码,你可以用Debug配置重置下试试.
V.关于stack around the variable “” was corrupted问题的处理
把“project->配置属性->c/c++->代码生成->基本运行时检查 设置为默认值,就没有这样的错误了。关于MSDN的解释是在堆栈外面读写某数据。错误是名为RTC1的编译器检测的。又看了更多的技术文章,发现这样的错误是程序员在项目到了一定大的时候,它占用的堆栈量就比较大。
总结一下:
程序在Unicode环境和多字节环境下面转来转去是在是麻烦,因此我们要尽量采用微软给我们提供的一些类型来做,让程序自己完成相应的转化!
如写char类型的时候可以定义为tchar, char*类型的时候定义为LPTSTR,等等等等!
备忘:为了适用于Unicode环境,要养成使用_T()宏的习惯
1、格式化字符串
CString s;
s.Format(_T("The num is %d."), i);
2、转为 int
转10进制最好用_ttoi(),它在 ANSI 编码系统中被编译成_atoi(),而在 Unicode 编码系统中编译成_wtoi()。用_tcstoul()或者_tcstol()可以把字符串转化成任意进制的(无符号/有符号)长整数。
CString hex = _T("FAB");
CString decimal = _T("4011");
ASSERT(_tcstoul(hex, 0, 16) == _ttoi(decimal));
3、转为 char *
强制类型转换为 LPCTSTR,不能修改字符串。
LPCTSTR p = s;
或者直接 (LPCTSTR)s;