我需要从文本文件中读取数字,然后将它们存储在矩阵中,但我一直得到一个空指针

时间:2018-04-21 22:54:17

标签: c pointers fopen

我需要从文本文件中读取数字,然后将它们存储在矩阵中。这是我到目前为止所得到的:

#include <stdio.h>

int main() 
{   
    int MazeMatrix[64];     
    int i;   

    FILE *mazefile; //created a pointer named mazefile  
    mazefile = fopen("m5.txt","r");         
    if (mazefile == NULL)   
    {       
        printf("Didn't work");          
    } 
    for (i = 0; i < 32; i++) 
    {       
        fscanf_s(mazefile, "%d", &MazeMatrix[i]);   
    }       
    printf("%d", &MazeMatrix);   
}

它构建但我得到错误stream!=nullptr

2 个答案:

答案 0 :(得分:1)

以下提议的代码:

  1. 干净地编译
  2. 执行所需的功能
  3. 正确检查并处理错误
  4. 更正阵列上的访问权限
  5. 消除了魔法&#39;编号
  6. 如果内部数组已满或EOF遇到,则
  7. 退出读取循环
  8. 使用&#39; \ n&#39;正确结束每个格式字符串所以文本将立即输出
  9. printf()
  10. 的调用中正确引用了MazeMatrix []
  11. 删除了两个无关的后退标记&#39;在发布的代码中
  12. 现在,建议的代码:

    #include <stdio.h>
    #include <stdlib.h>
    
    #define MATRIX_SIZE 64
    
    
    int main( void )
    {
        int MazeMatrix[ MATRIX_SIZE ];
        int i = 0;    // initialized per @chux comment
        FILE *mazefile = fopen("m5.txt","r");
        if ( !mazefile )
        {
            perror( "fopen failed" );
            exit( EXIT_FAILURE );
        }
    
        while( i< MATRIX_SIZE &&  fscanf(mazefile, "%d", &MazeMatrix[i]) != 1 )
        {
            fprintf( stderr, "failed to read a matrix entry\n" );
            fclose( mazefile );
            exit( EXIT_FAILURE );
        }
    
        printf( "%d\n", MazeMatrix[0] );
    }
    

答案 1 :(得分:0)

我想通过直接回答你的问题来为这篇文章做准备:

  

我需要从文本文件中读取数字,然后将它们存储在矩阵中,但我一直得到一个空指针[它构建但我得到错误stream != nullptr]。

您发布的代码中有几件事可能出错。最有可能的是,文件m5.txt实际上并不存在于您指定的目录中。

As stated in the Microsoft CRT Documentationfscanf_s和相关函数通过错误和参数检查提供额外的安全性。查看您的代码,文件不存在,至少在同一目录中(甚至是您在代码中指定的名称),似乎是最可能的罪魁祸首。同样,from the documentation

  

在Debug版本中,无效参数宏通常会在调用调度函数之前引发失败的断言和调试器断点。

此宏/断言失败似乎是导致stream != nullptr错误的原因。

当您检查文件指针时,您只是想尝试打开,您需要检查以确保它不是NULL,这很棒,但是您不会这样做。 t正确处理错误。正如其他人所指出的那样,如果您无法打开输入文件,则应该退出。这应该是有意义的,因为如果应用程序的全部内容是读取数据并处理它,并且在尝试读取数据时失败,那么应用程序的其余部分将很难做任何事情。

以下是我对您的代码的重写,因为我建议您这样做。它似乎有点矫枉过正(对于这个玩具示例而言),但这里的重点是习惯于在更大,更强大的环境中看到代码。你的眼睛可能需要一点点调整到更多的代码,但尽力坚持下去;我已尽力解释每个设计/实施决策背后的理由。

首先,我要注意矩阵是两个 - 维MxN结构。鉴于你声明你的数组有64个元素,我将假设你正在寻找使用方形,8×8矩阵的帮助。

