5.C Primer Plus 第四章后续内容
《c Primer Plus》第四章剩余内容
4.4.4转换说明的意义
转换说明是翻译说明,而并非用一个值替换原始值,%d的意思是“把给定的值翻译成十进制 整数文本并打印出来”。
1.转换不匹配
转换说明应该与待打印值的类型相匹配。例如,如果打印一个int类型的值,可以使用%d、%x或者%o,这些转换说明都可用于打印int类型的值,区别在于他们分别表示一个值的形式不同。类似于int类型,打印double类型的值时,可使用%f、%e、%g。
若转换说明与待打印的值的类型不匹配,如下:

输出结果如下:

然而,第2行mnum变量对应的转换说明%u(无符 号)输出的结果却为65200,并非期望的336。这是由于有符号short int类型 的值在我们的参考系统中的表示方式所致。首先,short int的大小是2字节; 其次,系统使用二进制补码来表示有符号整数。这种方法,数字0~32767代 表它们本身,而数字32768~65535则表示负数。其中,65535表示-1,65534 表示-2,以此类推。因此,-336表示为65200(即, 65536-336)。所以被解 释成有符号int时,65200代表-336;而被解释成无符号int时,65200则代表 65200。注意:在所有系统中都用此种方法表示复数,%u转换说明不能将数字和符号分开。
第3行演示结果的出现的原因:在我 们的系统中,short int是2字节,char是1字节。当printf()使用%c打印336时, 它只会查看储存336的2字节中的后1字节。这种截断(见图4.8)相当于用一 个整数除以256,只保留其余数。在这种情况下,余数是80,对应的ASCII值 是字符P。用专业术语来说,该数字被解释成“以256为模”(modulo 256), 即该数字除以256后取其余数,转换为ASCII码值。原理如下:

最后,我们在该系统中打印比short int类型最大整数(32767)更大的整 数(65618)。这次,计算机也进行了求模运算。在本系统中,应把数字 65618储存为4字节的int类型值。用%hd转换说明打印时, printf()只使用最后 2个字节。这相当于65618除以65536的余数。这里,余数是82。鉴于负数的 储存方法,如果余数在32767~65536范围内会被打印成负数。对于整数大小 不同的系统,相应的处理行为类似,但是产生的值可能不同。
混淆整型和浮点型

输出结果:

首先,%e转换说 明让printf()函数认为待打印的值是double类型(本系统中double为8字节)。 当printf()查看n3(本系统中是4字节的值)时,除了查看n3的4字节外,还会 查看查看n3相邻的4字节,共8字节单元。接着,它将8字节单元中的位组合 解释成浮点数(如,把一部分位组合解释成指数)。因此,即使n3的位数正 确,根据%e转换说明和%ld转换说明解释出来的值也不同。最终得到的结果 是无意义的值。 第1行也说明了前面提到的内容:float类型的值作为printf()参数时会被 转换成double类型。在本系统中,float是4字节,但是为了printf()能正确地显 示该值,n1被扩成8字节。
第2行输出显示,只要使用正确的转换说明,printf()就可以打印n3和 n4。
第3行输出显示,如果printf()语句有其他不匹配的地方,即使用对了转 换说明也会生成虚假的结果。问题在于参数传递,具体情况因编译器实现而异。
参数传递
参数传递机制因编译器实现而异。
例如:printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
原理如下:1.程序将传入的值放入被称为栈的内存区域。
2.计算机根据变量类型(而不是根据转换说明)把这些值放入栈中。
3.控制转到printf()函数。
4.printf()函数根据转换说明(不是根据变量类型)从栈中读取值。
5.根据转换说明读取值的字节,如果值的字节数大于转换说明的字节数,则将值的字节分为两个部分,解释成某变量类型的数。

2.printf()的返回值
大部分C语言中的函数都有一个返回值,这是函数计算并返回给主调程序的值。例如:
C语言库中包含一个sqrt()函数,接受一个数作为参数,并返回该数的平方作为返回值,可以将返回值赋给某一变量或者用于计算,或者用于参数传递像其他的值一样使用。
而printf()函数的返回值则是它打印的字符的个数,如果有输出错误则返回一个负值(printf()函数的不同版本会返回不同的值)。且printf()函数的返回值作为其打印输出功能的附带功能,通常很少用到,通常用于检查输出错误(如,写入文件时很常用)。
如何确定函数的返回值

