【问题标题】:How to read an array from a file, sort it, and print it into another file?如何从文件中读取数组,对其进行排序,然后将其打印到另一个文件中?
【发布时间】:2013-07-20 04:47:06
【问题描述】:

我的本​​地机器上有一个名为“data.in”的文件,其中包含以下内容:

1
5
6
6
8
10
33
24
20
3

以及源代码:

#include <stdio.h>

int main (void)
{
    int n,i,a,V[i],ch,aux;
    FILE *f1, *f2;

    f1 = fopen("data.in", "r");
    f2 = fopen("data.out", "w"); //create data.out

    char line[1024];
    n = 0;
    while( fgets(line,sizeof(line),f1) != NULL)
       n++; // n = number of lines from the file

    for (i=0; i<n; i++)
        fscanf(f1,"%d", &V[i]); //reading the array from data.in

    do {
        ch=0;
        for (i=0; i<n-1; i++)
            if (V[i]>V[i+1])
            {
                aux=V[i]; V[i]=V[i+1]; V[i+1]=aux; ch=1;
            }
    } while (ch); //Bubble sort

    for (i=0; i<n; i++)
        fprintf(f2, "%d\n", V[i]); // print the array into data.out

    fclose(f1);
    fclose(f2);

}

编译正常,但每当我执行它时,data.out 只包含:

0
0
0
0
0
0
0
0
0
0

我什至尝试只打印数组,但它仍然是一堆零。 我什至尝试修改 data.in 以使所有数字都在同一行,但输出仍然只是一堆零。我一定是错过了什么……

我有点卡在这里所以任何帮助将不胜感激。

【问题讨论】:

  • 代码块请缩进,否则很难阅读和理解。
  • 我猜你的程序在行计数周期中运行在文件末尾,而不是你只读取 0。尝试重新打开文件或在文件开头再次读取它。
  • int n,i,a,V[i], 我不太确定您对 V 的声明有何期望。不管你期待什么,我很确定你没有得到它。
  • 我几乎可以肯定 V[i] 是错误的。我不知道你的代码是怎么运行的
  • 否;阅读代码不正确。您阅读整个文件以了解它有多大,然后从最后继续阅读。重读前必须倒带。如果您费心检查您调用的函数的返回状态,它们会告诉您存在问题(例如,fscanf() 调用将返回 EOF)。您应该检查您的fopen() 调用是否也成功,尽管事实上它们必须没问题,因为您的代码没有崩溃。你也应该推迟V的定义,直到你知道n的值,你可以写int V[n];

标签: c arrays file sorting


【解决方案1】:

如果您使用动态内存分配,您既不需要定义固定大小的数组,也不需要冒需要比分配更多空间的风险,也不需要重新读取文件。 (另一方面,对于几十个甚至几千个数字的文件,这可能是多余的。)

您也可以使用标准库排序函数qsort(),而不是使用冒泡排序。当然,对于您正在处理的数据大小,qsort() 和冒泡排序之间的差异不太可能容易衡量,但是如果您从数十个数字移动到数千个数字,O(N 2) 和 O(N log N) 算法变得显而易见。 (请参阅How to sort an array of structures in C?,了解为什么下面的intcmp() 是这样写的。)

此外,您应该错误检查输入操作(和内存分配)。使用像代码中显示的err_exit() 函数这样的简单函数可以使错误报告简洁,因此不那么繁琐,并且减少了省略错误检查的借口。在我的大多数程序中,我使用err_exit() 的一个更有特色的变体,但那是在它自己的源文件中带有自己的头文件的代码。许多程序(包括下面的重写)不检查输出操作是否成功;他们可能应该这样做。

这会导致类似这样的代码:

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

static int intcmp(const void *p1, const void *p2);
static void err_exit(const char *fmt, ...);

int main(void)
{
    static const char n1[] = "data.in";
    static const char n2[] = "data.out";
    FILE *f1 = fopen(n1, "r");
    FILE *f2 = fopen(n2, "w");
    int *V = 0;
    char line[1024];
    int n = 0;
    int max_n = 0;

    if (f1 == 0)
        err_exit("Failed to open file %s for reading\n", n1);
    if (f2 == 0)
        err_exit("Failed to open file %s for writing\n", n2);

    while (fgets(line, sizeof(line), f1) != NULL)
    {
        int v;
        if (sscanf(line, "%d", &v) != 1)
            break;
        if (n == max_n)
        {
            int new_n = (max_n + 2) * 2;
            int *new_V = realloc(V, new_n * sizeof(*V));
            if (new_V == 0)
                err_exit("Failed to realloc array of size %d\n", new_n);
            V = new_V;
            max_n = new_n;
        }
        V[n++] = v;
    }

    qsort(V, n, sizeof(V[0]), intcmp);

    for (int i = 0; i < n; i++)
        fprintf(f2, "%d\n", V[i]);

    free(V);
    fclose(f1);
    fclose(f2);
    return(0);
}

static int intcmp(const void *p1, const void *p2)
{
    int i1 = *(int *)p1;
    int i2 = *(int *)p2;
    if (i1 < i2)
        return -1;
    else if (i1 > i2)
        return +1;
    else
        return 0;
}

