【问题标题】:Reading from file to dynamic struct从文件读取到动态结构
【发布时间】:2014-12-13 02:09:31
【问题描述】:

我想逐行读取文件。每行保证有 3 个参数。前 2 个是名字和姓氏,第三个是年龄。 我想制作一个链表,其中每个节点代表文件中的一个人(线)。 我不知道名字的大小,所以我把它变成了动态的。我也不知道文件中的行数,所以我也希望它是动态的。

我的方法是使用 fscanf,但是在读取之前我不知道需要分配多少内存。 函数 convertToList 应该接收我们要读取的文件的文件路径,将其转换为链表,然后返回头节点。 (有待改进)

查看我的代码,看看我卡在哪里了:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef enum
{
    FALSE,
    TRUE 
}bool;

struct Node{
    char firstName[50];
    char lastName[50];
    int age;
    struct Node *next;
};

typedef struct {
    struct Node *head;
}LinkedList;


struct Node * convertToList(char *inputFilePath);

int main(int argc, char* argv[]) {

    if(argc != 4) {
        printf("Invalid arguments.\n");
        exit(0);
    }
    if (strlen(argv[3])!=1) {
        printf("Invalid sorting type.\n");
        exit(0);
    }

    char *inputFilePath = (char*) malloc(sizeof(char*) * strlen(argv[1]) +1);
    memcpy(inputFilePath, argv[1], strlen(argv[1]));
    char *outputFilePath = (char*) malloc(sizeof(char*) * strlen(argv[2]) +1);
    memcpy(outputFilePath, argv[2], strlen(argv[2]) +1);
    char *sortType = argv[3];

    //LinkedList* inputList = (LinkedList*)malloc(sizeof(struct Node));

    struct Node* head = malloc(sizeof(struct Node));

    head = convertToList(inputFilePath);
    printf("\n%s %s %d\n", head->firstName, head->lastName, head->age);
//              printf("\nsaaap\n");

    getchar();


}

struct Node * convertToList(char *inputFilePath) {
FILE* ifp;
ifp = fopen(inputFilePath, "r");
if (!ifp) { perror("fopen"); exit(0); }
struct Node *head = NULL;
struct Node *prev = NULL;
bool isHead = TRUE;
while(!feof(ifp))   {
    struct Node *tmp = (struct Node*)malloc(sizeof(struct Node));
    if (prev != NULL)
        prev->next = tmp;


    if (head==NULL) 
        head = tmp;

    fscanf(ifp, "%s %s %d\n", tmp->firstName, tmp->lastName, &tmp->age);
    prev = tmp;

    //Need to link to next node as well

}

fclose(ifp);
return head;

}

我知道 fscanf 是错误的,但我不确定如何修复它。 另外,我如何返回根?我的方法行得通吗? 最后,如何设置列表中的下一个节点?我没有看到它在当前的 while 循环中发生。

谢谢。

【问题讨论】:

  • 作为建议,您不必强制转换malloc 的返回值,并且您还应该避免使用while(!feof(ifp)),因为只有在您尝试读取结束时才会设置 EOF 标志文件,所以当你到达 EOF 时(但你还没有通过)你会得到垃圾。
  • 是的,我知道选角。你建议我用什么来代替while(!feof(ifp))
  • 你有一个基本错误,我认为理解指针有问题,你分配内存并在convertToList函数的第5行用head指向它,然后你覆盖第11行的指针,您应该初始化head = NULL;,然后当您拥有第一个节点时,您只需检查if (head == NULL) head = tmp;
  • 这也是错误的char *inputFilePath = (char*) malloc(sizeof(char*) * strlen(argv[1]));,因为您正在为strlen(argv[1]) 字符分配内存,因此它应该是char *inputFilePath = (char*) malloc(1 + strlen(argv[1]));,因为sizeof(char) 始终是1,并且您需要一个额外的字符来终止空字节'\0'.
  • @iharob 好的,我会修复这些并重新发布。不过,您能帮我解决如何将节点链接在一起吗?

标签: c file linked-list


【解决方案1】:

如果您需要链接节点,这就是您可以这样做并使用动态存储的方法,就这样吧,我没想太多,但没关系。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

