关于C语言中的switch{} case语句?

11

我正在阅读一些关于C语言的文本。文本说switch{} case只能接受整数类型。

我很好奇为什么switch{} case不能接受其他类型,比如浮点数或字符串。这背后有什么原因吗?

非常感谢。


在我看来,我认为这是由于整数的固定大小和常量值。当表示为int时,您也可以使用char:https://dev59.com/questions/QXRB5IYBdhLWcg3wWGEH - vaugham
2
兄弟,顺便说一下,浮点数不容易比较(考虑小数点右侧的精度)。好好想想。 - luis.espinal
9个回答

18

传统的理由可能是对于整数值的“决策表达式”,可以进行非常好的优化。

基本上,您可以将case语句列表映射到包含地址的表中,然后根据值直接跳转。显然,对于浮点数和字符串,这种方法行不通。

在GCC中,您可以使用一些扩展手动执行此操作,例如此处所述:

const char * digit_name(int d)
{
  const void * handlers[] = { &&zero, &&one, &&two, &&three, &&four,
                              &&five, &&six, &&seven, &&eight, &&nine };
  goto *handlers[d]; /* Assumes d is in range 0..9. */

zero:  return "zero";
one:   return "one";
two:   return "two";
three: return "three";
four:  return "four";
five:  return "five";
six:   return "six";
seven: return "seven";
eight: return "eight";
nine:  return "nine";
 return NULL;
}

通常称之为"计算跳转(computed goto)", 而且很明显可以将switch编译成非常相似的东西。使用紧凑定义的开关表达式有所帮助,例如使用enum

此外,在C语言中并没有字符串的概念。


1
对于稀疏值集,一些编译器也使用二分查找;有些甚至可以从二分查找开始,然后切换到跳转表以处理密集区域。我看到的另一种方法是使用配置文件反馈来扭曲二分查找,以支持常见情况。 - AProgrammer
然而,如果只有少量的开关情况,一个简单的线性if(x == case1) { ... }; if (x == case1) { ... }; ...可能比完整的二进制搜索需要更少的指令。这总是取决于实际问题。 - datenwolf

3
我会用一个问题来回答:为什么你要使用switch语句而不是if...else if呢?
令人惊讶的是,许多程序员从未提出这个问题,但认为switch是语言中必须存在的基本内容。这不是真的!您可以编写任何类型的C程序,而无需使用switch。严格来说,switch是一项多余的功能。
那么为什么要使用它呢?
可读性不是原因。switch实际上比if-else具有更糟糕和不太直观的语法。在switch内部需要break语句,在switch的奇怪语法规则允许在另一个case的局部范围内声明case,default的任意位置等等。
switch不仅不易读,而且比if-else更容易出错。遗忘break是最明显的危险,导致了数百万难以发现的软件错误。
另一个更明显的反对switch更易读的论点是这个“裸骨”代码:
if (A)
{
}

else if (B)
{
}

else if (C)
{
}

else
{
}


switch(something)
{
  case A:
  {
    break;
  }

  case B:
  {
    break;
  }

  case C:
  {
    break;
  }

  default:
  {
    break;  
  }
}

上面的 if 语句和 switch 语句在语法和功能上是等价的,应该被编译成完全相同的机器代码。以下是这个例子的统计数据。
         if-else  switch
Symbols    33       65        // Not counting the expressions
Lines      12       19        // Not counting empty lines

“switch”需要更多的代码才能得到相同的结果,因此必须被视为比“if-else” 不可读
在开始争论“switch”看起来更像表格而不是“if-else”之前,请注意这与代码格式无关,也不相关。标准中没有任何限制,防止你写出像这样的代码:
if      (A) {}
else if (B) {}
else if (C) {}
else        {}

switch(something)
{
  case A:   { break; }
  case B:   { break; }
  case C:   { break; }
  default:  { break; }
}

