KANIKIG

KANIKIG

just for fun | 兴趣使然 Ph.D. in Engineering|❤️ #NFT $ETH| [Twitter](https://twitter.com/kanikig2)|[Github](https://github.com/KANIKIG)|[Telegram channel](https://t.me/kanikigtech)

Golang基礎筆記

image

前言#

Go 簡介#

  • Google 開源
  • 編譯型語言
  • 21 世紀的 C 語言

2005 年出現多核處理器,其他語言都是單核時代誕生的。Go 天生考慮了多核並發。

特點:

  • 語法簡潔(只有 25 個關鍵字,比 Python 更簡潔,自帶格式化,互相閱讀容易)
  • 開發效率高
  • 執行性能好(接近 java)

發展:

百度自動駕駛,小程序

騰訊藍鯨,微服務框架

知乎最早用 python 寫,後期承受不了負載,用 go 重構節省了 80% 資源。

課程簡介#

李文周博客

倉庫

8 周基礎

3 個實戰項目

Go 項目結構#

個人開發者

image-20220113164729380

流行方式

image-20220113165000771

Helloworld#

go build

win 編譯得 exe,macos 得可執行文件

go install

install 相當於 build 後再移到 bin

go run

當腳本運行

支持跨平台交叉編譯

// wincmd SET, macos export
export CGO_ENABLED=0	//禁用CGO
export GOOS=linux	//設置目標平台linux,windows,darwin
export GOARCH=amd64//目標處理器架構是amd64
go build
export CGO_ENABLED=0 GOOS=linux GOARCH=amd64
go build

變量與常量#

函數外不能寫語句

標識符:字母數字下劃線,不以數字開頭

關鍵字與保留字不建議用於變量名

變量#

初始化#

數字默認0,字符串默認空,布爾默認false,切片、函數、指針默認nil

var 變量名 類型 = 表達式
var name string = "Q1mi"
var age int = 18
var name, age = "Q1mi", 20	//會根據值推導類型
var (
    a string
    b int
    c bool
    d float32
)

寫在函數外為全局變量

函數內聲明局部變量簡寫為

	n := 10
	m := 200
	fmt.Println(m, n)

注意:在 Golang 裡非全局變量聲明必須使用,不然編譯不通過!

fmt.Print()
fmt.Printf()
fmt.Println() //換行

保存時會自動格式化

命名規則#

var studentName string

Golang 使用小駝峰命名

匿名變量#

用短下劃線接收,不占命名空間,不分配內存

x, _ = foo()
_, y = foo()

常量#

const pi = 3.14

iota#

常量計數器,每新增一行常量聲明則計數,注意是一行

const (
  n1 = iota //0
  n2	//1
  n3	//2
  n4	//3
)
const (
  n1 = iota //0
  n2	//1
  _	//2 但是被丟棄
  n3	//3
)

定義數量級#

const (
  _ = iota
  KB = 1 << (10 * iota)
  MB = 1 << (10 * iota)
  GB = 1 << (10 * iota)
  TB = 1 << (10 * iota)
  PB = 1 << (10 * iota)
)

<< 左移符號,二進制 1 左移 10 位是 1024

基本數據類型#

整型分為以下兩個大類:按長度分為:int8、int16、int32、int64

對應的無符號整型:uint8、uint16、uint32、uint64

uint8 就是 byte,int16 就是 short,int64 是 long

特殊整型#

uint int 會根據系統判別是 32 還是 64

uintptr 指針,存放內存地址

進制#

Golang 無法直接定義二進制數,八、十六均可

	// 十進制
	var a int = 10
	fmt.Printf("%d \n", a)  // 10
	fmt.Printf("%b \n", a)  // 1010  占位符%b表示二進制
 
	// 八進制  以0開頭
	var b int = 077
	fmt.Printf("%o \n", b)  // 77
 
	// 十六進制  以0x開頭
	var c int = 0xff
	fmt.Printf("%x \n", c)  // ff
	fmt.Printf("%X \n", c)  // FF
	fmt.Printf("%T \n", c)  // 輸出類型
	fmt.Printf("%v \n", c)  // 輸出變量值,任意類型

浮點數#

golang 中小數默認float64

math.MaxFloat64 // float64最大值