要声明我们的矩阵,我们执行以下操作。首先,我们定义一个常量N来表示矩阵的单行中的元素数量。由于我们正在使用方形矩阵(例如M = N),因此我不会为M声明一个单独的常量,我只使用单个矩阵。

#define N (8)

然后,我们声明我们的矩阵:

int matrix[N][N] = { 0 };

在您的计划中,您声明了MazeMatrix[64]int iFILE *mazefile,而没有立即初始化它们。从编译器要求您在函数开头声明所有变量的日子开始,这通常仍然是一种保留。现在不再是这种情况了,所以我建议在干净,清晰,小代码的精神下,在需要时声明变量,并在声明变量时初始化变量。在上面的代码中,我们将整个矩阵清零。

在您的代码中,接下来的两行是:

FILE *mazefile; //created a pointer named mazefile
mazefile = fopen("m5.txt", "r");

我们知道您创建了一个名为mazefile的指针,因为在该注释之前的代码中,您创建了一个名为mazefile 的指针。请记住a)您应该在按照之前的讨论声明后立即初始化此文件句柄,并且2)评论很好,但前提是它们必须(并且有效)帮助其他人理解您的代码。在这种情况下,您的评论虽然是善意的,但实际上只是多余的代码噪音,可以安全删除。

这是我的程序实现。请注意,虽然我为了简洁而排除了函数原型,但我在本文中进一步描述了这些函数。

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

#define DEFAULT_FILE ("m4.txt")

#define N (8)

// [Function prototypes excluded for brevity]


int main(int argc, char *argv[])
{
    const char* inputFileName = getInputFileName(argc, argv);

    int matrix[N][N] = { 0 };

    PrintMatrix(matrix, "Original Matrix");

    FILE *inputFile = readFile(inputFileName);

    ReadMatrix(matrix, inputFile);
    PrintMatrix(matrix, inputFileName);
    PrintSeparator();

    CloseFile(&inputFile);

    return EXIT_SUCCESS;
}

您会注意到我的版本中包含argcargv。每次想要在不同的文件中阅读时,继续重新编译程序变得很麻烦,而且实际上没有理由这样做。 argc是&#34;参数count&#34; argv是&#34;参数向量。&#34; argc始终至少为1,因为程序的名称是第一个参数(因此argv[0]始终是程序的名称)。

为了确定要打开的文件,我们定义了一个函数getInputFileName,如下所示:

const char* getInputFileName(int argc, char *argv[]) {
    if (argc == 2) {
        return argv[1];
    }

    return DEFAULT_FILE;
}

还有其他方法可以处理程序的空参数。您只需将一条消息打印到控制台,说明您必须传递参数。我选择简单地定义另一个宏来保存我在测试期间轻松制作的随机文件的名称。

#define DEFAULT_FILE ("m4.txt")

注意:我使用简单的随机数生成器程序通过m8.txt生成了我的数据样本文件m1.txt。如果有帮助的话,我会在这个答案的底部包含代码。

分配了我们的矩阵并确定了我们的输入文件之后,我们现在按原样打印矩阵,以验证它确实已经归零。

void PrintMatrix(int m[][N], const char* name) {
    PrintSeparator();
    PrintMatrixName(name);
    PrintMatrixElements(m);
    printf("\n\n\n");
}

这个函数只调用我写的其他三个函数。这本来可以简单得多,但这有两个目的:首先,你可以看到一个简单的抽象实现,我们将问题减少到越来越小的字节,我们一次只处理必要的复杂性。其次,我对输出非常好,所以看起来更好,只是为了踢。

以下是其他三个函数:PrintSeparator只打印宽度为80的=符号的水平线。Check out this question on Software Engineering Stack Overflow解释为什么80通常是标准。作为some code style advocates claim,人类在60-80柱屏幕上读得最好,我倾向于同意,所以我在这里硬编码,半希望它会导致群众的一致性,但你是欢迎改变它。同样,它只是为了使输出相当漂亮,它本质上毫无意义。

