Сначала я покажу вам пример кода, а затем объясню. Обратите внимание: это одно из многих возможных решений. И я добавил много отладочной информации, чтобы вы могли видеть, что происходит за кулисами.
#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».
Все деривации публикуются c, так что мы можем наследовать защищенные и публичные c члены и функции.
Базовый класс содержит переменную "name". И это наследуется всеми производными классами. Поэтому нет необходимости определять его там снова.
Базовый класс имеет чисто виртуальную функцию, обозначаемую = 0
после определения функции. Это означает, что вы не можете создать / создать экземпляр класса "Game" вообще. Но вы можете определить функции, такие как конструктор и деструктор. Все деструкторы в каждом классе просто сгенерируют какой-либо отладочный вывод.
Конструктор базового класса установит «имя» игры. И конструкторы производных классов будут вызывать конструктор базового класса и устанавливать с ним «имя».
Вы можете видеть, что функция print
была задумана как виртуальная функция в базовом классе «Игра». ». Таким образом, мы можем переопределить его во всех производных классах.
Позже мы получим доступ к функции print
через указатель на Game
, и полиморфизм убедится, что будет вызвана правильная функция.
ОК, это была иерархия класса "Game".
Далее "GameList".
Содержит вектор указателей на "Game". Пожалуйста, обратите внимание. В настоящее время мы больше не используем сырые указатели для собственной памяти, а умные указатели, такие как std::unique_pointer
. Это предотвратит утечки памяти и другие катастрофы. Мы также больше не будем использовать new
, но std::make_unique
.
Но, все это перед вашим базовым c вопросом: Как читать данные? Для этого мы переопределяем оператор экстрактора для класса «GameList». При этом мы можем читать из любого потока, например std::ifstream
или std::istringstream
или любого другого потока, и использовать функциональность iostream
.
Итак, что мы делаем в этой функции?
Сначала мы используем для l oop, чтобы прочитать все строки файла. В части объявления для l oop мы объявляем 2 std::string
переменные: "тип" и "имя". Затем следует немного хитрая часть.
Сначала мы хотим прочитать «type» с помощью is >> type
. Затем мы хотим прочитать остальную часть строки, пока не нажмем '\ n'. Это можно сделать с помощью std::getline(is, name)
.
Возможно, вы слышали, что операции извлечения или вставки могут быть связаны, например, std::cin >> a >> b >> c
. Это работает, потому что эти операции возвращают ссылку на исходный поток. Итак, std::cin >> a
возвращает std::cin
, а затем это будет использоваться для ">> b" и т. Д.
С этим ноу-хау (is >> type
вернется), мы можем использовать это и поместите его в первый параметр функции std::getline
, которая ожидает "is" в качестве первого параметра. Итак, мы можем написать
std::getline((is >> type), name)
Затем сначала будет прочитан тип, а затем имя.
Хорошо, понятно. Но почему это в условной части for
l oop. Для этого вам нужно знать, что std::getline
также возвращает ссылку на данный поток, поэтому снова «есть». И поток имеет перезаписанный bool operator
, и он будет возвращать состояние потока, например, «конец файла». И по этой причине результат std::getline
будет преобразован в логическое значение и может использоваться как условие.
В for
l oop после того, как мы прочитали «тип», мы используем вид фабричного шаблона и создайте новый подходящий производный класс и сохраните указатель на базовый класс в нашем внутреннем std::vector
.
В части вставки класса "GameList" мы извлекаем все указатели на базу Класс через диапазон, основанный на l oop и вызвать их виртуальную функцию "печати". И, к чуду полиморфизма, будет вызвана правильная функция.
В основном для этого примера я не открываю файл, потому что у меня нет файлов в StackOverflow. Вместо этого я использую std::istringstreasm
, но вы, конечно, можете использовать любой другой поток.
Main тогда довольно прост. Мы определяем класс "GameList" и затем используем оператор экстрактора, чтобы прочитать все данные и создать все классы.
То же самое с оператором насекомого. Мы вставляем полный «GameList» в std::cout
и с этим вызываем виртуальные print
функции.
Некоторые тяжелые вещи. Но, пожалуйста, попытайтесь усвоить этот принцип, а затем сделайте свое собственное извинение.
Как жаль, что никто не прочитает это. , .