C++设计日志:读写定界符文件

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: 欲插入行的值,所有对行进行写操作的方法,都会判断value里的数目是否
//       恰好和列数相等,否则不准进行写操作.此举为了保证所有数据呈矩形.
//row  : 行号.在这一行之前插入.
//
bool insert_row(const std::vector& value, int row);
//
//作用 : 更新一行.
//value: 将用此值去更新目标行.
//row  : 行号.此行将被更新.
//
bool update_row(const std::vector& value, int row);
//
//作用: 删除一行.
//row : 行号.此行将被删除.
//
bool delete_row(int row);
//
//作用 : 读取一行.
//value: 读取值存放于此.
//row  : 行号.此行将被读取.
//
bool read_row(std::vector& value, int row); 
//
//作用 : 在末尾附加一行.
//value: 欲被附加的行的值.
//
bool append_row(const std::vector& value);
//
//作用      : 更新符合条件的行.
//value     : 欲以此值进行更新.
//col       : 列号.将对该列进行条件判断.
//condition : 条件.
//only_first: 默认为false,更新符合条件的所有行,否则,只更新符合条件的第一行.
//
bool update_rows(const std::vector& value, int col, std::string condition, bool only_first = false); 
//
//作用      : 更新符合条件的行.
//value     : 欲以此值进行更新.
//col       : 列名.将对该列进行条件判断.注意,并没有限定列名不允许重复
//            倘有重复,将以具有该列名的第一列为准.
//condition : 条件.
//only_first: 默认为false,更新符合条件的所有行,否则,只更新符合条件的第一行.
//
bool update_rows(const std::vector& 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& value, int col, const std::string& condition); 
//
//作用     : 读取符合条件的一行.
//value    : 读取结果存放于此变量之中.
//col      : 列名,将对该列进行条件判断.倘有列名重复情况,将以具有该列名的第一列为准.
//condition: 条件.
//
bool read_row(std::vector& value, const std::string& col, const std::string& condition);
//
//作用     : 读取符合条件的所有行.
//values   : 读取结果存放于此变量之中.
//col      : 列号,将对该列进行条件判断.
//condition: 条件.
//
bool read_rows(std::vector >& values, int col, const std::string& condition); 
//
//作用     : 读取符合条件的所有行.
//values   : 读取结果存放于此变量之中.
//col      : 列名.将对该列进行条件判断.倘有列名重复情况,将以具有该列名的第一列为准.
//condition: 条件.
//
bool read_rows(std::vector >& 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& value, int col); 
//
//作用 : 读取一列,含列标题.
//value: 读取结果存放于此.
//col  : 列名.将对该列进行条件判断.倘有列名重复情况,将以具有该列名的第一列为准.
//
bool read_col(std::vector& 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& 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& 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 m_col_names;
//
//作用: 所有行值.
//
std::vector m_rows; 
};
#endif

    /****************************************************************************

    一个测试例子:

    #include "delimfile.h"
    #include "utils.h" //后面对此文件内容作了简要说明
    using namespace std;

    int main()
    {
        Delimfile df("test.dat"," ");
        df.clear();
        vector 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<                }
                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(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()<        } 
    }

    在“命令提示符窗口”编译、运行情况如下(先后使用了两种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]

    祝各位新年快乐。

-完-

你可能感兴趣的:(C++)