我有一个文件,其行格式为:
<string><spaces><string><spaces><string>
我不知道每个字符串之间的空格数。我想解析该行并将每个字符串插入一个变量(第一个字符串代表一个名称,第二个姓氏和第三个id)。我看到我可以使用strtok
,但我不想使用它,而是使用循环遍历该行的循环。
我还发现我可以使用:
if(fscanf(party_data,"%s %s %s",name,last,id) != 3){
break;
}
但是我认为使用while循环会更好。 while循环的问题是我不知道每个字符串之间的空格数量。我的目标是创建一个函数parseLine
,该函数将获取三个指针(名称,姓氏和ID)并解析该行。该函数应如何显示?
答案 0 :(得分:2)
scanf
格式字符串中的单个空格字符(或其堂兄,例如fscanf
,sscanf
,vfscanf
等)可以匹配任意数量输入中的空白(不仅包括空格,还包括制表符,垂直制表符和换行符),因此您的fscanf
调用现在可能还不错。哦,除了一个细节之外:您通常希望避免进行简单的%s
转换,而应使用类似以下内容的方法:
char dest[16];
scanf("%15s", dest);
也就是说,您总是要指定最大大小,该大小应该比要提供的缓冲区的大小小一个。
如果您不想使用scanf
和公司,则有两种选择。您可以以strspn
和strcspn
开头,也可以只使用isspace
的while循环:
char *line = /* whatever*/;
while (!isspace(*line))
*first++ = *line++;
*first = '\0';
while (isspace(*line))
++line;
while (*isspace(*line))
*second++ = *line++;
*second = '\0';
while (isspace(*line))
++line;
while (*isspace(*line))
*third++ = *line++;
*third = '\0';
在实际使用中,您还希望跟踪目标缓冲区的长度,并且仅将其实际可容纳的数量复制到该缓冲区中(或者计算出每种需要的大小并相应地分配)。
哦,还有其他一些次要细节:调用isspace
时,应该将其操作数真正转换为unsigned char
。如果不进行强制转换,则将其用于某些非英语字符(例如带有重音符号,反引号等)可能会产生不确定的行为。
答案 1 :(得分:1)
这是您必须使用任何一种语言进行的最基本操作之一:
在您的情况下,输入文件中包含空格分隔的名称和ID。尽管您可以直接使用fscanf
,但它非常脆弱。如果一行与您的 format字符串不匹配,则读取将失败,并出现 matching 失败,流中的字符提取将停止,然后剩下其余的输入缓冲区中要处理的行,然后才能继续操作。
因此,一种更好的方法是使用fgets
和足够大小的缓冲区(或使用POSIX getline
)将每一行读入缓冲区,以在每次读取时消耗整行输入。您可以从存储在缓冲区中的行中解析所需的信息,而不会影响您的读取操作。这还提供了能够独立验证您的(1)阅读和(2)信息解析的好处。
有很多方法可以从缓冲区解析所需的信息。您可以使用sscanf
从缓冲区中读取(就像您在输入本身上使用fscanf
一样),可以在缓冲区中移动一对指针,将每个单词放在括号中,然后memcpy
和 nul-terminate ,您可以使用strtok
(但它会修改原始缓冲区),也可以结合使用strspn
和{{1 }}将每个单词括起来,类似于遍历指针。
在您的情况下,我们只使用strcspn
,因为对于固定格式,这同样容易。要存储价值sscanf
的3串字符串,使用这些成员创建一个结构,然后可以创建一个结构数组(我们将保留动态数组或链接列表供以后使用),然后可以存储所有您阅读的名称和ID,例如:
name, last, id
您现在有了一个结构体(使用方便的#include <stdio.h>
#define MAXID 16 /* if you need a constant, #define one (or more) */
#define MAXNM 32
#define MAXPN 128
#define MAXC 1024
typedef struct {
char name[MAXNM],
last[MAXNM],
id[MAXID];
} typeperson;
至typedef
,您可以用来创建结构体数组(每个数组都初始化为全零),例如
typeperson
您现在可以填充一组int main (int argc, char **argv) {
char buf[MAXC];
size_t n = 0;
typeperson person[MAXPN] = {{"", "", ""}};
(MAXPN
)人。现在,只需使用作为程序第一个参数提供的名称打开文件(或者,如果没有给出参数,则默认从128
读取),然后 validate 文件已打开以供阅读:
stdin
打开文件并 已验证 ,您现在可以将每一行读入 /* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
,然后从buf
解析name, last, id
使用buf
(除sscanf
和"%c"
以外的所有转换说明符(技术上为"%[..]"
,但它不是从缓冲区提取的)跳过所有前导空格,使您可以分隔您的"%n"
,无论它们之间有多少空白:
name, last, id
(注意: /* protect array bounds and read each line into struct */
while (n < MAXPN && fgets (buf, MAXC, fp)) {
if (sscanf (buf, "%s %s %s",
person[n].name, person[n].last, person[n].id) == 3)
n++;
}
的测试可以保护数组边界,并防止您编写超出存储容量的元素)
如果该行的格式错误,该怎么办?您如何恢复?简单。通过在每次读取时占用一行,任何与您的n < MAXPN
格式字符串不匹配的行都会被忽略,不会给您造成任何问题。
剩下的就是关闭文件并以所需的任何方式使用数据。将其放到一个简短的示例中,您可以这样做:
sscanf
示例输入文件
使用与格式不匹配的故意行(例如#include <stdio.h>
#define MAXID 16 /* if you need a constant, #define one (or more) */
#define MAXNM 32
#define MAXPN 128
#define MAXC 1024
typedef struct {
char name[MAXNM],
last[MAXNM],
id[MAXID];
} typeperson;
int main (int argc, char **argv) {
char buf[MAXC];
size_t n = 0;
typeperson person[MAXPN] = {{"", "", ""}};
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
/* protect array bounds and read each line into struct */
while (n < MAXPN && fgets (buf, MAXC, fp)) {
if (sscanf (buf, "%s %s %s",
person[n].name, person[n].last, person[n].id) == 3)
n++;
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (size_t i = 0; i < n; i++) /* output the resutls */
printf ("person[%3zu] : %-20s %-20s %s\n",
i, person[i].name, person[i].last, person[i].id);
}
):
"..."
使用/输出示例
$ cat dat/peopleid.txt
George Washington 1
John Adams 2
Thomas Jefferson 3
James Madison 4
...
Royal Embarrasment 45
仔细检查一下,如果还有其他问题,请告诉我。