Q:代碼是如何運(yùn)行的对供?
代碼是由CPU執(zhí)行的藕甩,而目前的CPU并不能直接執(zhí)行諸如if…else
之類的語句橘蜜,它只能執(zhí)行二進(jìn)制指令含鳞。但是二進(jìn)制指令對(duì)人類實(shí)在是太不友好了:我們很難快速準(zhǔn)確的判斷一個(gè)二進(jìn)制指令1000010010101001代表什么?所以科學(xué)家們發(fā)明匯編語言(實(shí)際上就是二進(jìn)制指令的助記符)延曙。
假設(shè)10101010代表讀取內(nèi)存操作豌鹤,內(nèi)存地址是10101111,寄存器地址是11111010枝缔,那么完整的操作101010101010111111111010就代表讀取某個(gè)內(nèi)存地址的值并裝載到寄存器布疙,而匯編語言并沒有改變這種操作方式,它只是二進(jìn)制指令的映射:
LD:10101010
id:10101111
R:11111010
這樣上述指令就可以表達(dá)為LD id R
愿卸,大大增強(qiáng)了代碼的可讀性灵临。
但是這樣還不夠友好,CPU只能執(zhí)行三地址表達(dá)式趴荸,和人的思考方式儒溉、語言模式相距甚遠(yuǎn)。所以偉大的科學(xué)家們又發(fā)明了高級(jí)語言发钝。
高級(jí)語言之所以稱之為“高級(jí)”顿涣,就是因?yàn)樗臃衔覀兊乃季S和閱讀習(xí)慣。if…else
這種語句看起來要比1010101010舒服的多了酝豪。但是計(jì)算機(jī)并不能直接執(zhí)行高級(jí)語言涛碑,所以還需要把高級(jí)語言轉(zhuǎn)化為匯編語言/機(jī)器指令才能執(zhí)行。這個(gè)過程就是編譯寓调。
JavaScript是什么類型的語言锌唾?
- JavaScript 動(dòng)態(tài)類型的動(dòng)態(tài)語言锄码;
在運(yùn)行時(shí)代碼可以根據(jù)某些條件改變自身結(jié)構(gòu)夺英,如JavaScript在運(yùn)行時(shí)新的函數(shù)、對(duì)象滋捶、甚至代碼都可以被引進(jìn)(eval)痛悯,因此JavaScript是動(dòng)態(tài)語言。JavaScript數(shù)據(jù)類型不是在編譯階段確定重窟,而是在運(yùn)行時(shí)確定载萌,所以JavaScript是動(dòng)態(tài)類型的語言。 - JavaScript是 解釋型語言且弱類型巡扇,JavaScript 代碼需要在機(jī)器(node 或者瀏覽器)上安裝一個(gè)工具(JS 引擎)才能執(zhí)行扭仁,這是解釋型語言所需要的。在生成 AST 之后厅翔,就開始一邊解釋乖坠,一邊執(zhí)行。
Q:JavaScript需要編譯嗎刀闷?
與傳統(tǒng)的編譯語言不同熊泵,它不是提前編譯的仰迁,編譯結(jié)果也不能在分布式系統(tǒng)中進(jìn)行移植。但是JavaScript引擎進(jìn)行編譯的步驟和傳統(tǒng)的編譯語言非常相似顽分,在某些環(huán)節(jié)可能比預(yù)想的要復(fù)雜徐许,具體表現(xiàn)在:
- JavaScript引擎在語法分析和代碼生成階段有特定的步驟來對(duì)運(yùn)行性能進(jìn)行優(yōu)化,包括對(duì)冗余元素進(jìn)行優(yōu)化等卒蘸。
- 與其他編譯語言不同雌隅,JavaScript的編譯過程不是發(fā)生在構(gòu)建之前的,因此JavaScript引擎不會(huì)有大量的時(shí)間進(jìn)行優(yōu)化缸沃。
- 對(duì)于JavaScript澄步,大部分情況下編譯發(fā)生在代碼執(zhí)行前的幾微秒(甚至更短)。
- JavaScript引擎用盡了各種辦法(如JIT和泌,可以延遲編譯甚至實(shí)施重編譯)來保證性能最佳村缸。
JavaScript是如何運(yùn)行的
在傳統(tǒng)編譯語言的流程中,程序中的一段源代碼在執(zhí)行之前會(huì)經(jīng)歷一系列步驟武氓,統(tǒng)稱為“編譯”梯皿。
常見編譯型語言(例如:Java)來說,編譯步驟分為:詞法分析->語法分析->語義檢查->代碼優(yōu)化和字節(jié)碼生成县恕。
對(duì)于解釋型語言(例如 JavaScript)來說东羹,通過詞法分析 -> 語法分析 -> 語法樹,生成 AST 之后忠烛,就開始一邊解釋属提,一邊執(zhí)行。
在JavaScript的執(zhí)行過程主要有以下幾個(gè)關(guān)鍵角色:
- 引擎:從頭到尾負(fù)責(zé)整個(gè)JavaScript程序的編譯及執(zhí)行過程美尸;
- 編譯器:負(fù)責(zé)語法分析以及代碼生成等冤议;
- 作用域:負(fù)責(zé)收集并維護(hù)由所有聲明的標(biāo)識(shí)符(變量)組成的一系列查詢,并實(shí)施一套非常嚴(yán)格的規(guī)則师坎,確定當(dāng)前執(zhí)行的代碼對(duì)這些標(biāo)識(shí)符的訪問權(quán)限恕酸。作用域本質(zhì)上就是程序存儲(chǔ)和訪問變量的規(guī)則。
我們帶著Q:變量住在哪里胯陋?它們儲(chǔ)存在哪里蕊温?程序需要時(shí)如何找到它們?一起看看JavaScript的具體執(zhí)行過程:
- 分詞/詞法分析(Tokenizing/Lexing)
編譯器將由字符組成的字符串分解成(對(duì)編程語言來說)有意義的代碼塊遏乔,這些代碼塊被稱為詞法單元(token)义矛。如果是有狀態(tài)的解析,還會(huì)賦予單詞語義盟萨。
詞法單元生成器在判斷a是一個(gè)獨(dú)立的詞法單元還是其他詞法單元的一部分時(shí)凉翻,調(diào)用的是有狀態(tài)的解析規(guī)則,那么這個(gè)過程就被稱為詞法分析鸯旁。
For example
如程序var a = 2;
通常會(huì)被分解為詞法單元:var噪矛、a量蕊、=、2艇挨、; 具體如下圖所示残炮。并且給它們加上標(biāo)注,代表這是一個(gè)變量還是一個(gè)操作缩滨∈凭停空格是否會(huì)被當(dāng)作詞法單元,取決于空格在這門語言中是否具有意義脉漏。
- 解析/語法分析(Parsing)
語法分析的任務(wù)是在詞法分析的基礎(chǔ)上將單詞序列組合成各類語法短語苞冯,如“程序”,“語句”侧巨,“表達(dá)式”等等舅锄。
語法分析程序判斷源程序在結(jié)構(gòu)上是否正確。如var str ='s ;
這就是典型的語法錯(cuò)誤司忱,這種代碼無法生成AST皇忿,在詞法分析階段就會(huì)報(bào)錯(cuò)。通常我們這么寫代碼坦仍,IDE 就會(huì)報(bào)錯(cuò)鳍烁。這是IDE的優(yōu)化工作,和詞法分析相關(guān)繁扎。
將詞法單元流(數(shù)組)轉(zhuǎn)換成一個(gè)由元素逐級(jí)嵌套所組成的代表了程序語法結(jié)構(gòu)的樹幔荒。這個(gè)樹被稱為“抽象語法樹”(Abstract Syntax Tree,AST)梳玫。
For example
上述例子var a = 2;
被分解的詞法單元在語法分析階段會(huì)被轉(zhuǎn)換成如下結(jié)構(gòu):
- 預(yù)編譯(開放內(nèi)存空間爹梁,存放變量和函數(shù))
當(dāng)JavaScript引擎解析腳本時(shí),它會(huì)先在預(yù)編譯期對(duì)所有聲明的變量和函數(shù)進(jìn)行處理汽纠!編譯階段的一部分工作就是找到所有的聲明卫键,并用合適的作用域?qū)⑺鼈冴P(guān)聯(lián)起來傀履,因此這個(gè)過程編譯器和作用域會(huì)進(jìn)行如下互動(dòng):
?? 預(yù)編譯階段沒有初始化行為(賦值)虱朵,匿名函數(shù)不參與預(yù)編譯。只有在解釋執(zhí)行階段才會(huì)進(jìn)行變量初始化钓账。
JavaScript的作用域才有詞法作用域工作模型碴犬,JavaScript 的變量和函數(shù)作用域是在定義時(shí)決定的,而不是執(zhí)行時(shí)決定的梆暮。
例:看一個(gè)簡(jiǎn)單的聲明語句var name = 'bubble'
服协;在JS引擎眼里,它包含兩個(gè)聲明啦粹,其中
-
var name
在編譯時(shí)(此步驟由編譯器)處理偿荷, -
name=bubble
在運(yùn)行時(shí)處理窘游,即第4步(解釋執(zhí)行由JS引擎處理)。
- 解釋執(zhí)行
在執(zhí)行過程中跳纳,JavaScript 引擎是嚴(yán)格按著作用域機(jī)制(scope)來執(zhí)行的忍饰。引擎在運(yùn)行時(shí)會(huì)完成對(duì)變量的賦值操作,因此和作用域有如下互動(dòng):
作用域套作用域寺庄,就有了作用域鏈:
JavaScript 引擎通過作用域鏈(scope chain)把多個(gè)嵌套的作用域串連在一起艾蓝,并借助這個(gè)鏈條幫助 JavaScript 解釋器檢索變量的值。這個(gè)作用域鏈相當(dāng)于一個(gè)索引表斗塘,并通過編號(hào)來存儲(chǔ)它們的嵌套關(guān)系赢织。當(dāng) JavaScript 解釋器檢索變量的值,會(huì)按著這個(gè)索引編號(hào)進(jìn)行快速查找馍盟,直到找到全局對(duì)象(global object)為止于置,如果沒有找到值,則傳遞一個(gè)特殊的 undefined 值贞岭。
var scope = "global";
scopeTest();
function scopeTest(){
console.log(scope);
var scope = "local";
console.log(scope);
}
打印結(jié)果:undefined俱两,local;
而引擎查找變量的方式會(huì)直接影響到查找的結(jié)果曹步,尤其在變量未聲明的情況下:
總結(jié)一下宪彩,任何JavaScript代碼片段在執(zhí)行前都要進(jìn)行編譯(通常就在執(zhí)行前)。因此讲婚,JavaScript編譯器首先會(huì)對(duì)var a = 2;
這段程序進(jìn)行編譯尿孔,然后做好執(zhí)行它的準(zhǔn)備,并且通常馬上就會(huì)執(zhí)行它筹麸。
讓我們看看引擎對(duì)下面這段代碼做了什么吧活合!
<script>
var a = 1; // 變量聲明
function b(y){ //函數(shù)聲明
var x = 1;
console.log('so easy');
};
var c = function(){ //變量聲明
//...
}
b(100);
</script>
<script>
var d = 0;
</script>
- 頁面產(chǎn)生便創(chuàng)建了GO全局對(duì)象(Global Object),也就是window對(duì)象物赶;
- 第一個(gè)script腳本文件加載白指;
- 腳本文件加載后,分析語法是否合法酵紫;
- 開始預(yù)編譯:
- 查找變量聲明告嘲,作為GO屬性,值賦予
undefined
奖地; - 查找函數(shù)聲明橄唬,作為GO屬性,值賦予函數(shù)體参歹;
GO/window = {
//頁面加載創(chuàng)建GO同時(shí)仰楚,創(chuàng)建了document、screen等屬性
a: undefined,
c: undefined,
b: function(y){
var x = 1;
console.log('so easy');
}
}
- 解釋執(zhí)行代碼,找到變量并賦值(直到執(zhí)行函數(shù)b)
GO/window = {
a: 1,
c: function(){ },
b: function(y){
var x = 1;
console.log('so easy');
}
}
- 執(zhí)行函數(shù)b之前僧界,發(fā)生預(yù)編譯:
- 創(chuàng)建AO活動(dòng)對(duì)象(Active Object)
- 查找函數(shù)形參及函數(shù)內(nèi)變量聲明侨嘀,形參名及變量名作為AO對(duì)象的屬性,值為undefined
- 實(shí)參值賦給形參
AO = {
//創(chuàng)建AO同時(shí)捂襟,創(chuàng)建了arguments等等屬性飒炎,此處省略
y: 100,
x: undefined
}
- 解釋執(zhí)行函數(shù)中的代碼;
x=1
輸出so easy - 第一個(gè)腳本文件執(zhí)行完畢笆豁,加載第二個(gè)腳本文件
- 第二個(gè)腳本文件加載完畢后郎汪,進(jìn)行語法分析
- 語法分析完畢,開始預(yù)編譯
重復(fù)最開始的預(yù)編譯步驟……
測(cè)試
- 例1
function foo() {
console.log(a);
a = 1;
}
foo(); // Uncaught ReferenceError: a is not defined
function bar() {
a = 1;
console.log(a);
}
bar(); // 1
這是因?yàn)楹瘮?shù)中的 "a" 并沒有通過 var 關(guān)鍵字聲明闯狱,所有不會(huì)被存放在 AO 中煞赢。沒有 a 的值,然后就會(huì)到全局去找哄孤,全局也沒有照筑,所以會(huì)報(bào)錯(cuò)。
- 例2
console.log(foo);
function foo(){
console.log("foo");
}
var foo = 1;
會(huì)打印函數(shù)瘦陈,而不是 undefined 凝危。
這是因?yàn)樵谶M(jìn)入執(zhí)行上下文時(shí),首先會(huì)處理函數(shù)聲明晨逝,其次會(huì)處理變量聲明蛾默,如果如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會(huì)干擾已經(jīng)存在的這類屬性捉貌。
下方開始大量擴(kuò)展知識(shí)
編程語言的分類
與硬件的距離
- 比較低級(jí) Low-level 語言
最低級(jí)的語言就是機(jī)器語言支鸡,由0和1構(gòu)成,通過面板趁窃、打孔卡輸入牧挣。
接下來是匯編語言,它對(duì)硬件指令做了簡(jiǎn)單的封裝醒陆,一些操作可以用ADD瀑构、MOVE等英文單詞來表示。目前在內(nèi)核/驅(qū)動(dòng)中會(huì)被用到刨摩。 - 比較高級(jí) High-level 語言
除了上面兩種寺晌,其他語言都是高級(jí)語言,將很多細(xì)節(jié)交由計(jì)算機(jī)(編譯器)把控码邻,同時(shí)變得更加抽象折剃。高級(jí)語言中也有相對(duì)低級(jí)和高級(jí)的。如C屬于非常低級(jí)的高級(jí)語言像屋,因?yàn)镃語言中也還是時(shí)不時(shí)的會(huì)用到硬件知識(shí)。而類似Ruby或JS這樣的腳本語言就基本不用操心硬件的事了边篮。
一般來講己莺,跟硬件離的越近奏甫,就越能通過打磨去挖掘硬件潛力,寫成的程序執(zhí)行效率就會(huì)越高凌受,但是開發(fā)效率肯定也就越低阵子。
是否需要編譯
所有語言最終都需要轉(zhuǎn)變?yōu)闄C(jī)器碼,基于其轉(zhuǎn)換為機(jī)器碼的方式胜蛉,高級(jí)語言可大致分為編譯型和解釋型兩類(匯編語言無須編譯或解釋挠进,僅需匯編成機(jī)器碼)
編譯型語言(Compiled Language)
利用編譯器先將代碼編譯為機(jī)器碼,再加以運(yùn)行誊册。如C++代碼在Windows上會(huì)編譯成.obj文件领突,而在Linux上則生成.o文件。解釋型語言(Interpreted Language)
利用解釋器案怯,在運(yùn)行期間君旦,動(dòng)態(tài)將代碼逐行解釋(Interpret)為機(jī)器代碼執(zhí)行。
有時(shí)也叫腳本語言(Scripting Language)嘲碱。如Python金砍,Ruby、BASIC麦锯、JavaScript恕稠,寫好之后無需編譯,直接運(yùn)行于自己的解釋器之上扶欣。
編譯型語言的運(yùn)行速度更快(因?yàn)橐呀?jīng)預(yù)先編譯好谱俭,運(yùn)行時(shí)無須執(zhí)行解釋這一步驟),而因此宵蛀,編譯型語言的開發(fā)/調(diào)試時(shí)間也較長(zhǎng)昆著,因?yàn)槊看握{(diào)試之前都需要編譯一次。而解釋型語言則可以快速的測(cè)試和調(diào)試术陶,因?yàn)楦布袅艘粚哟斩孕噬弦话闶潜容^低的,但功能上可以更為靈活梧宫。
-
半解釋半編譯
Java就是兩種類型的結(jié)合典型接谨。無論是在什么操作系統(tǒng)上.java文件編譯出的都是.class文件(這就是字節(jié)碼文件,一種中間形態(tài)的目標(biāo)代碼)塘匣。然后Java對(duì)不同的系統(tǒng)提供不同的Java虛擬機(jī)用于解釋執(zhí)行字節(jié)碼文件脓豪。解釋執(zhí)行并不生成目標(biāo)代碼,但其最終還是要轉(zhuǎn)為匯編/二進(jìn)制指令來給計(jì)算機(jī)執(zhí)行的忌卤。
Java采用半解釋半編譯的好處就是大大提升了開發(fā)效率扫夜,然而相應(yīng)的則降低了代碼的執(zhí)行效率,畢竟虛擬機(jī)是有性能損失的。
編程范式(Programming Paradigms)
也叫編程范型笤闯、編程典范堕阔,基于編程語言的特點(diǎn)而進(jìn)行分類的方式,一種語言可以支持超過一種編程范型颗味,常見范式如下:
命令式和聲明式
這是兩個(gè)相對(duì)/并列的范式超陆,命令式編程描述過程 ,聲明式編程描述目標(biāo)浦马。
- 命令式編程(Imperative programming)
命令式編程描述計(jì)算所需作出的行為时呀。幾乎所有的計(jì)算機(jī)硬件都是命令式的。
子范式:過程式和面向?qū)ο笫骄^程式更靠近機(jī)器谨娜,面向?qū)ο笫礁N近程序員。
-
過程式編程(Procedural programming)
把操作轉(zhuǎn)換成語句一步步的去做荤胁,主要使用順序瞧预、條件選擇、循環(huán)三種基本結(jié)構(gòu)來編寫程序仅政。
來源于結(jié)構(gòu)化編程垢油,其概念基于過程(procedure、routine圆丹、subroutine滩愁、function),過程由一系列可執(zhí)行可計(jì)算的步驟構(gòu)成辫封。
Fortran硝枉、ALGOL、COBOL倦微、BASIC妻味、Pascal和C等語言采用過程式編程。 -
面向?qū)ο笫骄幊蹋∣bject-oriented programming)
具有對(duì)象概念的編程范式欣福,先把數(shù)據(jù)封裝成對(duì)象责球,通過對(duì)象之間的交互來實(shí)現(xiàn)功能。
重要的面向?qū)ο缶幊陶Z言包括Common Lisp拓劝、Python雏逾、C++、Java郑临、C#栖博、Perl、PHP厢洞、Ruby仇让、Swift等典奉。
- 聲明式編程(Declarative programming)
聲明式編程描述目標(biāo)的性質(zhì),讓計(jì)算機(jī)明白目標(biāo)妹孙,而非流程秋柄。聲明式編程通常被定義為所有的“非命令式編程”获枝。
聲明式編程包括數(shù)據(jù)庫查詢語言(SQL)蠢正、正則表達(dá)式、邏輯式編程省店、函數(shù)式編程和configuration management嚣崭。聲明式編程通常用作解決人工智能和約束滿足問題。
子范型:函數(shù)式編程懦傍、邏輯式編程雹舀、約束式編程、數(shù)據(jù)流式編程
-
函數(shù)式編程(Functional programming)
又稱泛函編程粗俱,它將計(jì)算視為數(shù)學(xué)上的函數(shù)運(yùn)算说榆,避免變量或狀態(tài)(只有函數(shù)及其參數(shù))。其最重要的基礎(chǔ)是λ演算(lambda calculus)寸认,λ演算的函數(shù)可以接受函數(shù)當(dāng)作輸入和輸出签财。
分為純函數(shù)式編程(Purely functional programming)和函數(shù)邏輯式編程(Functional logic programming,函數(shù)式編程和邏輯式編程的組合) -
邏輯式編程(Logic programming)
邏輯式編程基于數(shù)理邏輯偏塞,它設(shè)置答案所須匹配的規(guī)則來解決問題唱蒸,而非設(shè)置步驟來解決問題。過程為:事實(shí)+規(guī)則=結(jié)果灸叼。
最常用的邏輯式編程語言是Prolog神汹,Mercury則較適用于大型方案。 -
約束式編程(Constraint programming)
在這種范式中古今,變量之間的關(guān)系是以約束的形式陳述的屁魏,它們并非明確說明了要去執(zhí)行步驟的某一步,而是規(guī)范其解的一些屬性捉腥。 -
數(shù)據(jù)流式編程(Dataflow programming)
將程序建模為一個(gè)描述數(shù)據(jù)流的有向圖氓拼。例如BLODI。
結(jié)構(gòu)化和非結(jié)構(gòu)化
這是兩個(gè)相對(duì)的范式但狭,非結(jié)構(gòu)化編程是最早的編程范式披诗,現(xiàn)今的計(jì)算機(jī)科學(xué)家都同意結(jié)構(gòu)化編程的好處。
- 結(jié)構(gòu)化編程(Structured programming)
通過子程序立磁、代碼塊呈队、for循環(huán)、while循環(huán)等結(jié)構(gòu)來取代之前的goto語句唱歧,以提高代碼的清晰程度宪摧,獲得更好的可讀性×J現(xiàn)今的大部分高級(jí)語言都是結(jié)構(gòu)化的。
結(jié)構(gòu)化編程的流程包括順序几于、選擇(if, else, switch)蕊苗、循環(huán)(for, while)幾類。 - 非結(jié)構(gòu)化編程(Non-structured programming)
是最早的編程范式沿彭,相對(duì)于結(jié)構(gòu)化編程朽砰,特點(diǎn)是其控制流是通過(容易引起混亂的)goto語句跳轉(zhuǎn)實(shí)現(xiàn)的。非結(jié)構(gòu)化編程包括機(jī)器語言喉刘、匯編語言瞧柔、MS-DOS batch、以及早期的BASIC及Fortran等等睦裳。
- 泛型編程(Generic programming)
泛型允許程序員在用強(qiáng)類型語言編寫代碼時(shí)使用一些以后才指定的類型造锅。
Ada碰声、Delphi嗽交、C#萨螺、Java织狐、Swift稱之為泛型(generics)茬末,Scala和Haskell稱之為參數(shù)多態(tài)(parametric polymorphism)赡盘,C++稱之為模板粤策。
動(dòng)態(tài)or靜態(tài)分類
動(dòng)態(tài)語言(Dynamic programming language)在運(yùn)行時(shí)可以改變自身結(jié)構(gòu):新的函數(shù)型奥、對(duì)象甚至代碼可以被引進(jìn)宇驾,已有的函數(shù)可以被刪除或有其他結(jié)構(gòu)上的變化倍靡。JavaScript、PHP课舍、Python塌西、Ruby屬于動(dòng)態(tài)語言,而C和C++則不屬于動(dòng)態(tài)語言筝尾。
大部分動(dòng)態(tài)語言都使用動(dòng)態(tài)類型捡需,但也有些不是。
語言類型系統(tǒng)(Type system)分類
- 動(dòng)態(tài)和靜態(tài)類型檢查
- 靜態(tài)類型檢查
對(duì)類型的檢查發(fā)生在編譯時(shí)筹淫。編譯語言通常使用靜態(tài)類型檢查站辉。 - 動(dòng)態(tài)類型檢查
對(duì)類型的檢查發(fā)生在運(yùn)行時(shí)。動(dòng)態(tài)類型檢查經(jīng)常出現(xiàn)在腳本語言和解釋型語言中损姜。
大部分動(dòng)態(tài)語言都使用動(dòng)態(tài)類型饰剥,但也有些不是。
- 強(qiáng)弱類型
按照編程語言對(duì)于混入不同數(shù)據(jù)類型的值進(jìn)行運(yùn)算時(shí)的處理方式不同分為強(qiáng)類型和弱類型摧阅。
- 強(qiáng)類型(Strongly typed)
強(qiáng)類型的語言遇到函數(shù)形參和實(shí)參的類型不匹配時(shí)通常會(huì)失敗汰蓉。 - 弱類型(Weakly/Loosely typed)
弱類型的語言常常會(huì)進(jìn)行隱式的轉(zhuǎn)換(并可能造成不可知的后果)。
- 類型安全和內(nèi)存安全
按照類型運(yùn)算和轉(zhuǎn)換的安全性不同分為類型安全和內(nèi)存安全棒卷。通常來說顾孽,類型安全和內(nèi)存安全是同時(shí)存在的祝钢。
- 類型安全
它不允許導(dǎo)致不正確的情況的運(yùn)算或轉(zhuǎn)換,計(jì)算機(jī)科學(xué)就認(rèn)為該語言是類型安全的若厚。 - 內(nèi)存安全
指程序不被允許訪問沒有分配給它的內(nèi)存拦英,比如:內(nèi)存安全語言會(huì)做數(shù)組邊界檢查。
比如以下例子:
var x:= 5
var y:= “37”
var z:= x + y
上例中的z的值為42测秸,不管編寫者有沒有這個(gè)意圖疤估,該語言定義了明確的結(jié)果,且程序不會(huì)就此崩潰乞封,或?qū)⒉幻鞫x的值賦給z做裙。就這方面而言岗憋,這樣的語言就是類型安全的肃晚。
再比如:
int x = 5
char y[] = “37”
char* z = x + y
在這個(gè)例子中,z將會(huì)指向一個(gè)超過y地址5個(gè)字節(jié)的存儲(chǔ)器地址仔戈,相當(dāng)于指向y字符串的指針之后的兩個(gè)空字符之處关串。這個(gè)地址的內(nèi)容尚未定義,且有可能超出存儲(chǔ)器的定址界線监徘,這就是一個(gè)類型不安全/內(nèi)存不安全的語言晋修。
- 顯式聲明和隱式暗示
許多靜態(tài)類型系統(tǒng),如C和Java凰盔,要求變量聲明類型:編寫者必須以指定類型明確地關(guān)系到每一個(gè)變量上墓卦。其它的,如Haskell户敬,則進(jìn)行類型推斷:編譯器根據(jù)編寫者如何運(yùn)用這些變量落剪,以草擬出關(guān)于這個(gè)變量的類型的結(jié)論。
例如尿庐,給定一個(gè)函數(shù)f(x,y)忠怖,它將x和y加起來,編譯器可以推斷出x和y必須是數(shù)字——因?yàn)榧臃▋H定義給數(shù)字抄瑟。因此凡泣,任何在其它地方以非數(shù)值類型(如字符串或鏈表)作為參數(shù)來調(diào)用f的話,將會(huì)發(fā)出一個(gè)錯(cuò)誤皮假。
在代碼中數(shù)值鞋拟、字符串常量以及表達(dá)式,經(jīng)橙亲剩可以在詳細(xì)的前后文中暗示類型贺纲。例如,一個(gè)表達(dá)式3.14可暗示浮點(diǎn)數(shù)類型布轿;而[1, 2, 3]則可暗示一個(gè)整數(shù)的鏈表哮笆;通常是一個(gè)數(shù)組来颤。