使用getline读取.csv文件时出现问题
Having trouble using getline to read from a .csv file
程序的一部分是我尝试从.csv文件中读入行,并将其中的一部分存储到结构中。然而,当我试图执行下面显示的代码时,控制台中告诉我有一个无效的stod
实例。有了这些信息,我进入调试器,发现文件中没有任何内容被读取到我创建的伪变量(part1 - part4
)中,并且它们的值仍然是""
。
我正在阅读的.csv中的一行示例是:
"Photo Editor & Candy Camera & Grid & ScrapBook,ART_AND_DESIGN,4.1,159,19M,10;000+,Free,0,Everyone,Art & Design,January 7; 2018,1.0.0,4.0.3 and up"
我只想一直到它说"159"
(也就是评论的数量)。
#include <iostream>
#include <cmath>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <string>
using namespace std;
struct App {
std::string name;
std::string category;
double rating;
int reviewNum;
};
void readData(App appList[], const int size) {
ifstream inFile;
inFile.open("googleplaystore.csv");
if(inFile.fail()) {
cout << "File open error!";
exit(0); //Close program
}
for(int appIndex = 0; appIndex < size; appIndex++) {
string part1, part2, part3, part4, trash = "";
//Line1
getline(inFile, part1, ','); //read until ,
getline(inFile, part2, ',');
getline(inFile, part3, ',');
getline(inFile, part4 , ',');
getline(inFile, trash); //read until end of line
appList[appIndex].name = part1;
appList[appIndex].category = part2;
appList[appIndex].rating = stod(part3);
appList[appIndex].reviewNum = stoi(part4);
if(inFile.fail()) {
cout << "File read error!";
exit(0); //Close program
}
}
int main()
{
cout << fixed << setprecision(1);
App appList[NUM_RECORDS] = {};
readData(appList, NUM_RECORDS);
}
您的程序执行错误检查。它尝试读取超过文件末尾的内容,但失败,忽略错误,然后尝试将不存在的输入字符串转换为double
。出现异常时,此操作失败。
在程序尝试执行所有这些操作后,检查文件为时已晚。
在每个IO操作完成后立即检查其是否成功。
一种流行的方法是逐行读取输入,然后tgen分别解析每一行,例如下面的代码片段。
while (std::getline(infile, instring)) {
std::istringstream linestream (instring);
// read the linestream
}
除@n指出的问题外代词"m"。由于无法验证getline
的返回,您将很快遇到尝试通过使用','
作为分隔符(或任何其他非换行分隔符)连续调用getline
来解析.csv文件的限制。当您指定除'n'
之外的分隔符时,getline
将在尝试读取所有字段时忽略'n'
,以查找下一个','
。
相反,通常从.csv中读取的行创建stringstream
,然后使用getline
和分隔符解析stringstream
中需要的内容。为什么?stringstream
中只有一行数据,因此getline
必须在读取最后一个字段后停止——因为stringstream
将为空。。。在您的情况下,由于您没有读取所有字段,因此可以使用字段计数器和App
的临时实例来填充数据。您可以简单地使用switch(fieldcount) {...}
将从字符串流读取的数据分离到正确的成员变量中。
您已经在使用std::string
,您还可以使用#include <vector>
并使用std::vector<App>
作为存储,而不是一个普通的旧阵列。您可以简单地将std::vector<App>
作为读取函数的返回类型,并在读取.csv文件时填充矢量,然后返回矢量以供程序中的其他地方使用。
把这些部分放在一起,你可以定义你的读取函数如下:
/* function reading csv and returning std::vector<App> containing
* first 4 fields from each line of input .csv
*/
std::vector<App> read_csv (const std::string& name)
{
std::vector<App> v {}; /* vector of App */
std::string line; /* string to hold each line */
std::ifstream fin (name); /* input file stream */
...
现在只需声明App
的一个临时实例,从line
创建一个std::stringstream
,一个用于保存每个字段的std:string
,并循环从字符串流中获取每个字段,例如
while (std::getline (fin, line)) { /* read entire line into line */
App tmp; /* temporary instance to fill */
size_t nfield = 0; /* field counter for switch */
std::string field; /* string to hold each field */
std::stringstream ss (line); /* create stringstream from line */
while (getline (ss, field, ',')) { /* read each field from line */
...
现在您有了字段,只需在字段计数器上输入switch
即可将其分配给正确的成员变量,并在填充第4个字段后,将App
的临时实例添加到向量中,例如
switch (nfield) { /* switch on nfield */
case 0: tmp.name = field; break; /* fill name */
case 1: tmp.category = field; break; /* fill category */
case 2: try { /* convert field to double */
tmp.rating = stod (field);
}
catch (const std::exception & e) {
std::cerr << "error invalid tmp.rating: " <<
e.what() << 'n';
goto nextline;
}
break;
case 3: try { /* convert field to int */
tmp.reviewNum = stoi (field);
v.push_back(tmp);
}
catch (const std::exception & e) {
std::cerr << "error invalid tmp.reviewNum: " <<
e.what() << 'n';
}
goto nextline; /* all done with fields, get next line */
break;
}
...
读取循环中剩下的就是更新字段计数器nfield
,并提供一个标签来打破嵌套循环和switch
,例如
nfield++; /* increment field counter */
}
nextline:; /* label for nextline */
}
从文件中读取所有行后,只需返回向量:
return v; /* return filled vector of App */
}
您可以从main()
调用类似于的读取函数
/* fill vector of App from csv */
std::vector<App> appdata = read_csv (argv[1]);
在一个简短的例子中,您可以执行以下操作,将您的googleplay.csv中所有想要的信息读取到App
,的向量中
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
struct App {
std::string name;
std::string category;
double rating;
int reviewNum;
};
/* function reading csv and returning std::vector<App> containing
* first 4 fields from each line of input .csv
*/
std::vector<App> read_csv (const std::string& name)
{
std::vector<App> v {}; /* vector of App */
std::string line; /* string to hold each line */
std::ifstream fin (name); /* input file stream */
while (std::getline (fin, line)) { /* read entire line into line */
App tmp; /* temporary instance to fill */
size_t nfield = 0; /* field counter for switch */
std::string field; /* string to hold each field */
std::stringstream ss (line); /* create stringstream from line */
while (getline (ss, field, ',')) { /* read each field from line */
switch (nfield) { /* switch on nfield */
case 0: tmp.name = field; break; /* fill name */
case 1: tmp.category = field; break; /* fill category */
case 2: try { /* convert field to double */
tmp.rating = stod (field);
}
catch (const std::exception & e) {
std::cerr << "error invalid tmp.rating: " <<
e.what() << 'n';
goto nextline;
}
break;
case 3: try { /* convert field to int */
tmp.reviewNum = stoi (field);
v.push_back(tmp);
}
catch (const std::exception & e) {
std::cerr << "error invalid tmp.reviewNum: " <<
e.what() << 'n';
}
goto nextline; /* all done with fields, get next line */
break;
}
nfield++; /* increment field counter */
}
nextline:; /* label for nextline */
}
return v; /* return filled vector of App */
}
int main (int argc, char **argv) {
if (argc < 2) {
std::cerr << "error: insufficient inputn" <<
"usage: " << argv[0] << " <file>n";
return 1;
}
/* fill vector of App from csv */
std::vector<App> appdata = read_csv (argv[1]);
for (auto& v : appdata) /* output results */
std::cout << "nname : " << v.name
<< "ncategory : " << v.category
<< "nrating : " << v.rating
<< "nreviewNum: " << v.reviewNum << 'n';
}
示例使用/输出
使用文件dat/googleplay.csv
中提供的一行输入,您将收到程序的以下输出:
$ ./bin/read_googlecsv dat/googleplay.csv
name : Photo Editor & Candy Camera & Grid & ScrapBook
category : ART_AND_DESIGN
rating : 4.1
reviewNum: 159
读取每一行并使用std::stringstream
从逗号分隔的值解析字段,可以解决在需要使用所有字段时将面临的许多问题。它还允许在每次调用getline
时消耗整行输入,以防止在文件出现格式问题时部分读取一行。仔细考虑使用App
向量而不是普通的旧数组的优点。如果您还有其他问题,请告诉我。
虽然答案已经被接受,但我想展示一种更"现代"的C++方法。
如果你能研究这个解决方案,并在未来尝试使用一些功能,我会很高兴。
在面向对象的世界中,我们使用类(或结构),并将对这些数据进行操作的数据和函数放在一个(封装的)对象中。
只有类应该知道如何读取和写入其数据。不是一些外部全局函数。因此,我为您的结构添加了2个成员函数。我已经重写了插入器和提取器运算符。
在提取器中,我们将使用现代C++算法,将字符串拆分为令牌。为此,我们有了std::sregex_token_iterator
。因为有一个专门的功能,我们应该使用它。此外,它非常简单。
使用下面的一行,我们将整个字符串拆分为令牌,并将生成的令牌放入std::vector
中
std::vector token(std::sregex_token_iterator(line.begin(), line.end(), delimiter, -1), {});
然后,我们将结果数据复制到成员变量中。
对于演示输出,我还覆盖了插入器运算符。现在,您可以对App类型的变量使用extractor和inserter运算符(">>"answers"<<"),就像对任何其他C++积分变量一样。
总的来说,我们还使用了一种超简单的方法。首先,我们打开文件并检查是否可以。
然后,我们定义了一个变量"apps"(App的std::vector
),并使用其范围构造函数和std::istream_operator
来读取完整的文件。而且,由于应用程序有一个重写的提取器运算符,它知道如何读取并将为我们解析完整的CSV文件。
再次,非常简单和短的一行
std::vector apps(std::istream_iterator<App>(inFile), {});
将读取完整的源文件、所有行、解析行并将成员变量存储在生成的std::vector
的单个App元素中。
请参阅下面的完整示例:
#include <string>
#include <iostream>
#include <vector>
#include <fstream>
#include <regex>
#include <iterator>
#include <algorithm>
std::regex delimiter{ "," };
struct App {
// The data. Member variables
std::string name{};
std::string category{};
double rating{};
int reviewNum{};
// Overwrite extractor operator
friend std::istream& operator >> (std::istream& is, App& app) {
// Read a complete line
if (std::string line{}; std::getline(is, line)) {
// Tokenize it
std::vector token(std::sregex_token_iterator(line.begin(), line.end(), delimiter, -1), {});
// If we read at least 4 tokens then assign the values to our struct
if (4U <= token.size()) {
// Now copy the data from the vector to our members
app.name = token[0]; app.category = token[1];
app.rating = std::stod(token[2]); app.reviewNum = std::stoi(token[2]);
}
}
return is;
}
// Overwrite inserter operator
friend std::ostream& operator << (std::ostream& os, const App& app) {
return os << "Name: " << app.name << "nCategory: " << app.category
<< "nRating: " << app.rating << "nReviews: " << app.reviewNum;
}
};
int main() {
// Open file and check, if it could be opened
if (std::ifstream inFile("googleplaystore.csv"); inFile) {
// Define the variable and use range constructor to read and parse the complete file
std::vector apps(std::istream_iterator<App>(inFile), {});
// Show result to the user
std::copy(apps.begin(), apps.end(), std::ostream_iterator<App>(std::cout, "n"));
}
return 0;
}
真遗憾没有人会读这篇。
免责声明:这是纯粹的示例代码,没有生产力,因此没有错误处理。
- 使用mongocxx驱动程序时包含头文件问题
- 在WSL:configure_file上对config_file的每次调用都失败:配置文件时出现问题
- 编译要在英特尔Hyperscan中使用的.cc文件时出现问题
- 使用 seekg() 读取C++中的文件时出现问题
- sqlite3 和生成文件中的链接出现问题
- C++头文件和类实现出现问题
- 用户输入字符串的文件附加问题..C++
- node-gyp 的先有鸡还是先有蛋的问题:指向依赖项中的头文件
- 读取文件并将其存储在unordered_map中时出现问题
- 编译 llvm 插件时出现问题:llvm/Config/llvm-config.h:没有这样的文件或目录
- 我在使用C++文件时遇到了一些问题
- 在Linux上使用Clang / OLLVM交叉编译helloworld Windows可执行文件时的问题
- 将文件复制到自定义位置,存在字符串转换问题
- DirectX12 的问题:"d3dApp.h":没有这样的文件或目录
- (ODR 使用问题)在不同文件中priority_queue名称相同的结构
- C++模板编程设计问题 - 根据输入文件返回不同的类型
- 读取制表符分隔的文件时出现问题 C++
- 当我在 CLion 中读取数组中的 txt 文件时C++编码问题
- 使用 RapidXML 解析大型 XML(大小大于 65 KB)文件时出现问题
- C++:读取.BMP文件时出现问题;文件结束时间早于预期