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

C Write / Read Data From Binary File

更新

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

所以我有点尝试阅读两条记录。第一个很合适。第二个看起来很糟糕。 我有点固定的建议,非常感谢它有助于前进。所以现在我坚持输出两条记录。 结果是 ->

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 = �ьев

用结构制作一个二进制文件,试图打印出所有包含的内容..

仅scanf/printf/FILE/struct

这是一个代码...

Lab.h

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

Lab.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

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

    input();
    print();

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

这有两个主要问题。第一个已经被 Johnny Mopp 指出,因为你对 fgetws 的调用需要至少 c.mark 的 11 个元素,所以你溢出了它。

关于为什么将 0 读作年份,这是由于此溢出以及您试图手动将 NULL 终止符添加到 c.mark:

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

因为你已经溢出 c.mark,这恰好进入 c.year 并将其设置为 0(尝试在阅读 c.mark 后立即放置此行,你会看到你读对了年份)。

事实上,这不是必需的,因为 fgetws 已经包含 NULL 终止符(您的调用将只读取 10 个字符并添加字符 11 '\0'。

Event 然后,考虑到你尝试添加 NULL 终止符的尝试注定会失败,因为 wcslen 不起作用,除非已经有一个 NULL 终止符,所以你正在尝试设置一个 NULL 终止符已经有一个的地方。此外,由于 -1.

,您要删除字符串中的最后一个字符

假设您有一个只有一个字符 L"A" 的字符串。如果您执行该操作,wcslen 将 return 1,如果您减去 1,您将执行 c.str[0] = L'[=22=]',从而将字符串转换为 L""。在这种情况下,最好使用 sizeof 而不是 wcslen,因为它会 return 11 而不管内容如何,​​减去 1 你会得到 c.str[10] = '[=25=]' 这就是你真的想要。

然而,正如我之前所说,这是不必要的,因为 fgetws 已经为您处理了 NULL 终止符(请查看 https://docs.microsoft.com/es-es/cpp/c-runtime-library/reference/fgets-fgetws?view=msvc-160 的备注部分)。

更新

关于什么时候结束阅读的决定,我一般都是读到运行没数据为止,不管文件大小。这意味着使用 while (true) 永远循环 运行,并检查 fgetwsfscanf 的输出,正如其他人所建议的那样。如果您查看 fgetws(我之前写的 link)的文档,您可以在 Return 值部分看到它 return 是指向缓冲区的指针成功(这通常没有用)但它 returns NULL 在错误或文件结束的情况下。如果在读取标记时出现错误,可以使用它来中断循环:

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

类似地,fscanf_s returns EOF 以防出现错误或文件结束 (https://docs.microsoft.com/es-es/cpp/c-runtime-library/reference/fscanf-s-fscanf-s-l-fwscanf-s-fwscanf-s-l?view=msvc-160),因此您可以随时添加该条件您使用 fscanf_s 读取了一个值。例如:

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

其余的也是如此。或者您可以只使用 fgetws 中的条件,但是如果您有不完整的行(其中 fgetws 成功但一个或多个 fscanf_s 失败),这可能会导致记录损坏。最后,这一切都归结为您想要投入多少工作以及您希望您的代码对无效输入有多大的弹性。