sscanf的奇技淫巧

sscanf是C语言库函数,作用是从字符串中进行格式化解析。

例如在读文本文件时先用fgets把整行读入buffer再用sscanf进一步解析buffer的内容。

头文件

1
#include <stdio.h>
1
#include <cstdio>

函数原型

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

参数

  • str – 以’\0’结尾的字符串,解析的数据源。
  • format – 格式化字符串,描述解析的规则
  • args… – 对应format参数,提供需要读取的变量的地址

返回值

返回读取到的参数的个数。

当没有读入任何数据时,可能是因为走到了字符串的结尾,也可能是因为当前将要读入的字符串无法转换为指定的格式。

如果因为读到了字符串结尾则返回EOF,即-1,如果是因为其他原因没有读入任何参数,则返回0。

样例

1
2
3
4
5
6
int i;
float f;
double ff;
char buf[128];
char ch;
sscanf("123 3.14 2.71828 \t\r\n Hello x", "%d %f %lf %s %c", &i, &f, &ff, buf, &ch);

说明

%d表示读取一个整数

%f表示读取一个浮点数

%lf表示读取一个双精度浮点

%s表示读取一个字符串

%c 表示读取一个字符

在format中空格表示跳过任意多的空白字符(空白字符包括' ', '\t','\r','\n'

%d %f %lf %s这些格式自带跳过空白字符即使前面没有空格。而%c不会自动跳过空白字符。

高级用法

指定读取字符串的长度

1
2
char buf[5];
sscanf("Hello", "%4s", buf);

我们的buf只有5个字节大小,考虑到sscanf读取的字符串会在结尾加上’\0’,所以我们最多读取4个自己,可以在%s中间加上希望读取的长度。

这同样适用于%d %f %lf仍然表示最多读取的字符串长度,只是会在读取后会将字符串转换成相应类型的数据。

丢弃读取内容

%后面加上*可以丢弃当前%表示的格式读取到的内容,即后面的参数列表不需要传入接收此项的数据地址。

1
2
char buf[5];
sscanf("Hello", "%*4s %s", buf);

丢弃了4个字符,再跳过任意多空白字符,然后读入一个字符串,所以这时读入的buf里只有"o"

读取指定的字符

通过%[]指定读取的字符,可逐个列出或通过-指定范围,也可以指定多个范围。

1
2
3
char buf1[128];
char buf2[128];
sscanf("helloWorld", "%[a-z]%s", buf1, buf2);

读取除指定字符外的字符

%[...]类似,通过%[^...]可以反向指定需要读取的字符,即除了什么字符以外的所有字符。

非常适合读取用某种字符分割的字符串。

1
2
3
char buf1[128];
char buf2[128];
sscanf("Hello,World", "%[^,]%s", buf1, buf2);

获取当前读取到的位置

通过在format中需要确定的位置插入%n获取

1
2
3
4
5
char buf1[128];
int n1;
char buf2[128];
int n2;
sscanf("Hello,World", "%[^,]%n%s%n", buf1, &n1, buf2, &n2);

n1 = 5, n2 = 11,注意%n实际上不是表示前一个%读入了多少字符,而是表示当前已经读到的位置,但是如果有需要我们可以通过这个参数算出每个参数读入了多少字符。

综合应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const char* p = "Hello,World,123.45,, Hello world   ,ttt";
int n;
char buf[128] = {0};
int r;
while(*p != '\0' && (r = sscanf(p, "%127[^,]%n", buf, &n)) != EOF) {
if(r == 0) {
buf[0] = '\0';
n = 0;
}
printf("%s\n", buf);
p += n;
if(sscanf(p, "%*c%n", &n) == EOF) break;
p += n;
}

这里演示了读入以,分割的多字段的情况。其中有一点需要说明,while条件里的sscanf我们是期望读当前位置到下一个,前的字符串,而当r==0时,说明当前就指在下一个,上,此时读入了0个参数,而buf中会保留这之前的内容,所以将其手动置空,而n也同样没有被修改,所以我们也将其置0,以便后面的输出和移动可以用相同的代码统一处理。

通过这种方式sscanf完全可以替代strtok,而且避免了strtok使用了内部静态变量带来的线程安全问题。