C++设计日志:读写定界符文件
荣耀 2003
我将撰写的“C++设计实践”系列文章,会讲到一些数据处理系统设计方法。我并不希望文章局限于特定数据库产品,我也不喜欢空对空地讲述太多抽象道理。我必须编写一些模拟数据库操作的代码,用于读写定界符文件的类Delimfile首当其冲。
最初并没有打算编写一个“完备类”。倘若只为撰写文章,一个具有十来个成员函数的“演示类”就足够。但在代码编写过程中,我渐渐意识到,这个类肯定具有实用价值,比如说,一个小型数据采集系统,就可采用文件方式存储。何况,它功能完备,也有助于我撰写后续代码和相应文章。既然利人利己,何乐而不为?
动手之前,我照例先察看有无现成资源可以利用。在http://codeguru.earthweb.com/mfc_database/CSpreadSheet.html,我找到了CSpreadSheet。这是一个利用MFC编写的可以读取.xls文件和定界符文件的类,尽管功能有限,但总的来说写得不错,可它并不符合我的要求。
我不打算使用MFC。我希望这些代码,以及后续的一系列代码,能够具有良好的移植性。无论你使用何种C++编译器,工作于何种操作系统之上,我都希望它们对你有用。显然,最好的表达手段,就是标准C++和STL,所以,就有了Delimfile,但我要感谢CSpreadSheet作者的开创性工作(对我而言)。
我必须对这个类的功能做出取舍。提供对行/列数据(假如这一行/列是数值型的话)的max/min/mean/sum等数值运算操作,不是Delimfile的责任,它的重心在于对文件的灵活存取。至于究竟如何处理读取到的值,那是使用者的事。对我来说,我会编写另外一些代码,来完成这些常见数值运算。
你现在看到的Delimfile类,提供了大约五十个public成员函数,支持对定界符文件所有常见存取操作。比如read、insert、update、delete行/列/数据,并支持对列进行sort、对行/列进行swap之类的扩展操作。
这个类的设计思想源于CSpreadSheet。先将文件内容读入内存,所有后续操作都于内存中进行。你可以begin_transaction()开始,对内存数据进行修改,调用commit()方法,即将修改持久化到磁盘文件中去。倘若要放弃自最近一次提交事务以来的所有内存数据修改,调用rollback()即可。
下面是Delimfile完整头文件说明,大略浏览一下,就可以跳过它。在这个冗长的说明之后,还有一个演示用法的简单例子,以及另外一些文字说明,它们或许更有看头。
/**************************************************************************** * 文件名称: delimfile.h * 功能特点: 用于读写具有定界符的平文件,采用标准C++/STL编写. * 作 者: 荣耀 * 发布时间: 2003年1月11日 * 声 明: 任何个体/组织都可以将此代码用于非商业用途. * Version : beta1 * Copyright (c) royaloo.com by royal. All rights reserved. ****************************************************************************/ // #ifndef _DELIMFILE_H_ #define _DELIMFILE_H_ // #include #include #include #include #include // //说明: 出错信息常量清单,你可以自由转换为你所需要的语言. // const std::string err_row_smaller_than_2 = "total row count is smaller than two/n"; const std::string err_col_smaller_than_2 = "total col count is smaller than two/n"; const std::string err_col_initial_already = "cols have been initialized already/n"; const std::string err_first_row_equal_second_row = "first row no. equal second row no./n"; const std::string err_first_col_equal_second_col = "first col no. equal second col no./n"; const std::string err_col_out_of_range = "col no. out of range/n"; const std::string err_row_out_of_range = "row no. out of range/n"; const std::string err_cell_out_of_range = "cell coordinate out of range/n"; const std::string err_row_size_too_large = "row size is larger than total cols/n"; const std::string err_fail_to_initial_col_headers = "fail to initialize col headers/n"; const std::string err_fail_to_write_file = "fail to write to file/n"; const std::string err_fail_to_open_file = "fail to open file/n"; const std::string err_fail_to_clear = "fail to clear file content/n"; const std::string err_fail_to_append_row = "fail to append row/n"; const std::string err_fail_to_insert_row = "fail to insert row/n"; const std::string err_fail_to_update_row = "fail to update row/n"; const std::string err_fail_to_delete_row = "fail to delete row/n"; const std::string err_fail_to_read_row = "fail to read row/n"; const std::string err_fail_to_append_col = "fail to append col/n"; const std::string err_fail_to_insert_col = "fail to insert col/n"; const std::string err_fail_to_update_col = "fail to update col/n"; const std::string err_fail_to_delete_col = "fail to delete col/n"; const std::string err_fail_to_sort_col = "fail to sort col/n"; const std::string err_fail_to_update_cell = "fail to update cell/n"; // //用途: 每行字符串缓冲区大小,你可以根据需要,自由设定. // const int ROW_BUF_SIZE = 3000; // class Delimfile { public: // //file_name: 意欲操作的文件名,存在即打开,不存在即创建. //separator: 文件中每个cell的定界符,比如若干空格" "或一个分号";"(考虑"人类"可读性). //backup : 打开文件时是否需要做备份.是,则备份文件名为file_name + ".bak". // Delimfile(const std::string& file_name, const std::string& separator, bool backup = true); // //无任何代码. // ~Delimfile(); public: // //作用 : 初始化列标题.标题栏和普通row并无区别,行号为1的row,就是标题栏. //value: 将作为标题栏的一组列名.注意,这个方法并不限制列标题名字唯一. // bool initial_col_headers(const std::vector // //作用 : 插入一行. //value: 欲插入行的值,所有对行进行写操作的方法,都会判断value里的数目是否 // 恰好和列数相等,否则不准进行写操作.此举为了保证所有数据呈矩形. //row : 行号.在这一行之前插入. // bool insert_row(const std::vector // //作用 : 更新一行. //value: 将用此值去更新目标行. //row : 行号.此行将被更新. // bool update_row(const std::vector // //作用: 删除一行. //row : 行号.此行将被删除. // bool delete_row(int row); // //作用 : 读取一行. //value: 读取值存放于此. //row : 行号.此行将被读取. // bool read_row(std::vector // //作用 : 在末尾附加一行. //value: 欲被附加的行的值. // bool append_row(const std::vector // //作用 : 更新符合条件的行. //value : 欲以此值进行更新. //col : 列号.将对该列进行条件判断. //condition : 条件. //only_first: 默认为false,更新符合条件的所有行,否则,只更新符合条件的第一行. // bool update_rows(const std::vector // //作用 : 更新符合条件的行. //value : 欲以此值进行更新. //col : 列名.将对该列进行条件判断.注意,并没有限定列名不允许重复 // 倘有重复,将以具有该列名的第一列为准. //condition : 条件. //only_first: 默认为false,更新符合条件的所有行,否则,只更新符合条件的第一行. // bool update_rows(const std::vector // //作用 : 删除符合条件的行. //col : 列号,将对该列进行条件判断. //condition : 条件. //only_first: 默认为false,删除符合条件的所有行,否则,只删除符合条件的第一行. // bool delete_rows(int col, const std::string& condition, bool only_first = false); // //作用 : 删除符合条件的行. //col : 列名,将对该列进行条件判断.注意,并没有限定列名不允许重复, // 倘有重复,将以具有该列名的第一列为准. //condition : 条件. //only_first: 默认为false,删除符合条件的所有行,否则,只删除符合条件的第一行. // bool delete_rows(const std::string& col, const std::string& condition, bool only_first = false); // //作用 : 读取符合条件的一行. //value : 读取结果存放于此变量之中. //col : 列号,将对该列进行条件判断. //condition: 条件. // bool read_row(std::vector // //作用 : 读取符合条件的一行. //value : 读取结果存放于此变量之中. //col : 列名,将对该列进行条件判断.倘有列名重复情况,将以具有该列名的第一列为准. //condition: 条件. // bool read_row(std::vector // //作用 : 读取符合条件的所有行. //values : 读取结果存放于此变量之中. //col : 列号,将对该列进行条件判断. //condition: 条件. // bool read_rows(std::vector // //作用 : 读取符合条件的所有行. //values : 读取结果存放于此变量之中. //col : 列名.将对该列进行条件判断.倘有列名重复情况,将以具有该列名的第一列为准. //condition: 条件. // bool read_rows(std::vector // //作用 : 追加一列. //name : 列名.即列标题.即显示在第一行的那个cell的名字. //value: 除了第一行之外的所有该列的值,默认为带有一个空格的字符串. // bool append_col(const std::string& name, const std::string& value = " "); // //作用 : 插入一列. //name : 列名,即列标题名,即显示在第一行的那个cell的名字. //col : 位置列,将在该列之前插入一列. //value: 除了第一行之外的所有该列的值,默认为带有一个空格的字符串. // bool insert_col(const std::string& name, int col, const std::string& value = " "); // //作用 : 插入一列. //name : 列名,即列标题,即显示在第一行的那个cell的名字. //col : 列名.将对该列进行条件判断.倘有列名重复情况,将以具有该列名的第一列为准. //value: 除了第一行之外所有该列的值,默认为带有一个空格的字符串. // bool insert_col(const std::string& name, const std::string& col, const std::string& value = " "); // //作用 : 更新一列. //col : 列号.将更新该列. //value: 除了第一行之外所有该列的值,默认为“updated”. // bool update_col(int col, const std::string& value = "updated"); // //作用 : 更新一列. //col : 列名.将对该列进行条件判断.倘有列名重复情况,将以具有该列名的第一列为准. //value: 除了第一行之外所有该列的值,默认为“updated”. // bool update_col(const std::string& col, const std::string& value = "updated"); // //作用: 删除一列. //col : 列号.将删除该列. // bool delete_col(int col); // //作用: 删除一列. //col : 列名.将对该列进行条件判断.倘有列名重复情况,将以具有该列名的第一列为准. // bool delete_col(const std::string& col); // //作用 : 读取一列,含列标题. //value: 读取结果存放于此. //col : 列号.将读取该列. // bool read_col(std::vector // //作用 : 读取一列,含列标题. //value: 读取结果存放于此. //col : 列名.将对该列进行条件判断.倘有列名重复情况,将以具有该列名的第一列为准. // bool read_col(std::vector // //作用 : 更新列标题. //value: 欲以此值进行更新. //col : 列号.将更新该列. // bool update_col_name(const std::string& value, int col); // //作用 : 更新列标题. //value: 欲以此值进行更新. //col : 列名.将对该列进行条件判断.倘有列名重复情况,将以具有该列名的第一列为准. // bool update_col_name(const std::string& value, const std::string& col); // //作用 : 更新cell(row, col)值. //value: 欲以此值进行更新. //col : 列号. //row : 行号. // bool update_cell(const std::string& value, int col, int row); // //作用 : 更新cell(row, col)值. //value: 欲以此值进行更新. //col : 列名.倘有列名重复情况,将以具有该列名的第一列为准. //row : 行号. // bool update_cell(const std::string& value, const std::string& col, int row); // //作用 : 读取cell(row, col)值. //value: 读取值存放于此. //col : 列号. //row : 行号. // bool read_cell (std::string& value, int col, int row); // //作用 : 读取cell(row, col)值. //value: 读取值存放于此. //col : 列名.倘有列名重复情况,将以具有该列名的第一列为准. //row : 行号. // bool read_cell (std::string& value, const std::string& col, int row); // //作用 : 交换两列位置. //first_col : 列号. //second_col: 行号. // bool swap_cols(int first_col, int second_col); // //作用: 交换两列位置. //col : 列名.倘有列名重复情况,将以具有该列名的第一列为准. //col : 列名.倘有列名重复情况,将以具有该列名的第一列为准. // bool swap_cols(const std::string& first_col, const std::string& second_col); // //作用 : 交换两行位置. //first_row : 行号. //second_row: 行号. // bool swap_rows(int first_row, int second_row); // //作用: 对该列进行排序(按字母顺序). //col : 列号. //asc : true为升序,false为降序.默认为升序. // bool sort_col(int col, bool asc = true); // //作用: 对该列进行排序(按字母顺序). //col : 列名.倘有列名重复情况,将以具有该列名的第一列为准. //asc : true为升序,false为降序,默认为升序. // bool sort_col(const std::string& col, bool asc = true); // //作用: 清空缓存,并清空文件内容. // bool clear(); // //作用: 开始事务. // void begin_transaction(); // //作用: 提交事务.将所有内存更改,持久化到数据文件中. // bool commit(); // //作用: 回滚到最近一次事务提交点,放弃所有内存修改. // bool rollback(); // //作用: 判断给定的列号是否在有效范围内[1, m_col_total_count]. //col : 列号. // bool is_col_in_range(int col); // //作用: 判断给定的行号是否在有效范围内[1, m_row_total_count]. //row : 行号. // bool is_row_in_range(int row); // //作用: 判断给定的cell(row, col)是否在有效范围内[1, 1] [1, m_col_total_count] // [m_row_total_count, 1] [m_row_total_count, m_col_total_count]. //col : 列号. //row : 行号. // bool is_cell_in_range(int col, int row); // //作用 : 返回所有列标题. //value: 返回值存放于此. // void get_col_names (std::vector // //作用: 由给定列名,返回对应列号. //col : 列名.倘有列名重复情况,将以具有该列名的第一列为准. // int get_col_no(const std::string& col) const; // //作用: 返回总行数. // int get_row_total_count() const; // //作用: 返回总列数. // int get_col_total_count() const; // //作用: 返回当前行号. // int get_current_row_no() const; // //作用: 返回备份状态. // bool get_backup_status() const; // //作用: 返回事务状态. // bool get_transaction_status() const; // //作用: 返回最近一次出错信息. // const std::string& get_last_error() const; protected: // //作用: 打开文件,将内容读取到m_rows中,行尾以"/r"定界. // bool open(); // //作用: 将vector中的字符串,进行定界处理,并返回结果字符串. // std::string delimit(const std::vector private: // //作用: 文件备份状态. // bool m_backup; // //作用: 事务状态. // bool m_transaction; // //作用: 当前行行号. // int m_current_row; // //作用: 总共行数. // int m_row_total_count; // //作用: 总共列数. // int m_col_total_count; // //作用: 被操作的文件名. // std::string m_file_name; // //作用: 定界符,比如若干空格/符号/字母等等. // std::string m_separator; // //作用: 最近一次出错信息. // std::string m_last_error; // //作用: 所有列名. // std::vector // //作用: 所有行值. // std::vector }; #endif |
/****************************************************************************
一个测试例子:
#include "delimfile.h"
#include "utils.h" //后面对此文件内容作了简要说明
using namespace std;
int main()
{
Delimfile df("test.dat"," ");
df.clear();
vector
string ts;
char ch = 'A';
df.begin_transaction();
for (int i = 1; i <= 5; ++i)
{
tv.clear();
for (int j = 1; j <= 5; ++j)
{
ts = stringmaker() << (ch++) << i;
tv.push_back(ts);
}
if (i == 1)
{
df.initial_col_headers(tv);
}
else
{
df.append_row(tv);
}
ch = 'A';
}
df.commit();
cout << "total number of rows = " << df.get_row_total_count() << endl;
for (int i = 1; i <= df.get_row_total_count(); ++i)
{
df.read_row(tv, i);
for (size_t j = 1; j <= tv.size(); ++j)
{
if (j != tv.size())
{
cout<
else
{
cout<< tv.at(j-1)<<"/n";
}
}
}
cout << "total number of columns = " << df.get_col_total_count() << endl;
tv.clear();
df.read_col(tv, "C1");
for (size_t i = 1; i <= tv.size(); ++i)
{
cout<< static_cast
}
if (df.read_cell(ts, 2, 4))
{
cout << "cell value at (2, 4): " << ts << endl;
}
else
{
cout << df.get_last_error()<
}
在“命令提示符窗口”编译、运行情况如下(先后使用了两种C++编译器):
F:/DelimfileTest>cl /GX dftest.cpp delimfile.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 13.00.9466 for 80x86
Copyright (C) Microsoft Corporation 1984-2001. All rights reserved.
dftest.cpp
delimfile.cpp
正在生成代码...
Microsoft (R) Incremental Linker Version 7.00.9466
Copyright (C) Microsoft Corporation. All rights reserved.
/out:dftest.exe
dftest.obj
delimfile.obj
F:/DelimfileTest>bcc32 dftest.cpp delimfile.cpp
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
dftest.cpp:
delimfile.cpp:
Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland
F:/DelimfileTest>dftest
total number of rows = 5
A1 B1 C1 D1 E1
A2 B2 C2 D2 E2
A3 B3 C3 D3 E3
A4 B4 C4 D4 E4
A5 B5 C5 D5 E5
total number of columns = 5
1 : C1
2 : C2
3 : C3
4 : C4
5 : C5
cell value at (2, 4): B4
用notepad打开test.dat文件,内容如下:
"A1" "B1" "C1" "D1" "E1"
"A2" "B2" "C2" "D2" "E2"
"A3" "B3" "C3" "D3" "E3"
"A4" "B4" "C4" "D4" "E4"
"A5" "B5" "C5" "D5" "E5"
至于那个utils.h文件,上例中使用的stringmaker类的定义就包含于该文件中。这个精巧的“helper class”完整定义如下:
class stringmaker
{
public:
template
stringmaker& operator << (const T& t) { osm << t; return *this; }
operator std::string() const { return osm.str(); }
private:
std::ostringstream osm;
};
因为std::string中并无象CString(MFC)的Format那样的现成函数可以调用,我使用这个类来构造一个“带有参数的”std::string字符串。
尽管Delimifile大部分开发工作完成于一个文本编辑器和命令行式编译器,但你当然可以在任何一个C++ IDE中使用它,并可用于任何一个图形界面程序。不过,要注意包含该IDE可能需要的附加文件,比如在Visual C++项目中,别忘了包含stdafx.h文件。
倘若工作于Visual C++中,你十有八九要使用MFC,假如打算试用Delimfile,你或许需要在CString和std::string之间进行转换。下面是转换方法之一:
std::string ts1("string");
CString cs(ts.c_str());
std::string ts2(cs.GetString());
我完成了这个类的代码编写工作,但我没有更多的时间和精力进行细致测试。源码已经开放于此[delimfile.h][delimfile.cpp][utils.h](请注意,它们时刻处于更新中),希望能够得到积极反馈意见,无论是bug报告还是别的。
请给我写信:[email protected]
祝各位新年快乐。
-完-