struct Node
{
    char *firstName;
    char *lastName;
    int   age;
    struct Node *next;
};

struct Node *convertToList(const char *const inputFilePath);
void freeList(struct Node *);

int main(int argc, char* argv[])
{
    struct Node *head;

    if (argc != 2)
    {
        printf("Invalid arguments.\n");
        return 1;
    }

    head = convertToList(argv[1]);
    if (head != NULL)
    {
        struct Node *current;

        current = head;
        while (current != NULL)
        {
            fprintf(stderr, "%s %s %d\n", current->firstName, current->lastName, current->age);
            current = current->next;
        }
        /* do manupulations with the list, example above, print the values */
        freeList(head);
    }
    return 0;
}

void freeList(struct Node *node)
{
    struct Node *current;

    current = node;
    while (current != NULL)
    {
        struct Node *next;

        next = current->next;
        if (current->firstName != NULL)
            free(current->firstName);
        if (current->lastName != NULL)
            free(current->lastName);
        free(current);

        current = next;
    }

}

size_t appendChar(char **buffer, char character, size_t length)
{
    char *temporary;
    if (buffer == NULL)
        return length;
    temporary = realloc(*buffer, 1 + length);
    if (temporary == NULL)
        return length;
    temporary[length] = character;
    *buffer           = temporary;

    return 1 + length;
}

struct Node *parseFileLine(char *line)
{
    char        *word;
    struct Node *node;
    char        *endptr;

    if (line == NULL)
        return NULL;

    node = malloc(sizeof(struct Node));
    if (node == NULL)
        return NULL;

    node->firstName = NULL;
    node->lastName  = NULL;
    node->age       = -1; // an invalid value;
    node->next      = NULL;

    word = strtok(line, " ");
    if (word == NULL)
        return node;
    node->firstName = strdup(word);

    word = strtok(NULL, " ");
    if (word == NULL)
        return node;
    node->lastName = strdup(word);

    word = strtok(NULL, " ");
    if (word == NULL)
        return node;

    node->age = strtol(word, &endptr, 10);
    if (*endptr != '\0')
        node->age = -1;

    return node;
}

struct Node *getNode(FILE *file)
{
    char  *line;
    int    character;
    size_t length;

    line   = NULL;
    length = 0;
    while ((character = fgetc(file)) != EOF)
    {
        if (((char)character == '\n') && (line != NULL))
        {
            struct Node *node;

            length = appendChar(&line, '\0', length);
            node   = parseFileLine(line);
            free(line);

            return node;
        }
        length = appendChar(&line, (char)character, length);
    }

    if (line != NULL)
        free(line);

    return NULL;
}

struct Node *convertToList(const char *const inputFilePath)
{
    FILE        *ifp;
    struct Node *head;
    struct Node *current;
    struct Node *last;

    ifp = fopen(inputFilePath, "r");
    if (ifp == NULL)
    {
        perror("fopen");
        return NULL;
    }

    head = NULL;
    last = NULL;
    while ((current = getNode(ifp)) != NULL)
    {
        if (current == NULL)
            return head;
        if (head == NULL)
            head = current;
        if (last != NULL)
            last->next = current;
        last = current;
    }
    fclose(ifp);

    return head;
}

这里还可以打印节点,看看数据是否正确。

我认为您不了解malloc 的用途,并且您对指针也不太了解,在您的fscanf 中,您将数据存储在firstNamelastName 中而没有为其分配内存,他们甚至没有初始化,所以你会得到一个分段错误。

【讨论】:

  • 可以看到使用时不需要复制argv[1]argv[2]。如果出于其他原因您必须复制它们,那么您还必须在完成它们后释放内存。
  • 是的,如果您签入我重新发布的代码(已编辑),这正是我修复它的方式。但我想尽量避免使用firstName[256]。但我想没有办法解决它。我将发布我如何链接节点。你能检查一下,请告诉我是否可以?
  • 当然可以避免,但你没有要求。
  • 其实这是我最关心的问题。但是我早上5点的自我表达能力很差。你能告诉我怎么做吗?
  • 你需要在getNode(泄漏)的while循环之后释放line。并且 EOF 不能保证适合有符号的字符,应该是 int。 “Segmentation fault: 11” 的原因可能是由于 freeList 函数——因为它对自身进行递归调用。如果堆栈很小和/或输入文件足够大,最终会导致堆栈溢出,从而导致 SIGSEGV。程序用完了存储调用指针的空间。
