从文本文件加载变量的最简单方法

Easiest way to load variables from a text file

我有一个程序,我想从文本文件加载变量以将它们用作默认变量。

文本文件应如下所示:

Name=No Name

Age=8

Gender=male

etc.

有没有更简单的方法,如果没有,我该如何在带问号的地方做到这一点?

我的代码是这样的:

int Age;
std::string Name;
bool male;

if(f.is_open())
{
    while (!f.eof())
    {

        getline(f, line);
        if (line.find("Name=") == std::string::npos)
        {
            Name=?????;
            continue;
        }
        else if (line.find("Gender=") == std::string::npos)
        {
            if(????? == "true"); then
               male=true;
            else
               male=false;

            continue;
        }
        else if (line.find("Age=") == std::string::npos)
        {
            Age=?????;
            continue;
        }
        //etc. ...
}
f.close();

我不会重新发明这个。正如所建议的那样,存在用于序列化的库。以 Boost.PropertyTree 为例,Boost 通常有助于学习。

Is there a simpler way?

您可以使用序列化库,例如 cereal or Boost,正如@JesperJuhl 所建议的。

但是,我强烈建议退后一步,重新审视您的方法。您要求改进,但目前您没有好的解决方案,因为 Why is iostream::eof inside a loop condition considered wrong?

正如我写的 here,我将使用 std::getline() 作为循环条件而不是 ios::eof(),以便逐行解析文件。

How do I do that in the place with the question marks?

然后,对于每一行,我将根据定界符(在您的情况下为等号)对其进行标记,以提取两个标记,即变量名称及其默认值。在 Parse (split) a string in C++ using string delimiter (standard C++)

中阅读更多相关信息

之后,我会使用if-else方法(你可以使用switch语句代替)来检查变量的名称,并将其默认值赋给程序的实际变量。

完整代码示例:

#include <iostream>
#include <string>
#include <fstream>

int main(void) {
  std::string defaultName, gender;
  int age;

  std::ifstream infile("mytextfile.txt");
  std::string line, varName, defaultValue;
  std::string delimiter = "=";
  while (std::getline(infile, line)) {
    varName = line.substr(0, line.find(delimiter));
    defaultValue = line.substr(line.find(delimiter) + 1);
    if(varName == "Name") {
      defaultName = defaultValue;
      continue;
    } else if(varName == "Age") {
      age = std::stoi(defaultValue);
      continue;
    } else if(varName == "Gender") {
      gender = defaultValue;
      continue;
    } else {
      std::cout << "Unknown entry: " << line << std::endl;
    }
  }

  std::cout << defaultName << ", " << age << ", " << gender << std::endl;

  return 0;
}

输出:

No Name, 8, male

如果您觉得需要自己编写而不是使用现成的库,您可以使用 std::unordered_map<> 并围绕它添加一些流和提取支持。这是代码中带有注释的示例:

#include <string>
#include <unordered_map>

class KeyValue { //        Key          Value    
    std::unordered_map<std::string, std::string> m_kv{};

public:
    // at() is used to get a reference to a Value given the supplied Key. It uses
    // the function with the same name in the unordered_map.

    inline std::string& at(const std::string& Key) { return m_kv.at(Key); }
    inline const std::string& at(const std::string& Key) const { return m_kv.at(Key); }

    // The "as<T>" function below is used to extract values from the map.
    // The exact version of the function that will be used depends on the type
    // you want to extract from the string. Explicit specializations of the function
    // are declared outside the class.

    // A generic conversion function to anything that can be constructed from a std::string
    template<typename T>
    T as(const std::string& Key) const {
        return at(Key);
    }

    // A function to extract directly into a variable using the proper as<T>
    template<typename T>
    void extract_to(T& var, const std::string& Key) const {
        var = as<T>(Key);
    }

    // A friend function to read from an input stream (like an open file) and
    // populate the unordered_map.
    friend std::istream& operator>>(std::istream&, KeyValue&);
};

// Explicit specializations of KeyValue::as<T>()

// floats
template<>
float KeyValue::as(const std::string& Key) const {
    return std::stof(at(Key));
}

template<>
double KeyValue::as(const std::string& Key) const {
    return std::stod(at(Key));
}

template<>
long double KeyValue::as(const std::string& Key) const {
    return std::stold(at(Key));
}
// signed integers
template<>
int KeyValue::as(const std::string& Key) const {
    return std::stoi(at(Key));
}

template<>
long KeyValue::as(const std::string& Key) const {
    return std::stol(at(Key));
}

template<>
long long KeyValue::as(const std::string& Key) const {
    return std::stoll(at(Key));
}
// unsigned integers
template<>
unsigned KeyValue::as(const std::string& Key) const {
    return std::stoul(at(Key));
}

template<>
unsigned long KeyValue::as(const std::string& Key) const {
    return std::stoul(at(Key));
}

template<>
unsigned long long KeyValue::as(const std::string& Key) const {
    return std::stoull(at(Key));
}
// bool
template<>
bool KeyValue::as(const std::string& Key) const {
    const std::string& val = at(Key);
    if(val=="true" || val=="1") return true;
    else if(val=="false" || val=="0") return false;
    throw std::range_error("\"" + Key + "\" is neither true nor false");
}   

// the friend function that extracts key value strings from a stream
std::istream& operator>>(std::istream& is, KeyValue& kv) {
    std::string line;

    // read one line at a time
    while(std::getline(is, line)) {
        auto pos = line.find('=');
        if(pos == std::string::npos || pos == 0) {
            // if '=' was not found (or found at pos 0), set the failbit on the stream
            is.setstate(std::ios::failbit);
        } else {
            // if '=' was found, put the Key and Value in the map by
            // using substr() to split the line where the '=' was found
            kv.m_kv.emplace(line.substr(0, pos), line.substr(pos + 1));
        }
    }
    return is;
}

有了它,您可以读取文件并填充您最好放入 class / struct 中的变量。示例:

#include <fstream>

struct Variables {
    std::string Name{};
    unsigned int Age{};
    std::string Gender{};
    double PI{};
    bool Hungry{};
    bool Sad{};

    Variables(const std::string& filename) {
        std::ifstream is(filename);
        if(is) {
            KeyValue tmp;
            is >> tmp; // stream the whole file into tmp

            // extract values
            tmp.extract_to(Name, "Name");
            tmp.extract_to(Age, "Age");
            tmp.extract_to(Gender, "Gender");
            tmp.extract_to(PI, "PI");
            tmp.extract_to(Hungry, "Hungry");
            tmp.extract_to(Sad, "Sad");
        } else throw std::runtime_error("Could not read \""+filename+"\".");
    }
};

示例数据文件 (vars.dat):

Name=No name
Age=8
Gender=male
PI=3.14159
Hungry=true
Sad=false

...和一个主要示例::

#include <iostream>

int main() {
    try {
        Variables var("vars.dat"); // open file and populate variables

        std::cout << std::boolalpha
            << "Name:   " << var.Name << "\n"
            << "Age:    " << var.Age << "\n"
            << "Gender: " << var.Gender << "\n"
            << "PI:     " << var.PI << "\n"
            << "Hungry: " << var.Hungry << "\n"
            << "Sad:    " << var.Sad << "\n";

    } catch(const std::exception& ex) {
        std::cerr << ex.what() << "\n";
    }
}

我试图简化@Ted Lyngmo 的解决方案: ...我认为这不是最快的方法也不是最好的方法,但它更简单也更简短:

#include <sstream>

class loadVars
{
public:
    std::string file;
    loadVars() { }

    //Input ->
    loadVars(std::string Text) {
        this->setFile(Text);
    }

    loadVars(std::istream& is) {
        this->setFile(is);
    }

    friend void operator>>(std::istream& is, loadVars& lv) {
        lv.file = std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>());
    }

    void setFile(std::string Text) {
        this->file = Text;
    }

    void setFile(std::istream& is) {
        this->file = std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>());
    }
    //<-

    std::string extract_to_first(std::string to) {
        std::string line;
        std::stringstream s_string = std::stringstream(this->file);

        while (std::getline(s_string, line)) {
            if(line.find("=") != std::string::npos) {
                if(line.substr(0,line.find("=")) == to) {
                    return line.substr(line.find("=")+1);
                }
            }
        }
        return "-1";
    }

};