简体   繁体   English

C 从二进制文件写入/读取数据

[英]C Write / Read Data From Binary File

UPDATE更新

IBM HC-486 1995 11 12 228 Иванов IBM HC-476 1990 1 42 218 Васильев

So i kinda try to read two records.所以我有点尝试阅读两条记录。 First one fits out well.第一个很适合。 Second looks bad.第二个看起来很糟糕。 I kinda fixed suggestions thanks a lot it helped to move forward.我有点固定建议,非常感谢它有助于向前发展。 So for now i stuck on outputing two records.所以现在我坚持输出两条记录。 Result is ->结果是->

mark = IBM HC-486 year = 1995 month = 11 day = 12 numroom = 228 lastname = Ивановmark =  IBM HC-47 year = 6 month = 1990
 day = 1 numroom = 42 lastname = 218mark =  Васи� year = 6 month = 1990 day = 1 numroom = 42 lastname = �ьев

Making a binary file out of structs, attempting to print out all cointaining..用结构制作二进制文件,尝试打印出所有包含的内容。

ONLY scanf/printf/FILE/struct仅 scanf/printf/FILE/struct

Here's a code...这是一个代码...

Lab.h实验室.h

#pragma once
    void input();
    void find();
    int getdays(int year, int month);
    void correction();
    void print();

Lab.cpp实验室.cpp

#include "Lab.h"
#include <stdio.h>      //FILE
#include <iostream>
#include <conio.h>      //getch
#include <windows.h>
#include <io.h>
struct Computer
{
    wchar_t mark[11];
    int year;
    int month;
    int day;
    unsigned char numroom;
    wchar_t lastname[20];
};
void input()
{
    FILE *inputFile, *outputFile;
    fopen_s(&outputFile, "output.dat", "wb");
    fopen_s(&inputFile, "input.txt", "r");
    Computer c;
    while (fgetws(c.mark, 11, inputFile))
    {
        fscanf_s(inputFile, "%d", &c.year);
        fscanf_s(inputFile, "%i", &c.month);
        fscanf_s(inputFile, "%i", &c.day);
        fscanf_s(inputFile, "%hhu", &c.numroom);
        fwscanf_s(inputFile, L"%s", c.lastname, _countof(c.lastname));
        fwrite(&c, sizeof(struct Computer), 1, outputFile);
    }
    _fcloseall();
    return;
}
void find()
{
    FILE *outputFile;
    fopen_s(&outputFile, "output.dat", "rb+");
    Computer c;
    while (fread(&c, sizeof(struct Computer), 1, outputFile))
    {
        if (c.year == 1995 && wcscmp(L"IBM HC-486", c.mark) == 0)
        {
            wprintf_s(L"\nmark = %s year = %i month = %i day = %i numroom = %i lastname = %s", 
                c.mark, c.year, c.month, c.day, c.numroom, c.lastname);
            _getch();
            _fcloseall();
            return;
        }
    }
    _getch();
    return;
}

int getdays(int year, int month)
{
    int days = 0;
    if (month == 4 || month == 6 || month == 9 || month == 11)
        days = 30;
    else if (month == 2)
    {
        bool leapyear = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
        if (leapyear == 0)
            days = 28;
        else
            days = 29;
    }
    else
        days = 31;
    return days;
}

void correction()
{
    FILE* outputFile;
    fopen_s(&outputFile, "output.dat", "rb+");
    fseek(outputFile, 0, 0);
    Computer c;
    long item = 0;
    while (fread(&c, sizeof(struct Computer), 1, outputFile))
    {

        while (c.month < 1 || c.month > 12)
        {
            wprintf_s(L"mark = %s year = %i month = %i day = %i numroom = %i lastname = %s",
                c.mark, c.year, c.month, c.day, c.numroom, c.lastname);
            wprintf_s(L"%s%i", L"Некорректный номер месяца \nПожалуйста введите другой номер месяца:", c.month);
            scanf_s("%i", &c.month);
            fseek(outputFile, item * sizeof(struct Computer), 0);
            fwrite(&c, sizeof(struct Computer), 1, outputFile);
        }
        while (c.day < 1 || c.day > getdays(c.year, c.month))
        {
            wprintf_s(L"mark = %s year = %i month = %i day = %i numroom = %i lastname = %s",
                c.mark, c.year, c.month, c.day, c.numroom, c.lastname);
            wprintf_s(L"%s%i", L"Некорректный номер дня\nПожалуйста введите другой номер дня:", c.day);
            scanf_s("%i", &c.day);
            fseek(outputFile, item * sizeof(struct Computer), 0);
            fwrite(&c, sizeof(struct Computer), 1, outputFile);
        }
        item += 1;
    }
    _getch();
    _fcloseall();
    return;
}
void print()
{
    FILE* outputFile;
    fopen_s(&outputFile, "output.dat", "rb+");
    fseek(outputFile, 0, SEEK_SET);
    Computer c;
    while (fread(&c, sizeof(struct Computer), 1, outputFile))
    {
        wprintf_s(L"mark = %s year = %d month = %i day = %i numroom = %i lastname = %s",
            c.mark, c.year, c.month, c.day, c.numroom, c.lastname);
    }
    _getch();
    _fcloseall();
    return;
}

Lab2.cpp实验室2.cpp

#include <windows.h>
#include "Lab.h"
int main()
{
    SetConsoleCP(65001);
    SetConsoleOutputCP(65001);

    input();
    print();

    //find();
    //correction();
    return 0;
}