输出结果为:

该程序用rv=printf()的形式将printf()函数的返回值赋给rv。
因此该语句完成了两项内容:1.打印信息 2.给变量赋值
注意:printf()函数的返回值是打印字符的个数,这个字符个数包含所有字符数包括空格和不可见的换行符(\n)。
3.打印较长的字符串
有时,printf()语句太长,在屏幕上不方便阅读。如果空白(空格、制表 符、换行符)仅用于分隔不同的部分,C 编译器会忽略它们。因此,一条语 句可以写成多行,只需在不同部分之间输入空白即可。
可以在逗号与字符之间断行,C语言编译器会无视多余的空白。
注意:不能在双引号括起来的字符串中间断行,否则编译器会报错:字符串常量中有非法字符。
在字符串中,可以使用\n来表示换行字符,但是不能通过按下Enter(Return)键产生实际的换行符。
字符串断行:
方法一:使用多个printf()语句。

方法二:用反斜杠(\)和Enter(或Return)键组合来断行。

方法三:ANSI C引入的字符串连接。

在两个用双引号括起来的字符串之 间用空白隔开,C编译器会把多个字符串看作是一个字符串。

因此,上述三种方式的输出结果是一样的。
注意:上述方法中,要记得在字符串中包含所需的空格。
如,"young""lovers"会成为"younglovers",而"young " "lovers"才是"young lovers"。
4.4.5使用scanf()函数
C库包含了多 个输入函数,scanf()是最通用的一个,因为它可以读取不同格式的数据。
如果要将其储存为数值而不是字符串,程序就必须把字符一次转换为数值,这就是scanf()函数要做的。scanf()把输入的字符串转换成整数、浮点数、字符和字符串换成显示在屏幕上的文本。
scanf()函数也使用格式字符串和参数列表。scanf()中的格式字符串表明字符输入流的目标数据类型。两个函数主要的区别在于参数列表中,printf()函数使用变量、变量和表达式,而scanf()函数使用指向变量的指针。
注意:scanf()函数使用的两条规则:
1.如果用scanf()读取基本变量类型的值,在变量名前加上一个&。
2.如果用scanf()把字符串读入字符数组中,不要使用&。
scanf()函数使用空白(换行符、制表符和空格)把输入分成多个字段。 在依次把转换说明和字段匹配时跳过空白。
**注意:上面示例的输入项(粗体 部分是用户的输入)分成了两行。只要在每个输入项之间输入至少一个换行 符、空格或制表符即可,可以在一行或多行输入。

输出结果为:

唯一例外的是%c转换说明。**根据%c,scanf()会读取每个字符,包括空白。
scanf()函数所用的转换说明与printf()函数几乎相同。主要的区别是,对 于float类型和double类型,printf()都使用%f、%e、%E、%g和%G转换说 明。而scanf()只把它们用于float类型,对于double类型时要使用l修饰符。
scanf()转换说明:

scanf()转换说明中的修饰符


