【问题标题】:Adding contents of a file to a struct将文件的内容添加到结构中
【发布时间】:2016-09-14 21:59:01
【问题描述】:

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

/* 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

【问题讨论】:

  • 如果您使用 C++,您应该考虑使用 std::string 而不是 char 数组。
  • 在互联网上搜索“stackoverflow c++ 读取文件结构”,了解如何将数据文件读入结构的示例。已经有太多类似的问题和答案了。
  • 提示:无论您的 txt 文件中有多少本书,您似乎只增加一次 currentIndex
  • 请提出一个具体的问题,并展示一个您尝试过但不起作用的最小示例。
  • 这不是问题,但是std::fstream my_stream("input.txt");会打开文件;无需单独致电my_stream.open()std::fstream 的析构函数将关闭文件;无需单独致电my_stream.close()

标签: c++ io


【解决方案1】:

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

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

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

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

while(my_stream) {

为了读取字符串,我们需要使用 std::getline 读取并存储整行(my_stream &gt;&gt; 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()));

对于数字,我们可以使用 operator>> 读取数字并忽略行尾的任何额外字符。同样,此操作可能会失败并且也会被检查。例如,这里的价格:

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

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

一些额外的小问题。正如上面在 cmets 中所讨论的,std::string 可能应该用于 Book::title、Book::author 和 Book::publisher。这是因为 std::string 可以更优雅地处理未知字符数的情况,而无需显式地管理内存。同样,collection 更适合标准容器(例如 std::vector)。这确实会导致 indexBook 的当前实现出现问题,可以将其更改为使用 Book 的复制构造函数来存储在集合中。对于 Book IO,可以重载 operator>> 和 operator

【讨论】:

    【解决方案2】:

    在这段代码中有很多问题和坏习惯需要学习。

    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++;
    }
    

    memcpy 在 C++ 中应避免使用,而应依赖 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 做同样的事情。最后,除非您需要 i 在 for 循环之外可见,否则您可以将其设置为循环本地。

    因为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 你在 while 循环中调用getline(my_stream, line)i for 循环中,

    3 你不调整currentIndex

    4(最糟糕的是)你滥用了operator&gt;&gt;

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

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

    因为你在main中对一个未初始化的对象调用了indexBook,所以currentIndex是1,所以我们进入for循环。

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

    现在我们尝试将std::cin中的一个单词读入行,然后将std::cin中的另一个单词读入collection[0].title(*3,记住:i=0,并且有成为&lt; currentIndex)。

    您无需检查这些 std::cin 读取,如果其中任何一个失败,它将继续执行所有这些读取。

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

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

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

    i 递增,因此 i 现在等于 1。检查条件,i &lt; currentIndex 不再为真,所以我们退出 for 循环。

    您似乎认为您正在编写代码以将 from 行中的单词读入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);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-22
      • 2023-03-21
      • 1970-01-01
      • 2021-09-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多