布爾#

默認 false,不允許轉換

字符串#

只能雙引號,單引號為字符

轉義含義
\r返回行首
\n換行(下行同列)
\t制表
// 在win中路徑轉義
s := "D:\\Documents\\A"

// 反引號原樣輸出, 多行字符串
s := `
asda
		asd
`
s := "D:\Documents\A"
len(str)
ss := s1 + s2
ret := strings.Split(s3, "\\")
ret = strings.Contains(s3, "abcd")
ret = strings.HasPrefix(s3, "abcd")
ret = strings.HasSufix(s3, "abcd")
ret = strings.Index(s3, "c")
ret = strings.LastIndex(s3, "c")
ret = strings.Join(a, b)

英文字符為byte,其他語系如中文字符為rune,實際為int32,占 3 位

字符串遍歷

for _, char := range str {
  fmt.Printf("%c", char)
}

字符串沒法直接修改,只能轉換為其他類型處理

s3 := []rune(s2)	//切片
s3[0] = 'e'	//修改
s4 := string(s3)

流程控制#

if#

if 表達式1 {
    分支1
} else if 表達式2 {
    分支2
} else{
    分支3
}
// 局部變量score只在if中生效,減少內存占用
	if score := 65; score >= 90 {
		fmt.Println("A")
	} else if score > 75 {
		fmt.Println("B")
	} else {
		fmt.Println("C")
	}

for#

golang 只有 for

for 初始語句;條件表達式;結束語句{
    循環體語句
}
for i := 0; i < 10; i++ {
		fmt.Println(i)
}

初始語句和結束語句可省略,相當於 while

i := 0
	for i < 10 {
		fmt.Println(i)
		i++
	}

無限循環

for {
    循環體語句
}

通過breakgotoreturnpanic語句強制退出循環

遍歷#

for range遍歷數組、切片、字符串、map 及通道(channel)

for i,v := range s{
  fmt.Println(i, v)
}
  1. 數組、切片、字符串返回索引和值。
  2. map 返回鍵和值。
  3. 通道(channel)只返回通道內的值。

switch#

finger := 3
	switch finger {
	case 1:
		fmt.Println("大拇指")
    fallthrough
	case 2:
		fmt.Println("食指")
	case 3:
		fmt.Println("中指")
	case 4:
		fmt.Println("無名指")
	case 5:
		fmt.Println("小拇指")
	default:
		fmt.Println("無效的輸入!")
	}

fallthrough語法可以執行滿足條件的 case 的下一個 case,是為了兼容 C 語言中的 case 設計的

switch n := 7; n {
	case 1, 3, 5, 7, 9:
		fmt.Println("奇數")
	case 2, 4, 6, 8:
		fmt.Println("偶數")
	default:
		fmt.Println(n)
	}

goto#

goto語句通過標籤進行代碼間的無條件跳轉。goto語句可以在快速跳出循環、避免重複退出上有一定的幫助。Go 語言中使用goto語句能簡化一些代碼的實現過程。 例如雙層嵌套的 for 循環要退出時

var breakFlag bool
	for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			if j == 2 {
				// 設置退出標籤
				breakFlag = true
				break
			}
			fmt.Printf("%v-%v\n", i, j)
		}
		// 外層for循環判斷
		if breakFlag {
			break
		}
	}

簡化為

for i := 0; i < 10; i++ {
		for j := 0; j < 10; j++ {
			if j == 2 {
				// 設置退出標籤
				goto breakTag
			}
			fmt.Printf("%v-%v\n", i, j)
		}
	}
	return
	// 標籤
breakTag:
	fmt.Println("結束for循環")

運算符#

++(自增)和--(自減)在 Go 語言中是單獨的語句,並不是運算符。

// 邏輯運算
&&
||
!

// 位運算
&
|
^
<<
>>

// 赋值
+=
-=
<<=

數組#

初始化#

數組從聲明時就確定,使用時可以修改數組成員,但是數組大小不可變化

var a [3]int

var a [3]int
var b [4]int
a = b //不可以這樣做,因為此時a和b是不同的類型

