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


typedef struct{
        char cStdName[50];
        int  nStdNum;
        char cStdClass[4];
        float dStdAvg;
}student;

student* students;

int cmp(const void* a, const void* b);

void main() {
    int num = 0,i=0;
    FILE *f;

    printf("Number of students:");
    scanf("%d", &num);
    students = (student*)malloc(num*sizeof(student));

    for(i=0;i<num;++i){

        student* ptr = students+i*sizeof(student);
        printf("Name:");
        scanf("%s", ptr->cStdName);
        printf("Num");
        scanf("%d", &ptr->nStdNum);
        printf("Class:");
        scanf("%s", ptr->cStdClass);
        printf("Grade:");
        scanf("%f", &ptr->dStdAvg);
    }

    f = fopen("bin.bin","wb");
    fwrite(&num,sizeof(int),1,f);
    fwrite(students,sizeof(student),num,f);
    fclose(f);
    system("pause");
}

这应该在二进制文件中输出学生的数量和所有结构“数组”,它适用于 1 个学生。但是当我添加 >=2 人时,文件如下所示:http: //i.imgur.com/LgL8fUa.png

如果我只添加 1 名学生,仍然会有一些 Windows 路径废话: http: //i.imgur.com/s7fm9Uv.png 不过没关系,读取文件的程序会忽略 NULL 之后的所有内容(我的意思是,对于第一个 char 数组)。

我认为问题出在 for() 循环和指针杂耍的某个地方,但我不知道在哪里。

4

4 回答 4

5
student* ptr = students + i * sizeof(student);

在 C 中,指针算术已经包括sizeof(student). 你会读到你的数组的末尾。

student* ptr = students + i;

但是,您会注意到访问 toptr与访问students[i].

于 2013-03-27T15:45:45.723 回答
1

您的代码存在一些问题:

  • 首先,正如基里连科所说,你应该students[i]在你的代码中使用。在您的情况下,students + i * sizeof(student)超出范围。
  • fwrite与结构数组一起使用从来都不是一个好主意。这是因为编译器可能会在结构的成员之间添加一些空间(填充),这意味着当您将结构数组传递给 时fwrite,将打印填充字节(其中将包含垃圾)。
  • 这同样适用于结构中的 char 数组成员。所有未使用的字节都将包含垃圾,当您使用fwrite. 最好用于strlen确定每个 char 数组包含多少个读取字符。

这是我将学生数组写入文件的方法:

void write(students* array, int len, FILE* out)
{
    int i;
    fwrite(len, 1, sizeof(len), out);

    for (i = 0; i < len; i++){
        fwrite(array[i]->cStdName, 1, strlen(array[i]->cStdName), out);
        fwrite(array[i]->nStdNum, 1, sizeof(array[i]->nStdNum), out);
        fwrite(array[i]->cStdClass, 1, strlen(array[i]->cStdClass), out);
        fwrite(array[i]->dStdAvg, 1, sizeof(array[i]->dStdAvg), out);
    }
}
于 2013-03-27T16:15:58.303 回答
0

指针分配可以在 for 循环之外一次,您可以在 for 循环结束时递增指针。试试这个。

行。这是试图解释正在发生的事情,ptr 指向结构类型的学生列表中的第一个元素,当您在 for 循环结束时增加 ptr 时,它指向列表中的下一个学生。


---
students = (student*)malloc(num*sizeof(student));
student* ptr = students;

    for(i=0;i<num;++i){

        printf("Name:");
        ----
        ----
        ptr++;
    }
于 2013-03-27T15:51:57.370 回答
0

基里连科的回答应该可以解决您当前的问题,但是该程序的几乎每一行都存在从微不足道到严重的错误。根据您更大的目标,您可能不需要修复所有这些问题,但无论如何我都会将它们全部写下来,以说明冰山的大小。