【解决方案2】:

有点不同的方法。

argv 复制

首先,如上所述,您不需要复制 argv 值。这样做的主要原因是如果您操纵这些值。在某些情况下,人们想要删除 argv 值,因为它们可以被ps 和其他工具读取,从/proc/ 等读取。例如,某些程序将密码作为参数,以防止任何有权访问的人读取密码系统通常会复制参数,然后覆盖 argv 值。

然而,使用变量作为参数通常是一种很好的做法。它通常使代码更清晰,但如果进行更改,也更容易维护。例如。实现标志参数,如-f &lt;filename&gt;

exit() 并从 main() 返回

您还 exit() 错误为零。您可能希望在成功时退出零,在错误或其他时退出其他值。这是常态。 0 == 成功。一些应用程序实现了可能意味着不同事物的数字退出代码。例如。 0 是正常退出,1 不是错误而是一些特殊情况,2 同样 3 可能是错误等等。例如grep:

EXIT STATUS
   The exit status is 0 if selected lines are found, and 1 if not found.  If  an
   error occurred the exit status is 2.  (Note: POSIX error handling code should
   check for '2' or greater.)

scanf

当您使用scanf 读取字符串时,可以使用一些技巧使其变得更好。首先总是使用 size 参数。

char name[16]
sscanf(buf, "%15s", name);

还要检查阅读的项目:

if (sscanf(buf, "%15s %d", name, &age) != 2)
     ... error ...

第三你还可以节省%n读取的字节数:

sscanf(buf, "%n%15s%n %n%d%n", &of1, name, &of2, &age, &of3)

用法

一个很简单,也很快捷,对用户友好的事情,就是添加一个使用函数。

通常:

int usage(const char *self, const char *err_str)
{
    fprintf(stderr,
        "Usage: %s <in-file> <out-file> <sort-type>\n"
        "  Sort types:\n"
        "   f Sort by First Name\n"
        "   l Sort by Last Name\n"
        "   a Sort by Age\n"
        ,
        self
    );
    if (err_str) {
        fprintf(stderr,
            "\nError: %s\n",
            err_str
        );
    }
    return ERR_ARG;
}

然后在main() 中,您可以快速干净地添加如下内容:

if (argc < 4)
    return usage(argv[0], "Missing arguments.");

关于您验证 sort 参数的说明。您可以检查字节 2 是否为 0,而不是使用 strlen()。

if (argv[3][1] != '\0')
    ... error ...

最后 main 可能是这样的:

int main(int argc, char *argv[])
{
    char *in_file, *out_file, sort;
    struct Node *head = NULL;
    int err = 0;

    if (argc < 4)
        return usage(argv[0], "Missing arguments.");
    if (argc > 4)
        return usage(argv[0], "Unknown arguments.");
    if (argv[3][1] != '\0')
        return usage(argv[0], "Invalid sorting type.");

    in_file  = argv[1];
    out_file = argv[2];
    sort     = argv[3][0];

    if (sort != 'f' && sort != 'l' && sort != 'a')
        return usage(argv[0], "Invalid sorting type.");

    if ((err = file_to_llist(in_file, &head)) != 0)
        return err;

    prnt_llist(stdout, head);
    free_ll(head);

    return err;
}

malloc 助手

在处理大量mallocing 和类似情况时,添加一些辅助函数会很有用。如果出现内存错误,通常会立即退出。

void *alloc(size_t size)
{
    void *buf;

    if ((buf = malloc(size)) == NULL) {
        fprintf(stderr, "Memory error.\n");
        exit(ERR_MEM);
    }
    return buf;
}

void *re_alloc(void *old, size_t size)
{
    void *buf;

    if ((buf = realloc(old, size)) == NULL) {
        fprintf(stderr, "Memory error.\n");
        exit(ERR_MEM);
    }
    return buf;
}

