C语言 从文件读取到动态结构



我想逐行读取文件。每行保证 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 %dn", head->firstName, head->lastName, head->age);
//              printf("nsaaapn");
    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 %dn", tmp->firstName, tmp->lastName, &tmp->age);
    prev = tmp;
    //Need to link to next node as well
}
fclose(ifp);
return head;

}

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

谢谢。

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

#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 %dn", 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 != '')
        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, '', 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值。执行 do 的主要原因是您是否操纵了值。在某些情况下,人们想要擦除 argv 值,因为它们可以通过ps和其他工具读取,从/proc/读取等。例如,一些程序将密码作为参数,为了防止密码被任何有权访问系统的人在读取,通常会复制参数然后覆盖argv值。

但是,通常最好对参数使用变量。它通常使代码更清晰,但如果进行更改,也更容易维护。例如,实现像 -f <filename> 这样的标志参数。

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 读取字符串时,可以使用一些技巧来使其更好。首先,始终使用 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 Namen"
        "   l Sort by Last Namen"
        "   a Sort by Agen"
        ,
        self
    );
    if (err_str) {
        fprintf(stderr,
            "nError: %sn",
            err_str
        );
    }
    return ERR_ARG;
}

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

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

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

if (argv[3][1] != '')
    ... 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] != '')
        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和类似操作时,添加一些辅助函数可能很有用。如果出现内存错误,通常会立即退出。

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 Namen"
        "   l Sort by Last Namen"
        "   a Sort by Agen"
        ,
        self
    );
    if (err_str) {
        fprintf(stderr,
            "nError: %sn",
            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, %sn"
            "  Age : %dn"
            "}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] != '')
        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;
}

相关内容

  • 没有找到相关文章

最新更新