30

在我的 C++ 应用程序中,我有一些值作为代码来表示其他值。为了翻译代码,我一直在争论是使用 switch 语句还是 stl map。开关看起来像这样:

int code;
int value;
switch(code)
{
case 1:
    value = 10;
    break;
case 2:
    value = 15;
    break;
}

地图将是一个stl::map<int, int>,翻译将是一个简单的查找,代码用作键值。

哪个更好/更高效/更清洁/被接受?为什么?

4

12 回答 12

9

就个人而言,我会使用地图,因为它的使用意味着数据查找 - 使用开关通常表示程序行为的差异。此外,使用映射比使用开关更容易修改数据映射。

如果性能是一个真正的问题,分析是获得可用答案的唯一方法。如果分支错误预测经常发生,则切换可能不会更快。

考虑这一点的另一种方法是将代码和相关值组合到数据结构中是否更有意义,特别是如果代码和值的范围是静态的:

struct Code { int code; int value; };

Code c = ...

std::cout << "Code " << c.code << ", value " << c.value << std::end;
于 2010-03-17T20:54:14.620 回答
8

如果您的代码足够连续并且它们的范围允许您使用老式的简单数组会更好,例如

int lookup[] = {-1, 10, 15, -1 222};

那么 switch 语句可以简单地重写为

值 = 查找 [代码];

所有其他选项都会在一定程度上带来额外的成本。

于 2010-03-17T20:48:09.827 回答
5

这取决于代码是什么以及有多少。好的编译器有各种用于优化 switch 语句的技巧,其中一些他们不会使用直接的 if/then 语句。大多数人都足够聪明,可以进行简单的数学运算或使用查找/跳转表来处理案例 0、1、2 或案例 3、6、9 等情况。

当然有些没有,而且许多很容易被不寻常或不规则的价值观集所挫败。此外,如果处理多个案例的代码看起来非常相似,那么剪切和粘贴可能会导致维护问题。如果您有很多代码,但可以通过算法将它们分成几组,您可以考虑几个/嵌套的 switch 语句,例如,而不是:

switch (code) {
    case 0x0001: ...
    case 0x0002: ...
    ...
    case 0x8001: ...
    case 0x8002: ...
    ...
}

您可能会使用:

if (code & 0x8000) {
    code &= ~0x8000;
    switch (code) {
        case 0x0001: ... // actually 0x8001
        case 0x0002: ... // actually 0x8002
        ...
    }
}
else {
    switch (code) {
        case 0x0001: ...
        case 0x0002: ...
        ...
    }
}

许多语言解释器以这种方式解码操作码,因为单字节操作码可能将附加信息打包到不同的位中,并且转录所有可能的组合及其处理程序将是重复且脆弱的。另一方面,过多的位修改可能会破坏编译器的任何优化并适得其反。

除非您确定这是一个真正的性能瓶颈,否则我会避免过早的优化:无论采用哪种方式都会让您觉得相当健壮且可以快速实施。如果您的应用程序运行得太慢,请对其进行分析并进行相应的优化。

于 2010-03-17T21:16:34.137 回答
2

switch 语句会更快,但如果这不是您的应用程序性能瓶颈,您不应该真正关心这一点。

从长远来看,寻找使您的代码更易于维护的东西。

您的样本太短,无法在这方面做出任何有意义的调用。

于 2010-03-17T20:51:04.697 回答
2

我自己偏爱查找表,因为在我看来异常长的 switch 语句混淆了代码和数据之间的分离。我还认为表格更适合未来的更改和维护。

当然,所有恕我直言。

于 2010-03-17T21:21:56.123 回答
2

我建议使用静态的、恒定的、成对的表。这是查找表的另一种形式:

struct Table_Entry
{
    int code;
    int value;
};

static const Table_Entry  lookup_table[] =
{
  {1, 10},
  {2, 15},
  {3, 13},
};

static const unsigned int NUM_TABLE_ENTRIES =
    sizeof(lookup_table) / sizeof(lookup_table[0]);

这样做的一个好处是表是在编译时生成的,std::map这与必须在运行时初始化的不同。如果数量很大,您可以使用std::lower_bound查找条目,前提是表格已订购。

另一个好处是这种技术是数据驱动的。数据可以在不更改搜索引擎的情况下更改。代码或流程的更改可能需要认真的回归测试,但数据更改可能不需要;YMMV。

这类似于编译器可能生成的内容。

于 2010-03-18T00:09:39.930 回答
1

如果可以使用 tr1 ,则可以使用 unordered_map 对值进行散列(散列整数也可以非常快),这应该使大多数查找保持恒定时间。

但是,除非您有分析数据表明这是程序中的瓶颈,否则请以从设计角度来看最有意义的方法对其进行编码。

于 2010-03-17T21:25:25.377 回答
0

通常我会建议一个映射或查找数组,甚至可能是一些混合查找怪物(当然,假设您正在优化速度或代码大小而不是可读性),但值得注意的是新版本的 GCC 很聪明足以将这样的开关/案例分配替换为查找表。虽然这对于完全稀疏的键来说并不是那么好,但如果你的键是成簇的,可能是这样的:0、1、2、3、4、5、11、12、13、14、15、193、194、 195、196、197、198……

当然,如果您可能喜欢一些算术来进行转换,那就更好了(通常)。在做这样的事情时,永远不要忽视移位、交换字节顺序或更传统的数学。

于 2010-03-24T22:16:27.283 回答
0

我认为 switch-case 结构的生成代码会变得非常大,如果“代码”的数量变大,我认为 stl::map 更合适。

于 2010-03-17T20:50:11.623 回答
0

如果您所做的只是分配一个值,我会说 map。我的原因是它可以在不更改代码的情况下进行扩展,而您的 switch 语句则不是。

顺便说一句,枚举怎么样?

于 2010-03-17T20:47:25.647 回答
0

unordered_map 可能会更适合这里,因为它使用哈希表,并且查找会比 switch 更快。

于 2018-10-12T02:46:58.280 回答
-1
  • 将整数读入数组/向量
  • 对数组/向量进行排序
  • 在底层数组上使用 bsearch
于 2010-03-17T21:19:57.960 回答