C++字符型专题

C++字符型专题

本文为原创文章,转载请注明出处,或注明转载自“黄邦勇帅(原名:黄勇)
本文是对《C++语法详解》一书相关章节的第二版(增修版),《C++语法详解》网盘地址:https://pan.baidu.com/s/1dIxLMN5b91zpJN2sZv1MNg

有兴趣的读者可参阅本人所著《C++语法详解》一书,电子工业出版社出版,该书语法示例短小精悍,对查阅C++知识点相当方便,并对语法原理进行了透彻、深入详细的讲解,可确保读者彻底弄懂C++的原理,彻底解惑C++,使其知其然更知其所以然。此书是一本全面了解C++不可多得的案头必备图书。


专业名词解释

实现:通常是指按某种标准或原理而做出来的应用程序,如C++实现是指的按C++标准而制作出来的应用程序,通常是指编译器,因此,若本文中出现“依实现而定”是指的“依实现C++标准的应用程序而定,通常是指依编译器而定”



2.3.1 字符编码基础知识


一、计算机怎样处理字符

计算机只能处理二进制数字,那么怎样处理字符呢?可以想到的最简单的办法就是在字符和数字之间进行映射(编码),即,只需把字符想办法转换为二进制数字就行了,比如将字符“A”映射为10进制整数65,然后再将65直接映射为二进制数0100 0001,同理,可将“B”映射为66等,这样计算机就能处理字符A了,这里的“映射”在计算机中被称为“编码”,现在的计算机就是使用这种简单的思想处理字符的,只是其具体过程更复杂,至此,可能大家会产生出以下问题:

  • 为什么要进行两次编码,直接编码为二进制不是更省事吗?
    其实这很简单,第一次编码准确的说其实就是为每个字符编了一个号,这个编号可方便人们使用,编号通常是10进制或16进制数,这样在以后指定某个字符时,可以直接使用数字来指定这个字符,很明显,直接使用二进制更不方便,比如,对于ASCII字符集,可以方便的使用65表示字符“A”,显然,使用二进制数0100 0001更不方便。
  • 为什么要使用编号来指定字符,而不是直接指定字符呢?
    比如,字符“A”,为什么要用编号65呢,直接用“A”不是更方便?对于英文字母这些常见的字符,直接使用字符当然更方便,但对于不是经常使用或者难输入、难显示、不显示的字符(比如积分号、求和符号、零宽度空格、回车换行符等),那么使用编号就更方便了。
  • 一个二进制比特串,比如0100 0001,为什么不会被处理为整数65,而被处理为字符“A”呢?
    这与该比特串的类型有关,如果类型为整型,则计算机就会解释为整数65,若为字符型,就会解释为字符了,同理,若为浮点型,则会按浮点型的规则解释这个比特串。
二、字符编码
1、字符的第一次编码
  • 字符进行的第一次编码是将字符编码为与一个数值(比如一个整数)相对应,其实就是将每个字符都编了一个号,所以将这里的编码称为编号更准确。比如将字符A编码为十进制整数65,B编码为66等,通常把此步骤形成的“字符数字对”集合称为字符集,比如常用的ASCII字符集,就是将128个常用的字符编码为128个整数,另外,还有Unicode字符集,GB2312字符集等。
2、字符的第二次编码
  • 字符的第二次编码就是把第一次编码好的数值再编码为相应的二进制,这样计算机就可以直接使用相应的二进制进行存储了,这次编码通常会使用相应的算法把数值转换(即编码)为二进制,当然,也有直接编码为二进制的。比如ASCII码就是直接编码的,如字符A的第一次编码65,第二次编码就是相应的二进制值0100 0001
3、编码数值与字符一一对应问题
  • 通常情况下,字符集中的字符与第一次编码后的数值是一一对应的,但由于某些原因,Unicode并不总是如此,但是,人们还是习惯性的认为他们是一一对应的,于是通常使用第一次编码后的数值来确定一个字符。
4、易混、常混合使用的概念------编码
  • 由于历史原因,“编码”一词经典被混用,通常把第一次编码、第二次编码、字符集都称为编码,比如ASCII字符集也常称为ASCII编码,再如Unicode的第二次编码算法UTF-8,也被称为UTF-8编码。