數組可以通過下標進行訪問,下標是從0開始,最後一個元素下標是:len-1,訪問越界(下標在合法範圍之外),則觸發訪問越界,panic

	var testArray [3]int	//數組會初始化為int類型的零值
	var numArray = [3]int{1, 2}	//使用指定的初始值完成初始化
	var cityArray = [3]string{"北京", "上海", "深圳"} //使用指定的初始值完成初始化
	var numArray = [...]int{1, 2} //根據值推斷數組長度
	var cityArray = [...]string{"北京", "上海", "深圳"}

	a := [...]int{1: 1, 3: 5}	//指定索引初始化
	fmt.Println(a)                  // [0 1 0 5]
for index, value := range a {
		fmt.Println(index, value)
	}

多維數組#

a := [3][2]string{
		{"北京", "上海"},
		{"廣州", "深圳"},
		{"成都", "重慶"},
	}

多維數組只有第一層可以使用...來讓編譯器推導數組長度

數組是值類型,賦值和傳參會複製整個數組。因此改變副本的值,不會改變本身的值。

  1. 數組支持 “==“、”!=” 操作符,因為內存總是被初始化過的。
  2. [n]*T表示指針數組,*[n]T表示數組指針 。

切片#

數組的局限性,長度固定。

切片(Slice)是一個擁有相同類型元素的可變長度的序列。它是基於數組類型做的一層封裝。它非常靈活,支持自動擴容。

切片是一個引用類型,它的內部結構包含地址長度容量。切片一般用於快速地操作一塊數據集合。

初始化#

	var a = []string              //聲明一個字符串切片
	var b = []int{}             //聲明一個整型切片並初始化
	var c = []bool{false, true} //聲明一個布爾切片並初始化
	var d = []bool{false, true} //聲明一個布爾切片並初始化

切片有指向時值就不為空了。

a1 := [...]int{1, 3, 5, 7, 9, 11, 13}
s3 := a1[0:4] //左包右不包,索引為0-3切片

len(s3) // 4 切片長度
cap(s3)	// 7 容量=原數組切片點到末尾的長度

a[2:]  // 等同於 a[2:len(a)]
a[:3]  // 等同於 a[0:3]
a[:]   // 等同於 a[0:len(a)]

原數組元素改了切片也變,引用類型。

a[low : high : max]
a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5] //t:[2 3] len(t):2 cap(t):4

構造與簡單切片表達式a[low: high]相同類型、相同長度和元素的切片。另外,它會將得到的結果切片的容量設置為max-low。在完整切片表達式中只有第一個索引值(low)可以省略;它默認為 0。

make()#

動態創建一個切片

make([]T, size, cap)

a := make([]int, 2, 10) // 初始化值為0

空切片判斷#

要檢查切片是否為空,使用len(s) == 0來判斷,而不應該使用s == nil來判斷。

切片之間是不能比較的,我們不能使用==操作符來判斷兩個切片是否含有全部相等元素。 切片唯一合法的比較操作是和nil比較。 一個nil值的切片並沒有底層數組,一個nil值的切片的長度和容量都是 0。但是我們不能說一個長度和容量都是 0 的切片一定是nil

賦值#

	s1 := make([]int, 3) //[0 0 0]
	s2 := s1             //將s1直接賦值給s2,s1和s2共用一個底層數組
	s2[0] = 100
	fmt.Println(s1) //[100 0 0]
	fmt.Println(s2) //[100 0 0]

append()#

	var s []int
	s = append(s, 1)        // [1]
	s = append(s, 2, 3, 4)  // [1 2 3 4
	s2 := []int{5, 6, 7}  
	s = append(s, s2...)    // [1 2 3 4 5 6 7]

var 聲明的零值切片可以在append()函數直接使用,無需初始化

var s []int
s = append(s, 1, 2, 3)

每個切片會指向一個底層數組,這個數組的容量夠用就添加新增元素。當底層數組不能容納新增的元素時,切片就會自動按照一定的策略進行 “擴容”,此時該切片指向的底層數組就會更換。“擴容” 操作往往發生在append()函數調用時,所以我們通常都需要用原變量接收 append 函數的返回值。

func main() {
	//append()添加元素和切片擴容
	var numSlice []int
	for i := 0; i < 10; i++ {
		numSlice = append(numSlice, i)
		fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
	}
}

輸出