文件解析

由于您希望动态分配所有内容并且没有限制(超出系统内存),因此一种解决方案是实现某种标记器。使用结构将其结合在一起会很有帮助。比如:

struct file_toker {
    FILE *fh;     /* File handle */
    char *buf;    /* Dynamic Read buffer */
    size_t size;  /* Size of buffer */
    size_t len;   /* Length of actual data in buffer. */
};

这里的一点是保持读取的令牌长度。这样就不需要一直使用strlen等了。

如果您负担得起,通常最好一次读取整个文件,然后解析缓冲区。可选地,可以以 4096*16 字节的块读取文件,但是在读取之间的重叠行等方面会变得有些复杂。

无论如何,在这个例子中,一次读取一个字节。


起始码

最后的起点可能是这样的:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>     /* memcpy/strncpy */
#include <errno.h>      /* errno for fopen() */
#include <ctype.h>      /* isspace() */

#define ERR_ARG         1
#define ERR_FILE_FMT    2
#define ERR_MEM         3

struct Node {
    char *name_first;
    char *name_last;
    int age;
    struct Node *next;
};

struct file_toker {
    FILE *fh;
    char *buf;
    size_t size;
    size_t len;
};

/* ===============----- GEN HELPERS ------=================== */

int usage(const char *self, const char *err_str)
{
    fprintf(stderr,
        "Usage: %s <in-file> <out-file> <sort-type>\n"
        "  Sort types:\n"
        "   f Sort by First Name\n"
        "   l Sort by Last Name\n"
        "   a Sort by Age\n"
        ,
        self
    );
    if (err_str) {
        fprintf(stderr,
            "\nError: %s\n",
            err_str
        );
    }
    return ERR_ARG;
}

void *alloc(size_t size)
{
    void *buf;

    if ((buf = malloc(size)) == NULL) {
        fprintf(stderr, "Memory error.\n");
        exit(ERR_MEM);
    }
    return buf;
}

void *re_alloc(void *old, size_t size)
{
    void *buf;

    if ((buf = realloc(old, size)) == NULL) {
        fprintf(stderr, "Memory error.\n");
        exit(ERR_MEM);
    }
    return buf;
}

/* ===============----- LINKED LIST ------=================== */

void free_node(struct Node *n)
{
    if (!n)
        return;
    if (n->name_first)
        free(n->name_first);
    if (n->name_last)
        free(n->name_last);
    free(n);
}

void free_ll(struct Node *n)
{
    struct Node *p;

    if (!n)
        return;
    for ( ; n ; ) {
        p = n;
        n = n->next;
        free_node(p);
    }
}


void prnt_llist(FILE *fd, struct Node *n)
{
    int i = 0;

    fprintf(fd, "NODELIST:\n");
    for ( ; n != NULL ; n = n->next) {
        fprintf(fd,
            "Entry %d {\n"
            "  Name: %s, %s\n"
            "  Age : %d\n"
            "}\n",
            ++i,
            n->name_last,
            n->name_first,
            n->age
        );
    }
}

