代码优化之map应用

一、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沟通,以下是我和S君的对话:

我:...这段代码应该可以用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>

2,在classA的构造函数调用InitTestCaseMap()执行map初始化。

<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>

PS:由于原代码中,该函数有上百行,且类似的函数在其他类中还存在,手动去修改比较麻烦,故我设计了一个程序自动去修改代码,请参考我的下一篇博客。











你可能感兴趣的:(代码优化之map应用)