C++性能剖析教程之switch語(yǔ)句
前言
幾乎每本面向初學(xué)者的C語(yǔ)言或C++書籍在前面兩章都會(huì)提到分支控制語(yǔ)句if……else和switch……case,在某些情況下這兩種分支控制語(yǔ)句可以互相替換,但卻很少有人去深究在if……else和switch……case語(yǔ)句的背后到底有什么異同?應(yīng)該選擇哪一個(gè)語(yǔ)句才能使得效率最高?要回答這些問題,只能走到switch語(yǔ)句的背后,看看這些語(yǔ)句到底是怎么實(shí)現(xiàn)的。
基本格式
switch語(yǔ)句的基本格式如下:
switch (表達(dá)式) {
case 常量表達(dá)式1:《語(yǔ)句序列1》《break;》 //《》中的內(nèi)容可省
……
case 常量表達(dá)式n:《語(yǔ)句序列n》《break;》 //同上,下同
《default:語(yǔ)句序列》
}
其中:
- 表達(dá)式——稱為“條件表達(dá)式”,用作判斷條件,取值為整型、字符型、布爾型或枚舉型。
- 常量表達(dá)式——由常量構(gòu)成,取值類型與條件表達(dá)式相同。
- 語(yǔ)句序列——可以是一個(gè)語(yǔ)句也可以是一組語(yǔ)句。
if語(yǔ)句與switch語(yǔ)句
相信學(xué)過C/C++的同學(xué)對(duì)這兩個(gè)語(yǔ)句的異同早就了如指掌,if語(yǔ)句作為條件判斷,滿足條件進(jìn)入if語(yǔ)句塊,不滿足條件則進(jìn)入else語(yǔ)句塊,而且if和else語(yǔ)句塊又可以繼續(xù)嵌套if語(yǔ)句。switch則是通過判斷一個(gè)整型表達(dá)式的值來(lái)決定進(jìn)入到哪一個(gè)case語(yǔ)句中,如果所有case條件都不滿足則進(jìn)入到default語(yǔ)句塊。
//簡(jiǎn)單的if語(yǔ)句 if (a == 1) i = 1; else if (a == 2) i = 2; else i = 3;
//簡(jiǎn)單的switch語(yǔ)句 switch (a){ case 1: i = 1; case 2: i = 2; default: i = 3; }
編譯器如何實(shí)現(xiàn)switch語(yǔ)句?
現(xiàn)在編譯器已經(jīng)足夠智能和強(qiáng)大,經(jīng)過測(cè)試,g++實(shí)現(xiàn)switch語(yǔ)句的方式就至少有三種,編譯器會(huì)根據(jù)代碼的實(shí)際情況,權(quán)衡時(shí)間效率和空間效率,去選擇一個(gè)對(duì)當(dāng)前代碼而言綜合效率最高的一種。
編譯器實(shí)現(xiàn)switch語(yǔ)句的三種方式:
- 逐條件判斷
- 跳轉(zhuǎn)表
- 二分查找法
后面我們將就這三種實(shí)現(xiàn)方法適用的代碼場(chǎng)景進(jìn)行測(cè)試和分析。
1. 逐條件判斷法
逐條件判斷法其實(shí)就是和if……else語(yǔ)句的匯編實(shí)現(xiàn)相同,編譯器把switch語(yǔ)句中各個(gè)case條件逐個(gè)進(jìn)行判斷,直到找到正確的case語(yǔ)句塊。這種方法適用于switch語(yǔ)句中case條件很少的情況,即使逐個(gè)條件判斷也不會(huì)導(dǎo)致大量時(shí)間和空間的浪費(fèi),比如下面這段代碼:
#include <algorithm> int test_switch(){ int i ; int a = std::rand(); switch(a){ case 0: i = 0;break; case 1: i = 1;break; case 2: i = 2;break; default: i = 3;break; } return i; }
該代碼對(duì)應(yīng)的匯編代碼如下:
movl -4(%rbp), %eax cmpl $1, %eax je .L3 cmpl $2, %eax je .L4 testl %eax, %eax jne .L8 movl $0, -8(%rbp) jmp .L6 .L3: movl $1, -8(%rbp) jmp .L6 .L4: movl $2, -8(%rbp) jmp .L6 .L8: movl $3, -8(%rbp) nop
eax寄存器存儲(chǔ)的是判斷條件值(對(duì)應(yīng)于C++代碼中的a值),首先判斷a是否等于1,如果等于1則跳轉(zhuǎn)到.L3執(zhí)行a==1對(duì)應(yīng)的代碼段,然后判斷a是否等于2,如果等于2則跳轉(zhuǎn)到.L4執(zhí)行a==2對(duì)應(yīng)的代碼段……可能難理解的是第6行代碼testl %eax, %eax,其實(shí)這只是編譯器提高判斷一個(gè)寄存器是否為0效率的一個(gè)小技巧,如果eax不等于0則跳轉(zhuǎn)到.L8代碼段,執(zhí)行default代碼段對(duì)應(yīng)的代碼,如果eax等于0則執(zhí)行a==0對(duì)應(yīng)的代碼段。
由上面對(duì)編譯器生成匯編代碼的分析,我們可以發(fā)現(xiàn):編譯器在這種情況下使用逐個(gè)條件判斷來(lái)實(shí)現(xiàn)switch語(yǔ)句。
2. 跳轉(zhuǎn)表實(shí)現(xiàn)法
在編譯器采用這種switch語(yǔ)句實(shí)現(xiàn)方式的時(shí)候,會(huì)在程序中生成一個(gè)跳轉(zhuǎn)表,跳轉(zhuǎn)表存放各個(gè)case語(yǔ)句指令塊的地址,程序運(yùn)行時(shí),首先判斷switch條件的值,然后把該條件值作為跳轉(zhuǎn)表的偏移量去找到對(duì)應(yīng)case語(yǔ)句的指令地址,然后執(zhí)行。這種方法適用于case條件較多,但是case的值比較連續(xù)的情況,使用這種方法可以提高時(shí)間效率且不會(huì)顯著降低空間效率,比如下面這段代碼編譯器就會(huì)采用跳轉(zhuǎn)表這種實(shí)現(xiàn)方式:
#include <algorithm> int test_switch(){ int i ; int a = std::rand(); switch(a){ case 0: i = 0;break; case 1: i = 1;break; case 2: i = 2;break; case 3: i = 3;break; case 4: i = 4;break; case 5: i = 5;break; case 6: i = 6;break; case 7: i = 7;break; case 8: i = 8;break; case 9: i = 9;break; default: i = 10;break; } return i; }
該代碼對(duì)應(yīng)的匯編代碼如下:
movl -4(%rbp), %eax movq .L4(,%rax,8), %rax jmp *%rax .L4: .quad .L3 .quad .L5 .quad .L6 .quad .L7 .quad .L8 .quad .L9 .quad .L10 .quad .L11 .quad .L12 .quad .L13 .text .L3: movl $0, -8(%rbp) jmp .L14 .L5: movl $1, -8(%rbp) jmp .L14 #后面省略……
在x64架構(gòu)中,eax寄存器是rax寄存器的低32位,此處我們可以認(rèn)為兩者值相等,代碼第一行是把判斷條件(對(duì)應(yīng)于C++代碼中的a值)復(fù)制到eax寄存器中,第二行代碼是把.L4段偏移rax寄存器值大小的地址賦值給rax寄存器,第三行代碼則是取出rax中存放的地址并且跳轉(zhuǎn)到該地址處。我們可以清楚的看到.L4代碼段就是編譯器為switch語(yǔ)句生成的存放于.text段的跳轉(zhuǎn)表,每種case均對(duì)應(yīng)于跳轉(zhuǎn)表中一個(gè)地址值,我們通過判斷條件的值即可計(jì)算出來(lái)其對(duì)應(yīng)代碼段地址存放的地址相對(duì)于.L4的偏移,從而實(shí)現(xiàn)高效的跳轉(zhuǎn)。
3. 二分查找法
如果case值較多且分布極其離散的話,如果采用逐條件判斷的話,時(shí)間效率會(huì)很低,如果采用跳轉(zhuǎn)表方法的話,跳轉(zhuǎn)表占用的空間就會(huì)很大,前兩種方法均會(huì)導(dǎo)致程序效率低。在這種情況下,編譯器就會(huì)采用二分查找法實(shí)現(xiàn)switch語(yǔ)句,程序編譯時(shí),編譯器先將所有case值排序后按照二分查找順序?qū)懭雲(yún)R編代碼,在程序執(zhí)行時(shí)則采二分查找的方法在各個(gè)case值中查找條件值,如果查找到則執(zhí)行對(duì)應(yīng)的case語(yǔ)句,如果最終沒有查找到則執(zhí)行default語(yǔ)句。對(duì)于如下C++代碼編譯器就會(huì)采用這種二分查找法實(shí)現(xiàn)switch語(yǔ)句:
#include <algorithm> int test_switch(){ int i ; int a = std::rand(); switch(a){ case 4: i = 4;break; case 10: i = 10;break; case 50: i = 50;break; case 100: i = 100;break; case 200: i = 200;break; case 500: i = 500;break; default: i = 0;break; } return i; }
改代碼段對(duì)應(yīng)的匯編代碼為:
movl -4(%rbp), %eax cmpl $50, %eax je .L3 cmpl $50, %eax jg .L4 cmpl $4, %eax je .L5 cmpl $10, %eax je .L6 jmp .L2 .L4: cmpl $200, %eax je .L7 cmpl $500, %eax je .L8 cmpl $100, %eax je .L9 jmp .L2
代碼第二行條件值首先與50比較,為什么是50而不是放在最前面的4?這是因?yàn)槎植檎沂紫炔檎业氖翘幱谥虚g的值,所以這里先與50進(jìn)行比較,如果eax等于50,則執(zhí)行case
50對(duì)應(yīng)代碼,如果eax值大于50則跳轉(zhuǎn)到.L4代碼段,如果eax小于50則繼續(xù)跟4比較……直至找到條件值或者查找完畢條件值不存在??梢钥闯龆植檎曳ㄔ诒3至溯^高的查詢效率的同時(shí)又節(jié)省了空間占用。
總結(jié)
何時(shí)應(yīng)該使用if……else語(yǔ)句,何時(shí)應(yīng)該使用switch……case語(yǔ)句?
通過上面的分析我們可以得出結(jié)論,在可能條件比較少的時(shí)候使用if……else和switch……case所對(duì)應(yīng)的匯編代碼是相同的,所以兩者在性能上是沒有區(qū)別的,使用哪一種取決于個(gè)人習(xí)慣。如果條件較多的話,顯而易見switch……case的效率更高,無(wú)論是跳轉(zhuǎn)表還是二分查找都比if……else的順序查找效率更高,所以在這種情況下盡量選用switch語(yǔ)句來(lái)實(shí)現(xiàn)分支語(yǔ)句。當(dāng)然如果我們知道哪種條件出現(xiàn)的概率最高,我們可以將這個(gè)條件放在if判斷的第一個(gè),使順序查找提前結(jié)束,這時(shí)使用if……else語(yǔ)句也可以達(dá)到較高的運(yùn)行效率。
switch語(yǔ)句也有他本身的局限性,即switch語(yǔ)句的值只能為整型,比如當(dāng)我們需要對(duì)一個(gè)double型數(shù)據(jù)進(jìn)行判斷時(shí),便無(wú)法使用switch語(yǔ)句,這時(shí)只能使用if……else語(yǔ)句來(lái)實(shí)現(xiàn)。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)我們的支持。
上一篇:基于VC 6.0使用C語(yǔ)言實(shí)現(xiàn)俄羅斯方塊
欄 目:C語(yǔ)言
下一篇:C++控制臺(tái)實(shí)現(xiàn)俄羅斯方塊游戲
本文標(biāo)題:C++性能剖析教程之switch語(yǔ)句
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/767.html
您可能感興趣的文章
- 04-02c語(yǔ)言沒有round函數(shù) round c語(yǔ)言
- 01-10深入理解C++中常見的關(guān)鍵字含義
- 01-10使用C++實(shí)現(xiàn)全排列算法的方法詳解
- 01-10c++中inline的用法分析
- 01-10用C++實(shí)現(xiàn)DBSCAN聚類算法
- 01-10全排列算法的非遞歸實(shí)現(xiàn)與遞歸實(shí)現(xiàn)的方法(C++)
- 01-10C++大數(shù)模板(推薦)
- 01-10淺談C/C++中的static與extern關(guān)鍵字的使用詳解
- 01-10深入C/C++浮點(diǎn)數(shù)在內(nèi)存中的存儲(chǔ)方式詳解
- 01-10深入理解C/C++混合編程


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02c語(yǔ)言函數(shù)調(diào)用后清空內(nèi)存 c語(yǔ)言調(diào)用
- 04-02func函數(shù)+在C語(yǔ)言 func函數(shù)在c語(yǔ)言中
- 04-02c語(yǔ)言的正則匹配函數(shù) c語(yǔ)言正則表達(dá)
- 04-02c語(yǔ)言用函數(shù)寫分段 用c語(yǔ)言表示分段
- 04-02c語(yǔ)言中對(duì)數(shù)函數(shù)的表達(dá)式 c語(yǔ)言中對(duì)
- 04-02c語(yǔ)言編寫函數(shù)冒泡排序 c語(yǔ)言冒泡排
- 04-02c語(yǔ)言沒有round函數(shù) round c語(yǔ)言
- 04-02c語(yǔ)言分段函數(shù)怎么求 用c語(yǔ)言求分段
- 04-02C語(yǔ)言中怎么打出三角函數(shù) c語(yǔ)言中怎
- 04-02c語(yǔ)言調(diào)用函數(shù)求fibo C語(yǔ)言調(diào)用函數(shù)求
隨機(jī)閱讀
- 01-10delphi制作wav文件的方法
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 04-02jquery與jsp,用jquery
- 01-10C#中split用法實(shí)例總結(jié)
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改