После записи структуры в двоичный файл в файле все еще есть обычные символы вместо нечитаемых - PullRequest
0 голосов
/ 07 ноября 2019

Я пытаюсь записать структуру в двоичный файл. Структура состоит из строк и целых чисел. Если бы не было строк, я бы просто взял весь объект и записал бы его в двоичный файл в обычном режиме, но если я сделаю это сейчас, строку также можно легко прочитать.

Поэтому я решил написать каждый атрибут структуры отдельно. Я работал со строками так же, как это упоминалось в ответе Stackoverflow .

Это основная функция, которая должна сохранять структуру в name.bin file.

void saveFileBin(std::string nameOfFile) {
    Person people[SIZE_OF_ARRAY] =
    {Person("Name1", "lastName2", Address("Street1", "City1", "111"), Date(1, 1, 1111)),
    Person("Name2", "lastName2", Address("Street2", "City2", "222"), Date(2, 2, 2222)),
    Person("Name3", "lastName3", Address("Street3", "City3", "333"), Date(3, 3, 3333))};

    std::ofstream myFile(nameOfFile + ".bin", std::ios::binary);
    if (myFile.is_open())
    {
        for (int i = 0; i < SIZE_OF_ARRAY; i++)
        {
            people[i].write(&myFile);
        }
        myFile.close();

        std::cout << "The entire thing is in memory";
    }
    else 
    {
        throw std::exception("Unable to open file");
    }
};

, и это функция для записи каждого атрибута в файл.

void Person::write(std::ofstream* out)
{
    out->write(_name.c_str(), _name.size());
    out->write(_lastName.c_str(), _lastName.size());
    out->write(_residence.getStreet().c_str(), _residence.getStreet().size());
    out->write(_residence.getZip().c_str(), _residence.getZip().size());
    out->write(_residence.getCity().c_str(), _residence.getCity().size());
    std::string day = std::to_string(_birthDate.getDay());
    std::string month = std::to_string(_birthDate.getMonth());
    std::string year = std::to_string(_birthDate.getYear());
    out->write(day.c_str(), day.size());
    out->write(month.c_str(), month.size());
    out->write(year.c_str(), year.size());
}

В результирующем файле есть все для чтения в виде простого текста. Хотя, если я вместо вызова метода main myFile.write((char*)people, sizeof(people));, то он правильно отображает нечитаемые символы, но строковые переменные по-прежнему нормально читаются. Вот почему я пошел с преобразованием в строку всех переменных и затем записал их в файл bin.

Почему мой метод по-прежнему показывает все строки, как будто это даже не двоичный файл? Даже если у меня есть std :: ios :: binary в качестве параметра?

Выходной файл содержит это:

Name1lastName1Street11111City11111111Name2lastName2Street22222City22222222Name3lastName3Street33333City3333333

Принимая во внимание, что если я запишу всю структуру в двоичный файл, он выглядиткак это:

     lÊ87  Name1 ÌÌÌÌÌÌÌÌÌÌ              à^Ê87  lastName1 ÌÌÌÌÌÌ                  Ð_Ê87  Street1 ÌÌÌÌÌÌÌÌ              ÐdÊ87  City1 ÌÌÌÌÌÌÌÌÌÌ               bÊ87  111 ÌÌÌÌÌÌÌÌÌÌÌÌ                    W  ÌÌÌÌ`kÊ87  Name2 ÌÌÌÌÌÌÌÌÌÌ              fÊ87  lastName2 ÌÌÌÌÌÌ                 €iÊ87  Street2 ÌÌÌÌÌÌÌÌ              PbÊ87  City2 ÌÌÌÌÌÌÌÌÌÌ              ÐiÊ87  222 ÌÌÌÌÌÌÌÌÌÌÌÌ                    ®  ÌÌÌÌ€dÊ87  Name3 ÌÌÌÌÌÌÌÌÌÌ               `Ê87  lastName3 ÌÌÌÌÌÌ                p`Ê87  Street3 ÌÌÌÌÌÌÌÌ               gÊ87  City3 ÌÌÌÌÌÌÌÌÌÌ              ðbÊ87  333 ÌÌÌÌÌÌÌÌÌÌÌÌ                    
  ÌÌÌÌ lÊ87  Name1 ÌÌÌÌÌÌÌÌÌÌ              à^Ê87  lastName1 ÌÌÌÌÌÌ                Ð_Ê87  Street1 ÌÌÌÌÌÌÌÌ              ÐdÊ87  City1 ÌÌÌÌÌÌÌÌÌÌ               bÊ87  111 ÌÌÌÌÌÌÌÌÌÌÌÌ                    W  ÌÌÌÌ`kÊ87  Name2 ÌÌÌÌÌÌÌÌÌÌ              fÊ87  lastName2 ÌÌÌÌÌÌ                 €iÊ87  Street2 ÌÌÌÌÌÌÌÌ              PbÊ87  City2 ÌÌÌÌÌÌÌÌÌÌ              ÐiÊ87  222 ÌÌÌÌÌÌÌÌÌÌÌÌ                    ®  ÌÌÌÌ€dÊ87  Name3 ÌÌÌÌÌÌÌÌÌÌ               `Ê87  lastName3 ÌÌÌÌÌÌ                p`Ê87  Street3 ÌÌÌÌÌÌÌÌ               gÊ87  City3 ÌÌÌÌÌÌÌÌÌÌ              ðbÊ87  333 ÌÌÌÌÌÌÌÌÌÌÌÌ                    
  ÌÌÌÌ lÊ87  Name1 ÌÌÌÌÌÌÌÌÌÌ              à^Ê87  lastName1 ÌÌÌÌÌÌ                Ð_Ê87  Street1 ÌÌÌÌÌÌÌÌ              ÐdÊ87  City1 ÌÌÌÌÌÌÌÌÌÌ               bÊ87  111 ÌÌÌÌÌÌÌÌÌÌÌÌ                    W  ÌÌÌÌ`kÊ87  Name2 ÌÌÌÌÌÌÌÌÌÌ              fÊ87  lastName2 ÌÌÌÌÌÌ                 €iÊ87  Street2 ÌÌÌÌÌÌÌÌ              PbÊ87  City2 ÌÌÌÌÌÌÌÌÌÌ              ÐiÊ87  222 ÌÌÌÌÌÌÌÌÌÌÌÌ                    ®  ÌÌÌÌ€dÊ87  Name3 ÌÌÌÌÌÌÌÌÌÌ               `Ê87  lastName3 ÌÌÌÌÌÌ                p`Ê87  Street3 ÌÌÌÌÌÌÌÌ               gÊ87  City3 ÌÌÌÌÌÌÌÌÌÌ              ðbÊ87  333 ÌÌÌÌÌÌÌÌÌÌÌÌ                    
  ÌÌÌÌ

РЕДАКТИРОВАТЬ: В соответствии с запросом здесь есть заголовок для Person.h

#pragma once
#ifndef PERSON_H
#define PERSON_H
#include <string.h>
#include "Address.h"
#include "Date.h"
#include <fstream>

struct Person {
public:
    Person(std::string name, std::string last_name, Address _residence, Date birthDate);
    Person();
    friend std::ostream& operator<<(std::ostream& os, const Person& p);
    friend std::istream& operator>>(std::istream& is, Person& p);
    std::string getName() const { return _name; }
    std::string getLastName() const { return _lastName; };
    Address getResidence() const { return _residence; };
    Date getDate() const { return _birthDate; };
    void read(std::ifstream *in);
    void write(std::ofstream *out);
private:
    std::string _name;
    std::string _lastName;
    Address _residence;
    Date _birthDate;
};
#endif // !PERSON_H

1 Ответ

0 голосов
/ 07 ноября 2019

Образец для сериализации std::string (с ограниченной длиной ≤ 65536 символов):

#include <cassert>
#include <iostream>
#include <fstream>

void writeString(std::ostream &out, const std::string &str)
{
  // write length of string (two bytes, little endian)
  assert(str.size() <= 1 << 16);
  const size_t size = str.size();
  char buffer[2] = { (char)(size & 0xff), (char)(size >> 8 & 0xff) };
  out.write(buffer, sizeof buffer)
  // write string contents
  && out.write(str.c_str(), size);
}

void readString(std::istream &in, std::string &str)
{
  // read length
  char buffer[2];
  if (!in.read(buffer, 2)) return; // failed
  const size_t size = (unsigned char)buffer[0] | (unsigned char)buffer[1] << 8;
  // allocate size
  str.resize(size);
  // read contents
  in.read(&str[0], size);
}

int main()
{
  // sample
  std::string name = "Antrophy";
  // write binary file
  { std::ofstream out("test.dat", std::ios::binary);
    writeString(out, name);
  } // closes file
  // reset sample
  name = "";
  // read binary file
  { std::ifstream in("test.dat", std::ios::binary);
    readString(in, name);
  } // closes file
  // report result
  std::cout << "name: '" << name << "'\n";
}

Вывод:

name: 'Antrophy'

Шестнадцатеричный код test.dat:

00000000  08 00 41 6e 74 72 6f 70  68 79                    |..Antrophy|
0000000a

Живая демоверсия на coliru

Примечание:

Рассмотрим, как длина (ограниченадо 16 бит) написано. Это можно сделать аналогично сериализации целочисленных значений.


A (ИМХО) хорошее введение представлено в C ++ FAQ:

Сериализация и десериализация


Расширенная выборка для двоичного ввода-вывода составного типа Person:

#include <cassert>
#include <iostream>
#include <fstream>

template <size_t nBytes, typename VALUE>
std::ostream& writeInt(std::ostream &out, VALUE value)
{
  const size_t size = sizeof value;
  char buffer[nBytes];
  const size_t n = std::min(nBytes, size);
  for (size_t i = 0; i < n; ++i) {
    buffer[i] = (char)(value >> 8 * i & 0xff);
  }
  for (size_t i = size; i < nBytes; ++i) buffer[i] = '\0';
  return out.write(buffer, nBytes);
}

template <size_t nBytes, typename VALUE>
std::istream& readInt(std::istream &in, VALUE &value)
{
  const size_t size = sizeof value;
  char buffer[nBytes];
  if (in.read(buffer, nBytes)) {
    value = (VALUE)0;
    const size_t n = std::min(nBytes, size);
    for (size_t i = 0; i < n; ++i) {
      value |= (VALUE)(unsigned char)buffer[i] << 8 * i;
    }
  }
  return in;
}

void writeString(std::ostream &out, const std::string &str)
{
  // write length of string (two bytes, little endian)
  assert(str.size() <= 1 << 16);
  const size_t size = str.size();
  writeInt<2>(out, size)
  // write string contents
  && out.write(str.c_str(), size);
}

void readString(std::istream &in, std::string &str)
{
  // read length
  std::uint16_t size = 0;
  if (!readInt<2>(in, size)) return; // failed
  // allocate size
  str.resize(size);
  // read contents
  in.read(&str[0], size);
}

struct Person {
  std::string lastName, firstName;
  int age;

  void write(std::ostream&) const;
  void read(std::istream&);
};

void Person::write(std::ostream &out) const
{
  writeString(out, lastName);
  writeString(out, firstName);
  writeInt<2>(out, age);
}

void Person::read(std::istream &in)
{
  readString(in, lastName);
  readString(in, firstName);
  std::int16_t age; assert(sizeof age == 2); // ensure proper sign extension
  if (readInt<2>(in, age)) this->age = age;
}

int main()
{
  // sample
  Person people[2] = {
    { "Mustermann", "Klaus", 23 },
    { "Doe", "John", -111 }
  };
  // write binary file
  { std::ofstream out("test.dat", std::ios::binary);
    for (const Person &person : people) person.write(out);
  } // closes file
  // read sample
  Person peopleIn[2] = {
    { "", "", -1 },
    { "", "", -1 }
  };
  // read binary file
  { std::ifstream in("test.dat", std::ios::binary);
    for (Person &person : peopleIn) person.read(in);
  } // closes file
  // report result
  int i = 1;
  for (const Person &person : peopleIn) {
    std::cout << "person " << i++ << ": '"
      << person.firstName << ' ' << person.lastName
      << ", age: " << person.age << '\n';
  }
}

Выход:

person 1: 'Klaus Mustermann, age: 23
person 2: 'John Doe, age: -111

Шестнадцатеричный код test.dat:

00000000  0a 00 4d 75 73 74 65 72  6d 61 6e 6e 05 00 4b 6c  |..Mustermann..Kl|
00000010  61 75 73 17 00 03 00 44  6f 65 04 00 4a 6f 68 6e  |aus....Doe..John|
00000020  91 ff                                             |..|
00000022

Live демо на coliru

Примечание:

Тип двоичного ввода-вывода для целочисленных значений (readInt() и writeInt()) может выглядеть слишком сложным по сравнению с простым out.write((char*)value, sizeof value);, найденным в другом месте. Я сделал это более переносимым способом, который будет работать даже на разных платформах с четким порядком байтов и / или разным размером интегралов.

...