[0]  len:1  cap:1  ptr:0xc0000a8000
[0 1]  len:2  cap:2  ptr:0xc0000a8040
[0 1 2]  len:3  cap:4  ptr:0xc0000b2020
[0 1 2 3]  len:4  cap:4  ptr:0xc0000b2020
[0 1 2 3 4]  len:5  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5]  len:6  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6]  len:7  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6 7]  len:8  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8]  len:9  cap:16  ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9]  len:10  cap:16  ptr:0xc0000b8000

從上面的結果可以看出:

  1. append()函數將元素追加到切片的最後並返回該切片。
  2. 切片 numSlice 的容量按照 1,2,4,8,16 這樣的規則自動進行擴容,每次擴容後都是擴容前的 2 倍。

$GOROOT/src/runtime/slice.go源碼:

newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
	newcap = cap
} else {
	if old.len < 1024 {
		newcap = doublecap
	} else {
		// Check 0 < newcap to detect overflow
		// and prevent an infinite loop.
		for 0 < newcap && newcap < cap {
			newcap += newcap / 4
		}
		// Set newcap to the requested cap when
		// the newcap calculation overflowed.
		if newcap <= 0 {
			newcap = cap
		}
	}
}
  • 首先判斷,如果新申請容量(cap)大於 2 倍的舊容量(old.cap),最終容量(newcap)就是新申請的容量(cap)。
  • 否則判斷,如果舊切片的長度小於 1024,則最終容量 (newcap) 就是舊容量 (old.cap) 的兩倍,即(newcap=doublecap),
  • 否則判斷,如果舊切片長度大於等於 1024,則最終容量(newcap)從舊容量(old.cap)開始循環增加原來的 1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最終容量(newcap)大於等於新申請的容量 (cap),即(newcap >= cap)
  • 如果最終容量(cap)計算值溢出,則最終容量(cap)就是新申請容量(cap)。

中文字符串是 3*2^n

copy()#

切片是引用類型,所以 a 和 b 其實都指向了同一塊內存地址。修改 b 的同時 a 的值也會發生變化。

Go 語言內建的copy()函數可以迅速地將一個切片的數據複製到另外一個切片空間中。

	a := []int{1, 2, 3, 4, 5}
	c := make([]int, 5, 5)
	copy(c, a)     //使用copy()函數將切片a中的元素複製到切片c
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1 2 3 4 5]
	c[0] = 1000
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1000 2 3 4 5]

刪除元素#

	a = append(a[:index], a[index+1:]...)
	a := []int{30, 31, 32, 33, 34, 35, 36, 37}
	// 要刪除索引為2的元素
	a = append(a[:2], a[3:]...)
	fmt.Println(a) //[30 31 33 34 35 36 37]

	//底層數組長度不變,元素左移,右邊的由最右元素補全

排序對切片排

sort.Ints(a[:])

指針#

ptr := &v // v的類型為T 輸出指針類型*T 如 *string *int

	a := 10
	b := &a
	fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
	fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
	fmt.Println(&b)                    // 0xc00000e018
	c := *b // 指針取值(根據指針去內存取值)
	fmt.Printf("type of c:%T\n", c)
	fmt.Printf("value of c:%v\n", c)

& 與 * 互補

func modify1(x int) {
	x = 100
}

func modify2(x *int) {
	*x = 100
}

func main() {
	a := 10
	modify1(a)
	fmt.Println(a) // 10
	modify2(&a)
	fmt.Println(a) // 100
}

new 與 make#

new 函數不太常用,使用 new 函數得到的是一個類型的指針,並且該指針對應的值為該類型的零值

	a := new(int)
	b := new(bool)
	fmt.Printf("%T\n", a) // *int
	fmt.Printf("%T\n", b) // *bool
	fmt.Println(*a)       // 0
	fmt.Println(*b)       // false

make 也是用於內存分配的,區別於 new,它只用於 slice、map 以及 chan 的內存創建,而且它返回的類型就是這三種類型變量本身,而不是它們的指針類型,因為這三種類型就是引用類型

	var b map[string]int
	b = make(map[string]int, 10)
	b["沙河娜扎"] = 100
	fmt.Println(b)

map#

Go 語言中提供的映射關係容器為map,其內部使用散列表(hash)實現,類似 python 的字典

