1、語法
(1)變量定義和初始化
var a int //標准,類型放后面:var 變量名 類型
a = 10
var a int = 10 //標准
var a = 10 //省略type
a,b := 10,"s" //短變量聲明,最簡化,【只能在函數內用,函數外不可以】。注意:直接a=10會報錯undefined,:=左側是新變量,=左側是已聲明變量
(2)bool類型
與C或Java不同,Go的整型和布爾型之間壓根就沒關系。
Go語言中不允許將整型強制轉換為布爾型
布爾值並不會隱式轉換為數字值 0 或 1,反之亦然
(3)整型
a := 3.1415e2 // 314.15,科學計數法表示
b := int64(a) // 強轉語法
(4)交換
a,b = b,ap
(5)defer
https://www.cnblogs.com/phpper/p/11984161.html
defer特性:
1. 關鍵字 defer 用於注冊延遲調用。
2. 這些調用直到 return 前才被執。因此,可以用來做資源清理。
3. 多個defer語句,按[先進后出]的方式執行。
4. defer語句中的變量,在defer聲明時就決定了。
defer用途:
1. 關閉文件句柄
2. 鎖資源釋放
3. 數據庫連接釋放
潛在問題
https://www.jianshu.com/p/f897e69f5504
defer x.Close() 會忽略它的返回值,但在執行 x.Close() 時,我們並不能保證 x 一定能正常關閉,萬一它返回錯誤應該怎么辦?這種寫法,會讓程序有可能出現非常難以排查的錯誤。
(6)Go 語言 for 循環
Go 語言的 For 循環有 3 種形式,只有其中的一種使用分號。
和 C 語言的 for 一樣:
包括for i 和 for range兩種形式。
// for i
for init; condition; post { }
// for range
// for 循環的 range 格式可以對 slice、map、數組、字符串等進行迭代循環。格式如下:
for key, value := range oldMap {
newMap[key] = value
}
和 C 的 while 一樣:
for condition { }
和 C 的 for( ; ; ) 一樣:
for { }
- init: 一般為賦值表達式,給控制變量賦初值;
- condition: 關系表達式或邏輯表達式,循環控制條件;
- post: 一般為賦值表達式,給控制變量增量或減量。
Go語言類型別名 vs 類型定義
參考:
https://studygolang.com/articles/22667?fr=sidebar
http://www.weixueyuan.net/a/481.html
- 類型別名是 Go1.9 版本添加的新功能。之前都是類型定義。
- 類型定義會形成一種新的類型,編譯后依然有該類型。
- 別名類型只會在代碼中存在,編譯后不會有別名類型。
type myInt int // 類型定義,a 的類型是 main.myInt,表示main 包下定義的myInt 類型。
type intAlias = int // 類型別名,b 的類型是 int 。intAlias 類型只會在代碼中存在,編譯完成時,不會有 intAlias 類型
2、復合數據類型
(1)數組
數組和Slice區別:指定長度為數組,不指定長度為切片。
定義數組:
var variable_name [SIZE] variable_type // 格式
var balance [10] float32
(2)Slice
定義切片,兩種方式:
var identifier []type // 不指定長度。identifier := []int,會報錯!
var slice1 []type = make([]type, len)
slice1 := make([]type, len) // make創建,指定長度
slice2 := make([]T, length, capacity) // 也可以指定容量capacity,可選。 默認初始為[0,0,...]。
// 補充:
capacity vs length:slice是個fat array,正是因為它有len和cap屬性才能實現動態擴縮。
length:已使用的長度
capacity:容量
假如沒有cap,只有len,怎么知道是否需要擴容呢?
但是一般都不怎么需要關注cap。
特殊場景關注cap:https://github.com/valyala/bytebufferpool
初始化:
s :=[] int {1,2,3}
s := arr[:] // 初始化切片 s,是數組 arr 的引用。
s := arr[startIndex:endIndex] // 將 arr 中從下標 startIndex 到 endIndex-1 下的元素創建為一個新的切片。
s := arr[startIndex:] // 默認 endIndex 時將表示一直到 arr 的最后一個元素。
s := arr[:endIndex] // 默認 startIndex 時將表示從 arr 的第一個元素開始。
初始化地址:
a := make([]int, 2)
println(a) // 實質就是&a[0],前面加個len
println(&a[0])
println(&a[1])
println(&a)
輸出結果:
[2/2]0xc00003ff48
0xc00003ff48
0xc00003ff50
0xc00003ff60
(3)Map
-
map類型可以寫為map[K]V
-
其中K對應的key必須是支持==比較運算符的數據類型,所以map可以通過測試key是否相等來判斷是否已經存在
-
浮點數最好不要作為key:類型也是支持相等運算符比較的,但是將浮點數用做key類型則是一個壞的想法,正如第三章提到的,最壞的情況是可能出現的NaN和任何浮點數都不相等
-
創建map
// 1. 創建+賦值:內置的make函數 ages := make(map[string]int) ages["alice"] = 31 ages["charlie"] = 34 // 2. 創建+初始化: age2 := map[string]int{ "alice": 31, "charlie": 34, } //3. 創建空map age3 := map[string]int{} -
訪問map
// Map中的元素通過key對應的下標語法訪問: ages["alice"] = 32 fmt.Println(ages["alice"]) // 使用內置的delete函數可以刪除元素: delete(ages, "alice")
(4)結構體
一個命名為S的結構體類型將不能再包含S類型的成員:因為一個聚合的值不能包含它自身。(該限制同樣適用於數組)
但是S類型的結構體可以包含*S指針類型的成員,這可以讓我們創建遞歸的數據結構,比如鏈表和樹結構等。
創建一個結構體變量
// 方法1。 &Point{1, 2}寫法可以直接在表達式中使用,比如一個函數調用。
pp := &Point{1,2}
// 方法2
pp := new(Point)
*pp = Point{1,2}
結構體賦值,如果換行,最后要有",",如果不換行,最后不要有","
type a struct {
AA string
BB string
}
var S1 = a{
AA: "ddd",
BB: "sss", // 最后有逗號
}
var S2 = a{AA: "ddd", BB: "sss"}// 最后沒有逗號
結構體作為參數
結構體可以作為函數的參數和返回值。
如果要在函數內部修改結構體成員的話,用指針傳入是必須的;因為在Go語言中,所有的函數參數都是值拷貝傳入的,函數參數將不再是函數調用時的原始變量。
(5)JSON
(6)文本和HTML模板
3、函數
func name(parameter-list) (result-list) {
body
}
(1)Go語言沒有默認參數值
(2)沒有函數體的函數聲明,這表示該函數不是以Go實現的。這樣的聲明定義了函數標識符。
package math
func Sin(x float64) float //implemented in assembly language
(3)大寫字母開頭是公有,小寫字母開頭為私有。
4、方法
(1)接收器(receiver):相當於Java中的this或Python中的self,可以任意的選擇接收器的名字。
(2)方法就是在函數名前加個該類的接收器:
type Point struct{ X, Y float64 }
// traditional function
func Distance(p, q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// Point類型的方法,p是接收器,一般就用類型的首字母
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
(3)函數調用,會對每一個參數值進行拷貝,參數太大可以用指針避免這種默認拷貝。接收器也是參數。當接收器太大時,可以用其指針而不是對象來聲明方法:
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
(4)在現實的程序里,一般會約定如果Point這個類有一個指針作為接收器的方法,那么所有Point的方法都必須有一個指針接收器,即使是那些並不需要這個指針接收器的函數。
一個方法用的是指針接收器,所有方法都要是指針接收器。
5、接口interface
data := map[string]interface{}{
"lang": "GO語言",
"tag": "<br>",
}
6、第八章 Goroutines和Channels
6.1引言
- Go語言中的並發程序可以用兩種手段來實現,分別是第八章和第九章。
- 本章講解goroutine和channel,其支持“順序通信進程”(communicating sequential processes)或被簡稱為CSP。
- CSP是一種現代的並發編程模型,在這種編程模型中值會在不同的運行實例(goroutine)中傳遞,盡管大多數情況下仍然是被限制在單一實例中。
- 第9章覆蓋更為傳統的並發模型:多線程共享內存,如果你在其它的主流語言中寫過並發程序的話可能會更熟悉一些。
6.2
6.3
6.4Channels
創建channel
ch := make(chan int) // make函數,我們可以創建一個channel
操作channel
發送和接收兩個操作都使用<-運算符
ch <- x // 把x發送到channel中
x = <- ch // x接收channel中的值
<- ch // 接收channel值,匿名,eg, fmt.Println(<- ch)
channel死鎖(deadlock)的情況
(1)聲明channel時,沒有說明具體的大小,會導致在存儲數據時死鎖
//make(chan type ,size)//第二個參數為存儲的大小
//正確情況:指定尺寸為1,同一個線程就可以讀寫同一個channel,不會死鎖。
func main() {
ch := make(chan int, 1)
ch <- 1
fmt.Println(<-ch)
fmt.Println("lalalala")
time.Sleep(3 * time.Second)
}
// 運行結果:
1
lalalala
下面是不指定size時,幾種死鎖的情況,並且采用多線程解決:
func main() {
//情況1:不用多線程,報錯
ch := make(chan int) // 沒有指定size,導致死鎖。
//ch <- 10 //直接報錯,因為只有一個主線程,ch存入一個元素后就阻塞了主線程。
}
//運行結果:
fatal error: all goroutines are asleep - deadlock!
func main() {
//情況2:1個多線程寫入
ch2 := make(chan int) //1 沒有指定size,導致死鎖。
go func() { //2 4
fmt.Println("開啟協程1") //5
ch2 <- 10 //7 6 寫入channel死鎖,協程1被阻塞。
fmt.Println("協程1復活") //8 從這行往后都無法輸出了
fmt.Println("協程1從ch2讀:", <-ch2) //9 10 8
}() //13 12
//雖然上面的協程已經死鎖,但是主線程沒有死鎖,所以仍然執行,並且繼續輸出。
fmt.Println("主線程不受影響") //3
fmt.Println("主線程從ch2讀:", <-ch2) //6 7 主線程可以直接讀channel
ch2 <- 5 //10 9 7 主線程寫入channel,主線程被阻塞
fmt.Println("主線程最后一行") //11 9
time.Sleep(3 * time.Second) //12 13
} //14
//運行結果:
開啟協程1
主線程不受影響
主線程從ch2讀: 10
協程1復活
主線程最后一行
協程1從ch2讀: 5
或者:
主線程不受影響
開啟協程1
協程1復活
主線程從ch2讀: 10
主線程最后一行
協程1從ch2讀: 5
func main() {
//情況3:1個線程寫入,1個線程讀出。這樣即使寫線程阻塞,讀線程仍不受影響,一旦channel被讀出了,寫入線程就可以繼續寫入了!
ch := make(chan int) // 沒有指定size,導致死鎖。
time.Sleep(3 * time.Second)
(2)channel存儲滿后,如果再進行存儲,會導致線程鎖住,只能等到channel將數據取出之后,才能進行正常存儲
a:=make(chan int ,1)
a<-1
a<-2
<-a
1
2
3
4
此時,如果沒有<-a,會導致通道的阻塞,只能等到<-a才能再存儲
3:channel中沒有值時,進行讀取數據,不能正常讀取,造成通道的阻塞
<-a
如果不賦值,直接從channel中讀取數據,會造成通道的阻塞
原文鏈接:https://blog.csdn.net/Xiang_lhh/article/details/108779630
關閉channel
(1)關閉后,對基於該channel的任何發送操作都將導致panic異常。
(2)對一個已經被close過的channel進行接收操作依然可以接受到之前已經成功發送的數據;如果channel中已經沒有數據的話將產生一個零值的數據。
close(ch) // 使用內置的close函數關閉channel
打開channel
參考:https://www.jianshu.com/p/bc7aa77a609c
go語言原生語法並沒有提供方法打開channel,只能自己寫方法,利用指針再次打開。
channel的結構體在chan.go中:
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
//... 以下字段沒有用上,先省略
}
Channel是否關閉取決於hchan.closed,0是打開,1是關閉。
方法:讓指針指向hchan.closed直接修改它的值。
代碼實現【不理解!!!】
//go:linkname lock runtime.lock
func lock(l *mutex)
//go:linkname unlock runtime.unlock
func unlock(l *mutex)
func open(c interface{}) error {
v := reflect.ValueOf(c)
if v.Type().Kind() != reflect.Chan {
return errors.New("type must be channel")
}
i := (*[2]uintptr)(unsafe.Pointer(&c)) //定位c所在數據空間,這里的c是個指針所以要進行一步取值
var closedOffset, lockOffset uintptr = 28, 88
closed := (*uint32)(unsafe.Pointer(i[1] + closedOffset)) //指向closed的地址
if *closed == 1 {
lockPtr := (*mutex)(unsafe.Pointer(i[1] + lockOffset)) //指向lock地址
lock(lockPtr) //上鎖
if *closed == 1 {
*closed = 0 //直接修改值
}
unlock(lockPtr) //解鎖
}
return nil
}
7、環境搭建
(1)安裝和配置SDK
go1.16.5.windows-amd64.msi
/bin 下面是
……
(2)GOROOT和GOPATH
Go開發相關的環境變量如下:
GOROOT:GOROOT就是Go的安裝目錄,(類似於java的JDK)。不用往環境變量配置。包管理方式變成Go Module之后就用處不大了。
如,C:\Program Files\Go
GOPATH:GOPATH是我們的工作空間,保存go項目代碼和第三方依賴包。安裝后,在環境變量中有。
如,GOPATH = %USERPROFILE%\go
參考:https://blog.csdn.net/qq_38151401/article/details/105729884
8、結構
(1)package包聲明
package main //執行程序必須叫main,下面必須有main函數,作為入口
package test // 其他的包,不一定要與目錄同名,但最好同名
(2)import引入包
// 方式1
import "fmt"
import "demo1/test" // 項目名/包名
// 方式2
import (
"fmt"
"demo1/test" // 項目名/包名
)
(3)函數
大寫字母開頭是公有,小寫字母開頭為私有。
(4)變量
(5)語句 & 表達式
(6)注釋
單行注釋://
多行注釋:/**/
9、大小寫命名規范和訪問權限
(1)首字母大寫是公有的,首字母小寫是私有的,因此,變量或函數只需要首字母大寫即可跨文件訪問
(2)golang的命名需要使用駝峰命名法,且不能出現下划線
(3)結構體中屬性名最好大寫:如果屬性名小寫則在數據解析(如json解析,或將結構體作為請求或訪問參數)時無法解析
10、運行
(1)直接運行
go run hello.go
(2)編譯運行
go build hello.go
hello
