MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C 语言printf()和scanf()详解

2021-01-176.2k 阅读

一、printf()函数概述

printf() 是 C 语言标准库中用于格式化输出的函数,它在<stdio.h>头文件中声明。这个函数功能强大,允许我们以特定格式将数据输出到标准输出设备(通常是控制台)。其基本语法如下:

int printf(const char *format, ...);

这里,format 是一个字符串,它包含了普通字符以及格式说明符。... 表示可变参数列表,用于传递需要按照格式说明符输出的数据。printf() 函数返回成功输出的字符数,如果发生错误则返回一个负数。

二、格式说明符

格式说明符是 printf() 函数的核心部分,它们决定了如何格式化和输出数据。格式说明符以 % 字符开头,后面跟着一个或多个修饰符,最后是一个转换说明符。以下是一些常见的转换说明符:

1. 整数类型

  • %d%i:用于输出有符号十进制整数。%d%i 在功能上基本相同,但 %i 在解析输入时,如果前缀是 0x0X,会将其按十六进制解析,而 %d 始终按十进制解析。
#include <stdio.h>
int main() {
    int num = 10;
    printf("使用 %d 输出: %d\n", num);
    printf("使用 %i 输出: %i\n", num);
    return 0;
}
  • %u:用于输出无符号十进制整数。
#include <stdio.h>
int main() {
    unsigned int num = 10;
    printf("无符号整数输出: %u\n", num);
    return 0;
}
  • %o:用于输出无符号八进制整数。
#include <stdio.h>
int main() {
    unsigned int num = 10;
    printf("八进制输出: %o\n", num);
    return 0;
}
  • %x%X:用于输出无符号十六进制整数。%x 使用小写字母(a - f)表示十六进制数字,%X 使用大写字母(A - F)。
#include <stdio.h>
int main() {
    unsigned int num = 10;
    printf("小写十六进制输出: %x\n", num);
    printf("大写十六进制输出: %X\n", num);
    return 0;
}

2. 浮点类型

  • %f:用于输出单精度或双精度浮点数,默认保留 6 位小数。
#include <stdio.h>
int main() {
    double num = 3.1415926;
    printf("浮点数输出: %f\n", num);
    return 0;
}
  • %e%E:以指数形式输出浮点数。%e 使用小写的 e 表示指数部分,%E 使用大写的 E
#include <stdio.h>
int main() {
    double num = 3.1415926;
    printf("指数形式输出(小写 e): %e\n", num);
    printf("指数形式输出(大写 E): %E\n", num);
    return 0;
}
  • %g%G:根据数值的大小自动选择 %f%e 格式,以较短的形式输出浮点数。%g 使用小写的 e(如果使用指数形式),%G 使用大写的 E
#include <stdio.h>
int main() {
    double num1 = 3.1415926;
    double num2 = 12345678901234567890.0;
    printf("自动选择格式(%g): %g\n", num1);
    printf("自动选择格式(%g): %g\n", num2);
    printf("自动选择格式(%G): %G\n", num1);
    printf("自动选择格式(%G): %G\n", num2);
    return 0;
}

3. 字符和字符串类型

  • %c:用于输出单个字符。
#include <stdio.h>
int main() {
    char ch = 'A';
    printf("字符输出: %c\n", ch);
    return 0;
}
  • %s:用于输出字符串。字符串必须以空字符 '\0' 结尾。
#include <stdio.h>
int main() {
    char str[] = "Hello, World!";
    printf("字符串输出: %s\n", str);
    return 0;
}

三、修饰符

% 和转换说明符之间,可以插入一些修饰符,用于进一步控制输出的格式。

1. 字段宽度

字段宽度是指输出数据所占的最小字符数。如果数据的实际宽度小于字段宽度,会在数据前面(默认左对齐时)或后面(右对齐时)填充空格。字段宽度可以是一个正整数常量,也可以是 *,当使用 * 时,实际的字段宽度由可变参数列表中的下一个整数指定。

#include <stdio.h>
int main() {
    int num = 10;
    printf("字段宽度为 5: |%5d|\n", num);
    printf("字段宽度由变量指定: |%*d|", 5, num);
    return 0;
}