void main() {

int main(void). 是int绝对要求。()在 C(不是 C++)中,为函数参数列表编写意味着参数是未指定的,而不是没有参数;从技术上讲,您可以在这里摆脱它,因为这是一个函数定义,但它的风格很糟糕。

函数定义的左大括号总是单独一行,即使所有其他左大括号都被拥抱。

    int num = 0,i=0;

间距不一致。初始化是不必要的。

    printf("Number of students:");

fputs("Number of students", stdout);当您不使用printf的格式化功能时,一些样式指南更喜欢。但是有些编译器可以为你做转换,这没什么大不了的。

    scanf("%d", &num);

永远不要使用scanf, fscanf, orsscanf,因为:

  1. 数值溢出会触发未定义的行为。允许 C 运行时仅仅因为某人输入了太多数字而使您的程序崩溃。
  2. 一些格式说明符(特别是%s,您稍后将在此程序中使用!)是不安全的,其方式与不安全完全相同gets,即它们会愉快地写入提供的缓冲区的末尾并让您的程序崩溃(这个特定的程序看起来不安全- 对我很敏感,但应该总是像一个人的程序那样编码至少有点危险)。
  3. 它们使正确处理格式错误的输入变得极其困难。

从用户那里读取单个非负数的正确方法是这样的:

unsigned long getul(void)
{
    char buf[80], *endp;
    unsigned long val;

    for (;;) {
        fgets(buf, 80, stdin);
        val = strtoul(buf, &endp, 10);
        if (endp != buf && *endp == '\n')
            return val;

        if (buf[strlen(buf)] != '\n')
            while (getchar() != '\n')
                /* discard */;
        fprintf(stderr, "*** Enter one nonnegative integer, smaller than %lu.\n",
                ULONG_MAX);
    }
}

您可以在这里使用固定大小的缓冲区,因为没有人的 ULONG_MAX 大到无法容纳 80 个字符。

    students = (student*)malloc(num*sizeof(student));

您应该calloc在此处使用,这样当您将结构写入磁盘时,它们不会充满垃圾。(或者按照 Alexandros 的建议编写自定义序列化程序,这也可以避免这个问题。)

    for(i=0;i<num;++i){

首选样式是for (i = 0; i < num; i++) {. 除了间距之外,使用i++代替++iso 可以i在所有三个表达式中出现在相同的位置;这使它更容易阅读。

        student* ptr = students+i*sizeof(student);

student* ptr = students + i;如其他地方所讨论的。

        printf("Name:");
        scanf("%s", ptr->cStdName);

请参阅上面的评论。您需要另一个辅助函数,如下所示:

void getstr(char *buf, size_t len)
{
    size_t n;

    for (;;) {
        fgets(buf, len, stdin);
        n = strlen(buf);
        if (n < len && buf[n] == '\n') {
            memset(buf+n, 0, len-n);
            return;
        }
        while (getchar() != '\n')
            /* discard */;
        fprintf(stderr, "*** Enter no more than %lu characters.",
                (unsigned long)(len-1));
    }
}

...

        scanf("%f", &ptr->dStdAvg);

在这里你需要另一个辅助函数。和除了它使用getf的完全一样,当然错误信息也有一点不同。getulstrtod

    f = fopen("bin.bin","wb");

这不是一个非常通用的文件名吗?用户可能应该有一种方法来指定它。

    fwrite(&num,sizeof(int),1,f);

您的文件格式需要一个幻数

    fwrite(students,sizeof(student),num,f);

您正在以 CPU-endian 顺序将二进制数据写入磁盘。对于此应用程序来说,这可能不是问题,但请注意,您可能会遇到跨平台兼容性问题。(就个人而言,对于您正在做的事情,我会使用诸如 JSON 之类的文本序列化,或诸如 sqlite 之类的简单无守护进程数据库。)有关此文件格式的更多潜在问题,请参阅 Alexandros 的回答。

写入磁盘上的文件时很少出现问题,而且这不是那种输出通过管道传输到某处的程序,但是,我仍然要提到它fwrite并不能保证写入您提供的所有数据。从技术上讲,您必须fwrite像这样调用循环:

size_t r, n = sizeof(student) * num;
char *p = (char *)students;
while (n > 0) {
    r = fwrite(p, 1, n, f);
    if (r == 0) break;  /* write error */
    n -= r;
    p += r;
}

为此,您必须自己进行乘法运算并将第二个参数的 1 传递给fwrite; 否则,简短的写入可能会在“数据元素”的中间结束,您无法知道这已经发生了。

    fclose(f);

在关闭文件之前检查写入错误。

if (ferror(f) || fclose(f)) {
    perror("bin.bin");
    return 1; /* unsuccessful exit */
}

...

    system("pause");

只是return 0。使您按键退出的程序对批处理不友好。

于 2013-03-27T16:28:18.323 回答