多字节编码与Unicode码的区别 内码

说好的GBK呢

 

 

============================================================================================================

 

 

多字节字符与宽字节字符

char与wchar_t

我们知道C++基本数据类型中表示字符的有两种:char、wchar_t。 
char叫多字节字符,一个char占一个字节,之所以叫多字节字符是因为它表示一个字时可能是一个字节也可能是多个字节。一个英文字符(如’s’)用一个char(一个字节)表示,一个中文汉字(如’中’)用3个char(三个字节)表示,看下面的例子。

 
  1. void TestChar()

  2. {

  3. char ch1 = 's'; // 正确

  4. cout << "ch1:" << ch1 << endl;

  5. char ch2 = '中'; // 错误,一个char不能完整存放一个汉字信息

  6. cout << "ch2:" << ch2 << endl;

  7.  
  8. char str[4] = "中"; //前三个字节存放汉字'中',最后一个字节存放字符串结束符\0

  9. cout << "str:" << str << endl;

  10. //char str2[2] = "国"; // 错误:'str2' : array bounds overflow

  11. //cout << str2 << endl;

  12. }

 

结点如下:

ch1:s 
ch2: 
str:中

wchar_t被称为宽字符,一个wchar_t占2个字节。之所以叫宽字符是因为所有的字都要用两个字节(即一个wchar_t)来表示,不管是英文还是中文。看下面的例子:

 
  1. void TestWchar_t()

  2. {

  3. wcout.imbue(locale("chs")); // 将wcout的本地化语言设置为中文

  4.  
  5. wchar_t wch1 = L's'; // 正确

  6. wcout << "wch1:" << wch1 << endl;

  7. wchar_t wch2 = L'中'; // 正确,一个汉字用一个wchar_t表示

  8. wcout << "wch2:" << wch2 << endl;

  9.  
  10. wchar_t wstr[2] = L"中"; // 前两个字节(前一个wchar_t)存放汉字'中',最后两个字节(后一个wchar_t)存放字符串结束符\0

  11. wcout << "wstr:" << wstr << endl;

  12. wchar_t wstr2[3] = L"中国";

  13. wcout << "wstr2:" << wstr2 << endl;

  14. }

 

结果如下:

ch1:s 
ch2:中 
str:中 
str2:中国

说明: 
1. 用常量字符给wchar_t变量赋值时,前面要加L。如: wchar_t wch2 = L’中’; 
2. 用常量字符串给wchar_t数组赋值时,前面要加L。如: wchar_t wstr2[3] = L”中国”; 
3. 如果不加L,对于英文可以正常,但对于非英文(如中文)会出错。

string与wstring

字符数组可以表示一个字符串,但它是一个定长的字符串,我们在使用之前必须知道这个数组的长度。为方便字符串的操作,STL为我们定义好了字符串的类string和wstring。大家对string肯定不陌生,但wstring可能就用的少了。

string是普通的多字节版本,是基于char的,对char数组进行的一种封装。

wstring是Unicode版本,是基于wchar_t的,对wchar_t数组进行的一种封装。

string 与 wstring的相关转换:

以下的两个方法是跨平台的,可在Windows下使用,也可在Linux下使用。

 
  1. #include

  2. #include

  3. #include

  4.  
  5. // wstring => string

  6. std::string WString2String(const std::wstring& ws)

  7. {

  8. std::string strLocale = setlocale(LC_ALL, "");

  9. const wchar_t* wchSrc = ws.c_str();

  10. size_t nDestSize = wcstombs(NULL, wchSrc, 0) + 1;

  11. char *chDest = new char[nDestSize];

  12. memset(chDest,0,nDestSize);

  13. wcstombs(chDest,wchSrc,nDestSize);

  14. std::string strResult = chDest;

  15. delete []chDest;

  16. setlocale(LC_ALL, strLocale.c_str());

  17. return strResult;

  18. }

  19.  
  20. // string => wstring

  21. std::wstring String2WString(const std::string& s)

  22. {

  23. std::string strLocale = setlocale(LC_ALL, "");

  24. const char* chSrc = s.c_str();

  25. size_t nDestSize = mbstowcs(NULL, chSrc, 0) + 1;

  26. wchar_t* wchDest = new wchar_t[nDestSize];

  27. wmemset(wchDest, 0, nDestSize);

  28. mbstowcs(wchDest,chSrc,nDestSize);

  29. std::wstring wstrResult = wchDest;

  30. delete []wchDest;

  31. setlocale(LC_ALL, strLocale.c_str());

  32. return wstrResult;

  33. }

 


字符集(Charcater Set)与字符编码(Encoding)

字符集(Charcater Set或Charset):是一个系统支持的所有抽象字符的集合,也就是一系列字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。常见的字符集有:ASCII字符集、GB2312字符集(主要用于处理中文汉字)、GBK字符集(主要用于处理中文汉字)、Unicode字符集等。

字符编码(Character Encoding):是一套法则,使用该法则能够对自然语言的字符的一个字符集(如字母表或音节表),与计算机能识别的二进制数字进行配对。即它能在符号集合与数字系统之间建立对应关系,是信息处理的一项基本技术。通常人们用符号集合(一般情况下就是文字)来表达信息,而计算机的信息处理系统则是以二进制的数字来存储和处理信息的。字符编码就是将符号转换为计算机能识别的二进制编码。

一般一个字符集等同于一个编码方式,ANSI体系(ANSI是一种字符代码,为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符)的字符集如ASCII、ISO 8859-1、GB2312、GBK等等都是如此。一般我们说一种编码都是针对某一特定的字符集。 
一个字符集上也可以有多种编码方式,例如UCS字符集(也是Unicode使用的字符集)上有UTF-8、UTF-16、UTF-32等编码方式。

从计算机字符编码的发展历史角度来看,大概经历了三个阶段: 
第一个阶段:ASCII字符集和ASCII编码。 
计算机刚开始只支持英语(即拉丁字符),其它语言不能够在计算机上存储和显示。ASCII用一个字节(Byte)的7位(bit)表示一个字符,第一位置0。后来为了表示更多的欧洲常用字符又对ASCII进行了扩展,又有了EASCII,EASCII用8位表示一个字符,使它能多表示128个字符,支持了部分西欧字符。

第二个阶段:ANSI编码(本地化) 
为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如:汉字 ‘中’ 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。 
不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。 
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。

第三个阶段:UNICODE(国际化) 
为了使国际间信息交流更加方便,国际组织制定了 UNICODE 字符集,为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。UNICODE 常见的有三种编码方式:UTF-8(1个字节表示)、UTF-16((2个字节表示))、UTF-32(4个字节表示)。

我们可以用一个树状图来表示由ASCII发展而来的各个字符集和编码的分支: 

多字节编码与Unicode码的区别 内码_第1张图片 
图 1: 各种类型的编译

 


如果要更详细地了解字符集和字符编码请参考: 
字符集和字符编码(Charset & Encoding)



工程里多字节与宽字符的配制

右键你的工程名->Properties,设置如下: 

多字节编码与Unicode码的区别 内码_第2张图片 
图 2: Character Set

 

  1. 当设置为Use Unicode Character Set时,会有预编译宏:_UNICODE、UNICODE 

    多字节编码与Unicode码的区别 内码_第3张图片
    图 3: Unicode

  2. 当设置为Use Multi-Byte Character Set时,会有预编译宏:_MBCS 

    多字节编码与Unicode码的区别 内码_第4张图片
    图 4: Multi-Byte

Unicode Character Set与Multi-Byte Character Set有什么区别呢?

Unicode Character Set和Multi-Byte Character Set这两个设置有什么区别呢?我们来看一个例子: 
有一个程序需要用MessageBox弹出提示框:

 
  1. #include "windows.h"

  2.  
  3. void TestMessageBox()

  4. {

  5. ::MessageBox(NULL, "这是一个测试程序!", "Title", MB_OK);

  6. }

 

上面这个Demo非常简单不用多说了吧!我们将Character Set设置为Multi-Byte Character Set时,可以正常编译和运行。但当我们设置为Unicode Character Set,则会有以下编译错误:

error C2664: ‘MessageBoxW’ : cannot convert parameter 2 from ‘const char [18]’ to ‘LPCWSTR’

这是因为MessageBox有两个版本,一个MessageBoxW针对Unicode版的,一个是MessageBoxA针对Multi-Byte的,它们通过不同宏进行隔开,预设不同的宏会使用不同的版本。我们使用了Use Unicode Character Set就预设了_UNICODE、UNICODE宏,所以编译时就会使用MessageBoxW,这时我们传入多字节常量字符串肯定会有问题,而应该传入宽符的字符串,即将”Title”改为L”Title”就可以了,”这是一个测试程序!”也一样。

 
  1. WINUSERAPI

  2. int

  3. WINAPI

  4. MessageBoxA(

  5. __in_opt HWND hWnd,

  6. __in_opt LPCSTR lpText,

  7. __in_opt LPCSTR lpCaption,

  8. __in UINT uType);

  9. WINUSERAPI

  10. int

  11. WINAPI

  12. MessageBoxW(

  13. __in_opt HWND hWnd,

  14. __in_opt LPCWSTR lpText,

  15. __in_opt LPCWSTR lpCaption,

  16. __in UINT uType);

  17. #ifdef UNICODE

  18. #define MessageBox MessageBoxW

  19. #else

  20. #define MessageBox MessageBoxA

  21. #endif // !UNICODE

 

