预计阅读本页时间:-
匹配printf()说明符的类型
使用printf()语句时,切记每个要显示的值都必须对应自己的格式说明符,并且显示值的类型要同说明符相匹配。
3.4.3 使用字符:char类型
char类型用于存储字母和标点符号之类的字符。但是在技术实现上char却是整数类型,这是因为char类型实际存储的是整数而不是字符。为了处理字符,计算机使用一种数字编码,用特定的整数表示特定的字符。美国最常用的编码是ASCII码,这张表在本书封二给出来了。本书也使用此编码。在ASCII码中,整数值65代表大写字母A;因此要存储字母A,实际只需存储数65(许多IBM主机使用另一种称为EBCDIC的编码,但其原理是相同的。其他国家的计算机系统也许会使用完全不同的编码)。
标准ASCII码值的范围从0到127,只需7位即可表示。而char类型通常定义为使用8位内存单元,该大小容纳标准ASCII编码是绰绰有余的。许多系统(比如IBM PC和Apple Macintosh)提供的不同的扩展ASCII编码也使用8位存储单元。更普遍一些来看,C保证char类型足够大,以存储其实现所在的系统上的基本字符集。
许多字符集包含远多于127甚至远多于255个值,例如,日本kanji字符集。商用的Unicode字符集建立了一个能够表示世界范围内多种字符集的系统,目前已有超过96 000个字符。国际标准化组织(International Organization for Standardization, ISO)和国际电工技术委员会(International Electrotechnical Commission, IEC)为字符集开发了ISO/IEC 10646标准。幸运的是,Unicode标准保持了同更广泛的ISO/IEC 10646标准的兼容性。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
采用上述集合之一作为基本字符集的平台应该使用16位甚至32位的char表示方法。C把一个字节(byte)定义为char类型使用的位(bit)数。所以在这样的系统上,C文档中提到的一个字节是16位或者32位,而不是8位。
一、声明char类型变量
正如您所预料的那样,char变量同其他类型变量的声明方式相同,下面是一些例子:
这段代码创建了 3个char变量:response、itable和latan。
二、字符常量及其初始化
假定您要把一个字符常量初始化为字母A。计算机语言应该使事情更为简单,因此您无须记住字符的ASCⅡ码。可以使用下列初始化语句把字符A赋给grade:
单引号中的一个字符是C的一个字符常量,编译器遇到‘A’时会将其转换为相应的编码值,其中单引号是必不可少的。看另外一个例子:
如果不使用单引号,编译器会将T视为一个变量名;如果使用双引号,编译器将其视为一个字符串我们将在第4章讨论字符串。
因为字符实际上以数值的形式存储,所以也可以使用数值编码来赋值:
上面语句中,65是int类型,但是它在char类型大小范围之内,所以这样的赋值完全允许。由于65是字母A的ASCII码,此语句将字符A赋予变量grade。但是要注意,这个结果的假设是系统使用ASCII码。而使用‘A’代替65进行赋值则可在任意系统中正常工作。因此,推荐使用字符常量,而不是数值编码。
令人奇怪的是,C将字符常量视为int类型而非char类型。例如,在int类型为32位和char类型为8位的ASCII系统中,下列代码:
意味着‘B’作为数值66存储在一个32位单元中,而赋值后的grade则把66存储在一个8位单元中。利用字符常量的这个特性,可以定义一个字符常量‘FATE’,这将把4个独立的8位ASCII码存储在一个32位单元中。然而,如果把这个字符常量赋给一个char变量,那么只有最后8位会起作用,因此变量的值为‘E’。
三、非打印字符
单引号技术适用于字符、数字和标点符号,但是如果浏览一下本书封二的ASCⅡ表,您会发现有些ASCII字符是打印不出来的。例如一些动作描述:退格、换行或者让终端铃响(或扬声器蜂鸣)。怎样表示这些字符?C提供了3种方法。
我们已经提到过第一种方法,就是使用ASCII码。例如,蜂鸣字符的ASCII值为7,所以可以这样写:
第二种方法是使用特殊的符号序列,即转义序列(Escape Sequence)。表3.2列出了转义序列及其意义。
表3.2 转义序列
序列 | 意 义 |
---|---|
\a | 警报(ANSIC) |
\b | 退格 |
\f | 走纸 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\V | 垂直制表符 |
\\ | 反斜杠(\) |
\' | 单引号(') |
\" | 双引号(") |
\? | 问号(?) |
\0oo | 八进制值(o表示一个八进制数字) |
\xhh | 十六进制值(h表示一个十六进制数字) |
给一个字符变量进行赋值时,转义序列必须用单引号括起来。例如,可以使用下列语句:
这样,打印变量nerf在打印机或屏幕上将表现为换行。
现在我们来研究一下每个转义序列的功能。警报字符\a(由C90新增)产生一个能听到或能看到的警报,这取决于计算机的硬件,蜂鸣是最常见的警报(在一些系统中警报字符不起作用)。ANSI标准规定警报字符不改变系统的活动位置。活动位置(active position)即在显示设备(屏幕、电传打字机、打印机,等等)中下一个字符将出现的位置。也就是说,如果在程序中把警报字符输出到屏幕上,将只发出一声蜂鸣而并不移动屏幕光标。
接下来,转义序列\b、\f、\n、\r、\t和\v是常用的输出设备控制字符。说明它们的最好方法是描述它们对活动位置的影响。退格符\b使活动位置在当前行上退回一个空格。走纸符\f将活动位置移到下一页的开始处。换行符\n将活动位置移到下一行的开始处。回车符\r将活动位置移到当前行的开始处。水平制表符\t将活动位置移到下一个水平制表点(通常为字符位置1、9、17、25,等等)。垂直制表符\v将活动位置移到下一个垂直制表点。
这些转义字符不一定适用于所有设备。例如,走纸符和垂直制表符在PC屏幕上产生奇怪的符号,而不会产生任何光标移动,它们只有在输出到打印机上时才会像前面描述的那样工作。
下面三个转义序列\\、\'和\"使您可以引用\、'和"字符常量(由于这些符号被作为printf()命令的一部分用来定义字符常量,所以如果您在字面上使用它们时会造成混乱)。如果要打印下面这行内容:
可以使用如下代码:
最后两个转义字符\0oo和\xhh是ASCII码的专用表示方法。如果想用一个字符的八进制ASCII码代表它,可以在编码值前加一个反斜杠(\)并用单引号引起来。例如:如果编译器不识别警报字符(\a),则可以使用ASCII码代替:
可以省去前面的0,就是说‘\07’和‘\7’都可以。即使没有前缀0,这种写法仍会使数值被解释为八进制数。
从C90开始,C提供了第三种选择,即使用十六进制形式表示字符常量。在这种形式中,反斜杠后跟一个x或X,再加上1到3位十六进制数字。例如,Ctrl+P字符的十六进制ASCII码值为10(相当于十进制中的16),它可以表示为‘\x10’或‘\X010’。图3.5显示了一些有代表性的整数类型。
图3.5 书写int系列类型的常量
使用ASCII码时要注意数字和数字字符的区别。例如,字符4的ASCII码值为52。写法‘4’表示符号4而不是数值4。
关于转义序列,您可能会有如下三个疑问:
● 为什么在上一个例子(printf(“Gramps sez,\“a \\ is a backslash\“\“n”);)中,转义序列没有用单引号引起来呢?无论普通字符还是转义序列,如果作为双引号中字符集合的一部分,则无需单引号。该例中的其他字符(G、r、a、m、p、s,等等)也没有用单引号引起来。双引号中的字符集合称为字符串(详见第4章)。与之类似,printf(“Hello!\007\n”);语句将打印Hello!并发出一声蜂鸣,而printf(“Hello!7\n”);语句则打印Hello!7。不在转义字符中的数字将像普通字符那样被打印出来。
● 什么时候使用ASCII码,什么时候使用转义序列呢?如果要在某个转义序列和与其对应的ASCII码之间做出选择,则应当使用转义序列。比如选择‘\f’而不是‘\014’。首先,转义字符更容易记忆;其次,这样做使程序的可移植性更好。因为在不使用ASCII码的系统中,‘\f仍然适用。
● 当需要使用数值编码时,为什么使用‘\032’而不是032?首先,‘\032’能更清晰地表达程序员表示一个字符编码的意图;其次,‘\032’这样的转义序列可以嵌入到C字符串中,比如字符串“Hello!\007\n”中就嵌入了‘\007’。
四、打印字符
printf()函数使用%c说明符打印一个字符。回忆一下,字符变量被存储为1字节长的整数值。因而,如果使用通常的%d说明符打印char变量,将得到一个整数。%c格式说明符告诉printf()函数打印编码值等于那个整数的字符。程序清单3.5显示了char变量的两种打印方法。
程序清单3.5 charcode.c程序
运行此程序,在键入字母后不要忘记按Enter或Return键。随后scanf()函数将读取您键入的字符,&符号指示把输入的字符赋给变量ch,接着printf()函数把ch的值打印两次。首先作为字符打印(由代码中的%c指示),然后作为十进制整数打印(由代码中的%d指示)。注意printf()说明符决定数据的显示方式而不是决定数据的存储方式,如图3.6所示。
图3.6 数据显示与数据存储
五、有符号还是无符号
一些C实现把char当作有符号类型。这意味着char类型值的典型范围为-128到127。另一些C实现把char当作无符号类型,其取值范围为0到255。编译器手册会指明char的类型,或者您可以通过limits.h头文件检查这一信息,下一章将对该头文件做详细介绍。
根据C90标准,C允许在关键字char前使用signed和unsigned。这样,无论默认的char类型是什么,signed char是有符号类型,而unsigned char则是无符号类型。这对于使用字符类型处理小整数十分有用。如果处理字符,则只须使用不带修饰词的标准char类型。
3.4.4 _Bool类型
_Bool类型由C99引入,用于表示布尔值,即逻辑值true(真)与false(假)。因为C用值1表示true,用值0表示false,所以_Bool类型实际上也是一种整数类型。只是原则上它仅仅需要1位来进行存储。因为对于0和1而言,1位的存储空间已经够用了。
程序使用布尔值来选择执行哪个代码分支。第6章“C控制语句:循环”和第7章“C控制语句:分支和跳转”将详细介绍代码的执行,我们将在那里做进一步讨论。
3.4.5 可移植的类型:inttypes.h
还有更多的整数类型吗?没有了,但是已有类型有一些别名。您可能认为自己已经接触到了足够多的名字,可是这些基本的名字不够明确。比如,知道一个变量是int类型并不能告诉您它有多少位,除非您查看系统文档。为解决这类问题,C99提供了一个可选的名字集合,以确切地描述有关信息。例如:int16_t表示一个16位有符号整数类型,uint32_t表示一个32位无符号整数类型。
要使这些名字对于程序有效,应当在程序中包含inttypes.h头文件(注意,在编写本书第五版的时候,有些编译器还不支持这个特性)。这个文件使用typedef工具创建了新的类型名字(第5章“运算符、表达式和语句”中有简要介绍)。比如,该头文件会用uint32_t作为一个具有某种特征的标准类型的同义词或别名,在某个系统中这个标准类型可能是unsigned int,而在另一个系统中则可能是unsigned long。编译器会提供同所在系统相一致的头文件。这些新的名称叫作“确切长度类型”(exact width type)。注意,与int不同,uint32_t不是关键字,所以必须在程序中包含inttypes.h头文件,编译器才能够识别它。
使用确切长度类型的一个潜在问题是某个系统可能不支持一些选择。比如,不能保证某个系统上存在一种int8_t类型(8位有符号整数)。为解决这个问题,C99标准定义了第2组名字集合。这些名字保证所表示的类型至少大于指定长度的最小类型,被称为“最小长度类型”(minimum width type)。例如,int_least8_t是可以容纳8位有符号数的那些类型中长度最小的一个的别名。某个特殊系统的最小类型的长度也许是8位,而该系统上不一定会定义int8_t类型。但是仍然可以使用int_least8_t类型,它的实现也许是16位整数。
当然,一些程序员更加关心速度而非空间。C99为他们定义了一组可使计算达到最快的类型集合。这组集合被称为“最快最小长度类型”(fastest minimum width type)。例如,把int_fast8_t定义为系统中对8位有符号数而言计算最快的整数类型的别名。
最后,对于某些程序员有时会需要系统最大的可能整数类型。为此,C99把intmax_t定义为最大的有符号整数类型,即可以容纳任何有效的有符号整数值的类型;类似地,把uintmax_t定义为最大的无符号整数类型。顺便说一句,这些类型可能大于long long和unsigned long类型,因为除了要求实现的类型之外,C实现还可以定义其他类型。
C99不仅提供这些新的、可移植的类型名,还提供了对这些类型数据进行输入输出的方法。例如,printf()打印某类型的值时要求与之相对应的说明符。那么如果打印int32_t类型值在一种定义中应使用%d说明符,而在另一种定义中应使用%ld说明符,您该怎么办?C99标准提供了一些串宏来帮助打印这些可移植类型,详见第4章。例如,inttypes.h头文件将定义串PRId16来表示打印16位有符号值所需的合适说明符(例如,hd或d)。程序清单3.6演示了使用一种可移植类型及其相应说明符的方法。
程序清单3.6 altnames.c程序
在最后的printf()语句中,参数PRId16被它在inttypes.h里的定义“hd”所替代,因而这行语句等价于:
printf(“me16 = %” “hd” “\n”, me16);
C语言将三个连续的字符串合并为一个引号引起来的串,因而这行语句又等价于:
printf(“me16 = %hd\n”,me16);
下面是输出结果,注意示例中还使用了\“转义序列来显示双引号:
First,assume int16_t is short:me16=4593
Next,let's not make any assumptions.
Instead,use a “macro” from inttypes.h:me16=4593
参考资料6“扩展的整数类型”提供了完整的inttypes.h头文件中的附加类型,并列出了所有的说明符宏。