使用转换说明比较复杂,而且这些表中还省略了一些特性。 省略的主要特性是,从高度格式化源中读取选定数据,如穿孔卡或其他数据 记录。
1.从scanf()角度输入
假设scanf()根据一 个%d转换说明读取一个整数。scanf()函数每次读取一个字符,跳过所有的 空白字符,直至遇到第1个非空白字符才开始读取。因为要读取整数,所以 scanf()希望发现一个数字字符或者一个符号(+或-)。如果找到一个数字或 符号,它便保存该字符,并读取下一个字符。如果下一个字符是数字,它便 保存该数字并读取下一个字符。scanf()不断地读取和保存字符,直至遇到非 数字字符。如果遇到一个非数字字符,它便认为读到了整数的末尾。然后, scanf()把非数字字符放回输入。这意味着程序在下一次读取输入时,首先读 到的是上一次读取丢弃的非数字字符。最后,scanf()计算已读取数字(可能 还有符号)相应的数值,并将计算后的值放入指定的变量中。
如果使用字段宽度,scanf()会在字段结尾或第1个空白字符处停止读取 (满足两个条件之一便停止)。
scanf()将停在 221 那里,并把A放回输入中,不会把值赋给指定变量。程序在下一次读取输入 时,首先读到的字符是A。如果程序只使用%d转换说明, scanf()就一直无 法越过A读下一个字符。另外,如果使用带多个转换说明的scanf(),C规定 在第1个出错处停止读取输入。
用其他数值匹配的转换说明读取输入和用%d 的情况相同。区别在于 scanf()会把更多字符识别成数字的一部分。例如,%x转换说明要求scanf()识 别十六进制数a~f和A~F。浮点转换说明要求scanf()识别小数点、e记数法 (指数记数法)和新增的p记数法(十六进制指数记数法)。
如果使用%s 转换说明,scanf()会读取除空白以外的所有字符。scanf()跳 过空白开始读取第 1 个非空白字符,并保存非空白字符直到再次遇到空白。 这意味着 scanf()根据%s 转换说明读取一个单词,即不包含空白字符的字符 串。
注意:1.如果使用字段宽度,scanf()在字段末尾或第1个空白字符处停止读取。 无法利用字段宽度让只有一个%s的scanf()读取多个单词。
2.当scanf()把字符串放进指定数组中时,它会在字符序列的末尾加上'\0',让数 组中的内容成为一个C字符串。
在C语言中scanf()并不是最常用的输入函数。这里重点介绍它 是因为它能读取不同类型的数据。C 语言还有其他的输入函数,如 getchar() 和 fgets()。这两个函数更适合处理一些特殊情况,如读取单个字符或包含空格的字符串。
2.格式字符串中的普通字符
scanf()函数允许把普通字符放在格式字符串中。除空格字符外的普通字 符必须与输入字符串严格匹配。
例如,假设在两个转换说明中添加一个逗号:
scanf("%d,%d", &n, &m);
scanf()函数将其解释成:用户将输入一个数字、一个逗号,然后再输入一个数字。也就是说,用户必须像下面这样进行输入两个整数:

由于格式字符串中,%d后面紧跟逗号,所以必须在输入88后再输入一 个逗号。但是,由于scanf()会跳过整数前面的空白,所以下面两种输入方式都可以:

格式字符串中的空白意味着跳过下一个输入项前面的所有空白。所以以下的输入格式都没问题:

注意:“所有空白”的概念包括没有空格的特殊情况。**
除了%c,其他转换说明都会自动跳过待输入值前面所有的空白。因 此,scanf("%d%d", &n, &m)与scanf("%d %d", &n, &m)的行为相同。对 于%c,在格式字符串中添加一个空格字符会有所不同。
3.scanf()的返回值
scanf()函数返回成功读取的项数。如果没有读取任何项,且需要读取一 个数字而用户却输入一个非数值字符串,scanf()便返回0。当scanf()检测 到“文件结尾”时,会返回EOF(EOF是stdio.h中定义的特殊值,通常用 #define指令把EOF定义为-1)。
4.4.6printf()和scanf()的*修饰符
printf()和scanf()都可以使用*修饰符来修改转换说明的含义。但是,它 们的用法不太一样。
1.printf()的*修饰符
希望通过程序来指定,那么可以用* 修 饰符代替字段宽度。但还是要用一个参数告诉函数,字段宽度应该是多少。 也就是说,如果转换说明是% * d,那么参数列表中应包含*和 d对应的值。这 个技巧也可用于浮点值指定精度和字段宽度。

变量width提供字段宽度,number是待打印的数字。因为转换说明中*在 d的前面,所以在printf()的参数列表中,width在number的前面。同样,width 和precision提供打印weight的格式化信息。
输出结果如下:

这里,用户首先输入6,因此6是程序使用的字段宽度。类似地,接下来 用户输入8和3,说明字段宽度是8,小数点后面显示3位数字。一般而言,程 序应根据weight的值来决定这些变量的值。
2.scanf()的*用法
把*放在%和转换字符之间时,会使得 scanf()跳过相应的输出项。

上述的scanf()指示:跳过两个整数,把第3个整数拷贝给n。
输出结果为:

在程序需要读取文件中特定列的内容时,这项跳过功能很有用。**
4.4.7printf()的用法提示
想把数据打印成列,指定固定字段宽度很有用。因为默认的字段宽度是 待打印数字的宽度,如果同一列中打印的数字位数不同,打印出来的数字可能参差不齐。
使得打印出的数字整齐美观
1.使用足够大的固定字段宽度可以让输出整齐美观,但可能造成不必要的空白。
2.在两个转换说明中间插入一个空白字符,可以确保即使一个数字溢出了 自己的字段,下一个数字也不会紧跟该数字一起输出(这样两个数字看起来 像是一个数字)。这是因为格式字符串中的普通字符(包括空格)会被打印出来。另一方面,如果要在文字中嵌入一个数字,通常指定一个小于或等于该 数字宽度的字段会比较方便。这样,输出数字的宽度正合适,没有不必要的空白。

本地化设置
美国和世界上的许多地区都使用一个点来分隔十进制值的整数部分和小 数部分,如3.14159。然而,许多其他地区用逗号来分隔,如 3,14159。读者 可能注意到了,printf()和 scanf()都没有提供逗号的转换说明。
如果指定了荷兰语言环境,printf()和 scanf()在显示和读取浮点值时会使用本地惯例(在这种情况下,用逗号代替 点分隔浮点值的整数部分和小数部分)。另外,一旦指定了环境,便可在代 码的数字中使用逗号:

C标准有两个本地化设置:"C"和""(空字符串)。
默认情况下,程序使 用"C"本地化设置,基本上符合美国的用法习惯。而""本地化设置可以替换 当前系统中使用的本地语言环境。原则上,这与"C"本地化设置相同。
事实上,大部分操作系统(如UNIX、Linux和Windows)都提供本地化设置选项 列表,只不过它们提供的列表可能不同。
4.5关键概念
char类型表示单个字符
字符串表示字符序列
字符常量是 一种字符串形式,即用双引号把字符括起来:"Good luck, my friend"。可以 把字符串储存在字符数组(由内存中相邻的字节组成中。
字符串,无论是 表示成字符常量还是储存在字符数组中,都以一个叫做空字符的隐藏字符结 尾。
#define 定义数值常量
const 关键字声明的变量为 只读变量
在程序中使用符号常量(明示常量),提高了程序的可读性和可 维护性。
C 语言的标准输入函数(scanf())和标准输出函数(printf())都使用一 种系统。在该系统中,第1个参数中的转换说明必须与后续参数中的值相匹 配。
确保转换说明的数量和类型与函数的其余参数相匹配。对于scanf(), 一定要记得在变量名前加上地址运算符(&)。
空白字符(制表符、空格和换行符)在 scanf()处理输入时起着至关重要 的作用。除了%c 模式(读取下一个字符),scanf()在读取输入时会跳过非 空白字符前的所有空白字符,然后一直读取字符,直至遇到空白字符或与正 在读取字符不匹配的字符。
4.6本章小结
字符串是一系列被视为一个处理单元的字符。在C语言中,字符串是以 空字符(ASCII码是0)结尾的一系列字符。可以把字符串储存在字符数组 中。数组是一系列同类型的项或元素。
声明:char name【30】(数字是元素长度)(数字要确保有足够多的元素长度来储存整个字符串(包括空字符))。
字符串常量是用双引号括起来的字符序列,如:"This is an example of a string"。
scanf()函数(声明在string.h头文件中)可用于获得字符串的长度(末尾 的空字符不计算在内)。scanf()函数中的转换说明是%s时,可读取一个单 词。
C预处理器为预处理器指令(以#符号开始)查找源代码程序,并在开始编译程序之前处理它们。
处理器根据#include指令把另一个文件中的内容 添加到该指令所在的位置。
define指令可以创建明示常量(符号常量),即代表常量的符号。
limits.h和float.h头文件用#define定义了一组表示整型和浮点型不同属性的符号常量。
另外,还可以使用const限定符创建定义后就不 能修改的变量。
printf()和scanf()函数对输入和输出提供多种支持。
两个函数都使用格式 字符串,其中包含的转换说明表明待读取或待打印数据项的数量和类型。
另外,可以使用转换说明控制输出的外观:字段宽度、小数位和字段内的布局。

浙公网安备 33010602011771号