1. 程式人生 > >C++程設實驗項目二:用正則表達式制作一個簡易的SQL系統

C++程設實驗項目二:用正則表達式制作一個簡易的SQL系統

search linux c++ AC 2.0 地方 文件的 由於 font

本文將盡可能簡單地概括如何搭起這個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;
}

以上。感謝閱讀。

C++程設實驗項目二:用正則表達式制作一個簡易的SQL系統