Bootstrap

44 C 语言输入输出流、scanf 与 printf 函数详解、清除输入缓冲区

目录

1 文件基本介绍

1.1 文件的主要功能

1.2 输入输出流

2 C 语言中的输入与输出

2.1 输入

2.2 输出

2.3 标准文件与文件指针

3 scanf() 函数详解

3.1 功能描述

3.2 函数原型

3.3 常用格式说明符

3.4 返回值

3.5 注意事项

3.5.1 处理空白字符

3.5.2 防止缓冲区溢出

3.5.3 处理输入失败

3.5.4 清除输入缓冲区

3.5.5 处理多个输入项

3.5.6 读取字符串的替代函数

3.5.7 读取浮点数精度问题

4 printf() 函数详解

4.1 功能描述

4.2 函数原型

4.3 常用格式说明符

4.4 返回值

4.5 修饰符

4.5.1 标志位 (Flags)

4.5.2 宽度 (Width)

4.5.3 精度 (Precision)

4.5.4 长度修饰符 (Length Modifier)


1 文件基本介绍

1.1 文件的主要功能

        文件是我们日常生活中频繁接触到的数据存储形式之一,无论是 Word 文档、文本文件(.txt)、还是 Excel 表格,都属于文件的范畴。文件的主要功能在于数据的持久化存储,它可以保存多种形式的数据,包括但不限于文字、图片、视频及音频等。

1.2 输入输出流

        在 C 语言编程中,对文件进行读写操作是通过 “流(stream)” 的概念来实现的,这种机制将数据的传输过程形象地比喻为水流,具有直观易懂的特点。

        输入流(Input Stream)指的是数据从外部数据源(如文件、键盘或网络)向程序内部(内存)传输的过程。这一过程通常用于读取文件内容或将用户输入的数据加载到程序中。

        输出流(Output Stream)指的是数据从程序内部(内存)向外部数据源(如文件、屏幕、打印机)传输的过程。输出流常用于将处理后的数据保存到文件或显示在屏幕上。


2 C 语言中的输入与输出

        在 C 语言中,输入和输出是程序与外界交互的重要方式。C 语言提供了一系列内置函数,方便我们轻松完成这些操作。

2.1 输入

        当提到输入时,意味着向程序中写入数据。这些数据可以来源于多种渠道:

  • 键盘输入
  • 文件读取
  • 网络接收

        C 语言提供了诸如 scanf()、fscanf()、getchar()、gets() 和 fgets() 等函数,用于从不同的输入源读取数据并将其加载到程序中。

2.2 输出

        输出则是指将数据展示或保存到指定的位置,常见的输出目标包括:

  • 屏幕显示
  • 打印机打印
  • 文件保存

        对于输出操作,C 语言提供了printf()、fprintf()、putchar()、puts() 和 fputs() 等函数,用以将数据输出到不同的目标。

2.3 标准文件与文件指针

        在 C 语言中,所有设备都被视为文件来处理,这意味着像显示器这样的设备也会按照文件的方式来操作。为了便于访问键盘和屏幕,程序启动时会自动打开以下三个标准文件:

标准文件文件指针设备
标准输入stdin键盘
标准输出stdout屏幕
标准错误stderr屏幕

        文件指针是 C 语言中用于访问文件的主要手段。通过学习如何利用这些文件指针从键盘获取输入值以及如何将结果输出到屏幕上,我们可以构建出能够与用户有效互动的应用程序。


3 scanf() 函数详解

        C 语言中的 I/O (输入/输出)通常使用 printf() 和 scanf() 两个函数,scanf() 函数从标准输入流 stdin 读取输入,printf() 函数把输出写入到标准输出流 stdout。

3.1 功能描述

        scanf 函数用于从标准输入(stdin,通常是键盘)读取格式化的输入,并根据指定的格式字符串解析输入的数据,然后将解析后的值存储到对应的指针变量中

3.2 函数原型

#include <stdio.h>
int scanf(const char *format, ...);

格式字符串 (const char *format):

  • 这是一个指向以空字符终止的字符串的指针,该字符串包含了待读取的格式说明符和普通字符。
  • 格式说明符以百分号 % 开始,后跟一个或多个字符,这些字符定义了如何解释后续的输入。
  • 普通字符必须与输入中的相应字符完全匹配,否则读取会失败。
  • 格式说明符可以包含宽度限制,例如 %5d 表示最多读取 5 个字符作为整数。