上面的Multi-Byte Character Set一般是指ANSI(多字节)字符集,关于ANSI请参考第二小节字符集(Charcater Set)与字符编码(Encoding)。而Unicode Character Set就是Unicode字符集,一般是指UTF-16编码的Unicode。也就是说每个字符编码为两个字节,两个字节可以表示65535个字符,65535个字符可以表示世界上大部分的语言。

一般推荐使用Unicode的方式,因为它可以适应各个国家语言,在进行软件国际时将会非常便得。除非在对存储要求非常高的时候,或要兼容C的代码时,我们才会使用多字节的方式 。



理解_T()、_Text()宏即L”“

上一小节对MessageBox的调用中除了使用L”Title”外,还可以使用_T(“Title”)和_TEXT(“Title”)。而且你会发现在MFC和Win32程序中会更多地使用_T和_TEXT,那_T、_TEXT和L之间有什么区别呢?

通过第一小节多字节字符与宽字节字符我们知道表示多字节字符(char)串常量时用一般的双引号括起来就可以了,如”String test”;而表示宽字节字符(wchar_t)串常量时要在引号前加L,如L”String test”。

查看tchar.h头文件的定义我们知道_T和_TEXT的功能是一样的,是一个预定义的宏。

 
  1. #define _T(x) __T(x)

  2. #define _TEXT(x) __T(x)

 

我们再看看__T(x)的定义,发现它有两个:

 
  1. #ifdef _UNICODE

  2. // ... 省略其它代码

  3. #define __T(x) L ## x

  4. // ... 省略其它代码

  5. #else /* ndef _UNICODE */

  6. // ... 省略其它代码

  7. #define __T(x) x

  8. // ... 省略其它代码

  9. #endif /* _UNICODE */

 

这下明白了吗?当我们的工程的Character Set设置为Use Unicode Character Set时_T和_TEXT就会在常量字符串前面加L,否则(即Use Multi-Byte Character Set时)就会以一般的字符串处理。



Dword、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR

VC++中还有一些常用的宏你也许会范糊涂,如Dword、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR。这里我们统一总结一下: 
常见的宏:

类型 MBCS UNICODE
WCHAR wchar_t wchar_t
LPSTR char* char*
LPCSTR const char* const char*
LPWSTR wchar_t* wchar_t*
LPCWSTR const wchar_t* const wchar_t*
TCHAR char wchar_t
LPTSTR TCHAR*(或char*) TCHAR* (或wchar_t*)
LPCTSTR const TCHAR* const TCHAR*

相互转换方法: 
LPWSTR->LPTSTR: W2T(); 
LPTSTR->LPWSTR: T2W(); 
LPCWSTR->LPCSTR: W2CT(); 
LPCSTR->LPCWSTR: T2CW();

ANSI->UNICODE: A2W(); 
UNICODE->ANSI: W2A();


字符串函数: 
还有一些字符串的操作函数,它们也有一 一对应关系:

MBCS UNICODE
strlen(); wcslen();
strcpy(); wcscpy();
strcmp(); wcscmp();
strcat(); wcscat();
strchr(); wcschr();

通过这些函数和宏的命名你也许就发现了一些霍规律,一般带有前缀w(或后缀W)的都是用于宽字符的,而不带前缀w(或带有后缀A)的一般是用于多字节字符的。

理解CString产生的原因与工作的机理

CString:动态的TCHAR数组,是对TCHAR数组的一种封闭。它是一个完全独立的类,封装了“+”等操作符和字符串操作方法,换句话说就是CString是对TCHAR操作的方法的集合。它的作用是方便WIN32程序和MFC程序进行字符串的处理和类型的转换。

关于CString更详细的用法请参考: 
CString与string、char*的区别和转换 

CString的常见用法

 

参考文章: 
字符集和字符编码(Charset & Encoding) 
字符,字节和编码 
《windows核心编程系列》二谈谈ANSI和Unicode字符集 
Dword、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR

 

转载地址: http://blog.csdn.net/luoweifu/article/details/49382969

 

 

 

 

===================================================================================================================================================================================================================================================================================================================================================================

C++ 字符编码转换类

记录一下C++ 编码转换的函数:

 

复制代码

 1 #pragma once
 2 #include "afx.h"
 3 
 4 
 5 #define DEFAULT_CODE 0
 6 #define CHINESE_SIMPLIFIED 1
 7 #define   CHINESE_TRADITIONAL 2
 8 
 9 class CChineseConvertor:
10 //public CObject
11 {
12 public:
13     CChineseConvertor(void);
14     ~CChineseConvertor(void);
15     LPSTR Big52GBKSimplified(char * szText);
16     LPSTR Big52GBKTraditional(char * szText);
17     LPSTR GBK2Big5(char * szText);
18     LPSTR GBKSimplified2GBKTraditional(char * szSimplified);
19     LPSTR GBKTraditional2GBKSimplified(char * szTraditional);
20     LPWSTR UTF82UNICODE(char*   utf8str);
21     LPSTR UNICODE2UTF8(LPCWSTR  strText);
22 
23     char *m_pszUnknown;
24     // 转换到Unicode
25     LPWSTR ToUnicode(char * szSource, int nEncoding);
26     LPSTR ToMultiByte(LPCWSTR szSource, int nEncoding);
27 };

复制代码

 

