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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何解析文本文件并使用构造函数中的文件输入来创建对象容器相关的知识,希望对你有一定的参考价值。

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

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

例:

“男爵Samedi”法师神奇的远程

名称需要在没有包含引号的情况下存储,然后需要在与名称对应的容器中构造属性,这样当我为特定名称(对象)调用.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;
```


答案

使用原始文件格式:

#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';

另一答案

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

你可以尝试这样做:

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

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

SingleWordName or Multiple Word Name
AbilityType
AttackType
DamageType

NextName
AbilityType
AttackType
DamageType

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

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

Entity Name Single Or Multiple Words
AbilityType AttackType DamageType

Next Entity
AbilityType AttackType DamageType

这种结构将采用文本或字符串的第一行并设置实体的名称,然后第二行包含Attributes结构的所有字段。如果你的所有属性都是单字,那么这种情况就足够了。如果你有多个单词的属性,并且不喜欢用引号,括号,大括号等括起它们的想法,你可以在每个单词之间使用下划线,例如:

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;

-Output-

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

-注意-

上面没有健全性检查来确定字符串中是否没有"字符。这很容易做到,你要做的就是首先检查字符串的索引或迭代器是否不在结束位置,如果它在结束位置然后只返回原始字符串,否则只需执行上面的操作计算没有任何变化。

以上是关于如何解析文本文件并使用构造函数中的文件输入来创建对象容器的主要内容,如果未能解决你的问题,请参考以下文章

如何从python中的图像中删除某些文本?

解析 Java 源代码

卡住试图从文本文件输入字符串

实现java类并测试方法中的许多条件

Python解析Python中的文件操作

c语言 如何读取中文字符串