精解C++的switch语句
?入門書籍對switch語句的介紹相對較淺,我也因此而產生了很多想當然的誤解。為解惑而寫了以下一小篇精解switch語句,相信會對很多朋友有所幫助,同時順便補充一些相關知識。
先拋出個題目,見下程序:
//原代碼出自《C語言參考手冊(原書第5版)》
//為了表達我的意圖,特做了部分改動
switch(x)
{
??? default:
??? if(prime(x))
??? {
?????? case 2: case 3: case 5: case 7:
?????????? process_prime(x);
??? }
??? else
??? {
?????? case 4: case 6: case 8: case 9: case 10:
?????????? process_composite(x);
??? }
}
你能說出它如何執行嗎?
switch語句的格式為:
switch(條件)語句
其中,條件的類型可以是整數類型,枚舉類型,或者類類型(但該類需要有單一的轉換到整數類型或(可以是字符類型,但不能是浮點類型、字符串、指針類型等),語句部分不一定非得是一條復合語句。因此,switch("123"[2]+(int)3.1);是條合法的switch語句,switch(j)case 5:i++;也是條合法的switch語句。如果switch的語句部分是一條非復合語句,則其內定義的變量作用域,效果上等同于該條語句加上了{}。如int i=3;switch(i)int i=4;,相當于int i=3;switch(i){int i=4;},因此這并不會導致同一局部域下的重復定義錯誤。
如果條件為類類型,則該類內要有一個用戶定義的類型轉換操作符重載函數。如下邊代碼:
#include <iostream>
using namespace std;
class CTest
{
public:
??? operator int(){cout<<"int"<<endl;return data;}
??? operator char(){cout<<"int"<<endl;return static_cast<char>(data);}
??? CTest(int i):data(i){}
??? CTest(char c):data(c){}
private:
??? int data;
};
int main()
{
??? CTest x1(3);
??? CTest x2('5');
??? switch(x1)
??? {
??? case '5':break;
??? case 3:break;
??? }
??? return 0;
}
在VC++6下,編譯器會報如下錯誤信息:
error C2450: switch expression of type 'class CTest' is illegal Ambiguous user-defined-conversion
因為類型轉換函數有兩個都是整數類型,編譯器無法判斷該去調用哪一個進行轉換。
條件也可以是int i=3這樣的初始化,其結果就是i的值。其作用域從聲明處開始,直至switch語句的結束??梢岳斫獬稍趕witch外再加上了{}。int i=3,j=4這樣的多個初始化,在C++標準中,不是條件。
語句部分,可以出現多個case標號以及一個default標號,它們的出現順序隨意。一個case標號或default標號,與屬于其上層最近的switch語句,如:
switch(i)
{
case 1:
case 2:
??? if(a>5)
??? {
case 3:
case 4:
?????? b=4;
?????? switch(j)
?????? {
?????? case 5:
?????? default:;
?????? }
default:;
??? }
}
中,case 3:、case 4:,以及最后一個default:,屬于外層switch,雖然它們在if語句內。注意在上面的代碼中,}前的最后一個標號,后面至少要出現一條語句,因此如果沒有內容的情況下,也至少要以一個空語句;作為結束。
case標號后為一個整數類型的常量表達式,因此int i=3;switch(i){case 3:;}合法,而int i=3;switch(3){case i:;}不合法,因為case i:的i不是個靜態表達式。如果將int i=3;換成const int i=3;則后者在C++中就合法了,但在C中仍然不合法。原因是C和C++對const的處理不同,在C中,const限定的量是不能直接去修改的,但它本身并不是常量表達式;在C++中,const限定的量,如果其值能在編譯時確定,則其可出現在必須使用常量表達式之處。
同一個switch的各個case標號的值不能夠相互重復。要注意的是,case標號在實現中是有上限的:C89標準要求至少257個,這保證了ASCII被switch列舉一遍。
雖然要求case標號是常量表達式,看起來似乎不是很零活方便(比如對比VB的Select Case),但是這樣的設計可以保證更高的效率,而效率則是C和C++最為看重的因素。因為case標號的值是編譯時可確定的整數類型,又因為其不可有重復,因此編譯器可以進行優化。比如以下代碼:
switch(b)
{
case 0: ...
case 1: ...
...
case 255: ...
}
不用被翻譯成
if(b==0)
...
else if(b==1)
...
else if(b==255)
...
這樣當b為255的情況,將最慢被執行到。而如果編譯器對256個數字進行了優化,它可以根據比較的頻率及重要性,產生這樣的代碼:
if(b<128)
{
??? if(b<64)
??? ...
}
else
{
??? if(b<192)
??? ...
}
這樣的折半法,會減少每個比較經歷的步數,在一個多層循環中的switch語句的執行效率會因此而得到提高。當然,前提是你要知道什么時候適合用它比較好,如命令行參數的解析。如果條件相對復雜,就使用if else而不是switch。
對switch比較常見的誤解,就是把switch理解成if,把:
switch(b)
{
case 1: ... ;break;
case 2: ... ;break;
case 3: ... ;break;
default: ... ;break;
}
理解成它其實就是:
if(b==1)
{
??? ...
}
else if(b==2)
{
??? ...
}
else if(b==3)
{
??? ...
}
else
{
??? ...
}
沒錯,以上兩段代碼執行的效果的確等價,但是:
switch(b)
{
case 1: ...
case 2: ...
case 3: ...
default: ...
}
并不等價于:
if(b==1)
{
??? ...
}
else if(b==2)
{
??? ...
}
else if(b==3)
{
??? ...
}
else
{
??? ...
} //如果代碼如此生成,則不僅不使用break的分支要多生成一條goto,而且對條件值的比較,也分散到了多個地方,低效!
如果每個標號后面,沒有加break,則switch代碼的執行會發生下落。上面的switch代碼應該理解成:
//偽代碼
{
??? if(b==1)goto case 1;
??? else if(b==2)goto case 2;
??? else if(b==3)goto case 3;
??? else goto default;
??? goto end_of_switch //如果沒有default的話
??? {
??? case 1: ...
??? case 2: ...
??? case 3: ...
??? default: ...
??? end_of_switch:
??? }
}
而switch語句內最外層的break本身,作用就相當于偽代碼的goto end_of_switch。這解釋了各標號后的代碼執行順序,進而明白下落的具體原理。它也解釋了標號順序為何可以隨意。
switch的執行方式:switch對條件求值,然后對所有的case標號(如果有的話)進行比較(可能被優化過比較順序,或者被比較的條件值一直放在某個寄存器中,用CMP這樣的匯編指令以提高效率),符合哪個就像goto一樣跳到指定的標號。如果沒成功,就會去goto default。如果程序沒寫default,則跳到switch語句下一條的位置。case標號和default標號,本身與普通標號類似,并不會在其出現處,設置else if進行條件判斷,因此也不會影響下落。比較并轉移到相應標號的代碼,可以理解成完全是switch(條件)自己一句所生成的。
回到文章開始的那個題目,我們將其轉換成對應的偽代碼:
{
??? if(x==2)goto case 2; //實際匯編代碼可能做優化
??? else if(x==3)goto case 3;
??? else if(x==5)goto case 5;
??? else if(x==7)goto case 7;
??? else if(x==4)goto case 4;
??? else if(x==6)goto case 6;
??? else if(x==8)goto case 8;
??? else if(x==9)goto case 9;
??? else if(x==10)goto case 10;
??? else goto default;
??? goto end_of_switch; //此句無用,有default時無產生的必要
??? {
??? default:
??????? if(!prime(x))goto part_of_else;//為了方便理解,將if語句也轉換了
??? case 2:
??? case 3:
??? case 5:
??? case 7:
?????????? process_prime(x);
??????? goto end_of_if;
??????? part_of_else:
??? case 4:
??? case 6:
??? case 8:
??? case 9:
??? case 10:
??????????? process_composite(x);
??????? end_of_if:;
??? end_of_switch:;
??? }
}
該程序的if部分,用prime()函數來求x值是否為質數,如果是則調用process_prime(),否則去調用process_composite()。但是,其他部分的因素,在于大多數情況下,要判斷的數<=10。如果對<=10的情況,也要完全靠調用prime(),則無疑會降低效率。為此,該switch會先行對2至10范圍內的情況進行判斷,并跳轉相應位置進行處理。如果不在2至10范圍的,則跳轉到default,往下再調用prime進行判斷。
?????? 當switch(x)int i=3;
最后要強調的問題是,C++中goto不能從前往后跳過變量定義,因此switch內出現的變量定義語句,最好放在復合語句{}中包起來。
請看以下代碼:
switch(x)
{
case 0: f0(); break;
case 1:CTest i(3); f1(); break;
default: fdef();
}
其偽代碼為:
{
??? if(x==0)goto case 0;
??? else if(x==1)goto case 1;
??? else goto default;
??? {
??? case 0:
?????? f0();goto end_of_switch;
??? case 1:
?????? CTest i(3);
?????? f1();
?????? goto end_of_switch;
??? default:
?????? fdef();
??? end_of_switch:
?????? CTest::~CTest();
??? }
}
當x為0或1時,不會有問題,問題是當轉到default時,相當于從外層直接跳過了i的構造,然后在default自己的代碼執行完畢后,還要執行i的析構。改成:
switch(x)
{
case 0: f0(); break;
case 1:{CTest i(3); f1(); break;}
default: fdef();
}
則i的作用域被限制在{}中,不會再出現未構造就析構的問題。
?
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/myliupp/archive/2009/08/07/4420792.aspx
總結
以上是生活随笔為你收集整理的精解C++的switch语句的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ASP中怎么实现SQL数据库备份、恢复!
- 下一篇: C++中引用和指针的不同