将用户输入拆分为特定长度的字符串

时间:2017-03-29 22:32:38

标签: c string input split scanf

我正在编写一个将用户输入解析为char的C程序,以及两个设置长度的字符串。用户输入使用fgets存储到缓冲区中,然后使用sscanf进行解析。麻烦的是,这三个字段的长度最大。如果字符串超过此长度,则应消耗/丢弃下一个空格之前的剩余字符。

#include <stdio.h>
#define IN_BUF_SIZE 256

int main(void) {
    char inputStr[IN_BUF_SIZE];
    char command;
    char firstname[6];
    char surname[6];

    fgets(inputStr, IN_BUF_SIZE, stdin);
    sscanf(inputStr, "%c %5s %5s", &command, firstname, surname);
    printf("%c %s %s\n", command, firstname, surname);
}

所以,输入为 a bbbbbbbb cc
期望的输出将是
a bbbbb cc
但相反输出是
a bbbbb bbb

使用格式说明符"%c%*s %5s%*s %5s%*s"会遇到相反的问题,每个子字符串需要超过设定的长度才能达到预期的结果。

有没有办法通过使用格式说明符来实现这一点,或者是在将它们缩减到所需长度之前将子串保存在自己的缓冲区中的唯一方法?

2 个答案:

答案 0 :(得分:1)

除了其他答案之外,永远不要忘记在遇到字符串解析问题时,您总是可以选择只需向指针移动字符串以完成您需要的任何类型解析。当您将字符串读入buffer(我的buf下方)时,您可以手动分析一系列字符(使用数组索引,例如buffer[i]或指定指针开头,例如char *p = buffer;)使用您的字符串,buffer中有以下内容,p指向buffer中的第一个字符:

--------------------------------
|a| |b|b|b|b|b|b|b|b| |c|c|\n|0|    contents
--------------------------------
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4      index
 |
 p

要测试p指向的字符,您只需取消引用指针,例如*p。因此,要测试a-z之间是否有一个初始字符,后跟buffer开头的空格,您只需要执行以下操作:

    /* validate first char is 'a-z' and followed by ' ' */
    if (*p && 'a' <= *p && *p <= 'z' && *(p + 1) == ' ') {
        cmd = *p;
        p += 2;     /* advance pointer to next char following ' ' */
    }

注意:,您首先测试*p(这是*p != 0或等效*p != '\0'的简写)以验证字符串是否为空(例如,第一个字符不是nul-byte),然后再进行进一步的测试。如果任何一项测试失败,您还会包含else { /* handle error */ }(意味着您没有command后跟space)。

完成后,您的p指向buffer中的第三个字符,例如:

--------------------------------
|a| |b|b|b|b|b|b|b|b| |c|c|\n|0|    contents
--------------------------------
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4      index
     |
     p

现在,您的工作很简单,只需提前5个字符(或直到遇到下一个space,将字符分配到firstname然后nul-terminate跟随最后一个字符:

    /* read up to NLIM chars into fname */
    for (n = 0; n < NMLIM && *p && *p != ' ' && *p != '\n'; p++)
        fname[n++] = *p;
    fname[n] = 0;           /* nul terminate */

注意:,因为fgets在<{1}}中读取并包含结尾'\n',您还应该测试换行符。

退出循环时,buffer指向缓冲区中的第七个字符,如下所示:

p

您现在只需向前阅读,直到遇到下一个-------------------------------- |a| |b|b|b|b|b|b|b|b| |c|c|\n|0| contents -------------------------------- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 index | p ,然后超过space,例如:

space

注意:如果您退出指向 /* discard remaining chars up to next ' ' */ while (*p && *p != ' ') p++; p++; /* advance to next char */ 的{​​{1}}循环,则上述代码无法执行。

最后,您所做的就是为firstname重复space的同一循环。把拼图的所有部分放在一起,你可以做类似以下的事情:

surname

示例使用/输出

firstname

如果您有任何疑问,请告诉我们。无论字符串多么复杂或者你需要什么,你总是可以通过简单地沿着字符串的长度走指针(或一对指针)来获得你所需要的东西。

答案 1 :(得分:0)

将输入缓冲区拆分为OP所需的一种方法是使用多个sscanf()调用,并使用%n转换说明符来跟踪读取的字符数。在下面的代码中,输入字符串分三个阶段进行扫描。

