首页 > 解决方案 > 在 C++ 中读取 TXT 配置文件的简单方法

问题描述

这个问题似乎已经被问过了,但我没有为我的案例找到任何方便的解决方案。我有以下 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、一个 float 或一个结构内的 bool。在 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++parsingconfig

解决方案


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

我们使用它的基本功能std::getline可以读取完整的行或到给定字符的行。请看这里

让我们举个例子。如果文本存储在 a 中,std::string我们将首先将其放入 astd::istringstream中。然后我们可以使用std::getlinestd::istringstream. 这始终是标准方法。首先,使用 读取文件的完整行std::getline,然后再次将其放入 a 中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作为布尔表达式使用呢?为什么这行得通,它有什么作用?它可以工作,因为 的bool operatorstd::stream覆盖并返回,如果状态正常或失败。请看这里的解释。始终检查任何 IO 操作的结果。

最后但并非最不重要的一点是,我们需要if用初始化器来解释语句。你可以在这里阅读。

我可以写

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 和值存储在地图中,以便可以轻松访问它们。


推荐阅读