三、补充知识-----Unicode与ISO 10646
1、Unicode
  • Unicode又称为统一码、万国码、单一码,是一种试图容纳全球所有字符的编码方案。Unicode包括字符集、编码方案等,它为每种语言中的每个字符设定了统一且唯一的二进制编码,以满足跨语言、跨平台的要求。Unicode制定的内容非常多,有兴趣的读者可以参阅相关文章。
2、ISO 10646与通用字符集(Universal Character Set,UCS)
  • 历史上,除了Unicode在试图制定全球统一的通用字符集外,国际标准化组织 ( ISO )也在制定相应的标准,其中通用多八位编码字符集(Universal Multiple-Octet Coded Character Set ),简称通用字符集(Universal Character Set,UCS)就是由ISO制定的ISO 10646标准所定义的字符集,是与Unicode并行的标准。最初,ISO与Unicode各自开发各自的项目,后来,双方意识到世界不需要两个不兼容的字符集,于是,双方进行了整合,并使彼此制定的标准相互兼容,以使两者保持一致,直到现在,两个组织都存在,并且各自独立公布各自的标准,但二者基本是一致的,不过Unicode的知名度比UCS更大,应用也更广泛。
3、代码点 ( Code Point,简称码点 )
  • Unicode把第一次编码后的数值称为码点(或代码点),使用“U+十六进制数”的形式表示,如U+6C49。注:码点、码点值、代码点三者经常混用。本文对码点的定义仅是一种简单粗略的理解,详细精确的定义请参阅有关Unicode编码的文献。
4、UTF-8、UTF-16、UTF-32
  • Unicode在进行第二次编码时有3种不同的算法把码点转换为二进制,分别是UTF-8,UTF-16和UTF-32,这三种算法常被称为UTF-8编码、UTF-16编码、UTF-32编码,他们分别使用变长位、16或32位、32位编码。也就是说,同一个字符,使用不同的编码算法将得到不同的二进制数值,比如,“汉(U+6C49)”,若使用UTF-8编码,则第二次编码后的值为0xE6 B1 89,若使用UTF-16编码,则第二次编码后的值就是码点值0x6C49。
  • 需要注意的是,在Unicode中,任意一个码点都可以使用UTF-8、UTF-16、UTF-32三种算法将其分别编码为三个不同的二进制数值,具体使用哪一种编码算法,这需要视所使用的应用而定,通常,应用都可自行设置使用哪一种算法。
5、码点与字符一一对应下周
  • 在Unicode标准中,码点与字符并不一定总是一一对应的,在Unicode标准中,一个字符有可能有多个码点,也有可能由多个码点来表示一个字符(常见于组合字符)。比如U+51C9与U+F977都是同一个字符“凉”,这主要是为了兼容韩国字符集的标准。再如,以下字符
    在这里插入图片描述
    是由基本字符g(U+0067)和U+0308(这个字符称为组合字符)组合而成,虽然以上字符是由两个Unicode码点组成,但现实中,人们会认为这是一个字符。以上由两个码点组合而成的字符在Unicode中被称为“用户感知字符”。

2.3.2 C++的字符常量


一、C++使用的字符集

1、C++使用的是什么字符集是一个很重要的问题,这关系到能否正确显示、处理字符以及字符处理的方式,若两个C++系统使用了不同的字符集,有可能会出现乱码的情形。

2、C++实现(通常指编译器)有4种类型的字符集:基本源字符集、基本执行字符集、扩展源字符集、扩展执行字符集。

  • 基本源字符集用来编写程序的源代码,包括大小写字母、数字、常用符号等;
  • 基本执行字符集用于处理程序执行期间的字符,如显示到屏幕上的字符,基本执行字符集比基本源字符集增加了一些字符,如退格字符、振铃字符等。
  • C++标准还允许实现提供扩展源字符集和扩展执行字符集。

3、 C++具体使用的是什么字符集,要依实现而定。通常情况下,C++实现使用的字符集是主机系统的编码,如IBM大型机使用EBCDIC编码,不过,最常用的字符集是ASCII字符集,本文未作特殊说明,均表示使用的ASCII字符集。

