C++程设实验项目二:用正则表达式制作一个简易的SQL系统

本文将尽可能简单地概括如何搭起这个SQL系统的框架。

 

一、正则表达式分析语句

首先需要使用c++的regex库:

#include <regex>

推荐到菜鸟教程上了解正则表达式的最基础语法。

 

然后,新建一个表达式。假定现在要分析的语句是CREATE TABLE (col1,col2,...) TO filename

regex r("CREATE TABLE \\((.+)\\) TO ([^ ]+)");

注意,在c++中还要转义一次反斜杠,所以一般的像\w, \(这样的符号都要写成\\w, \\(的形式。

 

然后用regex_match匹配一个string对象。

string cmd="CREATE TABLE (name,score) TO rec.txt";
smatch m;
if(regex_match(cmd,m,r)){
    //...
}
else cout << "NOT MATCH!" << endl;

如果cmd的内容与r匹配成功,那么结果将会保存在m里面。

那么怎么利用m的内容呢?举个例子,输出m的内容(for里面的auto这个用法也是c++11才有的)

for(auto x:m) cout << m << endl;
//result:
//CREATE TABLE (name,score) TO rec.txt     //即m.str(0),匹配到的整个式子
//name,score                               //即m.str(1),即用n个括号保存的信息,都依次保存在m的1~n个位置中//rec.txt                                  //即m.str(2)

 

将m.str(1)保存之后,如何处理它呢?万一这是一个很长的col1,col2,col3,...,col99的长字符串呢?这就要用到regex_search了。

regex c("([^,]+)");
string cols=m.str(1);
while(regex_search(cols,m,c)){ //确保m的文件路径信息已经被保存了
    //保存m.str(0)的信息……
    cols=m.suffix().str();  
    //m.str(0): col1
    //m.suffix(): ,col2,col3,...,col99
}

regex_search会在整个字符串中寻找第一个匹配正则表达式的字串。然后,这个字串之前的部分保存在m.prefix(),之后的部分保存在m.suffix()。只要把已经查找到的字串截去,就可以再在后面的串里搜索了。

再举个例子吧:

regex r("glim");
string s="starlightglimmer";
smatch m;
regex_search(s,m,r);
//m.str(0)=="glim"
//m.prefix()=="starlight"
//m.suffix()=="mer"

 

由此,只要灵活地运用正则表达式,就可以很轻松地分析各种语句了。即使是有多个可选的命令,例如SELECT * FROM table [WHERE col = name] [ORDER BY col DESC] [TO file],你可以将前面的必填命令和后面三个可选参数拆成四个正则表达式,然后运用regex_search完成各种命令的分析。

 

二、数据结构与排序

注意,由于各人的数据结构存在差异,所以这部分不是这么通用。首先,一个表格是二维的,这就可以使用一个vector的vector来储存表格。具体我是这样操作的:

struct column {
    vector<string> item;
    string name;
};
class table {
public:
    vector<column> col;
    string tablename;
    table(string);
};

table其实当成结构体用,所以都public。

一个表的行数可以从它其中一个列里得知,同时你可以要求表名与文件名相同来节省一个string变量。

构造函数的string就是方便设置tablename用的,你也可以把它拆出来,然后写成一个结构体。

此外,在内存里,你不必储存任何一个静态的表格:毕竟是从文件读取的,只要确保要操作的表格曾经通过CREATE TABLE记录在一个vector<string> tablelist这样的地方就行了。

 

那么,如何对一个行操作呢?平常可以用下标来操作,但是排序怎么解决?这里提供一个思路:

例如,存在这些列:name, score, note

要对score排序整个表,可以用pair<int, string>这样的数据结构。

//table t(...)
//...
string tarcol="score";
vector<pair<int ,string>> tar;
for(auto c:t.col){
    if(c.name==tarcol){
        for(int i=0;i<c.item.size();i++){
            tar.push_back(make_pair(i,c.item[i]));
        }
    }
}

然后用<algorithm>自带的sort,同时配以自定义的cmp函数:

bool cmp(const pair &a, const pair &b) {
    int res = a.second.compare(b.second);
    if (res < 0) return true;
    else return false;
}

那么你就得到了一个排序过的pair序列。pair的数字,就是一种索引。

把当前的表一行一行地,根据索引指定的顺序塞进一个临时的新表(比如,序列的第一个索引数字是2,就把第二行的内容塞进表)。

最后,把新表复制会源表,就大功告成了。

 

三、文件的读取

可以考虑配合fstream,先用getline,再用流输入。具体操作:

#include <fstream>
int readtable(table &t) {
    ifstream in;
    char a[300];
    string line;
    column tmpc;

    in.open((t.tablename + ".txt").c_str());
    if (!in.is_open()) {
        //printf("Doc is not exist...\n");
        return -1;
    }
    in.getline(a, 299);
    line = a;

    smatch m;
    while (regex_search(line, m, divideSpace)) {
        tmpc.name = m.str(1);
        t.col.push_back(tmpc);
        line = m.suffix().str();
    }

    while (!in.eof()) {
        for (auto &c : t.col) {
            in >> line;
            c.item.push_back(line);
        }
    }
    for (auto &c : t.col) c.item.pop_back(); //delete invalid line
    in.close();
    return 1;
}

要注意的是,一般读取的时候是会多读一行的,这时候要把这多读的一行移除。此外,正则表达式就留给读者自己写了。

输出自然不难了。通过这种方法,可以每次都从文件读取表,然后再储存表,再退出。

 

四、表格分割线的绘制

这个其实不是难点,毕竟只要用一个vector<int>记录下一列的最长字串的长度,就可以轻松绘制了。

但是这里有一个坑:在Linux下,一个汉字占三个长度,然后实际显示只有两个。所以在计算长度的时候,需要进行一些处理。

具体来说,可以这么取巧:

int calLength(string s){
    double len=0;
    for(auto ch:s){
        if(0<=ch||ch<=127) len+=1;
        else len+=2.0/3;
    }
    return (int)len;
}

 

以上。感谢阅读。

 

posted on 2018-05-13 18:47  Ricochet!  阅读(395)  评论(0编辑  收藏  举报