2. 精度

精度用于控制浮点数的小数位数或字符串的最大输出长度。对于浮点数,精度表示小数点后的数字个数;对于字符串,精度表示最多输出的字符数(不包括空字符)。精度以 . 开头,后面跟着一个正整数常量,或者是 *,当使用 * 时,实际的精度由可变参数列表中的下一个整数指定。

#include <stdio.h>
int main() {
    double num = 3.1415926;
    printf("保留 2 位小数: %.2f\n", num);
    char str[] = "Hello, World!";
    printf("最多输出 5 个字符: %.5s\n", str);
    printf("精度由变量指定: %.2f", num);
    return 0;
}

3. 对齐方式

默认情况下,输出是右对齐的。可以使用 - 修饰符来指定左对齐。

#include <stdio.h>
int main() {
    int num = 10;
    printf("右对齐(字段宽度 5): |%5d|\n", num);
    printf("左对齐(字段宽度 5): |%-5d|\n", num);
    return 0;
}

4. 填充字符

除了使用空格填充外,还可以使用 0 修饰符指定用 0 填充。这在输出整数时,特别是需要固定宽度且前导零的情况下很有用。

#include <stdio.h>
int main() {
    int num = 10;
    printf("用 0 填充(字段宽度 5): |%05d|\n", num);
    return 0;
}

四、转义序列

printf() 的格式字符串中,还可以使用转义序列来表示一些特殊字符。常见的转义序列有:

  • \n:换行符,将光标移动到下一行的开头。
#include <stdio.h>
int main() {
    printf("第一行\n第二行");
    return 0;
}
  • \t:水平制表符,用于在输出中插入一个制表符的空格,使输出对齐。
#include <stdio.h>
int main() {
    printf("Name\tAge\nJohn\t25\nJane\t30");
    return 0;
}
  • \\:表示反斜杠字符 \ 本身。这在需要输出反斜杠时很有用,因为反斜杠在 C 语言中有特殊含义,需要用 \\ 来表示一个实际的反斜杠。
#include <stdio.h>
int main() {
    printf("路径: C:\\Program Files\n");
    return 0;
}
  • \'\":分别用于输出单引号和双引号字符。因为单引号和双引号在 C 语言中用于界定字符常量和字符串常量,所以需要用转义序列来输出它们。
#include <stdio.h>
int main() {
    printf("他说: \"你好!\"");
    printf('这是一个字符: \'A\'');
    return 0;
}

五、printf()函数的返回值

printf() 函数返回成功输出的字符数,如果发生错误则返回一个负数。这个返回值在一些情况下很有用,例如可以用来检查输出是否成功,或者根据输出的字符数进行一些后续处理。

#include <stdio.h>
int main() {
    int count = printf("Hello, World!");
    printf("\n输出的字符数: %d\n", count);
    return 0;
}

六、scanf()函数概述

scanf() 是 C 语言标准库中用于格式化输入的函数,同样在<stdio.h>头文件中声明。它用于从标准输入设备(通常是键盘)读取数据,并按照指定的格式将数据存储到变量中。其基本语法如下:

int scanf(const char *format, ...);

format 字符串包含了格式说明符,用于指定输入数据的格式。可变参数列表 ... 包含了接收输入数据的变量的地址。scanf() 函数返回成功匹配和赋值的输入项数,如果在匹配或读取输入时发生错误,或者在到达文件末尾时没有读取任何项,则返回 EOF(通常定义为 -1)。

七、scanf()的格式说明符

scanf() 的格式说明符与 printf() 有一些相似之处,但也有一些重要的区别。

1. 整数类型

  • %d%i:用于读取有符号十进制整数。与 printf() 类似,%d 始终按十进制解析输入,而 %i 如果输入前缀是 0x0X,会按十六进制解析。
#include <stdio.h>
int main() {
    int num;
    printf("请输入一个整数: ");
    if (scanf("%d", &num) == 1) {
        printf("你输入的整数是: %d\n", num);
    } else {
        printf("输入错误\n");
    }
    return 0;
}
  • %u:用于读取无符号十进制整数。