可变参数列表 (...):

  • 可变参数列表紧跟在格式字符串之后,它包含了一个或多个指向变量的指针,这些指针用于存储从输入中读取的数据。
  • 每个格式说明符对应一个指针参数,这些指针按照它们在调用 scanf 时出现的顺序与格式说明符匹配
  • 指针的类型必须与相应的格式说明符兼容,否则可能导致未定义的行为。

3.3 常用格式说明符

格式说明符用途示例输入示例变量声明
%d读取十进制整数123int num;
%i读取整数(支持十进制、八进制、十六进制)0177 (八进制)int num;
%x 或 %X读取十六进制整数FFint num;
%o读取八进制整数177int num;
%f读取单精度浮点数3.14float fnum;
%lf读取双精度浮点数3.14159double dnum;
%c读取单个字符Achar ch;
%s读取字符串(直到遇到空白字符)Hellochar str[50];
%u读取无符号整数4294967295unsigned int unum;
%p读取指针地址0x7fffa8c01234void *ptr;
#include <stdio.h>

int main()
{
    int decimal, integer;
    int hex, octal;
    float float_num;
    double double_num;
    char single_char, string[50];
    unsigned int unsigned_int;

    void *pointer;

    // 读取十进制整数
    printf("请输入一个十进制整数: ");
    scanf("%d", &decimal);
    printf("您输入的十进制整数是: %d\n", decimal);

    // 读取整数(支持多种进制)
    printf("请输入一个整数(十进制、八进制或十六进制): ");
    scanf("%i", &integer);
    printf("您输入的整数是: %d\n", integer);

    // 读取十六进制整数
    printf("请输入一个十六进制整数: ");
    scanf("%x", &hex); // 也可以使用 %X
    printf("您输入的十六进制整数是: %d\n", hex);

    // 读取八进制整数
    printf("请输入一个八进制整数: ");
    scanf("%o", &octal);
    printf("您输入的八进制整数是: %d\n", octal);

    // 读取单精度浮点数
    printf("请输入一个浮点数: ");
    scanf("%f", &float_num);
    printf("您输入的浮点数是: %.2f\n", float_num);

    // 读取双精度浮点数
    printf("请输入一个双精度浮点数: ");
    scanf("%lf", &double_num);
    printf("您输入的双精度浮点数是: %.6f\n", double_num);

    // 读取单个字符
    printf("请输入一个字符: ");
    scanf(" %c", &single_char); // 注意前面的空格,用于跳过可能的空白字符
    printf("您输入的字符是: %c\n", single_char);

    // 读取字符串(直到遇到空白字符)
    printf("请输入一个单词: ");
    scanf("%s", string); // 不需要 &,因为数组名本身就是指针
    printf("您输入的单词是: %s\n", string);

    // 读取无符号整数
    printf("请输入一个无符号整数: ");
    scanf("%u", &unsigned_int);
    printf("您输入的无符号整数是: %u\n", unsigned_int);

    // 读取指针地址
    printf("请输入一个指针地址: ");
    scanf("%p", &pointer);
    printf("您输入的指针地址是: %p\n", pointer);

    return 0;
}

        输出结果如下所示:

3.4 返回值

        成功读取的项目数量scanf 返回成功读取并赋值给变量的项目数量。例如,如果 scanf 成功读取了两个项目,它将返回 2。

        输入失败:如果在任何转换之前遇到输入失败(例如,输入的数据格式不符合预期),scanf 将返回 0

        到达文件结束或发生读取错误:如果到达文件结束(EOF)或发生读取错误,scanf 将返回 EOF(通常定义为 -1)

#include <stdio.h>