二、C++中的字符常量
1、在C++中字符常量有如下几种书写方式:
  • 将常规字符使用单引号括起来,如 ‘a’,‘b’,‘3’等,此种表示法代表的是字符的数值编码,这种表示方式的好处是不需要记住该字符的数值编码。单引号是C++表示字符的特定标志,比如字符a应书写为’a’,若直接写成a通常会被认为是变量名a,再如,字符2应书写为’2’,若直接写成2,则会被认为是数字2(通常被认为是整型)

  • 直接使用数值编码,即使用整数,这种表示方法并不是在所有场合都能表示字符,需视该数值的类型而定,如

    char c = 65;		//C++使用char声明字符类型,详见后文 
    cout<< c <<endl;	//输出字符A 
    int a= 65; 
    cout<< a <<endl;	//输出整数65
    
  • 使用单引号括起来的转义序列,转义序列详见后文。比如

    cout<< '\x41' <<endl;		//输出字符A
    
  • 通用字符名(详见后文)。比如

    cout<< '\u0041' <<endl;		//输出字符A
    
  • 宽字符(详见后文),如

    wcout << L'A' << endl;		//输出字符A,注:宽字符需使用wcout输出
    

2、字符串是用双引号括起来的,由0个或多个字符组合成,与单引号类似,双引号是C++表示字符串的特殊标志,应把字符串与字符加以区别,比如”a”表示字符串a,而’a’表示字符a。

3、除转义序列、通用字符名等特殊情形外,在单引号中只能有一个字符,若单引号中有超过1个以上的字符C++未作规定,因此不建义使用。超过1个以上的字符应使用双引号,表示这是一个字符串。比如’ab’,应写成”ab”。

三、转义序列

1、很多字符我们无法从键盘直接输入,比如回车符、退格符等不可打印字符,还有一些字符在C++中有特殊的意义,这些字符也不能被直接显示,比如双引号、单引号等,这些字符就可以使用转义序列的形式来表示,由转义序列表示的字符通常被称为转义字符。

2、转义序列以\反斜杠符号开始,后面紧接数字或字符,同时需要使用单引号括起来,比如’\n’表示回车换行,而’\b’表示退格,’\x61’表示字符a,同理’\’表示符号\。

3、注意:转义序列表示的是一个字符,虽然他是由几部分组成的。比如’\n’,表示一个字符,而不是两个字符’\’和’n’的组合。

4、转义序列有如下几种格式:

  • 1)、符号转义序列:即反斜杠后使用字符形式的转义序列,如\b、\n等,表2.4为C++定义的符号转义序列

  • 2)、8进制格式为:

    \ooo				//其中ooo表示1~3个8进制数
    
  • 3)、16进制格式为:

    \xh或\xhh...		//表示以x开头,后跟一个或多个16进制数
    

在这里插入图片描述

5、有关转义序列的规则

  • 1)、8进制或16进制的数值代表的是所使用字符集里与该字符所对应的数值编码,以下示例是使用ASCII字符集所表示的转义字符

    \40 (空格)			\7 (响铃)			\141 (字符a)
    
  • 2)、8进制或16进制转义序列,其取值范围不应超过该字符类型的长度,否则有可能报错,默认情况下字符的类型为char,通常char的长度为8位,比如

    cout<< ' \433';			//超出范围,可能报错
    

    字符数值编码超过char长度时,应使用其他字符类型,C++使用特有的前缀来表示其他字符类型,详见后文

  • 3)、8进制格式转义序列,最多只使用3位数来构成转义字符,超过3位数时就表示转义字符结束,比如,

    cout << "\1413";			//输出a3,即141所对应的字符a和字符3
    
  • 4)、16进制格式转义序列需要使用到后面跟着的所有数字,如

    cout<<"\x1413";			//可能会因数值超出范围而报错
    

    \x1413表示一个字符,该字符是16进制数值编码1413所对应的字符,由于大多数机器的char型只占据8位的长度,所以,该示例可能会报错。

  • 5)、系统默认使用8进制格式的转义序列,书写时不必书写前缀符数字0,因此,需要注意的是,无法在“\”反斜杠后面使用十进制数。比如"\0141",表示两个字符,即相当于’\014’和’1’两个字符,再如 ’ \141’ 其后的数字141是8进制数,而非10进制数

  • 6)、16进制格式转义序列的前缀x不能省略,前缀0须省略。如

    cout<<'\x61';		//输出字符a
    cout<< '\0x61';		//不能输出字符a,相当于单引号中有多个字符的情形
    
四、通用字符名

1、通用字符名其格式为

	\uhhhh 
	\Uhhhhhhhh

