天天看點

語言設計之控制流

  順序機制分為八個主要類别:順序執行、選擇、疊代、過程抽象、遞歸、并發、異常處理和推斷、非确定性。

  疊代:反複執行一段給定代碼,或者是執行一定次數,或者是執行到某個運作時條件成立。疊代結構包括for/do、while/repeat循環。

  1 表達式求值

  表達式:一個表達式或者是一個簡單對象,或者是應用于一組運算對象或參數的一個運算符或函數,而這些運算對象和參數也是表達式。

  1.1 優先級和結合性

  在任何特定語言中,在各種不同求職順序之間的選擇,依賴于運算符的優先級和結合性。在字首和字尾記法中沒有優先級和結合性的問題。多使用括号吧,少年!

  1.2 指派

  指令式語言中的計算通常是通過對記憶體中變量值的順序的一系列修改而完成的。指派提供了完成這種修改的最基本手段。

  一般而言,如果程式設計語言中的一種結構除了傳回由外圍上下文所用的值之外,還能以其他方式影響後續計算,我們就說這種結構有副作用。指派也許是最基本的副作用:雖然有時對指派的求值過程本身也許會得到一個值,但是我們真正關心的卻是它改變了一個變量的值,進而會影響任何用到該變量的後續計算的結果。

  大多數指令式語言都嚴格區分表達式和語句。前者總産生一個,但可能有也可能沒有副作用;而後者的執行就是為了産生副作用,并不傳回任何有用的值。

  變量的值模型和引用模型,前者認為變量是值的命名容器,後者認為變量是對值的命名引用。

  1.3 初始化

  由于已經提供了一種為變量設定值的結構(指派語句),是以并不是所有的指令式語言中都提供在聲明變量時指定初始值的方式。提供初始值的機制是有益處的:1)子程式的局部靜态變量需要一個初始值;2)對于靜态配置設定的變量,在聲明的上下文中指定初始值可以由編譯器放入全局記憶體,進而避免了在運作時賦予初始值造成的開銷;3)意外的使用未初始化的的變量,是一種最常見的程式設計錯誤。

  初始化隻是對那些靜态配置設定的變量節約時間,對運作中在棧和堆中配置設定的變量隻能等到運作時進行初始化。使用未初始化變量的問題不僅出現在變量加工之後,也可能出現在變量原有值被破壞,但卻沒有為它提供新值的操作之後。

  如果聲明的變量沒有明确的被賦予初始值,有些語言也可以指定一個預設(在某些場景下)。

  語言實作可以對未初始化變量的使用作為動态語義錯誤,在運作時捕捉這種錯誤。優點是能夠找出程式中的錯誤,這些錯誤可能由于存在預設值而被掩蓋或者糾正了。不幸的是,在大多數機器上,對于大多數語言而言,在運作時捕捉所有使用未初始化變量情況的代價非常的高。動态檢查需要給每一個值配置設定額外空間記錄是否初始化,每次加工變量,修改對應的初始化辨別。

  C++小心地區分了初始化和指派。它将初始化解釋為調用變量所屬類型的構造函數,以初始值作為參數。在沒有強制的情況下,指派被解釋為調用相關類型的指派運算符,而如果沒有定義這種運算符,則将指派操作右部的值簡單的按位指派過來。對于需要執行特定的儲存管理操作的使用者定義的抽象類型而言,區分初始化和指派尤為重要。例如長度可變的字元串,指派在為新枝配置設定空間之前釋放該字元串的原值所占的空間。初始化隻需要配置設定空間。

  1.4 表達式中的順序問題

  雖然優先級和結合性規則定義了二進制中綴運算符的應用順序,但它們沒有明确說明給定運算符的各個運算對象的求值順序。(參數的求值順序也是不确定的)

  求值順序的重要性的原因:1)副作用:如果其中一個運算對象會修改另一個運算對象的值,二者求值的先後順序影響表達式的值。 2)代碼改進:子表達式的求值順序對寄存器的配置設定及指令的次序安排都有影響。在表達式a*b + f(c)中,或許希望先調用f,因為如果先計算a*b,那麼在調用f期間就需要儲存這個乘積,而f也許希望使用所有的寄存器。

  1.5 短路求值(驟死性)

  針對布爾表達式,當後續表達式的結果不影響整個表達式的結果時不計算後續表達式的值,在某些情況下可以節約大量的時間。

  2 結構化和非結構化的流程

  goto被抛棄了,結構化的控制流語句可以完全替代goto的功能,而且使得代碼更加簡潔美觀。

  3 順序執行

  除非那些不提供傳回值的子表達式有副作用,否則這種順序執行是沒有任何作用的。

  即使在指令式語言中,人們對某些副作用的價值也有争論。無副作用的性質保證了該語言的函數是幂等的:如果使用同一組參數重複的調用,則總會傳回同樣的值。此外,一個子表達式中的函數調用不會對其他子表達式中的函數調用不會對其他子表達式的求值結果造成影響。可惜的是有些情況下人們特别希望函數可以有副作用,比如僞随機數生成器的典型借口,使用者希望每次調用時傳回不同的值。

  4 選擇

  if else;

  條件是一個布爾表達式,但通常沒有必要求出這個表示的值存入寄存器。大多數機器都提供了條件分支指令,用于利用簡單比較的結果。

  case/switch語句版本确實比對應的if/else版本更簡短一些,但還不是程式設計語言提供case語句的最主要動力。最主要的動力是為了生成效率更高的目标代碼。對于case語句,我們并不是順序地用其表達式與一系列可能的值比較,而是要計算出一個位址,以便通過一條跳轉指令轉調過來。在彙編層面生層轉跳表(位址表)。

  線性跳轉表的速度很快。當整個跳轉表标号密集且不包含很大的區間時,這種方式很有效。計算分支位址其他方式包括順序檢查,散列和折半查找等。

  5 疊代

  疊代和遞歸是使計算機能夠重複執行一系列類似操作的兩種機制。指令式語言的程式員更傾向于使用疊代而不是遞歸。大多數語言中的疊代都以循環的形式出現。循環有兩種變種:枚舉控制的循環、邏輯控制的循環。

  6 遞歸

  有時有人說疊代比遞歸效率更高。更準确的說法應該是,疊代的樸素實作的效率通常比遞歸的樸素實作高一些。如果遞歸的實作确實通過實實在在的子程式調用,在運作棧商為局部變量和簿記資訊配置設定空間,那麼疊代實作的效率就會比遞歸高一些。然而一個“優化”編譯器,特别是專門為函數式語言設計的編譯器,常常能為遞歸生成優異的代碼,尤其是為尾遞歸生成優異的代碼。尾遞歸:在遞歸調用之後再也沒有其他計算的函數,其傳回值就是遞歸調用的傳回值。即使是那些非尾遞歸的函數,通常通過很簡單的變換就可以産生尾遞歸代碼。