读取每行两个单词的文件,并在C中以有效的方式保存单词

时间:2017-04-20 20:37:27

标签: c

我有一个文本文件,每行都有一对单词,我用它来阅读它们:

for (i=0, j=0; (c=fgetc(fp))!=EOF; i++){
    if (c == ' '){
        pares[j].par1[i] = '\0';

        for (i=0; (c=fgetc(fp))!= '\n'; i++){
            pares[j].par2[i] = c;
        }
        j++;            
        i= -1;
    }
    else{
        pares[j].par1[i]=c;
    }
}

n_pares = j;
fclose(fp);

“pares”是一个具有第一个单词(par1)和第二个单词(par2)的结构,我知道我不需要这样做。但我不知道这是否是最有效的方法。因为在python中我只使用函数split(),我不需要做for循环。谁能告诉我是否有更有效的方法?

1 个答案:

答案 0 :(得分:0)

不是逐个字符地阅读,一般分配一个大的行缓冲区,将该行读入该缓冲区,并将其处理成更合适的大小,通常更有效。

char line[1024];
while(fgets(line, 1024, fp) != NULL) {
    ...now process line...
}

在C语言中分割一行比在高级语言中更难。标准函数是strtok(字符串标记),使用起来有点搞笑。

    const char sep[] = " \t\n";

    for(
        char *token = strtok( line, sep );
        token != NULL;
        token = strtok( NULL, sep )
    ) {
        printf("%s ", token);
    }

    printf("\n");

strtok在查看字符串时会记住它的位置,但它一次只能处理一个字符串。将其传递给字符串会将其重置为开头。因此,strtok( line, sep )会在line上启动此过程,而strtok( NULL, sep )会在line上获取下一个令牌,直到没有任何一个为止。

所有对strtok的调用都会共享此全局状态,因此在使用strtok时调用其他函数甚至不安全,该函数也可能会调用strtok和重置状态。

非标准strtok_r更安全,它使用变量来记住它的位置。 strsep具有更好的界面,但它也是非标准的。

这些都可以通过修改迭代的字符串来完成。 token只是指向line的指针。例如,如果我们有这样的事情:

// Let's say line is pointing at memory location 1000
// 'U' is at 1000, 'p' is at 1001, ' ' is at 1002, etc...
line = 1000
 |
 v
"Up down\n\0"

第一次拨打char *token = strtok( line, sep );会导致此问题。

line = 1000
 |
 v
"Up\0down\n\0"
 ^
 |
token = 1000

token指向字符串的开头,但请注意该空格已被空字节替换。这使您可以使用token作为字符串"Up",而无需分配新的内存。请注意,字符串已被修改!

第二个token = strtok( NULL, sep )导致token移动。

line = 1000
 |
 v
"Up\0down\0\0"
     ^
     |
    token = 1003

最后的换行符已替换为空字节。现在token包含字符串"down",但它只是指向与line共享的内存。如果您打印line,则会获得"Up"

在最后的token = strtok( NULL, sep )上,token将设置为NULL,因为不再有非分隔符。 line仍在修改中。

line = 1000
 |
 v
"Up\0down\0\0"

token = NULL

重点是,你不能存储token因为它指向lineline将改变循环的下一次迭代。你必须制作一个字符串token的副本,指向strdup之类的东西。这是非常节省内存的,它避免了不必要的内存分配和复制,但如果你来自更高级别的语言,可能有点难以理解。