如何在C语言中使用scanf读取空格?

9

问题:我需要能够识别出连续出现两个空格的情况。

我阅读了以下问题:

如何从一个以\n分隔的文件中读取字符串

如何读取带空格的 scanf

而且我也意识到了 scanf 的一些问题:http://c-faq.com/stdio/scanfprobs.html

输入将会按照以下格式进行:

1 5 3 2  4 6 2  1 9  0

两个空格表示下一组数据需要处理并与自身比较。行的长度未知,每组中的整数数量也未知。两个空格是分隔下一个数据集的最多的间隔。
虽然我可以使用fgets和各种内置函数来解决这个问题,但我已经到了使用scanf解决问题的地步,这可能更容易。但是,如果不是这种情况,则使用fgets、strtok和atoi将完成大部分工作,但仍需识别两个连续的空格。
以下代码将读取整数,直到输入非整数为止。
while ( scanf ( "%d", &x ) == 1 )
我需要它还能读取空格,并且如果有两个连续的空格,程序就会对下一组数据执行不同的操作。
一旦我得到一个空格,我不知道如何说:
if ((input == "whitespace") && (previousInput == "whitespace"))
  ya da ya da
else (input == "whitespace")
  ya da ya da
else 
  ya da ya da
感谢您的时间和帮助。

教训: 虽然Jonathan Leffler下面发布了scanf的解决方案,但通过使用getc(需要更少地了解内部scanf、正则表达式和char),解决方案会更加简单明了。回顾起来,更好的了解正则表达式、scanf和char会使问题更容易解决,当然,了解可用的函数以及从一开始就选择最佳函数也很重要。


2
那是一个相当可怕的输入格式。如果你负责它,重新设计一下吧。如果像我猜测的那样,你被分配了一项作业,那就倒霉了——你的老师们是一群虐待狂。 - Jonathan Leffler
3
注意,“空格”与“两个空格”是不同的概念;传统上,“空格”指的是包括制表符、空格(或空格键)、有时还包括换页符、垂直制表符或换行符在内的多种字符,偶尔也包括退格符。 - Jonathan Leffler
@Jonathan Leffler:至少他没有试图解析空格(http://compsoc.dur.ac.uk/whitespace/) - ninjalj
@ninjalj: 有趣!你可能已经了解了Stroustrup在这个领域的贡献!至少这个问题只涉及到C语言,而不是C++。 - Jonathan Leffler
5个回答

5

getcungetc是你的好朋友

#include <stdio.h>

int main(void) {
  int ch, spaces, x;
  while (1) {
    spaces = 0;
    while (((ch = getc(stdin)) != EOF) && (ch == ' ')) spaces++;
    if (ch == EOF) break;
    ungetc(ch, stdin);
    if (scanf("%d", &x) != 1) break;
    printf("%d was preceded by %d spaces\n", x, spaces);
  }
  return 0;
}

演示请访问http://ideone.com/xipm1

编辑:我不小心上传了C++代码,这里是同样的代码,但现在使用C99 strict标准(http://ideone.com/mGeVk


scanf、sscanf、fscanf、fgets、gets、getc... 哈哈,选项真多啊。我得去了解一下getc和ungetc。谢谢你的回复。 - MykC
+1 是因为 getc()ungetc() 比仅使用 scanf() 更好,但它有点回避了问题。 - Jonathan Leffler
4
@MykC:不是gets!永远不要使用gets,绝对不要。 - pmg
是的,gets()很糟糕。我还没有深入研究过,但getc()与gets()不同,而且并不糟糕? - MykC
getc 很好用。只要记住它返回的是 int(而不是 char),你就不会出错 :) - pmg
@MykC:使用gets()的问题在于,无论何时使用它与不可信任的输入一起,都会导致安全漏洞。 - ninjalj

1
while ( scanf ( "%c", &x ) == 1 )

使用 %c 可以读取空格字符,您只需要读取所有数据并存储在数组中。然后分配 char* cptr 并将 cptr 设置为数组的开头,接下来分析数组,如果想要读取十进制数,可以在想要读取十进制数时简单地在 cptr 上使用 sscanf,但必须在数组上有指针处于良好位置(在您想要读取的数字上)。

if (((*(cptr + 1)) == ' ') && ((*cptr)== ' '))
  ya da ya da
else ((*cptr)== ' '))
  ya da ya da
  sscanf(++cptr, "%d", &x);
else 
  ya da ya da

看起来不错。如果可以的话,我会避免使用指针和数组。注意:当有意义时,我会使用指针和数组。 - MykC
我在别人的评论中提到,如果有一个或多个空格,它们都会被存储在单个字符中,这将阻止您上面的方法起作用。 - MykC

0

这里有一个只使用scanf()函数的解决方案。在此示例中,我使用sscanf()实现了大致相同的功能。

#include <stdio.h>


int p_1_cnt = 0, p_2_cnt = 0;

void process_1(int x)
{
    p_1_cnt++;
}


void process_2(int x)
{
    p_2_cnt++;
}


