在上一篇中,我们讨论了如何设计一个“命令映射表”,但是上篇对应于表中“命令”的所有函数都是同一类型的,均为返回值为 void,参数为 void 的函数。
如果我们要根据不同的“命令”来执行不同类型的函数要怎么办呢? (这里的不同指的是执行函数的返回值可能不同,参数列表可能不同。)。好,我们下面就开始讨论这个问题。
可能这篇比上篇会难一点,拿出你程序员的毅力来。想想搞定了以后,我们就可以在程序中使用这种手法,可以明白MFC的消息映射表机制(其实这里非常类似于MFC的消息映射机制),还可以拓展自己的设计思路,提高自己的设计水平。最重要的是,使用它能够为你的程序带来好处。OK, Let's GO!
XXX.H 文件
1、首先,要把 "命令" 和 "函数" 对应起来,map是必不可少的,函数指针也是不能少的,而命令是 CString 类型的,所以我们在类里写下这两个东西。
如下:
class CParser
{
...
public:
typedef void (CParser::*FuncPointer)(void);
map<CString, FuncPointer> m_cmdMap;
...
}
2、我们现在是要执行不同类型的函数,上面的函数指针只是一种类型(返回值为void, 参数列表为void)。
所以,这里要定义各种类型的函数指针。
而且还需要一个enum结构来标示我们需要的函数类型。(因为函数指针都是4个字节的,可以说是无类型的,我们要另外定义类型来标示它,以便能判断选择),好,思路有了,赶快写下来:
// 要前置声明一下,因为下面这些代码不写在类里了,写在类的外头/前面
class CParser;
typedef void (CParser::*FuncPointer)(void);
// 定义一个 union 结构来装各种的函数指针
// 实际这里是使用 union 来做转换
// 因为函数指针在 union CmdFunctions 中定义,都是 4 bytes,
// 所以利用 union 的特性可顺利做转换
union CmdFunctions
{
FuncPointer pfn;
void (CParser::*pfn_vs)(CString&); // 返回值为 void, 参数为 CString,注意pfn_vs的命名,表示返回值为void,参数为CString
void (CParser::*pfn_vsi)(CString&, int); // 返回值为 void, 参数为 CString, int 注意命名,同上。
};
// 定义一个 enum 结构来标示我们的函数类型
enum FUNC_TYPE
{
FUCN_TYPE_VOID_STRING, // 函数类型:返回值为void,参数为:string
FUCN_TYPE_VOID_STRING_INT // 函数类型:返回值为void,参数为:string, int
};
3、好了,写下了上面两个关键的东西,我们要考虑一下,如何使用map把命令和函数指针对应起来了。
由于STL map只有两个参数,所以,我就要自定义一个结构放进map的第二个参数中去,这个结构包括了函数指针,函数类型,那我们下面就可以根据命令找到这个结构体,然后根据结构体中的函数类型调用相应的函数指针了。嗯,写下来:
// 在类的外头再定义一个结构体
typedef struct FunctionInfo
{
enum FUNC_TYPE funcType; // 函数类型
FuncPointer pfn; // 函数指针
}FuncInfo;
// 在类里
class CParser
{
...
public:
map<CString, FuncInfo> m_cmdMap; // map 改成这个样子了
...
}
好了,该定义的变量、结构等等的都搞定了,我们可以到 CPP 文件建立表和尝试利用命令来调用函数了。
XXX.CPP 文件
1、首先建表,在构造函数中
CParser::CParser()
{
FuncInfo fi;
// 下面要注意函数类型和函数指针类型
fi.funcType = FUCN_TYPE_VOID_STRING;
fi.pfn = (FuncPointer) ( void(CParser::*)(CString&) ) &CParser::MethodA;
m_cmdMap["#a"] = fi;
fi.funcType = FUCN_TYPE_VOID_STRING_INT;
fi.pfn = (FuncPointer) ( void(CParser::*)(CString&, int) ) &CParser::MethodB;
m_cmdMap["#b"] = fi;
}
由于map的限制,这里很难写成宏,可以写成函数,稍微简化一下。在MFC中是对每个不同的 ON_XXX 宏作不同的函数指针类型对应,这里不展开说明。
我们写一个函数来简化和统一建表操作。
void CParser::SetCmdMap(LPCTSTR strCmd, enum FUNC_TYPE ft, FuncPointer pfn)
{
FuncInfo fi;
fi.funcType = ft;
fi.pfn = pfn;
m_cmdMap[strCmd] = fi;
}
那我们的建表就可以简化成:
CParser::CParser()
{
SetCmdMap("#a", FUCN_TYPE_VOID_STRING, (FuncPointer) ( ( void(CTestCmdMapDlg::*)(CString&) ) &CParser::MethodA ) );
SetCmdMap("#b", FUCN_TYPE_VOID_STRING_INT, (FuncPointer) ( ( void(CTestCmdMapDlg::*)(CString&, int) ) &CParser::MethodB ));
}
2、根据命令找到相应的函数并执行
我们下面就可以根据命令来寻找表中对应的函数了。代码如下,和上篇的代码差不多,就是多了个函数指针类型的判断选择 switch-case。
void CParser::ExecCmd(CString &strCmd, CString &strParam)
{
if(m_cmdMap.find(strCmd) != m_cmdMap.end())
{
enum FUNC_TYPE ft;
// 由于函数指针在 union CmdFunctions 中定义,都是 4 bytes,
// 所以利用 union 的特性可顺利做转换
union CmdFunctions cfs;
ft = m_cmdMap[strCmd].funcType;
cfs.pfn = m_cmdMap[strCmd].pfn;
switch(ft)
{
case FUCN_TYPE_VOID_STRING:
(this->*cfs.pfn_vs)(strParam); // 利用 union 的特性转换
break;
case FUCN_TYPE_VOID_STRING_INT:
(this->*cfs.pfn_vsi)(strParam, 1); // 利用 union 的特性转换
break;
default:
AfxMessageBox("没有定义函数类型。");
break;
}
}
else
{
CString strFmt;
strFmt.Format("找不到/"%s/"对应的函数。", strCmd);
AfxMessageBox(strFmt);
}
}
3、写上对应的函数
void CParser::MethodA(CString &strParam)
{
AfxMessageBox(strParam);
}
void CParser::MethodB(CString &strParam, int nParam)
{
CString strFmt;
strFmt.Format("%s, %d", strParam, nParam);
AfxMessageBox(strFmt);
}
4、写个函数测试一下这个表
void CParser::OnButton1()
{
ExecCmd(CString("#a"), CString("123"));
ExecCmd(CString("#b"), CString("456"));
}
经过第4步的测试,显然结果是正确的。到这里,我们实现了更高级的“命令映射表”,可以执行不同类型的函数了。
看到这里,希望你能明白这种设计方法。我也累了。。。。。。
上面的代码可能不完整,我写了个简单完整的 MFC对话框 程序来演示这个设计,需要的朋友可以在以下地址下载:
http://www.cppblog.com/Files/dylgsy/TestCmdMap.rar
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/dylgsy/archive/2006/09/21/1260534.aspx