如何从 FILE 加载多个 "clones" 结构? C
How to load multiple "clones" of structure from FILE? C
我想学习如何从 文本文件 中加载多个结构(许多学生:名字、姓氏、索引、地址...),如下所示:
Achilles, 9999
Hector, 9998
Menelaos, 9997
... and so on
结构可以是这样的:
struct student_t {
char *name;
int index;
}
我的尝试(没有用;我什至不确定 fgets+sscanf 在这里是否是一个不错的选择):
int numStudents=3; //to simplify... I'd need a function to count num of lines, I imagine
int x, y=1000, err_code=1;
FILE *pfile = fopen("file.txt", "r");
if(pfile==0) {return 2;}
STUDENT* students = malloc(numStudents * sizeof *students);
char buffer[1024];
char *ptr[numStudents];
for (x = 0; x < numStudents; x++){ //loop for each student
students[x].name=malloc(100); //allocation of each *name field
fgets(buffer, 100, pfile); //reads 1 line containing data of 1 student, to buffer
if(x==0) *ptr[x] = strtok(buffer, ",");//cuts buffer into tokens: ptr[x] for *name
else *ptr[x] = strtok(NULL, ","); //cuts next part of buffer
sscanf(ptr[x], "%19s", students[x].name); //loads the token to struct field
*ptr[y] = strtok(NULL, ","); //cuts next part of the buffer
students[y].index = (int)strtol(ptr[y], NULL, 10); //loads int token to struct field
*buffer='[=13=]';//resets buffer to the beginning for the next line from x++ fgets...
y++;//the idea with y=1000 is that I need another pointer to each struct field right?
}
for (x = 0; x < numStudents; x++)
printf("first name: %s, index: %d\n",students[x].name, students[x].index);
return students;
然后打印它以查看加载的内容。 (为了简化我有 6 个字段的真实结构)。我知道一种从用户输入加载 1 名学生的好方法...() 但是要加载多个学生,我有这个想法,但我不确定它是否太笨拙而无法工作或只是写得很乱。
稍后我会尝试按姓名对学生进行排序,甚至可能会尝试做一个重新分配缓冲区,随着新学生被加载到缓冲区来增加它的大小......然后对加载的内容进行排序...但我想首先我需要将它从文件加载到缓冲区,然后从缓冲区加载到填充结构,然后才能对其进行排序?...
非常感谢您的帮助!
C有点苛刻。我在下面使用 GNU getline,它可能不可移植,您最终可能会自己实现。为了简单起见,我使用 stdin
作为输入 FILE *
。
该程序将学生列表读入 students
数组。然后我通过比较索引对学生进行排序,然后按姓名排序,每次都打印出来。
您的代码有点混乱 - 尝试编写一个单独的函数来加载单个学生,您不需要 char ptr[students]
只需要一个 char *ptr
用于 strtok
函数。 strtok
有点混乱,我更喜欢只使用 strchr
多次。我使用 memcpy
只是从字符串中复制名称并记住将其设为空定界符。
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
struct student_s {
char *name;
int index;
};
static int students_name_cmp(const void *a, const void *b)
{
const struct student_s *s1 = a;
const struct student_s *s2 = b;
return strcmp(s1->name, s2->name);
}
static int students_index_cmp(const void *a, const void *b)
{
const struct student_s *s1 = a;
const struct student_s *s2 = b;
return s1->index - s2->index;
}
int main()
{
struct student_s *students = NULL;
size_t students_cnt = 0;
FILE *fp = stdin;
size_t read;
char *line = NULL;
size_t len = 0;
// for each line
while ((read = getline(&line, &len, fp)) != -1) {
// resize students!
students = realloc(students, (students_cnt + 1) * sizeof(*students));
// handle erros
if (students == NULL) {
fprintf(stderr, "ERROR allocating students!\n");
exit(-1);
}
// find the comma in the line
const const char * const commapos = strchr(line, ',');
if (commapos == NULL) {
fprintf(stderr, "ERROR file is badly formatted!\n");
exit(-1);
}
// student has the neme between the start to the comma adding null delimeter
const size_t namelen = (commapos - line) + 1;
// alloc memory for the name and copy it and null delimeter it
students[students_cnt].name = malloc(namelen * sizeof(char));
// handle errors
if (students[students_cnt].name == NULL) {
fprintf(stderr, "ERROR allocating students name!\n");
exit(-1);
}
memcpy(students[students_cnt].name, line, namelen - 1);
students[students_cnt].name[namelen] = '[=10=]';
// convert the string after the comma to the number
// strtol (sadly) discards whitespaces before it, but in this case it's lucky
// we can start after the comma
errno = 0;
char *endptr;
const long int tmp = strtol(&line[namelen], &endptr, 10);
// handle strtol errors
if (errno) {
fprintf(stderr, "ERROR converting student index into number\n");
exit(-1);
}
// handle out of range values, I use INT_MIN/MAX cause index is int, no better idea, depends on application
if (tmp <= INT_MIN || INT_MAX <= tmp) {
fprintf(stderr, "ERROR index number is out of allowed range\n");
exit(-1);
}
students[students_cnt].index = tmp;
// handle the case when the line consist of any more characters then a string and a number
if (*endptr != '\n' && *endptr != '[=10=]') {
fprintf(stderr, "ERROR there are some rabbish characters after the index!");
exit(-1);
}
// finnally, increment students count
students_cnt++;
}
if (line) {
free(line);
}
// sort by index
qsort(students, students_cnt, sizeof(*students), students_index_cmp);
// print students out sorted by index
printf("Students sorted by index:\n");
for (size_t i = 0; i < students_cnt; ++i) {
printf("student[%zu] = '%s', %d\n", i, students[i].name, students[i].index);
}
// now we have students. We can sort them.
qsort(students, students_cnt, sizeof(*students), students_name_cmp);
// print students out sorted by name
printf("Students sorted by name:\n");
for (size_t i = 0; i < students_cnt; ++i) {
printf("student[%zu] = '%s', %d\n", i, students[i].name, students[i].index);
}
// free students, lucky them!
for (size_t i = 0; i < students_cnt; ++i) {
free(students[i].name);
}
free(students);
return 0;
}
对于标准输入的以下输入:
Achilles, 9999
Hector, 9998
Menelaos, 9997
程序输出:
Students sorted by index:
student[0] = 'Menelaos', 9997
student[1] = 'Hector', 9998
student[2] = 'Achilles', 9999
Students sorted by name:
student[0] = 'Achilles', 9999
student[1] = 'Hector', 9998
student[2] = 'Menelaos', 9997
可用的测试版本 here on onlinegdb。
我想学习如何从 文本文件 中加载多个结构(许多学生:名字、姓氏、索引、地址...),如下所示:
Achilles, 9999
Hector, 9998
Menelaos, 9997
... and so on
结构可以是这样的:
struct student_t {
char *name;
int index;
}
我的尝试(没有用;我什至不确定 fgets+sscanf 在这里是否是一个不错的选择):
int numStudents=3; //to simplify... I'd need a function to count num of lines, I imagine
int x, y=1000, err_code=1;
FILE *pfile = fopen("file.txt", "r");
if(pfile==0) {return 2;}
STUDENT* students = malloc(numStudents * sizeof *students);
char buffer[1024];
char *ptr[numStudents];
for (x = 0; x < numStudents; x++){ //loop for each student
students[x].name=malloc(100); //allocation of each *name field
fgets(buffer, 100, pfile); //reads 1 line containing data of 1 student, to buffer
if(x==0) *ptr[x] = strtok(buffer, ",");//cuts buffer into tokens: ptr[x] for *name
else *ptr[x] = strtok(NULL, ","); //cuts next part of buffer
sscanf(ptr[x], "%19s", students[x].name); //loads the token to struct field
*ptr[y] = strtok(NULL, ","); //cuts next part of the buffer
students[y].index = (int)strtol(ptr[y], NULL, 10); //loads int token to struct field
*buffer='[=13=]';//resets buffer to the beginning for the next line from x++ fgets...
y++;//the idea with y=1000 is that I need another pointer to each struct field right?
}
for (x = 0; x < numStudents; x++)
printf("first name: %s, index: %d\n",students[x].name, students[x].index);
return students;
然后打印它以查看加载的内容。 (为了简化我有 6 个字段的真实结构)。我知道一种从用户输入加载 1 名学生的好方法...(
稍后我会尝试按姓名对学生进行排序,甚至可能会尝试做一个重新分配缓冲区,随着新学生被加载到缓冲区来增加它的大小......然后对加载的内容进行排序...但我想首先我需要将它从文件加载到缓冲区,然后从缓冲区加载到填充结构,然后才能对其进行排序?...
非常感谢您的帮助!
C有点苛刻。我在下面使用 GNU getline,它可能不可移植,您最终可能会自己实现。为了简单起见,我使用 stdin
作为输入 FILE *
。
该程序将学生列表读入 students
数组。然后我通过比较索引对学生进行排序,然后按姓名排序,每次都打印出来。
您的代码有点混乱 - 尝试编写一个单独的函数来加载单个学生,您不需要 char ptr[students]
只需要一个 char *ptr
用于 strtok
函数。 strtok
有点混乱,我更喜欢只使用 strchr
多次。我使用 memcpy
只是从字符串中复制名称并记住将其设为空定界符。
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
struct student_s {
char *name;
int index;
};
static int students_name_cmp(const void *a, const void *b)
{
const struct student_s *s1 = a;
const struct student_s *s2 = b;
return strcmp(s1->name, s2->name);
}
static int students_index_cmp(const void *a, const void *b)
{
const struct student_s *s1 = a;
const struct student_s *s2 = b;
return s1->index - s2->index;
}
int main()
{
struct student_s *students = NULL;
size_t students_cnt = 0;
FILE *fp = stdin;
size_t read;
char *line = NULL;
size_t len = 0;
// for each line
while ((read = getline(&line, &len, fp)) != -1) {
// resize students!
students = realloc(students, (students_cnt + 1) * sizeof(*students));
// handle erros
if (students == NULL) {
fprintf(stderr, "ERROR allocating students!\n");
exit(-1);
}
// find the comma in the line
const const char * const commapos = strchr(line, ',');
if (commapos == NULL) {
fprintf(stderr, "ERROR file is badly formatted!\n");
exit(-1);
}
// student has the neme between the start to the comma adding null delimeter
const size_t namelen = (commapos - line) + 1;
// alloc memory for the name and copy it and null delimeter it
students[students_cnt].name = malloc(namelen * sizeof(char));
// handle errors
if (students[students_cnt].name == NULL) {
fprintf(stderr, "ERROR allocating students name!\n");
exit(-1);
}
memcpy(students[students_cnt].name, line, namelen - 1);
students[students_cnt].name[namelen] = '[=10=]';
// convert the string after the comma to the number
// strtol (sadly) discards whitespaces before it, but in this case it's lucky
// we can start after the comma
errno = 0;
char *endptr;
const long int tmp = strtol(&line[namelen], &endptr, 10);
// handle strtol errors
if (errno) {
fprintf(stderr, "ERROR converting student index into number\n");
exit(-1);
}
// handle out of range values, I use INT_MIN/MAX cause index is int, no better idea, depends on application
if (tmp <= INT_MIN || INT_MAX <= tmp) {
fprintf(stderr, "ERROR index number is out of allowed range\n");
exit(-1);
}
students[students_cnt].index = tmp;
// handle the case when the line consist of any more characters then a string and a number
if (*endptr != '\n' && *endptr != '[=10=]') {
fprintf(stderr, "ERROR there are some rabbish characters after the index!");
exit(-1);
}
// finnally, increment students count
students_cnt++;
}
if (line) {
free(line);
}
// sort by index
qsort(students, students_cnt, sizeof(*students), students_index_cmp);
// print students out sorted by index
printf("Students sorted by index:\n");
for (size_t i = 0; i < students_cnt; ++i) {
printf("student[%zu] = '%s', %d\n", i, students[i].name, students[i].index);
}
// now we have students. We can sort them.
qsort(students, students_cnt, sizeof(*students), students_name_cmp);
// print students out sorted by name
printf("Students sorted by name:\n");
for (size_t i = 0; i < students_cnt; ++i) {
printf("student[%zu] = '%s', %d\n", i, students[i].name, students[i].index);
}
// free students, lucky them!
for (size_t i = 0; i < students_cnt; ++i) {
free(students[i].name);
}
free(students);
return 0;
}
对于标准输入的以下输入:
Achilles, 9999
Hector, 9998
Menelaos, 9997
程序输出:
Students sorted by index:
student[0] = 'Menelaos', 9997
student[1] = 'Hector', 9998
student[2] = 'Achilles', 9999
Students sorted by name:
student[0] = 'Achilles', 9999
student[1] = 'Hector', 9998
student[2] = 'Menelaos', 9997
可用的测试版本 here on onlinegdb。