static void err_exit(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    exit(1);
}

【讨论】:

  • 谢谢先生!这种类型的代码肯定是高质量的,但在这一点上我很难完全理解。我想要一些没有太多功能的简单东西。我复制了你的代码,当我完全理解里面的所有内容时,我一定会喜欢重新阅读它!
  • 是的;对于您的直接问题,我的回答太过分了。倒带操作和修复V 的定义就足够了。您应该在代码中添加错误检查;由于data.in 丢失或data.out 无法创建/截断而导致程序崩溃并不好。如果您使用固定大小的数组,则应确保不会溢出它。如果您使用 VLA,您将受到运行时的摆布;如果数组的大小太大,你的代码会崩溃。
  • 函数的使用和编写很重要。 err_exit() 函数写成 8 行(函数定义加 1 行)。它的每 1 行调用替换 4 行代码(左大括号、fprintf()exit()、右大括号),因此每次使用节省了三行代码。在显示的三个用途的代码中,节省为零,但main() 中的代码因此更简单(更好)。使用常见工作的功能。诚然,它使用了您可能还没有学过的技术,但它实际上非常简单,并且该功能基本上是样板文件。我已经多次输入了该代码。
  • +1 您的回答非常全面,err_exit 让我印象深刻。我学到了很多。
【解决方案2】:

数完文件的行数后,fi 已更改。

你需要重置fi 喜欢:

fseek(f1, 0, SEEK_SET);

然后重新从头读取文件。

那么你可能会在“data.out”中得到正确的输出。

【讨论】:

  • 这是一项必要的更改。你对数组V的定义还是有问题。
  • @JonathanLeffler 是的,我想他/她知道V 的问题。不知道这一点,代码甚至无法成功编译。
  • 实际上,使用 C99 编译器 gcc -std=c99 -Wall -Wextra -O -c x39.c,原始代码给出了输出:x39.c: In function ‘main’:x39.c:5: warning: unused variable ‘a’。即使在非常严格的警告级别下,它也可以编译。它只是不能很好地工作。
  • @JonathanLeffler 抱歉,我没有看到 i 的定义,它在 V 的定义之前。所以我认为它一开始就没有编译。在我的测试中,我将V[i] 硬编码为V[100]。你是对的,它确实可以编译。
【解决方案3】:

除了声明 V (i 的值是多少?提示:它可能是零,也可能是 -2147483648)之外,您还使用 fgets 来获取文件末尾之前的行数。之后你需要rewind(f1);,这样你才能再次读取文件。否则你最终用 fscanf 什么也读不出来。

我是否可以建议使用 fgets 并在 fgets 的循环中同时使用 sscanf 从您读取的行中获取字符串?为什么要读取整个文件两次?

while (fgets(line, sizeof line, f1) != NULL) {
    sscanf(line, "%d", &V[n]);
    n++;
}

您应该对 sscanf 的返回值进行错误检查,但大体思路在代码中。那么你就不需要那个for循环,也不需要倒带文件来读取它两次。

【讨论】:

  • 你说得对,这样的代码效率会高很多!
【解决方案4】:

为了快速修复,您必须将数组声明 V[i]int ...,V[i]... 更改为 V[2000]; 这是因为在分配数组时,您必须知道它有多少项目,例如 V[2000] 它将有 2000 个项目,索引从 0 到 1999。

在 C99 中,您可以使用变量来获得不同的数组大小运行时...但是您必须有一个定义的值,i 在行中并不清楚。

然后,您不知道文件中有多少行,最简单的方法是固定数组大小并进行 som 控制以确保您不会溢出数组。

像这样更改您的代码:

const int my_max_numbers = 10; // test it with more than 10 items and change for your likings
int n,i,a,ch,aux;
int V[my_max_numbers];
...

如果你想声明一个正确大小的数组,你可以改变你的原始代码,不要在文件开头声明数组V,但是在你阅读行数并计算行数之后,你必须使用C99标准。

while( fgets(line,sizeof(line),f1) != NULL)
    n++; // n = number of lines from the file

int V[n];
// here you have to rewind the file to the beginning
fseek(f1,0L,SEEK_SET);

for (i=0; i<n; i++)
    fscanf(f1,"%d", &V[i]); //reading the array from data.in

Manpage for fseek

【讨论】:

  • 您的声明和“fseek(f1, 0, SEEK_SET);”数数后现在可以正常工作了,谢谢!
【解决方案5】:

我认为你的 V[i] 数组应该在使用之前进行初始化

【讨论】:

  • 正如所写,V 是一个 VLA(可变长度数组),您不能为 VLA 编写初始化程序。而且,如果您接下来要做的是将值读取到数组中,则不需要对其进行初始化;读取操作有效地初始化它。
  • 他应该使用malloc吗?
  • 可以编写程序以使用malloc(),但您也不能为动态分配的数组编写初始化程序。您只能为固定大小的数组编写初始化程序。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-19
  • 2016-10-20
  • 1970-01-01
  • 1970-01-01
  • 2022-01-26
  • 1970-01-01
相关资源
最近更新 更多