void PrintSeparator(void) {
    printf("\n\n");

    for (int i = 0; i < 80; i++) {
        putchar('=');
    }

    printf("\n\n");
}

此函数只打印传入的字符串。大量格式说明符用于将名称居中到矩阵。这特定于具有此特定间距的8x8矩阵,但如果您修改程序以通过argv接受多个文件,则此功能将允许您为每个输出矩阵打印唯一名称,例如,如果您使用文件名作为矩阵名称。

void PrintMatrixName(const char* name) {
    printf("\n\n\t\t\t\t%s\n\n", name);
}

这个函数PrintMatrixElements实际上是打印矩阵的主要功能。在printf格式规范中,我将元素宽度设置为2,因为我的数据使用了1到10的随机数,以及两个空格以便于阅读。外环还可以打印额外的空间,因此矩阵看起来不太宽。在我看来,这有助于提高可读性。

void PrintMatrixElements(m) {
    printf("\n\n");

    for (int i = 0; i < N; i++) {
        printf("\t\t\t");

        for (int j = 0; j < N; j++) {
            printf("%2d  ", m[i][j]);
        }

        printf("\n\n");
    }

    printf("\n\n");
}

该计划的下一行特别重要:

FILE *inputFile = readFile(inputFileName);

通常,您只需声明一个文件指针并通过调用fopen对其进行初始化,如下所示:

FILE *inputFile = fopen(inputFileName);

就像我之前说过的那样,你真的想在同一个函数中使用相同的抽象级别,因此所需的错误检查并不能完全符合我们想要的主流。这就是我定义一个也返回文件指针的辅助函数openFile的原因。通过这种方式,我们也可以使用它来初始化文件指针,就像我们使用fopen一样,还有额外的内置错误检查功能。

FILE* openFile(const char* filename, const char* mode) {
    FILE *newFileHandle = fopen(filename, mode);

    if (!newFileHandle) {
        perror("Failed to open input file");

        exit(EXIT_FAILURE);
    }

    return newFileHandle;
}

现在,我们可以简单地声明一个这样的文件指针:

FILE *inputFile = openFile("file.txt", "r");

但是,如果我们对文件指针inputFile进行命名,显然我们只能从文件中读取。这就是我宣布第二个辅助函数readFile的原因。请注意,我并不是简单地重写openFile并对"r"模式说明符进行硬编码。你应该总是尽量不重复自己。这就是为什么我根据我们之前的函数openFile简单地定义这个新函数的原因,如下所示:

FILE* readFile(const char* filename) {
    FILE *newFileHandle = openFile(filename, "r");

    return newFileHandle;
}

然后我们用这个新函数声明我们的输入文件:

FILE *inputFile = readFile("file.txt");

虽然这一开始看起来似乎多余,但有几个原因可以解释为什么它不像看起来那么疯狂。 [如果您将功能命名为],这有助于增加代码的清晰度。输入文件指针读取现有文件是有道理的,对吧?由于我们已在openFile完成了所有错误检查,因此我们不必在readFile中担心这一点。 抽象。

另外,正如鲍勃叔叔所说,

  

functions should only do one thing.

(他还说to stick to one level of abstraction per function,正如我们在这里所做的那样。)

下一个函数是程序的真正含义和土豆,因为它是将使用给定输入文件填充矩阵的函数。以下是ReadMatrix的代码:

void ReadMatrix(int m[N][N], FILE *inputFile) {
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            int n = fscanf(inputFile, "%d", &m[i][j]);

            if (n == EOF) {
                fprintf(stderr, "\n\n<Premature END OF FILE encountered - not enough elements in input file! [N: %d | Required: %d]>\n\n", (i * N) + j, N * N);

                exit(EXIT_FAILURE);
            } else if (n == 0) {
                fprintf(stderr, "\n\n<Error - incompatible data type in input file at line %d>\n\n", (i * N) + j + 1);

                exit(EXIT_FAILURE);
            }
        }
    }
}