map 是一種無序的基於key-value的數據結構,Go 語言中的 map 是引用類型,必須初始化才能使用

map 類型的變量默認初始值為 nil,需要使用 make () 函數來分配內存

	map[KeyType]ValueType

	scoreMap := make(map[string]int, 8) // 初始化才能用,避免動態擴容!
	scoreMap["張三"] = 90
	scoreMap["小明"] = 100
	fmt.Println(scoreMap)
	fmt.Println(scoreMap["小明"])
	fmt.Printf("type of a:%T\n", scoreMap)

	userInfo := map[string]string{
		"username": "沙河小王子",
		"password": "123456",
	}

判斷鍵值是否存在#

value, ok := map[key] // ok返回key是否存在的bool值

	v, ok := scoreMap["張三"]
	if ok {
		fmt.Println(v)
	} else {
		fmt.Println("查無此人")
	}

map 的遍歷#

for k, v := range scoreMap {
		fmt.Println(k, v)
}

for k := range scoreMap {
		fmt.Println(k)
}

for _, v := range scoreMap {
		fmt.Println(v)
}

注意:遍歷 map 時的元素順序與添加鍵值對的順序無關

刪除鍵值對#

delete(map, key)

按照指定順序遍歷#

func main() {
	rand.Seed(time.Now().UnixNano()) //初始化隨機數種子

	var scoreMap = make(map[string]int, 200)

	for i := 0; i < 100; i++ {
		key := fmt.Sprintf("stu%02d", i) //生成stu開頭的字符串
		value := rand.Intn(100)          //生成0~99的隨機整數
		scoreMap[key] = value
	}
	//取出map中的所有key存入切片keys
	var keys = make([]string, 0, 200)
	for key := range scoreMap {
		keys = append(keys, key)
	}
	//對切片進行排序
	sort.Strings(keys)
	//按照排序後的key遍歷map
	for _, key := range keys {
		fmt.Println(key, scoreMap[key])
	}
}

元素為 map 類型的切片#

	var mapSlice = make([]map[string]string, 3) // 切片初始化,每個元素都是一個map
	for index, value := range mapSlice {
		fmt.Printf("index:%d value:%v\n", index, value)
	}
	fmt.Println("after init")
	// 對切片中的map元素進行初始化
	mapSlice[0] = make(map[string]string, 10)
	mapSlice[0]["name"] = "小王子"
	mapSlice[0]["password"] = "123456"
	mapSlice[0]["address"] = "沙河"
	for index, value := range mapSlice {
		fmt.Printf("index:%d value:%v\n", index, value)
	}

值為切片類型的 map#

func main() {
	var sliceMap = make(map[string][]string, 3)
	fmt.Println(sliceMap)
	fmt.Println("after init")
	key := "中國"
	value, ok := sliceMap[key]
	if !ok {
		value = make([]string, 0, 2)
	}
	value = append(value, "北京", "上海")
	sliceMap[key] = value
	fmt.Println(sliceMap)
}

函數#

func 函數名(參數 類型) 返回值類型 {
    函數體
}

func intSum(x int, y int) int {
	return x + y
}

參數同類型簡寫#

func intSum(x, y int) int {
	return x + y
}

可變參數#

func intSum2(x ...int) int {
	fmt.Println(x) //x是一個切片
	sum := 0
	for _, v := range x {
		sum = sum + v
	}
	return sum
}

返回值#

//有命名的返回
func calc(x, y int) (sum, sub int) {
	sum = x + y
	sub = x - y
	return
}
//切片
func someFunc(x string) []int {
	if x == "" {
		return nil // 沒必要返回[]int{}
	}
	...
}

如果局部變量和全局變量重名,優先訪問局部變量

函數類型與變量#

我們可以使用type关键字來定義一個函數類型,具體格式如下:

type calculation func(int, int) int

上面語句定義了一個calculation類型,它是一種函數類型,這種函數接收兩個 int 類型的參數並且返回一個 int 類型的返回值。

func main() {
	var c calculation               // 聲明一個calculation類型的變量c
	c = add                         // 把add賦值給c
	fmt.Printf("type of c:%T\n", c) // type of c:main.calculation
	fmt.Println(c(1, 2))            // 像調用add一樣調用c

	f := add                        // 將函數add賦值給變量f1
	fmt.Printf("type of f:%T\n", f) // type of f:func(int, int) int
	fmt.Println(f(10, 20))          // 像調用add一樣調用f
}