我认为,如果你希望有一些类似于最小化表格语法的形式,那么两种形式都是可读的。
当然,使用switch可能出于审美、迷信或宗教原因,但我更愿意将这样的非主题讨论留给与编程无关的网站。
所以,switch 比 if-else 更不安全且不易读。那么,可能吸引程序员的是效率。有一个程序员必须通过程序测试的 n 个案例,肯定希望有一些能够尽快找到正确案例的东西。以线性方式检查所有案例是一个糟糕的想法。
正如您可能知道的那样,可以通过将 if-else 或 switch 实现为函数指针数组来对其进行优化:
typedef void (*Func_t)(void);

const Func_t cases [N] = { ... };
cases[i]();

这种激进的优化通常就是编译器在遇到 switch 语句时所做的。但是,只有在所有 case 都是相邻的整数时才能进行此优化。如果它们不是相邻的,则可以通过一个 const 查找表来创建相邻性。
但如果 case 不是整数类型,则无法进行上述优化。如果它们是浮点数、字符串或其他类型,则没有明智的优化方式。
因此,在我看来,switch 只存在于此目的:它使编译器创建比 if-else 更有效的代码更容易。因此,它属于与关键字 inline、register 等相同的类别,这些关键字也使编译器更轻松地优化您的代码。

3

C语言的语言哲学就是所见即所得,没有隐藏机制。这实际上是该语言的一大优势。

对整数进行开关操作涉及到预期的分支,而对浮点数和字符串进行比较会有隐藏的代价。


1

浮点数值通常不直接可比

x = 1 / 3.0;
switch (x) {
  case 0.3333: /* ... */; break;
  case 0.333333333875634875634: /* ... */; break;
  case 0.333333333784532452321: /* ... */; break;
  case 0.333333333847632874632: /* ... */; break;
  default: break;
}

对于字符串也是如此(不要使用strcpy(buff, "foobar"); if (buff == "foobar") /* ... */;


0

当有离散的选项时,开关可能是最好的选择。编译器可以警告您重复的情况,如果您使用枚举,则良好的编译器将警告未处理的值。

作为一种良好的实践,浮点数/双精度不应测试相等,“if(f = 3.141516)”会引起头痛,“const float kEpsilon = 1e-5;”,然后使用“if(fabs(f - 3.141516)< kEpsilon)”选择与您的问题相关的epsilon值。内联函数或宏可以帮助以更可读的方式编写此内容。


0

我们不能在 switch case 中使用 float。这是因为浮点数不精确。你永远不知道那个数字实际上会是什么。


0

浮点数的比较由于舍入误差不可靠,而C语言默认不支持字符串比较(只能通过函数strcmp等实现)。

-> 没有办法让编译器自动确定比较方法。


0
简单来说,整数类型易于比较,并且比较速度非常快。而在C语言中,浮点类型无法可靠地进行比较。我不认为C语言有字符串类型,但是字符串的比较速度较慢...你将不得不比较字符数组...速度较慢。我相信会有人给你一个更完整、更科学的答案。

0
你必须想一想“这段C代码如何转换成汇编代码?”。
Switch条件语句只是一种棘手的JMP指令,需要在编译之前将case排序(我猜编译器会自动帮你排序),但我不太确定。
例如,在PHP中,您可以使用字符串来做switch{},这可能使用某种二分搜索(先查找第一个字符等),就像maps一样。
您必须了解编译语言是一种生成“相当不错”的汇编代码的方式,但这并不意味着它比您只使用汇编语言编写程序更好。
使用类似C或C++这样的语言,您可以快速制作程序,并且编译器能够执行微小的优化,但是请记住,在使用编程语言时要考虑您程序的整体情况,并且不要忘记语言只是工具,它们并不神奇:如果您忽略了基本的底层行为,那么您就完蛋了。

不会的,switch语句通常不会被排序,因为如果它们没有以break结尾,它们可能会重叠。更一般地说,case标签只是任意嵌套语句块中的标签。 - Jens Gustedt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接