前言#
Go 簡介#
- Google 開源
- 編譯型語言
- 21 世紀的 C 語言
2005 年出現多核處理器,其他語言都是單核時代誕生的。Go 天生考慮了多核並發。
特點:
- 語法簡潔(只有 25 個關鍵字,比 Python 更簡潔,自帶格式化,互相閱讀容易)
- 開發效率高
- 執行性能好(接近 java)
發展:
百度自動駕駛,小程序
騰訊藍鯨,微服務框架
知乎最早用 python 寫,後期承受不了負載,用 go 重構節省了 80% 資源。
課程簡介#
8 周基礎
3 個實戰項目
Go 項目結構#
個人開發者
流行方式
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 {
循環體語句
}
通過break
、goto
、return
、panic
語句強制退出循環
遍歷#
for range
遍歷數組、切片、字符串、map 及通道(channel)
for i,v := range s{
fmt.Println(i, v)
}
- 數組、切片、字符串返回索引和值。
- map 返回鍵和值。
- 通道(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{
{"北京", "上海"},
{"廣州", "深圳"},
{"成都", "重慶"},
}
多維數組只有第一層可以使用
...
來讓編譯器推導數組長度
數組是值類型,賦值和傳參會複製整個數組。因此改變副本的值,不會改變本身的值。
- 數組支持 “==“、”!=” 操作符,因為內存總是被初始化過的。
[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
從上面的結果可以看出:
append()
函數將元素追加到切片的最後並返回該切片。- 切片 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
*/
//面試題 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()
}
recover()
必須搭配defer
使用。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
我們之前見過的rune
和byte
就是類型別名
type byte = uint8
type rune = int32
結構體定義#
使用type
和struct
关键字來定義結構體,具體代碼格式如下:
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 是面向接口編程。