int main()
{
    int num1, num2;
    int result;

    printf("请输入两个整数(用空格分隔): ");
    // 对于整数的输入可以不用空格隔开,但是为了输入的直观性,建议还是空格隔开
    // 对于 %c ,前面必须要有空格,用于跳过可能的空白字符
    result = scanf("%d %d", &num1, &num2);
    
    if (result == 2)
    {
        printf("您输入的两个整数是: %d 和 %d\n", num1, num2);
    }
    else if (result == 1)
    {
        printf("只成功读取了一个整数: %d\n", num1);
    }
    else if (result == 0)
    {
        printf("没有成功读取任何整数\n");
    }
    else if (result == EOF)
    {
        printf("读取失败或到达文件结束\n");
    }

    return 0;
}

        多次输入输出结果如下所示:

3.5 注意事项

3.5.1 处理空白字符

        问题scanf 在读取字符时不会跳过空白字符(如空格、制表符、换行符等)。这可能导致意外的行为,特别是当用户输入后按回车键时,换行符会被视为下一个字符输入

#include <stdio.h>

int main()
{
    char ch1, ch2;

    printf("请输入一个字符: ");
    scanf("%c", &ch1);
    printf("您输入的字符是: %c\n", ch1);

    printf("请输入另一个字符: ");
    scanf("%c", &ch2);
    printf("您输入的字符是: %c\n", ch2);

    return 0;
}

        输出结果如下所示:

        解决方案在读取字符时,可以在格式字符串中添加一个空格来跳过前导空白字符

#include <stdio.h>

int main() {
    char ch1, ch2;

    printf("请输入一个字符: ");
    scanf(" %c", &ch1); // 注意前面的空格
    printf("您输入的字符是: %c\n", ch1);

    printf("请输入另一个字符: ");
    scanf(" %c", &ch2); // 注意前面的空格
    printf("您输入的字符是: %c\n", ch2);

    return 0;
}

        输出结果如下所示:

3.5.2 防止缓冲区溢出

        问题当使用 %s 读取字符串时,如果不指定最大长度,可能会导致缓冲区溢出

        解决方案在格式字符串中指定最大长度,以防止缓冲区溢出。

char str[50];
scanf("%49s", str); // 最多读取 49 个字符,留一个位置给字符串终止符 \0

3.5.3 处理输入失败

        问题如果输入的数据格式不符合预期,scanf 可能会失败,导致后续的输入操作出现问题。

        解决方案检查 scanf 的返回值,确保输入成功。

int num;
if (scanf("%d", &num) != 1) {
    printf("输入无效,未能读取整数\n");
} else {
    printf("您输入的整数是: %d\n", num);
}

3.5.4 清除输入缓冲区

        问题如果用户输入了多余的数据,这些数据可能会留在输入缓冲区中,影响后续的输入操作

#include <stdio.h>

int main()
{
    char ch1, ch2;

    printf("请输入一个字符: ");
    scanf(" %c", &ch1); // 注意前面的空格
    printf("您输入的字符是: %c\n", ch1);

    printf("请输入一个字符: ");
    scanf(" %c", &ch2); // 注意前面的空格
    printf("您输入的字符是: %c\n", ch2);

    return 0;
}

        输出结果如下所示:

        解决方案 1使用 getchar 循环可以有效地清除输入缓冲区中的剩余字符

#include <stdio.h>

int main()
{
    char ch1, ch2;

    printf("请输入一个字符: ");
    scanf(" %c", &ch1); // 注意前面的空格
    printf("您输入的字符是: %c\n", ch1);

    // 清除输入缓冲区
    int c;
    while ((c = getchar()) != '\n' && c != EOF)
        ;

    printf("请输入一个字符: ");
    scanf(" %c", &ch2); // 注意前面的空格
    printf("您输入的字符是: %c\n", ch2);

    return 0;
}

        输出结果如下所示:

        解决方案 2:使用 fflush:fflush 通常用于清除输出缓冲区,但在某些编译器和平台上,它也可以用于清除输入缓冲区。然而,根据 C 标准,fflush 用于输入流的行为是未定义的,因此不推荐在生产代码中使用

#include <stdio.h>

int main()
{
    char ch1, ch2;

    printf("请输入一个字符: ");
    scanf(" %c", &ch1); // 注意前面的空格
    printf("您输入的字符是: %c\n", ch1);

    // 清除输入缓冲区
    fflush(stdin); // 注意:行为未定义,不推荐使用

    printf("请输入一个字符: ");
    scanf(" %c", &ch2); // 注意前面的空格
    printf("您输入的字符是: %c\n", ch2);

    return 0;
}

        输出结果如下所示: 