首先,指针strPos被指定为指向inputStr的第一个字符。然后使用" %c%n%*[^ ]%n"扫描输入字符串。此格式字符串会跳过用户可能在第一个字符之前输入的任何初始空格,并将第一个字符存储在command中。 %n指令告诉sscanf()存储到目前为止在变量n中读取的字符数;然后*[^ ]指令告诉sscanf()读取并忽略任何字符,直到遇到空白字符。这有效地跳过了在初始command字符之后输入的任何剩余字符。再次出现%n指令,并使用此时读取的字符数覆盖先前的值。使用%n两次的原因是,如果用户输入一个字符后跟一个空格(如预期的那样),则第二个指令将找不到匹配项,sscanf()将退出而不会到达最终{ {1}}指令。

指针%n通过向其添加strPos移动到剩余字符串的开头,第二次调用n,这次是sscanf()。这里,最多5个字符被读入字符数组"%5s%n%*[^ ]%n",读取的字符数由firstname[]指令保存,任何剩余的非空白字符被读取和忽略,最后,如果扫描到目前为止,读取的字符数再次保存。

%n再次增加strPos,最终扫描只需n即可完成任务。

请注意,检查"%s"的返回值以确保它成功。对fgets()的调用稍微改为:

fgets()

此处使用fgets(inputStr, sizeof inputStr, stdin) 运算符代替sizeof。这样,如果稍后更改IN_BUF_SIZE的声明,则此行代码仍然是正确的。请注意,inputStr运算符在这里工作,因为sizeof是一个数组,并且数组不会衰减为inputStr表达式中的指针。但是,如果将sizeof传递给函数,inputStr可以 以这种方式在函数内部使用,因为数组在大多数表达式中衰减为指针,包括函数调用。有些@DavidC.Rankin更喜欢OP使用的常量。如果这看起来令人困惑,我建议坚持使用常量sizeof

另请注意,检查每个IN_BUF_SIZE调用的返回值,以确保输入符合预期。例如,如果用户输入命令和名字,但没有输入姓氏,程序将打印错误消息并退出。值得指出的是,如果用户输入say,仅输入命令字符和名字,则在第sscanf()次匹配后,sscanf()上的匹配可能会失败,然后\n会增加指向strPtr,因此仍然在界限内。但这取决于字符串中的换行符。如果没有换行符,则匹配可能会在\0上失败,然后\0会在下次调用strPtr之前递增超出范围。幸运的是,sscanf()保留换行符,除非输入行大于指定的缓冲区大小。然后没有fgets(),只有\n终止符。更健壮的程序将检查\0的输入字符串,并在需要时添加一个。增加\n的大小不会有什么坏处。

IN_BUF_SIZE

示例互动:

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

#define IN_BUF_SIZE 256

int main(void)
{
    char inputStr[IN_BUF_SIZE];
    char command;
    char firstname[6];
    char surname[6];
    char *strPos = inputStr;        // next scan location
    int n = 0;                      // holds number of characters read

    if (fgets(inputStr, sizeof inputStr, stdin) == NULL) {
        fprintf(stderr, "Error in fgets()\n");
        exit(EXIT_FAILURE);
    }

    if (sscanf(strPos, " %c%n%*[^ ]%n", &command, &n, &n) < 1) {
        fprintf(stderr, "Input formatting error: command\n");
        exit(EXIT_FAILURE);
    }

    strPos += n;
    if (sscanf(strPos, "%5s%n%*[^ ]%n", firstname, &n, &n) < 1) {
        fprintf(stderr, "Input formatting error: firstname\n");
        exit(EXIT_FAILURE);
    }

    strPos += n;
    if (sscanf(strPos, "%5s", surname) < 1) {
        fprintf(stderr, "Input formatting error: surname\n");
        exit(EXIT_FAILURE);
    }

    printf("%c %s %s\n", command, firstname, surname);
}

a Zaphod Beeblebrox a Zapho Beebl 函数以微妙和容易出错的名声而闻名;上面使用的格式字符串可能看起来有点棘手。通过编写一个函数来跳到输入字符串中的下一个单词,可以简化对fscanf()的调用。在下面的代码中,sscanf()将一个指向字符串的指针作为输入;如果字符串的第一个字符是skipToNext()终止符,则返回指针不变。跳过所有初始非空白字符,然后跳过任何空白字符,直到下一个非空白字符(可能是\0)。指针返回到此非空白字符。

生成的程序比前一个程序稍微长一些,但它可能更容易理解,并且它当然具有更简单的格式字符串。该程序与第一个程序的不同之处在于它不再接受字符串中的前导空格。如果用户在\0字符之前输入空格,则将其视为错误输入。

command