字符编码详解及利用C++ STL string遍历中文字符串
作者:非妃是公主
专栏:《笔记》《C++》
博客地址:https://blog.csdn.net/myf_666
个性签:顺境不惰,逆境不馁,以心制境,万事可成。——曾国藩文章目录
- 序
- 一、C++遍历英文字符串
- 二、C++遍历中文字符串(不会出问题情况)
- 三、C++遍历中文字符串(会出现问题的情况)
- 四、英文字符的表示
- 五、中文字符的表示
- 5.1 定长编码
- 5.2 变长编码
- 5.3 Unicode编码
- 六、正确的中文字符串遍历方式
- 六、参考资料
- the end……
序
在Java、python等语言中,字符统一采用utf-8进行编码,在处理字符串时,十分方便,不需要关心字符编码的长度。(在编写程序时,每个字符的长度都可理解为1,然后通过get等函数获取第几个字符就可以很好地使用了)
但是,在C++中则不是这样的,C++中的字符编码采用了gbk格式,而且C++的设计原则是,不为了程序编写者的方便而浪费性能,所以在C++中我们通过下标访问字符串的位数则是第几个字节(字节偏移量),表示的并不是字符。
下面是关于C++编码的详细介绍!
一、C++遍历英文字符串
C++遍历英文字符串很简单,基本有两种方法
// 方法1
for(int i=0; i < str.size(); i++){cout << str[i];
}// 方法2
for(auto it = str.begin(); it != str.end(); it++){cout << *it;
}
以上两种方法基本就可以很好地遍历英文字符串了!
二、C++遍历中文字符串(不会出问题情况)
但是中文字符串呢?我们来试一下:
发现是可以正常输出的!
三、C++遍历中文字符串(会出现问题的情况)
再继续试!这次这样尝试,因为我们遍历字符串一般都是要对字符串中的字符进行操作,如果单纯只是输出或者显示而已,没必要去遍历字符,直接cout<<str;
就可以了,所以接下来试一下字符的操作:
这次添加了一个if (str[i] == '非') continue;
的判断,但是‘非’依然输出了出来。
调试一下:
i = 0
,但是str[i] == '非'
确实false,这是怎么回事呢?
我们知道,遍历字符串主要就是要对字符串进行操作,可是如今字符串判断相等却出现了问题……
这里给出测试代码,感兴趣的老哥可以自己去调试:
void test1() {string str = "非妃是公主";cout << "这时test1:";for (int i = 0; i < str.size(); i++) { if (str[i] == '非') continue;cout << str[i];}cout << endl;
}void test2() {string str = "非妃是公主";cout << "这时test2:";for (auto it = str.begin(); it != str.end(); it++) {cout << *it;}cout << endl;
}int main() {test1();test2();
}
四、英文字符的表示
其实,这里涉及到了一个编码的问题,ASCII码值是一个字符集表,里面编码了26个英文字母的大小写(大写字母65~90,小写字母97~120),还有其它英文字符(比如空格、单引号……),其中有一些甚至是不可显示的(比如换行符、分组符……)。具体可以查看ASCII码表.
利用ASCII(美国信息交换标准代码)就可以实现英文的字符映射了,因为英文字母只有那么些,所有的单词都是根据这些字母进行排列组合形成的。
下面为ASCII码表的节选(开头和结尾部分):
可以看到,从0~127,ASCII码表有128个编码,而28=2562^8=25628=256,也就是说可以用1个字节(Byte,等于8个bit)大小的内存空间来编码所有的英文字符,因此char利用这样的字节编码到字符进行映射就可以实现英文字符串的运算。
五、中文字符的表示
从上面不难看出,英文可以利用一个很小的字符集(ASCII——美国信息交换标准代码)去表示所有单词(因为只有26个字母等优先的符号),但中文不可以,中华汉字博大精深,其中包含了几千甚至上万的汉字(如果还包括一些繁体字、生僻字等数量会更大)!
因此,1个Byte不能满足中文编码的需要,我们需要2个、3个甚至4个Byte进行编码才能把中文表示出来!
这里就包含了两种编码的方式,定长编码和变长编码。
5.1 定长编码
定长编码:顾名思义,就是每个字符对应的编码的长度都是相等的,这里不得不提到GB2312编码和GBK编码。
- GB2312编码:就是把汉字编码成两个字节,一个字节有28=2562^8=25628=256种不同的编码,两个字节就有216=655362^{16}=65536216=65536种不同的编码,也就是说我们最多可以编码65536种情况,这些对于常用的文字应该可以了吧……但是,值得一提的是,GB2312并没有使用完全这些编码,它只用了一部分,那么剩下的呢?GB2312为了保持向下兼容ASCII,它避免了和ASCII进行冲突编码,这要浪费一部分编码空间,但依然还是有空余的,这些空余下的位置暂且留着,GB2312没有使用!
- GBK编码:和 GB2312 一样,GBK 也是双字节编码,同样为了向下兼容 GB2312, GBK使用了GB2312 没有用到的那些编码区域,简单地说,就是进一步拓展了编码集,GBK比GB2312编码了更多的汉字。
可以说,GBK编码是对GB2312编码的补充!
关于定长编码的详细规则可以在这篇文章里看到,总结的十分全面https://zhuanlan.zhihu.com/p/453675608
5.2 变长编码
变长编码:是一种包含多个长度编码的字节结构!换句话说,这种类型的编码既可以使用1个字节,也可以使用2个字节,3个字节,以及4个字节,那么就来了一个问题,我怎么知道这个编码到底是用了几个字节呢?到底是1个字节,2个字节还是3个、4个字节呢?也就是如何进行解析呢?
这里,以GB18030编码为例进行说明,它也是一种变长编码,有1个字节,2个字节以及4个字节大小的编码,下图为GB18030编码的字节结构示意图:
从图中可以很容易地看出:
- 前8位为00-80大小的,只有一个字节
- 前8位为81-FE,9~16位位40-FE的有两个字节,基本就是兼容GBK编码(但是和GBK还是有区别的,详细区别读者可以自行查阅)
- 前8位位81-84,9-16位为30-39的,17-24位为81-FE的,25-32位为30-39的有4个字节!
- ……
- 根据表格,按照上述的字节区间编码结构就可以进行解码了。
4个字节可以表示更多的字符。
其实GB是国标的意思:
国家标准GB18030-2000《信息交换用汉字编码字符集基本集的补充》是我国继GB2312-1980和GB13000-1993之后最重要的汉字编码标准,是我国计算机系统必须遵循的基础性标准之一。
GB18030-2000编码标准是由信息产业部和国家质量技术监督局在2000年3月17日联合发布的,并且将作为一项国家标准在2001年的1月正式强制执行。 GB18030-2005《信息技术中文编码字符集》是我国制订的以汉字为主并包含多种我国少数民族文字(如藏、蒙古、傣、彝、朝鲜、维吾尔文等)的超大型中文编码字符集强制性标准,其中收入汉字70000余个。
也就是说,这一个标准是我国制定的,并没有在国际上通用!它只编码了我国的汉字以及少数名族文字等。
5.3 Unicode编码
Unicode 其实是一个字符集,这个字符集给世界上常用的字符都进行了编码,每一个字符对应一个唯一的编码。但值得注意的是,它并不是一个字符编码,Unicode还需要依靠一些字符编码规范,才能发挥作用,后面会提到。Unicode 字符集的编码范围是 0x0000 - 0x10FFFF
,相比于上面提到的字符编码标准(带GB的都是国标的汉语拼音首字母,因此都是国内的标准),Unicode是一个国际化的标准。换句话说,如果说GB2312、GBK、GB18030是国家级
的字符编码,那么Unicode就是一个国际级
的字符集!
从上面提到的Unicode的范围可以看出,如果直接编码,我们只需要三个字符就可以编码它。但是,比如第1个字符,如果用3个Byte进行编码,那么它的编码应该是0x000001,问题来了,前面的0并没有包含什么信息,本来1个字节可以存储的,却消耗了3个字节,这是一种存储空间,以及计算机效率的浪费!
因此这里同样采用边长编码,这也就解释了上面为什么Unicode是字符集,而不是一种字符编码了,因为如果直接使用它进行编码会浪费大量的空间和时间。
Unicode的编码规则对应utf-8、utf-16、utf-32,每个都代表一种不同的编码规则,utf是Unicode transform format的缩写,Unicode变换格式的缩写。
- utf-8编码:是一种边长编码规则,可以使用1~4个字节,具体地说:
- 对于单字节的符号,字节的第一位设为 0,后面 7 位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的, 所以 UTF-8 能兼容 ASCII 编码,这也是互联网普遍采用 UTF-8 的原因之一。
- 对于 n 字节的符号( n > 1),第一个字节的前 n 位都设为 1,第 n + 1 位设为 0,后面字节的前两位一律设为 10 。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
- 那么紧接着就有一个问题,没有占满怎么半,答案是补0,从右往左占,高位占不满的自动补0。
- 还有1个问题,不够位数怎么半,这个问题好解决,增加字节就是了。4个字节最多可以表示
3+6+6+6=21
,仔细想一下,刚好覆盖Unicode 字符集的编码范围是0x0000 - 0x10FFFF
,没有任何问题。
- utf16编码:它也是一种变长编码规则,但是它将字符编码成2字节或者4字节。
- 对于 Unicode 码小于 0x10000 的字符, 使用 2 个字节存储,并且是直接存储 Unicode 码,不用进行编码转换;
- 对于 Unicode 码在 0x10000 和 0x10FFFF 之间的字符,使用 4 个字节存储,这 4 个字节分成前后两部分,每个部分各两个字节,其中,前面两个字节的前 6 位二进制固定为 110110,后面两个字节的前 6 位二进制固定为 110111, 前后部分各剩余 10 位二进制表示符号的 Unicode 码 减去 0x10000 的结果。20位bit正好可以表示0xFFFF。
- 大于 0x10FFFF 的 Unicode 码无法用 UTF-16 编码。
- utf-32编码:UTF-32 是固定长度的编码,始终占用 4 个字节,足以容纳所有的 Unicode 字符,所以直接存储 Unicode 码即可,不需要任何编码转换。虽然浪费了空间,但提高了效率。
六、正确的中文字符串遍历方式
从图中可以看到非妃是公主
5个字的汉字字符,但是,无论size和length都是10,这说明:length()和size()返回的并不是字符串的长度,而是字符串占用了多少个Byte。
进一步推测:s[i]
指的是第i个Byte,it++也指的是前进1个Byte。
而GBK和GB13080都对GB2312向下兼容,而GB2312就包含了汉字中绝大多数,部分生僻字和繁体字是不包含的,GB2312是用2个字节进行表示的。
因此,这里i和i+1才能表示1个字符。
而2个Byte就不能用char(1个Byte)来表示了,string底层是由char实现的,而汉字至少包含两个char的大小,所以要继续用string来表示一个汉字:
遍历算法应该如下:
void test3() {string str = "非妃是公主";cout << "这时test3:";for (int i = 0; i < str.size(); i = i + 2) {string tmp = "";tmp = tmp + str[i] + str[i + 1];if (tmp == "非") continue;cout << tmp;}cout << endl;
}
执行结果如下:
从输出结果中可以看出if (tmp == "非") continue;
已经被执行了。
同时,经过调试可以发现,因为字符串的==运算符应该是经过重载生成的,所以在调试时显示没有与操作数匹配的“==”运算符,无法进行监视。
但是从图中可以看出,tmp的值已经是“非”了。也实现了预期的结果,进而可以实现字符串遍历中对单个中文字符串的操作。
同时也在交流群里向大佬交流了一下,大佬帮忙给找了一个参考代码,此处一并贴出,并已标明出处[5],我在这里加上注释,对代码进行解释,如下:
string text = "今天周五123";
for(size_t i = 0; i < text.length();)
{int cplen = 1;if((text[i] & 0xf8) == 0xf0) cplen = 4; // 占用4个字节,前5位为11110else if((text[i] & 0xf0) == 0xe0) cplen = 3; // 占用3个字节,前4位为1110else if((text[i] & 0xe0) == 0xc0) cplen = 2; // 占用2个字节,前3位为110// 个人感觉这行代码好像没什么用,如果三种情况都不符合,那么cplen就为初始化的0,是符合utf-8编码定义的if((i + cplen) > text.length()) cplen = 1;cout << text.substr(i, cplen) << endl;i += cplen;
}
其实2个Byte基本已经可以表示大多数中文了,除了极少的繁体字和生僻字,但是上面的代码包含了3个Byte和4个Byte的情况,感叹大佬的代码确实更加完善!
最后还要感谢yyl1025
老哥的答疑,问题已采纳![6]
六、参考资料
[1] https://zhuanlan.zhihu.com/p/453675608
[2] https://zhuanlan.zhihu.com/p/427488961
[3] https://baike.baidu.com/item/ASCII/309296
[4] https://baike.baidu.com/item/GB18030/3204518
[5] https://stackoverflow.com/questions/40054732
[6] https://ask.csdn.net/questions/7874166
the end……
关于字符编码详解及利用C++ STL string遍历中文字符串的内容到这里就要结束啦~~到此既是缘分,欢迎您的点赞、评论、收藏!关注我,不迷路,我们下期再见!!
字符编码详解及利用C++ STL string遍历中文字符串相关推荐
- 字符编码详解及由来(UNICODE,UTF-8,GBK)
字符编码详解及由来(UNICODE,UTF-8,GBK) 各种字符编码方式详解及由来(ANSI,UNICODE,UTF-8,GB2312,GBK) - 2009-01-29 09:53 一直对 ...
- Python2.7字符编码详解
Python2.7字符编码详解 目录 Python2.7字符编码详解 声明 一. 字符编码基础 1.1 抽象字符清单(ACR) 1.2 已编码字符集(CCS) 1.3 字符编码格式(CEF) 1.3. ...
- 转1:Python字符编码详解
Python27字符编码详解 声明 一 字符编码基础 1 抽象字符清单ACR 2 已编码字符集CCS 3 字符编码格式CEF 31 ASCII初创 311 ASCII 312 EASCII 32 MB ...
- 可能是最详细的字符编码详解
Created By JishuBao on 2019-04-02 12:38:22 Recently revised in 2019-04-03 12:38:22 欢迎大家来到技术宝的掘金世界, ...
- Python字符编码详解
Python字符编码详解 转自http://www.cnblogs.com/huxi/archive/2010/12/05/1897271.html Python字符编码详解 本文简单介绍了各种常用的 ...
- 字符、字符集和字符编码详解(一文扫清疑惑)
前言 字符.字符集和字符编码时常看见,之前也看过一些博文,看得迷迷糊糊地,看过即忘,今天有幸碰到一篇能让我醍醐灌顶的文章,整理一下相关知识点与大家分享! 原博文地址:字符集编码详解(学习,看一篇就够了 ...
- java字符编码详解_Java中字符编码格式详解
一.前言 在分析Comparable和Comparator的时候,分析到了String类的compareTo方法,String底层是用char[]数组来存放元素,在比较的时候是比较的两个字符串的字符, ...
- java中文乱码解决之道(二)—–字符编码详解:基础知识 + ASCII + GB**
原文出处:http://cmsblogs.com/?p=1412 在上篇博文(java中文乱码解决之道(一)-–认识字符集)中,LZ简单介绍了主流的字符编码,对各种编码都是点到为止,以下LZ将详细阐述 ...
- 字符编码详解——彻底理解掌握编码知识,“乱码”不复存在
每一个程序员都不可避免的遇到字符编码的问题,特别是做Web开发的程序员,"乱码问题"一直是让人头疼的问题,也许您已经很少遇到"乱码"问题,然而,对解决乱码的方法 ...
最新文章
- [转载] 七龙珠第一部——第095话 悟空对抗克林
- 秋招 百度二轮面试---血淋淋的经历写实
- 微软成功测试氢燃料电池,为数据中心连续供电 48 小时
- 我所理解cocos2d-x 3.6 lua --使用Cocos Studio
- 机器学习理论《统计学习方法》学习笔记:第二章 感知机
- ajax post 提交无法进入controller 请求200
- c3p0连接池的配置和简单使用
- C语言之文件读写探究(六):fscanf、fprintf(格式化读写文件)
- 阶乘末尾蓝桥杯java_Java实现第九届蓝桥杯阶乘位数
- 4 图像处理基础知识
- C#创建单链表,翻转单链表
- matlab得到小波参数,MATLAB|高频信号的小波分析技术要点
- 在Mac下使用PanDownload完美下载BD云盘资源
- 无利不起早:理性看待IBM倾“芯”中国
- 瓜瓜的时空旅行,第三次模拟赛,dfs序+线段树维护最小值
- BFS和DFS算法原理(通俗易懂版)
- mysql盲注脱裤_BT5下使用SQLMAP入侵加脱裤 -电脑资料
- 什么是独立IP,独立IP主机怎么样?
- 数据“成精”究竟有多可怕?网络怎么知道我快秃了?
- Python数据容器、list列表、tuple元组、str字符串、数据容器(序列)切片、set集合、dict字典、字符串大小比较
热门文章