ascii码最初公布的字符长度是7位编码_C++ 中的中文编码

中文编码是一个复杂而繁琐的问题,尤其是在使用C++的时候,不像python这种直接就可以迭代出单个中文字符,C++中是以字节为单位的,那么我们要读取一个中文字符就要读取三次字节流,读取英文字符就只需要读取一次,是不是超级麻烦。那么C++怎么样在中英文混合的字符串中分离中英文或者计算字符串长度(不是字节数)呢,那就需要彻底搞清楚编码是个怎么回事。

一、ASCII 码

ASCII 码,全称 American Standard Code for Information Interchange,一共规定了 128 个字符的编码,包括 10 个阿拉伯数字(从 0x30 开始,使用16进制表示)、26 个大写字母(从 65 开始)、26 个小写字母(从 97 开始),33 个英文标点符号,及 32 个控制字符,还有最后一个 0x7F

128 个字符,至少需要 7 个比特(bit)来表示,而一个字节(byte)有 8 个比特,故将字节的最高位的比特规定为 0(这是规定),使用剩下的 7 个比特,刚好可以完整的表示 ASCII 码规定的字符。

阿拉伯数字的编码从 0x300x39,按顺序分别表示 0 到 9 这 10 个字符。这样带来了一个优势:可以直接做字符的减法,得到字符对应的数字大小。大写字母和小写字母亦是如此。举个例子:

char ch_9 = '9';
int value = ch_9 - '0';
assert(value == 9);

大写字母从 0x41 开始(二进制表示 1000001),小写字母从 0x61 (二进制表示 1100001)开始。注意观察,二者相差了一个 0x20,即 32。任意一个小写字母对比对应的大写字母,仅第 6 个比特有 1 与 0 的不同。进而可以通过这一点进行大小写字母的判断及其转换。

//判断字母大小写
char a = 'b';
if ((a  & 0x20) == 0x20){
    cout << "小写字母" << endl;
}
//小写转大写
char a = 'b';
char c = a ^ 0x20;
cout << c << endl;
//输出 B
//大写转小写
char a = 'B';
char c = a | 0x20;
cout << c << endl;
//输出 b
//当然直接加上或者减去 32 也是可以实现大小写转换的

ASCII 中的控制字符较为少用,有印象的仅仅是 Bell 字符(0x07)。大一学习编程的时候发现可以通过 printf("a"); 使用电脑发出蜂鸣声,如今在 Mac 上尝试依然有效。

二、中文编码

ASCII 码仅规定了 128 个字符,只能满足英文的基本需求。一个字节最多能表示 256 个字符,而中文的常用汉字就有数千了,故而需要使用多个字节来表示汉字。两个字节可以表示的字符上限为 65536,绝大部分情况下能够满足汉字使用的需求了。经典的汉字编码包括 GBK、GB2312、GB18030、CP939 等。

在汉字编码中,之前 ASCII 码没有使用的最高位派上了用场。如果一个字节最高位是 0,说明这个字节便是 ASCII 码,查表解析即可;如果最高位非 0,那么说明这是一个多字节编码,需要联合下一个字节一起进行解析。

不同的编码,也就意味着要查询不同的表,也就会得到不同的解码结果。年纪大点的人应该会懂,把小说下载到 MP3 里结果都是乱码的痛苦。再加上这个世界不止有中文,全球各个地区的文字、符号数量远超出两个字节可以表示的范围,这时“统一度量衡”就显得尤为重要了。

三、Unicode

Unicode 便是便是文字和符号的统一度量衡。Unicode,Unique Code,Universe Code,全世界每一个字符对应一个唯一的编码。Unicode 收录了大量的汉字,汉字的编码从 0x4E00 开始,到 0x9FFF 结束。

然而 Unicode 仅仅定义了符号与二进制编码的关系,但没有定义如何存储和解析这些二进制编码。如果直接将二进制编码写入文件,那么读取时会产生歧义。例如 4E 00 41,你无法知道这记录的是 1 个字符,还是 2 个字符(可以解码为“一A”),或者是 3 个字符(可以解码为“N[空]A”)。如果统一每个字符的记录长度,那么对于常用中文便需要至少 3 个字节来表示一个符号,那么对于全英的文件则太浪费了。

四、UTF-8

Unicode 解决了编码统一的问题,但没有解决编码存储和解析的问题。UTF-8 则解决了 Unicode 没有解决的问题。

UTF-8 是一种变长编码,会使用 1 到 4 个字节表示一个字符,类似于哈夫曼树,具体与 Unicode 的映射关系如下(复制自参考文献1):

UTF-8 对于原有的 ASCII 码完全兼容,而最高位的 1 的数量表示当前字符占用的字节数。可以通过上表将 Unicode 转换为 UTF-8 编码,即将 x 按照高低位顺序替换为 Unicode 的二进制位,缺了则使用 0 补齐。以汉字“一”为例,其 Unicode 编码为 0x4E00,对应的二进制为 100 1110 0000 0000,二进制共计 15 位。填充到 1110xxxx 10xxxxxx 10xxxxxx 中(从右往左依次替换掉X,因为有16个X,所有最左边的一个X用0代替),最高位缺了一位,使用 0 补齐,最终可得 11100100 10111000 10000000,即 E4 B8 80。使用一行 Python3 代码验证一下:

print(b'xe4xb8x80'.decode())
# 打印结果:一

UTF-8 编码还保持着一个优秀的特性,无论是使用左对齐(字符串排序),还是右对齐(数值排序),UTF-8 编码始终保持着与 Unicode 一致的大小顺序。举个栗子 ,字符串 u8"A" < u8"一&quot;, 同时宽字符 wchar_t(L'A') < wchar_t(L'一')。仔细想想还是蛮有意思的。

//使用C++实现像python一样的字符串遍历
std::vector <std::string> splitEachChar(const string chars)
{
    std::vector<std::string> words;
    std::string input(chars);
    int len = input.length();
    int i = 0;
​
    while (i < len) {
        assert ((input[i] & 0xF8) <= 0xF0);
        int next = 1;
        if ((input[i] & 0x80) == 0x00) {
            std::cout << "one character: " << input[i] << std::endl;
        } else if ((input[i] & 0xE0) == 0xC0) {
            next = 2;
            std::cout << "two character: " << input.substr(i, next) << std::endl;
        } else if ((input[i] & 0xF0) == 0xE0) {
            next = 3;
            std::cout << "three character: " << input.substr(i, next) << std::endl;
        } else if ((input[i] & 0xF8) == 0xF0) {
            next = 4;
            std::cout << "four character: " << input.substr(i, next) << std::endl;
        }
        words.push_back(input.substr(i, next));
        i += next;
    }
    return words;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值