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