提示:

        由于 fflush 用于输入流的行为未定义,推荐使用 getchar 循环来清除输入缓冲区。这种方法在所有平台上都是一致的,并且更容易理解和维护。

3.5.5 处理多个输入项

        问题当读取多个输入项时,如果某个项读取失败,后续的项也不会被读取

        解决方案:逐个读取输入项,并检查每个项的读取结果。

int num1, num2;
if (scanf("%d", &num1) != 1) {
    printf("输入无效,未能读取第一个整数\n");
} else if (scanf("%d", &num2) != 1) {
    printf("输入无效,未能读取第二个整数\n");
} else {
    printf("您输入的两个整数是: %d 和 %d\n", num1, num2);
}

3.5.6 读取字符串的替代函数

        问题scanf 在读取字符串时会在遇到空白字符时停止,这可能不是我们想要的行为

        解决方案使用 gets 或者 fgets 读取整行输入(下节内容讲解)。

3.5.7 读取浮点数精度问题

        问题scanf 在读取浮点数时可能会遇到精度问题

        解决方案使用 double 类型并确保格式说明符正确


4 printf() 函数详解

4.1 功能描述

        printf 函数的主要功能是根据指定的格式字符串,将一个或多个参数转换成格式化字符串并输出。它可以处理各种类型的数据,如整数、浮点数、字符和字符串等,并且支持多种格式化选项,比如对齐方式、宽度、精度等。

4.2 函数原型

#include <stdio.h>
int printf(const char *format, ...);

格式字符串 (const char *format):

  • 这是一个指向以空字符终止的字符串的指针,该字符串包含了待输出的文本以及零个或多个格式说明符。
  • 格式说明符以百分号 % 开始,后跟一个或多个字符,这些字符定义了如何解释后续的参数。例如,%d 表示一个整数,%f 表示一个浮点数。
  • 格式字符串还可以包含普通的文本字符,这些字符将直接被输出,而不会被解释为格式说明符。 

可变参数列表 (...):

  • 可变参数列表紧跟在格式字符串之后,它包含了一个或多个额外的参数,这些参数的数量和类型由格式字符串中的格式说明符决定
  • 每个格式说明符对应一个参数,这些参数按照它们在调用 printf 时出现的顺序与格式说明符匹配。
  • 参数的类型必须与相应的格式说明符兼容,否则可能导致未定义的行为。

4.3 常用格式说明符

格式符说明示例输出
%d 或 %i有符号十进制整数printf("%d", 123);123
%u无符号十进制整数printf("%u", -1);4294967295 (取决于系统)
%o无符号八进制数printf("%o", 10);12
%x无符号十六进制数,使用小写字母printf("%x", 255);ff
%X无符号十六进制数,使用大写字母printf("%X", 255);FF
%f浮点数printf("%.2f", 3.14159);3.14
%e科学计数法表示的浮点数,使用小写字母'e'printf("%e", 31415.9);3.141590e+04
%E科学计数法表示的浮点数,使用大写字母'E'printf("%E", 31415.9);3.141590E+04
%g根据数值大小自动选择 %f 或 %eprintf("%g", 0.0000314159);3.14159e-05
%G根据数值大小自动选择 %f 或 %Eprintf("%G", 0.0000314159);3.14159E-05
%c单个字符printf("%c", 'A');A
%s字符串printf("%s", "Hello");Hello
%%输出百分号printf("Success rate: %%d%%", 85);Success rate: 85%
%ld长整型(long int)printf("%ld", 123456789L);123456789
%lu无符号长整型(unsigned long int)printf("%lu", 123456789UL);123456789
%lld长长整型(long long int)printf("%lld", 123456789012345LL);123456789012345
%llu无符号长长整型(unsigned long long int)printf("%llu", 123456789012345ULL);123456789012345
%zdsize_t 类型的有符号整数printf("%zd", sizeof(int));4
%zusize_t 类型的无符号整数printf("%zu", sizeof(int));4
%p指针地址printf("%p", &num);0x7ffeeb0a1a4c (取决于系统)
%n将已打印的字符数写入对应的 int 变量int count; printf("Hello, World! %n", &count); printf("Characters printed: %d\n", count);Hello, World! Characters printed: 13
#include <stdio.h>

