首页 文章

如何在for循环中使用strtok()?

提问于
浏览
2

对于给定的代表,

typedef struct {
  int age;
  char *firstName;
  char *lastName;
}Record;

给予 file.txt

Age,LastName,FirstName
50,B,A
30,A,B
20,X,D
10,F,A
90,V,E
60,N,M

以下是 main() 中的代码,

pFile=fopen("file.txt", "r");
 ...
 //Complete file is copied to 'readBuffer', approach inspired from  
 // http://stackoverflow.com/a/11487960/3317808
 ....

 char *record = strtok(readBuffer,"\n"); //Ignore header record
  record = strtok(NULL, "\n");// Read first data record(50,'B','A')

  for(;record != NULL; record = strtok(NULL,"\n")){

    printf("###Print complete record\n");
    puts(record);

    Record *r = malloc(sizeof(Record)*1);

    r->age = atoi(strtok(record,","));

    char *firstName = strtok(NULL,",");
    char *lastName = strtok(NULL, ",");

    r->firstName = strdup(firstName);
    r->lastName = strdup(lastName);
    printf("Age: %d\n", r->age);
    printf("First name: %s\n", r->firstName);
    printf("Last name: %s\n", r->lastName);

  }

strtok(readBuffer,",") 在for循环中将编译器与 strtok(record,",") 混淆


实际输出显示只有一条记录发生了标记化 .

$ ./program.exe
Print complete file
Age,LastName,FirstName
50,B,A
30,A,B
20,X,D
10,F,A
90,V,E
60,N,M



###Print complete record
50,B,A
Age: 50
First name: B
Last name: A

怎么解决?

