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

问题描述

更新

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

这是一个代码...

实验室.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,&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,"rb+");
    Computer c;
    while (fread(&c,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,"rb+");
    fseek(outputFile,0);
    Computer c;
    long item = 0;
    while (fread(&c,outputFile))
    {

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

由于您已经溢出 c.mark,这恰好进入 c.year 并将其设置为 0(尝试在阅读 c.mark 后立即放置此行,您将看到您阅读正确的年份)。

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

事件然后,考虑到您尝试添加 NULL 终止符的尝试必然会失败,因为除非已经存在 NULL 终止符,否则 wcslen 不起作用,因此您试图在有空终止符的地方设置一个 NULL 终止符已经是一个了。此外,由于 -1,您正在删除字符串中的最后一个字符。

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

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

更新

关于何时结束阅读的决定,无论文件大小,我通常都会阅读到数据用完为止。这意味着使用 while (true) 使循环永远运行,并像其他人建议的那样检查 fgetwsfscanf 的输出。如果你看一下 fgetws 的文档(我之前写的链接),你可以在返回值部分看到它在成功时返回一个指向缓冲区的指针(这通常没有用)但它返回 NULL如果出现错误或文件结尾。如果在读取标记时出现错误,您可以使用它来中断循环:

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

类似地,fscanf_s 会在出现错误或文件结束 (https://docs.microsoft.com/es-es/cpp/c-runtime-library/reference/fscanf-s-fscanf-s-l-fwscanf-s-fwscanf-s-l?view=msvc-160) 的情况下返回 EOF,因此您可以在使用 {{ 读取值时添加该条件1}}。例如:

fscanf_s

其余的也是如此。或者,您可以仅使用 if (fscanf_s(inputFile,"%d",&c.year) == EOF) break; 中的条件,但如果您的行不完整(其中 fgetws 成功但一个或多个 fgetws 失败),这可能会导致记录损坏。归根结底,这一切都归结为您想要投入多少工作以及您希望您的代码对无效输入有多大的弹性。