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 <string>
#include <vector>
#include <algorithm>
#include <fstream>
#include <functional>
//
//说明: 出错信息常量清单,你可以自由转换为你所需要的语言.
//
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<std::string>& value);
//
//作用 : 插入一行.
//value: 欲插入行的值,所有对行进行写操作的方法,都会判断value里的数目是否
// 恰好和列数相等,否则不准进行写操作.此举为了保证所有数据呈矩形.
//row : 行号.在这一行之前插入.
//
bool insert_row(const std::vector<std::string>& value, int row);
//
//作用 : 更新一行.
//value: 将用此值去更新目标行.
//row : 行号.此行将被更新.
//
bool update_row(const std::vector<std::string>& value, int row);
//
//作用: 删除一行.
//row : 行号.此行将被删除.
//
bool delete_row(int row);
//
//作用 : 读取一行.
//value: 读取值存放于此.
//row : 行号.此行将被读取.
//
bool read_row(std::vector<std::string>& value, int row);
//
//作用 : 在末尾附加一行.
//value: 欲被附加的行的值.
//
bool append_row(const std::vector<std::string>& value);
//
//作用 : 更新符合条件的行.
//value : 欲以此值进行更新.
//col : 列号.将对该列进行条件判断.
//condition : 条件.
//only_first: 默认为false,更新符合条件的所有行,否则,只更新符合条件的第一行.
//
bool update_rows(const std::vector<std::string>& value, int col, std::string condition, bool only_first = false);
//
//作用 : 更新符合条件的行.
//value : 欲以此值进行更新.
//col : 列名.将对该列进行条件判断.注意,并没有限定列名不允许重复
// 倘有重复,将以具有该列名的第一列为准.
//condition : 条件.
//only_first: 默认为false,更新符合条件的所有行,否则,只更新符合条件的第一行.
//
bool update_rows(const std::vector<std::string>& value, std::string col, std::string condition, bool only_first = false);
//
//作用 : 删除符合条件的行.
//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<std::string>& value, int col, const std::string& condition);
//
//作用 : 读取符合条件的一行.
//value : 读取结果存放于此变量之中.
//col : 列名,将对该列进行条件判断.倘有列名重复情况,将以具有该列名的第一列为准.
//condition: 条件.
//
bool read_row(std::vector<std::string>& value, const std::string& col, const std::string& condition);
//
//作用 : 读取符合条件的所有行.
//values : 读取结果存放于此变量之中.
//col : 列号,将对该列进行条件判断.
//condition: 条件.
//
bool read_rows(std::vector<std::vector<std::string> >& values, int col, const std::string& condition);
//
//作用 : 读取符合条件的所有行.
//values : 读取结果存放于此变量之中.
//col : 列名.将对该列进行条件判断.倘有列名重复情况,将以具有该列名的第一列为准.
//condition: 条件.
//
bool read_rows(std::vector<std::vector<std::string> >& values, const std::string& col, const std::string& condition);
//
//作用 : 追加一列.
//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<std::string>& value, int col);
//
//作用 : 读取一列,含列标题.
//value: 读取结果存放于此.
//col : 列名.将对该列进行条件判断.倘有列名重复情况,将以具有该列名的第一列为准.
//
bool read_col(std::vector<std::string>& value, const std::string& col);
//
//作用 : 更新列标题.
//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<std::string>& value) const;
//
//作用: 由给定列名,返回对应列号.
//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<std::string>& value);
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::string> m_col_names;
//
//作用: 所有行值.
//
std::vector<std::string> m_rows;
};
#endif
/****************************************************************************
一个测试例子:
#include "delimfile.h"
#include "utils.h" //后面对此文件内容作了简要说明
using namespace std;
int main()
{
Delimfile df("test.dat"," ");
df.clear();
vector<string> tv;
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<<tv.at(j-1)<<"\t";
}
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<int>(i) + 1 <<" : "<< tv.at(i-1) << endl;
}
if (df.read_cell(ts, 2, 4))
{
cout << "cell value at (2, 4): " << ts << endl;
}
else
{
cout << df.get_last_error()<<endl;
}
}
在“命令提示符窗口”编译、运行情况如下(先后使用了两种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 <class T>
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报告还是别的。
请给我写信:royal@royaloo.com
祝各位新年快乐。
-完-