我有一个程序,可以逐行读取文本文件中的名称,并使用构造函数将这些名称存储为对象。构造函数用于制作所有名称的向量。但是,我的问题是我需要名称绑定属性,我具有属性的构造函数,但是我不知道如何解析文本文件以将名称与属性分开,然后如何存储属性与名称。
我的代码仅适用于文件中的名称,在这种情况下,我不能简单地使用分隔符,因为我需要先查找“名称”,然后再查找属性attribute属性。
示例:
“萨米迪男爵”法师远程游荡
需要存储名称而不包含引号,然后需要在与名称相对应的容器中构造属性,以便当我为特定名称(对象)调用.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() << " entities\n";
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;
}```
答案 0 :(得分:1)
使用原始文件格式:
#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 :(\n\n";
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';
}
答案 1 :(得分:0)
解析数据的方式全部取决于文件结构,请记住,只要符合约定,就可以将文件结构化为自己的特定格式。
您可以按照自己的意愿进行操作:
SingleWordName Class DamageType AttackType
"Multiple Word Name" Class DamageType AttackType
然后,您将不得不分别解析文本的每一行(字符串),但也可以通过更改文本文件的结构来使其更简单。如果您知道整个过程中都不会发生类似的变化,那么这种变化可能会让您更轻松。
SingleWordName or Multiple Word Name
AbilityType
AttackType
DamageType
NextName
AbilityType
AttackType
DamageType
然后,如果以这种方式构造它,则知道每一行包含一个字符串,集合中的第一行将是Entity类的名称变量,而后三行将填充该类中的Attributes Structure。然后是可以忽略的空白行。此空白行仅用于视觉阅读参考,供人类读者轻松区分一个实体与另一个实体。
您甚至可以像这样构造文件:
Entity Name Single Or Multiple Words
AbilityType AttackType DamageType
Next Entity
AbilityType AttackType DamageType
这种结构将采用文本或字符串的第一行并设置实体的名称,然后第二行包含Attributes结构的所有字段。 如果您所有的属性都是“单个单词”,那么这种情况就足够容易了。如果您具有多个单词的属性,并且不喜欢将其用引号,方括号,大括号等括起来的想法,则可以在每个单词之间使用下划线,例如:
Baron_Samedi
然后,一旦您有了这个单词,就可以在该单词中寻找任何_
并将其从字符串中删除,然后将其替换为' '
。
解析字符串数据有多种方法,这都取决于两个主要方面:第一是数据或类结构,第二是用于表示该数据结构的文件结构。一旦具备了这两个基础,就可以使用这些信息并从中构建解析功能。
编辑-跟进操作员关于在引号之间解析字符串的混淆的评论:
如果您在引号" "
中使用字符串;这里的问题是,您必须对该字符串进行单个字符"
的多个搜索,并且需要保留找到{_1}的first_occurence_of和{{1}的next_occurence_of的索引。 }}。找到第一个匹配项并保存其索引位置后,您必须遍历整个字符序列(如数组),直到找到下一个"
,然后再次需要保存该索引位置。然后,您必须了解两者之间的区别。
作为一个简单的示例,我们将使用"
,引号是我们的字符串,共有7个字符。前一个"
在索引"Hello"
处,下一个在索引"
处。然后,您将需要此原始字符串中的子字符串[(first + 1),(next-1)]。
0
如您所见,第一个6
位于索引[0][1][2][3][4][5][6]
["][H][e][l][l][o]["]
,下一个位于索引"
。字符串的总长度为7。我们可以使用此信息以及stl提供的字符串函数从该原始字符串构造一个子字符串。但是我们必须从头到尾搜索字符串,以找到开始和结束定界符的位置。
0
如果单词之间有空格,这将仍然有效,因为您使用定界符调用的函数不是在寻找6
,而是在寻找// 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; }
字符。这很容易做到,您要做的就是首先检查字符串的索引或迭代器是否不在结束位置,如果在结束位置,则只需返回原始字符串,否则只需执行上述操作即可。计算没有任何变化。