复制代码

  1 #include "stdafx.h"
  2 #include "Coding.h"
  3 
  4 
  5  
  6 CChineseConvertor::CChineseConvertor(void)
  7 {
  8   m_pszUnknown = new char[2];
  9   m_pszUnknown[0]=' ';
 10   m_pszUnknown[1]=0;
 11 }
 12 
 13 CChineseConvertor::~CChineseConvertor(void)
 14 {
 15   delete[] m_pszUnknown;
 16   m_pszUnknown = NULL;
 17 }
 18 
 19 //big5 to GBK_简体
 20 LPSTR CChineseConvertor::Big52GBKSimplified(char * szText)
 21 {
 22   int nLength;
 23   wchar_t *pBuffer;
 24   LPSTR pResult;
 25   int nResultLength;
 26 
 27   nLength=MultiByteToWideChar(950,0,szText,strlen(szText),NULL,0);
 28   pBuffer=new wchar_t[nLength+1];
 29   MultiByteToWideChar(950,0,(LPCSTR)szText,strlen(szText),(LPWSTR)pBuffer,nLength);
 30   pBuffer[nLength]=0;
 31 
 32   nResultLength=WideCharToMultiByte(936,0,pBuffer,nLength,NULL,0,m_pszUnknown,FALSE);
 33   pResult=new char[nResultLength+1];
 34   WideCharToMultiByte(936,0,(LPWSTR)pBuffer,nLength,(LPSTR)pResult,nResultLength,"  ",FALSE);
 35   pResult[nResultLength]=0;
 36 
 37   return GBKTraditional2GBKSimplified(pResult);
 38   
 39 }
 40 
 41 //big5 to GBK_繁体
 42 LPSTR CChineseConvertor::Big52GBKTraditional(char * szText)
 43 {
 44   int nLength;
 45   wchar_t *pBuffer;
 46   LPSTR pResult;
 47   int nResultLength;
 48 
 49   nLength=MultiByteToWideChar(950,0,szText,strlen(szText),NULL,0);
 50   pBuffer=new wchar_t[nLength+1];
 51   MultiByteToWideChar(950,0,(LPCSTR)szText,strlen(szText),(LPWSTR)pBuffer,nLength);
 52   pBuffer[nLength]=0;
 53 
 54   nResultLength=WideCharToMultiByte(936,0,pBuffer,nLength,NULL,0,m_pszUnknown,FALSE);
 55   pResult=new char[nResultLength+1];
 56   WideCharToMultiByte(936,0,(LPWSTR)pBuffer,nLength,(LPSTR)pResult,nResultLength,"  ",FALSE);
 57   pResult[nResultLength]=0;
 58 
 59   return pResult;
 60 }
 61 
 62 //GBK_简体 to GBK_繁体
 63 LPSTR CChineseConvertor::GBKTraditional2GBKSimplified(char * szTraditional)
 64 {
 65   LCID dwLocale;
 66   WORD wLangID;
 67   wLangID=MAKELANGID(LANG_CHINESE,SUBLANG_CHINESE_SIMPLIFIED);
 68   dwLocale=MAKELCID(wLangID,SORT_CHINESE_PRC);
 69 
 70   int nLength;
 71   char *pBuffer;
 72   nLength=LCMapStringA(dwLocale,LCMAP_SIMPLIFIED_CHINESE,(LPCSTR)szTraditional,strlen(szTraditional),NULL,0);
 73   pBuffer=new char[nLength+1];
 74   pBuffer[nLength]=0;
 75   LCMapStringA(dwLocale,LCMAP_SIMPLIFIED_CHINESE,(LPCSTR)szTraditional,strlen(szTraditional),pBuffer,nLength);
 76   return pBuffer;
 77 }
 78 
 79 //GBK_简体 to big5
 80 LPSTR CChineseConvertor::GBK2Big5(char * szText)
 81 {
 82   LPSTR szGBKTraditional;
 83   int nLength;
 84   wchar_t *pBuffer;
 85   LPSTR pResult;
 86   int nResultLength;
 87 
 88   szGBKTraditional=GBKSimplified2GBKTraditional(szText);
 89   nLength=MultiByteToWideChar(936,0,szGBKTraditional,strlen(szGBKTraditional),NULL,0);
 90   pBuffer=new wchar_t[nLength+1];
 91   MultiByteToWideChar(936,0,(LPCSTR)szGBKTraditional,strlen(szGBKTraditional),(LPWSTR)pBuffer,nLength);
 92   pBuffer[nLength]=0;
 93 
 94   nResultLength=WideCharToMultiByte(950,0,pBuffer,nLength,NULL,0,m_pszUnknown,FALSE);
 95   pResult=new char[nResultLength+1];
 96   WideCharToMultiByte(950,0,(LPWSTR)pBuffer,nLength,(LPSTR)pResult,nResultLength,"  ",FALSE);
 97   pResult[nResultLength]=0;
 98 
 99   return pResult;
100 }
101 
102 //将GBK的简体转换到GBK繁体
103 LPSTR CChineseConvertor::GBKSimplified2GBKTraditional(char * szSimplified)
104 {
105   LCID dwLocale;
106   WORD wLangID;
107   wLangID=MAKELANGID(LANG_CHINESE,SUBLANG_CHINESE_SIMPLIFIED);
108   dwLocale=MAKELCID(wLangID,SORT_CHINESE_PRC);
109 
110   int nLength;
111   char *pBuffer;
112   nLength=LCMapStringA(dwLocale,LCMAP_TRADITIONAL_CHINESE,(LPCSTR)szSimplified,strlen(szSimplified),NULL,0);
113   pBuffer=new char[nLength+1];
114   pBuffer[nLength]=0;
115   LCMapStringA(dwLocale,LCMAP_TRADITIONAL_CHINESE,(LPCSTR)szSimplified,strlen(szSimplified),pBuffer,nLength);
116   return pBuffer;
117 }
118 
119 // 转换到Unicode
120 LPWSTR CChineseConvertor::ToUnicode(char * szSource, int nEncoding)
121 {
122   int nLength;
123   wchar_t *pBuffer;
124   int nLanguage;
125 
126   if(nEncoding==CHINESE_SIMPLIFIED)
127     nLanguage=936;
128   else
129     if(nEncoding==CHINESE_TRADITIONAL)
130       nLanguage=950;
131     else
132       nLanguage= CP_ACP;
133 
134   nLength=MultiByteToWideChar(nLanguage,0,szSource,strlen(szSource),NULL,0);
135   pBuffer=new wchar_t[nLength+1];
136   MultiByteToWideChar(nLanguage,0,(LPCSTR)szSource,strlen(szSource),(LPWSTR)pBuffer,nLength);
137   pBuffer[nLength]=0;
138 
139   return pBuffer;
140 }
141 
142 //转换到多字节
143 LPSTR CChineseConvertor::ToMultiByte(LPCWSTR szSource, int nEncoding)
144 {
145   int nLength;
146   char *pBuffer;
147   int nLanguage;
148 
149   if(nEncoding==CHINESE_SIMPLIFIED)
150     nLanguage=936;
151   else
152     if(nEncoding==CHINESE_TRADITIONAL)
153       nLanguage=950;
154     else
155       nLanguage= CP_ACP;
156 
157   nLength=WideCharToMultiByte(nLanguage,0,szSource,wcslen(szSource),NULL,0,m_pszUnknown,FALSE);
158 
159   pBuffer=new char[nLength+1];
160   WideCharToMultiByte(nLanguage,0,szSource,wcslen(szSource),pBuffer,nLength,m_pszUnknown,FALSE);
161   pBuffer[nLength]=0;
162 
163   return pBuffer;
164 
165 }
166 
167 //UTF8转换到UNICODE
168 LPWSTR CChineseConvertor::UTF82UNICODE(char*   utf8str) 
169 {
170     int nLength;
171     wchar_t *pBuffer;
172     
173     nLength=MultiByteToWideChar(CP_UTF8,0,utf8str,strlen(utf8str),NULL,0);
174     pBuffer=new wchar_t[nLength+1];
175     MultiByteToWideChar(CP_UTF8,0,(LPCSTR)utf8str,strlen(utf8str),(LPWSTR)pBuffer,nLength);
176     pBuffer[nLength]=0;
177     
178     return pBuffer;
179 }
180 
181 //UNICODE转换到UTF8
182 LPSTR CChineseConvertor::UNICODE2UTF8(LPCWSTR  strText) 
183 {
184     int len;  
185     len = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)strText, -1, NULL, 0, NULL, NULL);  
186     char *szUtf8=new char[2*(len + 1)];
187     memset(szUtf8, 0, len * 2 + 2);  //UTF8最多的字节数最多是一个UINICODE字符所占字节数的两倍
188     WideCharToMultiByte (CP_UTF8, 0, (LPCWSTR)strText, -1, szUtf8, len, NULL,NULL);
189     return szUtf8;
190     
191 }

====================================================================================================================================================================================================================================================================================================================================================================比如Unicode和Utf8、Utf16的关系(其实Unicode是字符集和第一层字符编码,Utf8、Utf16是第二层字符编码,它们都表示了同一个字符集)

 

  在计算机中,要建立一种“字符编码模型”,需要四步【4】。

  1. 要有一个字符库,确定这些字符足够表意。 比如ASCII字符集,已经足够表示英语,但不能表示中文,于是产生GB2312字符集。

  2. 第一层编码,给每个字符编个数字,英文叫Code Point 或 Code Position。比如ASCII字符集中,65表示“A”,97表示“a”。

  3. 第二层编码,确定表示字符的二进制位数(8位,16位,32位)。ASCII使用7位,DBCS(双字节字符集)使用16位。

  4. 第三层编码,确定字符二进制值的存储格式(大端法,小端法)。比如X86机器使用小端法。

    一种字符集一般只有一种编码方式,当字符集不够用时,会增加一些新的符号,形成新的字符集。对于新的符号会有新的数字,新的编码格式。所以有时“字符集”和“字符编码”的概念并不严格区分。比如ASCII码,可以指128个字符的字符集,也可以指对这128个字符的编码方式。不过有的字符集有多种编码格式,比如Unicode字符集,Utf8、Utf16都是其编码格式(第二层编码)。

====================================================================================================================================================================================================================================================================================================================================================================

首先是单字节字符集:

1、最初美国ANSI发明了自己的编码ASCII,7-bit足够,这是标准ASCII。

2、标准ASCII码没有西欧国家拉丁文、英镑等字符,各公司、国家开始扩展,形成自己的扩展ASCII码字符集,各方混战,不过8-bit也就足够。

3、  天下分久必合,ISO统一了8-bit字符集,叫做ISO 8859.

  但是亚洲国家字符更多,一个字节远远不够,于是用多个字节表示,扩展形成本国字符集,中国GB系列,台湾Big5,日本JIS……,这些叫做多字节字符集(MBCS),windows中用双字节表示,也叫做(DBCS)。

  以上字符都是群雄割据,各自为政,windows为了迎合大家需求,在哪个国家,默认编码就用那个国家的,不过后来不知怎么被误传位ANSI编码,其实ANSI怎么可能定义世纪各国编码,不过可以理解成各编码都是在ANSI基础上扩展的,因为都兼容ANSI的标准ASCII码。

  这时,ISO再次出手,和Unicode联盟携手打造了Unicode(UCS),意图一统江湖。Unicode确实包罗万象,涵盖了各国字符,于是流行世界。Unicode自身只定义了每个字符的数值,真正二进制编码格式却是UTF-8,UTF-16(UCS-2),UTF-32(UCS-4)。

  至此,天下一统,但愿程序员的太平盛世到来了!

 

===========================================================================================================================================================================================================================================================================

 

  • 首先我们要明白,在java的世界里,所有字符的存储都是Unicode(UTF-16),因此所有的数据到java里,都会被以这种方式编码

    • 例如读入GBK的编码文件,那么java将读入文件的字节流,并且根据GBK编码规则正确识别每个字符,然后转换成Unicode码存储在内存中。

 

=========================

======================

===========================================================================

 于是聪明的中国人发明了GBK编码表,gbk编码规定,计算机不能在每次都只读一个字节(00000000,8位表示一个字节)那么死板了,你要先看看第一位是不是为0,要是为0 的话,就当作ASCII码来读入一个字节,不然的话就读入两个字节(汉子太多一个字节存不下,读入两个字节表示汉字就查GBK)。
 

 

  • Unicode 是「字符集」
  • UTF-8 是「编码规则」