char * input_line = "1 5 3 2  4 6 2  1 9  0";

int main(void)
{
    char * ip = input_line;

    int x = 0, ws_0 = 0, ws_1 = 0, preceding_spaces = 1, fields = -2;

    while (sscanf (ip, "%d%n %n", &x, &ws_0, &ws_1) > 0)
    {
        ip += ws_0;

        if ((preceding_spaces) == 1)
            process_1(x);
        else
            process_2(x);

        preceding_spaces = ws_1 - ws_0;
    }

    printf("\np_1_cnt = %d, p_2_cnt = %d", p_1_cnt, p_2_cnt);
    _fgetchar();

    return 0;
}

0

你对“空格”有什么定义?

坦白地说,我不认为我想尝试使用scanf()来识别双重空格;几乎每种其他方法都会更容易。

然而,如果你坚持做这个不是非常明智的事情,那么你可能想使用以下代码派生出来的代码:

#include <stdio.h>
#include <string.h>

int main(void)
{
    int d;
    char sp[3] = "";
    int n;

    while ((n = scanf("%d%2[ \t]", &d, sp)) > 0)
    {
        printf("n = %d; d = %d; sp = <<%s>>", n, d, sp);
        if (n == 2 && strlen(sp) == 2)
            printf(" end of group");
        putchar('\n');
    }
    return 0;
}

方括号括起来的是字符类,前面的2表示最多从该类中获取2个字符。您可能需要担心它读取换行符并尝试获取更多数据以满足字符类 - 这可以通过从字符类中删除换行符来解决。但这取决于您对空格的定义以及组是否自动由换行符结束。在循环结束时重置sp [0] ='\ 0';不会有任何损失。

也许,您最好将字段反转,以便检测数字之前的两个空格。但在普通情况下,这种方法会失败,因此您将退回到简单的"%d"格式来读取数字(如果失败,则知道既没有空格也没有数字- 错误)。请注意,%d会吞掉前导空格(根据标准定义)-所有空格都会被吞掉。

我看得越多,就越不喜欢'scanf() only。请提醒我不要去你们大学上课。


1
我相信我只需要关注空格是单个空白字符槽或' '。我不依赖于scanf,我只依赖于以最简单的方式完成任务,假设我必须再次执行它而不仅仅是完成工作。只是想看看是否有正则表达式或scanf技巧,可能会解决问题,因为输入已经格式化了。 - MykC
我一直在看你的回答,似乎在你的例子中scanf总是会返回2。我目前正在研究scanf可以返回哪些值以及原因。 - MykC
@MykC:scanf()返回成功转换的数量。 %2[ \t\n]需要至少一个,最多两个空格。因此,在您的代码中,它可能总是“工作”,您必须查看char sp [3];中的内容以查看您的分隔符是什么。实际上,在Unix上,如果有人在终端上键入^D(EOF),则您的输入可能会看到一个没有结尾换行符的“行”,因此没有空格,因此您将获得1的返回值。但是,正如我所说,我不认为我会使用scanf();它太难让它按照您想要的方式工作。这就是为什么我点赞@pmg的答案的原因。 - Jonathan Leffler
是的,最终我选择了与上面类似的东西。scanf也会获取一个或多个空格并将其存储在一个字符中,因此如果出现' '(例如10个空格),它们都会被放入单个字符中。现在我正在使用Visual Studio 2010,在我逐步执行时观察变量时,就会看到以下内容。另外,以下语句if(' ' == ' ')将返回1。因此,即使我可以让scanf返回我想要的值,也没有关系。就像我上面说的那样,我放弃了scanf,因为显然它不起作用,让它起作用也不是我的目标。 - MykC
好的,我再次尝试实现您的方法,并成功得到了一个可行的结果。所以,我不确定之前我做了什么,但这个实现方法是有效的。 - MykC

0
如果你真的想要scanf类型的功能,你可以使用fgetssscanf,并使用%n说明符来让scanf在完成其余工作的同时给你的程序提供每个空格跨度的开始和结束偏移量。
否则,放弃整个scanf家族。在我看来,它很可能是标准库中最无用的部分。

它是有用的,但通常不好。如果您想将输入等效于调试打印语句添加到程序中,则非常好。如果您想为测试或演示程序添加简单输入(在此类演示时,良好的输入实践并不重要),那么它还不错。但是,如果您想为生产代码进行输入,则非常糟糕。 - nategoose
实际上,scanf 有一个用途:可以使用类似 scanf("%99[^\n]%n", buf, &cnt);(其中 99 被替换为您的缓冲区大小)来实现 getline(或 getdelim)的便携式版本,包括嵌入 NUL 字符的清洁处理。 - R.. GitHub STOP HELPING ICE
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - chux - Reinstate Monica
@chux:要像“getline”一样使用它,您需要检测该情况,并保持增长缓冲区并重复调用,如果未命中换行符,则失败。 “fscanf”只是使其工作的一部分成分,而不是全部。 - R.. GitHub STOP HELPING ICE

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