0

私はすでにこのプロジェクトに関連する 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

それが私が今得ている唯一のエラーです。あなたのコードはとても優れていることがわかります。私の新しいコンパイラ エラーは非常に長いです。エラーが指している場所を示しました。最後にもう一度だけ手伝ってもらえますか?

4

3 に答える 3

2

注: これは、発生しているコンパイル エラーに対する回答ではなく、処理している永続性の問題のより広い視野です。

シリアライゼーションとデシリアライゼーションは、取り組むことができる最も単純な問題ではありません。私のアドバイスは、ライブラリ (boost::serialization) の学習とそれらの使用に投資することです。彼らは、あなたがいつか直面するであろう問題の多くをすでに解決してきました。さらに、既に異なる出力形式 (バイナリ、xml、json...) を持っています。

最初に決定しなければならないことは、つまり、先に進んで独自のものを実装することを決定した場合、ファイル形式がどうなるか、およびそれがすべてのニーズに適合するかどうかです。常に同じ環境で使用されますか? プラットフォームは変更されますか (32/64 ビット)? 最も単純なのでバイナリにするか、人間が読めるようにするかを決めることができます。XML、JSON、またはその他のより複雑な形式を決定した場合は、それを忘れてライブラリを使用してください。

最も簡単な解決策はバイナリ ファイルで作業することであり、これは最小のファイルを提供する解決策でもあります。一方で、アーキテクチャの変更には非常に賢明です (32 ビットから 64 ビットのアーキテクチャ/OS に移行するとします)。

フォーマットを決定したら、現在はオブジェクトの一部ではないが、後で取得するためにファイルに挿入する必要がある追加情報に取り組む必要があります。次に、最小のパーツからより複雑な要素まで作業 (およびテスト) を開始します。

もう 1 つのアドバイスは、最も単純で最も定義された部分から作業を開始し、そこから構築することです。可能な限りテンプレートを避け始め、特定のデータ型を明確にして機能するようになったら、それを他の型に一般化する方法に取り組みます。

免責事項: 私はブラウザで直接コードを書いたので、いくつかのエラー、タイプミスなどがあるかもしれません :)

文章

最初の単純なアプローチは、テキストのテキスト表現を書くことです。利点は、移植性が高く、バイナリ アプローチよりもコードが短い (単純ではないにしても) ことです。結果のファイルは大きくなりますが、ユーザーが読むことができます。

この時点で、テキストの読み取りが iostream でどのように機能するかを知る必要があります。特に、文字列を読み取ろうとするたびに、システムは区切り記号に到達するまで文字を読み取ります。これは、次のコードを意味します。

std::string str;
std::cin >> str;

最初のスペース、タブ、または行末までのみ読み取ります。数値 (例として int) を読み取る場合、システムは最初の無効な数字まですべての有効な数字を読み取ります。あれは:

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() がサイズの後の「\n」ではなく、最初の InventoryItem を実際に読み取ることを保証するためです。

コードの最も重要な部分は、文字列から挿入演算子が定義されている任意の型に変換できる「解析」テンプレートです。プリミティブ型、ライブラリ型 (文字列など)、および演算子が定義されているユーザー型を読み取るために使用できます。これを使用して、残りのコードをかなり単純化します。

バイナリ

バイナリ形式の場合 (アーキテクチャを無視して、移行すると大変なことになります) 私が考えることができる最も簡単な方法は、ベクター内の要素の数を 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 );
}

すべてのエラー チェックは、読者の演習として残されています :)

コードのどの部分についても質問がある場合は、質問してください。

于 2009-04-02T07:21:21.887 に答える
1

編集:FUDを片付けようとしています:

bind1stSTL のfunctionalヘッダーの一部です。ブーストが現れる前にSTLが存在しました。より一般的なバージョン、つまりbind(別名boost::bind)を優先して、C++ 0xでは非推奨です。詳細については、附属書 D.8 バインダーを参照してください。

ここで本当の問題 (複数の編集により、これはばかげているように見えるかもしれませんが、後世のためにこれを保持します):

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

これが問題の行です。longこれは2 番目のパラメーターとしてa を想定していますが、はタイプまたはフードの下にありますvectorsize()size_tunsigned int

更新タイプミスがありました: use size_tand not 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[]);

通常、事前定義された挿入/抽出演算子を使用すると、I/O ははるかに簡単になります。以下を使用できます (実際に使用する必要があります)。

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

同様に

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

また、単純に使用するのではなくreadFromFilewriteToFile関数を修正する必要があることにも注意してください。vector<InventoryItem>vector

于 2009-04-02T04:12:30.797 に答える