#include <stdio.h>
int main() {
    unsigned int num;
    printf("请输入一个无符号整数: ");
    if (scanf("%u", &num) == 1) {
        printf("你输入的无符号整数是: %u\n", num);
    } else {
        printf("输入错误\n");
    }
    return 0;
}
  • %o:用于读取无符号八进制整数。输入的八进制数可以以 0 开头,但不是必须的。
#include <stdio.h>
int main() {
    unsigned int num;
    printf("请输入一个八进制整数: ");
    if (scanf("%o", &num) == 1) {
        printf("转换为十进制是: %u\n", num);
    } else {
        printf("输入错误\n");
    }
    return 0;
}
  • %x%X:用于读取无符号十六进制整数。输入的十六进制数可以以 0x0X 开头,但不是必须的。%x 接受小写的十六进制数字(a - f),%X 接受大写的十六进制数字(A - F)。
#include <stdio.h>
int main() {
    unsigned int num;
    printf("请输入一个十六进制整数: ");
    if (scanf("%x", &num) == 1) {
        printf("转换为十进制是: %u\n", num);
    } else {
        printf("输入错误\n");
    }
    return 0;
}

2. 浮点类型

  • %f:用于读取单精度浮点数。
#include <stdio.h>
int main() {
    float num;
    printf("请输入一个浮点数: ");
    if (scanf("%f", &num) == 1) {
        printf("你输入的浮点数是: %f\n", num);
    } else {
        printf("输入错误\n");
    }
    return 0;
}
  • %lf:用于读取双精度浮点数。在 C 语言中,scanf() 读取双精度浮点数必须使用 %lf,而 printf()%f 即可用于输出单精度和双精度浮点数。
#include <stdio.h>
int main() {
    double num;
    printf("请输入一个双精度浮点数: ");
    if (scanf("%lf", &num) == 1) {
        printf("你输入的双精度浮点数是: %lf\n", num);
    } else {
        printf("输入错误\n");
    }
    return 0;
}

3. 字符和字符串类型

  • %c:用于读取单个字符。需要注意的是,%c 会读取输入缓冲区中的任何字符,包括空格、制表符和换行符。
#include <stdio.h>
int main() {
    char ch;
    printf("请输入一个字符: ");
    if (scanf("%c", &ch) == 1) {
        printf("你输入的字符是: %c\n", ch);
    } else {
        printf("输入错误\n");
    }
    return 0;
}
  • %s:用于读取字符串。它会在遇到空格、制表符或换行符时停止读取,并自动在字符串末尾添加空字符 '\0'
#include <stdio.h>
int main() {
    char str[50];
    printf("请输入一个字符串: ");
    if (scanf("%s", str) == 1) {
        printf("你输入的字符串是: %s\n", str);
    } else {
        printf("输入错误\n");
    }
    return 0;
}

八、scanf()的修饰符

scanf() 也支持一些修饰符,不过其修饰符的种类和用法与 printf() 有所不同。

1. 抑制符 *

在格式说明符的 % 之后加上 *,可以抑制对输入数据的赋值。也就是说,scanf() 会读取相应的数据,但不会将其存储到任何变量中。

#include <stdio.h>
int main() {
    int num1, num2;
    printf("请输入三个整数,用空格隔开: ");
    if (scanf("%d %*d %d", &num1, &num2) == 2) {
        printf("第一个和第三个整数分别是: %d, %d\n", num1, num2);
    } else {
        printf("输入错误\n");
    }
    return 0;
}

2. 最大字符数

在格式说明符的 % 之后,可以指定一个整数,表示读取的最大字符数。这在读取字符串时非常有用,可以防止缓冲区溢出。

#include <stdio.h>
int main() {
    char str[10];
    printf("请输入一个字符串(最多 9 个字符): ");
    if (scanf("%9s", str) == 1) {
        printf("你输入的字符串是: %s\n", str);
    } else {
        printf("输入错误\n");
    }
    return 0;
}

九、处理输入缓冲区

