作者:非妃是公主
专栏:《笔记》《C++》
个性签:顺境不惰,逆境不馁,以心制境,万事可成。——曾国藩文章目录
- C++遍历英文字符串
- C++遍历中文字符串(不会出问题情况)
- C++遍历中文字符串(会出现问题的情况)
- 英文字符的表示
- 中文字符的表示
- 定长编码
- 变长编码
- Unicode编码
- 正确的中文字符串遍历方式
- 参考资料
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;
}
以上两种方法基本就可以很好地遍历英文字符串了!
再继续试!这次这样尝试,因为我们遍历字符串一般都是要对字符串中的字符进行操作,如果单纯只是输出或者显示而已,没必要去遍历字符,直接cout<
这次添加了一个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编码。
可以说,GBK编码是对GB2312编码的补充!
关于定长编码的详细规则可以在这篇文章里看到,总结的十分全面https://zhuanlan.zhihu.com/p/453675608
变长编码:是一种包含多个长度编码的字节结构!换句话说,这种类型的编码既可以使用1个字节,也可以使用2个字节,3个字节,以及4个字节,那么就来了一个问题,我怎么知道这个编码到底是用了几个字节呢?到底是1个字节,2个字节还是3个、4个字节呢?也就是如何进行解析呢?
这里,以GB18030编码为例进行说明,它也是一种变长编码,有1个字节,2个字节以及4个字节大小的编码,下图为GB18030编码的字节结构示意图:
从图中可以很容易地看出:
4个字节可以表示更多的字符。
其实GB是国标的意思:
国家标准GB18030-2000《信息交换用汉字编码字符集基本集的补充》是我国继GB2312-1980和GB13000-1993之后最重要的汉字编码标准,是我国计算机系统必须遵循的基础性标准之一。
GB18030-2000编码标准是由信息产业部和国家质量技术监督局在2000年3月17日联合发布的,并且将作为一项国家标准在2001年的1月正式强制执行。 GB18030-2005《信息技术中文编码字符集》是我国制订的以汉字为主并包含多种我国少数民族文字(如藏、蒙古、傣、彝、朝鲜、维吾尔文等)的超大型中文编码字符集强制性标准,其中收入汉字70000余个。
也就是说,这一个标准是我国制定的,并没有在国际上通用!它只编码了我国的汉字以及少数名族文字等。
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变换格式的缩写。
3+6+6+6=21
,仔细想一下,刚好覆盖Unicode 字符集的编码范围是 0x0000 - 0x10FFFF
,没有任何问题。从图中可以看到非妃是公主
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