(1)unicode是charset(字符集),包括GBK,GBK2312也是字符集

(2)utf-8是encoding(编码):多字节编码,对英文使用8位(1个字节),对中文使用24位(3个字节)编码

(3)unicode是一个字符集,utf-8是在这个字符集基础上的一种具体的编码方案,为了更好的存储和传输,还有utf-16,utf-32等
===========================================================================================================================================================================================================================================================================

gcc编译参数指定编码格式

2016年11月18日 12:10:45 lzhf1122 阅读数:3868 标签: 编码 gcc utf-8 更多

个人分类: Linux

gcc 编译时程序编码方式控制

 使用GCC编译时可以使用如下参数:
-finput-charset 指定源文件(保存文件时选择)的编码方式(若不指定,编译器默认是UTF-8)
-fexec-charset  指定可执行程序中的字符以什么编码方式来表示,默认是UTF-8

测试代码: 

 
  1. #include

  2. int main(int argc, char **argv)

  3. {

  4. int i = 0;

  5. unsigned char *str = "1234中";

  6.  
  7. while(str[i])

  8. {

  9. printf("%02x ",str[i]);

  10. i++;

  11. }

  12. printf("\n");

  13. return 0;

  14. }

 

保存时:

 

多字节编码与Unicode码的区别 内码_第5张图片

在Linux环境下编译执行:

 

查看“中”对应的utf-8编码为e4 b8 ad:

 

===========================================================================================================================================================================================================================================================================

GCC字符集设置

 

 

GCC提供了以下的参数开关来支持其它文字编码的源文件:

        a)-finput-charset=charset

       

 

    gcc在默认情况下,总是假设源代码的编码是UTF-8,如果是其它编码的源代码文件,源代码里面又用到了wchar_t的类型,则可以使用-finput-charset=charset这个参数来实现.例如通常使用GBK编码的源代码可以假如参数:-finput-charset=GBK

 

         b)-fwide-exec-charset=charset
         

 

  默认情况下,gcc在Windows平台下,宽字符串串常量的每个字符是16位UTF-16类型,在Linux平台下,宽字符串串常量的每个字符是32位UTF-32类型,            使用这个参数,可以改变宽字符串串常量的类型.例如在x86的机器环境,Linux操作系统下,要使例如 L"汉字" 编译后保存为UTF-16的字符串,则可以使用 -fwide-exec-charset=UTF-16LE

备注:我后来的解决方法过程,是直接用记事本重新打开另存为UTF-8格式,编译通过,不过这样感觉好麻烦,我还得再试试其它方法,最后不理会源代码文件保存格式,只是在编译选项那里设置-finput-charset=GBK,这样也是可以的(Windows上的字符编码貌似总是GBK的).

 

===========================================================================================================================================================================================================================================================================

 

解决gcc编译时的乱码问题

今天发现用securecrt登陆时,gcc编译出错时会出现乱码,但直接在主机的窗口界面下用Shell编译却没有乱码。查看了一下当时的错误描述,发现它的引号是中文引号,导致在SecureCRT中显示出错:

    main.c:1:1: error: expected identifier or '(' before numeric constant

在网上查了一下,可以通过修改LC_CTYPE=zh_CN.GBK解决这个问题,具体的方法有两个:

1. 通过export命令修改LC_CTYPE变量的值

    tianfang > export LC_CTYPE=zh_CN.GBK
    tianfang > gcc main.c
    main.c:1:1: error: expected identifier or '(' before numeric constant
    tianfang >

2. 修改/etc/sysconfig/language(大部分linux版本下这个文件叫/etc/sysconfig/i18n)中的变量设置,重新登陆后生效。

    #RC_LC_CTYPE=""
    RC_LC_CTYPE="zh_CN.GBK"

我个人比较推荐方法2。

==================================================================================================================================================================================

==================================================================================================================================================================================

GCC 编码格式及编码转换

实用为主,相关知识背景另行查阅

编码场景

这里涉及的编码分为两个场景:源码文件的编码格式和源码中字符目标文件中的编码。

源码文件的编码格式

  • -finput-charset

该选项指源码文件的编码,如GBK,UTF-8,当然,你的host得支持。GCC调用host的字符转换功能将源文件的

编码格式转换为GCC内部编码格式UTF-8,然后再进行处理。

字符在目标文件中的编码

这个问题可以理解为你在源码中定义的字符串在内存中(当然是先编译进目标文件的)是如何被表示的。又要分

为两类:窄字符和宽字符。

  • -fexec-charset

即常用的 char 类型字符,每个字符占用1个字节,例如:

char str = "字符串";

  • -fwide-exec-charset

即 wchar_t 类型字符,每个字符占用4个字节,例如:

wchar_t wstr = L"字符串"

单独的编码转换

函数

如果你的源码中有多个字符串需要使用不同的编码,那么 -fexec-charset 和 -fwide-exec-charset 也就没有办法了。libc提供了

编码转换函数iconv,其实是一组函数,头文件:"iconv.h",函数原型如下:

复制代码

 1 /* Allocate descriptor for code conversion from codeset FROMCODE to
 2 codeset TOCODE.
 3 
 4 This function is a possible cancellation point and therefore not
 5 marked with __THROW. */
 6 extern iconv_t iconv_open (const char *__tocode, const char *__fromcode);
 7 
 8 /* Convert at most *INBYTESLEFT bytes from *INBUF according to the
 9 code conversion algorithm specified by CD and place up to
10 *OUTBYTESLEFT bytes in buffer at *OUTBUF. */
11 extern size_t iconv (iconv_t __cd, char **__restrict __inbuf,
12 size_t *__restrict __inbytesleft,
13 char **__restrict __outbuf,
14 size_t *__restrict __outbytesleft);
15 
16 /* Free resources allocated for descriptor CD for code conversion.
17 
18 This function is a possible cancellation point and therefore not
19 marked with __THROW. */
20 extern int iconv_close (iconv_t __cd);

复制代码

   为了使用方便,封装成了一个函数,也可以看作是使用方法的演示:

复制代码

 1 int charset_conv(   char     *from_charset,
 2                     char     *to_charset,
 3                     char     *inbuf,
 4                     size_t     inlen,
 5                     char     *outbuf,
 6                     size_t     outlen
 7 )
 8 {
 9     iconv_t cd;
10     char **pin = &inbuf;
11     char **pout = &outbuf;
12     size_t n;
13 
14     cd = iconv_open(to_charset,from_charset);  
15     if (cd == (iconv_t)-1) {
16         if (errno == EINVAL) {
17             printf("iconv_open: form %s to %s not support\n", from_charset, to_charset);
18         }
19 
20         return -1;
21     }
22 
23     memset(outbuf,0,outlen);
24 
25     n = iconv(cd, pin, &inlen, pout, &outlen);
26     if (n == (size_t)-1) {
27         printu(LOG_DEBUG, "iconv: error\n");
28     }
29 
30     iconv_close(cd);
31 
32     return n;
33 }

复制代码

如果想将UTF-16编码的字符串转换为GBK编码的字符串,可以这样使用:

1 charset_conv("UTF-16", "GBK", p_in, size_in, p_out, size_out);

iconv相关文件

在PC机上使用iconv一般都会正常,但是一旦到了嵌入式linux中,往往会调用失败,这是因为缺少相关文件。libc只实现了接口iconv,

但并没有实现具体的转换细节,可以想想,那么多的编码类型,如果都集成到libc库中,该是多么庞大!实际上转换细节使用动态链接库实现的。

文件位于:

/usr/lib/gconv

再看具体文件:

gconv-modules:指出了做相应转换应该调用的文件

*.so:实现由内部编码到某种编码转换的动态链接库

所以,为了支持UTF-16到GBK的转换,我们至少需要3个文件:

  UTF-16.so

  GBK.so

  gconv-modules

其中gconv-modules应包含如下内容:

# from to module cost
module GBK//     INTERNAL    GBK 1
module INTERNAL   GBK//      GBK 1

# from to module cost
module UTF-16//    INTERNAL   UTF-16 1
module INTERNAL   UTF-16//    UTF-16 1

   那么这些文件从那里来呢,最方便的就是从编译器目录拷贝,例如CodeSourcery g++ Lite(某厂商提供的ARM GCC),对应默认指令集的库的

路径是:

arm-none-linux-gnueabi/libc/usr/lib/gconv

是不是很方便,后面有时间再研究下如何自己编译这些动态链接库。。。

====================================================================================================================================================================================================================================================================================================================================================================

GCC提供了以下的参数开关来支持其它文字编码的源文件:

        a)-finput-charset=charset

           

 

gcc在默认情况下,总是假设源代码的编码是UTF-8,如果是其它编码的源代码文件,源代码里面又用到了wchar_t的类型,则可以使用-finput-charset=charset这个参数来实现.例如通常使用GBK编码的源代码可以假如参数:-finput-charset=GBK

 

         b)-fwide-exec-charset=charset
           

 

 

