一、Go語言基礎
1. 基礎
Go語言中的標識符必須以字母(Unicode字母,PHP/JS可以用中文作為變數名)底線開頭。大寫字母跟小寫字母是不同的:Hello和hello是兩個不同的名字。
Go中有25個關鍵字:
break default func interface selectcase defer go map structchan else goto package switchconst fallthrough if range typecontinue for import return var
如果一個名字是在函數內容定義,那麼它的範圍就在函數內容,如果在函數外部定義,那麼將在當前包的所有檔案中都可以訪問。名字的開頭字母的大小寫決定了名字在包外的可見度。如果一個名字是大寫字母開頭,那麼它可以被外部的包訪問,例如fmt包的Printf函數。
2. 注釋
- 單行注釋 // ...
- 多行注釋 /* ... */
3. Go程式的一般結構
- Go程式是通過package來組織的
- 只有package名稱為main的包可以包含main函數
- 一個可執行程式有且只有一個main包
// 當前程式的包名package main// 匯入其他的包import "fmt"// 一次匯入多個包import ( "io" "os")// 常量的定義const PI = 3.14// 全域變數的聲明與賦值,該變數在整個package中使用var name = "go"// 一般型別宣告type newType int// 結構的聲明type go struct{}// 介面的聲明type golang interface{}// 由main函數作為程式入口啟動func main() { fmt.Println("Hello World!")}
- 如果匯入的包但是沒有用到類型或者函數則會報編譯錯誤
- package別名: import io "fmt" 這樣將fmt設定一個別名為io,調用的時候就可以這樣: io.Println("...")
4. 可見度規則
Go語言中使用大小寫來決定該常量、變數、類型、介面、結構或函數是否可以被外部包所調用: 根據約定,函數名首字母小寫即為private(外部不可以調用),大寫為public
二、基礎資料型別 (Elementary Data Type)
1、變數和常量
// var 變數名稱 變數類型 = 值var num int = 1
var num1,num2 int = 1, 2
var ( num1 int = 1 num2 int = 2)
2、整數類型的命名和寬度
Go的整數類型一共有10個
其中計算架構相關的整數類型有兩個: 有符號的整數類型 int, 無符號的整數類型 uint。
在不同計算架構的電腦上,它們體現的寬度(儲存某個類型的值所需要的空間)是不一樣的。空間的單位可以是bit也可以是位元組byte。
除了這兩個計算架構相關的整數類型之外,還有8個可以顯式表達自身寬度的整數類型:
3、整數類型值的標記法
如果以8進位為變數num賦值:
num = 039 // 用"0"作為首碼以表明這是8進位標記法
如果以16進位為變數num賦值:
num = 0x39
4、浮點數類型
浮點數類型有兩個:float32/float64 浮點數類型的值一般由整數部分、小數點"."和小數部分組成。另外一種表示方法是在其中加入指數部分。指數部分由"E"或"e"以及帶加號或減號的10進位整數表示。例:3.9E-2表示浮點數0.039。3.9E+1表示浮點數39。
有時候浮點數類型值也可以被簡化。比如39.0可以被簡化為39。0.039可以被簡化為.039。 在Go中浮點數的相關部分只能由10進位標記法表示。
5、複數類型
複數類型有兩個:complex64和complex128。實際上,complex64類型的值會由兩個float32類型的值分別表示複數的實數部分和虛數部分。而complex128類型的值會由兩個float64類型的值表示複數的實數部分和虛數部分。
負數類型的值一般由浮點數表示的實數部分、加號"+"、浮點數表示的虛數部分以及小寫字母"i"組成,比如3.9E+1 + 9.99E-2i。
6、byte與rune
byte與rune都屬於別名資料型別。byte是uint8的別名資料型別,而rune是int32的別名資料型別。
一個rune的類型值即可表示一個Unicode字元。一個Unicode代碼點通常由"U+"和一個以十六進位標記法表示的整數表示,例如英文字母'A'的Unicode代碼點為"U+0041"。
rune類型的值需要由單引號"'"包裹,不過我們還可以用另外幾種方式表示:
另外在rune類型值的表示中支援幾種特殊的字元序列,即:轉義符。如:
7、字串類型
字串的標記法有兩種,即:原生標記法和解釋型標記法。原生標記法,需用用反引號"`"把字元序列包起來,如果用解釋型標記法,則需要用雙引號"""包裹字元序列。
var str1 string = "str"var str1 string = `str`
二者的區別是,前者表示的是所見即所得 (WYSIWYG)的(除了斷行符號符)。後者所表示的值中轉義符會起作用。字串值是不可變的,如果我們建立了一個此類型的值,就不可能再對它本身做任何修改。
三、進階資料類型
1、數群組類型
一個數組是可以容納若干相同類型的元素的容器。數組的長度是固定的。如下聲明一個數群組類型:
type MyNumbers [3]int
型別宣告語句由關鍵字type、類型名稱和類型字面量組成
上面這條型別宣告語句實際上是為數群組類型[3]int聲明了一個別名資料型別。這使得我們可以把MyNumbers當作數群組類型[3]int來使用。
我們表示這樣一個數群組類型的值的時候。應該把該類型的類型字面量寫在最左邊,然後用花括弧包裹該值包含的若干元素,各元素之間以(英文半形)逗號分割,即:
[3]int{1,2,3}
現在我們把這個數組字面量賦給一個名為numbers的變數:
var numbers = [3]int{1,2,3}
這是一條變數聲明語句,它在聲明變數的同時為該變數賦值
另一種方式是在其中的類型字面量中省略代表其長度的數組,例:
var numbers = [...]int{1,2,3}
可以用如下方式訪問該變數中的任何一個元素。例:
numbers[0]numbers[1]numbers[2]
如果要修改數組值中的某一個元素值,可以:
numbers[1] = 4
可以用如下方式擷取數組長度:
var length = len(numbers)
如果一個數組沒有賦值,則它的預設值為[length]type{0,0,0...}
2、切片類型
(1) 基礎
切片(slice)與數組一樣也是可以若干相同類型元素的容器。與數組不同的是切片類型的長度不確定。每個切片值都會將數組作為其底層資料結構。表示切片類型的字面量如:
[]int
或者是:
[]string
切片類型的聲明可以這樣:
type MySlice []int
對切片值的表示也與數組值相似
[]int{1,2,3}
運算元組值的方法同樣適用於切片值。還有一種運算元組的方式叫做“切片”,實施切片操作的方式就是切片運算式。例:
var number3 = [5]int{1,2,3,4,5}var slice1 = numbers3[1:4]
上例中切片運算式numbers3[1:4]的結果為[]int{2,3,4}很明顯被切下的部分不包含元素上界索引指向的元素。實際上slice1這個切片值的底層數組正是number3的值。
我們也可以在切片值上實施切片操作:
var slice2 = slice1[1:3]
除了長度切片值以及數組值還有另外一個屬性--容量。數組的容量總是等於其長度,而切片值的容量往往與其長度不同。如:
,一個切片值的容量即為它的第一個元素值在其底層數組中的索引值與該數組長度的差值的絕對值。可以使用cap()內建函數擷取數組、切片、通道類型的值的容量:
var capacity2 int = cap(slice2)
切片類型屬於參考型別,它的零值即為nil,即空值。如果我們只聲明了一個切片類型而不為它賦值,則它的預設值:nil。
(2) 切片的更多操作方法
有些時候我們可以在方括弧中放入第三個正整數。如所示:
numbers3[1:4:4]
第三個正整數為容量上界索引,它意義在於可以把作為結果的切片值的容量設定的更小。它可以限制我們通過這個切片值對其底層數組中的更多元素的訪問。上節中numbers3和slice的指派陳述式如下:
var numbers3 = [5]int{1,2,3,4,5}var slice1 = numbers3[1:4]
這時,變數slice1的值是[]int{2,3,4}。但是我們可以通過如下操作將其長度延展與其容量相同:
slice1 = slice1[:cap(slice1)]
通過此操作,變數slice1的值變為了[]int{2,3,4,5},且其長度和容量均為4。現在number3的值中的索引值在(1,5)範圍內的元素都被體現在了slice1的值中。這是以number3的值是slice1的值的底層數組為前提的。這意味著我們可以輕而易舉地通過切片訪問其底層數組中對應索引值更大的更多元素。如果我們編寫的函數返回了這樣一個切片值,那麼得到它的程式很可能會通過這種技巧訪問到本不應該暴露給它的元素。
如果我們在切片中加入了第三個索引(即容量上限索引),如:
var slice1 = numbers3[1:4:4]
那麼在此之後,我們將無法通過slice1訪問到number3的值中的第五個元素。
雖然切片值在上述方面受到了其容量的限制。但是我們可以通過另外一種手段對其進行不受限制的擴充。這需要用到內建函數append。append會對切片值進行擴充並返回一個新的切片值,使用方法如下:
slice1 = append(slice1, 6, 7)
通過上述操作,slice1的值變為了[]int{2,3,4,6,7}。一旦擴充操作超出了被操作的切片值的容量,那麼該切片的底層數組就會被替換 最後一種操作切片的方式是“複製”。該操作的實施方法是調用copy函數。該函數接收兩個類型相同的切片值作為參數,並把第二個參數值中的元素複製到第一個參數值中的相應位置(索引值相同)上。這裡有兩點需要注意:
- 這種複製遵循最小複製原則,即:被複製的元素的個數總是等於長度較短的那個參值的長度。
- 與append函數不同,copy函數會直接對其第一個參數值進行修改。
例:
var slice4 = []int{0,0,0,0,0,0}copy(slice4, slice1)
通過上述複製操作,slice4會變成[]int{2,3,4,6,7,0,0}。
3、字典類型
Go語言的字典(Map)類型是一個雜湊表的實現。字典類型的字面量如下:
map[K]T
其中,"K"為鍵的類型,而"T"則代表元素(值)的類型。如果我們描述一個鍵類型為int,實值型別為string的字典類型的話:
map[int]string
字典的鍵類型必須是可比較的,否則會引起錯誤,即鍵不能是切片、字典、函數類型
字典值的字面量標記法實際上與數組的切片的字面量標記法很相似。最左邊仍然是類型字面量,右邊緊挨著由花括弧包裹且有英文逗號分隔的索引值對。每個索引值對的鍵和值之間由冒號分隔。以字典類型map[int]string為例。他的值的字面量可以是這樣的:
map[int]string{1:"a",2:"b"m,3:"c"}
我們可以把這個值賦給一個變數
mm := map[int]string{1:"a",2:"b",3:"c"}
可用索引運算式取出字典中的值:
b := mm[2]
可以用索引運算式賦值:
mm[2] = b + "2"
這樣mm中鍵為2的值變為了"b2"。可以用如下方式向字典中添加一個索引值對:
mm[4] = ""
對於字典值來說,如果指定鍵沒有對應的值則預設為該類型的空值。所以mm[5]會返回一個""。但是這樣的話我們就不知道mm[5]到底是""還是mm[5]沒有這個值。所以go提供了另外一種寫法:
e, ok := mm[5]
針對字典的索引運算式可以有兩個求職結果,第二個求職結果是bool類型的。它用於表明字典值中是否存在指定的索引值對。 從字典中刪除索引值對的方法非常簡單,僅僅是調用內建函數delete:
delete(mm, 4)
無論mm中是否存在以4為鍵的索引值對,delete都刪除。 字典類型屬於參考型別,它的零值即為nil
4、通道類型
通道(Channel)是Go語言中一種非常獨特的資料結構。它可用於在不同Goroutine之間傳遞類型化的資料。並且是並發安全的。相比之下,之前幾種資料類型都不是並發安全的。
Goroutine可以被看作是承載可被並發執行的代碼塊的載體。它們由Go語言的運行時系統調度,並依託作業系統線程(又稱核心線程)來並發地執行其中的代碼塊。
通道類型的表示方法很簡單,僅由兩部分組成:
chan T
在這個類型字面量中,左邊是代表通道類型的關鍵字chan,而右邊則是一個可變的部分,即代表該通道類型允許傳遞的資料的類型(或稱通道的元素類型)。
與其他的資料類型不同,我們無法表示一個通道類型的值,因此,我們無法用字面量來為通道類型的變數賦值。只能通過調用內建函數make來達到目的。make參數可接受兩個參數,第一個參數是代表了將被初始化的值的類型的字面量(例: chan int),而第二個參數則是值的長度,例如,若我們想要初始化一個長度為5且元素類型為int的通道值,則需要這樣寫:
make(chan int, 5)
make函數也可以被用來初始化切片類型或字典類型的值。
暫存在通道值中的資料是先進先出。下面,我們聲明一個通道類型的變數,並為其賦值:
ch1 := make(chan string, 5)
這樣一來,我們就可以使用接受操作符<-向通道值發送資料了。當然,也可以使用它從通道值接收資料,例如,如果我們要向通道ch1 發送字串"value1",那麼應該這樣做:
ch1 <- "value1"
如果我們從ch1那裡接收字串,則要這樣:
<- ch1
我們可以把接受到字串賦給一個變數,如:
value := <- ch1
與針對字典值的索引運算式一樣,針對通道值的接受操作也可以有第二個結果值:
value, ok := <- ch1
這裡的ok的值是bool類型的。它代表了通道值的狀態,true代表通道值有效,而false則代表通道值已無效(或稱已關閉),更深層次的原因是,如果在接受操作進行之前或過程中通道值被關閉了,則接收操作會立即結束並返回一個該通道值的元素類型的零值。
可以通過函數close來關閉通道:
close(ch1)
對通道值的重複關閉會引發運行時異常,會使程式崩潰。在通道值有效前提下,針對它的發送操作會在通道值已滿(其中緩衝的資料的個數已等於它的長度)時被阻塞。而向一個已被關閉的通道值發送資料會引發運行時異常。針對有效通道值的接收操作會在它已經為空白時被阻塞。通道類型屬於參考型別,它的零值為nil。
5、通道的更多種類
上一節中的通道實際上只是Go中的通道的一種。是帶緩衝的通道,或者簡稱為緩衝通道。
通道有緩衝和非緩衝之分,緩衝通道中可以緩衝N個資料,我們在初始化一個通道值的時候必須指定這個N,**相對的非緩衝通道不會緩衝任何資料,發送方在向通道發送資料的時候會立即被阻塞。直到有一個接收方已從該通道中接收了這條資料。**非緩衝的通道值的初始化方法如下:
make(chan int, 0)
除了上述分類方法,我們還可以以資料在通道中的傳輸方向為依據來劃分通道。預設情況下,通道都是雙向的,即雙向通訊。如果資料只能在通道中單向傳輸,那麼該通道被稱作單向通道。我們在初始化一個通道值的時候不能指定為單向。但是在編寫型別宣告的時候可以:
type Receiver <- chan int
類型Receiver代表了一個只從中接收資料的單向通道類型,這樣的通道也被稱為接收通道。在關鍵字chan左邊的接收操作符<-表示了資料的流向。相對應的,如果我們想聲明一個發送通道類型,那麼應該這樣:
type Sender char <- int
這次<-被放在了chan的右邊,並且“箭頭”直指“通道”。我們可以把一個雙向通道賦予上述類型的變數,就像這樣:
var myChannel = make(chan int, 3)var sender Sender = myChannelvar receiver Receiver = myChannel
但是反過來是不行的:
var myChannel1 chan int = sender
單向通道的作用主要是約束程式對通道值的使用方式,比如,我們調用一函數時給予它一個發送通道作為參數,以此來約束它只能向該通道發送資料。又比如,一個函數將一個接受通道作為結果返回,以此來限制式呼叫該函數的代碼只能從這個通道中接收資料。
218 次點擊