我将首先向您展示一些示例代码,然后再给您解释。请注意:这是许多可能的解决方案之一。而且我添加了很多调试输出,这样你就可以看到幕后发生了什么。
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>
// A class hierachy
class Game {
protected:
// The name. This will be inherited from all other ganes. No need to define it there
std::string name{};
public:
// Constructor with some debug output
Game(const std::string& n) : name(n) { std::cout << "DEBUG: Call constructor for Game with name '" << name << "'\n"; }
// Virtual Destructor with some debug output
virtual ~Game() { std::cout << "DEBUG: Call destructor for Game\n"; }
// This is a virtual oure function. It will prevent a direct instantiation of Game
virtual void print(std::ostream&) const = 0;
};
class MMO : public Game {
public:
// Constructor with some debug output. Call base constructor from Game and set name
MMO(const std::string& n) : Game(n) { std::cout << "DEBUG: Call constructor for MMO with name '" << name << "'\n"; }
// Virtual Destructor with some debug output
virtual ~MMO() override { std::cout << "DEBUG: Call destructor for MMO\n"; }
// Virtual function overrides base function and will be used polymorph
virtual void print(std::ostream& os) const override { os << name << " --> is of type MMO\n"; }
};
class Strategy : public Game {
public:
// Constructor with some debug output. Call base constructor from Game and set name
Strategy(const std::string& n) : Game(n) { std::cout << "DEBUG: Call constructor for Strategy with name '" << name << "'\n"; }
// Virtual Destructor with some debug output
virtual ~Strategy() override { std::cout << "DEBUG: Call destructor for Strategy\n"; }
// Virtual function overrides base function and will be used polymorph
virtual void print(std::ostream& os) const override { os << name << " --> is of type Strategy\n"; }
};
class Arcade : public Game {
public:
// Constructor with some debug output. Call base constructor from Game and set name
Arcade(const std::string& n) : Game(n) { std::cout << "DEBUG: Call constructor for Arcade with name '" << name << "'\n"; }
// Virtual Destructor with some debug output
virtual ~Arcade() override { std::cout << "DEBUG: Call destructor for Arcade\n"; }
// Virtual function overrides base function and will be used polymorph
virtual void print(std::ostream& os) const override { os << name << " --> is of type Arcade \n"; }
};
class AR : public Game {
public:
// Constructor with some debug output. Call base constructor from Game and set name
AR(const std::string& n) : Game(n) { std::cout << "DEBUG: Call constructor for AR with name '" << name << "'\n"; }
// Virtual Destructor with some debug output
virtual ~AR() override { std::cout << "DEBUG: Call destructor for AR\n"; }
// Virtual function overrides base function and will be used polymorph
virtual void print(std::ostream& os) const override { os << name << " --> is of type AR \n"; }
};
class GameList {
protected:
std::vector<std::unique_ptr<Game>> data{};
public:
// Override extractor operator
friend std::istream& operator >> (std::istream& is, GameList& gl) {
// Read all lines from the stream
for (std::string type{}, name{}; std::getline((is >> type), name); ) {
// Factory pattern. Create a class depending on the type
if (type == "MMO") gl.data.emplace_back(std::make_unique<MMO>(name));
else if (type == "Strategy") gl.data.emplace_back(std::make_unique<Strategy>(name));
else if (type == "Arcade") gl.data.emplace_back(std::make_unique<Arcade >(name));
else if (type == "AR") gl.data.emplace_back(std::make_unique<AR>(name));
else std::cerr << "\n*** Error: invalid type read\n";
}
return is;
}
// Overide inserter operator
friend std::ostream& operator << (std::ostream& os, const GameList& gl) {
for (const std::unique_ptr<Game>& g : gl.data) {
// This is a call of a polymorph function. G is a came and will call the correct function
g->print(os);
}
return os;
}
};
std::istringstream fileStream{ R"(MMO League Of Legends
MMO World Of Warcraft
Strategy Civilization
Strategy Hearthstone
Arcade Street Fighter
Arcade PacMan
AR Beat Saber
AR Superhot )" };
int main() {
// Define the Game list
GameList gl;
// Read games from file and add to game list
fileStream >> gl;
// Print out all games
std::cout << gl;
return 0;
}
解释:
首先我们建立一个类层次结构。
我们从“Game”类开始,然后从中派生 4 个类:“MMO”、“Strategy”、“Arcade”和“AR”。
所有派生都是公开的,因此我们可以继承受保护和公开的成员和函数。
基类包含变量“name”。这是由所有派生类继承的。所以,这里不需要再定义了。
基类有一个纯虚函数,在函数定义后由= 0指定。这意味着您根本无法创建/实例化“游戏”类。但是您可以定义函数,例如构造函数和析构函数。每个类中的所有析构函数只会生成一些调试输出。
基类构造函数将设置游戏的“名称”。并且派生类的构造函数会调用基类构造函数并用它设置“名称”。
您可以看到print 函数已被设计为基类“Game”中的虚函数。因此,我们可以在所有派生类中重写它。
稍后,我们通过指向Game 的指针访问print 函数,多态性将确保调用正确的函数。
好的,这就是“游戏”类层次结构。
接下来是“游戏列表”。
这包含一个指向“游戏”的指针向量。请注意。如今,我们不再对拥有的内存使用原始指针,而是使用像std::unique_pointer 这样的智能指针。这将防止内存泄漏和其他灾难。我们也不会再使用new,而是std::make_unique。
但是,这一切都在您的基本问题之前:如何读取数据?为此,我们覆盖了“GameList”类的提取器运算符。有了它,我们可以从任何流中读取,例如 std::ifstream 或 std::istringstream 或任何其他流,并使用 iostream 功能。
那么,我们在这个函数中做了什么?
首先,我们使用 for 循环读取文件的所有行。在 for 循环的声明部分,我们声明了 2 个std::string 变量:“type”和“name”。然后是有点棘手的部分。
我们想先用is >> type 读取“类型”。然后我们要读取该行的其余部分,直到我们点击'\n'。这可以通过std::getline(is, name) 完成。
您可能听说过提取或插入操作可以链接起来,例如std::cin >> a >> b >> c。这是可行的,因为这些操作返回对原始流的引用。所以,std::cin >> a 返回std::cin 然后这将用于“>> b”等等。
知道如何(is >> type 将返回 is),我们可以使用它并将其放在 std::getline 函数的第一个参数中,该函数需要一个“is”作为第一个参数。所以,我们可以写
std::getline((is >> type), name)
然后,首先读取类型,然后读取名称。
好的,明白了。但为什么会出现在for 循环的条件部分。为此,您需要知道std::getline 还返回对给定流的引用,所以再次“是”。并且流有一个被覆盖的bool operator,它将返回流的状态,例如“文件结束”。出于这个原因,结果std::getline 将被转换为布尔值并可以用作条件。
在for循环中,当我们读取“类型”后,我们使用一种工厂模式并创建一个新的适合派生类,并将指向基类的指针存储在我们内部的std::vector中。
在“GameList”类的插入器部分,我们通过基于范围的 for 循环检索所有指向基类的指针,并调用它们的虚拟“打印”函数。而且,通过多态的奇迹,正确的函数会被调用。
在 main 中,对于这个例子,我没有打开文件,因为我在 StackOverflow 上没有文件。我改用std::istringstreasm,但您当然可以使用任何其他流。
Main 就相当简单了。我们定义了一个“GameList”类,然后使用提取操作符,读取所有数据并创建所有类。
插入运算符也是如此。我们将完整的“GameList”插入std::cout,并以此调用虚拟print函数。
一些沉重的东西。但请尽量消化原理,然后自己道歉。
很遗憾没有人会读到这个。 . .