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

时间:2019-02-17 04:11:09

标签: c++ oop

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

我的代码仅适用于文件中的名称,在这种情况下,我不能简单地使用分隔符,因为我需要先查找“名称”,然后再查找属性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;
}```


2 个答案:

答案 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; } 字符。这很容易做到,您要做的就是首先检查字符串的索引或迭代器是否不在结束位置,如果在结束位置,则只需返回原始字符串,否则只需执行上述操作即可。计算没有任何变化。