跳转到内容

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 中的格式化占位符 大致相同。

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

以下是一些最常用的占位符

  • %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 : 扫描一个字符串。扫描在空白 处结束。一个空字符 会存储在字符串的末尾,这意味着提供的缓冲区必须比指定的输入长度至少长 1 个字符。
  • %c : 扫描一个字符 (char)。不会添加空字符
  • (空格): 空格扫描空白 字符。
  • %lf : 作为双精度 浮点数扫描。
  • %Lf : 作为长双精度 浮点数扫描。

以上可以在数值修饰符和 lL 修饰符(代表“长”)之间复合使用,这些修饰符位于百分号和字母之间。百分号和字母之间也可能存在数值(在 long 修饰符之前,如果有的话),指定要扫描的字符数。百分号之后的一个可选的星号 (*) 表示通过此格式说明符读取的数据不应存储在变量中。格式字符串后面的参数不应包含此丢弃的变量。

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

格式字符串的示例为

"%7d%s %c%lf"

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

错误处理

[编辑 | 编辑源代码]

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

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

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

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

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

[编辑 | 编辑源代码]
  1. C99 标准,第 7.19.6.2 节“fscanf 函数” 第 11 行。
华夏公益教科书