其后是相应的16进制数,表示的是字符的ISO 10646码点,需要注意的是\u之后必须是4位数,\U之后必须是8位数。比如\u00E2、\u00F6、\U00006C49等,若系统不支持ISO 10646编码,则无法显示这些字符。注:ISO 10646是与Unicode同步的国际标准

2、若实现支持扩展字符集,则可在标识符或字符串中以“通用字符名”的形式使用一些特殊字符,比如,法文的重元音、汉字等。注意:转义字符不能用于标识符中,比如

int aaa\u6C49cccc = 33;			//正确,\u6C49是Unicode码点为6C49的“汉”字
int aaa\x6C40dddd=44;			//错误,转义字符\x6C49不能用于标识符中
int aaa'\x6C40'eeee=55;			//错误,原因同上

3、\u开始的通用字符名的类型为char16_t,\U开始的通用字符名的类型为char32_t,其中char16_t和char32_t是C++11中新增的类型,详见后文

4、注意:经gcc和VC++测试,通用字符名的类型是不确定的,gcc当数值范围在128之内时,其类型为char,超过则为int类型。VC++2019的规则是,若该字符不能正常显示则通通为char,在ASCII范围内的字符为char,能正常显示且超过ASCII范围的字符都为int。

五、宽字符

1、对于像ASCII码这样的字符集,在将其编码为计算机所存储的格式时(即第二次编码)只使用1个字节(8位)来存储就足够了,但对于汉字来讲,使用一个字节来存储显然是不行的,比如汉字的“汉”字,GB18030的编码为0xBABA ,很明显,即使直接存储该数值也至少需要2个字节,也就是说1字节(8位)长的char类型是无法存储汉字的。除了汉字的编码之外,各个国家都为各自的国家制定了自已的编码,这些编码有些是使用等长的字节来存储,有些使用的长度则是不等的。

2、当需要处理的字符无法用一个字节来表示时,C++有两种处理方式

  • 若C++实现的基本字符集是大型字符集,则编译器厂商可直接把char定义为16位或更长的长度。有关char长度的讲解,见后文
  • 若C++实现同时支持较小的基本字符集和较大的扩展字符集,则使用8位的char来表示基本字符集,使用另一种被称为wchar_t的类型 (宽字符类型) 来表示扩展字符集。
  • 从以上规则可见,C++能否处理以及怎样处理超过1字节的字符是因C++实现(编译器)而定的。

3、宽字符常量

  • 宽字符常量只需在常规字符的前面加上大写字母L即可(中间不能有空格),比如L’a’、L’\x65’;等。

4、宽字符类型

  • C++使用wchar_t来表示宽字符类型,比如wchar_t c=L’a’
  • wchar_t的长度和符号因实现而异。通常情况下,宽字符一般占据2个字节的长度,使用的字符集是Unicode字符集。

5、宽字符应使用wcout进行输出,比如wcout<<L’a’<<endl;

6、多字节字符和宽字符简介

  • 多字节字符指的是存储字符长度(即第二次编码时二进制位的长度)为1到多个字节长度的字符,而宽字符一般指的是Unicode字符集中的字符。宽字符和多字节字符并没有严格的定义,理论上来讲,Unicode也是多字节字符,再如,汉字的GB2312编码也是多字节字符,除此之外,各国都可能制定有各自的多字节字符编码集。
六、指定字符常量的类型

通过添加前缀的方式可以改变字符的默认类型,如表XXX所示,其中char16_t、char32_t是C++11新增的类型,详见后文
在这里插入图片描述


2.3.3 C++的字符类型


在C++中有如下几种字符类型

  • char:通常所说的字符类型就是指的char型,这是常规的、经常使用的字符类型,比如char a;表示变量a是一个字符类型。
  • wchar_t:即宽字符类型,详见前文
  • char16_t和char32_t:这是C++11新增的两种字符类型,详见后文讲解。
一、char的长度

1、char所占据的长度为能够表示目标计算机系统中的所有基本符号,也就是说,char的长度可能是8位、16位等长度,需视具体情况而定。通常情况下,使用8位就可以表示所有的基本符号了。

2、注意:虽然C++标准没有对1字节是多少位作出规定,但是char的长度始终被认为是“一个字节”,也就是说,若char的长度为16位,则在C++中,一个字节的长度就是16位,而不是8位。通常情况下,一个字节都是指的8位,本文未作特殊说明,一个字节都是指的8位。

