C++可复用代码:命令行控制模块
大二第一学期的数据结构课程设计中,我写的是一个族谱管理系统,用C语言写的win console application,黑咕隆咚的,但是程序控制方式我采用的是类似linux shell那样的命令行模式。后来觉得实现命令行控制的那部分代码可以复用,所以在大二下学期用C++对这个模块进行了改写,写出了我自认为可复用的代码。
这学期,临近考试月我们有操作系统课程设计,要求在linux下模拟实现一个命令解释器,则上面提到的那个命令行控制模块的代码正好派上了用场。下面是我写的命令解释器的main.cpp代码,这个shell只有9条命令。
CmdNode<ShellFunSet> shellCmds[9] = {//该数组的每一个元素对应该shell的一条命令
{"pwd",&ShellFunSet::pwd},//{"命令字符串",执行该命令的函数}
{"dir",&ShellFunSet::dir},
{"cd",&ShellFunSet::cd},
{"newdir",&ShellFunSet::newdir},
{"deldir",&ShellFunSet::deldir},
{"exit",&ShellFunSet::exit},
{"rename",&ShellFunSet::rename},
{"find",&ShellFunSet::find},
{"date",&ShellFunSet::date}
};
ShellFunSet osFunHolder;
CmdControl<ShellFunSet> cmdModul("YeShizhe@",9,osFunHolder,shellCmds);
cmdModul.run();//进入命令行控制模式
以上代码中,可复用代码由两部分组成:
结构体 CmdNode<T>
类 CmdControl<T>
复用该代码的方法就是根据需要编写自己的T类,然后将它作为以上俩物的持有类。
我知道直到现在为止我什么都没讲清楚,但请耐心看下去,下面是ShellFunSet的代码,
该类不属于可复用代码的范畴。
class ShellFunSet{
public:
bool pwd(string order[]);
bool dir(string order[]);
bool cd(string order[]);
bool newdir(string order[]);
bool deldir(string order[]);
bool exit(string order[]);
bool date(string order[]);
bool rename(string order[]);
bool find(string order[]);
};
函数内的具体代码就不展开了,对代码讲解无帮助。可以看到,ShellFunSet其实是一个函数的集合,里面的函数都有一个共同特点:
1.返回值类型为bool
2.参数类型为string数组(根据C/C++语言特性,其实更应该说是string型指针)。
只有持有按这种规范编写的函数的类才能作为CmdNode<T>以及CmdControl<T>的T类。至于函数为什么必须返回bool值,等下会说的,别急~
接下来说下CmdNode,从第二行代码后的注释可以看出,这个结构体只有两个数据成员,一个是string,代表这个shell内置的命令。另一个则是函数指针型数据,指向返回值为bool,参数为string数组的函数,我将其typedef为pFun。因此在上面代码中,初始化CmdNode数组shellCmds[9]时用到了类似这样的代码 {"pwd",&ShellFunSet::pwd},那个取址算符&取的其实就是ShellFunSet中某个函数的地址,将此地址作为函数指针赋值给CmdNode中的pFun型数据成员。通过这种方式编写代码能够实现如下功能:当用户敲入的命令匹配了某一CmdNode实例的string型数据成员时,程序能通过某种机制调用与该string成员相关联的T类中的函数,执行该命令对应的具体功能。例如{"pwd",&ShellFunSet::pwd}中,当用户敲入"pwd"时,shell程序就会调用ShellFunSet中的bool pwd(string order[])函数,在这个简易shell程序中pwd命令的功能是显示当前工作路径。
附上CmdNode的代码:
template <class T>
struct CmdNode{
typedef bool (T::*pFun)(string *);
string command;
pFun fun;
};
关于这个某种机制(我特意在上面把它高亮了出来,呵呵~)其实就是CmdControl<T>,如果说在这个简易Shell程序中,CmdNode<T>数组shellCmds是它的数据结构,那么这个CmdControl<T>就是这个程序的算法。恩,我知道这种说法很扯,此程序压根不涉及啥稍微有内涵的数据结构与算法,但这只是个比喻。
CmdControl<T>的功能是:获取用户输入的命令及参数并进行匹配,然后执行相应功能,或者在用户输入错误命令时进行报错(注意,只是对错误的命令进行报错,参数错了的话CmdControl<T>是不会去管的),控制命令行模块的启停。
CmdControl<T>的初始化方式如下:
CmdControl<ShellFunSet> cmdModul("YeShizhe@",9,osFunHolder,shellCmds);
参数"YeShizhe@"是自定义的命令行提示符。整型常量9代表这个简易shell内置的命令条数,shellCmds是CmdNode<ShellFunSet>的数组,它包含9个CmdNode<ShellFunSet>型元素。osFunHolder是ShellFunSet类实例。这里请允许我再次强调下:CmdControl与CmdNode属于可复用的代码,而ShellFunSet则是我根据这次操作系统课程设计要求而写的实现简易shell的具体功能代码。
然后,用这样的代码启动这个命令行控制模块cmdModul:
cmdModul.run();
至于怎么停止,这个就要靠ShellFunSet中函数的返回值:bool
当用户输入的命令对应了一个函数,这个函数的返回值是false,一旦cmdModul调用到了该函数就会停止对用户输入的接收,退出命令行控制模式。例如在课程设计中我为"exit"命令写了这么个函数bool exit(string order[]){ return false;},简单得坑爹,但就是它实现了用户输入"exit"时退出shell的这么一个功能。
CmdControl<T>的代码如下:
template <class T>
class CmdControl {
protected:
T holder;//客户程序员根据需要编写的功能类
string prompt;//命令提示符
string cmdNotExist;//自定义的错误命令提示信息
int cmds;//内置命令数
bool running;//用于判断模块处于运行状态
CmdNode<T> *cmdList;//内置命令列表
string* getOrder();//返回字符串数组,存储用户输入的命令及参数
int match(string order);//返回匹配用户输入的命令在cmdList中的下标
public:
void run();
//接收参数:命令条数、命令持有类、命令与函数的关联数组
CmdControl(string prmpt,int num,T Holder,CmdNode<T> *CMD);
};
重点说下getOrder()这个函数,该函数实现的功能是:
1.接收一行用户输入。
2.根据空格将用户输入分割成字符串数组。数组中下标为0的元素的第一个字符存储的是一个整型变量,代表有效字符串数,即一条命令及其所带参数所对应的字符串数。
代码:
string* CmdControl<T>::getOrder(){
string order;//该字符串是用户输入的原始字符串,包括空格符
getline(cin,order);
int num = 1;
deque<string> orders;//以空格为分隔符,将截取到的子串存入该容器中
int from = 0;//子串头部下标
int len = 1;//子串长度
for(int i = 0; i < order.size();i++){
if(order[i] == ' '){//读到空格符时将之前标记的字符串存入容器
orders.push_back(order.substr(from,len-1));
from = i+1;
len = 1;
num++;
}
else len++;
}orders.push_back(order.substr(from));//存入读得的最后一个子串
string *result = new string[num+1];//result是要返回的字符串数组,预留出0号单元
result[0].append(1,(char)num);//0号字符串存储的是数量信息
deque<string>::iterator itr = orders.begin();
for(int i = 1;i <= num; i++){//将容器里的字符串存入result数组
result[i] = *itr;
itr++;
}
return result;
}
最后是run函数,该函数调用getOrder与match函数进行命令的匹配及对应函数调用:
void run() {
running = true;
while(running){
cout<<prompt<<":";//输出命令行提示符
string *orderInput = getOrder();
if(orderInput[1].length() == 0) continue;//用户仅只敲了个回车
//将加工后的参数数据传递给相应函数
int funNum = match(orderInput[1]);//若未匹配得内置命令则返回-1
if(funNum == -1) cout<<cmdNotExist<<endl;//并输出自定义的错误提示信息
//否则funNum为命令在cmdList中的下标,并调用相应函数执行命令功能
else running = (holder.*(cmdList[funNum].fun))(orderInput);
}
}
可能在多道程序环境中这个应该写成一个线程,但现在还没去实现它。
恩,基本就是这样,希望能得到高手的指教,平手的讨论,新手的提问,哈哈~