将文件内容添加到结构中

时间:2016-09-14 21:59:01

标签: c++ io

我应该创建一个程序来索引我的集合中的书籍。该结构包含通常的书籍信息:标题,作者,出版商等。但是,我没有输出。一个问题是标题会有空格。

/* Book Inventory assignment 2 by Heath Martens. */

#include <iostream>
#include <stdlib.h>
#include <fstream>
#include <cstring> // I had to throw this in in order to get memcpy to work.

using namespace std;

typedef struct book{
    char title[100];
    char author[100];
    char publisher[100];
    float price;
    int isbn;
    int pages;
    int copies;
} Book;

Book collection[100];
int currentIndex;


void
indexBook(Book *my_book)
{
    memcpy(&collection[currentIndex], my_book, sizeof(Book));
    currentIndex++;
}

void
readfile(void)
{
    fstream my_stream;
    string line = " ";
    my_stream.open("input.txt");
    int i=0;
    for (i = 0; i < currentIndex; i++)
        {
            while (getline(my_stream, line))
            {
                cin >> line >> collection[i].title;
                cin >> line >> collection[i].author;
                cin >> line >> collection[i].publisher;
                cin >> line >> collection[i].price;
                cin >> line >> collection[i].isbn;
                cin >> line >> collection[i].pages;
                cin >> line >> collection[i].copies;
            }
        }

    my_stream.close();

}

void
printCollection(void)
{
    int i;
    for (i = 0; i < currentIndex; i++)
    {
        cout << "Title: " << "\t\t" << collection[i].title << endl;
        cout << "Author: " << "\t" << collection[i].author << endl;
        cout << "Publisher: " << "\t" << collection[i].publisher << endl;
        cout << "Price: " << "\t\t" << collection[i].price << endl;
        cout << "ISBN: " << "\t\t" << collection[i].isbn << endl;
        cout << "Pages: " << "\t\t" << collection[i].pages << endl;
        cout << "Copies: " << "\t" << collection[i].copies << endl;
    }
}

void printCollection(void);

int
main(void)
{
    currentIndex = 0;

    Book *my_book = new Book;

    indexBook(my_book);

    readfile();

    printCollection();

    delete my_book;

    return 0;
}

这是我给出的txt文件。

Magician: Apprentice
Raymond E. Feist
Spectra (January 1, 1994)
5.02
0553564943
512
1
Magician: Master
Raymond E. Feist
Spectra (January 1, 1994)
7.99
0553564935
499
1

