0

我已经问了两个与这个项目有关的问题,我已经得出了这个结论。将 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

您的读写功能有问题。特别是,您应该这样做:

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

OLD: 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;
}
于 2009-04-02T04:20:24.957 回答
2

注意:这不是您遇到的编译错误的答案,而是您正在处理的持久性问题的更广泛的看法。

序列化和反序列化不是您可以解决的最简单的问题。我的建议是投资学习库(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'。

最重要的一段代码是“解析”模板,它能够将字符串转换为定义了插入运算符的任何类型。它可用于读取基本类型、库类型(例如字符串)和定义了运算符的用户类型。我们用它来简化其余的代码。

二进制

对于二进制格式,(忽略架构,如果您迁移,这将是一件痛苦的事情)我能想到的最简单的方法是将向量中的元素数量写为 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:

bind1st是 STLfunctional标头的一部分。STL 在 boost 出现之前就已经存在。它在 C++0x 中被弃用,取而代之的是更通用的版本,即bind(aka boost::bind)。更多信息参见附录 D.8 粘合剂。

现在真正的问题(多次编辑可能会使这看起来很傻,但为了后代我会保留它):

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

这是违规行。这需要 along作为第二个参数,而vector'ssize()是类型size_tunsigned 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[]);

使用预定义的插入/提取操作符,I/O 通常会容易得多。您可以(并且确实应该)使用:

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

同样地

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

另请注意,您的readFromFilewriteToFile功能需要固定才能使用vector<InventoryItem>,而不是vector简单地使用。

于 2009-04-02T04:12:30.797 回答