函數作參數與返回值#

func add(x, y int) int {
	return x + y
}
func calc(x, y int, op func(int, int) int) int {
	return op(x, y)
}
func main() {
	ret2 := calc(10, 20, add)
	fmt.Println(ret2) //30
}
func do(s string) (func(int, int) int, error) {
	switch s {
	case "+":
		return add, nil
	case "-":
		return sub, nil
	default:
		err := errors.New("無法識別的操作符")
		return nil, err
	}
}

匿名函數#

函數內部定義函數

func main() {
	// 將匿名函數保存到變量
	add := func(x, y int) {
		fmt.Println(x + y)
	}
	add(10, 20) // 通过变量调用匿名函数

	//自執行函數:匿名函數定義完加()直接執行
	func(x, y int) {
		fmt.Println(x + y)
	}(10, 20)
}

閉包#

閉包指的是一個函數和與其相關的引用環境組合而成的實體。簡單來說,閉包=函數+引用環境

func adder() func(int) int {
	var x int
	return func(y int) int {
		x += y
		return x
	}
}
func main() {
	var f = adder()
	fmt.Println(f(10)) //10
	fmt.Println(f(20)) //30
	fmt.Println(f(30)) //60

	f1 := adder()
	fmt.Println(f1(40)) //40
	fmt.Println(f1(50)) //90
}

defer#

defer語句會將其後面跟隨的語句進行延遲處理。在defer歸屬的函數即將返回時,將延遲處理的語句按defer定義的逆序進行執行,也就是說,先被defer的語句最後被執行,最後被defer的語句,最先被執行

func main() {
	fmt.Println("start")
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	fmt.Println("end")
}

/*
start
end
3
2
1
*/

image-20220120184447325

//面試題 defer註冊要延遲執行的函數時,該函數所有的參數都需要確定其值
func calc(index string, a, b int) int {
	ret := a + b
	fmt.Println(index, a, b, ret)
	return ret
}

func main() {
	x := 1
	y := 2
	defer calc("AA", x, calc("A", x, y))
	x = 10
	defer calc("BB", x, calc("B", x, y))
	y = 20
}

/*
A 1 2 3 //defer calc("AA", 1, 3)
B 10 2 12 //defer calc("BB", 10, 12)
BB 10 12 22
AA 1 3 4
*/

內置函數#

內置函數介紹
close主要用來關閉 channel
len用來求長度,比如 string、array、slice、map、channel
new用來分配內存,主要用來分配值類型,比如 int、struct。返回的是指針
make用來分配內存,主要用來分配引用類型,比如 chan、map、slice
append用來追加元素到數組、slice 中
panic 和 recover用來做錯誤處理

Go 語言中目前(Go1.12)是沒有異常機制,但是使用panic/recover模式來處理錯誤。 panic可以在任何地方引發,但recover只有在defer調用的函數中有效

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	defer func() {
		err := recover()
		//如果程序出現了panic錯誤,可以通過recover恢復過來
		if err != nil {
			fmt.Println("recover in B")
		}
	}()
	panic("panic in B")
}

func funcC() {
	fmt.Println("func C")
}
func main() {
	funcA()
	funcB()
	funcC()
}
  1. recover()必須搭配defer使用。
  2. defer一定要在可能引發panic的語句之前定義。

fmt 標準庫#

fmt 包實現了類似 C 語言 printf 和 scanf 的格式化 I/O。主要分為向外輸出內容和獲取輸入內容兩大部分

Print#

func main() {
	fmt.Print("在終端打印該信息。") //不換行
	name := "沙河小王子"
	fmt.Printf("我是:%s\n", name)
	fmt.Println("在終端打印單獨一行顯示")
}

FPrint#

Fprint系列函數會將內容輸出到一個io.Writer接口類型的變量w中,我們通常用這個函數往文件中寫入內容