/* ================--------- FILE TOKER ------------==================== */
/* Free / close reader. */
void free_ft(struct file_toker *ft)
{
    if (!ft)
        return;
    if (ft->fh)
        fclose(ft->fh);
    free(ft->buf);
    ft->fh = NULL;
    ft->buf = NULL;
}
/* Initiate reader. */
int ft_init(struct file_toker *ft, const char *fn, size_t buf_sz)
{
    ft->size = buf_sz;
    ft->len = 0;
    ft->buf = alloc(ft->size);

    ft->fh = fopen(fn, "r");
    if (!ft->fh) {
        perror("Unable to open file");
        return errno;
    }
    return 0;
}
/* Increase buffer size. */
size_t ft_increase(struct file_toker *ft)
{
    if (ft->size < 1)
        ft->size = 1;
    ft->size *= 2;
    ft->buf = re_alloc(ft->buf, ft->size);
    return ft->size;
}
/* Read and skip spaces (\n, \r, ' ', \t etc.). Return first non-space. */
char ft_skip_space(struct file_toker *ft)
{
    int c;

    while ((c = fgetc(ft->fh)) != EOF && isspace(c))
        ;
    return c == EOF ? 0 : (char)c;
}
/* Read next token */
size_t file_tok(struct file_toker *ft)
{
    size_t i = 1;
    size_t max;
    int c;

    if (ft->size < 2)
        ft_increase(ft);

    ft->len = 0;
    max = ft->size - 1;

    /* Skip any leading spaces. Function return first non-space. */
    if ((ft->buf[0] = ft_skip_space(ft)) == 0)
        return 0;

    while ((c = fgetc(ft->fh)) != EOF) {
        /* If space, break. */
        if (isspace(c))
            break;
        /* Save char to buffer. */
        ft->buf[i++] = (char)c;
        /* If entire buffer used, increase it's size. */
        if (i > max)
            max = ft_increase(ft) - 1;
    }
    /* Null terminate. */
    ft->buf[i] = 0x00;
    /* Length without terminating null */
    ft->len = i;

    return i;
}
/* Read next space separated token and save it as new allocated string. */
int file_tok_str(struct file_toker *ft, char **out)
{
    if (file_tok(ft) == 0)
        return 1;
    *out = alloc(ft->len + 1);
    memcpy(*out, ft->buf, ft->len + 1);

    return 0;
}
/* Read next space separated token and scan it as int. */
int file_tok_int(struct file_toker *ft, int *out)
{
    if (file_tok(ft) == 0)
        return 1;
    if ((sscanf(ft->buf, "%d", out)) != 1)
        return 1;
    return 0;
}

/* ===============----- FILE PARSER ------=================== */    
int file_to_llist(const char *fn, struct Node **head)
{
    struct Node *node = NULL, *cur = *head;
    struct file_toker ft;

    /* Initiate new file token reader, initial buffer size 4096 bytes. */
    if (ft_init(&ft, fn, 4096))
        return 1;

    while (1) {
        /* Allocate next node */
        node = alloc(sizeof(struct Node));
        node->name_first = NULL;
        node->name_last  = NULL;
        /* Read and copy first name. */
        if (file_tok_str(&ft, &node->name_first))
            break;
        /* Read and copy last name. */
        if (file_tok_str(&ft, &node->name_last))
            break;
        /* Read and copy age. */
        if (file_tok_int(&ft, &node->age))
            break;

        /* Link and save current for next iteration. */
        node->next = NULL;
        if (cur) {
            cur->next = node;
        }
        cur = node;
        if (*head == NULL)
            *head = node;
    }
    /* Free last unused node. */
    free_node(node);
    free_ft(&ft);

    return 0;
}

/* ===============----- MAIN ROUTINE ------=================== */
int main(int argc, char *argv[])
{
    char *in_file, *out_file, sort;
    struct Node *head = NULL;
    int err = 0;

    if (argc < 4)
        return usage(argv[0], "Missing arguments.");
    if (argc > 4)
        return usage(argv[0], "Unknown arguments.");
    if (argv[3][1] != '\0')
        return usage(argv[0], "Invalid sorting type.");

    in_file  = argv[1];
    out_file = argv[2];
    sort     = argv[3][0];

    if (sort != 'f' && sort != 'l' && sort != 'a')
        return usage(argv[0], "Invalid sorting type.");

    if ((err = file_to_llist(in_file, &head)) != 0)
        return err;

    prnt_llist(stdout, head);
    free_ll(head);

    return err;
}

【讨论】:

  • 在处理大量 mallocing 和类似的问题时,添加一些辅助函数会很有用。如果您遇到内存错误,您通常会立即退出。 我不明白您为什么会在错误时退出,这是某种要求吗?如果您有打开的文件和其他资源,在退出之前清理会不会更好?据我所知,如果您要处理很多malloc 和类似的问题,您应该担心对齐,而不是如何编写程序,因为malloc 在错误时返回NULL,您知道它是否成功与否。
  • 而且,你没有回答这个问题。
猜你喜欢
  • 1970-01-01
  • 2017-04-13
  • 2013-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-06
  • 2018-11-13
  • 1970-01-01
相关资源
最近更新 更多