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转换说明得到没有意义的值,下面再例举转换不匹配会导致的问题:

#include <stdio.h>
#define NUM1 336
#define NUM2 65618

int main(void){
short num = NUM1;
short neg_num = -NUM1;
printf("%hd %hu\n", num, num);
printf("%hd %hu\n", neg_num, neg_num);
printf("%d %c\n", num, num);
printf("%d %hu %c\n", NUM2, NUM2, NUM2);
return 0;
}

在我的计算机系统上,通过运行上面的代码,可以得到下面的结果:

336 336
-336 65200
336 P
65618 82 R

第一个输出没有任何问题。而对于第二行%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()函数语句过长时,我们能将一条语句写成多行,只需在不同部分之间输入空白即可。我们要三种方法(前面我们介绍过一种,重复一遍):

printf("这是滕王阁序中的一个句子:");
printf("落霞与孤鹜齐飞,秋水共长天一色\n"); //1.使用多个printf语句

printf("落霞与孤鹜齐飞,\
秋水共长天一色\n"); //2.用反斜杠\来断行,使得光标移至下一行。所以下一行的句子必须要从本行最开始打印,比如缩进五个空格,就会打印五个空格

printf("落霞与孤鹜齐飞"
"秋水共长天一色\n"); //3.使用多个双引号"",C编译器会把多个字符串看作时一个字符串

5.scanf()函数

C库包含了多个输入函数,scanf()是更为通用的一个,因为它可以读取不同格式的数据。从键盘输入的都是文本,因为键盘只能生成文本字符:字母、数字和标点符号,如果要输入整数2014,就要键入字符2、0、1、4,如果要将其存储为数值而不是字符串,程序就必须字符依次转化成数值,这就是scanf()函数要做的。scanf()函数把输入的字符串转换成整数、浮点数、字符或字符串,而printf()函数正好与它相反。实际上,C语言中的其它输入函数,如getchar()和fgets()这,两个函数更适合处理一些特殊的情况,如读取单个字符或包含空格的字符串。

与printf()类似,也使用格式字符串和参数列表。而两个函数主要的区别在于参数列表中,printf()函数使用变量、常量和表达式,而scanf()函数使用只指向变量的指针(指针是C/C++区别于其它编程语言的一个特点,也是C语言中的一个重要知识,后面介绍)。这里只需要记住如何使用即可:scanf()在输入基本数据类型的值时,在变量名前加上&;在输入字符数组时,不用加&。

int num;
char ch;
char str[10];
scanf("%d%c%s", &num, &ch, str); //输入时一定要记得格式统一!格式统一!格式统一!
printf("num:%d,ch:%c,str:%s", num, ch, str);

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()中,如果你不想预先指定字段宽度,而是由通过程序来指定,那么可以用*修饰符来代替字段宽度。

const int c_width = 8;
const int c_precision = 3;
printf("%*d", c_width, 666);
printf("%*.*f", c_width, c_precision, 666.6);

而在scanf()中的*用法与此不同。把*放在%和转换字符之间,会跳过相应的输入项。在程序需要读取文件中特定列的内容时,这项功能很有用。

int num;
scanf("%*d %*d %d", &num);