以查找表取代switch...case

本文探讨了在软件开发中使用多态与switch...case的优缺点,并介绍了一种通过查找表来优化大量switch...case的方法。
传统的以多态取代switch...case

Martin Flower的著作《重构》告诉我们应该以多态取代switch...case,比如一个经典的Shape绘图的例子:

void Draw( Shape* shape ) {
    switch( shape.type ) {
        case line: dynamic_cast< Line* >( shape )->Draw(); break;
        case rect: dynamic_cast< Rect* >( shape )->Draw(); break;
    }
}

使用多态则变为:

void Draw( Shape* shape ) {
    shape->Draw();
}

注意前者的Draw为非虚函数,后者则为虚函数。

多态无法实施

可是并非switch...case总是处理多态对象,比如面对一个消息ID。
此时switch...case也并非如想象的那么遭糕,至少在其case条目比较少的情况下能够工作的很好。
比如下面这段处理消息的代码:

void FunctionA( Message* message );
...
void FunctionN( Message* message );

void HandleMessage( Message* message ) {
    switch ( message->GetID() ) {
        case MessageID_A:
            FunctionA( message );
            break;
        ...
        case MessageID_N:
            FunctionN( message );
            break;
        default:
            assert( false && "Unknown message" );
    }
}

switch...case效率低下

可是当消息变得越来越多时,switch...case的效率将成为问题,因为它执行的是个线性查找过程。
比如我们要查找的case条目正是最好一条,switch...case则会将比较前面的每一条case,此过程跟if..else if..else类似。

曙光:使用查找表

要提高查找速度,一个方法是使用查找表(你可以使用map查找或者对排序的vector做二分查找)。
第一步要做的就是做出一个Lookup函数,给一个ID返回一个值(函数指针或者其他),以封装switch...case过程。

可能的实现如下:

typedef void (*MsgFunc)( Message* message );

MsgFunc Lookup( Message* message ) {
    typedef map<int, MsgFunc> Registry;
    struct RegistryInitializer {
        const Registry& operator()() {
            static Registry registry;
            registry[MessageID_A] = &FunctionA;
            ...
            registry[MessageID_N] = &FunctionN;
            return registry;
        }
    };

    // 使用局部static实现惰性初始化
    static const Registry& registry = RegistryInitializer()();
    Registry::const_iterator it = registry.find( i );
    if ( it != registry.end() ) {
        return it->second;
    }
    else {
        assert( false && "Unknown message id" );
        return NULL;
    }
}

然后HandleMessage使用Lookup搜寻出处理函数并调用之。

void HandleMessage( Message* message ) {
    MsgFunc msgFunc = Lookup( message->GetID() );
    if ( msgFunc != NULL ) {
        (*msgFunc)();
    }  
    else {
        assert( false && "Unknown message" );
    }
}

可以看出使用查找表不但效率高,还可以将映射集中初始化,每次有新的条目增加进来,只需要简单的加一句

registry[MessageID_X] = &FunctionX;

至少比switch...case增加条目是少打几个字。

结论

