字符编码
ASCII及其扩展(字符集)
ASCII
ASCII使用 7位二进制位 来表示一个字符,总共可以表示128个字符(即2^7,二进制000 0000 ~ 111 1111 十进制0~127)。
ASCII 扩展一
需求 :ASCII字符集是美国人发明的,这些字符完全是为其量身定制的。但随着计算机技术的发展和普及,传到了欧洲(如法国、德国)各国。由于欧洲很多国家中使用的字符除了ASCII表中的128个字符之外,还有一些各国特有的字符,于是欧洲人民发现ASCII字符集表达不了他们所要表达的东西呀。怎么办?
解决 :ASCII只使用了一个字节(8位)之中的低7位,于是欧洲各国开始各显神通,打起了那1个最高位(第0位)的主意,将 最高位 利用了起来,这样又多了128个字符,从而满足了欧洲人民的需要。
ASCII 扩展二(多字节扩展)
编码名称 | 发布时间 | 字节数 | 汉字范围 |
---|---|---|---|
GB2312(GB2312-80/1980)(汉字DBCS类型编码) | 1980 | 变字节(ASCII 1字节,汉字2字节 | 6763 |
GB13000 | 1993 | 变字节(ASCII 1字节,汉字2字节 | 20902 |
GBK(CP936:DBCS:double-byte character set) | win95 | 2个字节 | 21886 |
GB18030 | 2000 | 变字节(ASCII 1字节,汉字2字节或4字节 | 27484 |
GB相关介绍
GB码,全称是GB2312-80《信息交换用汉字编码字符集 基本集》,1980年发布,是中文信息处理的国家标准,在大陆及海外使用简体中文的地区(如新加坡等)是强制使用的唯一中文编码。
GBK: 汉字国标扩展码,基本上采用了原来GB2312-80所有的汉字及码位,并涵盖了原Unicode中所有的汉字20902,总共收录了883个符号, 21003个汉字及提供了1894个造字码位。
GB汉字判断
GB2312-80编码的编码范围是高位0xa1-0xfe,低位是 0xa1-0xfe ,其中汉字范围为 0xb0a1 和 0xf7fe,如果只是简单地判断汉字,则只要查看高字节是否大于等于0xa1就可以了,还有就是,全角字符的高字节统统等于0xa3,所以很容易可以区别出全角字符来。
GBK 码总体编码范围为 8140-FEFE,编码范围是高位x81-0xFE(129-254),低位是0x40-0xFE(64-254)。
GBK汉字范围
a. GB 2312 汉字区。即 GBK/2: B0A1-F7FE。收录 GB 2312 汉字 6763 个,按原顺序排列。
b. GB 13000.1 扩充汉字区。包含:
(1) GBK/3: 8140-A0FE。收录 GB 13000.1 中的 CJK 汉字 6080 个。
(2) GBK/4: AA40-FEA0。收录 CJK 汉字和增补的汉字 8160 个。
GBK符号范围
a. GB 2312 非汉字符号区。即 GBK/1: A1A1-A9FE。
b. GB 13000.1 扩充非汉字区。即 GBK/5: A840-A9A0。
Windows下判断GB2312-80汉字代码:
int IsGB(PTSTR pText)
{
unsigned char sqChar[20];
sqChar[0]=*pText;
if (sqChar[0]>=0xa1)
if (sqChar[0]==0xa3)
return 1;//全角字符
else
return 2;//汉字
else
return 0;//英文、数字、英文标点
}
ASCII 扩展三 (ANSI)
世界各国针对ASCII的扩展方案(如欧洲的ISO/IEC 8859,中国的GB系列等),这些 ASCII扩展编码方案 的特点是:他们都兼容ASCII编码,但他们彼此之间是不兼容的。微软将这些编码方案统称为ANSI编码。
在windows操作系统上,默认使用ANSI来保存文件。那么操作系统是如何知道ANSI到底应该表示哪种编码了,是GBK,还是ASCII,或者还是EUC-KR了? windows通过一个叫 "Code Page" (翻译为中文就叫代码页)的东西来判断系统的默认编码。简体中文操作系统默认的代码页是936,它表示ANSI使用的是GBK编码。GB18030编码对应的windows代码页为CP54936。
windows查看代码也的命令:
chcp
可以使用 命令chcp 来查看系统默认的代码页。
Unicode统一码标准(字符编码)
需求 :各个国家使用不同的编码规则,虽然他们都是兼容ASCII的,但它们相互却是不兼容的。
Unicode字符集和ASCII字符集一样,也只是一个字符集合,标记着字符和数字之间的映射关系,它不包含任何编码规则和方案。和ASCII不一样的是,Unicode字符集支持的字符数量是没有限制的。
那么Unicode字符是怎样被编码成内存中的字节的了?它是通过UTF(Unicode Transformation Formats)实现的,比较常见得有UTF-8,UTF-16。
UTF-8
UTF-8编码规则 :
- 对于ASCII(单字节字符)字符,采用和ASCII相同的编码方式,即只使用一个字节表示,且该字节第一位为0.
- 对于多字节(2~4字节)字符,假设字节数为n(1 < n <= 4),第一个字节:前n位都设为1,第n+1位设为0;后面的n-1个字节的前两位一律设为10。所有字节中的没有提及的其他二进制位,全部为这个符号的unicode码。
UTF-8带BOM(Byte Order Mark)
“微软在自己的UTF-8格式的文本文件之前加上了EF BB BF三个字节, windows上面的notepad等程序就是根据这三个字节来确定一个文本文件是ASCII的还是UTF-8的, 然而这个只是 微软暗自作的标记 , 其它平台上并没有对UTF-8文本文件做个这样的标记。”
编码 | ‘ABC’ |
---|---|
UTF-16BE(Without BOM) | 00 41 00 42 00 43 |
UTF-16LE(Without BOM) | 41 00 42 00 43 00 |
UTF-16BE(Without BOM) | FE FF 00 41 00 42 00 43 |
UTF-16LE(Without BOM) | FF FE 41 00 42 00 43 00 |
在windows系统上汉字默认使用CP936(即GBK编码),占2个字节。而大多数Unicode字符的Unicode码值也占2个字节,所以大多数人误以为汉字字符串在内存中的值就是Unicode值,这是错误的。
可以从 站长工具-Unicode 查询汉字的Unicode码值。
其它
全角与半角
因为汉字在显示器上的显示宽度要比英文字符的宽度要宽一倍,在一起排版显示时不太美观。所以GB编码不仅仅加入了汉字字符,而且包括了ASCII字符集中本来就有的数字、标点符号、字母等字符。这些被编入GB编码的数字、标点、字母在显示器上的显示宽度比ASCII字符集中的宽度宽一倍,所以前者称为全角字符,后者称为半角字符。
Windows 转换接口
Windows API
//宽字节(Windows内UTF-16) -> 多字节编码(ASCII/UTF-8等)
int WideCharToMultiByte(
UINT CodePage,
DWORD dwFlags,
LPCWSTR lpWideCharStr,
int cchWideChar,
LPSTR lpMultiByteStr,
int cbMultiByte,
LPCSTR lpDefaultChar,
LPBOOL lpUsedDefaultChar
);
// 多字节编码(ASCII/UTF-8等) -> 宽字节(Windows内UTF-16)
int MultiByteToWideChar(
UINT CodePage,
DWORD dwFlags,
LPCSTR lpMultiByteStr,
int cbMultiByte,
LPWSTR lpWideCharStr,
int cchWideChar
);
接口封装
std::string UnicodeToANSI(const std::wstring &str, UINT iCodePage = CP_ACP) {
std::string strRes;
int iSize = ::WideCharToMultiByte(iCodePage, 0, str.c_str(), -1, NULL, 0, NULL, NULL);
if (iSize == 0)
return strRes;
char *szBuf = new (std::nothrow) char[iSize];
if (!szBuf)
return strRes;
memset(szBuf, 0, iSize);
::WideCharToMultiByte(iCodePage, 0, str.c_str(), -1, szBuf, iSize, NULL, NULL);
strRes = szBuf;
delete[] szBuf;
return strRes;
}
std::wstring ANSIToUnicode(const std::string &str, UINT iCodePage = CP_ACP) {
std::wstring strRes;
int iSize = ::MultiByteToWideChar(iCodePage, 0, str.c_str(), -1, NULL, 0);
if (iSize == 0)
return strRes;
wchar_t *szBuf = new (std::nothrow) wchar_t[iSize];
if (!szBuf)
return strRes;
memset(szBuf, 0, iSize * sizeof(wchar_t));
::MultiByteToWideChar(iCodePage, 0, str.c_str(), -1, szBuf, iSize);
strRes = szBuf;
delete[] szBuf;
return strRes;
}
std::string UnicodeToUTF8(const std::wstring &str) {
std::string strRes;
int iSize = ::WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, NULL, 0, NULL, NULL);
if (iSize == 0)
return strRes;
char *szBuf = new (std::nothrow) char[iSize];
if (!szBuf)
return strRes;
memset(szBuf, 0, iSize);
::WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, szBuf, iSize, NULL, NULL);
strRes = szBuf;
delete[] szBuf;
return strRes;
}
std::string UnicodeToUTF8BOM(const std::wstring &str) {
std::string strRes;
int iSize = ::WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, NULL, 0, NULL, NULL);
if (iSize == 0)
return strRes;
unsigned char *szBuf = new (std::nothrow) unsigned char[iSize + 3];
if (!szBuf)
return strRes;
memset(szBuf, 0, iSize + 3);
if (::WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, (LPSTR)(szBuf + 3), iSize, NULL, NULL) > 0) {
szBuf[0] = 0xEF;
szBuf[1] = 0xBB;
szBuf[2] = 0xBF;
}
strRes = (char*)szBuf;
delete[] szBuf;
return strRes;
}
std::wstring UTF8ToUnicode(const std::string &str) {
std::wstring strRes;
int iSize = ::MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0);
if (iSize == 0)
return strRes;
wchar_t *szBuf = new (std::nothrow) wchar_t[iSize];
if (!szBuf)
return strRes;
memset(szBuf, 0, iSize * sizeof(wchar_t));
::MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, szBuf, iSize);
strRes = szBuf;
delete[] szBuf;
return strRes;
}
std::string ANSIToUTF8(const std::string &str, UINT iCodePage = CP_ACP) {
return UnicodeToUTF8(ANSIToUnicode(str, iCodePage));
}
std::string ANSIToUTF8BOM(const std::string &str, UINT iCodePage = CP_ACP) {
return UnicodeToUTF8BOM(ANSIToUnicode(str, iCodePage));
}
std::string UTF8ToANSI(const std::string &str, UINT iCodePage = CP_ACP) {
return UnicodeToANSI(UTF8ToUnicode(str), iCodePage);
}
在windows下
常见windows下字符类型间转换
MFC C++ CString与string
unicode:
//1
CString sz1 = L"abc";
std::string sz2 = CT2A(sz1.GetBuffer()); //转化为非unicode.
//2
CString theCStr;
std::string STDStr( CW2A( theCStr.GetString() ) );
//3
CString cstrName;
CT2CA pszName(cstrName);
std::string m_NameStd(pszName);
//4
CString str = L"Test";
std::wstring ws(str);
std::string s;
s.assign(ws.begin(), ws.end())
非unicode:
CString sz1 = "abc";
std::string sz2 = sz1.GetBuffer();
string类型下: c_str()和data()区别是:前者返回带'/0'的字符串,后者则返回不带'/0'的字符串。
在windows下避免乱码
要开发支持多语言,在任意语言(系统代码页)的windows环境下都正常编译,且运行起来没有乱码的程序,需要遵循如下原则:
- 代码文件采用UTF-8 with BOM编码。
- Visual Studio字符集设置为Unicode字符集。
- 使用wchar_t。
字符长度
统计字符个数
- C语言
#include<string.h>
char* pcHello = "你好Hello"; //ANSI
wchar_t wstr[] = L"你好Hello"; //UNICODE
printf("len:%d\n",strlen(pcHello)); // 输出 9
printf("len:%d\n",wcslen(wstr)); // 输出 7
- C++
标准库 多字节
// length
#include<string>
string str="hello world"; //ANSI
cout<< str.length() <<endl; //输出 11
// size
string str="hello world"; //ANSI
cout<< str.size() <<endl; //输出 11
标准库 宽字节
#include <iostream>
#include <string>
wstring wstrg = L"你好Hello"; //UNICODE
cout << wstrg.size() << endl; //输出 7
cout << wstrg.length() << endl; // 输出 7
windows接口
CStringA cstrA = "你好Hello"; //ANSI
CStringW cstrW = L"你好Hello"; //UNICODE
cout << cstrA.GetLength() << endl; // 输出 9
cout << cstrW.GetLength() << endl; // 输出 7
_bstr_t bstr1 = cstrA;
_bstr_t bstr2 = cstrW;
cout << bstr1.length() << endl; // 输出 7
cout << bstr2.length() << endl; // 输出 7
附加
统计字节数
char str[] = "你好Hello"; //ANSI
cout << sizeof(str) << endl; //输出 10
不同操作系统对换行符的定义
CR: Carriage Return, 对应ASCII中转义字符\r,表示回车
LF: Linefeed, 对应ASCII中转义字符\n, 表示换行
CRLF: Carriage Return & Linefeed, \r\n, 表示回车并换行
注
修改不同的换行符需用二进制打开文本文件进行修改,例如:CRLF改成LF,需要将'\r\n'改成'\n'。
若使用字符模式打开文本,系统会自动解析(文本中)'\r\n'为(字符串)'\n'。
文本字符格式
c语言中,文本字符格式与写入文本的字符串格式(ANSI、UTF-8)相关,与fopen、fwrite的相关配置无关。