switch case 語句內部變數定義
switch case語句是非常常用的語句,入門的碼農也知道是做什麼的。
但關於switch case內定義變數的問題,網上的很多博文都有謬誤,在這裡我寫一下對這個語句的瞭解。
一
先看合法的定義方式:
int main(int argc, const char * argv[]) { int idx = 2; switch (idx) { int k; case 1: int j; break; case 2: k = 1; j = 2; std::cout<<"K:"<<k<<std::endl; std::cout<<"J:"<<j<<std::endl; break; default: break; } return 0; }
在C++11 std Dialect下 打印出的結果是:
K:1
J:2
二
然後看看不合法的寫法:
int main(int argc, const char * argv[]) { int idx = 2; switch (idx) { int k = 1; case 1: int j = 1; break; case 2: k = 1; j = 2; std::cout<<"K:"<<k<<std::endl; std::cout<<"J:"<<j<<std::endl; break; default: break; } return 0; }
編譯無法通過。
三
解釋下原因:
一為什麼合法?
首先一個變數有沒有被定義是在編譯時就檢查過的,因此這裡可以編譯通過。
其次在處理switch case語句中,C++11標準的編譯器都會在執行case跳轉前為變數分配空間,因此執行也沒有問題。
(當然我並不建議在case語句外定義變數,因為何時為變數分配空間是編譯器特性,而非語言特性,雖然這個問題中遵守C++11標準的編譯器沒有問題)
二為什麼不合法?
C++11標準禁止這種寫法。在編譯時,編譯器就會報錯。C++11禁止這種寫法的原因來自於C++的一個規定:
It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps from a point where a local variable
with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has POD type (3.9) and is declared without an initializer
眾所周知,C++不允許使用未初始化的變數,而初始化操作和定義變數對於編譯器來說是兩碼事,初始化操作是一個確確實實的在執行時才會被呼叫的語句,是可以被case跳轉遮蔽掉的語句,而定義則是在編譯器就完成檢查的。
如果二的寫法合法的話,那麼會發生這樣的問題:
編譯時定義變數被執行,初始化和賦值語句也用各自的方式編譯完成,並且在執行時,程式也的確為此變數分配了空間。但是在執行時,初始化語句卻在某些情況下被跳轉掉了。
雖然對於int等型別的變數,賦值和初始化在我們看來都是"=",但編譯後的二進位制是完全不同的,編譯器會在編譯時把"int j = 1;"編譯成初始化,而把"j = 1;"編譯成賦值。因此在整個執行過程中,如果初始化語句被case跳轉掉了,我們在其他case語句中的"="不可能對此變數進行初始化。
這時,如果在整個switch(){}的定義域中,有對此變數的呼叫,那就是『試圖在初始化一個變數前使用它』,因此C++11禁止了這種寫法。
四
額外談一些:
如果不在case中加上{},整個switch(){}都是使用同一個作用域,這一點通過上文符合語法例子中對J、K的定義和使用就能看出來。最好的方法就是在編碼時,將整個switch語句用到的變數在switch外宣告,並且針對某個case需要單獨使用變數的情況,用{}明確此case語句的作用域
case的實現與goto非常相似,case的本質是一種標籤,在switch case語句中變數的定義問題可以推廣到goto語句中。
下面的程式碼也是不合法的,因為string是一個類,類有自己的隱式初始化方法,實際上這依然是個可能被跳轉掉的初始化語句。
case 1:
std::string tempStr;
break;