Printf Scanf

Printf Scanf

在 C++ 中,printfscanf 是从 C 语言继承过来的输入/输出函数,它们提供了格式化输入和输出的功能。虽然在 C++ 中更常用的是 iostream 库中的 cincout,但在某些情况下,printfscanf 仍然很有用,尤其是在需要精细控制输入输出格式或追求更高效率(在大量数据读写时,且不使用 cin/cout 的优化)时。

printf:格式化输出

printf 函数用于向终端(标准输出,通常是屏幕)输出格式化的数据

知识点:

  • 函数原型: int printf(const char *format, ...);
    • format 是一个格式控制字符串,包含了要输出的文本以及格式说明符,用于指定如何输出后面的参数.
    • ... 表示可变参数列表,是要根据 format 字符串中的格式说明符输出的数据.
    • 返回值是实际输出的字符数,如果发生错误则返回一个负值。
  • 格式控制字符串:
    • 包含两种信息:
      • 普通字符: 这些字符会原样输出
      • 格式说明符:百分号 (%) 开头,后跟一个或多个格式字符,用于指定输出数据的类型和格式。
  • 常用格式字符(摘自表 3.1):
    • %d:以带符号十进制整数形式输出整数。
    • %lld:以超长整数long long)形式输出。
    • %c:以字符形式输出,只输出一个字符。
    • %s:以字符串形式输出,输出直到遇到空字符 \0 为止。
    • %f:以小数形式输出单精度浮点数 (float),默认输出 6 位小数。
    • %lf:以小数形式输出双精度浮点数 (double),默认输出 6 位小数。
    • %o:以八进制数的形式输出。
    • %x:以十六进制数的形式输出。
  • 格式说明符的修饰符: 可以在 % 和格式字符之间添加修饰符来控制输出格式,例如:
    • 宽度: 指定输出的最小字符数。如果实际输出的字符数少于宽度,则会进行填充(默认右对齐,可以使用 - 标志进行左对齐)。例如:%5d
    • 精度: 对于浮点数,指定小数点后的位数;对于字符串,指定输出的最大字符数。精度通过在宽度后加.再加数字来指定。例如:%.2f%10.4s
    • 对齐标志:
      • -左对齐输出。例如:%-5d
      • (空格):正数前输出一个空格
      • +:正数前输出加号 (+)
      • 0:用前导零填充空白(对于数字类型)。例如:%05d
  • 转义序列: 格式控制字符串中可以使用转义序列来输出特殊字符,例如 \n(换行符)。

printf 示例程序

#include <stdio.h> // 引入 stdio.h 头文件,包含 printf 和 scanf 的声明

int main() {
    int integer_num = 123;
    float float_num = 45.678f;
    double double_num = 98.7654321;
    char single_char = 'A';
    char string_val[] = "Hello";

    // 输出不同类型的变量
    printf("整数:%d\n", integer_num);             // 输出整数
    printf("单精度浮点数:%.2f\n", float_num);      // 输出单精度浮点数,保留两位小数
    printf("双精度浮点数:%lf\n", double_num);     // 输出双精度浮点数,默认六位小数
    printf("双精度浮点数(宽度10,两位小数):%10.2lf\n", double_num); // 指定宽度和精度
    printf("字符:%c\n", single_char);               // 输出字符
    printf("字符串:%s\n", string_val);               // 输出字符串
    printf("八进制:%o,十六进制:%x\n", integer_num, integer_num); // 输出八进制和十六进制

    // 格式控制的示例
    printf("左对齐整数(宽度5):%-5d|\n", integer_num);
    printf("右对齐整数(宽度5):%5d|\n", integer_num);
    printf("带前导零的整数(宽度5):%05d\n", integer_num);

    return 0;
}

注释:

  • 程序开始需要包含 <stdio.h> 头文件,或者在 C++ 中也可以包含 <cstdio>,它们包含了 printfscanf 的函数声明. 引入 <bits/stdc++.h> 这个万能头文件也可以使用这些函数.
  • printf 函数的第一个参数是格式控制字符串,后面的参数是要输出的变量。
  • %d%.2f%lf 等是格式说明符,告诉 printf 如何解释和输出后面的变量。
  • \n 是换行符,用于在输出后移动到下一行。

scanf:格式化输入

scanf 函数用于从终端(标准输入,通常是键盘)读取格式化的数据,并将其存储到指定的变量中