默认情况下,gcc在Windows平台下,宽字符串串常量的每个字符是16位UTF-16类型,在Linux平台下,宽字符串串常量的每个字符是32位UTF-32类型,            使用这个参数,可以改变宽字符串串常量的类型.例如在x86的机器环境,Linux操作系统下,要使例如 L"汉字" 编译后保存为UTF-16的字符串,则可以使用 -fwide-exec-charset=UTF-16LE

备注:我后来的解决方法过程,是直接用记事本重新打开另存为UTF-8格式,编译通过,不过这样感觉好麻烦,我还得再试试其它方法,最后不理会源代码文件保存格式,只是在编译选项那里设置-finput-charset=GBK,这样也是可以的(Windows上的字符编码貌似总是GBK的).

==========================================================================================================================================================================================================================================================================

关于UTF-8、GBK编码以及编译时charset的指定的一些总结

2018年02月04日 17:42:08 old_fisher_ 阅读数:773 标签: Linux 编码 charset gcc 更多

个人分类: 一些总结

版权声明:本文为博主原创文章,转载请注明出处: https://blog.csdn.net/Blazar/article/details/79253475

首先分清两个概念:C文件中(转换为16进制)汉字的编码,编译后bin文件中汉字的编码。

gcc的 -finput-charset 和 -fexec-charset 两个选项的存在就是为了实现这两者的转换。

1. 默认情况下,gcc使用UTF-8 charset。

2. C文件中使用GBK编码的汉字:若要使bin文件为UTF-8编码,必须同时指定 -finput-charset=GBK,-fexec-charset=UTF-8(不指定fexec-charset也是可以的,但是单独指定它无效,编译器会认为输入为UTF-8)。若要使bin文件为GBK编码,可以不指定charset,这样编译器就不会去做转换(它认为前后都是UTF-8),看起来就像是“骗过”了编译器;也可以同时指定 -finput-charset=GBK,-fexec-charset=GBK。

3. C文件中使用UTF-8编码的汉字:若要使bin文件为UTF-8编码,可以不指定charset;若要使bin文件为GBK编码,必须指定 -fexec-charset=GBK。当然,如果你想“欺骗”编译器:“我的C文件使用的是GBK编码”,而去指定-finput-charset=GBK,这时编译器可能不会像上面那种情况一样好骗,你可能收到这样一条答复:“cc1: error: failure to convert GBK to UTF-8”。

 

举个例子:我在程序中需要去调用HZK16(点阵字库),它是基于GBK编码的,所以,我希望我的bin文件中的汉字是GBK编码。我在UE编辑器中,使用GBK编码写了xxx.c,又使用UTF-8编码写了yyy.c,那么,我将可以用以下几种方式去编译它们:

对于xxx.c:

(1)gcc   -o   xxx  xxx.c

(2)gcc    -finput-charset=GBK    -fexec-charset=GBK    -o    xxx   xxx.c

对于yyy.c:

(1)gcc   -fexec-charset=GBK   -o   yyy   yyy.c

(2)gcc   -finput-charset=UTF-8   -fexec-charset=GBK   -o   yyy   yyy.c

 

 

====================================================================================================================================================================================================================================================================================================================================================================

 

第二部分:字符编码
 

 

一CharSet(字符集):

 

 


字符集(CharSet或者character repertoire)是一组抽象字符(abstract character)的集合,这里的字符是用来表达语义的符号。比如所有汉字构成的字符集,西欧语言字母构成的字符集,符号构成的字符集等。字符集的子集也为字符集,比如所有繁体字的集合。下面是一些字符集的ID,
#define ANSI_CHARSET 0 (ASCI)
#define GB2312_CHARSET 134 (中简GB2312)
#define CHINESEBIG5_CHARSET 136 (中繁BIG5)
#define SHIFTJIS_CHARSET 128 (日)
#define HANGUL_CHARSET 129 (韩)
此外还有阿拉伯、希腊等等。

二CodePage(代码页):
以简体中文为例子,为了计算机查找排序方便,给所有的简体中文和一些符号(按一定的规律和规则)映射一个整数表,表里面的每一个整数对应一个汉字或符号,这个表就是代码页。(这里的整数不约束于四个字节)。
具体的说,一个字节能表示的范围是0-127,而字符却成千上万个。由于字符个数大大多于一个字节能表示的范围,所以一般用多个字节表示一个字符。(这里的多个字节对应上段内容的整数,定义为“字节串”概念)
字符集和代码页不一定是一一对应的关系,字符集里面的字符和代码页中的整数也不一定是一一对应的。比如代码页A中可以对应字符集1的中文简体中文和字符集2的英文字母。而代码页B中可以对应所有的简体和繁体中文。
#define CP_ACP 0 // default to ANSI code page
#define CP_OEMCP 1 // default to OEM  code page
#define CP_MACCP 2 // default to MAC  code page
#define CP_THREAD_ACP 3 // current thread's ANSI code page
#define CP_SYMBOL 42 // SYMBOL translations
#define CP_UTF7 65000 // UTF-7 translation
#define CP_UTF8 65001 // UTF-8 translation
可以在控制面板-区域和语言中查看自己电脑中安装的CodePage。

三什么是编码Encoding
说明CodePage的时候,所谓的“一定的规律和规则”就是编码。编码的结果就是代码页。
当前国际上最为通用的字符编码(商业规范)是Unicode编码。Unicode是由一个非赢利性组织“Unicode学术学会”建立和发展的涵盖世界大多数流行语言的字符编码形式。Unicode[现有的标准]版本为4.1,其中包含的所有语言符号(超过9万个,其中汉字为7万多)。
其他编码:GB2312, GB13000, GB18030, Unicode,UTF-8这些都是对字符集的编码(encoding)。其中常见对汉语字符集的编码包括GB2312-1980, GB13000, GBK, GB12345, GB18030—2000, Big5, Big5+, HKSCS, Big5+HKSCS, CNS 11643-1992等。UCS-2, UCS-4, UTF-32, UTF-16, UTF-8, UTF-EBCDIC和UTF-7都是Unicode编码的具体形式(即它们不是直接映射的字符,而是映射的Unicode码,其实也就对应字符啦)。

四多字节编码
由于字符个数大大多余一个字节能表示的范围,所以一般用多个字节表示一个字符。还以汉字为例子,一个汉字,在这个代码表中用1234表示,在另外一个代码表中可能用56789表示,如何转换呢?先把1234转换成一个标准的编码,然后由标准的编码转换成56789,
如此转换可以减少转换复杂度。这个“标准”就是上面提到的Unicode。同时用到了以下两个转换的window API:
MultiByteToWideChar(...)
WideCharToMultiByte(...)

五字体(Font)
知道“宋体”和“黑体”的区别,就不用解释字体的意义了。
要注意的是,字体不支持所有的字符。比如说宋体不会支持阿拉伯和梵文。所以要生成字体的时候,要选择字符集。字体里面存的是下笔拐弯和画直线等信息,对应的是文字符号。

六乱码的产生和消除
乱码产生的原因可能原因有:1代码页转换问题2没有应用合适的字体。大多数乱码出现是由于第一个原因,这里只介绍原因1。
还是拿汉字为例子。比如一个汉字,目前表示对应的是UTF-8编码中的12345。而默认的PageCode是GB2312。当显示汉字的时候,在GB2312中找12345对应的文字,肯定是错误的字或者乱码。如何正确显示呢?首先用MultiByteToWideChar函数把UTF-8编码转换成Unicode编码,再用WideCharToMultiByte函数把Unicode编码转换成GB2312的编码,然后显示才会正确。如下:
UTF-8 ---- 12345
Unicode ---- 55555
GB2312 ---- 98765
当显示的时候,在GB2312中找98765就会找到UTF-8中12345对应的汉字了。当然,用阿拉伯文字体或者梵文字体还是显示不出正确的字,还是找宋体吧 :)

 

 

===========================================================================================================================================================================================================================================================================

三种常见中文内码的转换方法

2008年01月13日 21:03:00 benny5609 阅读数:752

我们平时常见的三种中文内码是:GB2312(简体中文)、GBK、BIG5(繁体中文)。网上有很多中文内码的专用转换工具。我们碰到由于内码不一致而导致的乱麻问题,用这些工具可以进行相互转换。但论坛里经常有人问如何在自己的程序中集成这些功能呢?本文将介绍如何利用 Windows 提供的API 函数来实现。转换涉及到的 API 函数主要有两个:MultiByteToWideChar 和 WideCharToMultiByte。有关这两个函数的详细文档请参考 MSDN,本文不再赘述。

本文将介绍四个转换函数分别实现如下的转换:

  • Big5 => GBK
  • GBK => Big5
  • GB2312 => GBK
  • GBK => GB2312

  有关 GB2312 =〉BIG5 的转换以及 BIG5 =〉GB2312 的转换可以通过 GBK 间接实现。先将 GB2312 转成 GBK,再将 GBK 转成 BIG5,反之亦然。当然也可以自己实现它们之间的直接转换。

