ПРИМЕЧАНИЕ. Это не ответ на получаемые вами ошибки компиляции, а скорее более широкий взгляд на проблему с сохранением, с которой вы работаете.
Сериализация и десериализация - не самая простая проблема, над которой вы можете работать. Мой совет - инвестировать в изучение библиотек (boost :: serialization) и их использование. Они уже решили многие проблемы, с которыми вы столкнетесь в тот или иной момент. Кроме того, они уже имеют разные форматы вывода (двоичный, XML, JSON ...)
Первое, что вы должны решить, то есть, если вы решите пойти дальше и реализовать свой собственный, это какой формат файла и соответствует ли он всем вашим потребностям. Будет ли он всегда использоваться в одной и той же среде? Изменится ли платформа (32 / 64бит)? Вы можете решить сделать его двоичным, так как он является самым простым, или сделать его читабельным для человека. Если вы выбираете XML, JSON или любые другие более сложные форматы, просто забудьте об этом и используйте библиотеку.
Самое простое решение - работа с двоичным файлом, и это также решение, которое даст вам самый маленький файл. С другой стороны, это весьма чувствительно к изменениям архитектуры (скажем, вы переходите с 32-разрядной архитектуры на 64-разрядную / ОС)
После выбора формата вам нужно будет работать с дополнительной информацией, которая сейчас не является частью ваших объектов, но должна быть вставлена в файл для последующего поиска. Затем начните работать (и тестировать) от самых маленьких деталей до более сложных элементов.
Еще один совет - начать работать с самой простой и определенной частью и продолжить ее с этого момента. Начните избегать шаблонов настолько, насколько это возможно, и как только вы их очистите и будете работать с данным типом данных, поработайте над тем, как обобщить их для любого другого типа.
Отказ от ответственности: я написал код прямо в браузере, поэтому могут быть некоторые ошибки, опечатки или что-нибудь еще
Текст
Первый простой подход - это просто написание текстового представления текста. Преимущество состоит в том, что он переносим и короче в коде (если не проще), чем двоичный подход. Полученные файлы будут больше, но будут удобочитаемыми.
На данный момент вам нужно знать, как чтение текста работает с iostreams. В частности, всякий раз, когда вы пытаетесь прочитать строку, система будет читать символы, пока не достигнет разделителя. Это означает, что следующий код:
std::string str;
std::cin >> str;
будет читать только до первого пробела, табуляции или конца строки. При чтении чисел (например, целых чисел) система будет считывать все действительные цифры вплоть до первой недействительной цифры. То есть:
int i;
std::cin >> i;
с вводом 12345a будет потреблять все символы до 'a'. Вы должны знать это, потому что это повлияет на то, как вы сохраните данные для последующего извлечения.
// input: "This is a long Description"
std::string str;
std::cin >> str; // Will read 'This' but ignore the rest
int a = 1;
int b = 2;
std::cout << a << b; // will produce '12'
// input: 12
int read;
std::cint >> read; // will read 12, not 1
Так что вам очень нужны разделители для вставки в вывод и для анализа ввода. В качестве примера я выберу «|» персонаж. Это должен быть символ, который не отображается в текстовых полях.
Также будет хорошей идеей не только разделять элементы, но и добавлять дополнительную информацию (размер вектора). Для элементов в векторе вы можете использовать другой разделитель. Если вы хотите иметь возможность читать файл вручную, вы можете использовать '\ n', чтобы каждый элемент находился в отдельной строке
namespace textual {
std::ostream & operator<<( std::ostream& o, InventoryItem const & data )
{
return o << data.Item << "|" << data.Description << "|" << data.Quantity
<< "|" << data. ...;
}
std::ostream & operator<<( std::ostream & o, std::vector<InventoryItem> const & v )
{
o << v.size() << std::endl;
for ( int i = 0; i < v.size(); ++i ) {
o << v[i] << std::endl; // will call the above defined operator<<
}
}
}
Для чтения вам нужно разделить входные данные на '\ n', чтобы получить каждый элемент, а затем на '|' для разбора InventoryItem:
namespace textual {
template <typename T>
void parse( std::string const & str, T & data )
{
std::istringstream st( str ); // Create a stream with the string
st >> data; // use operator>>( std::istream
}
std::istream & operator>>( std::istream & i, InventoryItem & data )
{
getline( i, data.Item, '|' );
getline( i, data.Description, '|' );
std::string tmp;
getline( i, tmp, '|' ); // Quantity in text
parse( tmp, data.Quantity );
getline( i, tmp, '|' ); // wholesaleCost in text
parse( tmp, data. wholesaleCost );
// ...
return i;
}
std::istream & operator>>( std::istream & i, std::vector<InventoryItem> & data )
{
int size;
std::string tmp;
getline( i, tmp ); // size line, without last parameter getline splits by lines
parse( tmp, size ); // obtain size as string
for ( int i = 0; i < size; ++i )
{
InventoryItem data;
getline( i, tmp ); // read an inventory line
parse( tmp, data );
}
return i;
}
}
В функции чтения вектора я использовал getline + parse для чтения целого числа. Это означает, что следующая функция getline () будет действительно читать первый InventoryItem, а не завершающий символ '\ n' после размера.
Самым важным фрагментом кода является шаблон 'parse', который может преобразовать строку в любой тип, для которого определен оператор вставки. Его можно использовать для чтения примитивных типов, библиотечных типов (например, строки) и пользовательских типов, для которых определен оператор. Мы используем его, чтобы немного упростить остальную часть кода.
Binary
Для двоичного формата (игнорируя архитектуру, это вызовет боль в заднице, если вы мигрируете), самый простой способ, который я могу себе представить, - записать число элементов в векторе как size_t (независимо от размера в реализации), а затем все элементы. Каждый элемент будет распечатывать двоичное представление каждого из его членов. Для базовых типов, таких как int, он просто выведет двоичный формат int. Для строк мы прибегнем к записи числа size_t с количеством символов в строке, за которым следует содержимое строки.
namespace binary
{
void write( std::ofstream & o, std::string const & str )
{
int size = str.size();
o.write( &size, sizeof(int) ); // write the size
o.write( str.c_str(), size ); // write the contents
}
template <typename T>
void write_pod( std::ofstream & o, T data ) // will work only with POD data and not arrays
{
o.write( &data, sizeof( data ) );
}
void write( std::ofstream & o, InventoryItem const & data )
{
write( o, data.Item );
write( o, data.Description );
write_pod( o, data.Quantity );
write_pod( o, data. ...
}
void write( std::ofstream & o, std::vector<InventoryItem> const & v )
{
int size = v.size();
o.write( &size, sizeof( size ) ); // could use the template: write_pod( o, size )
for ( int i = 0; i < v.size(); ++i ) {
write( o, v[ i ] );
}
}
}
Я выбрал другое имя для шаблона, который записывает базовые типы, чем функции, которые пишут строки или InventoryItems. Причина в том, что мы не хотим позже по ошибке использовать шаблон для записи сложного типа (т. Е. UserInfo, содержащего строки), который будет хранить ошибочное представление на диске.
Извлечение с диска должно быть примерно таким же:
namespace binary {
template <typename T>
void read_pod( std::istream & i, T& data)
{
i.read( &data, sizeof(data) );
}
void read( std::istream & i, std::string & str )
{
int size;
read_pod( i, size );
char* buffer = new char[size+1]; // create a temporary buffer and read into it
i.read( buffer, size );
buffer[size] = 0;
str = buffer;
delete [] buffer;
}
void read( std::istream & i, InventoryItem & data )
{
read( i, data.Item );
read( i, data.Description );
read( i, data.Quantity );
read( i, ...
}
void read( std::istream & i, std::vector< InventoryItem > & v )
{
v.clear(); // clear the vector in case it is not empty
int size;
read_pod( i, size );
for ( int i = 0; i < size; ++i )
{
InventoryItem item;
read( i, item );
v.push_back( item );
}
}
}
Для использования этого подхода std :: istream и std :: ostream должны быть открыты в двоичном режиме.
int main()
{
std::ifstream persisted( "file.bin", ios:in|ios::binary );
std::vector<InventoryItem> v;
binary::read( persisted, v );
// work on data
std::ofstream persist( "output.bin", ios::out|ios::binary );
binary::write( persist, v );
}
Вся проверка ошибок оставлена читателю в качестве упражнения:)
Если у вас есть какие-либо вопросы по какой-либо части кода, просто спросите.