我正在开发一个涉及多态和继承的项目。
假设项目中使用的类的层次结构是:
Media ----> Book ----> MediaRegistry
并且每个类的声明如下:(类已经缩小到我遇到问题的成员)
class Media
{
public:
Media();
Media(int id, string content);
virtual void input(istream& in) = 0;
friend istream& operator>>(istream& in, Media& media);
protected:
string _mediaTitle;
int _id;
};
使用:
istream& operator>>(istream& in, Media& media)
{
media.input(in);
return in;
}
和
class Book:public Media // Inherits from abstract base class Media
{
public:
Book();
Book(int id, string title, int nrOfPages); // Constructor
void input(istream& in);
private:
int _nrOfPages;
};
使用:
void Book::input(istream& in)
{
in >> _id >> _mediaTitle >> _nrOfPages;
}
和
class MediaRegistry
{
public:
MediaRegistry();
int addMedia(Media*);
int loadMedia();
private:
static const int MAX = 10;
//Media* _pMedias[MAX]; // Vector for MAX numbers of Media pointers
std::vector<Media*> _pMedias;
int _nrOfMedias; // _nrOfMedias == MAX when registry is full
ifstream inFile;
ofstream outFile;
string path = ".\\data.txt";
};
使用:
int MediaRegistry::loadMedia()
{
inFile.open(path.c_str());
while (!inFile.eof())
{
int mediaType = 0;
inFile >> mediaType;
if (inFile.eof())
break;
Media* media = nullptr;
media = new Book();
inFile >> *media;
addMedia(media);
}
inFile.close();
return 0;
}
现在在data.txt
,作为媒体的标题,如果我在单词之间有空格(即&#34; The Clown&#34;而不是例如&#34; the-Clown&#34 ;),程序遇到此成员函数istream& operator>>(istream& in, Media& media)
中的问题。但是,我无法理解,我多次调试它来跟踪问题。事实上,我希望我的程序能够获得一个包含空格的字符串,但它现在没有这样做?
答案 0 :(得分:1)
字符串上的>>
输入运算符将只读取一个单词(如果可能)或根本不读取任何内容。所以当你有代码时:
void Book::input(istream& in)
{
in >> _id >> _mediaTitle >> _nrOfPages;
}
它只需要一个整数,然后是一个字,然后再一个整数。如果您尝试给它以下输入:
1234
小丑
216
它将首先读取整数1234
,然后是单词The
,然后它将尝试将Clown
解释为整数(将失败)。注意最后216
是如何保持未读的。流上的下一个输入操作将从此处开始,这可能会导致意外结果。
如果您的文件设置为明确的行结构(例如,每个&#34;字段&#34;在单独的行上),std::getline(in, line)
之类的内容可能会更好:
#include <iostream> // std::istream
#include <string> // std::getline
#include <limits> // std::numeric_limits
using namespace std;
void Book::input(istream& in)
{
in >> _id;
// this ignores the rest of the line after the previously read integer
// (otherwise getline will just read the rest of the line)
in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
getline(in, _mediaTitle);
in >> _nrOfPages;
}
答案 1 :(得分:0)
除了@Frxstrem's answer之外,您的loadMedia()
功能还可以使用补丁。有一些事情它做错了或做得不好:
1)当可以在构造函数的成员初始化列表中完成时打开文件流。
loadMedia()
应该用于从输入文件读取数据到程序,而不是用于执行打开和关闭操作。这可以使用RAII,或在构造函数中初始化并在析构函数中释放的概念来完成。由于文件名已知,我们可以通过成员初始化列表初始化它。
MediaRegisty::MediaRegistry() : inFile{path} { }
因为您正在使用C ++ 11(使用nullptr
证明),我们可以使用std::ifstream
的构造函数作为参数。std::string
。我还使用了统一初始化语法(T{}
)。有些编译器不支持该语法,因此如果出现错误,只需用括号替换它们。
2)在没有可靠地检查失败的情况下执行提取。
!eof()
从未打算用作输入条件。输入应在控制通过循环之前完成,以便最小化使用提取失败的结果的可能性(从而避免未定义的行为)。输入流有一个布尔运算符,用于检查流的有效性。如果你以正确的方式使用它,那么在提取之后将会调用它:
for (auto p = std::make_pair(int{}, make_unique<Book>());
inFile >> p.first >> *p.second; addMedia(media));
我使用std::pair
来包含两个变量和一个for
循环来放置我们想要执行的语句。 make_unique<Book>()
创建std::unique_ptr<Book>
。 int{}
与0
相同。
3)动态分配数据而不释放它,有效地导致内存泄漏。
new
/ new[]
的每个实例都需要相应的delete
/ delete[]
。如果您没有释放资源,则会出现内存泄漏。
如果您遇到需要使用动态分配的情况,请考虑使用std::unique_ptr
和std::shared_ptr
等内存管理容器。这些类通过包含资源并在其析构函数中释放它来利用RAII(当对象超出范围时)。
4)不必要地关闭文件。
文件流在超出范围时通过调用析构函数内的close()
来关闭文件句柄。仅在需要使用其他文件重新打开时才调用close()
。
我希望这会有所帮助。这些是我所做的改变:
template<class T, class... Args>
std::unique_ptr<T> make_unique(Args&&... args) noexcept
{
return new T{std::forward<Args>(args)...};
}
class MediaRegistry
{
public:
MediaRegistry();
int addMedia(std::unique_ptr<Media>);
int loadMedia();
private:
std::vector<std::unique_ptr<Media>> _pMedias;
std::ifstream inFile;
...
};
MediaRegistry::MediaRegistry() : inFile{path} { }
int MediaRegistry::addMedia(std::unique_ptr<Media> p)
{
_pMedias.push_back(std::move(p));
return 0; // not clear what's supposed to be returned here so I put 0
}
int MediaRegistry::loadMedia()
{
for (auto p = std::make_pair(int{}, make_unique<Book>());
inFile >> p.first >> *p.second; addMedia(p.second));
return 0;
}