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

时间:2017-01-02 04:35:02

标签: c strtok

对于给定的表示,

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 :(得分:4)

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

来自man page

  

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

  

可以使用指定saveptr个不同{{1}}参数的{{1}}调用序列同时解析不同的字符串。

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

答案 1 :(得分:2)

正如@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的注意事项:

  • 在空终止符\n之前的缓冲区末尾添加\0个字符。可以轻松删除附加的\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);
    }
}

注意:我使用malloc() + strcpy()代替strdup(),因为它们来自标准C库,如<string.h>和{{1而不是POSIX C.

节目输出:

<stdlib.h>

答案 2 :(得分:0)

问题更多的是与逻辑有关,而不是使用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);
}