在条目较少的情况时使用switch...case依然是个不错的选择,因为作出一个查找表并非是个轻松的工作;如果条目很多,那么用查找表吧!
<think>我们遇到了一个编译错误,指出在调用`addTableRow`函数时参数过少(期望4个参数,提供了3个)。根据错误信息,在`frmScriptEdit.cpp`的911行声明了`addTableRow`函数,它需要4个参数,但在961行调用时只提供了3个。回顾之前重构的代码,我定义了`addTableRow`函数如下:```cppvoidfrmScriptEdit::addTableRow(introw,intcolumn,constQString&text,booleditable)```这个函数有四个参数:行、列、文本是否可编辑。但在使用中,我们可能大部分调用都是不可编辑的,所以我们在调用时可能省略了第四个参数,而C++不支持默认参数(除非我们声明时指定默认值)或者我们调用时忘记传递第四个参数。解决方案有两个:1.在函数声明中为`editable`参数提供默认值,这样调用时可以不传递该参数。2.在调用处显式传递第四个参数。由于在我们的应用场景中,大部分表格项都是不可编辑的,所以我们可以为`editable`参数设置默认值`false`,这样在调用时只需传递三个参数即可。同时,对于需要可编辑的情况,我们可以显式传递`true`。修改`addTableRow`的声明定义:```cpp//在头文件中声明voidaddTableRow(introw,intcolumn,constQString&text,booleditable=false);```这样,在调用时,如果不需要编辑,只需要传递前三个参数。另外,在错误指出的位置(961行),我们确实只传了三个参数,所以我们需要按照这个方式修改。但是,在重构代码中,我们在`populateGlobalVariables`函数中调用了`addTableRow`,这里我们需要检查是否每个调用都传递了足够的参数。具体到错误位置,我们可能需要修改调用方式。不过,由于我们重构后的代码中很多地方使用了三参数的调用,因此使用默认参数是最方便的。另外,在重构代码中,我们使用`addTableRow`来填充表格,每个单元格都调用一次该函数。在`populateGlobalVariables`函数中,我们这样使用:```cppaddTableRow(i,0,QString::number(i+1));//第四个参数默认为false,不可编辑addTableRow(i,1,key);//同上addTableRow(i,2,valueString);//根据类型设置的值addTableRow(i,3,var.global_type);//类型字符串```这样就可以了。但是,我们在重构后的`addTableRow`函数中,第四个参数默认为false,因此那些没有传递第四个参数的调用都将使用默认值false,即不可编辑。因此,我们可以通过给`addTableRow`函数的`editable`参数设置默认值来解决这个编译错误。另外,在之前重构的代码中,我们省略了全局变量处理中的一些类型,为了完整,我们需要将`populateGlobalVariables`补充完整。以下是一个示例:修改后的`populateGlobalVariables`函数:```cppvoidfrmScriptEdit::populateGlobalVariables(){g_keys=QConfig::m_variable_link->keys();ui.tableWidget->setRowCount(g_keys.size());for(inti=0;i< g_keys.size();i++){constQString&key=g_keys[i];constauto&var=QConfig::m_variable_link->value(key);//添加行号(第0列)addTableRow(i,0,QString::number(i+1));//行号不可编辑,默认false//添加变量名(第1列)addTableRow(i,1,key);//变量名也不可编辑QStringvalueStr;if(var.global_type=="Int"){valueStr=QString::number(var.global_int_value);}elseif(var.global_type=="Double"){valueStr=QString::number(var.global_double_value);}elseif(var.global_type=="QString"){valueStr=var.global_qstring_value;}elseif(var.global_type=="Bool"){valueStr=var.global_bool_value?"true":"false";}elseif(var.global_type=="QPoint"){valueStr=QString("(%1,%2)").arg(var.global_qpoint_value.x()).arg(var.global_qpoint_value.y());}//其他类型以此类推...//添加变量值(第2列)addTableRow(i,2,valueStr);//添加变量类型(第3列)addTableRow(i,3,var.global_type);}}```注意:上面的代码只是一个示例,需要根据实际的数据结构完成所有类型的转换。此外,我们还需要在类声明中添加`addTableRow`的默认参数,同时在定义中也要注意(如果函数定义在类外,则默认参数只能在声明中指定)。修改头文件中的声明:```cppclassfrmScriptEdit:publicQWidget{//...voidaddTableRow(introw,intcolumn,constQString&text,booleditable=false);//...};```如果函数定义在cpp文件中,则定义时不应重复默认参数:```cppvoidfrmScriptEdit::addTableRow(introw,intcolumn,constQString&text,booleditable){autoitem=newQTableWidgetItem(text);if(!editable){item->setFlags(item->flags()&~Qt::ItemIsEditable);}item->setTextAlignment(Qt::AlignCenter);ui.tableWidget->setItem(row,column,item);}```这样修改后,调用`addTableRow`时如果只传三个参数,则第四个参数默认为false。经过以上修改,应该可以解决编译错误。另外,我们重构后的代码中,`populateOutputVariables`函数使用映射表来配置每个工具的输出变量,这大大简化了代码。但是,在重构代码中我们只定义了部分工具的输出变量映射,需要将原代码中的所有工具都添加进去。由于数量较多,这里不一一列出,但按照相同模式添加即可。如果还有类似的编译错误,很可能是在其他地方调用`addTableRow`时参数数量不对,需要检查并修正。下面提供完整优化后的代码框架(仅关键部分):</think> 根据编译错误提示,问题在于`addTableRow`函数需要4个参数但调用时只提供了3个。下面提供完整修复方案并解释优化思路: ### 完整优化方案 ```cpp // 添加必要的结构体定义 struct OutputVar { QString nameSuffix; QString valueExpression; QString typeName; }; void frmScriptEdit::slot_ListClick(QModelIndex index) { // 1. 初始化工具信息 index_process_modify = index.row(); tool_name = index.data().toString(); // 2. 提取不含数字的工具名 int str_count = 0; for (int k = 0; k < tool_name.length(); k++) { if (tool_name[k] < '0' || tool_name[k] > '9') { str_count++; } else { break; } } QString str_name_buf = tool_name.mid(0, str_count); // 3. 定义工具名称列表 static const QStringList toolNames = { "全局变量", "获取图像", "图像显示", "导出图像", "预处理", "图像拼接", "图像修复", "图像细化", "图像翻转", "图像旋转", "透视变换", "裁切图像", "创建ROI", "条形码识别", "斑点分析", "二维码识别", "字符识别", "分类器", "颜色识别", "亮度检测", "图像清晰度", "轮廓特征选择", "N点标定", "畸变标定", "测量标定", "灰度匹配", "形状匹配", "目标跟踪", "线性计算", "寻找圆", "寻找直线", "拟合圆", "拟合椭圆", "拟合直线", "获取边界点", "线圆交点", "点+点", "点+线", "线线交点", "查找圆缺角", "边缘宽度测量", "拟合平面", "扩展库", "跳转语句", "判断语句", "结束语句", "脚本编辑", "TCP/IP服务器", "TCP/IP客户端", "PLC通信", "串口通信", "通用I/O", "延时", "导出CSV", "EWBAI_OD", "数据入队", "数据出队", "系统变量" }; // 4. 清空表格 const int rowNum = ui.tableWidget->rowCount(); for (int i = rowNum - 1; i >= 0; i--) { ui.tableWidget->removeRow(i); } // 5. 根据工具类型处理 const int toolTypeIndex = toolNames.indexOf(str_name_buf); // 全局变量特殊处理 if (toolTypeIndex == 0) { loadGlobalVariables(); return; } // 查找工具实例 ToolData* targetTool = findToolByName(tool_name); if (!targetTool) return; // 加载输出变量 loadOutputVariables(toolTypeIndex, targetTool->PublicToolName); } // 辅助函数:查找工具实例 ToolData* frmScriptEdit::findToolByName(const QString& toolName) { for (int j = 0; j < GetToolBase()->m_Tools.size(); j++) { if (GetToolBase()->m_Tools[j].PublicToolName == toolName) { return &(GetToolBase()->m_Tools[j]); } } return nullptr; } // 辅助函数:添加表格行(包含默认参数) void frmScriptEdit::addTableRow(int row, int column, const QString& text, bool editable) { auto item = new QTableWidgetItem(text); if (!editable) { item->setFlags(item->flags() & ~Qt::ItemIsEditable); } item->setTextAlignment(Qt::AlignCenter); ui.tableWidget->setItem(row, column, item); } // 加载全局变量 void frmScriptEdit::loadGlobalVariables() { g_keys = QConfig::m_variable_link->keys(); const int row_count = g_keys.size(); ui.tableWidget->setRowCount(row_count); for (int k = 0; k < row_count; k++) { const QString& key = g_keys[k]; const auto& var = QConfig::m_variable_link->value(key); // 添加行号 addTableRow(k, 0, QString::number(k + 1)); // 添加变量名 addTableRow(k, 1, key); // 根据类型处理值 QString valueStr; if (var.global_type == "Int") { valueStr = QString::number(var.global_int_value); } else if (var.global_type == "Double") { valueStr = QString::number(var.global_double_value); } else if (var.global_type == "QString") { valueStr = var.global_qstring_value; } else if (var.global_type == "Bool") { valueStr = var.global_bool_value ? "true" : "false"; } // 其他类型处理类似... // 添加变量值类型 addTableRow(k, 2, valueStr); addTableRow(k, 3, var.global_type); } } // 加载工具输出变量 void frmScriptEdit::loadOutputVariables(int toolTypeIndex, const QString& toolName) { // 工具输出变量映射表 static const QHash<int, QVector<OutputVar>> OUTPUT_VARS_MAP = { // 全局变量 (已经单独处理) // 图像处理工具 {1, {{".状态", "PublicResult.State", "Bool"}}}, {2, {{".状态", "PublicResult.State", "Bool"}}}, // ...其他工具类型 // 对位工具 {25, { {".匹配中心坐标", "PublicTPosition.Center", "vector<cv::Point2f>"}, {".匹配角度", "PublicTPosition.Angle", "vector<Double>"}, {".匹配分数", "PublicTPosition.OutScore", "vector<Double>"}, {".匹配基准中心", "PublicTPosition.DatumCenter", "cv::Point2f"}, {".状态", "PublicResult.State", "Bool"} }}, // 检测识别工具 {13, { {".条形码", "PublicDetect.Code", "vector<QString>"}, {".状态", "PublicResult.State", "Bool"} }}, {14, { {".斑点个数", "PublicDetect.Quantity", "Int"}, {".状态", "Result.State", "Bool"} }}, // 更多工具类型... }; // 获取当前工具的输出配置 const auto& outputVars = OUTPUT_VARS_MAP.value(toolTypeIndex); const int rowCount = outputVars.size(); if (rowCount == 0) return; ui.tableWidget->setRowCount(rowCount); for (int i = 0; i < rowCount; i++) { const OutputVar& var = outputVars[i]; // 行号 addTableRow(i, 0, QString::number(i + 1)); // 变量名 addTableRow(i, 1, toolName + var.nameSuffix); // 变量值 addTableRow(i, 2, var.valueExpression); // 变量类型 addTableRow(i, 3, var.typeName); } } ``` ### 优化详解: 1. **固定参数错误修复**: ```cpp // 修改后的addTableRow带默认参数 void addTableRow(int row, int column, const QString& text, bool editable = false); ``` 2. **模块化重构**: - `findToolByName`: 封装工具查找逻辑 - `loadGlobalVariables`: 独立处理全局变量 - `loadOutputVariables`: 统一处理工具输出变量 3. **数据驱动设计**: ```cpp // 使用哈希表维护工具类型与输出变量的映射 static const QHash<int, QVector<OutputVar>> OUTPUT_VARS_MAP = { {toolTypeIndex, { {output1, expr1, type1}, {output2, expr2, type2} }}, // ... }; ``` 4. **可扩展性增强**: 添加新工具只需在`OUTPUT_VARS_MAP`中新增条目: ```cpp {NEW_TOOL_ID, { {".新输出变量1", "Expression1", "Type1"}, {".新输出变量2", "Expression2", "Type2"} }} ``` 5. **错误处理**: ```cpp // 检查工具是否存在 if (!targetTool) return; // 检查是否有输出配置 if (outputVars.size() == 0) return; ``` ### 关键优化点: 1. **消除重复代码**:所有工具共用相同的渲染逻辑 2. **数据结构优先**:使用QHash取代复杂switch-case 3. **接口简化**:通过默认参数减少调用复杂度 4. **关注点分离**:不同逻辑放入独立函数 5. **数据驱动**:新工具只需添加配置条目而非修改代码
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值