int main()
{
    int num = 123;
    unsigned int unum = 4294967295U;
    int octal_num = 10;
    int hex_num = 255;
    float pi = 3.14159;
    double large_pi = 31415.9;
    char ch = 'A';
    const char *str = "Hello, World!";
    long lnum = 123456789L;
    unsigned long ulnum = 123456789UL;
    long long ll_num = 123456789012345LL;
    unsigned long long ull_num = 123456789012345ULL;
    size_t size = sizeof(int);
    int *ptr = &num;
    int count;

    // 输出有符号十进制整数
    printf("Integer: %d\n", num); // Integer: 123

    // 输出无符号十进制整数
    printf("Unsigned Integer: %u\n", unum); // Unsigned Integer: 4294967295

    // 输出无符号八进制数
    printf("Octal: %o\n", octal_num); // Octal: 12

    // 输出无符号十六进制数(小写字母)
    printf("Hexadecimal (lowercase): %x\n", hex_num); // Hexadecimal (lowercase): ff

    // 输出无符号十六进制数(大写字母)
    printf("Hexadecimal (uppercase): %X\n", hex_num); // Hexadecimal (uppercase): FF

    // 输出浮点数
    printf("Float: %.2f\n", pi); // Float: 3.14

    // 输出科学计数法表示的浮点数(小写字母'e')
    printf("Scientific Notation (lowercase): %e\n", large_pi); // Scientific Notation (lowercase): 3.141590e+04

    // 输出科学计数法表示的浮点数(大写字母'E')
    printf("Scientific Notation (uppercase): %E\n", large_pi); // Scientific Notation (uppercase): 3.141590E+04

    // 根据数值大小自动选择 %f 或 %e
    printf("Auto Float or Exponential: %g\n", 0.0000314159); // Auto Float or Exponential: 3.14159e-05

    // 根据数值大小自动选择 %f 或 %E
    printf("Auto Float or Exponential (uppercase): %G\n", 0.0000314159); // Auto Float or Exponential (uppercase): 3.14159E-05

    // 输出单个字符
    printf("Character: %c\n", ch); // Character: A

    // 输出字符串
    printf("String: %s\n", str); // String: Hello, World!

    // 输出百分号
    printf("Success rate: %d%%\n", 85); // Success rate: 85%

    // 输出长整型
    printf("Long Integer: %ld\n", lnum); // Long Integer: 123456789

    // 输出无符号长整型
    printf("Unsigned Long Integer: %lu\n", ulnum); // Unsigned Long Integer: 123456789

    // 输出长长整型
    printf("Long Long Integer: %lld\n", ll_num); // Long Long Integer: 123456789012345

    // 输出无符号长长整型
    printf("Unsigned Long Long Integer: %llu\n", ull_num); // Unsigned Long Long Integer: 123456789012345

    // 输出 size_t 类型的有符号整数
    printf("Size of int: %zd\n", size); // Size of int: 4

    // 输出 size_t 类型的无符号整数
    printf("Size of int (unsigned): %zu\n", size); // Size of int (unsigned): 4

    // 输出指针地址
    printf("Address of num: %p\n", (void *)ptr); // Address of num: 00000024eb3ffc9c

    // 使用 %n 记录已打印的字符数
    printf("Hello, World! %n\n", &count);      // Hello, World!
    printf("Characters printed: %d\n", count); // Characters printed: 14

    return 0;
}

4.4 返回值

        printf 函数的返回值是一个整数,具体含义如下:

  • 成功时返回成功输出的字符数(包括换行符)
  • 发生错误时返回一个负数,通常为 EOF(即 -1)
#include <stdio.h>

int main()
{
    int num = 123;
    float pi = 3.14159;
    char ch = 'A';
    const char *str = "Hello, World!";
    int result;

    // 输出整数
    result = printf("Integer: %d\n", num);      // Integer: 123
    printf("Characters printed: %d\n", result); // Characters printed: 13(包括换行符)

    // 输出浮点数
    result = printf("Float: %.2f\n", pi);       // Float: 3.14
    printf("Characters printed: %d\n", result); // Characters printed: 12(包括换行符)

    // 输出单个字符
    result = printf("Character: %c\n", ch);     // Character: A
    printf("Characters printed: %d\n", result); // Characters printed: 13(包括换行符)

    // 输出字符串
    result = printf("String: %s\n", str);       // String: Hello, World!
    printf("Characters printed: %d\n", result); // Characters printed: 22(包括换行符)

    return 0;
}