// 向標準輸出寫入內容
fmt.Fprintln(os.Stdout, "向標準輸出寫入內容")
fileObj, err := os.OpenFile("./xx.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
	fmt.Println("打開文件出錯,err:", err)
	return
}
name := "沙河小王子"
// 向打開的文件句柄中寫入內容
fmt.Fprintf(fileObj, "往文件中寫如信息:%s", name)

只要滿足io.Writer接口的類型都支持寫入

Sprint#

Sprint系列函數會把傳入的數據生成並返回一個字符串

s3 := fmt.Sprintln("沙河小王子")

Errorf#

e := errors.New("原始錯誤e")
w := fmt.Errorf("Wrap了一個錯誤%w", e)

Scan#

fmt.Scan(&name, &age, &married)
fmt.Scanf("1:%s 2:%d 3:%t", &name, &age, &married)
fmt.Scanln(&name, &age, &married)

另有 Fscan,Sscan

bufio.NewReader#

func bufioDemo() {
	reader := bufio.NewReader(os.Stdin) // 從標準輸入生成讀對象
	fmt.Print("請輸入內容:")
	text, _ := reader.ReadString('\n') // 讀到換行終止 空格也讀入
	text = strings.TrimSpace(text)
	fmt.Printf("%#v\n", text)
}

結構體#

Go 語言中沒有 “類” 的概念,也不支持 “類” 的繼承等面向對象的概念。Go 語言中通過結構體的內嵌再配合接口比面向對象具有更高的擴展性和靈活性。

自定義類型#

自定義類型是定義了一個全新的類型。我們可以基於內置的基本類型定義,也可以通過 struct 定義

//將MyInt定義為int類型
type MyInt int

通過type關鍵字的定義,MyInt就是一種新的類型,它具有int的特性。

類型別名#

類型別名規定:TypeAlias 只是 Type 的別名,本質上 TypeAlias 與 Type 是同一個類型

type TypeAlias = Type

我們之前見過的runebyte就是類型別名

type byte = uint8
type rune = int32

結構體定義#

使用typestruct关键字來定義結構體,具體代碼格式如下:

type 類型名 struct {
    欄位名 欄位類型
    欄位名 欄位類型

}

type person struct {
	name string
	city string
	age  int8
}

type person1 struct {
	name, city string
	age        int8
}

其中:

  • 類型名:標識自定義結構體的名稱,在同一個包內不能重複。
  • 欄位名:表示結構體欄位名。結構體中的欄位名必須唯一。
  • 欄位類型:表示結構體欄位的具體類型。

實例化#

只有當結構體實例化時,才會真正地分配內存。必須實例化後才能使用結構體的欄位。

結構體本身也是一種類型,我們可以像聲明內置類型一樣使用var關鍵字聲明結構體類型。

var 結構體實例 結構體類型

基本實例化

type person struct {
	name string
	city string
	age  int8
}

func main() {
	var p1 person
	p1.name = "沙河娜扎"
	p1.city = "北京"
	p1.age = 18
	fmt.Printf("p1=%v\n", p1)  //p1={沙河娜扎 北京 18}
	fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"沙河娜扎", city:"北京", age:18}
}

匿名結構體,用於臨時數據結構

func main() {
    var user struct{Name string; Age int}
    user.Name = "小王子"
    user.Age = 18
    fmt.Printf("%#v\n", user)
}

指針類型結構體,使用 new 分配地址

var p2 = new(person)
//使用&對結構體進行取地址操作相當於對該結構體類型進行了一次new實例化操作
p3 := &person{}


fmt.Printf("%T\n", p2)     //*main.person
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}

//支持對結構體指針直接使用.來訪問結構體的成員
p2.name = "小王子"
p2.age = 28
p2.city = "上海"
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"小王子", city:"上海", age:28}

初始化#

沒有初始化的結構體,其成員變量都是對應其類型的零值。初始化是賦值的實例化。

使用鍵值對初始化

p5 := person{
  name: "小王子",
	city: "北京",
	age:  18,
}

對結構體指針初始化

p6 := &person{
	name: "小王子",
	city: "北京",
	age:  18,
}

用列表初始化

p8 := &person{
	"沙河娜扎",
	"北京",
	28,
}

內存佈局#

結構體佔用一塊連續的內存,空結構體不佔空間

構造函數#

實現類似其他語言面向對象的構造函數,Go 是面向接口編程。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。