常常在GCC(MinGW)和C++ Builder 中都有读写数据的时候,也就是从文件中将一组数据读入二维数组<vector>,或者将数组中的数据格式化写入文件,甚至有时还想给文件加个文件头,当然文件头也要对齐了才好看一点,两个软件实现的方法都不一样,常常让人恼火,今天有空,编写了个类,两个都能通用的文件读写类FileData.
有3点请大家注意:
1.FileData类读的数据文件的格式是任意的,数据的排列方式也是任意的,
也就是说FileData可以自动检测数据的排列方式和分割方式,每行的数据个数可以互不相同!
一句话,只要文件里面有数据就可以正确识别!
2.FileData将数组和文件头写入文件的时候,自动对齐数据和文件头.
也就是说你用记事本打开一看,数据和文件头是排列得整整齐齐的.
3.FileData中使用了宽字符(汉字),所以为了让MinGW识别,请将FileData.hpp保存为UTF-8的格式.
// ----------------------------------------------------------------------------- // 功能强大的读写数据文件类 FileData.hpp // 经常要将数据导入C++,使用这个很方便 // Wu Xuping 2012-03-10 // 测试环境: // C++ builder 2010 // MinGW 4.5.1 windows // 使用很简单,看看最后附上的测试代码即可 // FileData.hpp一定要保存为UTF-8的格式,因为这是MinGW的默认格式 // ----------------------------------------------------------------------------- #ifndef FileData_H #define FileData_H // -------------------- #include <iostream> #include <fstream> #include <vector> #include <string> #include <sstream> #include <iterator> #include <algorithm> using namespace std; // ============================================================================= // IsCommentString::识别字符串是否是以"#%!"开头的注释行,空行也是注释行 // ============================================================================= bool IsCommentString(const string &istr) { bool IsValid = false; if (istr.size() > 0) { unsigned i = 0; while (i < istr.size()) { if (istr[i] == ' ' || istr[i] == '\t' || istr[i] == '\r' || istr[i] == '\n') { // 这些字符忽略不计 i++; } else { if (istr[i] == '#' || istr[i] == '%' || istr[i] == '!') { // 这些都是注释字符 IsValid = true; } break; // 可以中断循环了 } }; } else { IsValid = true; // 空行也是注释行 } return IsValid; }; // ============================================================================= // SplitString::分割字符串的函数,默认使用五种分隔符号": ;,\t" // ============================================================================= void SplitString(string str, vector<string>&sv, string separator = ": ;,\t") { // --------------------------------------- if (sv.size() > 0) { sv.clear(); } // --------------------------------------- vector<long int>index_v; // 分割索引 // --------------------------------------- for (unsigned i = 0; i < str.length(); i++) { for (unsigned j = 0; j < separator.length(); j++) { if (str[i] == separator[j]) { index_v.push_back(i); } } } // --------------------------------- index_v.push_back(-1); // 加上开始的字符 index_v.push_back(str.length()); // 加上结尾的字符 // ----------------------------------- sort(index_v.begin(), index_v.end()); // 排序 // -------------------------------- for (unsigned i = 1; i < index_v.size(); i++) { long int startindex = index_v[i - 1] + 1; long int endindex = index_v[i]; if (startindex < endindex) { string cstr(str.begin() + startindex, str.begin() + endindex); sv.push_back(cstr); } }; // -------------------------------- }; // ============================================================================= // StringToDouble::字符串转化为double的函数 // ============================================================================= bool StringToDouble(std::string Str, double &num) { bool isvalid = false; std::istringstream stream(Str); while (stream >> num) { isvalid = true; } return isvalid; }; // ============================================================================= // StringToInteger::字符串转化为int的函数 // ============================================================================= bool StringToInteger(std::string istr, int &num) { bool isvalid = false; std::istringstream stream(istr); ; while (stream >> num) { isvalid = true; } return isvalid; }; // ============================================================================= // StringToDoubleVector::将字符串转换为Double数组的函数 // ============================================================================= void StringToDoubleVector(string istr, vector<double> &ov) { if (ov.size() > 0) { ov.clear(); } // -------分割字符串 vector<string>sv; SplitString(istr, sv); // -----------字符转为double类型的数 for (unsigned int i = 0; i < sv.size(); i++) { double num; if (StringToDouble(sv[i], num)) { ov.push_back(num); } } }; // ============================================================================= // StringToIntegerVector::将字符串转换为Integer数组的函数 // ============================================================================= void StringToIntegerVector(string istr, vector<int> &ov) { if (ov.size() > 0) { ov.clear(); } // -------分割字符串 vector<string>sv; SplitString(istr, sv); // -----------字符转为double类型的数 for (unsigned int i = 0; i < sv.size(); i++) { int num; if (StringToInteger(sv[i], num)) { ov.push_back(num); } } }; // ----------------------------------------------------------------------------- // FileData类::读写文件中的数据到二维数组的类 // -------------------------------------------------- // 1.关于FileData::LoadDataFromFile(string FileName) // 以"#%!"开头的是注释行,空行忽略 // 数据分隔符可以是五种常规分隔符": ;,\t"的任意组合 // 数据每行的个数可以不相同 // 每个数据的前后可以包含文字说明,不影响数据的正确读入 // -------------------------------------------------- // 2.关于SaveDataToFile(string FileName, string Headstr = ""); // 保存二维数组到文件的函数,可以加文件头 // 每个数据的输出宽度是12个字符,文件头也一样 // 文件头和数据都会自动对齐 // 文件头可以使用五种常规分割符": ;,\t" // -------------------------------------------------- // 3. 关于void GetData(vector<vector<double> >&Data) // 提取数据 // -------------------------------------------------- // 4.关于 void SetData(vector<vector<double> >&Data) // 设置数据 // -------------------------------------------------- // 5.关于unsigned int GetRow() // 提取二维数组的行数 // -------------------------------------------------- // 6.关于unsigned int GetMinColumn() // 提取二维数组的最小列数 // -------------------------------------------------- // 7.关于unsigned int GetMaxColumn() // 提取二维数组的最大列数 // -------------------------------------------------- // 8.关于string GetHeadstr() // 提取文件第一行字符串 // -------------------------------------------------- // 9.关于void PrintData(); // 方便控制台打印和查看二维数组函数 // ----------------------------------------------------------------------------- class FileData { private: vector<vector<double> >_Data; // 二维数组 unsigned int _Row; // 数组的行数 unsigned int _MinColumn; // 数组最小列 unsigned int _MaxColumn; // 数组最大列 string _FileName; // 文件名 string _Headstr; // 文件第一行字符串 // -读写文件中的数据----------------------------------------------- void LoadDataFromFile(); void SaveDataToFile(); // ----------------------------------------------------------------- public: // ---构造函数 ------------- FileData() : _Row(0), _MinColumn(0), _MaxColumn(0), _FileName(""), _Headstr("") { }; // ----------------------------------------------------------------- // --读取文件中的数据 void LoadDataFromFile(string FileName); // ----------------------------------------------------------------- // --保存数据到文件中 void SaveDataToFile(string FileName, string Headstr = ""); // ------------------------------------------------------------------------ unsigned int GetRow() { _Row = _Data.size(); return _Row; }; // ------------------------------------------------------------------------ unsigned int GetMinColumn() { _MinColumn = 999999999; for (unsigned int i = 0; i < _Data.size(); i++) { if (_MinColumn > _Data[i].size()) { _MinColumn = _Data[i].size(); // 获取最小行 } } if (_MinColumn >= 999999999) { _MinColumn = 0; } return _MinColumn; }; // ------------------------------------------------------------------------ unsigned int GetMaxColumn() { _MaxColumn = 0; for (unsigned int i = 0; i < _Data.size(); i++) { if (_MaxColumn < _Data[i].size()) { _MaxColumn = _Data[i].size(); // 获取最小行 } } return _MaxColumn; }; // ------------------------------------------------------------------------- // 获取文件的第一行作为文件头 // ------------------------------------------------------------------------- string GetHeadstr() { ifstream fin(_FileName.c_str()); if (!fin.bad()) { getline(fin, _Headstr); } fin.close(); return _Headstr; }; // ------------------------------------------------------------------------ // 提取数据 // ------------------------------------------------------------------------ void GetData(vector<vector<double> >&Data) { Data.assign(_Data.begin(), _Data.end()); }; // ------------------------------------------------------------------------ // 设置数据 // ------------------------------------------------------------------------ void SetData(vector<vector<double> >&Data) { _Data.assign(Data.begin(), Data.end()); }; // ------------------------------------------------------------------------ void PrintData(); // -------------默认destructor函数 ------------- ~FileData() { }; // ----------- }; // ============================================================================== // LoadDataFromFile0 // ============================================================================== void FileData::LoadDataFromFile() { _Row = 0; _MinColumn = 999999999; _MaxColumn = 0; if (_Data.size() > 0) { _Data.clear(); } ifstream fin(_FileName.c_str()); if (!fin.bad()) { string sLine; // 行字符串 while (getline(fin, sLine)) { if (!IsCommentString(sLine)) { // 该行不是空行或注释行就可以提取数据 vector<double>tempdv; StringToDoubleVector(sLine, tempdv); if (tempdv.size() > 0) { _Data.push_back(tempdv); _Row++; if (tempdv.size() < _MinColumn) { _MinColumn = tempdv.size(); // 获取最小行 } if (tempdv.size() > _MaxColumn) { _MaxColumn = tempdv.size(); // 获取最大行 } } } } } fin.close(); // ---还原_MinColumn和_MaxColumn以及_Data的初始值 if (_MinColumn >= 999999999) { _MinColumn = 0; } if (_Row < 1) { _MinColumn = 0; _MaxColumn = 0; if (_Data.size() > 0) { _Data.clear(); } } } // ============================================================================== // LoadDataFromFile1 // ============================================================================== void FileData::LoadDataFromFile(string FileName) { _FileName = FileName; LoadDataFromFile(); } // ============================================================================== // SaveDataToFile0 // ============================================================================== void FileData::SaveDataToFile() { if (_Data.size() > 0) { vector<string>strv; for (unsigned int i = 0; i < _Data.size(); i++) { string cstr = ""; for (unsigned int j = 0; j < _Data[i].size(); j++) { char buffer[32]; sprintf(buffer, "%12.6g", _Data[i][j]); cstr = cstr + " " + buffer; } strv.push_back(cstr); } ofstream ofs(_FileName.c_str()); if (!ofs.bad()) { if (_Headstr.length() > 1) { vector<string>hvs; SplitString(_Headstr, hvs); string cstr = ""; for (unsigned int n = 0; n < hvs.size(); n++) { char buffer[256]; if (hvs[n].size() > 12) { sprintf(buffer, "%s", hvs[n].c_str()); } else { sprintf(buffer, "%12s", hvs[n].c_str()); } cstr = cstr + " " + buffer; } ofs << cstr << endl; } copy(strv.begin(), strv.end(), ostream_iterator<string>(ofs, "\n")); } ofs.close(); } } // ============================================================================== // SaveDataToFile1 // ============================================================================== void FileData::SaveDataToFile(string FileName, string Headstr) { _FileName = FileName; _Headstr = Headstr; SaveDataToFile(); } ////////////////////////////////////////////////////////////////////////// // --用于控制台打印数据的函数PrintData ////////////////////////////////////////////////////////////////////////// void FileData::PrintData() { cout << _FileName.c_str() << endl; if (_Data.size() > 0) { string cstr = ""; for (unsigned int n = 0; n < _Data[0].size() + 1; n++) { char buffer0[32]; char buffer[32]; if (n < 1) { sprintf(buffer, "%12s", "RowIndex"); cstr = cstr + buffer + " "; } else { sprintf(buffer0, "Column[%d]", n); sprintf(buffer, "%12s", buffer0); cstr = cstr + buffer + " "; } } cout << cstr << endl; // 输出数组 for (unsigned int i = 0; i < _Data.size(); i++) { char buffer0[32]; char buffer[32]; sprintf(buffer0, "Row[%d]", i); sprintf(buffer, "%12s", buffer0); cout << buffer << ' '; for (unsigned int j = 0; j < _Data[i].size(); j++) { sprintf(buffer, " %12.6g", _Data[i][j]); cout << buffer; } cout << endl; } } else { cout << "There is no data in this file at all!" << endl; } } // ------------------------------------------------------------------------------ #endif
下面是控制台测试程序,同样也要将测试程序保存为UTF-8的格式,
否则MinGW要使用 -finput-charset=GBK的编译选项,比较麻烦,
因为里面使用了宽字符(汉字):
// FileData example // GCC::g++ test.cpp // BCB::bcc32 test.cpp // 请将test.cpp保存为UTF-8的文本格式,这是MinGW(GCC)的默认格式 #include "FileData.hpp" int main() { string FileName = "Data.txt"; string Headstr; vector<vector<double> >Data; // 二维数组 FileData fd; // 定义类 fd.LoadDataFromFile(FileName); // 读取文件 fd.GetData(Data); // 获取数据 fd.PrintData(); // 在控制台打印数据 cout << "fd.GetRow()::" << fd.GetRow() << endl; // 数组行数 cout << "fd.GetMinColumn()::" << fd.GetMinColumn() << endl; // 数据最小列 cout << "fd.GetMaxColumn():: " << fd.GetMaxColumn() << endl; // 数据最大列 Headstr = fd.GetHeadstr(); // 文件第一行字符串 cout << "fd.GetHeadstr():: " << Headstr.c_str() << endl; fd.SaveDataToFile(" FileData0.txt ", Headstr); // 保存数组到文件 if (Data.size() > 0) { Data.clear(); } for (unsigned int j = 0; j < 3; j++) { vector<double>od1v; for (unsigned int i = 0; i < 5; i++) { od1v.push_back(i + j); } Data.push_back(od1v); } fd.SetData(Data); // 设置数据 fd.SaveDataToFile(" FileData.txt "); // 保存数组到文件 fd.SaveDataToFile(" FileDataWithHead.txt ", " index, Column1, column2, FileData "); // 保存数组到文件 // ----------------------------------------------- return 0; }
下面是C++Builder GUI中的测试程序:
// --------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { FileData fd; String FileName = "D:\\Backup\\我的文档\\RAD Studio\\Projects\\Data.txt"; fd.LoadDataFromFile(FileName.t_str()); // Form1->Caption=fd.GetRow(); // Form1->Caption=fd.GetHeadstr().c_str(); Form1->Caption = fd.GetMaxColumn(); } // ---------------------------------------------------------------------------
其中Data.txt的格式是任意的,数据的排列方式也是任意的,
也就是说FileData可以自动检测数据的排列方式和分割方式,每行的数据个数可以互不相同!
一句话,只要文件里面有数据就可以正确识别!
例如我测试的文件如下
col_1,col_2,col3,col_4,colfdsf #这是注释行 0.000000 3749.34324 2.000000 3.000000 4.000000 1.000000 2.000000 dsfdsf 3.000000,374.324 5.000000 年份 1990,月份 12,收入 130000,交税 30000 等 2.000000 3.000000 4.000000 37.e3, 6.000000
其它的,自己看看文件说明吧