字符编码

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编码规则

  1. 对于ASCII(单字节字符)字符,采用和ASCII相同的编码方式,即只使用一个字节表示,且该字节第一位为0.
  2. 对于多字节(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环境下都正常编译,且运行起来没有乱码的程序,需要遵循如下原则:

  1. 代码文件采用UTF-8 with BOM编码。
  2. Visual Studio字符集设置为Unicode字符集。
  3. 使用wchar_t。

字符长度

统计字符个数

  1. 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
  1. 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的相关配置无关。

参考网站

拨开字符编码的迷雾--字符编码概述
拨开字符编码的迷雾--MySQL数据库字符编码

posted @ 2020-12-22 20:43  Jefflnb  阅读(394)  评论(0编辑  收藏  举报