// Big5 => GBK:
// い地チ㎝瓣 --> 中華人民共和國
void BIG52GBK(char *szBuf)
{
  if(!strcmp(szBuf, ""))
   return;
  int nStrLen = strlen(szBuf);
  wchar_t *pws = new wchar_t[nStrLen + 1];
  try
  {
   int nReturn = MultiByteToWideChar(950, 0, szBuf, nStrLen, pws, nStrLen + 1);
   BOOL bValue = false;
   nReturn = WideCharToMultiByte(936, 0, pws, nReturn, szBuf, nStrLen + 1, "?", &bValue);
   szBuf[nReturn] = 0;
  }
  __finally
  {
   delete[] pws;
  }
}
//---------------------------------------------------------------------------
// GBK => Big5
// 中華人民共和國 --> い地チ㎝瓣
void GBK2BIG5(char *szBuf)
{
  if(!strcmp(szBuf, ""))
   return ;
  int nStrLen = strlen(szBuf);
  wchar_t *pws = new wchar_t[nStrLen + 1];
  __try
  {
   MultiByteToWideChar(936, 0, szBuf, nStrLen, pws, nStrLen + 1);
   BOOL bValue = false;
   WideCharToMultiByte(950, 0, pws, nStrLen, szBuf, nStrLen + 1, "?", &bValue);
   szBuf[nStrLen] = 0;
  }
  __finally
  {
   delete[] pws;
  }
}
//----------------------------------------------------------------------------
// GB2312 => GBK
// 中华人民共和国 --> 中華人民共和國
void GB2GBK(char *szBuf)
{
  if(!strcmp(szBuf, ""))
   return;
  int nStrLen = strlen(szBuf);
  WORD wLCID = MAKELCID(MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED), SORT_CHINESE_PRC);
  int nReturn = LCMapString(wLCID, LCMAP_TRADITIONAL_CHINESE, szBuf, nStrLen, NULL, 0);
  if(!nReturn)
   return;
  char *pcBuf = new char[nReturn + 1];
  __try
  {
   wLCID = MAKELCID(MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED), SORT_CHINESE_PRC);
   LCMapString(wLCID, LCMAP_TRADITIONAL_CHINESE, szBuf, nReturn, pcBuf, nReturn + 1);
   strncpy(szBuf, pcBuf, nReturn);
  }
  __finally
  {
   delete[] pcBuf;
  }
}
//---------------------------------------------------------------------------
// GBK =〉GB2312
// 中華人民共和國 --> 中华人民共和国
void GBK2GB(char *szBuf)
{
  if(!strcmp(szBuf, ""))
   return;
  int nStrLen = strlen(szBuf);
  WORD wLCID = MAKELCID(MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED), SORT_CHINESE_BIG5);
  int nReturn = LCMapString(wLCID, LCMAP_SIMPLIFIED_CHINESE, szBuf, nStrLen, NULL, 0);
  if(!nReturn)
   return;
  char *pcBuf = new char[nReturn + 1];
  __try
  {
   wLCID = MAKELCID(MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED), SORT_CHINESE_BIG5);
   LCMapString(wLCID, LCMAP_SIMPLIFIED_CHINESE, szBuf, nReturn, pcBuf, nReturn + 1);
   strncpy(szBuf, pcBuf, nReturn);
  }
  __finally
  {
   delete []pcBuf;
  }
}

// 调用示例

   ......

  char sourceEncode[255];
  char szBuf[1024];

  // 从 GB2312 转到 GBK
  strcpy(szBuf, sourceEncode);
  GB2GBK(szBuf);

  // 从GB2312 转到 BIG5,通过 GBK 中转
  strcpy(szBuf, sourceEncode);
  GB2GBK(szBuf);
  GBK2BIG5(szBuf);
   
   ......

} 

 

===========================================================================================================================================================================================================================================================================

 

