Go語言基礎知識總結(持續中)
Go基礎知識總結
變數宣告
Go語言中的變數需要宣告以後才可以使用(需要提前定義變數)並且聲明後必須使用(不適用會報錯)
標準宣告
var 變數名 變數型別
example:
var name string
var id int
var isOk bool
多變數宣告可以整合在一起
var (
name string
id int
isOk bool
)
變數初始化
Go語言在宣告變數的時候,會自動對變數對應的記憶體區進行初始化操作。
var 變數名 變數型別 = 表示式
example:
var name string = "A2rcher" var id int = 123
型別推斷
Go語言提供了一種簡易的方式,不需要提供變數型別,可以根據後面的初始化操作後自動推斷其變數型別
var name = "A2rcher"
var id = 123
短變數宣告
在之後學習到的函式中,還可以用更簡單的方式來宣告一個變數(也是最常用的宣告方法),使用:=
宣告並初始化變數
package main
import (
"fmt"
)
var m = 100 //全域性變數
func main(){
n:=100
m:=300//宣告並初始化區域性變數
fmt.Println(m, n)
}
匿名變數
所謂匿名,通俗的講就是沒有名字的變數,用下劃線_
表示,匿名變數不佔用名稱空間(Go語言中變數聲明後必須使用,匿名變數除外),不會分配記憶體,所以匿名變數之間不存在重複宣告。
package main
import "fmt"
func foo() (string, int) {
return "A2rcher", 20
}
func main() {
x, _ := foo()
_, y := foo()
fmt.Println("x=", x, "y=", y)
}
常量
變數中還有一中是常量,它相對於變數來講是永恆不變的值,對於變數來說一開始可以不做賦值直接定義型別,但是對於常量來講他需要在定義的時候就要賦值,用const
const p = 3.1415
const e = 2.7182
多常量宣告可以整合在一起
const ( p = 3.1415 e = 2.7182 )
iota常量計算器
iota是Go語言中的常量計算器,在常量表達式中使用。
package main
import "fmt"
const (
n1 = iota
n2
n3
n4
)
func main() {
fmt.Println(n1)//0
fmt.Println(n2)//1
fmt.Println(n3)//2
fmt.Println(n4)//3
}
iota
在const關鍵字出現時將被重置為0。const中每新增一行常量宣告將使iota
計數一次(iota可理解為const語句塊中的行索引)。 使用iota能簡化定義,在定義列舉時很有用。
使用下劃線_
可以跳過其中一個
package main
import "fmt"
const (
n1 = iota//0
_ //1
n2 //2
n3 //3
n4 //4
)
func main() {
fmt.Println(n1)//0
fmt.Println(n2)//2
fmt.Println(n3)//3
fmt.Println(n4)//4
}
資料型別
整型
型別 | 描述 |
---|---|
uint8 | 無符號 8位整型 (0 到 255) |
uint16 | 無符號 16位整型 (0 到 65535) |
uint32 | 無符號 32位整型 (0 到 4294967295) |
uint64 | 無符號 64位整型 (0 到 18446744073709551615) |
int8 | 有符號 8位整型 (-128 到 127) |
int16 | 有符號 16位整型 (-32768 到 32767) |
int32 | 有符號 32位整型 (-2147483648 到 2147483647) |
int64 | 有符號 64位整型 (-9223372036854775808 到 9223372036854775807) |
特殊整型
型別 | 描述 |
---|---|
uint | 32位作業系統上就是uint32 ,64位作業系統上就是uint64 |
int | 32位作業系統上就是int32 ,64位作業系統上就是int64 |
uintptr | 無符號整型,用於存放一個指標 |
浮點型
Go語言支援兩種浮點型數:float32
和float64
。這兩種浮點型資料格式遵循IEEE 754
標準: float32
的浮點數的最大範圍約為 3.4e38
,可以使用常量定義:math.MaxFloat32
。 float64
的浮點數的最大範圍約為 1.8e308
,可以使用一個常量定義:math.MaxFloat64
。
列印浮點數時,可以使用fmt
包配合動詞%f
,程式碼如下:
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("%f\n", math.Pi)
fmt.Printf("%.2f\n", math.Pi)
}
複數
complex64和complex128
var c1 complex64
c1 = 1 + 2i
var c2 complex128
c2 = 2 + 3i
fmt.Println(c1)
fmt.Println(c2)
複數有實部和虛部,complex64的實部和虛部為32位,complex128的實部和虛部為64位。
布林
字串
byte和rune
運算子
運算子 | 描述 |
---|---|
+ | 相加 |
- | 相減 |
* | 相乘 |
/ | 相除 |
% | 求餘 |
運算子 | 描述 |
---|---|
== | 檢查兩個值是否相等,如果相等返回 True 否則返回 False。 |
!= | 檢查兩個值是否不相等,如果不相等返回 True 否則返回 False。 |
> | 檢查左邊值是否大於右邊值,如果是返回 True 否則返回 False。 |
>= | 檢查左邊值是否大於等於右邊值,如果是返回 True 否則返回 False。 |
< | 檢查左邊值是否小於右邊值,如果是返回 True 否則返回 False。 |
<= | 檢查左邊值是否小於等於右邊值,如果是返回 True 否則返回 False。 |
運算子 | 描述 |
---|---|
&& | 邏輯 AND 運算子。 如果兩邊的運算元都是 True,則為 True,否則為 False。 |
|| | 邏輯 OR 運算子。 如果兩邊的運算元有一個 True,則為 True,否則為 False。 |
! | 邏輯 NOT 運算子。 如果條件為 True,則為 False,否則為 True。 |
運算子 | 描述 |
---|---|
& | 參與運算的兩數各對應的二進位相與。 (兩位均為1才為1) |
| | 參與運算的兩數各對應的二進位相或。 (兩位有一個為1就為1) |
^ | 參與運算的兩數各對應的二進位相異或,當兩對應的二進位相異時,結果為1。 (兩位不一樣則為1) |
<< | 左移n位就是乘以2的n次方。 “a<<b”是把a的各二進位全部左移b位,高位丟棄,低位補0。 |
>> | 右移n位就是除以2的n次方。 “a>>b”是把a的各二進位全部右移b位。 |
運算子 | 描述 |
---|---|
= | 簡單的賦值運算子,將一個表示式的值賦給一個左值 |
+= | 相加後再賦值 |
-= | 相減後再賦值 |
*= | 相乘後再賦值 |
/= | 相除後再賦值 |
%= | 求餘後再賦值 |
<<= | 左移後賦值 |
>>= | 右移後賦值 |
&= | 按位與後賦值 |
|= | 按位或後賦值 |
^= | 按位異或後賦值 |
流程控制
if else
for 初始語句;條件表示式;結束語句{
迴圈語句
}
example:
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}
for迴圈的初始語句可以被忽略,但是用作與隔開的分號;
還是要有的。
package main
import "fmt"
var i = 0
func main() {
for ; i < 10; i++ {
fmt.Println(i)
}
}
for迴圈的結束語句也是可以省略掉的
package main
import "fmt"
var i = 0
func main() {
for i < 10 {
fmt.Println(i)
i++
}
}
for range(鍵值迴圈)
鍵值迴圈可以用作與遍歷,切片,map還有channel。遵循一下規律
- 陣列、切片、字串返回索引和值。
- map返回鍵和值。
- 通道(channel)只返回通道內的值。
switch
goto(跳轉指定標籤)
goto的使用可以這麼理解,在一個迴圈中當執行到某一個位置時我不想讓他繼續執行下去而是跳到其他的地方,這個時候就可以使用goto,用來簡化一些程式碼的實現過程。
func gotoDemo1() {
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
}
}
}
使用goto
語句能簡化程式碼:
func gotoDemo2() {
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迴圈")
}
break
跳出迴圈
continue
繼續下次迴圈
陣列
陣列定義
var 變數名 [元素數量]T
var a [3]int
var b [4]string
陣列初始化
package main
import "fmt"
func main() {
var testArray [3]int
var numArray = [3]int{1, 2, 3}
var strArray = [3]string{"a", "b", "c"}
fmt.Println(testArray)
fmt.Println(numArray)
fmt.Println(strArray)
}
使用[...]可以根據初始值自動判斷元素有多少
package main
import "fmt"
func main() {
var testArray [3]int
var numArray = [...]int{1, 2, 3,4,5}
var strArray = [...]string{"a", "b", "c", "d","f","v"}
fmt.Println(testArray)
fmt.Println(numArray)
fmt.Println(strArray)
}
陣列遍歷
for迴圈遍歷
package main
import "fmt"
func main() {
var testArray [3]int
var numArray = [...]int{1, 2, 3, 4, 5}
var strArray = [...]string{"a", "b", "c", "d", "f", "v"}
fmt.Println(testArray)
fmt.Println(numArray)
fmt.Println(strArray)
for i := 0; i < len(numArray); i++ {
fmt.Println(i)
}
for v := 0; v < len(strArray); v++ {
fmt.Println(strArray[v])
}
}
for range遍歷
package main
import "fmt"
func main() {
var testArray [3]int
var numArray = [...]int{1, 2, 3, 4, 5}
var strArray = [...]string{"a", "b", "c", "d", "f", "v"}
fmt.Println(testArray)
fmt.Println(numArray)
fmt.Println(strArray)
for i, v := range numArray {
fmt.Println(i, v)
}
fmt.Println("----------------------------------------------------")
for m, n := range strArray {
fmt.Println(m, string(n))
}
}
執行結果可以明顯看出他兩個是有區別的。
for迴圈遍歷的結果並沒有把角標打印出來,而是直接出的結果。
for range迴圈遍歷的結果時把對應的角標也列印了出來,所以我們可以改善一下,使用匿名變數可以讓for range的結果for迴圈的結果一樣。
package main
import "fmt"
func main() {
var testArray [3]int
var numArray = [...]int{1, 2, 3, 4, 5}
var strArray = [...]string{"a", "b", "c", "d", "f", "v"}
fmt.Println(testArray)
fmt.Println(numArray)
fmt.Println(strArray)
for _, v := range numArray {
fmt.Println(v)
}
fmt.Println("----------------------------------------------------")
for _, n := range strArray {
fmt.Println(string(n))
}
}
同樣的原理,可以對多為陣列進行遍歷。
切片
切片跟陣列很像,但是切片相當於在陣列型別的基礎上做了一層封裝,相較於陣列可以更快的操作一塊資料集合。
切片定義
var 變數名 [元素個數]T
可以看到他的定義方式跟陣列一模一樣,所以他的初始化也是一樣的。
切片長度和容量
用內建函式len(),cap()可以求出切片的長度容量
package main
import "fmt"
func main() {
var test1Slice [3]int
fmt.Println(len(test1Slice))
fmt.Println(cap(test1Slice))
var test2Slice = [...]string{"a", "b", "c", "d", "e", "f"}
fmt.Println(len(test2Slice))
fmt.Println(cap(test2Slice))
}
表示式
切片表示式中有low和high兩個定義來表示切片中的界限值
func main() {
a := [5]int{1, 2, 3, 4, 5}
s := a[1:3] // s := a[low:high]
fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))
}
a[2:] // 等同於 a[2:len(a)]
a[:3] // 等同於 a[0:3]
a[:] // 等同於 a[0:len(a)]
make()函式構造切片
make([]T,元素數量,切片容量)
example:
func main(){
a := make([]int,2,10)
}
參考
判斷切片是否為空
一定要使用len(s)==0
來判斷,不能使用nil比較來判斷。
append()方法新增元素
append()方法為切片同臺新增元素,可以一次新增一個或者多個,還可以新增另一個其他切片中的元素。
package main
import "fmt"
func main() {
var s []int
s = append(s, 1)
s = append(s, 2, 3, 4)
s2 := []int{2, 3, 4}
s = append(s, s2...)
fmt.Println(s)
}
map使用
map:是一種無序的基於key-value
的資料結構,必須初始化才可以使用。
map定義
map[keyType]valueType
- KeyType:表示鍵的型別。
- ValueType:表示鍵對應的值的型別。
map初始化
map預設初始值為nil,使用make()函式分配記憶體初始化:
make(map[keyType]valueType,[cap])
其中cap表示map的容量,該引數雖然不是必須的,但是我們應該在初始化map的時候就為其指定一個合適的容量。
map使用
package main
import "fmt"
func main() {
sourceMap := make(map[string]int, 20)
sourceMap["A2rcher"] = 20
sourceMap["emo"] = 30
fmt.Println(sourceMap)
fmt.Printf("type %T\n", sourceMap)
}
map可以在宣告的時候就填充元素:
package main
import "fmt"
func main() {
sourceMap := make(map[string]int, 20)
sourceMap["A2rcher"] = 20
sourceMap["emo"] = 30
fmt.Println(sourceMap)
fmt.Printf("type %T\n", sourceMap)
fmt.Println("***************")
sourceTest := map[string]int{
"lucher": 20,
"xs":10,
}
fmt.Println(sourceTest)
}
判斷鍵值對是否存在
package main
import "fmt"
func main() {
sourceMap := make(map[string]int, 20)
sourceMap["A2rcher"] = 20
sourceMap["emo"] = 30
fmt.Println(sourceMap)
fmt.Printf("type %T\n", sourceMap)
fmt.Println("***************")
sourceTest := map[string]int{
"lucher": 20,
"xs": 10,
}
fmt.Println(sourceTest)
fmt.Printf("type %T\n", sourceTest)
fmt.Println("______________________________")
v, ok := sourceMap["emo"]
if !ok {
fmt.Println("查無此人")
} else {
fmt.Println(v)
}
}
map遍歷
for range遍歷
package main
import "fmt"
func main() {
sourceMap := make(map[string]int, 20)
sourceMap["a"] = 90
sourceMap["b"] = 100
sourceMap["c"] = 60
for k, v := range sourceMap {
fmt.Println(k, v)
}
}
如果只想遍歷前面的key時,可以把v省略。但要是想遍歷value時,就需要匿名變數的幫助了。
package main
import "fmt"
func main() {
sourceMap := make(map[string]int, 20)
sourceMap["趙"] = 90
sourceMap["錢"] = 100
sourceMap["孫"] = 60
for k, v := range sourceMap {
fmt.Println(k, v)
}
for k := range sourceMap {
fmt.Println(k)
}
for _, m := range sourceMap {
fmt.Println(m)
}
}
delete()函式刪除鍵值對
元素為map型別的切片
這個可以理解為在切片裡面,各個元素得型別是map。
例如:
var a = make([]map[string]int,3)
值為切片型別的map
這個可以理解為在map函式裡面key值是切片。
例如:
var a1 = make(map[string][]int,3)
函式
函式.最重要的一部分
函式定義
func 函式名(引數)(返回值){
函式體
}
example:
func TestFunction(x int,y,int)int{
return x + y
}
對於引數部分如果兩個引數型別是一樣的可以簡化
func TestFunction(x,y int)int{
return x + y
}
可變引數
可變引數就是引數數量不固定,參考陣列...
返回值
Go語言中函式支援多返回值,函式如果有多個返回值時必須用()
將所有返回值包裹起來。
func calc(x, y int) (int, int) {
sum := x + y
sub := x - y
return sum, sub
}
返回值命名
函式定義時可以給返回值命名,並在函式體中直接使用這些變數,最後通過return
關鍵字返回。
例如:
func calc(x, y int) (sum, sub int) {
sum = x + y
sub = x - y
return
}
返回值補充
當我們的一個函式返回值型別為slice時,nil可以看做是一個有效的slice,沒必要顯示返回一個長度為0的切片。
func someFunc(x string) []int {
if x == "" {
return nil // 沒必要返回[]int{}
}
...
}
全域性變數
在函式外定義的變數,函式內可以訪問到全域性變數
區域性變數
在函式內定義的變數,只能在函式內訪問得到。
注意:
如果區域性變數和全域性變數成名,有限訪問區域性變數。
定義函式型別
type 函式型別 func(int ,int) int
函式作為引數
package main
import "fmt"
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)
}
函式作為返回值
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(引數)(返回值){
函式體
}
匿名函式沒有辦法像普通函式那樣子呼叫,他需要儲存在某一個變數中(就是賦值),然後在執行。
package main
import "fmt"
func test() {
func () {
fmt.Println("匿 名 函 數")
}()
}
func main() {
test()
}
匿名函式執行:
func test() {
func () {
fmt.Println("匿 名 函 數")
}()//在後面加上括號就相當於執行
}
或者賦值:
package main
import "fmt"
func test() func() {
return func() {
fmt.Println("匿 名 函 數")
}
}
func main() {
r := test()
r()
}
閉包
閉包指的是一個函式和與其相關的引用環境組合而成的實體。簡單來說,閉包=函式+引用環境
。
package main
import "fmt"
//閉包簡單示例 //閉包概念 閉包=函式+外層變數的引用
func test() func() {
name := "A2rcher"
return func() {
fmt.Println("匿 名 函 數",name) // 如果在匿名函式中找不到呼叫的變數,他就會向外層去找
} //這個外層變數並不是全域性變數,而是函式test()中的區域性變數
}
func main() {
r := test() // r引用了函式test()中的變數還有匿名函式 ,可以說r此時就是一個閉包
r()
}
閉包還可以這麼寫(比較典型的一個例子)
package main
import "fmt"
//閉包簡單示例 //閉包概念 閉包=函式+外層變數的引用
func test(name string) func() {
return func() {
fmt.Println("匿 名 函 數", name)
}
}
func main() {
r := test("A2rcher")
r()
}
example:
兩個很經典的例子 [參考部落格](Go語言基礎之函式 | 李文周的部落格 (liwenzhou.com))
panic & recover & defer
panic/recover:可以理解為異常處理模式(但是Go語言中並沒有異常處理機制,只是這樣方便理解),
package main
import "fmt"
//panic and recover
func a() {
fmt.Println("func is a")
}
//recover必須配合defer使用,而且defer一定要在panic前定義。
func b() {
defer func() {
err := recover()
if err != nil { //如果err不等於nil,說明這個程式出錯
fmt.Println("func b is err ")
}
}()
panic("func is b")
}
func c() {
fmt.Println("func is c")
}
func main() {
a()
b()
c()
}
程式執行期間funcB
中引發了panic
導致程式崩潰,異常退出了。這個時候我們就可以通過recover
將程式恢復回來,繼續往後執行。
內建函式
內建函式 | 介紹 |
---|---|
close | 主要用來關閉channel |
len | 用來求長度,比如string、array、slice、map、channel |
new | 用來分配記憶體,主要用來分配值型別,比如int、struct。返回的是指標 |
make | 用來分配記憶體,主要用來分配引用型別,比如chan、map、slice |
append | 用來追加元素到陣列、slice中 |
panic和recover | 用來做錯誤處理 |
指標
Go語言中指標沒有偏移跟運算,只需要搞清楚三個概念和記住兩個符號:指標地址,指標型別,指標取值,&
(取地址)和*
(根據地址取值)。。
結構體
類型別名和自定義型別
type定義關鍵字型別
type myInt int
//將myInt定義成int型別
結構體定義
type 型別名 struct{
欄位名 欄位型別
欄位名 欄位型別
...
}
由於Go語言不是面向物件程式語言,是一個面向介面的變成語言,所以他本身不想java那樣有多型繼承等關係,在Go語言中通過struct
來實現面向物件的。(struct YYDS!)
- 型別名:標識自定義結構體的名稱,在同一個包內不能重複。
- 欄位名:表示結構體欄位名。結構體中的欄位名必須唯一。
- 欄位型別:表示結構體欄位的具體型別。
很簡單的例子:
type Person struct{
name string
address string
age int
sex string
}
自定義一個Person型別,他有name,address,age,sex四個欄位分別代表個自內容。如果我要使用Person中某一個欄位,我可以直接呼叫Person就行了。
結構體例項化
結構體例項化後才可以分配記憶體使用(例項化後才可以使用自定義型別)
由於結構體本身就是一共型別,所以在宣告的時候可以像宣告變數一樣宣告結構體
var 結構體例項 結構體型別
example:
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
var p1 Person
//通過.來訪問結構體的欄位(成員變數),例如p1.name和p1.age等。
p1.name = "A2rcher"
p1.age = 20
fmt.Println(p1)
}
匿名結構體
匿名結構體是用來處理一些臨時的資料,比如說我現在A,B兩個函式,但是我臨時需要使用c資料,這個時候可以用到匿名結構體。
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
var p1 Person
p1.name = "A2rcher"
p1.age = 20
var user struct {
Name string `json:"name"`
Age int `json:"age"`
}
user.Age = 30
user.Name = "emo"
fmt.Println(p1)
fmt.Println(user)
}
指標型別結構體
指標型別結構體是絕大部分情況下用到的,可以通過new
關鍵字對結構體進行例項化得到結構體的地址。(new跟make的區別以及使用的場景參考函式中的內建函式以及指標部分)