目录

C语言常用库函数-sscanf

函数原型

1
2
3
4
5

#include <stdio.h>

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

这里 format 可以是一个或者多个

{ %[*][width][{h|l|I64|L}]type | ' ' | '\t' | '\n' | 非%符号 }

其中

符号 说明
* 添加则表示满足条件的被过滤
width 表示宽度
h 表示单字节
l 表示双字节
L 表示 4 个字节
I64 表示 8 个字节
type 如 s, d 之类,表示字符串等

用法分析

基本用法

sscanf 一般用来从一个 ascii 字符串中读取一些值,基本用法很简单。例如

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <stdio.h>

int main ()
{
    char *str = "121000-13:00:20";
    int s[6];
    int i;

    sscanf(str,"%2d%2d%2d-%d:%d:%d",
           &s[0],&s[1],&s[2],&s[3],&s[4],&s[5]);

    for(i = 0;i < 6;i++)
        printf("s[%d] = %d\n",i,s[i]);

    return 0;
}

高级用法

sscanf 提供了简单的模式匹配,其格式为 %[]

这里的 [ ] 和正则表达式中是一样的,表示匹配其中出现的字符序列。如果在 [ ] 中使用 ^ ,则是表示取反。

例如:

1
2
[a-z]           表示小写字母序列,如abc ...
[^a-z]          表示除小写字母的字符

举例来说明

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <stdio.h>

int main ()
{
    char *str = "12:10:00-13:00:20";
    char s[6][3];
    int i;

    sscanf(str,"%[^:]:%[^:]:%[^-]-%[^:]:%[^:]:%[^:]",
           s[0],s[1],s[2],s[3],s[4],s[5]);

    for(i = 0;i < 6;i++)
        printf("s[%d] = %s\n",i,s[i]);

    return 0;
}

这个例子分隔出了 str 字符串中的所有数字。其格式字符串分析如下: %[^:] 表示匹配不含 : 的字符串,所以第一个 %[^:] 就匹配了数字字符串 12 ,第二个则匹配了 10 ; 同理, %[^-] 表示匹配不含 - 的字符串,所以匹配字符串 13 ;其他的就不言而寓了。

还是上面的例子,如果我们把格式化字串变为 %[0-9]:%[0-9]:%[0-9]-%[0-9]:%[0-9]:%[0-9] , 将会如何?嘿嘿,一样的结果。因为 %[0-9] 就匹配了数字组成的字符串。

相信这一个例子应该足够说明问题,具体情况具体分析,主要还是靠大家活学活用了。

再举一个复杂点的例子,供参考:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include   <stdio.h>

/* 获取/和@之间的字符串 */
int main()
{
    const char *s = "iios/12DDWDFF@122";
    char buf[20];

    sscanf(s, "%*[^/]/%[^@]", buf);
    printf("%s\n", buf);

    return 0;
}

注意事项

返回值检查

我们在使用 sscanf 的时候往往会忽略其返回值,事实上,sscanf 的返回值是很有用处的。

当匹配出错的时候其返回值为 EOF,否则返回匹配成功的参数个数,如够一个都没匹配到,就是 0 了。

举例子如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main(int argc, char **argv)
{
    int i = 0, j = 0, r;

    r = sscanf(argv[1], "%d:%d", &i, &j);

    printf("r = %d, i = %d, j = %d\n", r, i, j);

    return 0;
}

执行结果如下

1
2
3
4
5
6
$ ./main a
r = 0, i = 0, j = 0
$ ./main 1
r = 1, i = 1, j = 0
$ ./main 1:2
r = 2, i = 1, j = 2

溢出问题

sscanf 最为人诟病的地方,是很容易出现缓冲区溢出错误。实际上 sscanf 是可以避免出现缓冲区溢出的,只要在书写任何字符串解析的格式时,注意加上其缓冲区尺寸的限制。如

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

int main()
{
    char match[8] = "12345678"; // fill with values to test with terminating
                                // null character
    int len = sizeof(match)-1;
    const char *input = "hello world";
    char format[16];

    snprintf(format, sizeof(format), "%%%d[a-z ]", len);
    printf("format = %s\n", format);
    sscanf(input, format, match);
    printf("match = %s\n", match);
}

输出为

1
2
format = %7[a-z ]
match = hello w

这里 format 里面的 %7 就限定了拷贝过来的字符串长度为 7 ,也就是 match 的总长减一。 最后一个字符留给字符串的结尾字符 \0 ,sscanf 会自动添加。