C++字符编码详解及利用string遍历中文字符串

news2025/1/23 7:21:50

作者:非妃是公主
专栏:《笔记》《C++》
个性签:顺境不惰,逆境不馁,以心制境,万事可成。——曾国藩

在这里插入图片描述

文章目录

  • C++遍历英文字符串
  • C++遍历中文字符串(不会出问题情况)
  • C++遍历中文字符串(会出现问题的情况)
  • 英文字符的表示
  • 中文字符的表示
    • 定长编码
    • 变长编码
    • Unicode编码
  • 正确的中文字符串遍历方式
  • 参考资料

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个编码,而 2 8 = 256 2^8=256 28=256,也就是说可以用1个字节(Byte,等于8个bit)大小的内存空间来编码所有的英文字符,因此char利用这样的字节编码到字符进行映射就可以实现英文字符串的运算。

中文字符的表示

从上面不难看出,英文可以利用一个很小的字符集(ASCII——美国信息交换标准代码)去表示所有单词(因为只有26个字母等优先的符号),但中文不可以,中华汉字博大精深,其中包含了几千甚至上万的汉字(如果还包括一些繁体字、生僻字等数量会更大)!

因此,1个Byte不能满足中文编码的需要,我们需要2个、3个甚至4个Byte进行编码才能把中文表示出来!

这里就包含了两种编码的方式,定长编码和变长编码。

定长编码

定长编码:顾名思义,就是每个字符对应的编码的长度都是相等的,这里不得不提到GB2312编码和GBK编码。

  • GB2312编码:就是把汉字编码成两个字节,一个字节有 2 8 = 256 2^8=256 28=256种不同的编码,两个字节就有 2 16 = 65536 2^{16}=65536 216=65536种不同的编码,也就是说我们最多可以编码65536种情况,这些对于常用的文字应该可以了吧……但是,值得一提的是,GB2312并没有使用完全这些编码,它只用了一部分,那么剩下的呢?GB2312为了保持向下兼容ASCII,它避免了和ASCII进行冲突编码,这要浪费一部分编码空间,但依然还是有空余的,这些空余下的位置暂且留着,GB2312没有使用!
  • GBK编码:和 GB2312 一样,GBK 也是双字节编码,同样为了向下兼容 GB2312, GBK使用了GB2312 没有用到的那些编码区域,简单地说,就是进一步拓展了编码集,GBK比GB2312编码了更多的汉字。

可以说,GBK编码是对GB2312编码的补充!

关于定长编码的详细规则可以在这篇文章里看到,总结的十分全面https://zhuanlan.zhihu.com/p/453675608

变长编码

变长编码:是一种包含多个长度编码的字节结构!换句话说,这种类型的编码既可以使用1个字节,也可以使用2个字节,3个字节,以及4个字节,那么就来了一个问题,我怎么知道这个编码到底是用了几个字节呢?到底是1个字节,2个字节还是3个、4个字节呢?也就是如何进行解析呢?

这里,以GB18030编码为例进行说明,它也是一种变长编码,有1个字节,2个字节以及4个字节大小的编码,下图为GB18030编码的字节结构示意图:
在这里插入图片描述
从图中可以很容易地看出:

  1. 前8位为00-80大小的,只有一个字节
  2. 前8位为81-FE,9~16位位40-FE的有两个字节,基本就是兼容GBK编码(但是和GBK还是有区别的,详细区别读者可以自行查阅)
  3. 前8位位81-84,9-16位为30-39的,17-24位为81-FE的,25-32位为30-39的有4个字节!
  4. ……
  5. 根据表格,按照上述的字节区间编码结构就可以进行解码了。

4个字节可以表示更多的字符。

其实GB是国标的意思:

国家标准GB18030-2000《信息交换用汉字编码字符集基本集的补充》是我国继GB2312-1980和GB13000-1993之后最重要的汉字编码标准,是我国计算机系统必须遵循的基础性标准之一。
GB18030-2000编码标准是由信息产业部和国家质量技术监督局在2000年3月17日联合发布的,并且将作为一项国家标准在2001年的1月正式强制执行。 GB18030-2005《信息技术中文编码字符集》是我国制订的以汉字为主并包含多种我国少数民族文字(如藏、蒙古、傣、彝、朝鲜、维吾尔文等)的超大型中文编码字符集强制性标准,其中收入汉字70000余个。

