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() 在输入行留下一个换行符,会导致一些问题;

posted @ 2023-02-21 12:04  星光映梦  阅读(102)  评论(0编辑  收藏  举报