关于C中的switch {}案例?
我正在用C language
阅读一些文本。 文中说switch{} case
只能接受整数类型。
我只是好奇为什么switch{} case
不接受其他类型,如float或string。 这背后有什么理由吗?
非常感谢。
经典原因可能是对于整数值“决策表达式”,可以进行非常好的优化。
基本上,您可以将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; }
这通常被称为“计算goto”,并且应该清楚如何将switch
基本上编译成非常相似的东西。 开启表达式的紧密定义有助于,例如使用enum
。
此外,C在语言层面上并没有太多的字符串概念。
C语言哲学就是你所看到的就是你得到的。 没有隐藏的机制。 这实际上是该语言的强大力量之一。
切换整数涉及预期的分支,而在float和string上进行比较会产生隐藏成本。
我会回答一个问题: 你为什么使用switch语句而不是if else else if?
令人惊讶的是,许多程序员从不问这个问题,而是考虑switch
语言必须存在的基本内容。 那不是真的! 您可以在不使用switch
情况下编写任何类型的C程序。 严格来说, switch
是一项冗余function。
那么为什么要用呢?
可读性不是原因。 switch
实际上比if-else具有更糟糕且更不直观的语法。 在switch中需要break语句,switch的奇怪语法规则允许在另一个case的局部作用域内声明一个case,默认的任意位置。
switch
不仅可读性较差,而且比if-else更容易出错。 被遗忘的rest是最明显的危险,导致软件中数以百万计的难以发现的错误。
另一个更明显的反对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在语法和function上是等价的,应该编译成完全相同的机器代码。 以下是此示例中的统计信息
if-else switch Symbols 33 65 // Not counting the expressions Lines 12 19 // Not counting empty lines
switch需要更多代码才能得到相同的结果,因此必须被视为比if-else 更不易读。
在你开始争论开关看起来更像是一个表而不是if-else之前,那就是代码格式化,而且无关紧要。 标准中没有任何内容阻止您编写如下代码:
if (A) {} else if (B) {} else if (C) {} else {} switch(something) { case A: { break; } case B: { break; } case C: { break; } default: { break; } }
如果你想要某种类似于表格的最小语法,我会认为这两种forms都是可读的。
当然可能有美学,迷信或宗教的原因,为什么switch
应该用于可读性,但我宁愿将这种偏离主题的讨论留给非编程相关的网站。
因此,与if-else相比,switch的安全性和可读性更低。 那些可能对程序员有吸引力的是效率。 一个程序员有n个必须由程序测试的情况肯定会有一些能够尽快搜索正确案例的东西。 以线性方式检查所有案例是一个坏主意。
您可能知道,通过将其实现为函数指针数组,可以优化if-else或切换很多:
typedef void (*Func_t)(void); const Func_t cases [N] = { ... }; cases[i]();
这种激烈的优化通常正是编译器在遇到switch语句时所做的事情。 但是,只有在所有情况都是相邻整数的情况下才能进行此优化。 如果它们不相邻,则可以通过const查找表创建邻接。
但如果案例不是整数类型,则无法进行上述优化。 如果它们是浮点数,字符串或其他东西,则没有合理的方法来优化代码。
所以至少在我的书中, switch
只是出于这个目的而存在:它使编译器更容易创建比if-else更有效的代码。 因此,它与关键字inline,register等属于同一类别,这也使编译器更容易优化代码。
浮点值通常不能直接比较
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") /* ... */;
)
好吧,由于舍入错误,浮点值的比较不可靠,并且C默认不支持字符串比较(仅限函数strcmp,例如)
– >无法通过编译器自动确定比较方法。
简答题是整数类型易于比较,而且比较非常快。 C中的浮点类型无法可靠地进行比较。 我不认为C有String类型,但字符串比较慢……你将不得不比较字符数组……慢。 我相信有人会给你一个更完整,更科学的答案。
您必须考虑“如何将此C代码转换为汇编?”。
切换条件只是某种棘手的JMP指令,顺便说一下需要在编译之前对这些情况进行排序(我猜编译器会对你的情况进行排序),但我不太确定。
例如,在PHP中,您可以使用字符串切换{},这可能会使用某种二分法搜索(它首先查找第一个字符等),就像地图一样。
你必须要了解编译语言是产生“相当不错”的汇编代码的一种方式,但这并不意味着它比你刚刚在汇编中完成你的程序更好。
使用像C或C ++这样的语言,你将快速编写一个程序,你的编译器将能够进行微不足道的优化,但是出于上帝的缘故,在使用编程语言时要保持温和,考虑一下你的程序的大图,不要忘记语言只是工具,它们并不神奇:如果你忘记了基本的低级行为,你就搞砸了。
当存在离散数量的选项时,开关可能是最佳选择。 编译器可以警告您重复的情况,如果您使用枚举,良好的编译器将警告未处理的值。
作为一个好的做法,浮动/双打不应该测试相等,“如果(f = 3.141516)”是一个令人头疼的邀请,“const float kEpsilon = 1e-5;” 然后使用“if(fabs(f – 3.141516)
我们不能在开关盒中使用float。 这是因为花车不精确。 你永远不知道这个数字究竟是什么。