也就是说,这一个标准是我国制定的,并没有在国际上通用!它只编码了我国的汉字以及少数名族文字等。

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位为11110
    else if((text[i] & 0xf0) == 0xe0) cplen = 3; // 占用3个字节,前4位为1110
    else 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?spm=1001.2014.3001.5505

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/151440.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux基础——进程的概念和控制(操作系统级讲解)

前言 我们经常会听到一个概念——进程。但是进程并不是一个孤立的概念&#xff0c;需要对操作系统有比较深入的了解。所以这篇博客将在读者的脑中先对操作系统构建一个大概的印象&#xff0c;再对进程做了解。 冯诺依曼结构 冯诺依曼结构也称普林斯顿结构&#xff0c;是一种…

微信小程序|智能停车系统中车牌计费功能实现

&#x1f4cc;个人主页&#xff1a;个人主页 ​&#x1f9c0; 推荐专栏&#xff1a;小程序开发成神之路 --【这是一个为想要入门和进阶小程序开发专门开启的精品专栏&#xff01;从个人到商业的全套开发教程&#xff0c;实打实的干货分享&#xff0c;确定不来看看&#xff1f; …

数据结构基础篇》》约瑟夫环

数据结构开讲啦&#xff01;&#xff01;&#xff01;&#x1f388;&#x1f388;&#x1f388; 本专栏包括&#xff1a; 抽象数据类型线性表及其应用栈和队列及其应用串及其应用数组和广义表树、图及其应用存储管理、查找和排序将从简单的抽象数据类型出发&#xff0c;深入浅出…

python 基础入门

文章目录前言python 基础入门一、python环境如何搭建、开发工具pycharm如何破解01 python下载02 python 安装03 python开发工具安装(pycharm )03::01 安装pycharm03::02 多次试用二、python 常规基础01 python 规范02 python中的关键字03 python缩进04 python注释哈哈哈前言 如…

07-JVM 类加载机制?

1.JVM 类加载机制分为五个部分&#xff1a;加载&#xff0c;验证&#xff0c;准备&#xff0c;解析&#xff0c;初始化。 2.一个类型从被加载到虚拟机内存中开始&#xff0c;到卸载出内存为止&#xff0c;它的整个生命周期将会经历加载&#xff08;Loading、验证&#xff08;V…

分布式版本控制Git

从基本的环境配置与安装到Git的基本操作&#xff0c;轻松应对Git在使用时遇到的常见问题。 https://blog.csdn.net/a18307096730/article/details/124586216?spm1001.2014.3001.550202_版本控制器的方式03_svn(过时)_git04git工作流程简述05git环境配与安装06 获取本地仓库Git…

P3375 【模板】KMP字符串匹配

题目描述 给出两个字符串 s_1s1​ 和 s_2s2​&#xff0c;若 s_1s1​ 的区间 [l, r][l,r] 子串与 s_2s2​ 完全相同&#xff0c;则称 s_2s2​ 在 s_1s1​ 中出现了&#xff0c;其出现位置为 ll。 现在请你求出 s_2s2​ 在 s_1s1​ 中所有出现的位置。 定义一个字符串 ss 的 bor…

概率论【离散型二维变量与连续性二维变量(上)】--猴博士爱讲课

5.离散型二维变量与连续性二维变量&#xff08;上&#xff09; 1/8 已知二维离散型分布律&#xff0c;求??? 离散型直接看表 【做题方法参考如下】 2/8 已知二维离散型分布律&#xff0c;判断独立性 如果满足p(xy) p(x) * p(y)&#xff0c;那么相互独立 则我们只需要验证每…

C 程序设计教程(12)—— C 语言顺序结构程序设计

C 程序设计教程&#xff08;12&#xff09;—— C 语言顺序结构程序设计 该专栏主要介绍 C 语言的基本语法&#xff0c;作为《程序设计语言》课程的课件与参考资料&#xff0c;用于《程序设计语言》课程的教学&#xff0c;供入门级用户阅读。 目录C 程序设计教程&#xff08;1…

深入探索Flutter性能优化

