摘要:不允許隱式轉換的是強類型,允許隱式轉換的是弱類型。拿一段代碼舉例在使用調用函數的時候會先生成一個類模板運行時生成,執行的時候會生成類模板,執行的時候會生成類模板。
0 x 01 引言
今天和一個朋友討論 C++ 是強類型還是弱類型的時候,他告訴我 C++ 是強類型的,他和我說因為 C++ 在寫的時候需要 int,float 等等關鍵字去定義變量,因此 C++ 是強類型的,我告訴他 C++ 是弱類型的他竟然還嘲笑我不懂基礎。
我又嘗試去問了另外一個同學 Python 是強類型還是弱類型的時候,得到的竟然是弱類型,就因為定義變量沒有 int,float!
然后我想找一些網上的資料試圖告訴他們他們是錯的(我是對的),結果發現網上的資料大多為了嚴謹結果把簡單的問題(其實并不簡單)說的很復雜。比如:知乎上的一些 回答。所以用通俗的方式,以大多數程序猿(媛)所需要了解的知識去介紹類型系統,但是又不喪失嚴謹性就是這篇文章寫的意義。
0 x 02 什么是動態(靜態)類型,強(弱)類型基礎版本
編譯時就知道變量類型的是靜態類型;運行時才知道一個變量類型的叫做動態類型。比如:
編譯器在將 int age = 18; 這段代碼編譯的時候就會把 age 的類型確定,換言之,你不能對他進行除以 0 的操作等等,因為類型本身就定義了可操作的集合;但是像 C++ 里常見的 auto ite = vec.iterator(); 這種也屬于靜態類型,這種叫做類型推導,通過已知的類型在編譯時期推導出不知道的變量的類型。在靜態類型語言中對一個變量做該變量類型所不允許的操作會報出語法錯誤。
但是像 var name = student.getName(); 這行 JavaScript 代碼就是動態類型的,因為這行代碼只有在被執行的時候才知道 name 是字符串類型的,甚至是 null 或 undefined 類型。你也沒辦法進行類型推導,因為 student.getName 函數簽名根本不包含返回值類型信息。后面會介紹通過一些其他手段來給函數簽名加上類型。在動態類型中對一個變量做該變量類型所不允許的操作會報出運行時錯誤。
不允許隱式轉換的是強類型,允許隱式轉換的是弱類型。比如:
在 Python 中進行 "666" / 2 你會得到一個類型錯誤,這是因為強類型語言中是不允許隱式轉換的,而在 JavaScript 中進行 "666" / 2 你會得到整數 333,這是因為在執行運算的時候字符串 "666" 先被轉換成整數 666,然后再進行除法運算。
高級版本
需要先介紹一些基本概念:
Program Errors(程序錯誤)
trapped errors:導致程序終止執行(程序意識到出錯,使用對應的錯誤處理機制),如除 0,Java 中數組越界訪問
untrapped errors:程序出錯后繼續執行(其實并不一定保證繼續執行,程序本身并不知道出錯,也沒有對應的錯誤處理機制),如 C 語言里的緩沖區溢出,Jmp 到錯誤地址
Forbidden Behaviors(禁止行為)
程序在設計的時候會定義一組 forbidden behaviors,包括了所有的 untrapped errors,可能包括 trapped errors。
Well behaved、ill behaved
well behaved: 如果程序的執行不可能出現 forbidden behaviors,則稱為 well behaved
ill behaved: 只要有可能出現 forbidden behaviors,則稱為 ill behaved
他們之間的關系可以用下圖來表達:
從圖中可以看出,綠色的 program 表示所有程序(所有程序,你能想到和不能想到的),error 表示出錯的程序,error 不僅僅包括 trapped error 和 untrapped error。
根據圖我們可以嚴格的定義動態類型,靜態類型;強類型,弱類型
強類型:如果一門語言寫出來的程序在紅色矩形外部,則這門語言是強類型的,也就是上面說的 well behaved
弱類型:如果一門語言寫出來的程序可能在紅色矩形內部,則這門語言是弱類型的,也就是上面說的 ill behaved
靜態類型:一門語言在編譯時排除可能出現在紅色矩形內的情況(通過語法報錯),則這門語言是靜態類型的
動態類型:一門語言在運行時排除可能出現在紅色矩形內的情況(通過運行時報錯,但如果是弱類型可能會觸發 untrapped error,比如隱式轉換,使得程序看起來似乎是正常運行的),則這門語言是動態類型的
舉個栗子:
在 Python 中執行 test = "666" / 3 你會在運行時得到一個 TypeError 錯誤,相當于運行時排除了 untrapped error,因此 Python 是動態類型,強類型語言。
在 JavaScript 中執行 var test = "666" / 3" 你會發現 test 的值變成了 222,因為這里發生了隱式轉換,因此 JavaScript 是動態類型,弱類型的。更為夸張的是 [] == ![] 這樣的代碼在 JavaScript 中返回的是 true,這里是具體的 原因。
在 Java 中執行 int[] arr = new int[10]; arr[0] = "666" / 3; 你會在編譯時期得到一個語法錯誤,這說明 Java 是靜態類型的,執行 int[] arr = new int[10]; arr[11] = 3; 你會在運行時得到數組越界的錯誤(trapped error),這說明 Java 通過自身的類型系統排除了 untrapped error,因此 Java 是強類型的。
而 C 與 Java 類似,也是靜態類型的,但是對于 int test[] = { 1, 2, 3 }; test[4] = 5; 這樣的代碼 C 語言是沒辦法發現你的問題的,因此這是 untrapped error,因此我們說 C 是弱類型的。
下圖是常見的語言類型的劃分:
另外,由于強類型語言一般需要在運行時運行一套類型檢查系統,因此強類型語言的速度一般比弱類型要慢,動態類型也比靜態類型慢,因此在上述所說的四種語言中執行的速度應該是 C > Java > JavaScript > Python。但是強類型,靜態類型的語言寫起來往往是最安全的。
0 x 03 動態類型與靜態類型的區別,如何利用好動態類型靜態類型由于在編譯期會進行優化,所以一般來說性能是比較高的。而動態語言在進行類型操作的時候(比如字符串拼接,整數運算)還需要解釋器去猜測其類型,因此性能很低;但是現代的解釋器一般會有一些優化措施來提升速度,拿 JavaScript 的 V8 解釋器舉個栗子:
V8 的優化過程(粗略版本)
我們知道,像 Java / C++ 這樣的靜態類型語言對于對象一般都會有個類模板(一般調用函數的時候都是去類模板找的)。而像 V8 這種則是會在運行時創建類模板,從而在訪問屬性或調用方法的時候僅需要計算該屬性在類模板中的偏移就可以了;傳統的 JavaScript 對象一般是通過 Hash 或 Trie 樹實現的,但是查找的效率很低。拿一段代碼舉例:
function Point(x, y) { this.x = x; this.y = y; } var p1 = new Point(1, 2);
在使用 new 調用 Point 函數的時候會先生成一個 class0 類模板(運行時生成),執行 this.x = x 的時候會生成 class1 類模板,執行 this.y = y 的時候會生成 class2 類模板。具體的轉換過程如下圖:
為一個對象確定一個類模板可以極大的提升屬性的訪問速度,類模板的確定就是通過走圖里的路徑(轉換路徑)。每當你增加或刪除對象的屬性的時候都會導致對象的類模板發生改變,甚至你增加的順序不同也會生成不同的類模板!
V8 如果發現一個方法被調用(傳入相同類型的參數)多次時,會使用 JIT 將函數編譯成二進制代碼,從而提升速度。
結合 V8 總結的優化方案:
不要輕易的增加刪除一個對象的屬性,對于已有的屬性盡量做到保證類型的不變,保證隱藏類盡可能被復用
實例化屬性的時候盡可能保證屬性添加的順序一致性,保證隱藏類和優化代碼可以被復用
盡可能重復調用方法,傳的參數的個數和類型要在多次調用時要保持一致
對于數組,最好使用 push,unshift 等方法去改變數組大小,緊密的數組在 V8 中是以連續的地址存的,不要隨意去刪除數組中的元素,因為稀疏數組在 V8 中是一個 hash 表
V8 存儲整數用的是 4 個字節,出現大整數時將會涉及到隱式類型轉換,性能降低,因此盡量不要讓整數超過 32 bit
0 x 04 如何避免弱類型語言所帶來的問題弱類型語言由于在運行時缺乏類型系統,因此很容易出現類型操作上的 untrapped error;C 語言中我們前面介紹了數組訪問越界的情況,這里我們以弱類型語言 JavaScript 為例:
盡量使用嚴格比較符號,如:===
盡量不要讓字符串與其他類型的變量進行運算操作
復雜對象不要在運算符上進行操作
0 x 05 語言類型靜態化的方案像 JavaScript 這種動態類型的語言靜態化后對運行時的安全性,效率肯定會有很大的提升的,目前有 TypeScript 這種預編譯的方案;還有就是像 flow 這樣的通過注釋來標識類型的方案。
0 x 06 總結寫到最后,我才發現文章的標題沒取好,就這樣吧。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92387.html
摘要:準確的理解,是編譯型語言,源代碼整個編譯成字節碼,字節碼,是解釋型語言。是一個非常靈活的語言,支持命令式和函數式編程。編譯型語言通常會用做配置文件,因為我們通常不會改編譯后的字節碼。 編程語言按各種方法可以分為各種類型,現在讓我們來看看JS屬于什么類型語言 解釋型語言 按編譯執行過程,可以分為編譯型語言和解釋型語言。比如 c 語言,必須先經過編譯生成目標文件,然后鏈接各個目標文件和庫...
摘要:弱類型強類型會報錯靜態類型以上是的代碼,靜態類型語言在編譯時遇到錯誤就會立即提醒。備注意思是陷阱,也被稱為異常或故障。 弱類型: 1+2 12 強類型: 1+2 會報錯 靜態類型: public void ShowHi() { int a = Hi! string b = a; } 以上是c#的代碼,靜態類型語言在編譯時遇到trap錯誤就會立即提醒。 動態類型:...
閱讀 2337·2019-08-30 15:44
閱讀 1260·2019-08-30 13:01
閱讀 3306·2019-08-30 11:22
閱讀 3093·2019-08-29 15:23
閱讀 1614·2019-08-29 12:22
閱讀 3366·2019-08-26 13:58
閱讀 3439·2019-08-26 12:17
閱讀 3479·2019-08-26 12:16