前言
學習一下go 語言,也不完全是go,幾乎是所以語言通用的部分,主要在於鞏固一下基礎,幾乎不會涉及到語法相關的東西。
正文
前置內容
說起語言,很多人喜歡談論解釋型語言和編譯型語言,其實對語言談論編譯型還是解釋型語言是沒有意義的,也不知道當時是誰提出這個概念的,是圖啥呢?
c語言也有解釋器(http://www.softintegration.com/),難道就可以把c語言定義為既可以是編譯型也是解釋型語言。
同樣python 難道就不能被編譯嗎? 參考.net平台,可以編譯成IL語言。同樣c# 也有自己的解析器啊,以此為例的例子很多。
以此為衍生的問題就會有編譯型語言就是要編譯成可執行的語言,然后另外一個人又出來反駁,編譯型可以編譯成另外一種中間語言不一定要是直接可執行的。然后解釋型的爭論又來了,就是解釋一句執行,不需要編譯,然后另外一個人提出。。。。
至此無休無止,所以忘卻這個概念吧,如果有人談論到這個話題,就盡量不要參和,因為這是一個語言開發者都不會關心的問題,人家只關心解決什么樣的問題或者什么樣的痛點。
拋開這個話題,那么有一些概念倒是實打實存在的。
編譯器:
一個編譯器就是一個程序,他可以閱讀某一種語言編寫的程序,並把程序翻譯成一個等價的、用另外一種語言編寫的程序。
這里面有幾個關鍵字,等價的、另一種語言。其實這就是一個翻譯過程,比如說英文翻譯成中文,那么是由一種語言轉換為另外一種語言。
那么這個等價的是什么意思呢? 其實它指的是這樣的,比如說英文說你好,那么轉換中文的意思也應該是你好,這就是等價的,這里而不是完全相等的意思。
為什么不是完全相等的呢?其實是這樣的,比如說英文的hello,其實是打招呼的意思,而中文的你好也是打招呼的意思,所以是等價的。
同樣在計算機中,翻譯過程中執行邏輯可能不一樣,比如說我們的c = a - b,我們理解的是a=10,b=20,然后c=-10,編寫高級語言的時候一般是這樣理解,人類思維嘛。
但是呢,我們知道計算機中二進制沒有減法這個概念的,所以呢,計算機的處理邏輯和我們的邏輯不一樣,但是其還是會輸出-10,這其實就是等價的意思,結果相同,並不是說翻譯后邏輯會一致,而是結果會一致。
解釋器:
它並不會通過翻譯的方式生成目標程序。從用戶的角度上看,解釋器直接利用用戶提供的輸入執行源程序中的指定操作。
在用戶輸入映射成為輸入的過程中,由一個編譯器產生的機器語言目標程序比一個解釋器快很多。然而,解釋器的錯誤診斷通常比編譯器更好,因為它是逐條語句執行源程序。
那么為什么我們為啥一般不用c 語言和 c++ 去編寫web 響應程序呢?這里一個講究的是開發效率,還有一個最重要的是影響用戶體驗的是網速,因為網絡之間的時間遠大於代碼執行時間。
java 語言處理器結合了編譯和解釋的過程。一個java程序首先被編譯成一個稱為字節碼的中間表達形式。然后由一個虛擬機對得到的字節碼加以解釋執行。
采用這種方式的好處是可以跨平台,因為是字節碼是中間語言,說到底對計算機來說就是文本,可以理解為一篇文章。那么只要有程序能夠閱讀這篇文章,那么這篇文章就是被運行了。
這樣使得java 跨平台了,只要在不同平台安裝指定jvm,那么這個jvm相當於閱讀器,那么字節碼就可以被運行了,實際上是字節碼跨平台了。相同的,為啥c# 可以很容易跨平台呢?
其實是因為c# 翻譯成IL語言,然后IL語言是在net平台上運行的,加入net 平台能夠在linux 上運行,那么IL就可以在linux上運行,同樣的間接的c# 就跨平台了。
為了更快的完成輸入到輸入的處理,有些被稱為即時編譯器的java 編譯器在運行中間程序處理輸入的前一刻首先把字節碼翻譯成機器語言,然后再執行程序。
所以這里又出現了一個即時編譯的概念,這個可以參考jit,也就是java 中的即時編譯。因為這個非兩句話可以說清,所以暫時略過。
那么對於c++ 或者 c 語言,以及后面要介紹的go,他們是如何創建一個可執行程序的呢?是不是直接通過編譯器編譯成一個可執行程序呢?
當然不是。
首先其要進行預處理,那么預處理干些什么呢? 看下c 語言的預處理:
預處理是C語言的一個重要功能,它由預處理程序負責完成。
當對一個源文件進行編譯時,系統將自動引用預處理程序對源程序中的預處理部分作處理,處理完畢自動進入對源程序的編譯。
C語言提供多種預處理功能,主要處理#開始的預編譯指令,如宏定義(#define)、文件包含(#include)、條件編譯(#ifdef)等。合理使用預處理功能編寫的程序便於閱讀、修改、移植和調試,也有利於模塊化程序設計。
可能有些人還是有點蒙,那么直接看其到底做什么吧。
在集成開發環境中,編譯,鏈接是同時完成的。其實,C語言編譯器在對源代碼編譯之前,還需要進一步的處理:預編譯。預編譯的主要作用如下:
1、將源文件中以”include”格式包含的文件復制到編譯的源文件中。
2、用實際值替換用“#define”定義的字符串。
3、根據“#if”后面的條件決定需要編譯的代碼。
經過預處理后,然后就可以進行編譯了,編譯后一般目標代碼是匯編,因為匯編語言比較容易調試和輸出。
其實是這樣的,機器語言編寫是非常困難的,后面才有了匯編,如果高級語言直接編譯成機器語言,可想而知還得把原來匯編的工作量做一遍,還不如轉成匯編,然后匯編有自己的匯編器進行處理。
我們在轉成匯編后,那么匯編器會把匯編代碼轉成可重定位的機器代碼。注意,這里是可重位的機器代碼。
什么是可重位的機器代碼?這東西還和操作系統有關。
我們知道在單道程序的時候,用戶還在寫二進制的時候呢,用戶編寫的物理地址是寫死的。
哎,這就有人問了,我們的程序里面不都是變量嗎?哪來的固定的物理地址呢。舉一個簡單的例子啊,比如說我們程序要調用一個方法。
那么問題來了,它是如何調用的?是啊,憑什么程序能夠調用一個方法呢?它怎么這么聰明呢。其實吧,程序調用一個方法,相當於執行一條指令。
這個指令包括了跳轉的命令和具體的地址,以及參數。是啊,如果一個方法沒有具體的地址,那么程序怎么知道怎么運行呢,所以可重位的機器代碼有的方法的物理位置就是確定的了。
這些地址是固定的,但是我們知道程序在運行前是不會知道自己的內存位置在什么地方,因為這是操作系統分配的。
這么這個可重位的機器代碼 的意思是這樣的,比如可重位的機器代碼里面的地址是180,操作系統是從4000開始給這個程序分配物理地址的,那么可重位的機器代碼在運行的時候就會給180加上4000,在4180的物理位置進行操作。
當然這是舉一個例子了,也不一定是180+4000這樣讓程序去算,可能是cpu去搞定。
1、靜態重定位:即在程序裝入內存的過程中完成,是指在程序開始運行前,程序中的各個地址有關的項均已完成重定位,地址變換通常是在裝入時一次完成的,以后不再改變,故成為靜態重定位。
2、動態重定位:它不是在程序裝入內存時完成的,而是CPU每次訪問內存時 由動態地址變換機構(硬件)自動進行把相對地址轉換為絕對地址。動態重定位需要軟件和硬件相互配合完成。
上面這兩個就是重定位方式,第二種里面是非常復雜的,只需理解什么是可重位就行。
那么下面就介紹鏈接器了。
什么是鏈接器呢?
鏈接器的功能是,將一個或多個編譯器生成的目標文件及庫鏈接為一個可執行文件。
我們知道我們編譯器是編譯的是我們當前開發的模塊,並不包含我們引用的庫的編譯,因為其早就編譯好了。
而且我們有沒有發現,其實我們調用庫方法的時候,我們看到的是聲明而不是源碼。這就是讓我們編譯器可以通過,因為編譯器只要檢查聲明符合即可。
但是我們知道只有申明是無法運行的,還需要鏈接器將其鏈接起來。雖然這樣說鏈接器,不太准確,姑且這么理解,后面獨立系列補充。
那么編譯器又包括了解析器,解析器其包含了詞法分析器、語法分析器:
其主要作用是進行語法分析,提取出句子的結構。廣義來說輸入一般是程序的源碼,輸出一般是語法樹(syntax tree,也叫parse tree等)或抽象語法樹(abstract syntax tree,AST)。
進一步剝開來,廣義的解析器里一般會有掃描器(scanner,也叫tokenizer或者lexical analyzer,詞法分析器),以及狹義的解析器(parser,也叫syntax analyzer,語法分析器)。
掃描器的輸入一般是文本,經過詞法分析,輸出是將文本切割為單詞的流。狹義的解析器輸入是單詞的流,經過語法分析,輸出是語法樹或者精簡過的AST。
編譯器還包括了語義分析器、中間代碼生成器、代碼優化器、代碼生成器。
總之,編譯器會將我們的code 轉換成目標語言,像c++和c這種目標語言就是匯編語言了。
go 語言相關
說到go語言,那么其是和c++ 一樣編譯 + 匯編 + 鏈接器可以生成可執行文件的語言。
也沒有去看go語言是不是有解釋器哈,但是其主流還是編程成二進制的語言哈,那么其效率是要比在解釋器上運行是要快一些的。
那么其如果用go語言去開發http交互應用意義應該不大,因為其主要是網絡因素,你說運行效率高也就多台機器的問題了。
那么go語言適合做什么呢? 比較出名的就是docker了,docker 這種肯定不會用java去寫的,因為jvm 就讓人背負不起。
其應該是替代了原先c++ 和c開發的一些功能,因為其具備垃圾回收,開發起來更加迅速,出錯率也低上一些,且執行效率又比其他基於虛擬機要高很多。
go 語言開發環境的安裝:
https://www.runoob.com/go/go-environment.html
然后安裝后,看下其安裝的目錄,一般叫做goroot哈。
然后看一下go root下面的bin目錄下,bin目錄下面一般是可執行文件,一般其實是工具類。
那么看下這兩個是用來干什么的哈。
運行:
go help
上面說這個go.exe 是一個工具用來管理go源碼的。
可以看到有這樣一些功能哈。
如果想知道具體,那么可以通過go help
讓我奇怪的是有一個gopath的目錄。而且還要一個環境配置,后來我就去查了一下。
我也是一名go初學者,不知道理解的對不對。
它上面說用於解決聲明問題,會不會是可以引用其目錄下的庫文件的呢?這里只是猜測,接着往下看。
下面的文件是go build 生成的。
GOPATH環境變量列出了查找Go代碼的位置。
然后gopath 可以是多個,在windows 下面用逗號隔開。
如果環境沒有設置,然后就是在windows 在默認就是當前用戶下的go目錄下。
然后go env GOPATH 可以查看當前的GOPATH 。
然后給了我們這個 https://golang.org/wiki/SettingGOPATH 告訴我們去看一下怎么設置哈。
在GOPATH中列出的每個目錄必須有一個規定的結構:
src 保存源碼,在src目錄下確定了路徑和可執行文件名。
pkg目錄存放已安裝的包對象。 在Go樹中,每個目標操作系統和 體系結構對有自己的PKG子目錄 (包裹/ GOOS_GOARCH)。
bin目錄保存已編譯的命令。然后就嘰嘰歪歪舉了一些例子。
Each command is named for its source directory, but only
the final element, not the entire path. That is, the
command with source in DIR/src/foo/quux is installed into
DIR/bin/quux, not DIR/bin/foo/quux. The "foo/" prefix is stripped
so that you can add DIR/bin to your PATH to get at the
installed commands. If the GOBIN environment variable is
set, commands are installed to the directory it names instead
of DIR/bin. GOBIN must be an absolute path.
估計就是可以項目直接互相引用的。因為還沒用到,那么暫且知道這樣一個概念,等用到了估計就清晰了。
結
持續更新,一周一到兩節,學習記錄。下一節命名源碼文件。