以下是基于提供的一些示例的更新代码。

    void
    readfile(void)
    {
       fstream my_stream ("input.txt");
       if(!my_stream)
    {
       return;
    }
     string line = " ";
     int i=0;
for (i = 0; i < currentIndex; i++)
    {
        if(!std::getline(my_stream, line))
        {
            break;
        }
        memcpy(collection[currentIndex].title, line.c_str(), std::min(sizeof(collection[currentIndex].title), line.size()));
        memcpy(collection[currentIndex].author, line.c_str(), std::min(sizeof(collection[currentIndex].author), line.size()));
        memcpy(collection[currentIndex].publisher, line.c_str(), std::min(sizeof(collection[currentIndex].publisher), line.size()));
        my_stream >> collection[currentIndex].price;
        my_stream >> collection[currentIndex].isbn;
        my_stream >> collection[currentIndex].pages;
        my_stream >> collection[currentIndex].copies;
        my_stream.ignore();
        if(!my_stream)
        {
            break;
        }
    }

Error in output

2 个答案:

答案 0 :(得分:1)

主要问题是readfile函数。如注释中所述,std :: fstream可以将字符串作为第一个参数,因此不需要稍后调用open。此外,应该在对文件执行操作之前检查文件是否已打开。 std :: string不需要初始化,以便稍后在此函数中使用。这导致以下代码。

fstream my_stream("input.txt");
if(!my_stream) {
    return;
}
string line;

readfile的循环似乎没有正确构造。外部循环在集合数组的当前索引范围内递增,而内部循环看起来好像是要读取整个文件。如果内部循环构造正确,那么文件的内容将被写入集合中的第0本书。内循环开始于测试是否从文件中读取一行(通过读取std :: cin的输入而忽略)成功。

因为input.txt中的“Books”数量未知,所以外部循环似乎与将所有“Books”读入另一个集合元素的目标无关。为了读取整个文件,我们将更改循环以检查文件是否仍然可读。

while(my_stream) {

为了读取字符串,我们需要使用std :: getline读取和存储整行(my_stream >> line;这里将被空格绊倒),并将memcpy复制到相应的行内容中char数组。因为std :: getline可能会失败,所以我们在使用line的内容之前检查是否成功。例如,这里的标题是:

// title
if(!std::getline(my_stream, line)) {
    break;
}
memcpy(collection[currentIndex].title, line.c_str(), std::min(sizeof(collection[currentIndex].title), line.size()));

对于这些数字,我们只需使用运算符&gt;&gt;读取数字即可。并忽略行尾的任何多余字符。同样,此操作可能会失败并进行检查。例如,这是价格:

    my_stream >> collection[currentIndex].price; 
    my_stream.ignore(); 
    if(!my_stream) { 
        break;
    } 

如果当前Book的所有成员都已正确读取,那么在循环体的末尾,我们将增加所看到的书籍数量(++currentIndex;)。正如注释中所述,在readfile的上下文中没有必要显式关闭my_stream,因为当在范围的末尾调用my_stream的析构函数时,文件将被关闭。

一些额外的小问题。如上面评论中所讨论的,std :: string应该可以用于Book :: title,Book :: author和Book :: publisher。这是因为std :: string处理的情况是更加优雅地知道字符数,而不需要明确地管理内存。同样,集合将更适合于标准容器(例如,std :: vector)。这确实会导致indexBook的当前实现出现问题,可以将其更改为使用Book的复制构造函数存储在集合中。对于Book IO,运算符&gt;&gt;和运算符&lt;&lt;可以重载以替换和简化readfile和printCollection中的代码。

答案 1 :(得分:0)

本代码中有许多问题和不良习惯需要学习。

using namespace std;

这是一种危险的做法,因为它意味着std库命名空间中声明的所有名称都被导入到全局命名空间中,这将导致您将来出现各种奇怪的问题。

如果您不想一直输入std::coutstd::string,您可以改为:

using std::string;
using std::cout;

下一步:

typedef struct book { ... } Book

这是C语言构造,在C ++中完全没有必要。

struct Book {
};

就这么简单。

struct Book {
    char title[100];
};

既然你知道std :: string,为什么不在这里使用呢?

using std::string;

struct Book {
    string title;
    string author;
    string publisher;
    float price;
    int isbn;
    int pages;
    int copies;
};

indexBook功能坦率地说是可怕的。

void
indexBook(Book *my_book)
{
    memcpy(&collection[currentIndex], my_book, sizeof(Book));
    currentIndex++;
}
在C ++中应避免使用

memcpy,而应依赖于内置于C ++对象的复制/赋值/移动运算符。

    collection[currentIndex] = *my_book;

如果有一个特别好的理由为什么不应该复制一个对象,并且该对象的类的作者是好的,那么你将得到编译器错误,而使用memcpy你将得到未定义的行为。

让我们先略过一点:

void
printCollection(void)
{
    ...
}

void printCollection(void);

这里没有任何害处,但在定义之后向前声明printCollection是多余的。

Book *my_book = new Book;

indexBook(my_book);

...
delete my_book;

目前尚不清楚你为什么要这样做。这似乎很浪费。你可以很容易地做到:

Book my_book;

但无论哪种方式都有一个简单的问题:my_book尚未初始化。所以你复制到收藏中的是任何人的猜测。

Book my_book {};

会默认为你初始化它。

现在让我们看一下readfile。

fstream my_stream;
string line = " ";  // why?
my_stream.open("input.txt");
int i=0;
for (i = 0; ...)
{
   ...
}
my_stream.close();

你在这里做了很多不必要的工作和编码。您正在使用C ++,因此对象具有构造函数,您可以编写std::string line = " "std::string line(" ")std::string line {" "}(从C ++ 11开始,首选)。但你也可以为fstream做同样的事情。最后,除非您需要在for循环之外显示i,否则可以将其设置为循环的本地。

因为fstream是一个对象,它还有一个析构函数,它将确保文件被关闭。

这一切都将我们带到了

void
readfile()
{
    std::fstream my_stream("input.txt");
    for (int i = 0; i < currentIndex; ++i)
    {
       std::string line {};  // default initialized.
       ...
    }
    // call to close is redundant
}

现在我们要解决你的代码实际上已经破解的问题,其中大部分都在于这个功能。

1您没有检查文件是否已打开

2您在getline(my_stream, line) for循环中的while循环中调用i

3您不需要调整currentIndex

4(最糟糕的是)你滥用operator>>

在您的代码中,您可以编写以下内容:

for (i = 0; i < currentIndex; ++i)  // *1
{
    while (getline(my_stream, line))  // *2
    {
        cin >> line >> collection[i].title;  // *3

因为您在main中未初始化的对象上调用indexBookcurrentIndex为1,所以我们将进入for循环。

现在我们尝试将my_stream的第一行读入line(* 2),如果成功,我们进入while循环体。

现在我们尝试将std::cin 中的单词读入行,然后将另一个单词从std::cin读入集合[0] .title(* 3,记住:i = 0,必须是< currentIndex)。

你没有检查这些std::cin读取,如果它们中的任何一个失败,它将只是通过它们全部无效而继续。

最后,我们到达while循环的末尾,然后再次尝试getline(my_stream, line)。如果成功,我们将重新进入while循环体。

*请注意,i尚未更改,因此代码仍引用collection[0],覆盖第一次传递中从std::cin获取的所有数据。

这将一直持续到我们耗尽my_stream为止,此时我们最终退出while循环并到达for循环。

i会增加,以便i现在等于1。检查条件并且i < currentIndex不再为真,因此我们退出for循环。

您似乎认为您正在编写代码以将行读取到collection[i].title。我们可以使用std::stofstd::stoi将字符串转换为floatinteger,以便我们可以编写如下内容:

bool
readFile(void)  // returns true on success, false on error
{
    using std::getline;

    std::fstream my_stream("input.txt");
    if (!my_stream.is_open())
        return false;
    for (int i = 0; i < currentIndex; ++i)
    {
        Book newBook {};   // temporary local to read into
        if (!getline(my_stream, newBook.title))
            break;
        if (!getline(my_stream, newBook.author))
            break;
        if (!getline(my_stream, newBook.publisher))
            break;
        std::string line;
        if (!getline(my_stream, line))
            break;
        newBook.price = std::stof(line);
        if (!getline(my_stream, line))
            break;
        newBook.isbn = std::stoi(line);
        if (!getline(my_stream, line))
            break;
        newBook.pages = std::stoi(line);
        if (!getline(my_stream, line))
            break;
        newBook.copies = std::stoi(line);

        collection[i] = newBook;
    }

    return true;
}

这肯定是不是习惯性的C ++,但那是教你/老师教你的。但是,我将跳过如何解决这一任务。

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

using std::string;

struct Book
{
    // suffix member variables with `_` to distinguish from function names
    // and parameters.
    string title_;
    string author_;
    string publisher_;
    float price_;
    int isbn_;
    int pages_;
    int copies_;

    // support `stream >> book` syntax with `operator >> ()`.
    // note that it's not a member of the class, so we declare
    // it as a `friend` function so it can have full access.
    friend std::istream& operator >> (std::istream&, Book&);

    // because of the formatting, I wouldn't make this `operator <<`.
    void print() const
    {
        std::cout << "Title: " << "\t\t" << title_ << '\n';
        std::cout << "Author: " << "\t" << author_ << '\n';
        std::cout << "Publisher: " << "\t" << publisher_ << '\n';
        std::cout << "Price: " << "\t\t" << price_ << '\n';
        std::cout << "ISBN: " << "\t\t" << isbn_ << '\n';
        std::cout << "Pages: " << "\t\t" << pages_ << '\n';
        std::cout << "Copies: " << "\t" << copies_ << '\n';
    }
};

using Collection = std::vector<Book>;

std::istream& operator >> (std::istream& str, Book& book)
{
    std::getline(str, book.title_);
    std::getline(str, book.author_);
    std::getline(str, book.publisher_);
    str >> book.price_ >> book.isbn_ >> book.pages_ >> book.copies_;
    str.ignore();
    return str;
}

bool
readFile(std::string filename, Collection& collection)
{
    std::fstream my_stream(filename);
    if (!my_stream.is_open())
        return false;

    Book newBook {};
    while (my_stream >> newBook)
        collection.emplace_back(std::move(newBook));

    return true;
}

void
printCollection(const Collection& collection)
{
    for (auto&& book : collection)  // or: const Book& book
    {
        book.print();
        std::cout << '\n';
    }
}

int
main()
{
    Collection collection;

    if (!readFile("input.txt", collection))
    {
        std::cerr << "readFile on input.txt failed\n";
        return 1;  // non-zero return from main indicates program failure
    }

    std::cout << "Read " << collection.size() << " books\n\n";

    printCollection(collection);
}