05. 数据输入输出
一、数据输入输出
在 C 语言中,所有的数据输入输出都是由库函数完成的。在 C 语言中,使用标准输入输出库函数需要使用 “stdio.h” 头文件,因此在程序开头应该有 #include "stdio.h"
语句。
二、格式输出函数
2.1、printf()函数的使用
在 C 语言中,可以使用 printf() 函数输出指定类型的数据,其定义格式如下:
int printf (const char *__format, ...);
printf() 函数的作用是向终端(输出设备)输出若干任意类型的数据。printf() 语句把输出发送到一个叫做 缓冲区(buffer)的中间存储区域,然后缓冲区的内容再不断发送到屏幕上。C 标准明确规定了何时把缓冲区中的发送到屏幕:当缓冲区满、遇到换行字符或组要输入的时候(从缓冲区把数据发送到屏幕或文件被称为 刷新缓冲区)。
【1】、格式字符串
格式字符串 是用双引号括起来的字符串,其中包括 转换说明 (conversion specification)和 字面字符 两种。
- 转换说明 用来进行格式说明,作用是将输出的数据转换为指定的格式输出。转换说明是以 "%" 字符开头的。
- 字面字符 是需要原样输出的字符,其中包括双引号内的逗号、空格和换行符。
【2】、输出列表
输出列表 中列出的是要进行输出的一些数据,可以是变量或表达式。
由于 printf() 是函数,“格式控制” 和 “输出列表” 这两个位置都是函数的参数,因此 printf() 函数的一般形式也可以表示为:
printf(格式字符串, 输出列表);
函数中的每一个参数按照给定的格式和顺序依次输出。
printf() 函数的 转换说明:
转换说明 | 输出 |
---|---|
%a | 浮点数、十六进制数 和 p 计数法(C99/C11),a 是指字母以小写输出 |
%A | 浮点数、十六进制数 和 p 计数法(C99/C11),A 是指字母以大写输出 |
%c | 单个字符 |
%d | 有符号十进制整数 |
%e | 浮点数,e 计数法,e 是指字母以小写输出 |
%E | 浮点数,e 计数法,E 是指字母以大写输出 |
%f | 浮点数,十进制计数法 |
%g | 根据值不同,自动选择 %f 或者 %e,%e 格式用于指数小于 -4 或者大于等于精度时 |
%G | 根据值不同,自动选择 %f 或者 %E,%E 格式用于指数小于 -4 或者大于等于精度时 |
%i | 有符号十进制整数(与 %d 相同) |
%o | 无符号 8 进制整数 |
%p | 以 16 进制形式输出指针 |
%s | 字符串 |
%u | 无符号十进制整数 |
%x | 无符号十六进制整数,x 是指字母以小写输出 |
%X | 无符号十六进制整数,X 是指字母以大写输出 |
%% | 打印一个百分号 |
/* 使用转换说明 */
#include <stdio.h>
#define PI 3.141593
int main(void)
{
int number = 7;
float pies = 12.75;
int cost = 7800;
printf("The %d contestants ate %f berry pies.\n", number, pies);
printf("The value of pi is %f.\n",PI);
printf("Farewell ! thou art too dear for my possessing,\n");
printf("%c%d\n",'$', 2 * cost);
return 0;
}
格式字符串中的转换说明一定要与后面的每个项相匹配;
2.2、printf()函数的转换说明修饰符
在 % 和 转换字符 之间插入修饰符可修饰基本的转换说明。
修饰符 | 含义 |
---|---|
- | 待打印项左对齐。即,从字段的左侧开始打印该项; |
+ | 有符号值若为正,则在值前面显示加号; 若为负,则在值前面显示减号; |
空格 | 有符号值若为正,则在值前面显示前导空格(不显示任何字符); 若为负,则在值前面显示减号标记覆盖一个空格; |
# | 把结果转换为另一种形式; 如果是 %o 格式,则以 0 开始; 如果是 %x 或 %X 格式,则以 0x 或 0X 开始; 对于所有浮点格式,# 保证了即使后面没有任何数字,也打印一个小数点符号; 对于 %g 或 %G 格式,# 防止结果后面的 0 被删除; |
0 | 对于数值格式,用前导 0 代替空格填充字段宽度; 对于整数格式,如果出现 - 标记 或 指定精度,则忽略该标记; |
数字 | 最小字段宽度,如果该字段不能容纳待打印的数字或字符串,系统会使用更宽的字段; |
.数字 | 精度; 对于 %e、%E 和 %f 转换,表示小数点右边数字的位数; 对于 %g 和 %G 转换,表示有效数字最大位数; 对于 %s 转换,表达待打印字符的最大数量; 对于 整型转换,表示待打印数字的最小位数; 如果必要,使用前导 0 来达到这个位数; 只使用 . 表示其后跟随一个 0,所以 %.f 和 %.0f相同 |
h | 和整型转换说明一起使用,表示 short int 或 unsigned short int 类型的值 |
hh | 和整型转换说明一起使用,表示 signed char 或 unsigned char 类型的值 |
j | 和整型转换说明一起使用,表示 intmax_t 或 uintmax_t 类型的值。这些类型定义在 stdint.h中; |
l | 和整型转换说明一起使用,表示 long int 或 unsigned long int 类型的值 和浮点型转换说明一起使用,表示 long double 类型的值 |
ll | 和整型转换说明符一起使用,表示 long long int 或 unsigned long long int 类型的值(C99) |
t | 和整型转换说明一起使用,表示 ptrdiff_t 类型的值。prtdiff_t 是两个指针差值的类型(C99) |
z | 和整型转换说明一起使用给,表示 size_t 类型的值。size_t 是 sizeof 返回的类型(C99) |
/* 字段宽度 */
#include <stdio.h>
#define PAGES 959
int main(void)
{
printf("*%d*\n", PAGES);
printf("*%2d*\n", PAGES);
printf("*%10d*\n", PAGES);
printf("*%-10d*\n", PAGES);
return 0;
}
第 1 个转换说明 %d 不带任何修饰符,其对应的输出结果与整数字段宽度的转换说明的输出结果相同。在默认情况下,没有任何修饰符的转换说明,就是这样的打印结果。
第 2 个转换说明 %2d,其对应的输出结果应该是 2 字段宽度。因为待打印的整数有 3 位数字,所以字段宽度自动扩大以符合整数的长度。
第 3 个转换说明是 %10d,其对应的输出结果有 10 个空格宽度,其实在两个星号之间有 7 个空格和 3 位数字,并且数字位于字段的右侧。
最后一个转换说明是 %-10d,其对应的输出结果同样是 10 个空格长度,- 标记说明打印的数字位位于字段的左侧。
/* 浮点型修饰符的组合 */
#include <stdio.h>
int main(void)
{
const double RENT = 3852.99;
printf("*%f*\n", RENT);
printf("*%e*\n", RENT);
printf("*%4.2f*\n", RENT);
printf("*%3.1f*\n", RENT);
printf("*%10.3f*\n", RENT);
printf("*%10.3E*\n", RENT);
printf("*%+4.2f*\n", RENT);
printf("*%010.2f*\n", RENT);
return 0;
}
第 1 个格式说明是 %f。这种情况下,字段宽度和小数点后面的位数均由系统默认设置,即字段宽度是容纳待打印数字所需的位数和小数点打印 6 位数字。
第 2 个格式说明是 %e。默认情况下,编译器在小数点的左侧打印一个数字,在小数点右侧打印 6 个数字。
第 3 个到第 4 个格式说明指定小数点右侧显示的位数,会对结果进行四舍五入。
第 7 个格式说明中包含了 + 标记,这使得打印的值前面多了一个代数符号(+)。
第 8 个格式说明中的最前面的 0 标记使得打印的值前面以 0 填充以满足字段要求。注意,转换说明 %010.2f 的第 1 个 0 是标记,句点(.)之前、标记之后的数字是指定的字段宽度。
/* 格式标记 */
#include <stdio.h>
int main(void)
{
printf("%x %X %#x\n", 31, 31, 31);
printf("**%d**% d**% d**\n",42, 42, -42);
printf("**%5d**%5.3d**%05d**%05.3d\n",6, 6, 6, 6);
return 0;
}
第 1 行输出中,1f 是十六进制数,等于十进制 31。第 1 行 printf() 语句汇总给,根据 %x 打印处出 1f,%X 打印出 1F,%#x 打印出 0x1f。
第 2 行输出,在转换说明中用空格在输出的正值前面生成前导空格,负值前面不产生前导空格。
第 3 行输出,在整型格式中使用精度生成足够的前导 0 以满足最小位数的要求。然而,使用 0 标记会是编译器用前导 0 填充整个字段宽度。最后,如果 0 标记和精度一起出现,0 标记会被忽略。
/* 字符串格式 */
#include <stdio.h>
#define BLURB "Authentic imitation!"
int main(void)
{
printf("[%2s]\n",BLURB);
printf("[%24s]\n",BLURB);
printf("[%24.5s]\n",BLURB);
printf("[%-24.5s]\n",BLURB);
return 0;
}
虽然第 1 个转换说明是 %2s,但是字段被扩大为可容纳字符串的所有字符。还需注意,精度限制了待打印字符的个数。.5 告诉 printf() 只打印 5 个字符。另外,- 标记使得文本左对齐输出。
如果你不想预先指定字段宽度,希望通过程序来指定,那么可以用 * 修饰符代替字段宽度。但还是要用一个参数告诉函数,字段宽度应该是多少。也就是说,如果转换说明是 %*d,那么参数列表中应包含 * 和 d 对应的值。
#include <stdio.h>
int main(void)
{
unsigned int width,precision;
int number = 256;
double weight = 242.5;
printf("Enter a field width:\n");
scanf("%d", &width);
printf("The number is : %*d:\n", width, number);
printf("How enter a width and precision:\n");
scanf("%d %d", & width, &precision);
printf("Weight = %*.*f\n", width, precision, weight);
printf("Done !\n");
return 0;
}
3.3、转换说明的意义
转化说明把以而简直格式存储在计算机中的值转换成一系列字符(字符串)以便显示。例如,数字 76 在计算机内部的存储格式是 二进制 01001100。%d 格式说明将其转换成字符 7 和 6,并显示为 76;%x 格式说明把相同的值(01001100)转换成十六进制计数法 4c;%c 转换说明把 01001100 转换成字符 L。
转换(conversion)可能会让人误以为原始值被替换成转换后的值。实际上,转换说明是翻译说明,%d 的意思是 “把给定的值翻译成十进制整数文本并打印出来”。
3.4、printf()函数的返回值
printf() 函数有一个返回值,它返回大打印字符的个数。如果输出有错误,printf() 函数返回一个负值。
#include <stdio.h>
int main(void)
{
int bph2o = 212;
int rv;
rv = printf("%d F is water's boiling point.\n", bph2o);
printf("the printf() function printed %d characters.\n", rv);
return 0;
}
三、格式化输入函数
3.1、scanf()函数的使用
在 C 语言中,格式输入使用 scanf() 函数,scanf() 函数通过 % 转义的方式可以获取用户通过标准输入设备输入的数据。用户从键盘输入的是文本数据,scanf() 函数把输入的字符串转换成指定的格式,并将转后成指定格式后的数据保存在指定变量中。scanf() 函数的一般定义如下:
int scanf(const char *__format, ...);
通过 scanf() 函数的一般格式可以看出,参数位置的格式字符串与 printf() 函数相同。而在地址列表中,此处应该给出用来接收数据变量的地址。
- 如果用 scanf() 读取基本变量类型的值,在变量名前加上一个 &;
- 如果用 scanf() 把字符读入字符数组中,不要使用 &。
转换说明 | 含义 |
---|---|
%c | 把输入解释成字符 |
%d | 把输入解释成有符号十进制整数 |
%e、%f、%g、%a | 把输入解释成浮点型(C99标准 新增了 %a) |
%E、%F、%G、%A | 把输入解释成浮点型(C99标准 新增了 %A) |
%i | 把输入解释成有符号十进制整数 |
%o | 把输入解释成有符号八进制整数 |
%p | 把输入解释成指针(地址) |
%s | 把输入解释成字符串 从第 1 个非空白字符开始,到下一个空白字符之前的所有字符都是输入 |
%u | 把输入解释成无符号十进制整数 |
%x、%X | 把输入解释成有符号十六进制整数 |
#include <stdio.h>
int main(void)
{
int age;
float assets;
char pet[30];
printf("Enter you age, assets, and favorite pet.\n");
scanf("%d %f", &age, &assets);
scanf("%s", pet);
printf("%d $%.2f %s\n", age, assets, pet);
return 0;
}
3.2、scanf()函数的转换说明修饰符
修饰符 | 含义 |
---|---|
* | 抑制赋值 |
数字 | 最大字段宽度。输入达到最大字段宽度出,或第 1 次遇到空白字符时停止 |
hh | 把整数作为 signed char 或 unsigned char 类型读取 |
ll | 把整数作为 long long 或 unsigned long long 类型读取(C99) |
h | "%hd" 和 "%hi" 表明把对应的值储存为 short int 类型 "%ho"、"%hx" 和 "%hu" 表明把对应的值储存为 unsigned short int 类型 |
l | "%ld" 和 "%li" 表明把对应的值储存为 long 类型 "%lo"、"%lx" 和 "%lu" 表明把对应的值储存为 unsigned long 类型 "%le"、"%lf" 和 "%lg" 表明把对应的值储存为 double 类型 |
L | "%Le"、"%Lf" 和 "%Lg" 表明把对应的值储存为 long double 类型 |
j | 在整型转换说明后面时,表明使用 intmax_t 或 uintmax_t 类型(C99) |
t | 在整型转化说明后面时,表示使用表示两个指针差值的类型(C99) |
z | 在整型转换说明后面时,表明使用 sizeof 的返回类型(C99) |
#include <stdio.h>
static double pi = 3.14;
int main(void)
{
int year = 0, month = 0, day = 0;
printf("请输入年月日,格式yyyyddmm\n");
scanf("%4d%2d%2d",&year,&month,&day);
printf("你输入的时间为:\n");
printf("year: %d, month: %d, day: %d\n",year,month,day);
return 0;
}
scanf() 函数中的 * 放在 % 和转换字符之间,会使得 scanf() 跳过相应的输出项。
#include <stdio.h>
int main(void)
{
int n;
printf("Please enter three integers:\n");
scanf("%*d %*d %d", &n);
printf("The last integer was %d\n", n);
return 0;
}
3.3、scanf()函数返回值
scanf() 函数返回成功读取的项数。如果没有读取任何项,且需要读取一个数字而用户却输入一个非数值字符串,scanf() 便返回 0。当 scanf() 检测到“文件结尾”时,会返回 EOF(EOF 是 stdio.h 中定义的特殊值,通常用 #define 指令将 EOF 定义为 -1)。
3.4、scanf()函数使用注意事项
在输入多个字符时,若格式控制串中没有非格式字符作为输入数据之间的间隔,则可以用 空格、TAB、回车 作为间隔。C编译器 遇到 空格、TAB、回车 或 非法数据时,即认为该数据结束;
#include <stdio.h>
int main(void)
{
int a,b;
printf("请输入两个数字,以空格分隔:");
// &运算符,表示取地址运算符,多个占位符默认是以空格分隔,
scanf("%d%d",&a,&b);
printf("你输入的两个数字是:%d %d\n",a,b);
printf("请在输入两个数字,以英文逗号分隔:");
scanf("%d,%d",&a,&b);
printf("你输入的两个数字是:%d %d\n",a,b);
printf("请输入一个3位整数:");
scanf("%3d",&a);
return 0;
}
【1】、从 scanf() 角度看输入
假设 scanf() 根据一个 %的格式转换说明读取一个整数。scanf() 函数每次读取一个字符,跳过所有的空白字符,直至遇到第 1 个非空白字符才开始读取。因为要读去整数,所以 scanf() 希望发现一个数字字符或者一个符号(+ 或 -)。如果找到一个数字或符号,它便保存该字符,并读取下一个字符。如果下一个字符是数字,它便保存该数字并读取下一个字符。scanf() 不断地读取或把保存字符,直至遇到非数字字符。如果遇到一个非数字字符,它便认为读到了整数的末尾。然后,scanf() 把非数字字符放回输入。这意味着程序在下一次读取输入时,首先读到的时上一次读取丢弃的非数字字符。最后,scanf() 计算以读取(可能还有符号)相应的数字,并将计算后的值放入指定变量中。
如果使用字段宽度,scanf() 会在字段结尾或第 1 个空白字符出停止读取(满足两个条件之一便停止)。
如果第 1 个非空白字符是字母而不是数字,scanf() 将停在那里,并将字母放回输入中,不会把值赋给指定变量。程序在下一次读取时,首先读到的是那个字母。如果程序只使用 %d 转换说明,scanf() 就直至无法越过那么字母读下一个字符。另外,如果使用多个转换说明的 scanf(),C 规定在第 1 个出错处停止读取输入。
用其它数值匹配的转换说明读取输入和用 %d 的情况相同。
如果使用 %s 格式说明,scanf() 会读取除空白以外的所有字符,scanf() 跳过空白开始读取第 1 个非空白字符,并保存非空白字符直至再次遇到空白。这意味着 scanf() 根据 %s 转换说明会读取一个单词,即不包含空白字符和字符串。如果使用字段宽度,scanf() 在字段末尾或第 1 个空白字符处停止读取。无法利用字段宽度让只有一个 %s 的 scanf() 读取多个单词。当 scanf() 把字符串放在指定数组中,它会在字符序列的末尾加上 '\0',让数组中的内容成为一个 C 字符串。
【2】、格式字符串的普通字符
scanf() 函数允许把普通字符放在格式字符串中。除空格字符外的普通字符必须与输入字符串严格匹配。例如,假设在两个转换说明中添加一个逗号:
scanf("%d,%d", &n, &m);
scanf() 函数将其解释为:用户将输入一个数字、一个逗号,然后再输入一个数字。也就是或,用户必须项下面这样进行输入两个整数:
11,22
由于格式字符串中,%d 后面紧跟逗号,所以必须在输入 88 后在输入一个逗号。但是,由于 scanf() 会跳过整数前面的空白,所以下链的两种输入方式都可以:
11, 22
和
11,
22
格式字符串中的空白意味着跳过下一个输入项前面的所有空白。例如,对于下面的语句:
scanf("%d ,%d", &n, &m);
以下的输入格式都没有问题:
11,22
11 ,22
11 , 22
除了 %c,其它转换说明都会自动跳过待输入值前面所有的空白。因此,scanf("%d%d", &n, &m) 与 scanf("%d %d", &n, &m) 的行为相同。对于 %c,在格式字符串中添加一个空格字符会有所不同。例如,如果在格式字符串中把空格放在 %c 的前面,scanf() 便会跳过空格,从第 1 个非空白字符开始读取。也就是说,scanf("%c", &ch) 从输入中的第 1 个字符开始读取,而 scanf(" %c", &ch) 从第 1 个非空白字符开始读取。
四、字符数据输出
字符数据输出就是将字符显示出来。 在 C语言 中,使用 putchar() 函数输出字符数据,它的作用是向显示设备输出一个字符。putchar() 函数可以输出字符、字符变量、数字(0~127)、转义字符。其语法格式如下:
int putchar(int ch);
其中的 参数 ch 是要进行输出的字符,可以是 字符型变量 或 整型变量,也可以是常量。
#include <stdio.h>
int main(void)
{
char ch = 'a';
putchar(a);
putchar('\t');
putchar('A');
putchar('\n');
putchar(97);
return 0;
}
五、字符数据输入
在 C语言 中,可以使用 getchar() 函数是从标准输入设备读取一个字符,它的语法格式如下:
int getchar();
#include <stdio.h>
int main(void)
{
char ch;
printf("请输入一个字符:");
ch = getchar();
printf("你输入的字符是:%c",ch);
return 0;
}
六、字符串输出函数
字符串输出就是将字符串输出到控制台上。在 C语言 中,字符串输入使用的是 puts() 函数,它的作用是输出字符串并显示在屏幕上。其语法格式如下:
int puts(char *str);
其中,形式参数 str 是字符指针类型,可以用来接收要输出的字符串。puts() 函数会在字符串中判断 '\0' 结束符。遇到结束符时,后面的字符不再输出,并且自动换行。
#include <stdio.h>
int main(void)
{
puts("hello world!");
return 0;
}
七、字符串输入函数
在 C 语言中,字符串输入使用的是 gets() 函数,它的作用是将读取的字符串保存在形式参数中,读取过程直到出现新的一行为止。其中新的一行的换行符将会自动转换为字符串中的空终止符 '\0'。它的语法格式如下:
char *gets(char *str);
其中 str 字符指针变量为形式参数,从终端读取到的字符串将会保存在这个变量中。
gets() 函数无法检查实参数组是否能装下输入行。这是因为数组名会转换成该数组首元素的地址。因此,gets() 函数只知道数组的开始处,并不知道数组中有多少个元素。如果输入的字符串过长,会导致缓冲区溢出(buffer overflow),即多余的字符超出了指定的目标空间。如果这些多余的字符只是占用了尚未使用的内存,就不会立即出问题;如果它们擦写了程序中的其它数据,会导致程序异常终止,或者还有其它情况。
#include <stdio.h>
int main(void)
{
char str[20];
puts("请输入一个字符串:");
gets(str);
puts("你输入的字符串是:");
puts(str);
return 0;
}
C11 标准委员会采取了更强硬的态度,直接从标准中废除了 gets() 函数;
八、混合输入
scanf() 函数最好不要与 getchar() 函数 和 puts() 函数混用,这是因为 getchar() 函数和 puts() 函数在读取缓冲区中的内容时,会将 空格、制表符、换行符 读入,而 scanf() 函数在读取缓冲区内容时,会跳过 空格、制表符、换行符。
#include <stdio.h>
int main(void)
{
int num;
char ch;
char str[10];
printf("请输入一个整数:\n");
scanf("%d", &num);
puts("请输入一个字符串:");
gets(str);
return 0;
}
混合使用 getchar() 和 scanf() 时,如果在调用 getchar() 之前,scanf() 在输入行留下一个换行符,会导致一些问题;