知识点:

  • 函数原型: int scanf(const char *format, ...);
    • format 是一个格式控制字符串,与 printf 类似,包含格式说明符,用于指定期望输入的数据类型和格式.
    • ... 表示可变参数列表,是指向要存储输入数据的变量的地址. 必须使用取地址符 & 来获取变量的地址
    • 返回值是成功读取并赋值的输入项的数量。如果在读取任何输入项之前发生匹配失败,则返回 EOF(通常是 -1)。
  • 格式控制字符串:
    • 包含两种信息:
      • 空白字符(空格、制表符、换行符): scanf跳过输入中的零个或多个空白字符.
      • 格式说明符:百分号 (%) 开头,后跟一个或多个格式字符,用于指定期望输入的数据类型
      • 非空白字符: 这些字符必须与输入中相应位置的字符完全匹配,否则 scanf 会停止读取。
  • 常用格式字符(与 printf 大致对应):
    • %d:读取一个十进制整数并存储到 int 类型的变量中。
    • %lld:读取一个超长十进制整数并存储到 long long 类型的变量中。
    • %c:读取一个字符并存储到 char 类型的变量中。注意:scanf 不会跳过空白字符来读取 %c,如果要读取非空白字符,可能需要在 %c 前面加一个空格来跳过之前的空白字符
    • %s:读取一个字符串并存储到 char 数组中。scanf 在遇到空白字符时会停止读取一个字符串,并且会自动在字符串末尾添加空字符 \0。存在缓冲区溢出的风险,因此在读取字符串时应该限制读取的长度,例如 scanf("%19s", char_array); (假设数组大小为 20)
    • %f:读取一个浮点数并存储到 float 类型的变量中。
    • %lf:读取一个浮点数并存储到 double 类型的变量中。
    • %o:读取一个八进制整数
    • %x:读取一个十六进制整数
  • 格式说明符的修饰符:
    • 宽度: 指定最多读取的字符数。例如:%5d 最多读取 5 个数字作为一个整数。
    • *赋值抑制符。表示读取该格式指定的数据,但不将其存储到任何变量中,而是将其丢弃。例如:scanf("%d %*d %d", &a, &b); 会读取两个整数,但只将第一个和第三个存储到 ab 中,第二个整数被忽略。

scanf 示例程序

#include <stdio.h>

int main() {
    int num1, num2;
    float float_val;
    double double_val;
    char initial;
    char name;

    printf("请输入两个整数(空格分隔):");
    int count1 = scanf("%d %d", &num1, &num2); // 读取两个整数,注意使用 & 获取地址
    printf("读取了 %d 个整数,分别为:%d 和 %d\n", count1, num1, num2);

    printf("请输入一个浮点数:");
    int count2 = scanf("%f", &float_val);
    printf("读取了 %d 个浮点数,值为:%f\n", count2, float_val);

    printf("请输入一个双精度浮点数:");
    int count3 = scanf("%lf", &double_val);
    printf("读取了 %d 个双精度浮点数,值为:%lf\n", count3, double_val);

    printf("请输入你的首字母:");
    int count4 = scanf(" %c", &initial); // 注意 %c 前面的空格,用于跳过之前可能输入的换行符
    printf("读取了 %d 个字符,首字母为:%c (ASCII: %d)\n", count4, initial, initial);

    printf("请输入你的名字(不超过 19 个字符):");
    int count5 = scanf("%19s", name); // 读取字符串,限制长度以防止溢出
    printf("读取了 %d 个字符串,名字为:%s\n", count5, name);

    int day, month, year;
    printf("请输入日期(格式:年:月:日):");
    int count6 = scanf("%d:%d:%d", &year, &month, &day); // 匹配格式字符串中的非空白字符
    printf("读取了 %d 个日期部分,日期为:%d 年 %d 月 %d 日\n", count6, year, month, day);

    return 0;
}

注释:

  • 同样需要包含 <stdio.h><cstdio> 头文件。
  • scanf 函数的第一个参数是格式控制字符串,后面的参数是变量的地址,用于存储读取到的数据。务必在变量名前使用 & 运算符获取其内存地址
  • scanf 根据格式控制字符串中的格式说明符尝试匹配输入。如果输入与格式不匹配,读取可能会提前停止,并且 scanf 的返回值会反映成功读取的项目数量。
  • 在读取字符 %c 之前添加空格通常是为了消耗掉输入缓冲区中可能残留的换行符或其他空白字符,避免 %c 读取到这些空白字符。
  • 读取字符串 %s 时,应该注意目标 char 数组的大小,并使用宽度修饰符来限制读取的字符数,防止缓冲区溢出
  • scanf 可以匹配格式字符串中的非空白字符,输入时需要按照指定的格式输入。

混合使用 printfscanf

printf 用于输出,scanf 用于输入,它们可以根据需要在同一个程序中混合使用。

注意事项和效率

  • 类型匹配: 确保 printfscanf 的格式说明符与要输出或输入的变量的数据类型严格匹配,否则可能导致未定义的行为和错误的结果。
  • scanf 的安全性: 使用 %s 读取字符串时存在缓冲区溢出的风险。C++ 中更安全的做法是使用 fgets(来自 C 库)或 std::getline(来自 C++ 的 iostream 库)来读取一行字符串。
  • 返回值检查: 始终检查 scanf 的返回值,以确定是否成功读取了期望数量的输入项,这对于错误处理很重要。
  • 效率: 在大量数据输入输出的场景下,printfscanf 通常比默认情况下的 cincout 更快,因为 cincout 默认进行了同步以保证与 C 的 I/O 兼容,并且 cout 默认会刷新缓冲区。但是,可以通过使用 std::ios::sync_with_stdio(false);取消 cincout 与 C 标准流的同步,以及使用 std::cin.tie(0);解除 cincout 的绑定,从而显著提高 cincout 的效率,甚至超过 scanfprintf注意:在使用 std::ios::sync_with_stdio(false); 之后,不能再混合使用 cin/coutscanf/printf 等 C 标准输入输出函数

总的来说,printfscanf 是 C/C++ 中强大的格式化输入输出工具。理解它们的格式控制字符串和格式说明符是正确使用它们的关键。在 C++ 中,虽然有更类型安全和更方便的 iostream 库,但在需要特定格式控制或在特定性能要求的场景下,printfscanf 仍然是可行的选择。