一种在C++中读取TXT配置文件的简单方法

A simple way to read TXT config files in C++

本文关键字:TXT 读取 配置文件 方法 简单 C++ 一种      更新时间:2023-10-16

这个问题似乎已经被问过了,但我没有为我的案例找到任何方便的解决方案。我有以下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 %dn", &(settings->trackLength))) {
printf("Invalid formatting of configuration file. Check trackLength.n");
return -1;
}

%*s允许丢弃行的标签和感兴趣值之前的空格。我用fgets跳过空行或标题。这种方法也适用于C++。让我的代码保持原样好吗?或者你认为在C++中有更好、简单的方法可以做到这一点吗?非常感谢。

同样在C++中,分割一行很容易。我已经在SO上提供了几个关于如何拆分字符串的答案。不管怎样,我会在这里详细解释,并为您的特殊情况。稍后我还会提供一个完整的工作示例。

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

让我们举一个例子。如果文本存储在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操作,如std::cout << a << b << c。这是有效的,因为<lt;操作总是返回对给定流的引用。CCD_ 10也是如此。因为它做到了这一点,我们可以使用嵌套语句。也就是说,我们可以将第二个std::getline放在这个参数位置(实际上是第一个参数(,它期望std::istream。因此,如果我们遵循这种方法,那么我们可以编写嵌套语句:

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

哎呀,这是怎么回事?让我们从内而外进行分析。首先操作CCD_ 13从CCD_;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))

其将以CCD_ 16结束。那么,我们使用iss作为布尔表达式?为什么这样做,它做什么?它起作用,因为如果状态为OK或出现故障,std::streambool operator将被覆盖并返回。请参阅此处了解解释。始终检查任何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-语句范围内可见。因此,我们;"范围";变量尽可能窄。

你应该尽量经常这样做。您还应该始终通过将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 readingn";
return 0;
}

对于这个例子,我将id和值存储在一个映射中,这样就可以很容易地访问它们。