There are two main problems with that.这样做有两个主要问题。 The first one has already been pointed out by Johnny Mopp, in that your call to fgetws requires a minimum size of c.mark of 11 elements, so you are overflowing it. Johnny Mopp 已经指出了第一个,因为您对 fgetws 的调用需要至少 11 个元素的c.mark大小,因此您将其溢出。

And regarding why you read 0 as the year, it's due to this overflow and the fact that you are trying to manually add the NULL terminator to c.mark :关于为什么您将 0 读为年份,这是由于此溢出以及您尝试手动将 NULL 终止符添加到c.mark的事实:

c.mark[wcslen(c.mark) - 1] = '\0';

As you are already overflowing c.mark , this happens to go right into c.year and sets it to 0 (try putting this line right after reading c.mark and you will see that you read the correct year). As you are already overflowing c.mark , this happens to go right into c.year and sets it to 0 (try putting this line right after reading c.mark and you will see that you read the correct year).

In fact, this is not necessary because fgetws already includes the NULL terminator (your call will only read 10 characters and add as character 11 the '\0'.事实上,这不是必需的,因为fgetws已经包含了 NULL 终止符(您的调用将只读取 10 个字符并在字符 11 中添加 '\0'。

Event then, take into account that your attempt to add the NULL terminator is bound to fail, because wcslen does not work unless there is already a NULL terminator, so you are trying to set a NULL terminator where there is already one.事件然后,考虑到您尝试添加 NULL 终止符肯定会失败,因为wcslen除非已经存在 NULL 终止符,否则您将尝试设置一个 Z6C3E226B4D4794EC29Z 终止符。 Besides, you are removing the last character in the string due to the -1 .此外,由于-1 ,您正在删除字符串中的最后一个字符。

Imagine that you have a string with only one character L"A".想象一下,您有一个只有一个字符 L"A" 的字符串。 If you make that operation, wcslen will return 1 and if you substract 1, you are doing c.str[0] = L'\0' , thus converting the string to L"".如果您进行该操作, wcslen将返回 1,如果您减去 1,则您正在执行c.str[0] = L'\0' ,从而将字符串转换为 L""。 In this case, it would be better using sizeof instead of wcslen, because it would return 11 regardless of the content, and substracting 1 you would get c.str[10] = '\0' which is what you really want.在这种情况下,最好使用sizeof而不是 wcslen,因为无论内容如何,它都会返回11 ,并且减去 1 你会得到c.str[10] = '\0'这就是你真正想要的。

Nevertheless, as I said before, it's unnecessary because fgetws already takes care of the NULL terminator for you (take a look at the Remarks section of https://docs.microsoft.com/es-es/cpp/c-runtime-library/reference/fgets-fgetws?view=msvc-160 ).尽管如此,正如我之前所说,这是不必要的,因为fgetws已经为您处理了 NULL 终止符(请查看 https 的备注部分: //docs.microsoft.com/es-es/cpp/c-runtime-library /reference/fgets-fgetws?view=msvc-160 )。

UPDATE更新

Regarding the decission on when to end reading, I usually read until I run out of data, regardless of the file size.关于何时结束阅读的决定,我通常会阅读直到我用完数据,而不管文件大小。 That would mean making the loop run forever with while (true) , and checking the output of the fgetws and fscanf , as others have suggested.正如其他人所建议的那样,这意味着使用while (true)使循环永远运行,并检查fgetwsfscanf的 output 。 If you take a look at the documentation of fgetws (the link I wrote before) you can see in the Return value section that it returns a pointer to the buffer on success (this is not useful normally) but it returns NULL in case of an error or end-of-file.如果您查看fgetws的文档(我之前写的链接),您可以在“返回值”部分看到它在成功时返回指向缓冲区的指针(这通常没有用)但它返回 NULL错误或文件结束。 You can use this to break the loop if there is an error when you read mark by doing:如果在读取标记时出现错误,您可以使用它来中断循环:

if (fgetws(c.mark, 11, inputFile) == NULL)
    break;

Similarly, fscanf_s returns EOF in case of an error or end-of-file ( https://docs.microsoft.com/es-es/cpp/c-runtime-library/reference/fscanf-s-fscanf-sl-fwscanf-s-fwscanf-sl?view=msvc-160 ), so you could possibly add that condition whenever you read a value using fscanf_s .同样, fscanf_s在出现错误或文件结束时返回EOF ( https://docs.microsoft.com/es-es/cpp/c-runtime-library/reference/fscanf-s-fscanf-sl-fwscanf -s-fwscanf-sl?view=msvc-160 ),因此您可以在使用fscanf_s读取值时添加该条件。 For instance:例如:

if (fscanf_s(inputFile, "%d", &c.year) == EOF)
    break;

And so with the rest. rest 也是如此。 Or you could go just with the condition in fgetws , but that could lead to corrupt records if you have incomplete lines (where the fgetws succeeds but one or more of the fscanf_s fails).或者您可以只使用 fgetws 中的条件fgetws ,但是如果您的行不完整(其中fgetws成功但一个或多个fscanf_s失败),则可能导致记录损坏。 In the end it all boils down to how much work you want to put and how resilient do you want your code to be against invalid inputs.最后,这一切都归结为您想要投入多少工作以及您希望您的代码对无效输入具有多大的弹性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM