Ошибки компиляции при чтении / записи размера нескольких структур в файл - PullRequest
0 голосов
/ 02 апреля 2009

Я уже задал 2 вопроса, связанных с этим проектом, и пришел к такому выводу. Запись размера Struct в файл и последующее чтение - лучший способ сделать это.

Я создаю программу для домашнего задания, которая позволит мне вести инвентарь. Мне нужно прочитать / записать несколько структур одного типа в файл.

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

**** НАДЕЖДА ПОСЛЕДНЕЕ ИЗДАНИЕ № 3 *************

Мой код:

// Project 5.cpp : main project file.

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>

using namespace System;
using namespace std;
#pragma hdrstop

int checkCommand (string line);

template<typename Template>
void readFromFile(Template&);

template<typename Template>
void writeToFile(Template&);

template<typename T>
void writeVector(ofstream &out, const vector<T> &vec);

template<typename Template>
void readVector(ifstream& in, vector<Template>& vec);

struct InventoryItem {
    string Item;
    string Description;
    int Quantity;
    int wholesaleCost;
    int retailCost;
    int dateAdded;
} ;


int main(void)
{
    cout << "Welcome to the Inventory Manager extreme! [Version 1.0]" << endl;

    vector<InventoryItem> structList;

    ofstream out("data.dat");

    writeVector( out, structList );

    while (1)
    {

        string line = "";

        cout << endl;
        cout << "Commands: " << endl;
        cout << "1: Add a new record " << endl;
        cout << "2: Display a record " << endl;
        cout << "3: Edit a current record " << endl;
        cout << "4: Exit the program " << endl;
        cout << endl;
        cout << "Enter a command 1-4: ";

        getline(cin , line);


        int rValue = checkCommand(line);
        if (rValue == 1)
        {
            cout << "You've entered a invalid command! Try Again." << endl;
        } else if (rValue == 2){ 
            cout << "Error calling command!" << endl;
        } else if (!rValue) {
            break;
        }
    }


    system("pause");

    return 0;
}

int checkCommand (string line)
{
    int intReturn = atoi(line.c_str());
    int status = 3;

    switch (intReturn)
    {
        case 1:
            break;
        case 2:
            break;
        case 3:
            break;
        case 4:
            status = 0;
            break;
        default:
            status = 1;
            break;
    }
    return status;
}

template <typename Template>
void readFromFile(Template& t)
{
    ifstream in("data.dat");
    readVector(in, t); Need to figure out how to pass the vector structList via a Template
    in.close();
}

template <typename Template>
void writeToFile(Template& t)
{
    ofstream out("data.dat");
    readVector(out, t); Need to figure out how to pass the vector structList via a Template
    out.close();
}

template<typename T>
void writeVector(ofstream &out, const vector<T> &vec)
{
    out << vec.size();

    for(vector<T>::const_iterator i = vec.begin(); i != vec.end(); ++i)
    {
        out << *i; // SUPER long compile error
    }
}

template<typename T>
vector<T> readVector(ifstream &in)
{
    size_t size;
    in >> size;

    vector<T> vec;
    vec.reserve(size);

    for(int i = 0; i < size; ++i)
    {
        T tmp;
        in >> tmp;
        vec.push_back(tmp);
    }

    return vec;
}

Мои ошибки компиляции:

1>.\Project 5.cpp(128) : error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'const InventoryItem' (or there is no acceptable conversion)
1>        C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include\ostream(653): could be 'std::basic_ostream<_Elem,_Traits> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,const char *)'
1>        with

Это единственная ошибка, которую я получаю сейчас. Я вижу, ваш код НАСТОЛЬКО лучше. Моя новая ошибка компилятора - СУПЕР длинная. Я показал, на что указывает ошибка. Не могли бы вы помочь мне в последний раз?

Ответы [ 3 ]

2 голосов
/ 02 апреля 2009

Ваши функции чтения и записи содержат ошибки. В частности, вы должны сделать что-то вроде этого:

template<typename T>
void write(ofstream &out, const T &t)
{
    out << T;
}

СТАРЫЙ: bind1st требует, чтобы вы включили functional для его работы:

#include <functional>

Вместо того, чтобы иметь дело со всеми этими функциями и тому подобным, было бы лучше полагаться на итераторы:

template<typename T>
void writeVector(ofstream &out, const vector<T> &vec)
{
    out << vec.size();

    for(vector<T>::const_iterator i = vec.begin(); i != vec.end(); ++i)
    {
        out << *i;
    }
}

template<typename T>
vector<T> readVector(ifstream &in)
{
    size_t size;
    in >> size;

    vector<T> vec;
    vec.reserve(size);

    for(int i = 0; i < size; ++i)
    {
        T tmp;
        in >> tmp;
        vec.push_back(tmp);
    }

    return vec;
}

Вы бы хотели, чтобы функции читали и писали также ваш InventoryItem, вероятно:

ostream &operator<<(ostream &out, const InventoryItem &i)
{
    out << i.Item << i.Description;  // FIXME Read/write strings properly.
    out << i.Quantity;
    out << i.wholesaleCost << i.retailCost;
    out << i.dateAdded;
}

istream &operator>>(istream &out, InventoryItem &i)
{
    // Keep in same order as operator<<(ostream &, const InventoryItem &)!
    in >> i.Item >> i.Description;  // FIXME Read/write strings properly.
    in >> i.Quantity;
    in >> i.wholesaleCost >> i.retailCost;
    in >> i.dateAdded;
}
2 голосов
/ 02 апреля 2009

ПРИМЕЧАНИЕ. Это не ответ на получаемые вами ошибки компиляции, а скорее более широкий взгляд на проблему с сохранением, с которой вы работаете.

Сериализация и десериализация - не самая простая проблема, над которой вы можете работать. Мой совет - инвестировать в изучение библиотек (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 );
}

Вся проверка ошибок оставлена ​​читателю в качестве упражнения:)

Если у вас есть какие-либо вопросы по какой-либо части кода, просто спросите.

1 голос
/ 02 апреля 2009

РЕДАКТИРОВАТЬ: пытается очистить FUD:

bind1st является частью заголовка functional STL. STL существовал до появления наддува. В C ++ 0x это устарело в пользу более общей версии, т. Е. bind (она же boost::bind). См. Приложение D.8 Связующие для получения дополнительной информации.

Теперь реальная проблема (из-за нескольких правок это может выглядеть глупо, но я оставлю это для потомков):

 write<long>(out, structList.size());

Это оскорбительная строка. Это предполагает long в качестве второго параметра, тогда как vector s size() имеет тип size_t или unsigned int под капотами.

В обновлении произошла опечатка: используйте size_t, а не size_T:

write<size_t>(out, structList.size());

Следующая часть:

 for_each(structList.begin(), structList.end(), bind1st(write<InventoryItem>, out));

Это должен быть structList или какой-то другой тип. Кроме того, включите functional, чтобы иметь возможность использовать bind1st. Добавьте вверху:

#include <functional>

Шаблон bind1st принимает функтор. Обход обычных указателей на функции невозможен без некоторых других хаков. Вы можете использовать boost::bind в качестве альтернативы. Или:

for(InventoryItem::iterator i = structList.begin(), f = structList.end();
         i != f; ++i)
    write<InventoryItem>(out, *i);

Теперь для других щупалец:

Что такое:

#include <String>
...
using namespace System;

Вы уверены, что используете здесь? Если вы хотите строки STL, вам нужно включить:

#include <string>

void main(void)

не является стандартной подписью. Используйте любой из:

int main(void)

или

int main(int argc, char *argv[]);

Ввод / вывод обычно намного проще с предопределенными операторами вставки / извлечения. Вы можете (и действительно должны) использовать:

istream is(...);
is >> data;

и аналогично

ostream os(...);
os << data;

Обратите внимание также, что ваши readFromFile и writeToFile функции должны быть исправлены, чтобы просто использовать vector<InventoryItem> вместо vector.

...