Golang入門(2):一天學完GO的基本語法

摘要

在配置好環境以後,要研究的就是這個語言的語法了。在這篇文章中,做者但願能夠簡單的介紹一下Golang的各類語法,並與C和Java做一些簡單的對比以加深記憶。由於這篇文章只是入門Golang的第二篇文章,因此本文並不會對一些指令進行深挖,僅僅只是停留在「怎麼用」的程度,至於「爲何是這樣」,則涉及到了具體的應用場景和彙編指令,做者將會在之後的文章中進行介紹。php

1 導包

總所周知,「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導入的是源文件的相對路徑,而不是包名。
  • 在習慣上將包名和目錄名保證一致,但這並非強制規定(但不建議這麼作,這樣容易形成調用這個包的人,沒法快速知道這個包的名稱是什麼)
  • 在代碼中引用包內的成員時,使用包名而不是目錄名。
  • 在一個文件夾內,只能存在一種包名,源文件的名稱也沒有其餘的限制。
  • 若是多個文件夾下有相同名字的package,它們實際上是彼此無關的package。

以上部份內容摘自於這篇文章

2 聲明

看完了導包方面的內容,咱們再來看看如何聲明一個變量。在聲明變量這一部分,和C以及Java也有較大的區別

2.1 變量的定義

咱們先定義一些變量看看:

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是強類型的一種語言,全部的變量必須擁有類型,而且變量僅僅能夠存儲特定類型的數據。

2.2 匿名變量

標識符爲_(下劃線)的變量,是系統保留的匿名變量,在賦值後,會被當即釋放,稱之爲匿名變量。其做用是變量佔位符,對其變量賦值結構。一般會在批量賦值時使用。
例如,函數返回多個值,咱們僅僅須要其中部分,則不須要的使用_來佔位

func main() {
  // 調用函數,僅僅須要第二個返回值,第一,三使用匿名變量佔位
  _, v, _ := getData()
  fmt.Println(v)
}
// 返回兩個值的函數
func getData() (int, int, int) {
  // 返回3個值
  return 2, 4, 8
}

如上述代碼所示,若是我僅僅須要一個變量的值,就不須要去額外定義一些沒有意義的變量名了,僅僅只是須要使用佔位符這種「用後即焚」的匿名變量。

2.3 常量

在Golang的常量定義中,使用const關鍵字,而且不能使用:=標識符。

3 判斷

咱們在使用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還容許在判斷條件以前執行一個簡單的語句,並用一個分號隔開。

4 循環

在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 {
	    ...
	}
}

5 函數

5.1 函數的定義

在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語句則默認返回全部已經定義的返回變量。

5.2defer

在Golang中,有一個關鍵字叫defer

defer 語句會將函數推遲到外層函數返回以後執行。
推遲調用的函數其參數會當即求值,但直到外層函數返回前該函數都不會被調用。

func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}

在這段代碼中,原本的執行路徑是從上往下,也就是先輸出「world」,而後再輸出「hello」。可是由於defer這個關鍵字的存在,這行語句將在最後才執行,因此產生了先打印「hello」而後再打印「world」的效果。

注意,defer後面必須是函數調用語句,不能是其餘語句,不然編譯器會報錯。

能夠考慮到的場景是,文件的關閉,或數據庫鏈接的釋放等,這樣打開和關閉的代碼寫在一塊兒,既可使得代碼更加的整潔,也能夠防止出現開發者在寫了長長的業務代碼後,忘記關閉的狀況。

至於defer的底層實現,本文不進行詳細的解釋,簡單來說就是將defer語句後面的函數調用的地址壓進一個棧中,在當前的函數執行完畢,CPU即將執行函數外的下一行代碼以前,先把棧中的指令地址彈出給CPU執行,直到棧爲空,才結束這個函數,繼續執行後面的代碼。

從上文剛剛的表述中也能夠推斷出,若是有多條refer語句,將會從下往上依次執行。

由於本文只是對各類指令簡單的進行對比,因此對於refer的詳細解釋,將在之後的文章中詳細說明。

6 指針

對於指針,若是是C或者C++開發者,必定很熟悉;而對於Java開發者,指針是對開發者透明的一個東西,一個對象會在堆中佔據必定的內存空間,而在當前的棧楨中,有一個局部變量,他的值就是那個對象的首地址,這也是一個指針。

能夠說,指針就是開發者訪問內存的一種途徑,只不過是由控制權交給了開發者仍是虛擬機。

在Golang中,指針的用法和 C 是同樣的。一樣是用&取地址,用*取地址中的值。

可是,與 C 不一樣,Golang沒有指針運算。

7 數組

在Golang中,數組的定義是這樣的:

var a [10]int

這樣作會將變量 a 聲明爲擁有 10 個整數的數組。

注意,在Golang中,數組的大小也一樣和 C 語言同樣不能改變。

7.1切片

數組的切片,顧名思義,就是將一個數組按需切出本身所需的部分。

每一個數組的大小都是固定的。而切片則爲數組元素提供動態大小的、靈活的視角。在實踐中,切片比數組更經常使用。

切片經過兩個下標來界定,即一個上界和一個下界,兩者以冒號分隔:

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"四個元素。而後咱們定義了兩個切片,ab,根據定義能夠知道,a爲"aaa"和"bbb",b爲"bbb"和"ccc"。

這個時候,咱們把b[0]改爲了"XXX",那麼b變成了"XXX"和"ccc",這是毋庸置疑的。可是與直覺相違背的是,這個時候的數組str,也變成了"aaa","XXX","ccc","ddd"。

這是由於,Golang中的切片,不是拷貝,而是定義了新的指針,指向了原來數組所在的內存空間。因此,修改了切片數組的值,也就相應的修改了原數組的值了。

此外,切片能夠用append增長元素。可是,若是此時底層數組容量不夠,此時切片將會指向一個從新分配空間後進行拷貝的數組。

所以能夠得出結論:

  • 切片並不存儲任何數據,它只是描述了底層數組中的一段。
  • 更改切片的元素會修改其底層數組中對應的元素。
  • 與它共享底層數組的切片都會觀測到這些修改。

7.2 make

切片能夠用內建函數 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:若是有其餘的問題,也能夠在公衆號找到做者。而且,全部文章第一時間會在公衆號更新,歡迎來找做者玩~

相關文章
相關標籤/搜索