您的帖子中有 2 个问题:
- 如何在 cpp 中解析这个文件?
- 是否有像 Java 那样的拆分功能,所以我可以将所有内容拆分并存储为令牌?
我将回答这两个问题并展示一个演示示例。
让我们从将字符串拆分为标记开始。有几种可能性。我们从简单的开始。
由于字符串中的标记由空格分隔,我们可以利用提取运算符 (>>) 的功能。这将从输入流中读取数据,直到空格,然后将此读取的数据转换为指定的变量。你知道这个操作是可以链式的。
那么对于示例字符串
const std::string line{ "Token1 Token2 Token3 Token4" };
您可以简单地将其放入std::istringstream,然后从流中提取变量:
std::istringstream iss1(line);
iss1 >> subString1 >> subString2 >> subString3 >> subString4;
缺点是需要写很多东西,而且要知道字符串的元素个数。
我们可以通过使用向量作为taget 数据存储并使用其范围构造函数填充它来克服这个问题。向量范围构造函数采用 begin 和 end 插入器并将数据复制到其中。
作为迭代器,我们使用std::istream_iterator。简单来说,这将调用提取器运算符 (>>),直到所有数据都被消耗完。无论我们将拥有多少数据。
这将如下所示:
std::istringstream iss2(line);
std::vector token(std::istream_iterator<std::string>(iss2), {});
这可能看起来很复杂,但其实不然。我们定义了一个std::vector 类型的变量“token”。我们使用它的范围构造函数。
而且,我们可以在没有模板参数的情况下定义 std::vector。编译器可以从给定的函数参数中推断出参数。此功能称为 CTAD(“类模板参数推导”,需要 C++17)。
此外,您可以看到我没有明确使用“end()”-iterator。
这个迭代器将从带有正确类型的空大括号括起来的默认初始值设定项构造,因为由于 std::vector 构造函数需要,它将被推断为与第一个参数的类型相同。
还有一个额外的解决方案。这是最强大的解决方案,因此一开始可能会有点复杂。
这样可以避免使用 std::istringstream 并使用 std::sregex_token_iterator 直接将字符串转换为令牌。使用非常简单。结果是用于拆分原始字符串的单行:
std::vector<std::string> token2(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});
因此,现代 C++ 有一个内置功能,该功能正是为标记字符串而设计的。它被称为std::sregex_token_iterator。这是什么东西?
顾名思义,它是一个迭代器。它将遍历一个字符串(因此它的名称中包含“s”)并返回拆分的标记。标记将再次匹配正则表达式。或者,在本地,分隔符将被匹配,其余的将被视为令牌并返回。这将通过其构造函数中的最后一个标志来控制。
让我们看看这个构造函数:
token2(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});
第一个参数是它应该在源字符串中开始的位置,第二个参数是结束位置,迭代器应该工作到该位置。最后一个参数是:
- 1,如果您想对正则表达式进行肯定匹配
- -1,将返回与正则表达式不匹配的所有内容
最后但并非最不重要的是正则表达式本身。请阅读网络 abot 正则表达式。有大量可用的页面。
请在此处查看所有 3 个解决方案的演示:
#include <iostream>
#include <string>
#include <vector>
#include <regex>
#include <sstream>
#include <iterator>
#include <algorithm>
/// Split string into tokens
int main() {
// White space separated tokens in a string
const std::string line{ "Token1 Token2 Token3 Token4" };
// Solution 1: Use extractor operator ----------------------------------
// Here, we will store the result
std::string subString1{}, subString2{}, subString3{}, subString4{};
// Put the line into an istringstream for easier extraction
std::istringstream iss1(line);
iss1 >> subString1 >> subString2 >> subString3 >> subString4;
// Show result
std::cout << "\nSolution 1: Use inserter operator\n- Data: -\n" << subString1 << "\n"
<< subString2 << "\n" << subString3 << "\n" << subString4 << "\n";
// Solution 2: Use istream_iterator ----------------------------------
std::istringstream iss2(line);
std::vector token(std::istream_iterator<std::string>(iss2), {});
// Show result
std::cout << "\nSolution 2: Use istream_iterator\n- Data: -\n";
std::copy(token.begin(), token.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
// Solution 3: Use std::sregex_token_iterator ----------------------------------
const std::regex re(" ");
std::vector<std::string> token2(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});
// Show result
std::cout << "\nSolution 3: Use sregex_token_iterator\n- Data: -\n";
std::copy(token2.begin(), token2.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
return 0;
}
所以,现在是关于如何阅读文本文件的答案。
创建正确的数据结构至关重要。然后,覆盖插入器和提取器操作符,将上述功能放入其中。
请看下面的演示示例。当然还有很多其他可能的解决方案:
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <iterator>
struct ItemAndPrice {
// Data
std::string item{};
unsigned int price{};
// Extractor
friend std::istream& operator >> (std::istream& is, ItemAndPrice& iap) {
// Read a complete line from the stream and check, if that worked
if (std::string line{}; std::getline(is, line)) {
// Read the item and price from that line and check, if that worked
if (std::istringstream iss(line); !(iss >> iap.item >> iap.price))
// There was an error, while reading item and price. Set failbit of input stream
is.setf(std::ios::failbit);
}
return is;
}
// Inserter
friend std::ostream& operator << (std::ostream& os, const ItemAndPrice& iap) {
// Simple output of our internal data
return os << iap.item << " " << iap.price;
}
};
struct MarketPrice {
// Data
std::vector<ItemAndPrice> marketPriceData{};
size_t numberOfElements() const { return marketPriceData.size(); }
unsigned int weight{};
// Extractor
friend std::istream& operator >> (std::istream& is, MarketPrice& mp) {
// Read a complete line from the stream and check, if that worked
if (std::string line{}; std::getline(is, line)) {
size_t numberOfEntries{};
// Read the number of following entries and the weigth from that line and check, if that worked
if (std::istringstream iss(line); (iss >> numberOfEntries >> mp.weight)) {
mp.marketPriceData.clear();
// Now copy the numberOfEntries next lines into our vector
std::copy_n(std::istream_iterator<ItemAndPrice>(is), numberOfEntries, std::back_inserter(mp.marketPriceData));
}
else {
// There was an error, while reading number of following entries and the weigth. Set failbit of input stream
is.setf(std::ios::failbit);
}
}
return is;
};
// Inserter
friend std::ostream& operator << (std::ostream& os, const MarketPrice& mp) {
// Simple output of our internal data
os << "\nNumber of Elements: " << mp.numberOfElements() << " Weight: " << mp.weight << "\n";
// Now copy all marekt price data to output stream
if (os) std::copy(mp.marketPriceData.begin(), mp.marketPriceData.end(), std::ostream_iterator<ItemAndPrice>(os, "\n"));
return os;
}
};
// For this example I do not use argv and argc and file streams.
// This, because on Stackoverflow, I do not have files on Stackoverflow
// So, I put the file data in an istringstream. But for the below example,
// there is no difference between a file stream or a string stream
std::istringstream sourceFile{R"(2 300
abc12 130
bcd22 456
3 400
abfg12 230
bcpd22 46
abfrg2 13)"};
int main() {
// Here we will store all the resulting data
// So, read the complete source file, parse the data and store result in vector
std::vector mp(std::istream_iterator<MarketPrice>(sourceFile), {});
// Now, all data are in mp. You may work with that now
// Show result on display
std::copy(mp.begin(), mp.end(), std::ostream_iterator<MarketPrice>(std::cout, "\n"));
return 0;
}