printf()函数和scanf()函数能让用户可以与程序交流,它们是输入/输出函数,或简称I/O函数。最初,C把输入/输出的实现留给了编译器的开发者,这样可以针对特殊的机器更好地匹配输入输出,不过在后面地发展中,为了兼容性的考虑,C90和C99标准规定了这些函数的标准版本。虽然,printf()是输出函数,scanf()函数是输入函数,但是它们的工作原理几乎相同,且两个函数都使用格式字符串和参数列表,至于这两个函数的重要性不言而喻。
1.printf()函数
请求printf()函数打印数据的指令要与待打印数据的类型相匹配。例如,打印整数使用%d,打印浮点型数使用%f,这些符号成为转换说明,它们指定了如何把数据转换成可显示的形式。下表是转换说明及其打印的输出结果表:
%c | 一个字符 |
%d | 有符号十进制整数 |
%u | 无符号十进制整数 |
%i | 有符号十进制整数 |
%o | 无符号八进制整数(带前缀%#o) |
%x或%X | 无符号十六进制整数(带前缀%#x或%#X) |
%e或%E | 浮点数、e计数法(如:3.141592e+001) |
%a或%A | 浮点数、十六进制数和p进制法(如:0x5.b6p12) |
%g或%G | 根据值的不同,系统会自动选择%f或%e,并选择它们中最短的一个输出,并且不会输出无意义的0 |
%f | 浮点数、十进制数(如:31.405926) |
%s | 字符串 |
%p | 指针 |
%% | 打印一个百分号% |
printf()函数的格式:printf(格式字符串, 待打印项1, ……)
,待打印项都是要打印的项,它们可以是变量、常量,甚至是在打印之前先要计算的表达式,这说明printf()函数使用的是值,无论是变量、常量还是表达式的值;同时,格式字符串应包含每个待打印项对应的转换说明,所以格式化字符串包含两种形式不同的信息:实际要打印的字符,转换说明。调用printf()函数时,程序会把传入的值放入被称为栈的内存区域。
2.printf()的转换说明修饰符
在%和转换字符之间插入修饰符可修饰基本的转换说明,下表是转换说明的修饰符表(有些字符只有支持C99的编译器才可使用):
修饰符 | 含义 |
标记 | 下表将给出5中标记 |
数字 | 最小字段宽度 |
.数字 | 表示精度。 对于%e、%E和%f转换,表示小数点右边数字的位数; 对于%g和%G转换,表示有效数字最大位数; 对于%s转换,表示待打印字符的最大数量; 对于整型转换,表示待打印数字的最小位数 |
h | 和整型转换说明一起使用,表示short int或unsigned short int类型的值(如:%6.4hd、%hu、%hx) |
hh | 和整型转换说明一起使用,表示signed char或unsigned char类型的值(如:%6.4hhd、%hhu、%hhx) |
j | 和整型转换说明一起使用,表示intmax_t或uintmax_类型的值。这些类型定义在stdint.h中 |
l | 和整型转换说明一起使用,表示long int或unsigned long int类型的值(如:%ld) |
ll | 和整型转换说明一起使用,表示long long int或unsigned long long int类型的值(如:%lld) |
L | 和浮点转换说明一起使用,表示long double类型的值(如:%Lf) |
t(C99) | 和整型转换说明一起使用,表示ptrdiff_t类型的值。ptrdiff_t是两个指针差值的类型(如:%td) |
z(C99) | 和整型转换说明一起使用,表示size_t类型的值。size_t是sizeof运算符返回的类型(如:%zd) |
标记 | 含义 |
- | 待打印项左对齐。即从本行的最左侧开始打印 |
+ | 有符号值若为正,则在值前面显示加号;若为负,则在之前面显示减号 |
空格 | 有符号值若为正,则在值前面显示空格;若为负,则在之前面显示减号 |
# | 把结果转换成另一种形式。 对于%o格式,则以0开始,对于%x或%X格式,则以0x或0X开始; 对于所有浮点格式,#保证了即使后面没有任何数字也打印一个小数点; 对于%g或%G格式,#防止后面的0被删除 |
0 | 对于数值格式,用前导0代替空格填充字段宽度; 对于整数格式,如果出现-标记或指定精度,则忽略0标记 |
大家可以用printf("%%5d:%5d,%%7.3d:%7.3d,%%07.3d:%07.3d", 6, 6, 6);
试试输出结果。
3.转换说明的意义
转换说明把以二进制格式存储在计算机中的值转换成一系列字符(字符串)以便于显示。例如,数字76在计算机内部的存储格式是二进制数01001100,%d转换说明将其转换成字符7和字符6,并显示为76;%x转换说明将其转换成十六进制计数法法4c;%c转换说明将其转换成字符L。
前面有提到过转换说明应该与待打印值的类型相匹配。例如,如果要打印一个整数,可以使用%d、%i、%u等等,如果要打印一个浮点数,可以使用%f、%e、%a或%g等等,匹配非常重要,一定要熟稔于心,前面的内容举过如果一个浮点型的数用%d转换说明得到没有意义的值,下面再例举转换不匹配会导致的问题:
在我的计算机系统上,通过运行上面的代码,可以得到下面的结果:
第一个输出没有任何问题。而对于第二行%hu转换说明得到了65200,这是由于short int在系统中的表示方式导致的,首先我们知道short int占两个字节,其次,系统对于有符号整数使用二进制补码来表示,了解二进制补码表示有符号整数的能够知道,对于正数补码与原码表示相同,即数字0~32767代表它们本身,而数字32768~65535则表示负数,且补码用多余的0(全1)表示65536,其中65535表示-1,65534表示-2,以此类推,我们可以知道-336正好是65200(这里额外补充一点,32768表示绝对值最大的负数,它本身的二进制实际上表示的是-0,为了不浪费多余的表述,将-0看作是最小的数,因此负数的表示比整数的表示多一个)。第三行中%d则表示将占用两个字节的short int扩展为可能占用字节更多的int类型,这也涉及到计算机组成原理中的内容,对于%c得到字符P,这是由于两个字节short被截断为了只占用一个字节的char,相当于模256,看下面的补充:
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
short类型的336
0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
short类型的336被截断为char类型
通过上面我们可以知道short int被截断为char类型后,得到的码值为80,即字符P。第四行的65618,我们知道这个数已经超过了short int表示的最大整数32767,因此,系统将其存储为占用四个字节的int类型(假设系统使用4B的int),我们通过%d转换说明将其输出,同理,当我们用%hd转换说明,其被截断为占用两个字节的short int类型,这里得到的数值是82,再用%c转换说明将其截断为一个字节的char类型,得到的码值为82,对应字符R。
4.printf()的返回值
printf()函数也有一个返回值,它返回打印字符的个数。如果输出有错误,printf()函数会返回一个负值;其次,printf()函数会计算所有的字符数,包括空格和不可打印的字符,如\a、\n、\t等。
当printf()函数语句过长时,我们能将一条语句写成多行,只需在不同部分之间输入空白即可。我们要三种方法(前面我们介绍过一种,重复一遍):
5.scanf()函数
C库包含了多个输入函数,scanf()是更为通用的一个,因为它可以读取不同格式的数据。从键盘输入的都是文本,因为键盘只能生成文本字符:字母、数字和标点符号,如果要输入整数2014,就要键入字符2、0、1、4,如果要将其存储为数值而不是字符串,程序就必须字符依次转化成数值,这就是scanf()函数要做的。scanf()函数把输入的字符串转换成整数、浮点数、字符或字符串,而printf()函数正好与它相反。实际上,C语言中的其它输入函数,如getchar()和fgets()这,两个函数更适合处理一些特殊的情况,如读取单个字符或包含空格的字符串。
与printf()类似,也使用格式字符串和参数列表。而两个函数主要的区别在于参数列表中,printf()函数使用变量、常量和表达式,而scanf()函数使用只指向变量的指针(指针是C/C++区别于其它编程语言的一个特点,也是C语言中的一个重要知识,后面介绍)。这里只需要记住如何使用即可:scanf()在输入基本数据类型的值时,在变量名前加上&;在输入字符数组时,不用加&。
scanf()函数使用空白(换行符、制表符和空格等)把输入分成多个字段,在依次把转换说明和字段匹配时跳过空白。唯一例外的是%c转换说明,根据%c,scanf()会读取每个字符,包括空白。另外还需注意的是,scanf()函数允许把普通字符放在格式字符串中,除空格字符外的普通字符,必须与输入字符串严格匹配。
scanf()函数所用的转换说明与printf()函数几乎相同,主要的区别是,对于float类型和double类型,printf()都使用%f,%e,%E,%g和%G转换说明,而scanf()将它们只作为float类型的输入,对于double类型要使用l修饰符。下表是scanf()函数的转换说明:
转换说明 | 含义 |
%c | 把输入解释成字符 |
%s | 把输入解释成字符串。从第一个非空字符开始,到下一个空白字符之前的所有字符都是输入 |
%d | 把输入解释成有符号十进制整数 |
%u | 把输入解释成无符号十进制整数 |
%i | 把输入解释成有符号十进制整数 |
%o | 把输入解释成有符号八进制整数 |
%x、%X | 把输入解释成有符号十六进制整数 |
%e、%f、%g、%a | 把输入解释成浮点数(C99新增%a) |
%E、%F、%G、%A | 把输入解释成浮点数(C99新增%A) |
%p | 把输入解释成指针 |
6.scanf()的转换说明修饰符
转换说明 | 含义 |
* | 抑制赋值(如:%*d),见8 |
数字 | 最大字段宽度。输入达到最大字段宽度处,或第1次遇到空白字符时停止 |
hh | 把整数作为signed char或unsigned char类型读取 |
ll | 把整数作为long long或unsigned long long类型读取 |
h、l或L | 在e、f和g前面使用L,表明把对应的值存储为long double类型; %hd和%hi表明把对应的值存储为short int类型; %hu、%ho和%hx表明把对应的值存储为unsigned short int类型; %ld和%li表明把对应的值存储为long类型; %lu、%lo和%lx表明把对应的值存储为unsigned long类型 |
j | 和整型转换说明一起使用,表明使用intmax_t或uintmax_t类型(C99) |
z | 和整型转换说明一起使用,表明使用sizeof的返回类型(C99) |
t | 和整型转换说明一起使用,表明使用两个指针类型差值的类型(C99) |
如果没有修饰符,d、i、o和x表明对应的值被存储为int类型,f和g表明把对应的值存储为float类型。如你所见,使用转换说明比较复杂,记用结合,熟能生巧。
7.scanf()的返回值
scanf()函数返回成功读取的项数。如果没有读取任何数,scanf()函数便返回0,此时即读取错误;当scanf()检测到”文件结尾”时,会返回EOF(EOF是stdio.h中定义的特殊值,通常用#define指令把EOF定义为-1)
8.*修饰符
printf()和scanf()都可以用*修饰符来修改转换说明的含义,只是在这两个函数中的用法不一样。在printf()中,如果你不想预先指定字段宽度,而是由通过程序来指定,那么可以用*修饰符来代替字段宽度。
而在scanf()中的*用法与此不同。把*放在%和转换字符之间,会跳过相应的输入项。在程序需要读取文件中特定列的内容时,这项功能很有用。