3、char占据的位数可以使用climits头文件中的符号常量CHAR_BIT或根据其最大值UCHAR_MAX来判断。

二、char的符号

1、默认情况下C++没有规定char的符号,也就是说,默认情况下char既不是有符号的,也不是没有符号的,char的符号由实现决定,一般的编译器都把char当作signed来处理。可以使用unsigned和signed来明确指定char是否有符号,比如unsigned char表示无符号char。

2、当把char当作整数来使用时,char的符号就很重要了,因为char类型占据一个字节(一般为8位),因此signed char表示范围是-128 ~ 127,unsigned char表示的范围是0 ~ 255。

3、判断编译器中char的类型是否是有符号char的方法:

  • 1)、方法一:使用头文件中的符号常量:若char是有符号类型,则CHAR_MIN(表示char类型的最小值)的值应与SCHAR_MIN的值相同,CHAR_MAX应与SCHAR_MAX的值相同;若char是无符号类型,则CHAR_MIN的值应为0,而CHAR_MAX的值应与UCHAR_MAX的值相同。
  • 2)、方法二:假设一个字节为8位,首先对char变量赋一个大于128小于255的数,然后将此变量赋给一个整型变量,再输出这个整型变量,若输出的值为原值则表示是无符号型char,若不是原型(有符号型会溢出,因此不可能输出原值),则表示是有符号型char;比如char c=222; int a=c; cout<<a<<endl; 若输出的a是原值222则表示char的类型是无符号型的,若输出的的不是原值则表示是有符号型的。注意:在有些编译器中char c=222;可能会出错(数据溢出),若在这种编译器下此语句出错就表示char的类型是有符号型。
三、字符与整数

1、C++将字符表示为整数,该整数是与其字符相对应的数值编码,这样就不必使用专门的转换函数在字符和ASCII码之间转换,这在一定程度上提供了方便。因此,在C++中通常把char当作比short更小的整型来使用,或把整型当作char来使用,这意味着

  • 可对char进行整数的相关操作,比如,进行加、减等运算,比如

    char c='a'+1;
    cout<<c<<endl;		//输出字符b
    
  • 可将整数赋值给char类型变量,同理也可以将char变量赋给int变量;比如

    char c=97; 
    cout<<c<<endl;		//输出字符a(数值97是ASCII字符集中的字符a的编码)
    int d=’a’; 
    cout<<d<<endl; 		//输出97,因为字符a的ASCII编码为97。
    

2、为什么cout会把char类型变量输出为字符而不是整数值呢?这是因为cout将需要输出的内容进行了相应的转换,这种转换工作是由变量的类型所引导的,也就是说,变量的类型将影响cout如何显示值,因此,cout在输出char时,将其相应的值转换为需要显示的字符,同理,cout在输出int型变量时,则将其值显示为一个整数值。比如

char c=97;
cout<<c;			//输出a,通过查看内存可以知道,变量c的值仍然是整数97

3、注意:在C++版本2.0以前,cout将字符变量显示为字符,将字符常量显示为数字,把字符常量存储为int类型,不过现在基本上都是2.0以后的版本,C++将字符常量存储为char类型,因此不用担心以上问题。比如

char c = 'a';		//在早期版本中,表示从常量'a'(int型)中复制8位到变量c中,此步会有精度损失
cout<< 'a' <<endl;	//早期版本输出整数值97,并不会输出字符a
cout<< c << endl;	//输出整数97,注:此例有待考证,因为第一步赋值过程可能有精度损失
四、char16_t 和 char32_t ( C++11新增类型 )

1、宽字符类型wchar_t的长度和符号是不确定的,随实现而异,然而,在实际处理字符时,若使用特定长度和符号的类型,将会大有益处,为此,C++11新增了char16_t和char32_t两种类型。

2、char16_t是无符号的,长度为16位,其对应的常量使用前缀u表示,char16_t还与\u开始的通用字符名相匹配。

3、char32_t是无符号的,长度为32位,其对应的常量使用前缀U表示,char32_t还与\U开始的通用字符名相匹配。比如

char16_t c1 = u'x';
char32_t c2 = U'\u00006C49';

作者:黄邦勇帅(原名:黄勇) 2021年5月6日



在这里插入图片描述


Guess you like

Origin blog.csdn.net/hyongilfmmm/article/details/116446656