跳转到内容

c 编程/stdio.h/scanf

来自维基教科书,开放的书籍,开放的世界

scanf 是一个 函数,它从给定的字符串流源中读取指定格式的数据,起源于 C 编程语言,并且存在于许多其他编程语言中。

scanf 函数原型是

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

该函数返回成功匹配的项目总数,这可能少于请求的项目数。如果输入流耗尽或在任何项目匹配之前从该流中读取失败,则返回 EOF。

就可追溯性而言,“scanf” 代表“扫描格式”,因为它扫描输入以查找有效令牌并根据指定的格式解析它们。

scanf 函数存在于 C 中,它从 标准输入(通常是 命令行界面 或类似的 文本用户界面)读取数字和其他 数据类型 的输入。

以下展示了 C 中的代码,该代码从标准输入中读取可变数量的无格式十进制 整数,并在单独的行上打印出每个整数

#include <stdio.h>

int
main(void)
{
    int n;
    while (scanf("%d", & n))
        printf("%d\n", n);
    return 0;
}

在由上面的程序处理后,一组杂乱的整数,例如

456 123 789     456 12
456 1
      2378

将整齐地显示为

456
123
789
456
12
456
1
2378

打印一个单词

#include <stdio.h>

int
main(void)
{
    char word[20];
    if(scanf("%19s", word) == 1)
        puts(word);
    return 0;
}

无论程序员希望程序读取什么数据类型,参数(如上面的 &n)都必须是 指针,指向内存。否则,该函数将无法正常执行,因为它将尝试覆盖错误的内存区域,而不是指向您试图获取输入的变量的内存位置。

由于 scanf 被指定为仅从标准输入读取,因此许多具有 接口 的编程语言,例如 PHP,有派生函数,如 sscanffscanf,但没有 scanf 本身。

派生函数

[编辑 | 编辑源代码]

根据输入的实际来源,程序员可以使用 scanf 的不同派生函数。两个常见的例子是 sscanffscanf

fscanf 派生函数从指定的 文件流 读取输入。原型如下

(CC++)

int fscanf (FILE *file, const char *format, ...);

(PHP)

mixed fscanf (resource file, const string format [, mixed args...])

fscanf 派生函数的工作方式与原始的 scanf 函数类似 - 一旦读取了输入的一部分,就不会再次读取,直到该文件被关闭并重新打开。

sscanf 派生函数从作为第一个参数传递的字符字符串中读取输入。与 fscanf 的一个重要区别是,每次调用该函数时,都会从字符串的开头读取。没有在成功读取操作后递增的“指针”。原型如下

(CC++)

int sscanf (const char *str, const char *format, ...);

(PHP)

mixed sscanf (const string str, const string format [, mixed args...])

vscanf, vsscanf, 和 vfscanf

[编辑 | 编辑源代码]
int vscanf (const char *format, va_list args);
int vsscanf (const char *source, const char *format, va_list args);
int vfscanf (FILE *file, const char *format, va_list args);

这些函数与没有 v 前缀的相同函数类似,除了它们从 va_list 获取参数。(参见 stdarg.h。)这些变体可用于可变参数函数。

格式字符串规范

[编辑 | 编辑源代码]

scanf 中的格式 占位符 与其逆函数 printf 中的格式占位符大致相同。

格式字符串中很少有常量(即不是格式 占位符 的字符),主要是因为程序通常不是为了读取已知数据而设计的。例外是 空白 字符,它会丢弃输入中的所有空白字符。

以下是最常用的占位符

  • %d : 将整数扫描为带符号的 十进制 数。
  • %i : 将整数扫描为带符号的数字。类似于 %d,但在以 0x 开头时将其解释为 十六进制,以 0 开头时解释为 八进制。例如,字符串 031 使用 %d 将被读取为 31,使用 %i 将被读取为 25。%hi 中的标志 h 表示转换为 shorthh 表示转换为 char
  • %u : 扫描十进制 unsigned int(请注意,在 C99 标准中,输入值的减号是可选的,因此如果读取负数,则不会出现错误,结果将是 二进制补码。参见 strtoul().Template:Failed verification) 相应地,%hu 扫描 unsigned short%hhu 扫描 unsigned char
  • %f : 将浮点数扫描为普通 (定点) 表示法。
  • %g, %G : 将浮点数扫描为普通或指数表示法。%g 使用小写字母,%G 使用大写字母。
  • %x, %X : 将整数扫描为无符号的 十六进制 数。
  • %o : 将整数扫描为 八进制 数。
  • %s : 扫描一个字符字符串。扫描在遇到空白字符时结束。一个空字符存储在字符串的末尾,这意味着提供的缓冲区必须比指定的输入长度至少长一个字符。
  • %c : 扫描一个字符(char)。不添加任何空字符
  • (space): 空格扫描空白字符
  • %lf : 扫描为双精度浮点数。
  • %Lf : 扫描为长双精度浮点数。

以上可以在数字修饰符和表示“long”的lL修饰符之间进行组合使用。在百分号和字母之间,还可以有数值(在任何“long”修饰符之前),指定要扫描的字符数。百分号后面的可选星号 (*) 表示通过此格式说明符读取的数据不存储在变量中。格式字符串后面的参数不应包含此丢弃的变量。

ff修饰符在printf中不存在,导致输入和输出模式之间存在差异。llhh修饰符在C90标准中不存在,但在C99标准中存在。[1]

格式字符串的示例为

"%7d%s %c%lf"

上面的格式字符串将前七个字符扫描为十进制整数,然后读取剩余的字符作为字符串,直到遇到空格、换行符或制表符,然后扫描后面的第一个非空白字符,最后扫描双精度浮点数。

错误处理

[edit | edit source]

scanf通常用于程序无法保证输入是否为预期格式的情况。因此,健壮的程序必须检查scanf调用是否成功,并采取适当的措施。如果输入格式不正确,错误数据将仍然保留在输入流中,必须先读取并丢弃,才能读取新的输入。另一种避免这种情况的输入读取方法是使用fgets,然后检查读取的字符串。最后一步可以用sscanf完成,例如。

安全

[edit | edit source]

printf类似,scanf容易受到格式字符串攻击。应格外小心,确保格式字符串包含字符串和数组大小的限制。在大多数情况下,用户输入的字符串大小是任意的;它在执行scanf函数之前无法确定。这意味着不带长度说明符的%s占位符本质上是不安全的,可以被利用来进行缓冲区溢出。另一个潜在问题是允许动态格式字符串,例如存储在配置文件或其他用户控制文件中的格式字符串。在这种情况下,除非事先检查格式字符串并强制执行限制,否则无法指定允许的字符串大小的输入长度。与之相关的是额外的或不匹配的格式占位符,它们与实际的可变参数列表不匹配。这些占位符可能部分从堆栈中提取,包含不希望的甚至不安全的指针,具体取决于可变参数的特定实现。

/*另一种只在某些特殊编译器上有效的用法是

scanf("请输入一个值 %d",&n);

它会打印引号中的字符串,并在指示的%符号处停止接受输入。*/

另请参见

[edit | edit source]
[edit | edit source]
  1. C99 标准,§7.19.6.2 “fscanf 函数” 第 11 行。
华夏公益教科书