C++ 跳台开关案例问题

C++ 跳台开关案例问题,c++,c,hashtable,switch-statement,C++,C,Hashtable,Switch Statement,我试图理解一些关于跳转表的事情,以及它在switch-case语句之间的关系 有人告诉我,跳转表是编译器生成的一种O(1)结构,它使查找值的速度基本上尽可能快。但是,在某些情况下,哈希表/字典可能更快。我还被告知,只有当开关盒包含有序的数据值时,这才有效 请有人确认或否认这一点,并解释什么是跳转表,它的重要性以及与使用字典或哈希表相比的时间复杂性。谢谢。编译switch语句可以采取多种形式,具体取决于具体情况。如果案例非常接近,那么使用跳转表是很简单的。如果案例相距很远,请使用If(case==

我试图理解一些关于跳转表的事情,以及它在switch-case语句之间的关系

有人告诉我,跳转表是编译器生成的一种O(1)结构,它使查找值的速度基本上尽可能快。但是,在某些情况下,哈希表/字典可能更快。我还被告知,只有当开关盒包含
有序的
数据值时,这才有效


请有人确认或否认这一点,并解释什么是跳转表,它的重要性以及与使用字典或哈希表相比的时间复杂性。谢谢。

编译switch语句可以采取多种形式,具体取决于具体情况。如果案例非常接近,那么使用跳转表是很简单的。如果案例相距很远,请使用If(case==value)或映射。或者编译器可以使用一种组合:由跳转表范围的if检查确定的跳转表孤岛。

假设您有一个过程数组:

void fa() { 
 printf("a\n");
}

...

void fz() { 
 printf("it's z!\n");
}



typedef void (*F)();
F table[26]={fa,fb,...,fz};
假设您接受用户输入的字符(来自a-z)并运行fc:

char c;
switch(c) {
   case 'a': fa();break;
   case 'b': fb();break;
   ...
   case 'z': fz();break;       
   default: exit(-1);
}
理想情况下,这将被以下内容取代:

if (c<'a' || c>'z') exit(-1);
else (*table[c-'a'])();
if(c'z')退出(-1);
else(*表[c-'a']);
当然,你可以把桌子弄大一些,这样就不需要进行范围检查了


编译器将对任意代码执行此操作,而不一定只对函数调用执行此操作,并通过存储要跳转到的地址(本质上是一个goto)来执行此操作。C不直接支持任何类型的计算goto(索引到表或其他),但它的CPU指令非常简单。

跳转表基本上是指向代码段的指针数组,用于处理switch语句中的各种情况。它最有可能在案例密集时生成(即,对于某个范围内的每个可能值都有一个案例)。例如,给出如下语句:

switch (i) {
   case 1: printf("case 1"); break;
   case 2: printf("case 2"); break;
   case 3: printf("case 3"); break;
}
它可以生成大致相当于以下内容的代码:

void case1() { printf("case 1"); }
void case2() { printf("case 2"); }
void case3() { printf("case 3"); }

typedef void (*pfunc)(void);

pfunc functions[3] = {case1, case2, case3};

if ((unsigned)i<3)    
    functions[i]();
void case1(){printf(“case1”);}
void case2(){printf(“case2”);}
void case3(){printf(“case 3”);}
类型定义无效(*pfunc)(无效);
pfunc函数[3]={case1,case2,case3};

如果((unsigned)i一个跳转表是一个简单的函数指针数组,那么可以大致如下描述一个跳转表:

int(*函数[10])();/*由10个函数指针组成的数组*/

据我所知,这是与case语句一起使用的,如:每个条件case \都是该数组的索引,例如:

switch( a ) {
    case 1:  // (*functions[1])() // Call function containing actions in case of 1
        ...  
    case 2:  // (*functions[2])() // Call function containing actions in case of 2
        ...
每种情况下,转换为简单的函数[a]。这意味着访问函数[9]的速度与访问函数[1]的速度一样快。这给了您前面提到的O(1)时间


显然,如果您有case1和case4907,这将不是一个好方法,您提到的哈希表/字典方法可能会发挥作用。

a跳转表是一个抽象结构,用于将控制权转移到另一个位置。Goto、continue和break都是类似的,只是它们总是转换为r到一个特定的位置,而不是多个位置中的一个。特别是,此控制流与函数调用不同。(Wikipedia关于的文章是相关的。)

switch语句是如何在C/C++中编写跳转表。在这种常见情况下,只提供了一种有限的形式(只能切换整数类型),以使实现更简单、更快。(整数类型的跳转表的有效实现研究比一般情况下的要多。)一个典型的例子是

然而,通常不需要跳转表的全部功能,例如当每个案例都有中断语句时。这些“有限跳转表”是一种不同的模式
,它只利用跳转表经过充分研究的效率,并且在每个“操作”时都很常见是独立于其他人的


跳转表的实际实现有不同的形式,主要是索引键映射的实现方式不同。这种映射是“字典”和“哈希表”等术语出现的地方,这些技术可以独立于跳转表使用。说一些代码“使用跳转表”并不意味着你有O(1)查找

编译器可以为每个switch语句自由选择查找方法,并且不能保证您将得到一个特定的实现;但是,应该考虑编译器选项,例如速度优化和大小优化

您应该研究数据结构以了解它们所施加的不同复杂性要求。简而言之,如果“字典”指的是平衡二叉树,那么它就是O(logn);而哈希表取决于它的哈希函数和冲突策略。在switch语句的特殊情况下,由于编译器拥有完整的信息,它可以生成一个表示O(1)的查找。但是,不要仅仅通过查看整体算法复杂性而迷失方向:它隐藏了重要的因素。

进一步详细说明和其他

鉴于:

int x=1;
switch (i) {
   case 1: x=6; break;
   case 2: x++;
   // Fall through
   case 3: x+=7; break;
}
您可以有如下内容:

int f1() {return 6;}
int f2() {return 1+f3();}
int f3() {return 8;}
编译器可以使用跳转表索引
{f1,f2,f3}

当创建表时,编译器可以将
f1、f2、f3
设置为
x
直接设置为
6,9,8

但是如果编写函数并滚动自己的跳转表,
f1、f2、f3
可以在任何地方,但是编译器会知道将它们放在
开关附近,创建比您更好的代码局部性

请注意,在许多情况下,编译器将生成一个保护,以检查
i
是否在范围内(或处理
默认值
),如果您确定它始终是其中一种情况,则可以跳过该选项

有趣的是,对于少数情况,在不同的编译器标志(依赖于编译器)下,
开关
if (i==1) x=f1();
else if (i==2) x=f2();
else if (i==3) x=f3();
x=(i==1) ? f1()
: (i==2) ? f2()
: (i==3) ? f3()
: x;
int foo(int i)
{
   int x=1;
   switch (i) {
       case 1: x=6; break;
       case 2: x++;
        // Fall through
       case 3: x+=7; break;
       case 4: x+=2; break;
       case 5: x+=9; break;
    }
  return x;
}
        cmp     edi, 5                     //make sure it is not over 5
        ja      .L2                        //jump to default case
        mov     edi, edi
        jmp     [QWORD PTR .L4[0+rdi*8]]   // use the jump table at label L4:
.L4:
        .quad   .L2                        // if i=0, set x=1 (default)
        .quad   .L9                        // f1() see below
        .quad   .L10                       // f2() see below
        .quad   .L6                        // f3() see below
        .quad   .L7                        // f4() see below
        .quad   .L8                        // f5() see below
.L10:
        mov     eax, 9                     // x=9
        ret
.L9:
        mov     eax, 6                     // x=6
        ret
.L8:
        mov     eax, 10                    // x=10
        ret
.L6:
        mov     eax, 8                     // x=8
        ret
.L7:
        mov     eax, 3                     // x=3
        ret
.L2:
        mov     eax, 1                     // default, x was 1, noop is: x=1
        ret