今天和一個朋友討論 C++ 是強類型仍是弱類型的時候,他告訴我 C++ 是強類型的,他和我說由於 C++ 在寫的時候須要 int,float 等等關鍵字去定義變量,所以 C++ 是強類型的,我告訴他 C++ 是弱類型的他居然還嘲笑我不懂基礎。javascript
我又嘗試去問了另一個同窗 Python 是強類型仍是弱類型的時候,獲得的居然是弱類型,就由於定義變量沒有 int,float!java
而後我想找一些網上的資料試圖告訴他們他們是錯的(我是對的),結果發現網上的資料大多爲了嚴謹結果把簡單的問題(其實並不簡單)說的很複雜。好比:知乎上的一些 回答。因此用通俗的方式,以大多數程序猿(媛)所須要瞭解的知識去介紹類型系統,可是又不喪失嚴謹性就是這篇文章寫的意義。segmentfault
基礎版本數組
編譯時就知道變量類型的是靜態類型;運行時才知道一個變量類型的叫作動態類型。好比:安全
編譯器在將 int age = 18;
這段代碼編譯的時候就會把 age 的類型肯定,換言之,你不能對他進行除以 0 的操做等等,由於類型自己就定義了可操做的集合;可是像 C++ 裏常見的 auto ite = vec.iterator();
這種也屬於靜態類型,這種叫作類型推導,經過已知的類型在編譯時期推導出不知道的變量的類型。在靜態類型語言中對一個變量作該變量類型所不容許的操做會報出語法錯誤。app
可是像 var name = student.getName();
這行 JavaScript 代碼就是動態類型的,由於這行代碼只有在被執行的時候才知道 name 是字符串類型的,甚至是 null 或 undefined 類型。你也沒辦法進行類型推導,由於 student.getName 函數簽名根本不包含返回值類型信息。後面會介紹經過一些其餘手段來給函數簽名加上類型。在動態類型中對一個變量作該變量類型所不容許的操做會報出運行時錯誤。函數
不容許隱式轉換的是強類型,容許隱式轉換的是弱類型。好比:性能
在 Python 中進行 '666' / 2
你會獲得一個類型錯誤,這是由於強類型語言中是不容許隱式轉換的,而在 JavaScript 中進行 '666' / 2
你會獲得整數 333,這是由於在執行運算的時候字符串 '666' 先被轉換成整數 666,而後再進行除法運算。 優化
高級版本this
須要先介紹一些基本概念:
Program Errors(程序錯誤)
Forbidden Behaviors(禁止行爲)
程序在設計的時候會定義一組 forbidden behaviors,包括了全部的 untrapped errors,可能包括 trapped errors。
Well behaved、ill behaved
他們之間的關係能夠用下圖來表達:
從圖中能夠看出,綠色的 program 表示全部程序(全部程序,你能想到和不能想到的),error 表示出錯的程序,error 不只僅包括 trapped error 和 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。可是強類型,靜態類型的語言寫起來每每是最安全的。
靜態類型因爲在編譯期會進行優化,因此通常來講性能是比較高的。而動態語言在進行類型操做的時候(好比字符串拼接,整數運算)還須要解釋器去猜想其類型,所以性能很低;可是現代的解釋器通常會有一些優化措施來提高速度,拿 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 總結的優化方案:
弱類型語言因爲在運行時缺少類型系統,所以很容易出現類型操做上的 untrapped error;C 語言中咱們前面介紹了數組訪問越界的狀況,這裏咱們以弱類型語言 JavaScript 爲例:
===
像 JavaScript 這種動態類型的語言靜態化後對運行時的安全性,效率確定會有很大的提高的,目前有 TypeScript 這種預編譯的方案;還有就是像 flow 這樣的經過註釋來標識類型的方案。
寫到最後,我才發現文章的標題沒取好,就這樣吧。