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; }
以上。感谢阅读。