在配置好環境以後,要研究的就是這個語言的語法了。在這篇文章中,做者但願能夠簡單的介紹一下Golang的各類語法,並與C和Java做一些簡單的對比以加深記憶。由於這篇文章只是入門Golang的第二篇文章,因此本文並不會對一些指令進行深挖,僅僅只是停留在「怎麼用」的程度,至於「爲何是這樣」,則涉及到了具體的應用場景和彙編指令,做者將會在之後的文章中進行介紹。php
總所周知,「Hello World」是程序員的一種儀式感。html
而這一行「Hello World」,必定會涉及到輸入輸出相關的方法。因此,如何導入包,是咱們須要研究的第一步。程序員
在C語言中,咱們使用include
,在Java中,咱們使用了import
。在Golang中也同樣,咱們使用import
引入其餘的包。在上一篇文章中,咱們已經提到了對於導入的包,編譯器會首先在GOROOT
中尋找,隨後會在項目所對應的GOPATH
中尋找,最後纔是在全局GOPATH
中尋找,若是都沒法找到,編譯器將會報錯。數據庫
注意,在Golang中和Java有一點很大的區別,就是在Golang中,import導入的是目錄,而不是包名。並且,Golang沒有強制要求包名和目錄名須要一致。
下面舉一些例子來講明在Golang中包名和目錄的關係,先來看看目錄結構:數組
能夠看出,咱們在src
下面設置了兩個文件夾,在第二個文件夾下面設置了兩個go文件。
來看看這兩個文件的代碼,test1.go以下:併發
package pktest func Func1() { println("這是第一個函數") }
test2.go以下:app
package pktest func Func2() { println("這是第二個函數") }
而後咱們再來看看testmain.go下面的內容:函數
package main import "package1/package2" func main() { pktest.Func1() }
注意到了嗎,咱們在調用Func1
這個函數的時候,使用的是pktest
,而不是咱們認爲的package1/package2
中的package2
。學習
按照咱們在Java中的思想,咱們應該是使用package2.Func1
的調用方法或者說是使用test1.Func1
這樣的方法。指針
這是由於在Golang中,沒有強制要求包名和目錄名稱一致。也就是說,在上面的例子中,咱們引用路徑中的文件夾名稱是package2
,而在這個文件夾下面的兩個文件,他們的包名,卻被設置成了pktest
。而在Golang的引用中,咱們須要填寫的是源文件所在的相對路徑。
也就是說,咱們能夠理解爲,包名和路徑實際上是兩個概念,文件名在Golang中不會被顯式的引用,一般的引用格式是packageName.FunctionName
。
結論以下:
import
導入的是源文件的相對路徑,而不是包名。以上部份內容摘自於這篇文章
看完了導包方面的內容,咱們再來看看如何聲明一個變量。在聲明變量這一部分,和C以及Java也有較大的區別。
咱們先定義一些變量看看:
var a int var b float32 var c, d float64 e, f := 9, 10 var g = "Ricardo"
咱們能夠看到,在Golang中定義一個變量,須要使用var
關鍵字,而與C或者Java不一樣的是,咱們須要將這個變量的類型寫在變量名的後面。不只如此,在Golang中,容許咱們一次性定義多個變量並同時賦值。
還有另外的一種作法,是使用:=
這個符號。使用了這個符號以後,開發者再也不須要寫var
關鍵字,只須要定義變量名,並在後面進行賦值便可。而且,Golang編譯器會根據後面的值的類型,自動推導出變量的類型。
在變量的定義過程當中,若是定義的時候就賦予了變量的初始值,是不須要再聲明變量的類型的,如變量g
。
注意,Golang是強類型的一種語言,全部的變量必須擁有類型,而且變量僅僅能夠存儲特定類型的數據。
標識符爲_(下劃線)的變量,是系統保留的匿名變量,在賦值後,會被當即釋放,稱之爲匿名變量。其做用是變量佔位符,對其變量賦值結構。一般會在批量賦值時使用。
例如,函數返回多個值,咱們僅僅須要其中部分,則不須要的使用_來佔位
func main() { // 調用函數,僅僅須要第二個返回值,第一,三使用匿名變量佔位 _, v, _ := getData() fmt.Println(v) } // 返回兩個值的函數 func getData() (int, int, int) { // 返回3個值 return 2, 4, 8 }
如上述代碼所示,若是我僅僅須要一個變量的值,就不須要去額外定義一些沒有意義的變量名了,僅僅只是須要使用佔位符這種「用後即焚」的匿名變量。
在Golang的常量定義中,使用const
關鍵字,而且不能使用:=
標識符。
咱們在使用Java或者C的時候,寫判斷語句是這樣的:
if(condition){ ... }
在Golang中,惟一的不一樣是不須要小括號,可是大括號仍是必須的。以下:
func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } return lim }
除去不須要寫小括號之外,Golang還容許在判斷條件以前執行一個簡單的語句,並用一個分號;
隔開。
在Golang中,只有一種循環,for循環。
和判斷語句同樣,在Golang中也是沒有小括號的。
func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) }
此外,在循環條件中,初始化語句和後置語句是可選的,這個時候把分號去掉,for循環
就變成了while循環
。
func main() { sum := 1 for sum < 1000 { sum += sum } fmt.Println(sum) }
不只如此,若是省略循環條件,該循環就不會結束,所以無限循環能夠寫得很緊湊,這個時候,和while(true)
的效果是同樣的。
func main() { for { ... } }
在Golang的函數定義中,全部的函數都以func
開頭,而且Golang命名推薦使用駝峯命名法。
注意,在Golang的函數中,若是首字母是小寫,則只能在包內使用;若是首字母是大寫,則能夠在包外被引入使用。能夠理解爲,使用小寫的函數,是`private`的,使用大寫的函數,是`public`的。
在Golang的函數定義中,同樣能夠不接受參數,或者接受多個參數。而在參數的定義過程當中,也是按照定義變量的格式,先定義變量名,再聲明變量類型。對於函數的返回類型,也是按照這樣的格式,先寫函數名,再寫返回類型:
func add(x int, y int) int { return x + y } func main() { fmt.Println(add(42, 13)) }
而且,對於相同類型的兩個參數,參數類型能夠只寫一個,用法以下:
func add(x, y int) int { return x + y }
在Golang中,對於函數的返回值,和C以及Java是不同的。
Golang中的函數能夠返回任意多個返回值。
例以下面的小李子,
func swap(x, y string) (string, string) { return y, x } func main() { a, b := swap("hello", "world") fmt.Println(a, b) }
其次,函數的返回值是能夠被命名的:
func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return }
在這裏,咱們能夠理解爲在函數的頂部預先定義了這些變量值,而空的return
語句則默認返回全部已經定義的返回變量。
在Golang中,有一個關鍵字叫defer
。
defer 語句會將函數推遲到外層函數返回以後執行。
推遲調用的函數其參數會當即求值,但直到外層函數返回前該函數都不會被調用。
func main() { defer fmt.Println("world") fmt.Println("hello") }
在這段代碼中,原本的執行路徑是從上往下,也就是先輸出「world」,而後再輸出「hello」。可是由於defer
這個關鍵字的存在,這行語句將在最後才執行,因此產生了先打印「hello」而後再打印「world」的效果。
注意,defer後面必須是函數調用語句,不能是其餘語句,不然編譯器會報錯。
能夠考慮到的場景是,文件的關閉,或數據庫鏈接的釋放等,這樣打開和關閉的代碼寫在一塊兒,既可使得代碼更加的整潔,也能夠防止出現開發者在寫了長長的業務代碼後,忘記關閉的狀況。
至於defer的底層實現,本文不進行詳細的解釋,簡單來說就是將defer語句後面的函數調用的地址壓進一個棧中,在當前的函數執行完畢,CPU即將執行函數外的下一行代碼以前,先把棧中的指令地址彈出給CPU執行,直到棧爲空,才結束這個函數,繼續執行後面的代碼。
從上文剛剛的表述中也能夠推斷出,若是有多條refer語句,將會從下往上依次執行。
由於本文只是對各類指令簡單的進行對比,因此對於refer的詳細解釋,將在之後的文章中詳細說明。
對於指針,若是是C或者C++開發者,必定很熟悉;而對於Java開發者,指針是對開發者透明的一個東西,一個對象會在堆中佔據必定的內存空間,而在當前的棧楨中,有一個局部變量,他的值就是那個對象的首地址,這也是一個指針。
能夠說,指針就是開發者訪問內存的一種途徑,只不過是由控制權交給了開發者仍是虛擬機。
在Golang中,指針的用法和 C 是同樣的。一樣是用&
取地址,用*
取地址中的值。
可是,與 C 不一樣,Golang沒有指針運算。
在Golang中,數組的定義是這樣的:
var a [10]int
這樣作會將變量 a 聲明爲擁有 10 個整數的數組。
注意,在Golang中,數組的大小也一樣和 C 語言同樣不能改變。
數組的切片,顧名思義,就是將一個數組按需切出本身所需的部分。
每一個數組的大小都是固定的。而切片則爲數組元素提供動態大小的、靈活的視角。在實踐中,切片比數組更經常使用。
切片經過兩個下標來界定,即一個上界和一個下界,兩者以冒號分隔:
a[low : high]
它會選擇一個半開區間,包括第一個元素,但排除最後一個元素。
如下表達式建立了一個切片,它包含 a 中下標從 1 到 3 的元素:
a[1:4]
舉個例子:
func main() { str := [4]string{ "aaa", "bbb", "ccc", "ddd", } fmt.Println(str) a := str[0:2] b := str[1:3] fmt.Println(a, b) b[0] = "XXX" fmt.Println(a, b) fmt.Println(str) }
咱們定義了一個數組,裏面含有"aaa","bbb","ccc","ddd"四個元素。而後咱們定義了兩個切片,a
和b
,根據定義能夠知道,a
爲"aaa"和"bbb",b
爲"bbb"和"ccc"。
這個時候,咱們把b[0]改爲了"XXX",那麼b
變成了"XXX"和"ccc",這是毋庸置疑的。可是與直覺相違背的是,這個時候的數組str
,也變成了"aaa","XXX","ccc","ddd"。
這是由於,Golang中的切片,不是拷貝,而是定義了新的指針,指向了原來數組所在的內存空間。因此,修改了切片數組的值,也就相應的修改了原數組的值了。
此外,切片能夠用append增長元素。可是,若是此時底層數組容量不夠,此時切片將會指向一個從新分配空間後進行拷貝的數組。
所以能夠得出結論:
切片能夠用內建函數 make 來建立,這也是你建立動態數組的方式。
在此以前須要解釋兩個定義,len(長度)和cap(容量)。 len是數組的長度,指的是這個數組在定義的時候,所約定的長度。 cap是數組的容量,指的是底層數組的長度,也能夠說是原數組在內存中的長度。 在前文中所提到的切片,若是我定義了一個str[0,0]的切片,此時的長度爲0,可是容量依舊仍是5。
make 函數會分配一個元素爲零值的數組並返回一個引用了它的切片:
a := make([]int, 5) // len(a)=5
要指定它的容量,需向 make 傳入第三個參數:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5 b = b[:cap(b)] // len(b)=5, cap(b)=5 b = b[1:] // len(b)=4, cap(b)=4
也就是說,make函數能夠自定義切片的大小。用Java的話來講,他能夠被重載。
有兩種形式,若是隻有兩個參數,第一個參數是數組內元素的類型,第二個參數是數組的長度(此時長度和容量都爲5)。
而若是有第三個參數,那麼第三個參數能夠指定數組的容量,便可以指定這個數組在內存中分配多大的空間。
首先,謝謝你能看到這裏。
若是這篇文章對你能起到哪怕一點點的幫助,做者都會很開心!
其次要說明的是,做者也是剛開始接觸Golang,寫這篇文章的目的是起到一個筆記的效果,可以去比較一些C,Java,Golang中的語法區別,也必定會有很多的認知錯誤。若是在這篇文章中你看到了任何與你的認識有差距的地方,請必定指出做者的錯誤。若是本文有哪些地方是做者講的不夠明白的,或者是你不理解的,也一樣歡迎留言,一塊兒交流學習進步。
並且在本文中,不少地方沒有進行深刻挖掘,這些做者都有記錄,而且打算在以後的文章中,也會從源碼的角度出發,分析這些緣由。在這篇文章中,就只是單純的學會怎麼用,就達到目的了。
那麼在最後,再次感謝~
PS:若是有其餘的問題,也能夠在公衆號找到做者。而且,全部文章第一時間會在公衆號更新,歡迎來找做者玩~