使用sscanf读取带空格的字符串

44

我正在为一个项目尝试从字符串中读取一个整数和一个字符串。唯一的问题是,sscanf() 在读取到空格时似乎会中断读取%s。有没有办法绕过这个限制?下面是我尝试做的一个示例:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
    int age;
    char* buffer;
    buffer = malloc(200 * sizeof(char));
    sscanf("19 cool kid", "%d %s", &age, buffer);

    printf("%s is %d years old\n", buffer, age);
    return 0;
}

它打印的是:cool is 19 years old,而我需要的是cool kid is 19 years old。有人知道如何修复吗?


检查 sscanf() 的结果是确保成功扫描 age 等的好第一步。 - chux - Reinstate Monica
5个回答

56
以下代码将开始读取一个数字 (%d), 紧接着是除制表符和换行符之外的任何字符 (%[^\t\n])。
sscanf("19 cool kid", "%d %[^\t\n]", &age, buffer);

1
但是...但这是灾难性的错误!%[0-9]格式说明符(以及类似的说明符)只能用于读取字符串。而你正在使用它来读取一个int类型的变量(age)。这是行不通的,也不会起作用。 - AnT stands with Russia
如果你想读取一个 int,你需要使用 %d(或者可能是 %i 或任何兼容 int 的格式)。但不要使用 %[] - AnT stands with Russia
1
你的意思是使用%[0-9]格式读取一个int变量,然后像OP的代码一样打印它,这样就“可以”了吗?抱歉,我很难相信这点 :) 不,这不行。我可以想象你可以将一个3个字符(或更少)的字符串压缩成4字节的int,但是一旦你打印那个int,你会得到垃圾值。当然,说这种偶然的hack“可以”是对“工作”一词的侮辱 :) - AnT stands with Russia
@AndreyT,你让我感到悲伤,请在其他地方寻找快乐。 - RyanS
4
@RyanS:不,不是我让你感到悲伤,而是字符串和数字之间的自然不协调。它们并不相同。有时候这也会让我感到悲伤,但我不知怎么学会了应对。为什么啊,残酷的世界?!!! - AnT stands with Russia
显示剩余2条评论

14

你需要使用%c的转换说明符,它可以读取字符序列而不会对空格进行特殊处理。

请注意,您需要先用零填充缓冲区,因为%c说明符不会写入nul终止符。您还需要指定要读取的字符数(否则默认只读取1个字符):

memset(buffer, 0, 200);
sscanf("19 cool kid", "%d %199c", &age, buffer);

13
好的,对于我表兄乔治·福特斯库·阿洛伊修斯·布鲁姆希尔达·多琳·比尔泽布布......约翰森·麦格雷戈这个可疑的名字,这并不适用。 :-) - paxdiablo
7
确实 - 你的表弟应该向原帖作者投诉只分配了200字节的缓冲区 ;) - caf
好观点,既然你通过我的和布鲁诺的答案发现了缓冲区溢出问题,+1。来吧,加入我们 :-) - paxdiablo
1
@paxdiablo,我发现%199c对我很有用,因为在我的情况下,字符串实际上是固定长度的。 - Wesley

12

如果您想扫描到字符串的末尾(如果有换行符,则将其删除),只需使用:

char *x = "19 cool kid";
sscanf (x, "%d %[^\n]", &age, buffer);

这是因为%s只匹配非空格字符,并且将在遇到第一个空格时停止。格式说明符 %[^\n]将匹配给定选择中的每个非(由于使用了^)换行符以外的字符。换句话说,它将匹配任何其他字符。


请记住,在缓冲区中应分配足够的空间以容纳字符串,因为您无法确定将读取多少内容(这是远离scanf/fscanf的好理由,除非您使用特定的字段宽度)。

可以通过以下方式实现:

char *x = "19 cool kid";
char *buffer = malloc (strlen (x) + 1);
sscanf (x, "%d %[^\n]", &age, buffer);

按定义,* sizeof(char)总是1,所以你不需要它。


4
所以在这里,你的表弟不愿意让他的名字被截断,反而导致了一场事故?此外,我的表弟的名字更加可疑(他的第二个名字的第三个字符是换行符),非常不开心。 - caf
1
该注释需要在对我的答案的注释的背景下阅读。 - caf

2

如果您想从输入中获取尾随字符串,可以使用%n(到目前为止使用的字符数)来获取尾随字符串开始的位置。这避免了内存复制和缓冲区大小问题,但需要明确指定是否需要复制。

const char *input = "19  cool kid";
int age;
int nameStart = 0;
sscanf(input, "%d %n", &age, &nameStart);
printf("%s is %d years old\n", input + nameStart, age);

输出:

cool kid is 19 years old

@chux 很棒。我已经更新了答案,将nameStart = 0初始化。这至少可以防止错误输入导致的段错误。 - ɲeuroburɳ

-1

我猜这就是你想要的,它完全按照你的要求执行。

#include<stdio.h>
#include<stdlib.h>

int main(int argc, char** argv) {
    int age;
    char* buffer;
    buffer = malloc(200 * sizeof(char));
    sscanf("19 cool kid", "%d cool %s", &age, buffer);
    printf("cool %s is %d years old\n", buffer, age);
    return 0;
}

格式要求:首先是一个数字(并将其放在&age指向的位置),然后是空格(零个或多个),然后是字面字符串“cool”,再次是空格(零个或多个),最后是一个字符串(并将其放在任何缓冲区指向的位置)。 您在格式字符串中忘记了“cool”部分,因此格式化程序假定这是您想要分配给缓冲区的字符串。但是您不想分配该字符串,只想跳过它。

另外,您还可以使用格式字符串:“%d %s %s”,但是您必须为其分配另一个缓冲区(具有不同的名称),并将其打印为:“%s %s is %d years old\n”。


危险:缓冲区未分配指向任何内容的指针。如果100个字节足够,您需要说“char * buffer = malloc(100)”来获取内存。然后,您需要指定与其匹配的%s的大小(在这种情况下为99,留出一个0终止符,sscanf()将附加)。 - Swiss Frank

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接