在使用 scanf() 时,需要特别注意输入缓冲区的问题。当从键盘输入数据时,数据首先被存储在输入缓冲区中,scanf() 从缓冲区中读取数据。如果输入缓冲区中还有未处理的数据,可能会影响后续的 scanf() 操作。

1. 清除输入缓冲区

在连续使用 scanf() 读取不同类型的数据,特别是在读取字符或字符串之前读取了整数或浮点数时,可能需要清除输入缓冲区中的剩余字符(如换行符)。可以使用以下方法清除输入缓冲区:

#include <stdio.h>
#include <stdlib.h>
int main() {
    int num;
    char ch;
    printf("请输入一个整数: ");
    scanf("%d", &num);
    // 清除输入缓冲区中的换行符
    while ((getchar()) != '\n');
    printf("请输入一个字符: ");
    scanf("%c", &ch);
    printf("你输入的整数是: %d,字符是: %c\n", num, ch);
    return 0;
}

2. 避免缓冲区溢出

在读取字符串时,使用最大字符数修饰符(如 %nsn 为整数)可以有效避免缓冲区溢出。另外,也可以使用 fgets() 函数代替 scanf("%s") 来读取字符串,fgets() 会读取整行输入,包括换行符,并将其存储到指定的缓冲区中,从而减少缓冲区溢出的风险。

#include <stdio.h>
int main() {
    char str[50];
    printf("请输入一个字符串: ");
    fgets(str, sizeof(str), stdin);
    // fgets() 会读取换行符,可进行处理
    str[strcspn(str, "\n")] = '\0';
    printf("你输入的字符串是: %s\n", str);
    return 0;
}

十、scanf()的返回值

scanf() 函数返回成功匹配和赋值的输入项数。这个返回值可以用于检查输入是否成功。如果返回值不等于预期的输入项数,可能表示输入有误。例如,当期望读取两个整数,但 scanf() 返回 1 时,说明只有一个整数被成功读取,可能输入格式不正确。

#include <stdio.h>
int main() {
    int num1, num2;
    printf("请输入两个整数,用空格隔开: ");
    int result = scanf("%d %d", &num1, &num2);
    if (result == 2) {
        printf("你输入的两个整数是: %d, %d\n", num1, num2);
    } else {
        printf("输入错误,只读取了 %d 个整数\n", result);
    }
    return 0;
}

十一、printf()和scanf()的常见错误及注意事项

  1. 格式说明符不匹配:在 printf()scanf() 中使用格式说明符时,务必确保其与实际的数据类型匹配。例如,用 %d 输出浮点数或用 %f 读取整数都会导致未定义行为。
#include <stdio.h>
int main() {
    double num = 3.14;
    // 错误:用 %d 输出浮点数
    printf("错误输出: %d\n", num);
    return 0;
}
  1. 忘记取地址符:在 scanf() 中,传递给函数的变量必须是地址。忘记取地址符 & 会导致未定义行为,因为 scanf() 需要知道将数据存储在哪里。
#include <stdio.h>
int main() {
    int num;
    // 错误:忘记取地址符
    scanf("%d", num);
    return 0;
}
  1. 缓冲区溢出:在使用 scanf("%s") 读取字符串时,如果输入的字符串长度超过了目标数组的大小,就会发生缓冲区溢出,这是一个严重的安全漏洞。使用 %ns 格式说明符或 fgets() 函数可以避免这个问题。
  2. 输入缓冲区残留数据:如前文所述,连续使用 scanf() 读取不同类型的数据时,可能会因为输入缓冲区中残留的换行符等字符而导致问题。需要及时清除输入缓冲区,以确保程序的正确性。
  3. 格式字符串中的普通字符:在 printf() 的格式字符串中,普通字符会被直接输出。在 scanf() 的格式字符串中,普通字符必须与输入完全匹配(除了空白字符,空白字符可以匹配任意数量的空白字符)。例如,scanf("a%d", &num) 要求输入以 a 开头,然后是一个整数。

通过深入理解 printf()scanf() 函数的原理、格式说明符、修饰符以及常见问题,我们能够在 C 语言编程中更加准确和高效地进行输入输出操作,编写出健壮的程序。