【问题标题】:A simple way to read TXT config files in C++在 C++ 中读取 TXT 配置文件的简单方法
【发布时间】:2020-07-10 07:23:01
【问题描述】:

这个问题似乎已经被问过了,但我没有为我的案例找到任何方便的解决方案。 我有以下 TXT 配置文件要在 C++ 中读取:

--CONFIGURATION 1 BEGIN--

IP address:                          192.168.1.145
Total track length [m]:              1000
Output rate [1/s]:                   10
Time [s]:                            1
Running mode (0=OFF 1=ON):           1
Total number of attempts:            10
Mode (0=OFF, 1=BEG, 2=ADV, 3=PROF):  1

--Available only for Administrators--

Variable 1 [mV]:                     2600
Gain 1 [mV]:                         200
Position tracking (0=OFF 1=ON):      0
Coefficient 2 [V]:                   5.2

--CONFIGURATION 1 END--

--CONFIGURATION 2 BEGIN--

Max track distance [m]:             10000
Internal track length [m]:          100
Offset distance [mV]:               1180
GAIN bias [mV]:                     200
Number of track samples:            1000
Resolution (1 or 2) [profile]:      1

--CONFIGURATION 2 END--

我只需要在每行末尾存储一个值,该值可以是一个字符串(在 IP 地址的情况下)、一个 int、一个浮点数或一个结构体中的布尔值。在 C 中有一个非常简单的解决方案,我使用如下表达式读取每一行:

if(!fscanf(fp, "%*s %*s %*s %*s %d\n", &(settings->trackLength))) {

    printf("Invalid formatting of configuration file. Check trackLength.\n");
    return -1;
}

%*s 允许丢弃行的标签和感兴趣的值之前的空格。我使用 fgets 跳过空行或标题。这种方式也适用于 C++。让我的代码保持原样好还是你看到了在 C++ 中更好更简单的方法? 非常感谢。

【问题讨论】:

  • 读取整行,然后解析它们。此外,避免遗留 C 的东西(如 scanf 等),如 C++ 中的瘟疫。
  • /nscanf 格式字符串的末尾并没有按照您的想法执行。
  • 首先,感谢海德的评论。是的,我已经看到了这种方式,但是为什么我应该用更多的代码行来替换已经完成我想要的工作的单个(和简单的)代码行来做同样的事情呢? C++ 中没有一个表达式可以满足要求吗?再次感谢您。
  • 看起来您的fscanf 在您的示例代码中是完全错误的(至少它与您的输入文件不匹配)。对于初学者来说,它根本不读取行,它读取 4 个单词,然后尝试读取和解析一个整数。这突出了scanf 是多么容易出错 -> 使用一些让自己更难射中自己脚的东西。
  • 这看起来不像标准配置文件。通常它是一个ini 文件或xmljson 文件用于此目的,并且有许多现成的解决方案来读/写这样的配置。我从未见过您提出的格式,因此可能很难找到现成的解决方案。

标签: c++ parsing config


【解决方案1】:

在 C++ 中也很容易分割一行。我已经在这里提供了几个关于如何拆分字符串的答案。无论如何,我将在这里详细解释它并针对您的特殊情况。稍后我还会提供一个完整的工作示例。

我们使用std::getline 的基本功能,它可以读取整行或直到给定字符的行。请参阅here

让我们举个例子。如果文本存储在std::string 中,我们将首先将其放入std::istringstream。然后我们可以使用std::getlinestd::istringstream中提取数据。这始终是标准方法。首先,使用std::getline 从文件中读取完整的行,然后再次将其放入std::istringstream,以便能够再次使用std::getline 提取字符串的各个部分。

如果源代码行如下所示:

Time [s]:                            1

我们可以观察到我们有几个部分:

  • 标识符“时间 [s]”,
  • 冒号,用作分隔符,
  • 一个或多个空格和
  • 值“1”

所以,我们可以这样写:

std::string line{};  // Here we will store a complete line read from the source file
std::getline(configFileStream, line);  // Read a complete line from the source file
std::istringstream iss{ line };  // Put line into a istringstream for further extraction

std::string id{};  // Here we will store the target value "id"
std::string value{};   // Here we will store the target "value"
std::getline(iss, id, ':');  // Read the ID, get read of the colon
iss >> std::ws;  // Skip all white spaces
std::getline(iss, value);  // Finally read the value

所以,这是很多文字。您可能听说过可以链接 IO-Operations,例如在 std::cout << a << b << c 中。这是可行的,因为 std::getline 也是如此。因为它这样做,我们可以使用嵌套语句。意思是,我们可以将第二个std::getline 放在这个参数位置(实际上是第一个参数)它期望std::istream 的位置。如果我们因此遵循这种方法,那么我们可以编写嵌套语句:

std::getline(std::getline(iss, id, ':') >> std::ws, value);

哎呀,这是怎么回事?让我们从里到外分析。首先,操作std::getline(iss, id, ':')std::istringstream 中提取一个字符串并将其分配给变量“id”。好的,明白了。请记住:std::getline 将返回对给定流的引用。所以,那么上面的简化语句就是

std::getline(iss >> std::ws, value)

接下来,iss >> std::ws 将被评估,并将导致耗尽所有不必要的空白。你猜怎么着,它会返回一个对 gievn 流 "iss" 的引用。

语句现在看起来像:

std::getline(iss, value)

这将读取值。很简单。

但是,我们还没有完成。当然 std::getline 将再次返回“iss”。在下面的代码中,你会看到类似

if (std::getline(std::getline(iss, id, ':') >> std::ws, value))

最终将成为if (iss)。那么,我们使用iss 作为布尔表达式?为什么这行得通,它有什么作用?它可以工作,因为如果状态正常或失败,std::streambool operator 将被覆盖并返回。请参阅here 以获得解释。始终检查任何 IO 操作的结果。

最后但同样重要的是,我们需要解释带有初始化程序的if 语句。你可以阅读它here

我会写

if (std::string id{}, value{}; std::getline(std::getline(iss, id, ':') >> std::ws, value)) {

类似于

std::string id{}, value{}; 
if (std::getline(std::getline(iss, id, ':') >> std::ws, value)) {

但第一个示例的优点是定义的变量仅在if-statements 范围内可见。因此,我们尽可能缩小变量的“范围”。

您应该尽可能多地尝试这样做。您还应该始终通过将if 应用于流操作来检查 IO 操作的返回状态,如上所示。

读取所有内容的完整程序只需几行代码。

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <unordered_map>
#include <iomanip>

int main() {

    // Open config file and check, if it coul be opened
    if (std::ifstream configFileStream{ "r:\\config.txt" }; configFileStream) {

        // Here we wills tore the resulting config data
        std::unordered_map<std::string, std::string> configData;

        // Read all lines of the source file
        for (std::string line{}; std::getline(configFileStream, line); )
        {
            // If the line contains a colon, we treat it as valid data
            if (if (line.find(':') != std::string::npos)) {

                // Split data in line into an id and a value part and save it
                std::istringstream iss{ line };
                if (std::string id{}, value{}; std::getline(std::getline(iss, id, ':') >> std::ws, value)) {

                    // Add config data to our map
                    configData[id] = value;
                }
            }
        }
        // Some debug output
        for (const auto& [id, value] : configData)
            std::cout << "ID: " << std::left << std::setw(35) << id << " Value: " << value << '\n';
    }
    else std::cerr << "\n*** Error: Could not open config file for reading\n";

    return 0;
}

对于本例,我将 id 和值存储在地图中,以便可以轻松访问它们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多