4.5 修饰符

        printf 函数提供了丰富的格式化选项,包括宽度、精度、对齐方式等。这些选项可以通过格式说明符中的修饰符来实现。

%[flags][width][.precision][length]specifier

4.5.1 标志位 (Flags)

  • -左对齐默认情况下,输出是右对齐的
  • +:对于数值类型,显示正号(+)或负号(-)
  •  (空格):对于数值类型,如果值为正,则在前面加一个空格。
  • 0:对于数值类型,使用零(0)而不是空格来填充
  • #:对于某些类型(如 %o、%x、%X),显示前缀(0、0x、0X)

4.5.2 宽度 (Width)

  • width指定输出的最小宽度。如果输出的字符数小于指定的宽度,将使用空格(或指定的填充字符)填充。
  • *宽度可以由一个整数参数指定。例如,printf("%*d", 10, 123); 等同于 printf("%10d", 123);。

4.5.3 精度 (Precision)

  • .precision对于字符串,指定最大输出长度;对于浮点数,指定小数点后的位数
  • .0对于浮点数,指定不显示小数部分
  • .*精度可以由一个整数参数指定。例如,printf("%.*f", 2, 3.14159); 等同于 printf("%.2f", 3.14159);。

4.5.4 长度修饰符 (Length Modifier)

  • hd:短整型(short int)。
  • ld:长整型(long int)。
  • lld:长长整型(long long int)。
  • …………
#include <stdio.h>

int main()
{
    // 左对齐,最小宽度 15,空格填充
    printf("Left-aligned: |%-15s|\n", "Hello, World!"); //  |Hello, World!  |

    // 显示正负号
    printf("With sign: |%+d|\n", 123);  //  |+123|
    printf("With sign: |%+d|\n", -123); //  |-123|

    // 正数前加空格(没意义)
    printf("With space: |% d|\n", 123); //  | 123|
    // 负数数前不会加空格(没意义)
    printf("With space: |% d|\n", -123); // |-123|

    // 使用零填充,最小宽度 10
    printf("Zero-padded: |%010d|\n", 123); //  |0000000123|

    // 显示前缀
    printf("With prefix (octal): |%#o|\n", 255);         // 八进制  |0377|
    printf("With prefix (hex lowercase): |%#x|\n", 255); // 十六进制(大写)|0xff|
    printf("With prefix (hex uppercase): |%#X|\n", 255); // 十六进制(小写) |0XFF|

    // 指定宽度,最小宽度 10
    printf("Width 10: |%10d|\n", 123); //  |       123|
    // 宽度可以由一个整数参数指定
    printf("Width 10 (using *): |%*d|\n", 10, 123); //  |       123|

    // 指定精度
    // 对于浮点数,指定小数点后的位数
    printf("Precision 2: |%.2f|\n", 3.14159);              //  |3.14|
    printf("Precision 2 (using *): |%.*f|\n", 2, 3.14159); //   |3.14|
    // 对于字符串,指定最大输出长度
    printf("Precision 5: |%.5s|\n", "Hello, World!");             //  |Hello|
    printf("Precision 5(using *): |%.*s|\n", 5, "Hello, World!"); //  |Hello|

    // 不显示小数部分
    printf("No decimal: |%.0f|\n", 3.14159); //  |3|

    // 短整型
    printf("Short Integer: %hd\n", (short)12345); // 12345

    // 长整型
    printf("Long Integer: %ld\n", 123456789L); // 123456789

    // 长长整型
    printf("Long Long Integer: %lld\n", 123456789012345LL); // 123456789012345

    // size_t
    printf("Size of int: %zu\n", (size_t)4);   // 4
    printf("Size of int: %zu\n", sizeof(int)); // 4

    // 长双精度浮点数
    printf("Long Double: %Lf\n", 3.14159265358979323846L); // 3.141593

    return 0;
}

        输出结果如下所示:

;