我们传统的程序基本都只在Windows或只在Linux下运行,Windows程序使用简体中文GB18030编码,Linux程序则只使用英文,多年以来这些程序运行起来都没有问题。

  近年来,随着程序的组件化,部分代码特别是公用组件都需要同时支持Windows及Linux平台,这样就出现了不同程度的编码问题,例如在编译时编译器报错,或者在运行时出现乱码。这些问题都和程序选用的字符编码不正确有关。

  本文简要地分析了C++培训中容易出现的一些字符编码问题,并提供了建议的方案。受经验和时间的限制,有些内容可能不一定全面,仅供大家参考。

  1. C++源文件的编码需要特别考虑吗?

  1.1. 几个相关概念

  首先要区分几个概念:

  C++源文件的编码

  指的是C++源程序文件(。cpp/.h)本身使用什么字符编码(GB18030/UTF-8等)。

  C++程序的内码

  编译后,C++中的字符串常量都会变成一串字节存放在可执行文件中。这个内码指的就是在可执行文件中,字符串以什么编码进行存放。这里的字符串常量指的是窄字符(char)而非宽字符(wchar_t)。宽字符通常是以Unicode(VC使用UTF-16BE,gcc使用UTF-32BE)存放。

  运行环境编码

  指的是执行程序时,操作系统或终端所使用的编码。程序中输出的字符最终要转换为运行环境编码才能正确显示,否则就会出现乱码。

  1.2. 各种环境下通常使用的编码

  C++源文件的编码

  通常在简体中文Windows环境下,各种编辑器(包括Visual Studio)新建文件的缺省编码都是GB18030,所以不特别指定的话,Windows环境下C++源文件的编码通常为GB18030。而在Linux环境下,最常使用,也是推荐使用的是UTF-8编码。

  C++程序的内码

 

  一般来说,我们常用的简体中文版VC所使用的内码是GB18030,而gcc/g++使用的内码缺省是utf-8,但可以通过-fexec-charset参数进行修改。Note 可以通过在程序中打印字符串每个字节十六进制形式来判断程序所使用的内码。

 

 运行环境编码

  我们常用的简体中文版Windows的环境编码是GB18030,而Linux下最常用的环境编码是UTF-8。

  1.3. 这几个编码之间的关系

  源程序需要由编译器编译为目标文件,目标文件运行后输出信息到终端,因此这几个编码之间存在一些的关联:

  +--------+ | 源程序 |----------源文件编码 +---+----+ | 编译器编译 +---+----+ |目标文件|----------程序内码 +---+----+ | 运行后输出信息 +---+----+ | 输出 |----------运行环境编码 +--------+

  编译器需要正确识别源文件的编码,把源文件编译为目标文件,并把源文件中的以源文件编码的字符串转换为以程序内码编制的字符串保存在目标文件中。

  Note 当源文件的字符编码与程序内码都是UTF-8时(gcc的缺省情况),gcc似乎并不会对源文件中的字符编码进行转换,而是直接把字符串原样存放到目标文件中,在这种情况下,源程序中的GB18030编码的字符串在输出时仍然为GB18030编码。但如果在其它源文件字符编码的实际值与编译选项不同时,会在编译时报无法从XXX转换到UTF-8的错,因此还不清楚为什么两个编码都是UTF-8时,GB18030 编码的源文件能通过编译。C++标准库需要正确识别终端的运行环境编码,并把程序的输出转换为运行环境所使用的编码,以便正确显示。

  在这过程中,如果有一个环节出现问题,就会导致程序的输出发生异常,产生乱码或其它更严重的后果。

  2. 源文件应该采用什么编码?

  2.1. 编译器对不同源文件编码的支持一样吗?

  gcc/vc各版本对C++源文件编码有不同的处理:

  gcc (v4.3.2 20081105):

  支持UTF-8编码的源文件,UTF-8编码的源文件不能有BOM。

  开始支持带BOM的UTF-8文件。

  vc2003:

  支持UTF-8编码的源文件,UTF-8编码的源文件可以有BOM,也可以没有。

  vc2005+:

  如果源文件使用UTF-8编码的话,必须有BOM。Note gcc提供了-finput-charset参数可以指定源文件的字符编码,但由于标准头文件都是ascii编码的,因此如果要引用标准头文件的话,源代码的编码必须兼容ascii。而vc未能找到类似的选项。

  2.2. 源文件应该采用什么编码?

  很多文章都推荐C/C++代码中只使用ascii字符,如果有非ascii字符可以用\xHH或\uXXXX表示。注释中建议使用utf-8编码。也可以使用gettext 把非ascii字符串放到单独的语言文件中,而在源代码中只保留ascii字符。

  在实践中,由于\xHH或\uXXXX等方式很不直观,容易出错且不易发现,而未必所有程序都需要支持多语言,因此未必想引入gettext或类似的解决方案。在这样的情况下,大家都习惯在源程序文件中直接写入中文等非ascii字符,这就需要选择一种至少能被gcc和vc接受的文件编码。本来,Unicode是解决多语言问题的最好选择,而UTF-8由于与ASCII兼容,也是最通用的Unicode编码方式,但从上面的资料中可见,如果用UTF-8的话,gcc(至少是低版本)不允许有BOM,而vc2005 以上要求必须有BOM,因此同一个文件无法在gcc及vc下通过编译,UTF-8似乎不是一个好的选择。但如果使用gcc比较高的版本(4.4.0以上?),使用带BOM的UTF-8编码文件应该也是可行的。

 

 

 考虑到目前现状,我们一般都在简体中文Windows下工作,源文件中使用GB18030=编码似乎是一个比较现实的选择。在vc下可以直接编译,而在gcc下也可以通过增加编译选项-finput-charset=gb18030予以支持。而且根据维基百科中GB18030的词条内容,GB18030 is a superset of ASCII and can represent the whole range of Unicode code points(GB18030向后兼容ASCII,并且能表示所有的Unicode码点),因此使用GB18030有足够的表达能力,可以表示所有的Unicode字符。使用GB18030的唯一缺点就是在非简体中文版本的VC下,由于无法指定源文件的编码,因此有可能无法正确识别此编码的源文件。

 

 

 

  3. 应该使用什么程序内码?

  正如前面提到的,C++有窄字符(char)和宽字符(wchar_t)的分别,分别有一套相应的类和函数(string/cout/strlen与wstring/wcout/wcslen等)。前者在不同的编译器下有不同的缺省编码(简体中文vc是GB18030,gcc是UTF-8),后者一般都使用Unicode,其中vc下使用UTF-16,gcc缺省使用UTF-32。C++在输出窄字符时会按程序内码原样输出,不会进行编码转换,因此在使用窄字符时要求程序内码与运行环境编码一致,这样才不会出现乱码。由于简体中文版vc的程序内码是GB18030,因此使用窄字符的vc程序只能运行在GB18030环境下。同样,由于gcc缺省使用UTF-8作为程序内码,因此使用窄字符的gcc程序只能运行在UTF-8的终端环境下。(这里说的都是在源代码中直接写中文等非ascii字符的程序。用前面提到的gettext及其它工具,使用窄字符的程序也可以在不同编码的运行环境中正确输出中文)

  C++在输出宽字符时会自动转换为运行环境的编码,因此只要正确设置了运行环境编码,同一个程序就可以在不同编码的运行环境中正确显示中文。这一点与Java/.Net很象,Java/.Net的字符串类型都使用Unicode,在输入/输出时都需要与当前运行环境的编码进行互转。

  一般来说,如果需要支持多语言,有两种比较好的做法:

  使用窄字符:

       源程序中只使用ascii字符,非ascii字符通过gettext或其它工具放到单独的文件中,由gettext等工具处理编码转换的问题。在各种编码的运行环境中均能正确输出中文。程序中不能直接出现非ascii字符,也不能通过\uXXXX方式指定非ascii字符,后者也会被编译器转换为非ascii字符并存放在目标文件中。注释中可以使用ascii兼容的编码,不影响编译器。有比较多的现成代码可供重用。

  使用宽字符

  在各种编码的运行环境中均能正确输出中文。程序中可以使用非ascii字符。需要配合前面的源程序文件编码设置,让编译器能正确识别源程序中的非ascii字符。由于以前使用宽字符的程序比较少,可供重用的代码较少。

  Note: 如果程序中需要一些固定字符编码的字符串常量,例如固定是GB18030编码的字符串常量,这些常量应该以\xXX的方式存放字符串常量经GB18030编码后的内容,这样的内容才不会被转换为程序的内码,也不会转换为运行环境编码。

  4. 运行环境应该用什么字符编码?

  正如上面提到的,使用窄字符和使用宽字符的程序对运行环境的字符编码要求是不一样的。

 

 

 

  使用宽字符,只要在程序中正确设置当前环境的字符编码(一般通过locale::global(locale("")) 进行设置),C++标准库会在输入、输出时正确进行字符编码转换,因此可以适应各种编码的运行环境。

  使用窄字符,但程序中不出现非ascii字符的话,对运行环境没有特别要求,可以适应各种编码的运行环境。

  使用窄字符,程序中也直接使用汉字等非ascii字符的话,由于C++标准库会把目标文件中保存的字符串(以程序内码保存)直接输出,不会进行字符编码转换,因此要求运行环境的编码与程序内码一致。即简体中文VC编译的程序只能运行在GB18030环境下,gcc编译的程序只能运行在UTF-8环境下(可以在编译时通过-fexec-charset参数进行修改)。

 

 

 

 5. C++源文件编码的选择

  5.1. 几种可行做法

  根据上面的讨论,目前看来,要兼容Windows/Linux,VC/gcc的话,有几种做法:

  使用窄字符,源程序中只使用ascii字符,非ascii字符,如中文等通过gettext等工具放到单独的语言包中。这种做法比较多人推荐。兼容VC及gcc各版本。由于源程序中不出现非ascii字符,因此不需要考虑源程序文件的编码问题。兼容各种编码的运行环境。 

       使用窄字符,源程序中允许使用非ascii字符。要求运行环境的编码与程序内码一致,即只支持GB18030编码的Windows及UTF-8编码的Linux。

       根据源程序使用的编码不同,对编译器的兼容性也不同:

        使用窄字符,源程序使用带BOM的UTF-8编码。兼容VC各语种的各版本。兼容gcc 4.4.0以上版本。

      使用窄字符,源程序使用GB18030编码。兼容VC的简体中文各版本。兼容gcc各版本,但在编译时需要加上-finput-char=gb18030参数。

        使用宽字符,源程序中允许使用非ascii字符。兼容各种编码的运行环境。

  5.2. 推荐做法

  根据我们的现状,对于需要支持多语种的程序,建议使用窄字符,源程序中只使用ascii字符。

  对于不需要支持多语种的程序,考虑到重用已有的代码,可以考虑使用窄字符,采用GB18030编码,但只能运行在GB18030编码的Windows环境及UTF-8编码的Linux环境下。

  6. 其它问题

  6.1. 用户输入、输出及持久化

  由于用户输入、输出及从文件、网络等设施读写的数据在程序底层看来都是字节流,因此存在在输入时如何把这些字节流解释成有效的信息,在输出时怎么把程序中的信息转换为正确的字节流的问题。

  如果程序本身不需要处理这些数据,只是把数据从一个来源搬到另一个地方(如把用户输入保存到文件,或者从一个流读入,写到另一个流等),而输入的字符编码与输出的字符编码一致的话,程序不需要对数据进行任何编码转换,只需要把读入的数据按原样写到输出即可,数据的字符编码与程序的编码没有关系。

  比如网站应用程序,只需要保证用户页面使用UTF-8编码,数据库、数据文件也都使用UTF-8编码,那么用户输入的数据可以直接写入数据库及数据文件,从数据库或数据文件中读取的数据也可以直接展现给用户,不需要进行编码转换。

  如果程序需要在一定程序上对数据进行处理(如需要判断字符个数、对字符进行比较、在字符串上附加或去掉内容),就要把数据转换为一种明确的字符编码,一般来说是程序内码,再进行处理,在处理后再转换为所需的字符编码进行输出。

  对于宽字符程序,如果只需要处理采用当前运行环境字符编码的数据,可以通过ios::imbue()可以指定io流的字符编码,在输入、输出时C++标准库会自动在所指定的字符编码与程序内码之间进行编码转换。如果不使用流的话,也可以通过标准的wcstombs()或mbstowcs()函数进行当前编码(通过locale::global()或setlocale()指定)与宽字符之间的转换。

  对于窄字符程序,如果数据的字符编码与程序内码一致也不需要进行编码转换,直接处理即可。

  对于其它情形,需要引入iconv或类似的字符编码转换库,以便实现不同字符编码之间的转换。

  6.2. gettext、iconv的替代品

  由于gettext及iconv都属于GNU Project,考虑到版权因素,并非所有程序,特别是商业程序,都适合使用这些库。在Boost 1.48.0中,Boost.Locale库首次正式发布,该库提供了gettext、iconv的功能,并在此基础上进行了增强,提供了大小写变换、字符顺序比较、时间的处理 、分词、数字的格式化输入/输出、消息格式化、多语种支持、字符编码转换等功能,值得进一步研究及使用。

转自:http://www.voidcn.com/article/p-prwtaett-xg.html

 

==================================================================================================================================================================================

 

Centos: 解决系统编码 locale 的 LC_CTYPE / LC_ALL 问题

 

0 系统环境

CentOS 7.4

# rpm -q centos-release
centos-release-7-4.1708.el7.centos.x86_64

1 问题

初装的系统,如果输入 locale 查看系统字体编码,可能会出现报错

locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory

或者设置编码不完全时,会出现警告

LC_CTYPE: cannot change locale (UTF-8): No such file or directory

2 解决方法

如果要设置中文版的字体编码。在每个文件中增加以下内容。
中文版

# vim /etc/profile.d/locale.sh
export LC_CTYPE=zh_CN.UTF-8
export LC_ALL=zh_CN.UTF-8

# vim /etc/locale.conf
LANG=zh_CN.UTF-8

# vim /etc/sysconfig/i18n
LANG=zh_CN.UTF-8

# vim /etc/environment
LANG=zh_CN.UTF-8
LC_ALL=zh_CN.UTF-8

