如何解析文本文件并使用构造函数中的文件输入来创建对象的容器

How do i parse a text file and use the file input in constructors to create a container of objects

本文关键字:文件 输入 创建对象 构造函数 文本 何解析      更新时间:2023-10-16

>我有一个程序,可以逐行读取文本文件的名称,并使用构造函数将这些名称存储为对象。构造函数用于制作所有名称的向量。但是,我的问题是我需要我的名字具有绑定的属性,我有属性的构造函数,但我不知道如何解析文本文件以将名称与属性分开,然后如何存储属性与名称。

我的代码仅适用于文件中的名称,在这种情况下我不能简单地使用分隔符,因为我需要查找"名称"然后查找属性属性属性。

例:

"萨梅迪男爵"法师魔法远程

名称需要在不包含引号的情况下存储,然后需要在与名称对应的容器中构造属性,以便当我为特定名称(对象)调用 .getAttackType 时,它将返回适当的类型。

#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <exception>
#include <sstream>
#include <ctime>
#include <random>

enum class AttackType {
MELEE,
RANGE
};
enum class DamageType {
MAGICAL,
PHYSICAL
};
enum class AbilityType {
Mage,
Guardian,
Warrior,
Hunter,
Assassin
};
struct EntityAttributes {
AttackType attackType;
DamageType damageType;
AbilityType abilityType;
};
class Entity {
private:
std::string name_;
EntityAttributes attribs_;
public:
Entity() = default;
explicit Entity(const std::string& name) :
name_(name)
{}
Entity(const std::string& name, EntityAttributes attribs) :
name_(name),
attribs_(attribs)
{}
void assignAttributes(EntityAttributes attribs) {
attribs_ = attribs;
}
std::string getName() const { return name_; }
AbilityType getClassType() const { return attribs_.abilityType; }
AttackType getAttackType() const { return attribs_.attackType; }
DamageType getDamageType() const { return attribs_.damageType; }
};
void getAllLinesFromFile(const char* filename, std::vector<std::string>& output) {
std::ifstream file(filename);
if (!file) 
{
std::stringstream stream;
stream << "failed to open file " << filename << 'n';
throw std::runtime_error(stream.str());
}
std::string line;
while (std::getline(file, line)) {
if (line.size() > 0)
output.push_back(line);
}
file.close();
}
int main() {
srand(time(NULL));
try {
// This will store all of the names in from the text file.
std::vector<std::string> names;
getAllLinesFromFile("Names.txt", names);
// This will give us a container of all of our entities with a provided name
// after this container is filled you can go back later and add the addition
// properties, or if you read the properties in from a file as well you can use
// the other Entity constructor to generate all of them with their names and properties
std::vector<Entity> entities;
for (auto& n : names) {
Entity e(n);
entities.push_back(e);
}
// Check array of Entities
std::cout << "There are " << entities.size() << " entitiesn";
for (auto& e : entities) {
std::cout << e.getName() << 'n';
}
}
catch (std::runtime_error& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
system("pause");
return EXIT_SUCCESS;
}```

使用原始文件格式:

#include <string>       // std::string
#include <vector>       // std::vector<>
#include <iostream>     // std::cin, std::cout, std::cerr
#include <fstream>      // std::ifstream
#include <ctime>        // std::time()
#include <cstdlib>      // std::rand(), EXIT_FAILURE
#include <iterator>     // std::istream_iterator<>
#include <limits>       // std::numeric_limits<>
#include <algorithm>    // std::find()
char const *AbilityTypeStrings[]{ "Mage", "Guardian", "Warrior", "Hunter", "Assassin" };
enum class AbilityType {
Mage,
Guardian,
Warrior,
Hunter,
Assassin
};
char const *DamageTypeStrings[]{ "Magical", "Physical" };
enum class DamageType {
MAGICAL,
PHYSICAL
};
char const *AttackTypeStrings[]{ "Melee", "Range" };
enum class AttackType {
MELEE,
RANGE
};
struct EntityAttributes {
AttackType attackType;
DamageType damageType;
AbilityType abilityType;
};
class Entity {
std::string name;
EntityAttributes attributes;
public:
Entity(std::string const &name = {}, EntityAttributes const &attributes = {}) :
name(name),
attributes(attributes)
{}
friend std::istream& operator>>(std::istream &is, Entity &entity)
{
// ignore everything up to the first '"'.
is.ignore(std::numeric_limits<std::streamsize>::max(), '"');
// try to read the entities name
std::string name;
if (!std::getline(is, name, '"')) {
return is;
}
// try to read its abilities
std::string abilities;
if (!(is >> abilities)) {
return is;
}
EntityAttributes attributes{};
auto ability_type{ std::find(std::begin(AbilityTypeStrings), std::end(AbilityTypeStrings), abilities) };
if (ability_type == std::end(AbilityTypeStrings)) {
is.setstate(std::ios::failbit);
return is;
}
attributes.abilityType = static_cast<AbilityType>(ability_type - std::begin(AbilityTypeStrings));
std::string damage;
if (!(is >> damage)) {
return is;
}
auto damage_type{ std::find(std::begin(DamageTypeStrings), std::end(DamageTypeStrings), damage) };
if (damage_type == std::end(DamageTypeStrings)) {
is.setstate(std::ios::failbit);
return is;
}
attributes.damageType = static_cast<DamageType>(damage_type - std::begin(DamageTypeStrings));
std::string attack;
if (!(is >> attack)) {
return is;
}
auto attack_type{ std::find(std::begin(AttackTypeStrings), std::end(AttackTypeStrings), attack) };
if (attack_type == std::end(AttackTypeStrings)) {
is.setstate(std::ios::failbit);
return is;
}
attributes.attackType = static_cast<AttackType>(attack_type - std::begin(AttackTypeStrings));

entity.name = name;
entity.attributes = attributes;
return is;
}
friend std::ostream& operator<<(std::ostream &os, Entity const &entity)
{
os << '"' << entity.name << ""n" << DamageTypeStrings[static_cast<std::size_t>(entity.attributes.damageType)] << 'n'
<< AbilityTypeStrings[static_cast<std::size_t>(entity.attributes.abilityType)] << 'n'
<< AttackTypeStrings[static_cast<std::size_t>(entity.attributes.attackType)] << 'n';
return os;
}
};
int main()
{
std::srand(static_cast<unsigned>(std::time(nullptr)));  // why do you include <random> when
// you're using the old old C stuff?
char const *filename{ "test.txt" };
std::ifstream is{ filename };
if (!is.is_open()) {
std::cerr << "Couldn't open "" << filename << "" for reading :(nn";
return EXIT_FAILURE;
}
std::vector<Entity> entities{ std::istream_iterator<Entity>{ is }, std::istream_iterator<Entity>{} };
for (auto const &e : entities)
std::cout << e << 'n';
}

如何解析数据完全取决于您的文件结构,请记住,只要您与约定保持一致,您就可以将文件构建为自己的特定格式。

您可以按照尝试进行操作:

SingleWordName Class DamageType AttackType
"Multiple Word Name" Class DamageType AttackType

然后,您必须单独解析文本的每一行(字符串),但您也可以通过更改文本文件的结构来简化它。如果你知道会有一个类似的模式,不会改变,那么这样的事情可能会让你更容易。

SingleWordName or Multiple Word Name
AbilityType
AttackType
DamageType
NextName
AbilityType
AttackType
DamageType

然后,如果您以这种方式构建它,您就知道每行包含一个字符串,集合中的第一行将是实体类的名称变量,接下来的三行将填充该类中的属性结构。然后是一个可以忽略的空行。此空行仅用于视觉阅读参考,以便人类读者轻松区分一个实体和下一个实体。

您甚至可以像这样构建文件:

Entity Name Single Or Multiple Words
AbilityType AttackType DamageType
Next Entity
AbilityType AttackType DamageType

这种结构将采用文本或字符串的第一行并设置实体的名称,然后第二行包含属性结构的所有字段。 如果您的所有属性都是仅单个单词,则这种情况将很容易工作。如果您有多个单词的属性,并且不喜欢将它们括在引号、括号、大括号等中的想法,您可以在每个单词之间使用下划线,例如:

Baron_Samedi

然后,一旦有了这个单词,您就可以在该单词中查找任何_并将其从字符串中删除并替换为' '.

有多种方法可以解析字符串数据,这完全取决于两个主要因素:首先是数据或类结构,其次是用于表示该数据结构的文件结构。一旦你有了这两个,你就有了你的基础,那么就要使用该信息并从中构建你的解析函数。


编辑 -跟进 OP 关于在引号之间解析字符串的混淆的评论:

如果你在引号中有一个字符串" ";这里的问题是您必须对该字符串进行多次搜索以查找单个字符"并且您需要保留找到first_occurence_of"的位置以及next_occurence_of"的索引。找到第一个匹配项并保存其索引位置后,您必须像数组一样循环访问该字符序列,直到找到下一个",并且再次需要保存该索引位置。然后你必须得到两者之间的区别。

至于一个简单的例子,我们将使用"Hello"引号是我们的字符串,总共有 7 个字符。第一个"位于索引0处,下一个位于索引6处。然后,您将需要此原始字符串中的子字符串 [(first+1), (next-1)]。

[0][1][2][3][4][5][6]
["][H][e][l][l][o]["]

正如您在上面看到的,第一个"位于索引0,下一个位于索引6。字符串的总长度为 7。我们可以使用此信息和 stl 提供的字符串函数从这个原始字符串构造一个子字符串。但是我们必须从头到尾搜索字符串以找到开始和结束分隔符的两个位置。

// Pseudo Code
substring = [(original.firstIndexFound(") + 1) to (original.nextIndexFound(")-1)];
// Where the [] indicates inclusive at that position...
substring = [(0+1) to (6-1)] = [1,2,3,4,5]
// broken down into individual array indices..
substring[0] = original[1]
substring[1] = original[2]
substring[2] = original[3]
substring[3] = original[4]
substring[4] = original[5] 
// Visual Translation:
// Original:
[0][1][2][3][4][5][6]
["][H][e][l][l][o]["]
// Sub
[0][1][2][3][4]
[H][e][l][l][o]

如果单词之间有空格,这仍然有效,因为您使用分隔符调用的函数不是查找,而是查找" "或您确定为分隔符的任何其他字符。


这是一个简单的程序,用于演示在"之间解析字符串。

#include <string>
#include <iostream>
int main() {
std::string str = { ""Hello"" }; // need the " for quote in the string
std::cout << "Original String = " << str << 'n';
size_t first = str.find_first_of('"');
size_t next = str.find_first_of('"', first + 1);
std::cout << "First index at: " << first << "nNext index at: " << next << 'n';
std::string sub = str.substr(first + 1, next - 1);
std::cout << "Substring = " << sub << 'n';
return 0;
}

-输出-

Original String = "Hello"
First index at: 0
Next index at: 6
Substring = Hello

-注意-

上面没有健全性检查来确定字符串中是否根本没有CC_18字符。这很容易做到,您所要做的就是首先检查字符串的索引或迭代器是否不在结束位置,如果它在结束位置,则只需返回原始字符串,否则只需执行上述计算而没有任何更改。