c++中的if语句、switch语句、std::map以及多态比较

实现多分支逻辑的场景通常可以使用以下几种技术手段可以选择

  • if-else语句
  • switch语句
  • 三元运算符(只能实现2个分支的场景,本文不做讨论)
  • 映射表(std::map、数组或std::vector
  • 多态(策略模式为例)

其他几种手段不必多说,多态是面向对象的一个重要概念,本文以策略模式为例比较多态与其他几种多分支技术的区别。策略模式的一般形式如下

struct Strategy {virtual void execute() = 0;
};struct ConcreteStrategy1 : Strategy {void execute() override { // code block 1 }
};struct ConcreteStrategy2 : Strategy {void execute() override { // code block 2 }
};// Context class that uses the strategy
class Context {
public:Context(Strategy& s) : strategy(s) {}void execute() { strategy.execute(); }Strategy& strategy;
};// Usage
Context context(new ConcreteStrategy1());
context.execute();

在实际编码中该如何选择使用哪一种技术手段呢?我们先对这几种技术做个简单的比较

技术手段分支条件 & 灵活性动态性性能
if-else语句支持多种条件组合,灵活性很高不支持动态扩展
switch语句单一条件不支持动态扩展
映射表(数组或std::vector)单一条件可动态扩展
映射表(std::map)单一条件可动态扩展
多态单一条件,灵活性高可动态扩展

分支条件

显然,if-else语句几乎在任何场景中都可以使用,因为它的分支条件很灵活而且是可以组合的。而其他几种技术的分支条件都只支持单一条件,只能根据一个变量的值选择分支,而且分支条件的变量类型也有限制

  • switch语句:分支条件的类型可以是 整数类型、枚举类型、字符类型几种。浮点数、字符串等无法直接作为分支条件,自定义类型重载了比较操作符后也可以作为分支条件。
  • 映射表(数组或std::vector):分支条件的类型只能是整数类型,并且分支需要是紧密排列的。这种技术就是利用数组形成映射表,然后用数组下标查询分支。
  • 映射表(std::map):分支条件的类型就是std::map的key的类型。作为key的类型,要求满足严格的弱序关系(支持 < 运算符,并且且运算符定义的比较是可传递的),自定义类型重载了小于号操作符后也可以作为key。
  • 多态:分支条件实际上就是子类(策略)的类型,不同子类的实例触发的分支自然是不一样的。

动态性

动态性是指运行时分支是否可变(增加分支或者减少分支),显然if-else语句和switch语句无法做到运行时可变,而其余几种则是支持的。

性能对比

关于性能我们先看一组数据。下面是不同分支数量情况下,执行500次查询的耗时情况(测试方法见本文最后的 性能数据采集方法)。
image.png
随着分支数量增加if-else语句和std::map版本的耗时逐渐增多,switch语句和多态版本的耗时则相对稳定地保持在很低的水平。
关于性能的结论很明显,但是…为什么呢?理解四种技术各自的原理后就会得到答案

if-else语句if-else编译后汇编码对应一系列test指令,每次查询分支都会逐一测试分支条件直至成功命中后中断查询。分支越多查询的平均耗时越长。现代编译器的流水线技术还会提供分支预测能力,这有助于提升性能,但如果分支预测失败代价也会很高。

switch语句:编译器使用跳转表技术将switch语句转换为数组形式,每次查询分支相当于一次寻址操作,查询耗时理论上和分支多少没关系。

映射表(数组或std::vector):数组形式的映射表没统计在内,因为数组形式的映射表技术是编译器实现switch语句的原理,每次查询分支就是一次数组取值操作,查询耗时理论上和分支多少没关系。感兴趣的同学可以使用文末的方法自己采集数据观察。

映射表(std::map):每次查询分支实际上就是一次查表操作。std::map内部是用红黑树实现的,查询耗时与树大小有关,所以分支越多查询的平均耗时越长。

多态:c++多态是使用虚函数实现的,每次查询分支实际上对应一次查虚函数表的操作,相当于两次寻址操作,所以查询耗时理论上和分支多少没关系。

如何选择

本人原则:简单编码。在当前情形下,哪种技术用起来更简单(编码少、易读)就用哪个。另外有两个方面可能会特别注意

  • 扩展性:std::map和多态的扩展性更好,在分支量大或者分支改动频繁的情况下会优先考虑。
  • 性能:分支较多时if-else语句和std::map的性能表现较差,当性能成为瓶颈的时候考虑使用其他形式替代。

性能数据采集方法

使用下面的python脚本生成不同分支数量的cpp代码,然后编译、运行并观察结果。注意编译时使用-O0选项关闭编译器优化。

import randomBASE_CODE = """
#include <stddef.h>
#include <stdio.h>
#include <vector>
#include <functional>
#include <map>
#include <chrono>
#include <thread>struct Strategy {virtual int GetValue() { return -1; }static Strategy& GetInst(int value);
} strategy_base;struct Data {Data(int value) : _value(value), strategy(Strategy::GetInst(value)) { }int _value;Strategy& strategy;
};long long CalculateElapsedTime(std::vector<Data>& input, std::function<int(const Data&)> func) {auto startTime = std::chrono::high_resolution_clock::now();std::vector<int> res(input.size());for (int index = 0; index < input.size(); ++index) {res[index] = func(input[index]);}auto endTime = std::chrono::high_resolution_clock::now();long long elapsedTime = std::chrono::duration_cast<std::chrono::microseconds>(endTime - startTime).count();return elapsedTime;
}"""def CodeIfElse(num):left = "{"right = "}"print("int TransIfElse(const Data& input) {")print("    int res = -1;")for i in range(num):branchStr = "if" if i == 0 else "else if"print(f"    {branchStr} (input._value == {i}) {left}")print(f"        res = {100 + i};")print(f"    {right}")print("    return res;")print("}")def CodeSwitches(num):print("int TransWithSwitch(const Data& input) {")print("    int res = -1;")print("    switch(input._value) {")for i in range(num):print(f"        case {i}:")print(f"            res = {100 + i};")print(f"            break;")print(f"        default:")print(f"            break;")print("    }")print("    return res;")print("}")def CodeMap(num):left = "{"right = "}"print("int TransWithSTDMap(const Data& input) {")print("    static std::map<int, int> valuMap = {")for i in range(num):print(f"        {left}{i}, {100 + i}{right},")print("    };")print("    if (valuMap.find(input._value) == valuMap.end()) {")print("        return -1;")print("    }")print("    int res = valuMap[input._value];")print("    return res;")print("}")print("")def codeList(num):print("int TransWithSTVector(const Data& input) {")print("    static std::vector<int> valueVector = {")for i in range(num):print(f"        {100 + i},")print("    };")print("    if (valueVector.size() <= input._value) {")print("        return -1;")print("    }")print("    return valueVector[input._value];")print("}")print("")def CodeStrategy(num):left = "{"right = "}"print(f"static const int HANDLER_INST_NUM = {num};")for i in range(num):print(f"struct SubHandler_{i} : public Strategy {left} virtual int GetValue() {left} return {100 + i}; {right}{right} strategy_{i};")print("")print("Strategy& Strategy::GetInst(int value) {")print("    if (value >= 0 && value < HANDLER_INST_NUM) {")print("         return *(&strategy_0 + value);")print("    }")print("    return strategy_base;")print("}")print("")print("int TransWithStrategy(const Data& input) {")print("    int res = input.strategy.GetValue();")print("    return res;")print("}")print("")def CodeInput(rangeRight, num):random_numbers = [random.randint(0, rangeRight + 1) for _ in range(num)]inputData = "    std::vector<Data> input = { "for i in range(len(random_numbers)):if i != 0 and i % 50 == 0:inputData += "\n                                "inputData += f"{random_numbers[i]}, "inputData += "};"print(inputData)def code(branchNum):print(BASE_CODE)CodeIfElse(branchNum)CodeSwitches(branchNum)CodeMap(branchNum)codeList(branchNum)CodeStrategy(branchNum)print("int main() {")CodeInput(branchNum, 500)print("""    long long timeIfElse = CalculateElapsedTime(input, TransIfElse);""")print("""    printf("===> timeIfElse: %-lld us\\n", timeIfElse);""")print("""    long long timeSwitch = CalculateElapsedTime(input, TransWithSwitch);""")print("""    printf("===> timeSwitch: %-lld us\\n", timeSwitch);""")print("""    long long timeSTDMap = CalculateElapsedTime(input, TransWithSTDMap);""")print("""    printf("===> timeSTDMap: %-lld us\\n", timeSTDMap);""")print("""    long long timeVector = CalculateElapsedTime(input, TransWithSTVector);""")print("""    printf("===> timeVector: %-lld us\\n", timeVector);""")print("""    long long timeStrategy = CalculateElapsedTime(input, TransWithStrategy);""")print("""    printf("===> timeStrategy: %-lld us\\n", timeStrategy);""")print("""}""")import sys
if __name__ == "__main__":args = sys.argvbranchNumber = 2 if len(args) <= 1 else int(args[1])code(branchNumber)

完整的生成代码、编译、运行命令如下

# 生成分支数为20的cpp,如需生成分支数为50的版本,将下面的20改为50即可
python3 Coder.py 20 > Main.cpp# 编译
g++ Main.cpp -std=c++11 -O0 -o test# 运行
./test

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/537162.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java零基础入门-Map集合练习(下)

一、教学目标 能够使用HashMap集合实现值为双列集合存放。能够使用HashMap集合实现值为单列集合存放。能够使用HashMap集合实现值为基本数据类型存放。能够使用HashMap集合实现值为对象存放。 二、HashMap实现自定义元素存放 3、值存放双列集合 对于值又存放map&#xff1f;m…

对矩阵进行奇异值分解 numpy.linalg.svd()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 对矩阵进行奇异值分解 numpy.linalg.svd() [太阳]选择题 请问根据以下程序说法正确的是&#xff1a; import numpy as np A np.array([[1, 2], [3, 4], [5, 6]]) print("【显示】A:\n&q…

Tcl语言:基础入门(三)

相关阅读 Tcl语言https://blog.csdn.net/weixin_45791458/category_12488978.html?spm1001.2014.3001.5482 Tcl中的大括号 大括号{}可以使得被其包围的所有内容被解释为字面量&#xff0c;所以不会进行命令替换&#xff0c;转义符替换&#xff08;大部分情况的转义&#xff0…

爬虫学习 Scrapy中间件代理UA随机selenium使用

目录 中间件UA、代理处理---process_requestUA随机 代理处理seleniumscrapy 中间件 控制台操作 (百度只起个名 scrapy startproject mid scrapy genspider baidu baidu.com setting.py内 ROBOTSTXT_OBEY FalseLOG_LEVEL "WARNING"运行 scrapy crawl baidu middle…

openssl3.2 - exp - export ecc pubkey from ecc priv key

文章目录 openssl3.2 - exp - export ecc pubkey from ecc priv key概述笔记END openssl3.2 - exp - export ecc pubkey from ecc priv key 概述 前面实验已经生成了ECC私钥, 现在做从ECC私钥(内容为公私钥对, 里面既有私钥信息, 也有公钥信息)导出ECC公钥. 实验对应的命令行…

<支持向量机算法(SVM:Support Vector Machine)>——《机器学习算法初识》

目录 一、⽀持向量机(SVM)算法 1 SVM算法导⼊ 2 SVM算法定义 2.1 定义 2.2 超平⾯最⼤间隔介绍 2.3 硬间隔和软间隔 2.3.1 硬间隔分类 2.3.2 软间隔分类 3 ⼩结 二、 SVM算法api初步使⽤ 三、 SVM算法原理 1 定义输⼊数据 2 线性可分⽀持向量机 3 SVM的计算过程与算…

爬虫与DataFrame对象小小结合

import pandas as pd import requests from lxml import etree #数据请求 url"https://www.maigoo.com/brand/list_1715.html" headers{User-Agent:} #数据响应 resrequests.get(url,headersheaders) tree etree.HTML(res.text) #数据解析 titletree.xpath(.//div[c…

【Vue】Request模块 - axios 封装Vuex的持久化存储

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Vue ⛺️稳中求进&#xff0c;晒太阳 Request模块 - axios 封装 使用axios来请求后端接口&#xff0c;一般会对axios进行一些配置&#xff08;比如配置基础地址&#xff0c;请求响应拦截器…

六 超级数据查看器 讲解稿 详情1 概述

六 超级数据查看器 讲解稿 详情1 概述 点此此处 以新界面 打开B站 当前视频教程 APP下载地址 百度 下载地址 ​ 讲解稿全文&#xff1a; 大家好&#xff0c;今天我们讲解一下超级数据查看器详情界面。由于内容较多&#xff0c;讲解要分为7集&#xff0c;这是第一集 首…

Pulsar消息路由深入剖析

一、概述 大数据背景下&#xff0c;分区应该是所有组件必备的基本条件&#xff0c;否则面对海量数据时无论是计算还是存储都容易遇到瓶颈。跟其他消息系统一样&#xff0c;Pulsar通过Topic将消息数据进行业务层面划分管理&#xff0c;同时也支持Topic分区&#xff0c;通过将多…

何为一致性哈希?一致性哈希和哈希有什么区别?一文深入理解一致性哈希

目录 一、前言二、哈希2.1、哈希碰撞2.2、针对哈希碰撞的两种方案2.3、为什么用哈希2.4、普通哈希的缺点 三、一致性哈希3.1、实现方式 - 哈希环3.2、场景复现3.3、优化版本的哈希环 四、总结 一、前言 在学到分布式负载均衡时&#xff0c;负载均衡的方式有很多种&#xff0c;…

*地宫取宝c++

题目 输入样例1&#xff1a; 2 2 2 1 2 2 1输出样例1&#xff1a; 2输入样例2&#xff1a; 2 3 2 1 2 3 2 1 5输出样例2&#xff1a; 14 思路 题目说从入口开始&#xff0c;只能向右或向下行走到达右下角&#xff0c;类似“摘花生”这道题的模型。题目又说只有当格子里的宝…