根据 OP 的需要拆分输入缓冲区的一种方法是多次调用sscanf(),并使用%n 转换说明符来跟踪读取的字符数。在下面的代码中,输入字符串分三个阶段进行扫描。
首先,指针strPos被赋值为指向inputStr的第一个字符。然后使用" %c%n%*[^ ]%n" 扫描输入字符串。此格式字符串跳过用户可能在第一个字符之前输入的任何初始空格,并将第一个字符存储在command 中。 %n 指令告诉sscanf() 将到目前为止读取的字符数存储在变量n 中;然后*[^ ] 指令告诉sscanf() 读取并忽略任何字符,直到遇到空白字符。这有效地跳过了在初始 command 字符之后输入的任何剩余字符。 %n 指令再次出现,并用直到此时读取的字符数覆盖先前的值。使用%n 两次的原因是,如果用户输入一个字符后跟一个空格(如预期的那样),第二个指令将找不到匹配项,sscanf() 将退出而不会到达最终的%n 指令。
指针strPos 通过添加n 移动到剩余字符串的开头,并再次调用sscanf(),这次是"%5s%n%*[^ ]%n"。在这里,最多 5 个字符被读取到字符数组firstname[],读取的字符数由 %n 指令保存,任何剩余的非空白字符都被读取并忽略,最后,如果扫描使它成为至此,读取的字符数又被保存了。
strPos又增加了n,最后扫描只需要"%s"完成任务。
请注意,检查fgets() 的返回值以确保它成功。对fgets() 的调用略微更改为:
fgets(inputStr, sizeof inputStr, stdin)
这里使用sizeof 运算符代替IN_BUF_SIZE。这样以后如果inputStr的声明改了,这行代码还是正确的。请注意,sizeof 运算符在这里起作用,因为inputStr 是一个数组,并且数组不会衰减为sizeof 表达式中的指针。但是,如果 inputStr 被传递到函数中,sizeof 可以在函数内部以这种方式不使用,因为在大多数表达式中,数组会衰减为指针,包括函数调用。有些人,@DavidC.Rankin,更喜欢 OP 使用的常量。如果这看起来令人困惑,我建议坚持使用常量 IN_BUF_SIZE。
还要注意,每次调用sscanf() 的返回值都经过检查,以确保输入符合预期。例如,如果用户输入命令和名字,但没有姓氏,程序将打印错误消息并退出。值得指出的是,如果用户只输入命令字符和名字,则在第二个 sscanf() 之后匹配可能在 \n 上失败,然后 strPtr 递增以指向 @987654363 @ 等仍在界限内。但这依赖于字符串中的换行符。如果没有换行符,匹配可能会在\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 字符之前输入空格,则认为这是错误输入。
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#define IN_BUF_SIZE 256
char * skipToNext(char *);
int main(void)
{
char inputStr[IN_BUF_SIZE];
char command;
char firstname[6];
char surname[6];
char *strPos = inputStr; // next scan location
if (fgets(inputStr, sizeof inputStr, stdin) == NULL) {
fprintf(stderr, "Error in fgets()\n");
exit(EXIT_FAILURE);
}
if (sscanf(strPos, "%c", &command) != 1 || isspace(command)) {
fprintf(stderr, "Input formatting error: command\n");
exit(EXIT_FAILURE);
}
strPos = skipToNext(strPos);
if (sscanf(strPos, "%5s", firstname) != 1) {
fprintf(stderr, "Input formatting error: firstname\n");
exit(EXIT_FAILURE);
}
strPos = skipToNext(strPos);
if (sscanf(strPos, "%5s", surname) != 1) {
fprintf(stderr, "Input formatting error: surname\n");
exit(EXIT_FAILURE);
}
printf("%c %s %s\n", command, firstname, surname);
}
char * skipToNext(char *c)
{
int inWord = isspace(*c) ? 0 : 1;
if (inWord && *c != '\0') {
while (!isspace(*c)) {
++c;
}
}
inWord = 0;
while (isspace(*c)) {
++c;
}
return c;
}