c++ - 在 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++ 中,分割线很容易。我已经在这里提供了几个关于如何拆分字符串的答案。无论如何,我将在这里详细解释它并针对您的特殊情况。稍后我还会提供一个完整的工作示例。
我们使用它的基本功能std::getline
可以读取完整的行或到给定字符的行。请看这里。
让我们举个例子。如果文本存储在 a 中,std::string
我们将首先将其放入 astd::istringstream
中。然后我们可以使用std::getline
从std::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 operator
被std::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 和值存储在地图中,以便可以轻松访问它们。
推荐阅读
- javascript - ClickMarker 在 XY Amcharts 中不起作用
- java - 由于缺少 ReactiveWebServerFactory bean,无法启动 ReactiveWebApplicationContext
- reporting-services - SSRS饼图分割一个切片
- excel - 将 Excel 连接到 Power BI 数据模型
- java - TWS Java API 编辑AddJobStreamInstanceWithVariableSubstitution
- autohotkey - AHK 脚本在每次连续按下时发送不同的按钮
- ajax - 使用ajax从json读取和显示值
- angular - 如何覆盖 ngrx 商店中的某些操作?
- sql - sql group 通过忽略大小写和后缀或最后一个字母
- r - 使用组变量创建新列