Golang筆記--基礎篇彙總
Go程式結構:
- 包名
- 匯入其他的包
- 常量的定義
- 全域性變數的宣告和負責
- 一般型別宣告
- 結構的宣告
- 介面的宣告
- 由main函式作為程式入口啟動點
Go別名和省略呼叫:
在匯入包時可以使用別名來防止混淆,或者是使用省略呼叫來減少程式碼
別名:
//在匯入包時定義別名
import io “fmt”
//使用時直接使用別名,而不是原來的包名
io.Println("Hello World")
省略呼叫(不建議使用,易混淆):
//在匯入包時宣告省略呼叫
import . “fmt”
//使用時直接使用要使用的函式,不用加包名
Println("Hello World")
Go中的可見性規則:
使用大小寫來決定該常量,變數,型別,介面,結構或者函式是否可以被外部呼叫。
函式名首字母小寫為private函式
函式名首字母大寫為public函式
基本型別:
(布林型)bool:
- 長度:1位元組
- 取值範圍:true,false(不可以用1,0表示)
(整形型)int/uint:
- 長度:取決於所在平臺,32或64位
(8位整型)int8/uint8:
- 長度:1位元組
- 取值範圍:-128~127/0~255
(位元組型) byte(unit8):
- 長度:1位元組
- 取值範圍: 0~255
(16位整型)int16/uint16:
- 長度:2位元組
- 取值範圍:-32768~32768/0~65535
(32位整型)int32(rune)/uint32:
- 長度:4位元組
- 取值範圍:-(2^31)~(2^31)-1/0~(2^32)-1
(64位整型)int64/uint64:
- 長度:8位元組
- 取值範圍:-(2^64)~(2^64)-1/0~(2^64)-1
(浮點型)float32/64:
- 長度:4/8位元組
- 小數位:精確到7/15位小數位
(複數)complex64/128:
- 長度:8/16位元組
(無符號指標)uintptr:
- 長度:可以儲存指標的32位或者是64位整型
引用型別:
- slice,map,chan
其他值型別:
- array ,struct,string
介面型別:
- interface
函式型別:
- func
變數的宣告和賦值:
單個變數的宣告和賦值:
//變數的宣告
//(var <變數名稱> <變數型別>)
var a int
//變數的賦值
//(<變數名稱> = <表示式>)
a = 1
//變數宣告同時賦值
//(var <變數名稱> [變數型別] = <表示式>)
var a int = 65
//變數宣告同時賦值
//(var <變數名稱> = <表示式>)
var a = 2
//變數的宣告同時賦值
//<變數名> := <表示式>
a := 2
多個變數的宣告和賦值:
全域性變數:
//多個變數的宣告
//<var> [變數名1],[變數名2],[變數名3]... <型別名>
var a,b,c,d int
//多個變數的賦值
//[變數名1],[變數名2],[變數名3] = <表示式>,<表示式>,<表示式>...
a,b,c = 1,2,"string"
//多個變數的宣告和賦值
//var(<變數名> = <表示式>...)
var(
a = 1
b = "string"
)
區域性變數:
//多個變數的宣告
//<var> [變數名1],[變數名2],[變數名3]... <型別名>
var a,b,c,d int
//多個變數的賦值
//[變數名1],[變數名2],[變數名3] = <表示式>,<表示式>,<表示式>...
a,b,c = 1,2,"string"
//多個變數的宣告和賦值
//<變數名><變數名><變數名>... := <表示式><表示式><表示式>...
a,b,c := 1,2,3
變數的型別轉換:
Go中不存在隱式轉換,所有的型別轉換都需要顯式宣告
型別轉換隻能發生在兩個相互相容的型別之間
型別轉換格式:
- < V alueA> [:]= < TypeOfValueA> (< ValueB >)
常量的初始化和列舉
常量的值在編譯時就已經確定
常量得到的定義格式與變數基本相同
常量右側的表示式必須是常量或者是常量表達式
常量表達式中的函式必須是內建函式
在常量組中定義時,若是不提供初始值,則預設使用上一行的表示式
iota是常量的計數器,從0開始,組中每定義一個常量,就會自動增1
每遇到一個const關鍵字,iota就會重置為0
const (
FIRST = iota //FIRST=0
SECOND //SECOND = 1
THRID //THRID = 2
)
運算子
- 所有的運算子從左到右結合
- 優先順序從高到低依次是
運算子 | 優先順序 |
---|---|
^ ! | 一元運算子 |
* / % << >> & &^ | 二元運算子 |
+ - | ^ | 二元運算子 |
== != < <= > >= | 二元運算子 |
<- | 用於channel |
&& | |
|| |
指標
Go中的指標採用”.”選擇符來操作指標物件的成員
- 操作符”&”取地址變數,操作符”*”訪問目標物件
- 指標的預設值為nil而不是NULL
//指標的定義
var p *[]int
//只針的賦值
p = &a
自增/自減運算子
++(自增運算子)和–(自減運算子)不再是作為表示式,而是作為單獨的語句出現。也就是說他們都不可以出現在運算子的右側
//錯誤使用
a := 1++;
//正確使用
a := 1
a++
流程控制語句
if語句
在if之後沒有括號,可以直接寫判斷表示式,而且大括號必須和if在同一行,支援在if語句中進行變數的初始化。
if a, b := 1, 2; a >= 0 || b >= 0 {
fmt.Println(a)
fmt.Println(b)
}
for語句
只有for一個迴圈語句關鍵字,但是支援3種形式,for中的條件語句每次都會被檢查,因此不建議在條件語句中使用函式,同if語句一樣,左大括號必須和條件語句在同一行。可以通過break來跳出迴圈。
for a, b := 1, 2; a >= 0 || b >= 0; {
fmt.Println(a + b)
a--
b--
}
switch語句
- 可以使用任何型別或者是表示式作為條件語句
- 不需要寫break,符合條件時自動跳出
- 若是希望執行下一個case,需要使用fallthrough語句
- 支援一個初始化表示式(可以是並行方式),右側需要跟分號
- 左大括號必須和條件語句在一行
//傳統使用方式
a := 1
switch a {
case 0:
fmt.Println("a == 0")
case 1:
fmt.Println("a == 1")
default:
}
//在case中使用表示式
a := 1
switch {
case a >= 0:
fmt.Println("a == 0")
fallthrough //這樣可以使滿足case之後仍然進行判斷,而不是直接跳出switch語句
case a >= 1:
fmt.Println("a == 1")
default:
fmt.Println("預設處理方式") //當所有的case都不滿足使,會使用default來進行處理
}
跳轉語句(goto,break,continue)
- 三個語句都可以配合標籤使用
- 標籤名區分大小寫,而且定義了的標籤不使用的話會報編譯錯誤
- break和continue可以配合標籤來跳出多層的迴圈
- goto是調整執行的位置,與其他兩個語句配合標籤的結果不相同
Array陣列
- 定義陣列的格式為:var <陣列名> [陣列長度]<陣列型別> = 0
- 陣列長度也是型別的一部分,具有不同長度的陣列為不同型別
- 要區分指向陣列的指標和指標陣列的區別
- 陣列之間可以使用 == 和!=進行比較,但是不可以使用<或者是>比較
- 可以使用new來建立陣列,但是返回的只是一個指向陣列的指標
- Go支援多維陣列
//可以使用陣列的下標來指定具體某個陣列元素的初始值,若不指定則全部使用型別零值
f := [20]int{19: 1}
for i := 0; i < 20; i++ {
fmt.Println(f[i])
}
Slice切片
- 切片本身不是陣列,他是一種引用型別,他用來指向底層的陣列
- 他可以指向底層陣列的全部或者是部分,可以作為可變長陣列的替代方案
- 使用len()獲取元素個數,使用cap()獲取容量大小
- 一般使用make()來建立:make([]T,len,cap),len表示元素的個數,cap表示切片的容量,cap省略時預設和len值相同
L := [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := L//指向底層陣列L的全部
fmt.Println(s1)
//----------
L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
s1 := L[3:6] //指向底層陣列L的部分,指向的是陣列中下標為3,4,5的數
fmt.Println(s1)
//-----------
sl := make([]int, 3, 9)//[]int----所指向的型別,3----切片的長度,9----切片容量
Reslice
- Reslice時索引以被slice的切片為準
- 索引不可以超過被slice的切片的容量值
- 索引越界不會導致底層陣列的重新分配,而是引發錯誤
L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
slice := L[2:7] //輸出為2,3,4,5,6
reslice := slice[1:4]//在slice的基礎上再進行slice,輸出為3,4,5
fmt.Println(reslice)
Append
- 可以在slice尾部追加元素
- 可以將一個slice追加在另一個slice的尾部
- 如果最終長度未超過追加到slice的容量,那麼就返回原始slice
- 如果最終長度超過最忌到的slice的容量,那麼就重新分配陣列並拷貝元素資料
L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
slice := L[2:7]
fmt.Printf("%v %p", slice, slice)
slice = append(slice, 2, 3, 4, 5)
fmt.Printf("%v %p", slice, slice)
Copy
L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
slice := L[2:7]
slice2 := []int{1, 2, 3}
copy(slice, slice2)//將slice2中的元素拷貝到slice中
fmt.Println(slice, slice2)
//slice輸出為[1,2,3,3,4,5,6,7,8]
Map
- 和其他語言中的雜湊表類似,以Key-Value的形式來儲存值
- Key必須是支援==或者是!=比較運算的型別,不可以是函式,map,或者是slice
- Map查詢比線性搜尋快很多,但是比使用索引訪問慢很多
- Map使用make建立,支援:=的宣告賦值方式
- 使用len()函式獲取元素個數,在超出容量之後會自動擴容
- 鍵值對不存在時自動新增,使用delete刪除鍵值對
- 使用for range對map和slice進行遍歷操作
//宣告map變數:var關鍵字 變數名 map關鍵字 [Key型別]Value型別
var m map[int]string
//初始化map變數
m = map[int]string{1:"ok",2:"hello"}
//或者是
m = make(map[int]string)
//宣告並賦值
var m map[int]string := make(map[int]string)
//其實map[int]string整體是作為一個型別名稱的
//增加
m[1] = "ok"
//刪除
delete(m, 1)
//多層map的巢狀
var m map[int]map[int]string //map的value型別為map
m = make(map[int]map[int]string) //對最外層的map進行初始化
m[1] = make(map[int]string) //對內層中Key為1的map進行初始化
m[1][1] = "hello"
fmt.Println(m[1][1])
//利用多返回值確定map是否初始化
data, ok := m[1][1]
if !ok {
m[1] = make(map[int]string)
}
m[1][1] = "hello"
data, ok = m[1][1]
fmt.Println(data, ok)
//通過for range來遍歷map和slice,但是所有對value的操作都是對map和slice拷貝的操作
for k,v := range map{
//k是key,v是value
}
Function函式
- 定義函式使用關鍵字func,左起大括號不能另起一行
- 不支援函式巢狀,過載和預設引數
- 支援的特性:無需宣告函式原型,不定長形參,多返回值,命名返回值引數,匿名函式,閉包
- 函式也可以作為一種型別使用,賦值
//func關鍵字 函式名 形參列表,返回值列表
func A(data1 int,data2 int,data3 int) (a int, b int, c string) {
a, b, c = 1, 2, "hello"
fmt.Println(d)
return a, b, c
}
//go中支援不定長引數
func A(d ...int) (a int, b int, c string) {
//通過在型別名前加...來使用不定長引數,但是不定長引數只能作為引數列表的最後一個引數進行宣告
//不定長引數傳入的其實是一個slice切片,但是其實際上是傳入的一個值拷貝,在函式內修改不會影響原值
}
匿名函式
不給函式指定名稱,而是直接將其作為變數型別使用
//匿名函式,直接將函式作為變數進行使用
a := func() {
fmt.Println("匿名函式")
}
a()
函式閉包
//定義返回值為一個函式
func closure(x int) func(int) int {
return func(y int) int {
return x + y
}
}
//使用函式
f := closure(10)
fmt.Println(f(1))
defer
- 在函式體執行結束之後按照呼叫順序的相反順序逐個執行
- 即使函式發生嚴重錯誤也會執行
- 支援匿名函式的呼叫
- 常用於資源釋放,清理等工作
- 在函式return之後還可以對函式的計算結果進行修改
//列印結果會是a,c,b
func main{
fmt.Println("a")
defer fmt.Println("b")
defer fmt.Println("c")
}
//列印結果是3,3,3,因為傳入的是一個引用,所以當迴圈體執行完之後i指向的是3,然後開始反向的順序執行defer,所以會是3,3,3
func main(){
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}()
}
}
Panic/Recover:
go中沒有try/catch語句來進行異常處理,都是用panic和recover來就進行
panic可以在需要的地方將程式中斷,不再執行
,可以在任何地方引發recover可以配合defer函式來進行程式的恢復執行,但是隻有在defer呼叫的函式中有效
func main(){
B()
C()
D()
}
func B() {
fmt.Println("B")
}
func C() {
//defer函式必須在panic之前,不然無法註冊的defer函式
//recover只能在defer呼叫的函式內使用
defer func() {
if err := recover(); err != nil {
fmt.Println("Recover in C")
}
}()
panic("Panic in c")
}
func D() {
fmt.Println("D")
}
Struct
- 定義方法:type< Name > struct{},和C語言很相似
- 結構之間可以相互的組合,但是沒有繼承
- 同樣遵循命名的可見性規則
type Person struct {
Name string
Age int
}
//匿名結構,不需要為定義的結構命名,直接定義好結構之後使用
a := struct {
Name string
Color string
}{
Name: "cai",
Color: "Red",
}
//在初始化時,內部的匿名結構不可以通過指定欄位名來命名,只能通過操作符'.'來初始化。
type Person struct {
Name string
Age int
Family struct {
Mom string
}
}
//對結構體進行初始化
per := Person{
Name: "wqc",
Age: 10,
}
//只能這樣初始化內部的結構體
per.Family.Mom = "媽媽"
struct中的面向物件
在Go中不存在繼承關係,主要是通過組合的方式來實現一些公有屬性的抽象。
type People struct {
Name string
Age int
Sex int
}
type Student struct {
People
Sid int
}
//對使用結構組合的結構進行初始化
xiaoMing := Student{
Sid: 123,
People: People{
Name: "xiaoMing",
Age: 12,
Sex: 1,
},
}
xiaoMing.Age = 100
xiaoMing.Name = "whh"
xiaoMing.Sex = 11
fmt.Println(xiaoMing)
Method方法
Go中通過顯式說明receiver來實現與某個型別的組合
只能為同一個包中的型別定義方法
Receiver可以是型別的值或者是指標
不存在方法的過載
可以使用值或者是指標來呼叫方法,編譯器會自動完成轉換
如果外部結構和嵌入結構存在同名方法,則優先呼叫外部結構的方法
類型別名不會擁有底層型別所附帶的方法
方法可以使用結構中非公開的欄位
//方法的定義方式
type A struct {
Name string
}
//func(要繫結的結構名) 函式名(引數列表)(返回值列表)
//值傳遞方式傳入的只是一個拷貝,無法對外部的值產生影響
func (a A) Print(x int) (flag bool) {
fmt.Println("1")
}
//指標傳遞,傳入的是一個地址的拷貝,可以修改外部的值
func (a *A) Print(x int) (flag bool) {
fmt.Println("1")
}
//在Go中不允許方法過載,以上邊為例:也就是說結構體A只能有一個叫Print的方法。
Interface介面
介面是一個或者是多個方法簽名的集合
只要某個型別擁有該介面的所有方法簽名,計算實現該介面,無需顯示宣告實現了那個介面,稱為structural Typing
介面只有方法宣告,沒有實現,沒有欄位
介面可以嵌入其他介面或者是結構中去
只有當介面儲存的型別和物件都為nil時接口才等於nil
將物件賦值給介面時會發生拷貝,而介面內部儲存的是指向這個複製品的指標,既然無法修改複製品的狀態,也無法回去指標
空介面可以最為任何型別資料的容器
//定義介面
type USB interface {
Name() string
Connect()
}
//定義結構
type PhoneConnector struct {
name string
}
//為結構宣告實現了介面的方法
func (pc PhoneConnector) Name() string {
fmt.Println("Name ")
return pc.name
}
//為結構宣告實現了介面的方法
func (pc PhoneConnector) Connect() {
fmt.Println("Connect...", pc.name)
}
型別斷言
- 通過型別斷言的ok patteern可以判斷介面中的資料型別
- 使用type switch則可以針對空介面進行比較全面的型別判斷
介面轉換
- 可以將擁有超集的介面轉換為子集的介面
Reflecttion反射
- 反射可以提高程式的靈活性,使得interface有更大的發揮餘地
- 方式使用TypeOf和ValueOf函式重介面中獲取目標物件資訊
- 反射會將匿名欄位作為獨立欄位
- 想要利用反射修改物件狀態,前提是interface.data是settable
- 可以通過反射動態呼叫方法
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (user User) Hello() {
fmt.Println("Hello World")
}
func Info(o interface{}) {
t := reflect.TypeOf(o)
fmt.Println(t.Name())
//獲取所包含的欄位的資訊
v := reflect.ValueOf(o)
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
val := v.Field(i).Interface()
fmt.Println(f.Name, f.Type, val)
}
//獲取所包含的方法資訊
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Println(m.Name, m.Type)
}
}
func main() {
u := User{1, "ok", 12}
Info(u)
}
可以通過使用反射來獲取變數的地址資訊,然後直接對地址中所存的資訊進行修改
x := 9
v := reflect.ValueOf(&x)
v.Elem().SetInt(999)
fmt.Println(x)
通過反射來修改介面中的欄位值
- 首先判斷是否獲取到欄位,而且值可以被修改
- 然後檢驗是否獲取到所需要的欄位
- 修改欄位值
func Set(o interface{}) {
//獲取介面中的欄位值
v := reflect.ValueOf(o)
//檢驗是否獲取正確
if v.Kind() == reflect.Ptr && !v.Elem().CanSet() {
fmt.Println("XXX")
return
} else {
v = v.Elem()
}
//獲取具體某一欄位
f := v.FieldByName("Name")
//檢驗是否成功獲取到
if !f.IsValid() {
fmt.Println("錯誤")
}
//通過set方法修改欄位值
if f.Kind() == reflect.String {
f.SetString("正確")
}
}
Concurrency併發
高併發是Go的核心亮點,其實goroutine是官方實現的“超級執行緒池”,每個例項4-5KB的佔記憶體佔用和由於實現機制而大幅減少的建立和銷燬開銷。通過通訊來進行訊息互動,而不是通過共享記憶體來通訊。
通訊方式Channel
Channel是goroutine溝通的橋樑,大都是阻塞同步的
通過make建立,通過close關閉
可以使用for range來迭代不斷操作Channel
可以設定通道為單項或者是雙向
可以設定快取大小,在未被填滿前不會發生阻塞
func main() {
//宣告一個通道
channel := make(chan bool)
go func() {
fmt.Println("Go Go Go")
//向通道中寫入資料
channel <- true
//關閉通道
close(channel)
}()
//通過for range從通道中讀取資料
for v := range channel {
fmt.Println(v)
}
}
Select
可以處理一個或多個channel的傳送和接收
同時有多個可用的channel是按隨機順序處理
可用空的select來阻塞main函式
可設定超時機制