非常不言自明,我尝试了谷歌并得到了很多可怕的专家交流,我在这里也搜索了无济于事。最好是在线教程或示例。多谢你们。
9 回答
更多信息会很有用。
但最简单的形式:
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
int main()
{
std::ifstream data("plop.csv");
std::string line;
while(std::getline(data,line))
{
std::stringstream lineStream(line);
std::string cell;
while(std::getline(lineStream,cell,','))
{
// You have a cell!!!!
}
}
}
另请参阅此问题:C++ 中的 CSV 解析器
您可以尝试 Boost Tokenizer 库,特别是Escaped List Separator
如果您真正要做的是操作 CSV 文件本身,那么 Nelson 的回答是有道理的。但是,我怀疑 CSV 只是您正在解决的问题的产物。在 C++ 中,这可能意味着您有这样的数据模型:
struct Customer {
int id;
std::string first_name;
std::string last_name;
struct {
std::string street;
std::string unit;
} address;
char state[2];
int zip;
};
std::vector<Customer>
因此,当您处理数据集合时,使用或是有意义的std::set<Customer>
。
考虑到这一点,将您的 CSV 处理视为两个操作:
// if you wanted to go nuts, you could use a forward iterator concept for both of these
class CSVReader {
public:
CSVReader(const std::string &inputFile);
bool hasNextLine();
void readNextLine(std::vector<std::string> &fields);
private:
/* secrets */
};
class CSVWriter {
public:
CSVWriter(const std::string &outputFile);
void writeNextLine(const std::vector<std::string> &fields);
private:
/* more secrets */
};
void readCustomers(CSVReader &reader, std::vector<Customer> &customers);
void writeCustomers(CSVWriter &writer, const std::vector<Customer> &customers);
一次读取和写入一行,而不是保留文件本身的完整内存表示。有几个明显的好处:
- 您的数据以对您的问题(客户)有意义的形式表示,而不是当前解决方案(CSV 文件)。
- 您可以轻松地为其他数据格式添加适配器,例如批量 SQL 导入/导出、Excel/OO 电子表格文件,甚至是 HTML
<table>
呈现。 - 您的内存占用可能会更小(取决于相对
sizeof(Customer)
与单行中的字节数)。 CSVReader
并且CSVWriter
可以重用作为内存模型(例如 Nelson 模型)的基础,而不会损失性能或功能。反之则不成立。
在我的时间里,我处理过很多 CSV 文件。我想补充一点建议:
1 - 根据来源(Excel 等),逗号或制表符可能嵌入在字段中。通常,规则是它们将受到“保护”,因为该字段将用双引号分隔,如“波士顿,MA 02346”。
2 - 某些来源不会用双引号分隔所有文本字段。其他来源会。其他人将分隔所有字段,甚至是数字。
3 - 包含双引号的字段通常会使嵌入的双引号加倍(并且字段本身用双引号分隔,如“George”“Babe”“Ruth”。
4 - 一些来源将嵌入 CR/LF(Excel 就是其中之一!)。有时它只是一个 CR。该字段通常会用双引号分隔,但这种情况很难处理。
这对你自己来说是一个很好的练习:)
你应该把你的图书馆分成三个部分
- 加载 CSV 文件
- 在内存中表示文件,以便您可以修改和读取它
- 将 CSV 文件保存回磁盘
因此,您正在考虑编写一个包含以下内容的 CSVDocument 类:
- 加载(常量字符*文件);
- 保存(常量字符*文件);
- 获取身体
这样您就可以像这样使用您的库:
CSVDocument doc;
doc.Load("file.csv");
CSVDocumentBody* body = doc.GetBody();
CSVDocumentRow* header = body->GetRow(0);
for (int i = 0; i < header->GetFieldCount(); i++)
{
CSVDocumentField* col = header->GetField(i);
cout << col->GetText() << "\t";
}
for (int i = 1; i < body->GetRowCount(); i++) // i = 1 so we skip the header
{
CSVDocumentRow* row = body->GetRow(i);
for (int p = 0; p < row->GetFieldCount(); p++)
{
cout << row->GetField(p)->GetText() << "\t";
}
cout << "\n";
}
body->GetRecord(10)->SetText("hello world");
CSVDocumentRow* lastRow = body->AddRow();
lastRow->AddField()->SetText("Hey there");
lastRow->AddField()->SetText("Hey there column 2");
doc->Save("file.csv");
这为我们提供了以下接口:
class CSVDocument
{
public:
void Load(const char* file);
void Save(const char* file);
CSVDocumentBody* GetBody();
};
class CSVDocumentBody
{
public:
int GetRowCount();
CSVDocumentRow* GetRow(int index);
CSVDocumentRow* AddRow();
};
class CSVDocumentRow
{
public:
int GetFieldCount();
CSVDocumentField* GetField(int index);
CSVDocumentField* AddField(int index);
};
class CSVDocumentField
{
public:
const char* GetText();
void GetText(const char* text);
};
现在你只需要从这里填写空白:)
当我这么说的时候相信我——花时间学习如何制作图书馆,尤其是那些处理数据加载、操作和保存的图书馆,不仅会消除你对此类图书馆存在的依赖,还会让你成为一个全能的——围绕更好的程序员。
:)
编辑
我不知道您对字符串操作和解析了解多少;因此,如果您遇到困难,我很乐意提供帮助。
这是您可以使用的一些代码。来自 csv 的数据存储在行数组中。每行都是一个字符串数组。希望这可以帮助。
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
typedef std::string String;
typedef std::vector<String> CSVRow;
typedef CSVRow::const_iterator CSVRowCI;
typedef std::vector<CSVRow> CSVDatabase;
typedef CSVDatabase::const_iterator CSVDatabaseCI;
void readCSV(std::istream &input, CSVDatabase &db);
void display(const CSVRow&);
void display(const CSVDatabase&);
int main(){
std::fstream file("file.csv", std::ios::in);
if(!file.is_open()){
std::cout << "File not found!\n";
return 1;
}
CSVDatabase db;
readCSV(file, db);
display(db);
}
void readCSV(std::istream &input, CSVDatabase &db){
String csvLine;
// read every line from the stream
while( std::getline(input, csvLine) ){
std::istringstream csvStream(csvLine);
CSVRow csvRow;
String csvCol;
// read every element from the line that is seperated by commas
// and put it into the vector or strings
while( std::getline(csvStream, csvCol, ',') )
csvRow.push_back(csvCol);
db.push_back(csvRow);
}
}
void display(const CSVRow& row){
if(!row.size())
return;
CSVRowCI i=row.begin();
std::cout<<*(i++);
for(;i != row.end();++i)
std::cout<<','<<*i;
}
void display(const CSVDatabase& db){
if(!db.size())
return;
CSVDatabaseCI i=db.begin();
for(; i != db.end(); ++i){
display(*i);
std::cout<<std::endl;
}
}
使用 boost tokenizer 来解析记录,请参阅此处了解更多详细信息。
ifstream in(data.c_str());
if (!in.is_open()) return 1;
typedef tokenizer< escaped_list_separator<char> > Tokenizer;
vector< string > vec;
string line;
while (getline(in,line))
{
Tokenizer tok(line);
vec.assign(tok.begin(),tok.end());
/// do something with the record
if (vec.size() < 3) continue;
copy(vec.begin(), vec.end(),
ostream_iterator<string>(cout, "|"));
cout << "\n----------------------" << endl;
}
我发现了这个有趣的方法:
Quote: CSVtoC 是一个将 CSV 或逗号分隔值文件作为输入并将其转储为 C 结构的程序。
当然,您不能对 CSV 文件进行更改,但如果您只需要对数据进行内存只读访问,它就可以工作。