一、map应用简介
map是C++标准库(STL)中的一个关联容器(Associative Container),它包含的元素由键值对(key-value)组成,(参考map C++ Reference)。普通的容器,比如array或vector,它只能通过下标(index)来索引,它的索引器被限制为int类型(C#可以自定义索引器);而map则可以通过key来索引,获取对应的value,key的类型既可以是int型,也可以是string等复杂类型。此外,array等是顺序查找,而map是树形数据结构,可以通过hash查找,索引效率更高。
由于map的索引器key可以是任意复杂类型(支持compare),故可以用map的索引来代替大量的相似类型的if/else if/else语句块,将复杂类型的比较匹配语句(如string的匹配)转化为map的查找,简化代码。
在《深入浅出MFC》一书中提到的MFC消息映射也是应用以上的这个原理。MFC将一个enum(消息类型)和对应的被调函数指针封装成一个结构体(struct),然后初始化一个这样的结构体数组(array)。传入消息的时候,通过for循环遍历这个数组,匹配消息类型,然后通过函数指针调用对应的消息处理函数。
PS:C#和python中的关联容器是dictionary,与C++中的map相似。
二、案例分析
近日,review项目代码,发现S君写了如下一个函数,有100多行。
<span style="font-size:14px;">// 解析测量使能开关 void classA::ParseMeasurementFlag(const QString &itemName) { if (itemName.isEmpty()) { qCritical() << "itemName.isEmpty()"; return; } long lMeasurementFlag = 0; long lCurrTestItemFlag = 0; if (!QString::compare(itemName, QString1, Qt::CaseInsensitive)) { lMeasurementFlag = bit1 | bit2 | bit3; lCurrTestItemFlag = enum1; } else if (!QString::compare(itemName, QString2, Qt::CaseInsensitive)) { lMeasurementFlag = bit3 | bit5 | bit7; lCurrTestItemFlag = enum2; } ... else { lMeasurementFlag = 0; lCurrTestItemFlag = 0; } }</span>
我:...这段代码应该可以用map优化吧?
S:我之前考虑过map,但是这个函数的入参(itemName)是从配置文件中读出来的,万一传进来字符串大小写和map的key字符串大小写不一致,可能会匹配不上,而用QString::compare()函数可以设置大小写不敏感...
我:这个好解决。将map中的key字符串全转为小写,然后将传入的字符串在进行map的find之前也先转为小写,这样双方都是小写就不存在这个问题啦。我估计“QString::compare()函数可以设置大小写不敏感”的原理也是如此。
S:还有,if/else if语句体包含了两个赋值语句,map的value项只能装载一个值,难道要设计两个map?
我:这个也不难,设计一个struct,这个struct包含两个long类型的变量,然后将map的value,设置为该struct即可。
S:设计太多自定义类型不太好吧,特别是仅为这一处使用设计的?
我:你担心的其实是自定义类型过多造成“名字冲突”。我可以将这个struct声明为类A的嵌套结构体,只在该类的作用域内可见。
...
三、具体实现
1,在头文件中声明一个自定义结构体和定义一个map变量,此外还有设计一个私有的map初始化函数
<span style="font-size:14px;">private: void InitTestCaseMap(); private: // 嵌套结构体声明 struct stFlag { long lFlag1; long lFlag2; }; QMap<QString, stFlag> m_mapTestCase;</span>
<span style="font-size:14px;">void classA::InitTestCaseMap() { #define INSERT_MAP(expression1, expression2, expression3) \ m_mapTestCase.insert(QString(expression1).trimmed().toLower(), { expression2, expression3 }) INSERT_MAP(QString1, bit1 | bit2 | bit3, enum1); INSERT_MAP(QString2, bit3 | bit5 | bit7, enum2); INSERT_MAP(QString3, bit2 | bit5 | bit8, enum4); ... #undef INSERT_MAP(expression1, expression2, expression3) }</span>
1)减少代码行的长度;
2)添加了字符串处理函数,也可方便增删其他处理函数;
3,原函数改造
<span style="font-size:14px;">// 解析测量使能开关 void classA::ParseMeasurementFlag(const QString &itemName) { if (itemName.isEmpty()) { qCritical() << "itemName.isEmpty()"; return; } QString caseName = itemName.trimmed().toLower(); long lMeasurementFlag = 0; long lCurrTestItemFlag = 0; auto iter = m_mapTestCase.find(caseName); if (iter == m_mapTestCase.end()) { throw CustomException(Fatal, std::string("there is no matching test case")); } else { lMeasurementFlag = iter.value().lFlag1; lCurrTestItemFlag = iter.value().lFlag2; } }</span>