重要的是要注意,此函数假设您的输入文件结构为由新行分隔的简单数字,如下所示:

7
12
56
8
4
66

这是函数的编写方式。另外,请注意我们可以使用for循环而不必使用while循环,因为我们将矩阵的大小设置为宏(在本例中为行和列) N,在这种情况下,我已经定义为8.由于我们在编译时知道矩阵的大小,for循环将完成工作。如果您想让用户在运行时确定矩阵大小,则需要动态分配矩阵并使用while循环来填充它。 (Do not use variable-length arrays

还有一些关于ReadMatrix功能的注意事项。首先,我们调用fscanf并将局部变量n设置为其返回值。然后,我们检查n是否存在两个错误:

首先,如果fscanf完全失败,它甚至可以尝试格式化它的输入值(即...它在读取所有值之前得到EOF),它将引发错误。这就是为什么我们的错误消息说&#34;遇到文件过早结束,&#34;这基本上就是这个。它还包含一个方便的信息消息,让您知道它在崩溃之前读取了多少值,以及它预期的值。

比如说,你的一个输入文件m4.txt只有4个元素:

7
11
84
9

我们的错误检查会产生以下错误消息:

<Premature END OF FILE encountered - not enough elements in input file! [N: 4 | Required: 64]>

其次,我们正在检查n以确保它不为零。如果一切顺利,scanfprintf和co。返回一个等于&#34的整数值;分配的项目数。&#34; [来源:The Standard C Library, by P.J. Plauger]因此,如果scanf返回0,我们就会遇到问题。

例如,如果在您的一个输入文件中,您有kk而不是第17行的数字。我们的错误检查实现将导致以下消息:

<Error - incompatible data type in input file at line 17>

最后,如果发生任何一个错误,我们会立即致电exit并向其传递返回代码EXIT_FAILURE。这只是<stdlib.h>中定义的宏,以及EXIT_SUCCESS,其值为:

EXIT_SUCCESS: 0
EXIT_FAILURE: 1

我们将在上一个函数中使用这个事实:CloseFile。首先,显而易见的问题是:为什么我们定义另一个函数来关闭文件时已经定义了一个文件?

这是一个有效的问题,它又与抽象概念有关。并不是众所周知,fclose实际上返回一个整数值取决于它是成功还是失败(是的,可以失败),所以我们再次想要移动错误检查到不同的抽象级别。因此,我们在此定义CloseFile函数:

void CloseFile(FILE **fileHandle) {
    if (fclose(*fileHandle)) {
        perror("Error Closing Input File");

        exit(EXIT_FAILURE);
    }
}

请注意,我们使用双指针在这里通过引用传递我们的文件指针。我这样做有两个原因:首先,在教学上,记住如何通过引用传递指针是很重要的;第二,如果你想实现Professor Reese's pointer heuristic here,你需要通过引用传递指针。请注意,您需要使用&#34;地址 - &#34;调用CloseFile函数时的运算符。

我知道我可能会遇到海啸信息,但学习C就像那样。 C很简单,但是开始学习它可能很棘手。请不要犹豫,问我任何问题,祝你好运。

生成随机测试数据

您没有包含示例数据集,因此为了生成我的数据集,我假设每行一个数字和每个文件64个数字(我正在测试错误处理的文件除外)。请注意,此程序没有最后一个保护措施。

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

#define N (8)


int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: generate <file-name-1>");

        return EXIT_FAILURE;
    }

    srand(time(NULL));

    FILE *outputFile = fopen(argv[1], "w");

    if (!outputFile) {
        perror("Failed to open output file");

        exit(EXIT_FAILURE);
    }

    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            fprintf(outputFile, "%d\n", rand() % 10 + 1);
        }
    }

    fclose(outputFile);

    return EXIT_FAILURE;
}