英文版本

# vim /etc/profile.d/locale.sh
export LC_CTYPE=en_US.UTF-8
export LC_ALL=en_US.UTF-8

# vim /etc/locale.conf
LANG=en_US.UTF-8

# vim /etc/sysconfig/i18n
LANG=en_US.UTF-8

# vim /etc/environment
LANG=en_US.UTF-8
LC_ALL=en_US.UTF-8

3 参考

CentOS7 LC_CTYPE: cannot change locale (UTF-8): No such file or directory
CentOS cannot change locale UTF-8解决方法及设置中文支持

 

 

===========================================================================================================================================================================================================================================================================

 

GCC 编码格式及编码转换

实用为主,相关知识背景另行查阅

编码场景

这里涉及的编码分为两个场景:源码文件的编码格式和源码中字符目标文件中的编码。

源码文件的编码格式

  • -finput-charset

该选项指源码文件的编码,如GBK,UTF-8,当然,你的host得支持。GCC调用host的字符转换功能将源文件的

编码格式转换为GCC内部编码格式UTF-8,然后再进行处理。

字符在目标文件中的编码

这个问题可以理解为你在源码中定义的字符串在内存中(当然是先编译进目标文件的)是如何被表示的。又要分

为两类:窄字符和宽字符。

  • -fexec-charset

即常用的 char 类型字符,每个字符占用1个字节,例如:

char str = "字符串";

  • -fwide-exec-charset

即 wchar_t 类型字符,每个字符占用4个字节,例如:

wchar_t wstr = L"字符串"

单独的编码转换

函数

如果你的源码中有多个字符串需要使用不同的编码,那么 -fexec-charset 和 -fwide-exec-charset 也就没有办法了。libc提供了

编码转换函数iconv,其实是一组函数,头文件:"iconv.h",函数原型如下:

复制代码

 1 /* Allocate descriptor for code conversion from codeset FROMCODE to
 2 codeset TOCODE.
 3 
 4 This function is a possible cancellation point and therefore not
 5 marked with __THROW. */
 6 extern iconv_t iconv_open (const char *__tocode, const char *__fromcode);
 7 
 8 /* Convert at most *INBYTESLEFT bytes from *INBUF according to the
 9 code conversion algorithm specified by CD and place up to
10 *OUTBYTESLEFT bytes in buffer at *OUTBUF. */
11 extern size_t iconv (iconv_t __cd, char **__restrict __inbuf,
12 size_t *__restrict __inbytesleft,
13 char **__restrict __outbuf,
14 size_t *__restrict __outbytesleft);
15 
16 /* Free resources allocated for descriptor CD for code conversion.
17 
18 This function is a possible cancellation point and therefore not
19 marked with __THROW. */
20 extern int iconv_close (iconv_t __cd);

复制代码

   为了使用方便,封装成了一个函数,也可以看作是使用方法的演示:

复制代码

 1 int charset_conv(   char     *from_charset,
 2                     char     *to_charset,
 3                     char     *inbuf,
 4                     size_t     inlen,
 5                     char     *outbuf,
 6                     size_t     outlen
 7 )
 8 {
 9     iconv_t cd;
10     char **pin = &inbuf;
11     char **pout = &outbuf;
12     size_t n;
13 
14     cd = iconv_open(to_charset,from_charset);  
15     if (cd == (iconv_t)-1) {
16         if (errno == EINVAL) {
17             printf("iconv_open: form %s to %s not support\n", from_charset, to_charset);
18         }
19 
20         return -1;
21     }
22 
23     memset(outbuf,0,outlen);
24 
25     n = iconv(cd, pin, &inlen, pout, &outlen);
26     if (n == (size_t)-1) {
27         printu(LOG_DEBUG, "iconv: error\n");
28     }
29 
30     iconv_close(cd);
31 
32     return n;
33 }

复制代码

如果想将UTF-16编码的字符串转换为GBK编码的字符串,可以这样使用:

1 charset_conv("UTF-16", "GBK", p_in, size_in, p_out, size_out);

iconv相关文件

在PC机上使用iconv一般都会正常,但是一旦到了嵌入式linux中,往往会调用失败,这是因为缺少相关文件。libc只实现了接口iconv,

但并没有实现具体的转换细节,可以想想,那么多的编码类型,如果都集成到libc库中,该是多么庞大!实际上转换细节使用动态链接库实现的。

文件位于:

/usr/lib/gconv

再看具体文件:

gconv-modules:指出了做相应转换应该调用的文件

*.so:实现由内部编码到某种编码转换的动态链接库

所以,为了支持UTF-16到GBK的转换,我们至少需要3个文件:

  UTF-16.so

  GBK.so

  gconv-modules

其中gconv-modules应包含如下内容:

# from to module cost
module GBK//     INTERNAL    GBK 1
module INTERNAL   GBK//      GBK 1

# from to module cost
module UTF-16//    INTERNAL   UTF-16 1
module INTERNAL   UTF-16//    UTF-16 1

   那么这些文件从那里来呢,最方便的就是从编译器目录拷贝,例如CodeSourcery g++ Lite(某厂商提供的ARM GCC),对应默认指令集的库的

路径是:

arm-none-linux-gnueabi/libc/usr/lib/gconv

是不是很方便,后面有时间再研究下如何自己编译这些动态链接库。。

 

 

===========================================================================================================================================================================================================================================================================

 

关于UTF-8、GBK编码以及编译时charset的指定的一些总结

2018年02月04日 17:42:08 old_fisher_ 阅读数:778

版权声明:本文为博主原创文章,转载请注明出处: https://blog.csdn.net/Blazar/article/details/79253475

首先分清两个概念:C文件中(转换为16进制)汉字的编码,编译后bin文件中汉字的编码。

gcc的 -finput-charset 和 -fexec-charset 两个选项的存在就是为了实现这两者的转换。

1. 默认情况下,gcc使用UTF-8 charset。

2. C文件中使用GBK编码的汉字:若要使bin文件为UTF-8编码,必须同时指定 -finput-charset=GBK,-fexec-charset=UTF-8(不指定fexec-charset也是可以的,但是单独指定它无效,编译器会认为输入为UTF-8)。若要使bin文件为GBK编码,可以不指定charset,这样编译器就不会去做转换(它认为前后都是UTF-8),看起来就像是“骗过”了编译器;也可以同时指定 -finput-charset=GBK,-fexec-charset=GBK。

3. C文件中使用UTF-8编码的汉字:若要使bin文件为UTF-8编码,可以不指定charset;若要使bin文件为GBK编码,必须指定 -fexec-charset=GBK。当然,如果你想“欺骗”编译器:“我的C文件使用的是GBK编码”,而去指定-finput-charset=GBK,这时编译器可能不会像上面那种情况一样好骗,你可能收到这样一条答复:“cc1: error: failure to convert GBK to UTF-8”。

 

举个例子:我在程序中需要去调用HZK16(点阵字库),它是基于GBK编码的,所以,我希望我的bin文件中的汉字是GBK编码。我在UE编辑器中,使用GBK编码写了xxx.c,又使用UTF-8编码写了yyy.c,那么,我将可以用以下几种方式去编译它们:

对于xxx.c:

(1)gcc   -o   xxx  xxx.c

(2)gcc    -finput-charset=GBK    -fexec-charset=GBK    -o    xxx   xxx.c

对于yyy.c:

(1)gcc   -fexec-charset=GBK   -o   yyy   yyy.c

(2)gcc   -finput-charset=UTF-8   -fexec-charset=GBK   -o   yyy   yyy.c

 

 

===========================================================================================================================================================================================================================================================================

 

gdb查看内存区命令

分类: LINUX

2012-08-04 14:45:23

 

用gdb查看内存

格式: x /nfu

说明
x 是 examine 的缩写

n表示要显示的内存单元的个数

f表示显示方式, 可取如下值
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
i 指令地址格式
c 按字符格式显示变量。
f 按浮点数格式显示变量。

u表示一个地址单元的长度
b表示单字节,
h表示双字节,
w表示四字节,
g表示八字节


Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),
t(binary), f(float), a(address), i(instruction), c(char) and s(string).
Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes)

举例
x/3uh buf
表示从内存地址buf读取内容,
h表示以双字节为一个单位,
3表示三个单位,
u表示按十六进制显示

例子:

n是个局部变量

Breakpoint 1, main (argc=1, argv=0xbffff3a4) at calc.c:7
7        int n = atoi(argv[1]);
(gdb) print &n
$1 = (int *) 0xbffff2ec
(gdb) x 0xbffff2ec
0xbffff2ec:    0x00282ff4
(gdb) print * (int *) 0xbffff2ec
$2 = 2633716
(gdb) x /4xw 0xbffff2ec
0xbffff2ec:    0x00282ff4    0x080484e0    0x00000000    0xbffff378
(gdb) x /4dw 0xbffff2ec
0xbffff2ec:    2633716    134513888    0    -1073745032
(gdb)

你可能感兴趣的:(多字节编码与Unicode码的区别 内码)