表達式編譯優化
A. 正則表達式優化
^\[(?:(?:\d,)*\d)*\]$
這樣不會有捕獲組
B. 如何優化你的C代碼
一、程序結構的優化
1、程序的書寫結構
雖然書寫格式並不會影響生成的代碼質量,但是在實際編寫程序時還是應該尊循一定的書寫規則,一個書寫清晰、明了的程序,有利於以後的維護。在書寫程序時,特別是對於While、for、do…while、if…elst、switch…case等語句或這些語句嵌套組合時,應採用「縮格」的書寫形式,
2、標識符
程序中使用的用戶標識符除要遵循標識符的命名規則以外,一般不要用代數符號(如a、b、x1、y1)作為變數名,應選取具有相關含義的英文單詞(或縮寫)或漢語拼音作為標識符,以增加程序的可讀性,如:count、number1、red、work等。
3、程序結構
c語言是一種高級程序設計語言,提供了十分完備的規范化流程式控制制結構。因此在採用C語言設計單片機應用系統程序時,首先要注意盡可能採用結構化的程序設計方法,這樣可使整個應用系統程序結構清晰,便於調試和維護。於一個較大的應用程序,通常將整個程序按功能分成若干個模塊,不同模塊完成不同的功能。各個模塊可以分別編寫,甚至還可以由不同的程序員編寫,一般單個模塊完成的功能較為簡單,設計和調試也相對容易一些。在C語言中,一個函數就可以認為是一個模塊。所謂程序模塊化,不僅是要將整個程序劃分成若干個功能模塊,更重要的是,還應該注意保持各個模塊之間變數的相對獨立性,即保持模塊的獨立性,盡量少使用全局變數等。對於一些常用的功能模塊,還可以封裝為一個應用程序庫,以便需要時可以直接調用。但是在使用模塊化時,如果將模塊分成太細太小,又會導致程序的執行效率變低(進入和退出一個函數時保護和恢復寄存器佔用了一些時間)。
4、定義常數
在程序化設計過程中,對於經常使用的一些常數,如果將它直接寫到程序中去,一旦常數的數值發生變化,就必須逐個找出程序中所有的常數,並逐一進行修改,這樣必然會降低程序的可維護性。因此,應盡量當採用預處理命令方式來定義常數,而且還可以避免輸入錯誤。
5、減少判斷語句
能夠使用條件編譯(ifdef)的地方就使用條件編譯而不使用if語句,有利於減少編譯生成的代碼的長度,能夠不用判斷語句則少用判斷用語句。
6、表達式
對於一個表達式中各種運算執行的優先順序不太明確或容易混淆的地方,應當採用圓括弧明確指定它們的優先順序。一個表達式通常不能寫得太復雜,如果表達式太復雜,時間久了以後,自己也不容易看得懂,不利於以後的維護。
7、函數
對於程序中的函數,在使用之前,應對函數的類型進行說明,對函數類型的說明必須保證它與原來定義的函數類型一致,對於沒有參數和沒有返回值類型的函數應加上「void」說明。如果果需要縮短代碼的長度,可以將程序中一些公共的程序段定義為函數,在Keil中的高級別優化就是這樣的。如果需要縮短程序的執行時間,在程序調試結束後,將部分函數用宏定義來代替。注意,應該在程序調試結束後再定義宏,因為大多數編譯系統在宏展開之後才會報錯,這樣會增加排錯的難度。
8、盡量少用全局變數,多用局部變數。
因為全局變數是放在數據存儲器中,定義一個全局變數,MCU就少一個可以利用的數據存儲器空間,如果定義了太多的全局變數,會導致編譯器無足夠的內存可以分配。而局部變數大多定位於MCU內部的寄存器中,在絕大多數MCU中,使用寄存器操作速度比數據存儲器快,指令也更多更靈活,有利於生成質量更高的代碼,而且局部變數所的佔用的寄存器和數據存儲器在不同的模塊中可以重復利用。
9、設定合適的編譯程序選項
許多編譯程序有幾種不同的優化選項,在使用前應理解各優化選項的含義,然後選用最合適的一種優化方式。通常情況下一旦選用最高級優化,編譯程序會近乎病態地追求代碼優化,可能會影響程序的正確性,導致程序運行出錯。因此應熟悉所使用的編譯器,應知道哪些參數在優化時會受到影響,哪些參數不會受到影響。
在ICCAVR中,有「Default」和「Enable Code Compression」兩個優化選項。
在CodeVisionAVR中,「Tiny」和「small」兩種內存模式。
在IAR中,共有7種不同的內存模式選項。
在GCCAVR中優化選項更多,一不小心更容易選到不恰當的選項。
二、代碼的優化
1、選擇合適的演算法和數據結構
應該熟悉演算法語言,知道各種演算法的優缺點,具體資料請參見相應的參考資料,有很多計算機書籍上都有介紹。將比較慢的順序查找法用較快的二分查找或亂序查找法代替,插入排序或冒泡排序法用快速排序、合並排序或根排序代替,都可以大大提高程序執行的效率。.選擇一種合適的數據結構也很重要,比如你在一堆隨機存放的數中使用了大量的插入和刪除指令,那使用鏈表要快得多。
數組與指針語句具有十分密碼的關系,一般來說,指針比較靈活簡潔,而數組則比較直觀,容易理解。對於大部分的編譯器,使用指針比使用數組生成的代碼更短,執行效率更高。但是在Keil中則相反,使用數組比使用的指針生成的代碼更短。。
3、使用盡量小的數據類型
能夠使用字元型(char)定義的變數,就不要使用整型(int)變數來定義;能夠使用整型變數定義的變數就不要用長整型(long int),能不使用浮點型(float)變數就不要使用浮點型變數。當然,在定義變數後不要超過變數的作用范圍,如果超過變數的范圍賦值,C編譯器並不報錯,但程序運行結果卻錯了,而且這樣的錯誤很難發現。
在ICCAVR中,可以在Options中設定使用printf參數,盡量使用基本型參數(%c、%d、%x、%X、%u和%s格式說明符),少用長整型參數(%ld、%lu、%lx和%lX格式說明符),至於浮點型的參數(%f)則盡量不要使用,其它C編譯器也一樣。在其它條件不變的情況下,使用%f參數,會使生成的代碼的數量增加很多,執行速度降低。
4、使用自加、自減指令
通常使用自加、自減指令和復合賦值表達式(如a-=1及a+=1等)都能夠生成高質量的程序代碼,編譯器通常都能夠生成inc和dec之類的指令,而使用a=a+1或a=a-1之類的指令,有很多C編譯器都會生成二到三個位元組的指令。在AVR單片適用的ICCAVR、GCCAVR、IAR等C編譯器以上幾種書寫方式生成的代碼是一樣的,也能夠生成高質量的inc和dec之類的的代碼。
5、減少運算的強度
可以使用運算量小但功能相同的表達式替換原來復雜的的表達式。如下:
(1)、求余運算。
a=a%8;
可以改為:
a=a&7;
說明:位操作只需一個指令周期即可完成,而大部分的C編譯器的「%」運算均是調用子程序來完成,代碼長、執行速度慢。通常,只要求是求2n方的余數,均可使用位操作的方法來代替。
(2)、平方運算
a=pow(a,2.0);
可以改為:
a=a*a;
說明:在有內置硬體乘法器的單片機中(如51系列),乘法運算比求平方運算快得多,因為浮點數的求平方是通過調用子程序來實現的,在自帶硬體乘法器的AVR單片機中,如ATMega163中,乘法運算只需2個時鍾周期就可以完成。既使是在沒有內置硬體乘法器的AVR單片機中,乘法運算的子程序比平方運算的子程序代碼短,執行速度快。
如果是求3次方,如:
a=pow(a,3.0);
更改為:
a=a*a*a;
則效率的改善更明顯。
(3)、用移位實現乘除法運算
a=a*4;
b=b/4;
可以改為:
a=a<<2;
b=b>>2;
說明:通常如果需要乘以或除以2n,都可以用移位的方法代替。在ICCAVR中,如果乘以2n,都可以生成左移的代碼,而乘以其它的整數或除以任何數,均調用乘除法子程序。用移位的方法得到代碼比調用乘除法子程序生成的代碼效率高。實際上,只要是乘以或除以一個整數,均可以用移位的方法得到結果,如:
a=a*9
可以改為:
a=(a<<3)+a
6、循環
(1)、循環語
對於一些不需要循環變數參加運算的任務可以把它們放到循環外面,這里的任務包括表達式、函數的調用、指針運算、數組訪問等,應該將沒有必要執行多次的操作全部集合在一起,放到一個init的初始化程序中進行。
(2)、延時函數:
通常使用的延時函數均採用自加的形式:
void delay (void)
{
unsigned int i;
for (i=0;i<1000;i++)
;
}
將其改為自減延時函數:
void delay (void)
{
unsigned int i;
for (i=1000;i>0;i--)
;
}
兩個函數的延時效果相似,但幾乎所有的C編譯對後一種函數生成的代碼均比前一種代碼少1~3個位元組,因為幾乎所有的MCU均有為0轉移的指令,採用後一種方式能夠生成這類指令。
在使用while循環時也一樣,使用自減指令控制循環會比使用自加指令控制循環生成的代碼更少1~3個字母。
但是在循環中有通過循環變數「i」讀寫數組的指令時,使用預減循環時有可能使數組超界,要引起注意。
(3)while循環和do…while循環
用while循環時有以下兩種循環形式:
unsigned int i;
i=0;
while (i<1000)
{
i++;
//用戶程序
}
或:
unsigned int i;
i=1000;
do
i--;
//用戶程序
while (i>0);
在這兩種循環中,使用do…while循環編譯後生成的代碼的長度短於while循環。
7、查表
在程序中一般不進行非常復雜的運算,如浮點數的乘除及開方等,以及一些復雜的數學模型的插補運算,對這些即消耗時間又消費資源的運算,應盡量使用查表的方式,並且將數據表置於程序存儲區。如果直接生成所需的表比較困難,也盡量在啟動時先計算,然後在數據存儲器中生成所需的表,後以在程序運行直接查表就可以了,減少了程序執行過程中重復計算的工作量。
C. 關系代數表達式的優化策略中,首先要做的是
關系代數表達式的優化策略中,首先要做的是:盡早執行選擇運算。
關系代數是關系資料庫系統查宏稿讓詢語言的理論基礎。
一、關系代數的9種操作:
關系代數中包括了:並、交、差、乘、選擇、投影、聯接、除、自然敬褲聯接等操作。
五個基本操作:
並(∪)、差(-)、笛卡爾積(×)、投影(σ)、選擇(π)
四個組合操作:
交(∩)、聯接(等值聯接)、自然聯接(RS)、除法(÷)
注意:等值連接表示先做笛卡爾積(×)之後,對相應列蔽局進行選擇或等值關聯後的結果(僅篩選行、不篩選列)
注2:自然連接表示兩個關系中若有相同名稱的屬性,則自動作為關聯條件,且僅列出一列
二、關系代數表達式:
由關系代數運算經有限次復合而成的式子稱為關系代數表達式。這種表達式的運算結果仍然是一個關系。可以用關系代數表達式表示對資料庫的查詢和更新操作。
三、關系代數表達式的優化:
目的:為了系統在執行時既省時間又能提高效率。
基本策略:先做選擇,運用投影去除多餘屬性等等。
優化演算法:語法樹(盡量提前做選擇操作;在每個操作後,應做個投影操作,去掉不用的屬性值)。
例如:
π SNO,SNAME(σGRADE>60(SSC)) 進行優化後轉換為:
π SNO,SNAME(πSNO,SNAME(S)πSNO(σGRADE>60(SC)))
--即提前做選擇操作;在每個操作後,應做個投影操作,去掉不用的屬性值。
關鍵詞:正則表達式 | 緩存 | 性能優畝滑尺化
Python 3 的 re 庫中,對正則表達式的編譯函數 re.compile() 調用了私有函數 re._compile() ,但更根本上編譯的計算是由 sre_compile.compile() 完成的,而 re._compile() 中對編譯好的表達式進行了緩存,使用 _MAXCACHE 將緩存迅高大小硬編碼為512。以下是 re._compile() 的源碼,摘自: https://github.com/python/cpython/blob/3.5/Lib/re.py (3.6,3.7里也沒有變化)
在某些 大規模應用場景 下,512的緩存顯然太小了一些,為了擺脫這個瓶頸但不去碰cpython的源碼,我們可以自己改寫 re._compile() ,從而實現自定義緩存大小( max_regex_cache ),輕松排個10000出來。原函數里很多語句都不知道幹嘛用的,但照葫蘆畫瓢總沒錯。
調用方法:
進一步優化是將這讓虛個類變成Singleton(之後我應該會專門寫一篇),以及 多模塊共享 。
E. 為什麼要對關系代數表達式進行優化
關系代數表達式由關系代數操作組合而成。操作中,以笛卡爾積和聯接操作最費時,御冊並生成大量的中間結果。如果直接按表達式書寫的順序執行,必將花費很多時間,並生成游拆畝大量的中間結果,效率較低。在執行前,由DBMS的查詢子系統先對關系代數表達式進行優化,盡可能先執神森行選擇和投影操作,以便減少中間結果,並節省時間。
望採納 謝謝
F. 如何優化單片機C語言代碼 轉
優化代碼和優化速度實際上是一個予盾的統一,一般是優化了代碼的尺寸,就會帶來執行時間的增加,如果優化了程序的執行速度,通常會帶來代碼增加的副作用,很難魚與熊掌兼得,只能在設計時掌握一個平衡點。 一、程序結構的優化 1、程序的書寫結構雖然書寫格式並不會影響生成的代碼質量,但是在實際編寫程序時還是應該尊循一定的書寫規則,一個書寫清晰、明了的程序,有利於以後的維護。在書寫程序時,特別是對於While、for、do…while、if… elst、switch…case 等語句或這些語句嵌套組合時,應採用"縮格"的書寫形式, 2、標識符程序中使用的用戶標識鏈搏符除要遵循標識符的命名規則以外,一般不要用代數符號(如a、b、x1、y1)作為變數名,應選取具有相關含義的英文單詞(或縮寫)或漢語拼音作為標識符,以增加程序的可讀性,如:count、 number1、red、work 等。 3、程序結構C 語言是一種高級程序設計語言,提供了十分完備的規范化流程式控制制結構。因此在採用C 語言設計單片機應用系統程序時,首先要注意盡可能採用結構化的程序設計方法,這樣可使整個應用系統程序結構清晰,便於調試和維護。於一個較大的應用程序,通常將整個程序按功能分成若干個模塊,不同模塊完成不同的功能。各個模塊可以分別編寫,甚至還可以由不同的程序員編寫,一般單個模塊完成的功能較為簡單,設計和調試也相對容易一些。在模喚迅 C 語言中,一個函數就可以認為是一個模塊。所謂程序模塊化,不僅是要將整個程序劃分成若干個功能模塊,更重要的是,還應該注意保持各個模塊之間變數的相對獨立性,即保持模塊的獨立性,盡量少使用全局變數等。對於一些常用的功能模塊,還可以封裝為一個應用程序庫,以便需要時可以直接調用。但是在使用模塊化時,如果將模塊分成太細太小,又會導致程序的執行效率變低 (進入和退出一個函數時保護和恢復寄存器佔用了一些時間)。 4、定義常數在程序化設計過程中,對於經常使用的一些常數,如果將它直接寫到程序中去,一旦常數的數值發生變化,就必須逐個找出程序中所有的常數,並逐一進行修改,這樣必然會降低程序的可維護性。因此,應盡量當採用預處理命令方式來定義常數,而且還可以避免輸入錯誤。 5、減少判斷語句能夠使用條件編譯(ifdef)的地方就使用條件編譯而不使用if 語句,有利於減少編譯生成的代碼的長度。 6、表達式對於一個表達式中各種運算執行的優先順序不太明確或容易混淆的地方,應當採用圓括弧明確指定它們的優先順序。一個表達式通常不能寫得太復雜,如果表達式太復雜,時間久了以後,自己也不容易看得懂,不利於以後的維護。 7、函數對於程序中的函數,在使用之前,應對函數的類型進行說明,對函數類型的說明必須保證它與原來定義的函數類型一致,對於沒有參數和沒有返回值類型的函數應加上"void"說明。如果果需要縮短代碼的長度,可以將程序中一些公共的程序段定義為函數,在Keil 中的高級別優化就是這樣的。如果需要縮短程序的執行時間,在程序調試結束後,將部分函數用宏定義來代替。注意,應該在程序調試結束後再定義宏,因為大多數編譯系統在宏展開之後才會報錯,這樣會增加排錯的難度。 8、盡量少用全局變數,多用局部變數。因為全局變數是放在數據存儲器中,定義一個全局變數,MCU 就少一個可以利用的數據存儲器空間,如果定義了太多的全局變數,會導致編譯器無足夠的內存可以分配。而局部變數大多定位於 MCU 內部的寄存器中,在絕大多數MCU 中,使用寄存器操作速度比數據存儲器快,指令也更多更靈活,有利於生成質量更高的代碼,而且局部變數所的佔用的寄存器和數據存儲器在不同的模塊中可以重復利用。 9、設定合適的編譯程序選項許多編譯程序有幾種不同的優化選項,在使用前應理解各優化選項的含義,然後選用最合適的一種優化方式。通常情況下一旦旦此選用最高級優化,編譯程序會近乎病態地追求代碼優化,可能會影響程序的正確性,導致程序運行出錯。因此應熟悉所使用的編譯器,應知道哪些參數在優化時會受到影響,哪些參數不會受到影響。在ICCAVR 中,有"Default"和 "Enable Code Compression"兩個優化選項。在CodeVisionAVR 中,"Tiny"和 "small"兩種內存模式。在IAR==有7 種不同的內存模式選項。在GCCAVR 中優化選項更多,一不小心更容易選到不恰當的選項。 二、代碼的優化1、選擇合適的演算法和數據結構應該熟悉演算法語言,知道各種演算法的優缺點,具體資料請參見相應的參考資料,有很多計算機書籍上都有介紹。將比較慢的順序查找法用較快的二分查找或亂序查找法代替,插入排序或冒泡排序法用快速排序、合並排序或根排序代替,都可以大大提高程序執行的效率。.選擇一種合適的數據結構也很重要,比如你在一堆隨機存放的數中使用了大量的插入和刪除指令,那使用鏈表要快得多。數組與指針具有十分密碼的關系,一般來說,指針比較靈活簡潔,而數組則比較直觀,容易理解。對於大部分的編譯器,使用指針比使用數組生成的代碼更短,執行效率更高。但是在Keil 中則相反,使用數組比使用的指針生成的代碼更短。 2、使用盡量小的數據類型能夠使用字元型(char)定義的變數,就不要使用整型(int)變數來定義;能夠使用整型變數定義的變數就不要用長整型(long int),能不使用浮點型(float)變數就不要使用浮點型變數。當然,在定義變數後不要超過變數的作用范圍,如果超過變數的范圍賦值,C 編譯器並不報錯,但程序運行結果卻錯了,而且這樣的錯誤很難發現。在ICCAVR 中,可以在 Options 中設定使用printf 參數,盡量使用基本型參數(%c、%d、%x、%X、%u 和%s 格式說明符),少用長整型參數(%ld、%lu、%lx 和%lX 格式說明符),至於浮點型的參數(%f)則盡量不要使用,其它C 編譯器也一樣。在其它條件不變的情況下,使用%f 參數,會使生成的代碼的數量增加很多,執行速度降低。 3、使用自加、自減指令通常使用自加、自減指令和復合賦值表達式(如a- =1 及a+=1 等)都能夠生成高質量的程序代碼,編譯器通常都能夠生成inc 和 dec 之類的指令,而使用a=a+1 或a=a-1 之類的指令,有很多C 編譯器都會生成二到三個位元組的指令。在AVR 單片適用的ICCAVR、GCCAVR、IAR 等C 編譯器以上幾種書寫方式生成的代碼是一樣的,也能夠生成高質量的inc 和dec 之類的的代碼。 4、減少運算的強度可以使用運算量小但功能相同的表達式替換原來復雜的的表達式。如下:(1)、求余運算。a=a%8;可以改為:a=a&7;說明:位操作只需一個指令周期即可完成,而大部分的C 編譯器的"%"運算均是調用子程序來完成,代碼長、執行速度慢。通常,只要求是求2n 方的余數,均可使用位操作的方法來代替。(2)、平方運算a=pow(a,2.0);可以改為:a=a*a;說明:在有內置硬體乘法器的單片機中(如51 系列),乘法運算比求平方運算快得多,因為浮點數的求平方是通過調用子程序來實現的,在自帶硬體乘法器的 AVR 單片機中,如ATMega163 中,乘法運算只需2 個時鍾周期就可以完成。既使是在沒有內置硬體乘法器的AVR 單片機中,乘法運算的子程序比平方運算的子程序代碼短,執行速度快。如果是求3 次方,如:a=pow(a,3.0);更改為:a=a*a*a;則效率的改善更明顯。(3)、用移位實現乘除法運算 a=a*4;b=b/4;可以改為:a=a 2; b=b 2;說明:通常如果需要乘以或除以2n,都可以用移位的方法代替。在 ICCAVR 中,如果乘以2n,都可以生成左移的代碼,而乘以其它的整數或除以任何數,均調用乘除法子程序。用移位的方法得到代碼比調用乘除法子程序生成的代碼效率高。實際上,只要是乘以或除以一個整數,均可以用移位的方法得到結果,如:a=a*9 可以改為:a=(a 3)+a 5、循環(1)、循環語對於一些不需要循環變數參加運算的任務可以把它們放到循環外面,這里的任務包括表達式、函數的調用、指針運算、數組訪問等,應該將沒有必要執行多次的操作全部集合在一起,放到一個init 的初始化程序中進行。(2)、延時函數:通常使用的延時函數均採用自加的形式:void delay(void){unsigned int i;for(i=0;i 1000;i++);}將其改為自減延時函數:void delay(void){unsigned int i; for(i=1000;i 0;i--);}兩個函數的延時效果相似,但幾乎所有的C 編譯對後一種函數生成的代碼均比前一種代碼少1~3 個位元組,因為幾乎所有的MCU 均有為0 轉移的指令,採用後一種方式能夠生成這類指令。在使用while 循環時也一樣,使用自減指令控制循環會比使用自加指令控制循環生成的代碼更少 1~3 個字母。但是在循環中有通過循環變數"i"讀寫數組的指令時,使用預減循環時有可能使數組超界,要引起注意。(3)while 循環和do…while 循環用 while 循環時有以下兩種循環形式:unsigned int i;i=0;while(i 1000){i++;//用戶程序}或:unsigned int i;i=1000;do i--;//用戶程序 while(i 0);在這兩種循環中,使用do…while 循環編譯後生成的代碼的長度短於while 循環。6、查表在程序中一般不進行非常復雜的運算,如浮點數的乘除及開方等,以及一些復雜的數學模型的插補運算,對這些即消耗時間又消費資源的運算,應盡量使用查表的方式,並且將數據表置於程序存儲區。如果直接生成所需的表比較困難,也盡量在啟動時先計算,然後在數據存儲器中生成所需的表,後以在程序運行直接查表就可以了,減少了程序執行過程中重復計算的工作量。7、其它比如使用在線匯編及將字元串和一些常量保存在程序存儲器中,均有利於優化。
G. 在C++中,進行邏輯運算時,為什麼要對邏輯表達式進行優化
那樣不就可以脊空簡化計算量了嗎。如果櫻芹瞎出現在循環中,那減少的運算量將首顫是非常巨大的。
有這樣的規定?
H. javashort怎麼-1
註:如未特別說明,Java語言規范 jls 均基於JDK8,使用環境是 eclipse4.5 + win10 + JDK 8
本篇的知識點,主要是涉及到 Java 中一些比較答橡雹常見的默認窄化處理(Java編譯器自動添加的),這里將從一個問題開始,據說這也是一道常見的筆試題/面試題:
為什麼 short i = 1; i += 1; 可以正確編譯運行而 short i = 1; i = i + 1; 會出現編譯錯誤?
其他說法:都放在一起編譯會出現有什麼結果,哪一行報錯?為什麼?
筆者註:其實這其中會涉及到一些編譯優化和底層的知識,限於知識面,本篇不涉及,如有需要,可自行搜索。
本文的目錄結構如下:
1、結論
關於開篇提出的問題,這里先直接給出結論:
Java語言規范規定基礎數據類型運算默認使用32位精度的int類型
只要是對基本類型做窄化處理的,例如 long -> int -> short -> char,都需要做強制轉換,有些是Java編譯器默認添加的,有的則是代碼中顯式做強制轉換的。
short i = 1; i += 1;可以正確編譯運行是因為Java編譯器自己添加了強制窄化處理,即對於任何的T a; X b; a += b;等價於T a; X b; a = (T) (a + b);Java編譯器會默認做這個顯式強制轉換(盡管有時候會出現精度問題,例如 b 是 float 、 double 類型,強烈建議不要有這樣的操作)。前面的i += 1其實就等價於i = (int) (i + 1),即便將數字1換成是double類型的1.0D也是如此。
short i = 1; i = i + 1;編譯不通過的原因就很明顯了:無論是代碼中,還是Java編譯器,都沒有做強制轉換,int 類型直接賦給 short ,因此編譯出錯。
對於常量(數字常量、常量表達式、final常量等),Java編譯器同樣也可以做默認的強制類型轉換,只要常量在對應清帆的數據范圍內即可。
2、詳如殲解
接下來講詳細分析為什麼 short i = 1; i += 1; 可以正確編譯而 short i = 1; i = i + 1; 則會編譯失敗。先列一下搜出來的一些令人眼前一亮(or 困惑)的代碼
public static voidmain(String[] args) {//注:short ∈ [-32768, 32767]
{/** 1、對於 +=, -=, *=, /=, Java編譯器默認會添加強制類型轉換,
* 即 T a; X b; a += b; 等價於 T a; X b; a = (T) (a + b);*/
//0是int類型的常量,且在short范圍內,被Java編譯器默認強制轉換的
short i = 0;
i+= 1; //等價於 i = (short) (i + 1);
System.out.println("[xin01] i=" + i); //輸出結果: 1
i = (short) (i + 1);
System.out.println("[xin02] i=" + i); //輸出結果: 2
/** 下面這2行都會有編譯報錯提示:
* Exception in thread "main" java.lang.Error: Unresolved compilation problem:
* Type mismatch: cannot convert from int to short
* [注]錯誤: 不兼容的類型: 從int轉換到short可能會有損失
* Eclipse 也會有提示: Type mismatch: cannot convert from int to short*/
//i = i + 1;//i = 32768;
i= 0;
i+= 32768; //等價於 i = (short) (i + 32768); 下同
System.out.println("[xin03] i=" + i); //輸出結果: -32768
i += -32768;
System.out.println("[xin04] i=" + i); //輸出結果: 0
i= 0;long j = 32768;
i+=j;
System.out.println("[xin05] i=" + i); //輸出結果: -32768
i= 0;float f = 1.23F;
i+=f;
System.out.println("[xin06] i=" + i); //(小數位截斷)輸出結果: 1
i= 0;double d = 4.56D;
i+=d;
System.out.println("[xin07] i=" + i); //(小數位截斷)輸出結果: 4
i= 10;
i*= 3.14D;
System.out.println("[xin08] i=" + i); //輸出結果: 31
i= 100;
i/= 2.5D;
System.out.println("[xin09] i=" + i); //輸出結果: 40
}
{/** 2、常量表達式和編譯器優化: 常量折疊*/
//2 * 16383 = 32766//(-2) * 16384 = -32768//都在 short 范圍內,常量表達式在編譯優化後直接用對應的常量結果,然後編譯器做強制轉換
short i = 2 * 16383; //等價於 short i = (short) (2 * 16383);
short j = (-2) * 16384;//2 * 16384 = 32768,超過 short 范圍,編譯器不會做轉換//Type mismatch: cannot convert from int to short//short k = 2 * 16384;//常量表達式在編譯優化後直接用對應的常量結果,然後編譯器做強制轉換
short cThirty = 3 * 10;short three = 3;short ten = 10;//Type mismatch: cannot convert from int to short//short thirty = three * ten;
final short fTthree = 3;final short fTen = 10;//常量表達式在編譯優化後直接用對應的常量結果,然後編譯器做強制轉換
short fThirty = fTthree *fTen;final short a = 16384;final short b = 16383;//常量表達式在編譯優化後直接用對應的常量結果,然後編譯器做強制轉換
short c = a +b;
}
}
接下來根據代碼羅列的兩部分分別進行說明:
2.1、對於 +=, -=, *=, /=, Java編譯器默認會添加強制類型轉換,即 T a; X b; a += b; 等價於 T a; X b; a = (T) (a + b);
A compound assignment expression of the formE1 op= E2is equivalent toE1 = (T) ((E1) op (E2)), whereTis the type ofE1, except thatE1is evaluated only once.
For example, the following code is correct:
short x = 3;
x+= 4.6;
and results in x having the value 7 because it is equivalent to:
short x = 3;
x= (short)(x + 4.6);
筆者註:
實際上,直接加上強制類型轉換的寫法,也是大家都熟悉且理解起來最清晰的方式,可以避免可能潛在的類型不匹配時出現的精度損失問題,使用的時候需要注意。當然,筆者認為這些方式都沒有好壞之分,正確地使用即可。
Java從語言規范層面對此做了限制。有興趣的還可以通過 class文件和 javap -c 反匯編對所使用的位元組碼作進一步的研究。
知道了Java語言相關的規范約定,我們就可以看出,與之對應的是以下這種出現編譯錯誤的寫法(報錯提示:Type mismatch: cannot convert from int to short):
short i = 1;//i + 1 是 int 類型,需要強制向下類型轉換
i = i + 1;
2.2、常量表達式和編譯器優化: 常量折疊
需要注意的是,前面的示例short x = 3;中的3其實默認是 int 類型,但是卻可以賦值給short類型的x。這里涉及到到的其實是 常量表達式。
In addition, if the expression is a constant expression (
A narrowing primitive conversion may be used if the type of the variable is byte, short, or char, and the value of the constant expression is representable in the type of the variable.
A narrowing primitive conversion followed by a boxing conversion may be used if the type of the variable is:
Byte and the value of the constant expression is representable in the type byte.
Short and the value of the constant expression is representable in the type short.
Character and the value of the constant expression is representable in the type char.
對於常量表達式,其結果是可以自動做窄化處理的,只要是在對應的數據類型範圍內,Java編譯器就進行做默認強制類型轉換。
Some expressions have a value that can be determined at compile time. These are constant expressions (
true(short)(1*2*3*4*5*6)
Integer.MAX_VALUE/ 2
2.0 *Math.PI"The integer " + Long.MAX_VALUE + " is mighty big."
A constant variable is a final variable of primitive type or type String that is initialized with a constant expression (
通過常量表達式賦值的final變數都是常量,這種也是編譯期可以確認最終值,會通過編譯優化直接賦予最終值,而且可以直接依靠編譯器做窄化處理。
對於這一塊,其實對應的是一個比較基礎的編譯器優化:常量折疊(Constant Folding),有興趣的可以自行搜索。stackoverflow 上由相關的討論,參考[9]、[10]、[11]
筆者註:
盡管常量表達式最終都被編譯器優化為直接值,但是為了清晰,提高可讀性、可維護性,代碼中沒必要對常量直接換算,例如一天 24 * 60 * 60 秒,其實可以分別用可讀性更強的final常量來表示。
Bloch大神的 Java Puzzlers 中也有相關的一些說明,有興趣的可以去看看
I. 關於c語言邏輯表達式的問題,求高手啊
c語言中,為了加快編譯速度,有一條這樣的規定,當一個邏輯表達式的值能夠確定下來的時候,它不會執行下面的語句。
例如:a && b這個表達式,如果a為假,則 a&&b一定為假,不管迅前b為真,還是假,也就是a等於假亮塵時,這個表達式的值已經確定了。所以b不會執行了。但,如果a為真,則 a&&b的值就確定不了,因為b若為假,則表達式為假,b為真,表達式為真,所以還需要判斷b的真假,所以b會執行。
++ix 結果為2,為真。敬昌禪
++iy 結果為2,為真
所以++ix&&++iy 為真, 因為 ||有一個為真,就為真,所以 ++iz不會執行,因為已經得出前面為真了。
J. 一個正則表達式優化
優化結果就是a
原因是正則表達式的機制是「急於表功」即悄滲懶惰的,右側其它4個表達式由於都包括a在內,因此即使在匹配時有符合ab或abc……的字元串,正則引擎在查找時都需要第一個先找到a,啟好脊再找b,再襪明找c,可是在找到a以後就已經匹配了第一個可選路徑a,此時正則引擎急於表功,立即返回結果,所以後面4個可選路徑其實都廢掉了,永遠輪不到它們。優化下來就是a。
估計這是一道考題吧?實際應用中不會遇到這種正則式的,都會由長至短寫成abcde|abcd|abc|ab|a