C ++:从文本文件中读取
C++: reading in from a text file
我正在尝试找出读取 .txt 文件的最佳方式,信息以逗号分隔,行分隔。使用该信息,创建我的库存对象 class。
.txt 文件如下所示:
GOOGL, 938.85, 30
APPL, 506.34, 80
MISE, 68.00, 300
我的股票 class 构造函数就像 stock(string symbol, double price, int numOfShares);
在我的主程序中,我想设置一个输入文件流,它将读取信息并创建股票对象 class,如下所示:
stock stock1("GOOGL", 938.85, 30);
stock stock2("APPL", 380.50, 60);
我假设我使用 ifstream 和 getline,但不太确定如何设置它。
谢谢!
#include <fstream>
#include <string>
#include <sstream>
int main()
{
//Open file
std::ifstream file("C:\Temp\example.txt");
//Read each line
std::string line;
while (std::getline(file, line))
{
std::stringstream ss(line);
std::string symbol;
std::string numstr;
//Read each comma delimited string and convert to required type
std::getline(ss, symbol, ',');
std::getline(ss, numstr, ',');
double price = std::stod(numstr);
std::getline(ss, numstr, ',');
int numOfShares = std::stoi(numstr);
//Construct stock object with variables above
stock mystock(symbol, price, numOfShares);
}
return 0;
}
您似乎想要读取 csv 数据。这是一个标准任务,我会给你详细的解释。最终所有的阅读都将在一行中完成。
我建议使用 "modern" C++ 方法。
仍然所有谈论 csv 的人都链接到 How can I read and parse CSV files in C++?,这些问题来自 2009 年,现在已有 10 多年的历史了。大多数答案也很旧而且非常复杂。所以,也许是时候做出改变了。
在现代 C++ 中,您有迭代范围的算法。您经常会看到类似 "someAlgoritm(container.begin(), container.end(), someLambda)" 的内容。这个想法是我们迭代一些相似的元素。
在您的情况下,我们迭代输入字符串中的标记,并创建子字符串。这称为标记化。
正是为了这个目的,我们有了 std::sregex_token_iterator
。因为我们已经为此目的定义了一些东西,所以我们应该使用它。
这个东西是一个迭代器。用于遍历字符串,因此是 sregex。 begin 部分定义了我们要对什么范围的输入进行操作,然后有一个 std::regex
表示输入字符串中应该匹配/或不应该匹配的内容。匹配策略的类型由最后一个参数给出。
- 1 --> 给我在正则表达式和
中定义的内容
- -1 --> 告诉我根据正则表达式不匹配的内容。
所以,现在我们了解了迭代器,我们可以 std::copy 从迭代器到我们的目标的标记,std::vector
的 std::string
。由于我们不知道我们有多少列,我们将使用 std::back_inserter
作为目标。这将添加我们从 std::sregex_token_iterator
获得的所有标记并将其附加到我们的 std::vector<std::string>>
。我们有多少列并不重要。
很好。这样的声明可能看起来像
std::copy( // We want to copy something
std::sregex_token_iterator // The iterator begin, the sregex_token_iterator. Give back first token
(
line.begin(), // Evaluate the input string from the beginning
line.end(), // to the end
re, // Add match a comma
-1 // But give me back not the comma but everything else
),
std::sregex_token_iterator(), // iterator end for sregex_token_iterator, last token + 1
std::back_inserter(cp.columns) // Append everything to the target container
);
现在我们可以理解这个复制操作是如何工作的了。
下一步。我们想从文件中读取。该文件还包含某种相同的数据。相同的数据是行。
和上面一样,我们可以迭代类似的数据。如果它是文件输入或其他什么。为此,C++ 有 std::istream_iterator
。这是一个模板,作为模板参数,它获取应读取的数据类型,作为构造函数参数,它获取对输入流的引用。不管输入流是 std::cin
、std::ifstream
还是 std::istringstream
,都没有关系。所有类型的流的行为都是相同的。
并且由于我们没有文件 SO,我使用(在下面的示例中)std::istringstream
来存储输入的 csv 文件。但是你当然可以通过定义 std::ifstream testCsv(filename)
打开一个文件。没问题。
并且使用 std::istream_iterator
,我们遍历输入并读取相似的数据。在我们的例子中,一个问题是我们想要遍历特殊数据而不是一些内置数据类型。
为了解决这个问题,我们定义了一个 Proxy class,它为我们做内部工作(我们不想知道如何,应该封装在代理中)。在代理中,我们覆盖类型转换运算符,以获得我们预期的 std::istream_iterator
.
类型的结果
还有最后一个重要步骤。 std::vector
有一个范围构造器。它还有很多其他的构造函数,我们可以在 std::vector
类型的变量的定义中使用。但就我们的目的而言,此构造函数最适合。
所以我们定义了一个变量 csv 并使用它的范围构造函数并给它一个范围的开始和一个范围的结束。并且,在我们的特定示例中,我们使用 std::istream_iterator
.
的开始和结束迭代器
如果我们结合以上所有内容,读取完整的CSV文件是一行,它是调用其构造函数的变量定义。
请查看结果代码:
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <regex>
#include <algorithm>
std::istringstream testCsv{ R"(GOOGL, 938.85, 30
APPL, 506.34, 80
MISE, 68.00, 300)" };
// Define Alias for easier Reading
using Columns = std::vector<std::string>;
using CSV = std::vector<Columns>;
// Proxy for the input Iterator
struct ColumnProxy {
// Overload extractor. Read a complete line
friend std::istream& operator>>(std::istream& is, ColumnProxy& cp) {
// Read a line
std::string line; cp.columns.clear();
std::getline(is, line);
// The delimiter
const std::regex re(",");
// Split values and copy into resulting vector
std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1),
std::sregex_token_iterator(),
std::back_inserter(cp.columns));
return is;
}
// Type cast operator overload. Cast the type 'Columns' to std::vector<std::string>
operator std::vector<std::string>() const { return columns; }
protected:
// Temporary to hold the read vector
Columns columns{};
};
int main()
{
// Define variable CSV with its range constructor. Read complete CSV in this statement, So, one liner
CSV csv{ std::istream_iterator<ColumnProxy>(testCsv), std::istream_iterator<ColumnProxy>() };
// Print result. Go through all lines and then copy line elements to std::cout
std::for_each(csv.begin(), csv.end(), [](Columns& c) {
std::copy(c.begin(), c.end(), std::ostream_iterator<std::string>(std::cout, " ")); std::cout << "\n"; });
}
我希望解释足够详细,让您了解您可以使用现代 C++ 做什么。
这个例子基本上不关心源文本文件中有多少行和列。它什么都吃
我正在尝试找出读取 .txt 文件的最佳方式,信息以逗号分隔,行分隔。使用该信息,创建我的库存对象 class。
.txt 文件如下所示:
GOOGL, 938.85, 30
APPL, 506.34, 80
MISE, 68.00, 300
我的股票 class 构造函数就像 stock(string symbol, double price, int numOfShares);
在我的主程序中,我想设置一个输入文件流,它将读取信息并创建股票对象 class,如下所示:
stock stock1("GOOGL", 938.85, 30);
stock stock2("APPL", 380.50, 60);
我假设我使用 ifstream 和 getline,但不太确定如何设置它。
谢谢!
#include <fstream>
#include <string>
#include <sstream>
int main()
{
//Open file
std::ifstream file("C:\Temp\example.txt");
//Read each line
std::string line;
while (std::getline(file, line))
{
std::stringstream ss(line);
std::string symbol;
std::string numstr;
//Read each comma delimited string and convert to required type
std::getline(ss, symbol, ',');
std::getline(ss, numstr, ',');
double price = std::stod(numstr);
std::getline(ss, numstr, ',');
int numOfShares = std::stoi(numstr);
//Construct stock object with variables above
stock mystock(symbol, price, numOfShares);
}
return 0;
}
您似乎想要读取 csv 数据。这是一个标准任务,我会给你详细的解释。最终所有的阅读都将在一行中完成。
我建议使用 "modern" C++ 方法。
仍然所有谈论 csv 的人都链接到 How can I read and parse CSV files in C++?,这些问题来自 2009 年,现在已有 10 多年的历史了。大多数答案也很旧而且非常复杂。所以,也许是时候做出改变了。
在现代 C++ 中,您有迭代范围的算法。您经常会看到类似 "someAlgoritm(container.begin(), container.end(), someLambda)" 的内容。这个想法是我们迭代一些相似的元素。
在您的情况下,我们迭代输入字符串中的标记,并创建子字符串。这称为标记化。
正是为了这个目的,我们有了 std::sregex_token_iterator
。因为我们已经为此目的定义了一些东西,所以我们应该使用它。
这个东西是一个迭代器。用于遍历字符串,因此是 sregex。 begin 部分定义了我们要对什么范围的输入进行操作,然后有一个 std::regex
表示输入字符串中应该匹配/或不应该匹配的内容。匹配策略的类型由最后一个参数给出。
- 1 --> 给我在正则表达式和 中定义的内容
- -1 --> 告诉我根据正则表达式不匹配的内容。
所以,现在我们了解了迭代器,我们可以 std::copy 从迭代器到我们的目标的标记,std::vector
的 std::string
。由于我们不知道我们有多少列,我们将使用 std::back_inserter
作为目标。这将添加我们从 std::sregex_token_iterator
获得的所有标记并将其附加到我们的 std::vector<std::string>>
。我们有多少列并不重要。
很好。这样的声明可能看起来像
std::copy( // We want to copy something
std::sregex_token_iterator // The iterator begin, the sregex_token_iterator. Give back first token
(
line.begin(), // Evaluate the input string from the beginning
line.end(), // to the end
re, // Add match a comma
-1 // But give me back not the comma but everything else
),
std::sregex_token_iterator(), // iterator end for sregex_token_iterator, last token + 1
std::back_inserter(cp.columns) // Append everything to the target container
);
现在我们可以理解这个复制操作是如何工作的了。
下一步。我们想从文件中读取。该文件还包含某种相同的数据。相同的数据是行。
和上面一样,我们可以迭代类似的数据。如果它是文件输入或其他什么。为此,C++ 有 std::istream_iterator
。这是一个模板,作为模板参数,它获取应读取的数据类型,作为构造函数参数,它获取对输入流的引用。不管输入流是 std::cin
、std::ifstream
还是 std::istringstream
,都没有关系。所有类型的流的行为都是相同的。
并且由于我们没有文件 SO,我使用(在下面的示例中)std::istringstream
来存储输入的 csv 文件。但是你当然可以通过定义 std::ifstream testCsv(filename)
打开一个文件。没问题。
并且使用 std::istream_iterator
,我们遍历输入并读取相似的数据。在我们的例子中,一个问题是我们想要遍历特殊数据而不是一些内置数据类型。
为了解决这个问题,我们定义了一个 Proxy class,它为我们做内部工作(我们不想知道如何,应该封装在代理中)。在代理中,我们覆盖类型转换运算符,以获得我们预期的 std::istream_iterator
.
还有最后一个重要步骤。 std::vector
有一个范围构造器。它还有很多其他的构造函数,我们可以在 std::vector
类型的变量的定义中使用。但就我们的目的而言,此构造函数最适合。
所以我们定义了一个变量 csv 并使用它的范围构造函数并给它一个范围的开始和一个范围的结束。并且,在我们的特定示例中,我们使用 std::istream_iterator
.
如果我们结合以上所有内容,读取完整的CSV文件是一行,它是调用其构造函数的变量定义。
请查看结果代码:
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <regex>
#include <algorithm>
std::istringstream testCsv{ R"(GOOGL, 938.85, 30
APPL, 506.34, 80
MISE, 68.00, 300)" };
// Define Alias for easier Reading
using Columns = std::vector<std::string>;
using CSV = std::vector<Columns>;
// Proxy for the input Iterator
struct ColumnProxy {
// Overload extractor. Read a complete line
friend std::istream& operator>>(std::istream& is, ColumnProxy& cp) {
// Read a line
std::string line; cp.columns.clear();
std::getline(is, line);
// The delimiter
const std::regex re(",");
// Split values and copy into resulting vector
std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1),
std::sregex_token_iterator(),
std::back_inserter(cp.columns));
return is;
}
// Type cast operator overload. Cast the type 'Columns' to std::vector<std::string>
operator std::vector<std::string>() const { return columns; }
protected:
// Temporary to hold the read vector
Columns columns{};
};
int main()
{
// Define variable CSV with its range constructor. Read complete CSV in this statement, So, one liner
CSV csv{ std::istream_iterator<ColumnProxy>(testCsv), std::istream_iterator<ColumnProxy>() };
// Print result. Go through all lines and then copy line elements to std::cout
std::for_each(csv.begin(), csv.end(), [](Columns& c) {
std::copy(c.begin(), c.end(), std::ostream_iterator<std::string>(std::cout, " ")); std::cout << "\n"; });
}
我希望解释足够详细,让您了解您可以使用现代 C++ 做什么。
这个例子基本上不关心源文本文件中有多少行和列。它什么都吃