3 回答

  • 0

    正如@David C. Rankin建议的那样,使用fgetsstrtok来读取每一行是解决这个问题的好方法 .

    如果您希望将来使用 mergesort ,那么使用此排序算法最容易实现将数据存储在结构数组中 . 此外,如果您不知道文件中将包含多少行,则可能需要在运行时动态分配该行 .

    您可以在文件中存储较低级别的 struct

    typedef struct {
        int age;
        char *firstname;
        char *lastname;
    } record_t;
    

    还有一个更高级别的 struct 存储文件的所有内容:

    typedef struct {
        record_t *records; /* pointer to record_t */
        char *headers;     /* pointer holding header */
        size_t currsize;   /* current status of information being added */
        size_t lastidx;
    } allrecords_t;
    

    有关fgets的注意事项:

    • 在空终止符 \0 之前的缓冲区末尾添加 \n 字符 . 这个附加的 \n 可以很容易地删除 .

    • 出错时,返回 NULL . 如果达到 EOF 且没有读取任何字符,则返回 NULL .

    • 必须静态声明缓冲区大小 .

    • 需要从 stdinFILE * 从指定的流中读取 .

    程序中fgets的可选用法:

    使用 fgets() 时,您可以调用一次以使用标头信息:

    fgets(buffer, 256, pfile); /* error checking needed */
    

    然后,您可以在 while() 循环中再次调用它,以使用文件中的其余数据:

    while (fgets(buffer, 256, pfile) != NULL) {
        ....
    }
    

    在计划中实施所有这些想法:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    /* Constants used */
    #define INITSIZE 20
    #define BUFFSIZE 256
    
    #define MALLOC_MSG "Allocation"
    #define REALLOC_MSG "Reallocation"
    
    /* array of structs setup */
    typedef struct {
        int age;
        char *firstname;
        char *lastname;
    } record_t;
    
    typedef struct {
        record_t *records;
        char *headers;
        size_t currsize;
        size_t lastidx;
    } allrecords_t;
    
    /* function prototypes */
    allrecords_t *initialize_records(void);
    void read_header(FILE *filestream, allrecords_t *Record, char buffer[]);
    void read_data(FILE *filestream, allrecords_t *Record, char buffer[]);
    void print_records(allrecords_t *Record);
    void check_ptr(void *ptr, const char *msg);
    void remove_newline(char buffer[]);
    
    int main(void) {
        FILE *fp;
        allrecords_t *Record;
    
        /* static buffer for fgets() */
        char buffer[BUFFSIZE];
    
        fp = fopen("fileex.txt", "r");
        if (!fp) {
            fprintf(stderr, "Cannot read file.\n");
            exit(EXIT_FAILURE);
        }
    
        Record = initialize_records();
    
        /* Reads the first line */
        read_header(fp, Record, buffer);
    
        /* Reads next lines */
        read_data(fp, Record, buffer);
    
        /* prints and frees structure elements*/
        print_records(Record);
    
        return 0;
    }
    
    /* function which reads the age/firstname/lastname data */
    void read_data(FILE *filestream, allrecords_t *Record, char buffer[]) {
        char *data; /* only need one char *pointer for strtok() */
        const char *delim = ",";
    
        while (fgets(buffer, BUFFSIZE, filestream) != NULL) {
            remove_newline(buffer); /* optional to remove '\n' */
    
            /* resize array when necessary */
            if (Record->currsize == Record->lastidx) {
                Record->currsize *= 2;
                Record->records = realloc(Record->records, Record->currsize * sizeof(record_t));
                check_ptr(Record->records, REALLOC_MSG);
            }
    
            /* adding info to array */
            /* using strdup() will lead to less code here */
            data = strtok(buffer, delim);
            Record->records[Record->lastidx].age = atoi(data);
    
            data = strtok(NULL, delim);
            Record->records[Record->lastidx].firstname = malloc(strlen(data)+1);
            check_ptr(Record->records[Record->lastidx].firstname, MALLOC_MSG);
            strcpy(Record->records[Record->lastidx].firstname, data);
    
            data = strtok(NULL, delim);
            Record->records[Record->lastidx].lastname = malloc(strlen(data)+1);
            check_ptr(Record->records[Record->lastidx].lastname, MALLOC_MSG);
            strcpy(Record->records[Record->lastidx].lastname, data);
    
            Record->lastidx++;
        }
    
    }
    
    /* prints and frees all members safely, without UB */
    void print_records(allrecords_t *Record) {
        size_t i;
    
        printf("\nComplete Record:\n");
    
        printf("%s\n", Record->headers);
        free(Record->headers);
        Record->headers = NULL;
    
        for (i = 0; i < Record->lastidx; i++) {
            printf("%d,%s,%s\n", Record->records[i].age, 
                                 Record->records[i].firstname, 
                                 Record->records[i].lastname);
    
            free(Record->records[i].firstname);
            Record->records[i].firstname = NULL;
    
            free(Record->records[i].lastname);
            Record->records[i].lastname = NULL;
        }
    
        free(Record->records);
        Record->records = NULL;
    
        free(Record);
        Record = NULL;
    }
    
    /* function which only reads header */
    void read_header(FILE *filestream, allrecords_t *Record, char buffer[]) {
        if (fgets(buffer, BUFFSIZE, filestream) == NULL) {
            fprintf(stderr, "Error reading header.\n");
            exit(EXIT_FAILURE);
        }
    
        remove_newline(buffer);
    
        Record->headers = malloc(strlen(buffer)+1);
        check_ptr(Record->headers, MALLOC_MSG);
        strcpy(Record->headers, buffer);
    }
    
    /* function which removes '\n', lots of methods to do this */
    void remove_newline(char buffer[]) {
        size_t slen;
    
        slen = strlen(buffer);
    
        /* safe way to remove '\n' and check for bufferoverflow */
        if (slen > 0) {
            if (buffer[slen-1] == '\n') {
                buffer[slen-1] = '\0';
            } else {
                printf("Buffer overflow detected.\n");
                exit(EXIT_FAILURE);
            }
        }
    }
    
    /* initializes higher level struct */
    allrecords_t *initialize_records(void) {
        allrecords_t *Record = malloc(sizeof(*Record));
        check_ptr(Record, MALLOC_MSG);
    
        Record->currsize = INITSIZE;
    
        Record->headers = NULL;
    
        Record->records = malloc(Record->currsize * sizeof(record_t));
        check_ptr(Record->records, MALLOC_MSG);
    
        Record->lastidx = 0;
    
        return Record;
    }
    
    /* instead of checking for 'ptr == NULL' everywhere, just call this function */
    void check_ptr(void *ptr, const char *msg) {
        if (!ptr) {
            printf("Null pointer returned: %s\n", msg);
            exit(EXIT_FAILURE);
        }
    }
    

    Note :我使用 malloc() strcpy() 而不是 strdup() ,因为它们来自标准C库,如 <string.h><stdlib.h> ,而不是POSIX C.

    节目输出:

    Complete Record:
    Age,LastName,FirstName
    50,B,A
    30,A,B
    20,X,D
    10,F,A
    90,V,E
    60,N,M
    
  • 4

    如果在我们的情况下可行,使用 strtok_r() 似乎是最简单的方法 . 只是告知,这不是标准C,它在POSIX中 .

    来自man page

    strtok_r()函数是一个可重入的版本strtok() . saveptr参数是指向char *变量的指针,该变量由strtok_r()在内部使用,以便在解析相同字符串的连续调用之间维护上下文 .

    可以使用指定不同saveptr参数的strtok_r()调用序列同时解析不同的字符串 .

    手册页还有一个您正在寻找的场景的示例 .

  • 2

    问题更多的是与逻辑而不是strtok的使用 .

    另外值得注意的是您正在阅读的记录的格式,您在姓氏字段后面没有逗号的问题 .

    下面的代码将实现您的问题所要求的

    {
    char str[80] = "Age,LastName,FirstName\n50,B,A\n30,A,B\n20,X,D\n";
    
    const char newline[2] = "\n";
    const char comma[2] = ",";
    
    /* get over the header fields */
    strtok(str, newline);
    
    /* walk through other tokens */
    for(;;)
    {
        Record *r = (Record*)malloc(sizeof(Record)*1);
    
        char *age = strtok(NULL, comma);
        if(age != NULL)
        {
            r->age = atoi(age);
    
            char *firstName = strtok(NULL, comma);
            char *lastName = strtok(NULL, newline);
    
            r->firstName = (char *)strdup(firstName);
            r->lastName = (char *)strdup(lastName);
    
            printf("Age: %d\n", r->age);
            printf("First name: %s\n", r->firstName);
            printf("Last name: %s\n", r->lastName);
        }
        else
            break;
    }
    
    return(0);
    }
    

相关问题