前言 Flutter 作为目前最火爆的移动端跨平台框架&#xff0c;能够帮助开发者通过一套代码库高效地构建多平台的精美应用&#xff0c;并支持移动、Web、桌面和嵌入式平台。对于 Android 来说&#xff0c;Flutter 能够创作媲美原生的高性能应用&#xff0c;但是&#xff0c;在较…

【nvivo11plus教程】02_编码与节点

1、对文档进行编码(1)建立节点(2)使用快速编码栏进行编码(3)将整个文件编码为一个代码(4)范围编码(5)在vivo中编码(6)使用节点昵称加快编码速度2、取消、增加和查看编码(1)编码带(2)删除编码(3)查看编码邻近区(4)增加编码(5)查看编码信息3、组织节点(1)节点结构化(2)移动归类节…

leetcode-每日一题-还原排列的最少操作步数(中等,数学逻辑)

回家了很少看了&#xff0c;今天突然心血来潮做了今天的每日一题&#xff0c;还不错&#xff0c;最后是一次AC&#xff0c;说明这么长时间没看实力没有下降多少&#xff0c;哈哈哈哈&#xff0c;自恋一下&#xff0c;后面我会更新一些课设和实验作业&#xff0c;进入正题。给你…

密码学_ECC椭圆曲线加密算法

算法介绍 椭圆加密算法&#xff08;ECC&#xff09;是一种公钥加密体制&#xff0c;最初由Koblitz和Miller两人于1985年提出&#xff0c;其数学基础是利用椭圆曲线上的有理点构成Abel加法群上椭圆离散对数的计算困难性。公钥密码体制根据其所依据的难题一般分为三类&#xff1a…

【jQuery】常用API——jQuery样式操作

一、操作 css 方法 jQuery 可以使用 css 方法来修改简单元素样式。1. 参数只写属性名&#xff0c;则是返回属性值$(this).css(color);2. 参数是属性名&#xff0c;属性值&#xff0c;逗号分隔&#xff0c;是设置一组样式&#xff0c;属性必须加引号&#xff0c;值如果是数字可以…

Go基础学习

文章目录回看下历史环境安装和开发工具&#xff1a;基础语法&#xff1a;go的注释&#xff1a;变量定义&#xff1a;简短定义模式Go的变量交换匿名变量&#xff08;空白标识符&#xff09;&#xff1a;变量的作用域&#xff1a;iota常量计数器数据类型布尔类型数值型整数浮点数…

新冠COVIN-19流感病患轨迹追溯

实验背景 冬季是流感的高发季节&#xff0c;现已知某流感病毒的传播力很强&#xff0c;政府部门也陆续公开了部分流感确诊患者&#xff08;后续简称“病患”&#xff09;的非隐私信息&#xff0c;这部分数据为相关研究人员研究该流感病毒的传播与防控提供了重要的数据支撑。 然…

Linux网站服务实操练习

作者简介&#xff1a;一名99年软件运维应届毕业生&#xff0c;正在自学云计算课程。宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。创作不易&#xff0c;动动小手…

python:打包package

简介 把模块打包成package&#xff0c;可以进行分发和安装。 packaged的打包和安装一、package层次架构二、 package的打包和安装1. 创建setup.py2. 打包package3. 安装package一、package层次架构 其中mypackage为进行打包的文件夹&#xff0c;文件夹下包含多个脚本&#xff1…

钢铁行业应用APS生产排产系统的好处

1 钢铁行业APS生产排产系统设计的主要业务流程 全局一体化计划&#xff1a;主要负责订单交期评审与应答、销产转换、主生产计划、铁水需求计划&#xff0c;该计划的最终目标是对各个分厂的日计划提出整体要求。主要对口业务部门为公司生产计划排程部门。 各个工段厂区的一体化…

SpringCloud微服务项目实战 - 4.自媒体平台(博主后台)

“我读过很多书&#xff0c;但后来大部分都忘记了&#xff0c;你说这样的阅读究竟有什么意义&#xff1f;” “当我还是个孩子时&#xff0c;我吃过很多食物&#xff0c;现在已经记不起来吃过什么了。但可以肯定的是&#xff0c;它们中的一部分已经长成我的骨头和肉。” 系列文…