1688運(yùn)營(yíng)自學(xué)全套教程seo網(wǎng)站推廣工具
初見(jiàn)golang語(yǔ)法
package mainimport "fmt"func main() {/* 簡(jiǎn)單的程序 萬(wàn)能的hello world */fmt.Println("Hello Go")}
- 第一行代碼package main定義了包名。你必須在源文件中非注釋的第一行指明這個(gè)文件屬于哪個(gè)包,如:package main。package main表示一個(gè)可獨(dú)立執(zhí)行的程序,每個(gè) Go 應(yīng)用程序都包含一個(gè)名為 main 的包
-
下一行import "fmt"告訴 Go 編譯器這個(gè)程序需要使用 fmt 包(的函數(shù),或其他元素),fmt 包實(shí)現(xiàn)了格式化 IO(輸入/輸出)的函數(shù)
-
下一行func main()是程序開(kāi)始執(zhí)行的函數(shù)。main 函數(shù)是每一個(gè)可執(zhí)行程序所必須包含的,一般來(lái)說(shuō)都是在啟動(dòng)后第一個(gè)執(zhí)行的函數(shù)(如果有 init() 函數(shù)則會(huì)先執(zhí)行該函數(shù))
注意:這里面go語(yǔ)言的語(yǔ)法,定義函數(shù)的時(shí)候,‘{’ 必須和函數(shù)名在同一行,不能另起一行
變量聲明
單獨(dú)變量聲明
第一種,指定變量類型,聲明后若不賦值,使用默認(rèn)值0
var a int
fmt.Printf(" = %d\n", a)
第二種,根據(jù)值自行進(jìn)行判斷變量類型
package mainimport ("fmt""reflect"
)func main() {var a = "abc"fmt.Println("a的類型是:", reflect.TypeOf(a))
}
第三種,省略var, 注意 :=左側(cè)的變量不應(yīng)該是已經(jīng)聲明過(guò)的,否則會(huì)導(dǎo)致編譯錯(cuò)誤
a := 10
b := "hello"var c int
c := 20 // 這里會(huì)報(bào)錯(cuò),因?yàn)?`c` 已經(jīng)聲明過(guò)了
總結(jié)
package mainimport "fmt"func main() {//第一種 使用默認(rèn)值var a intfmt.Printf("a = %d\n", a)//第二種var b int = 10fmt.Printf("b = %d\n", b)//第三種 省略后面的數(shù)據(jù)類型,自動(dòng)匹配類型var c = 20fmt.Printf("c = %d\n", c)//第四種 省略var關(guān)鍵字d := 3.14fmt.Printf("d = %f\n", d)
}
多變量聲明
package mainimport "fmt"var x, y int
var ( //這種分解的寫(xiě)法,一般用于聲明全局變量a intb bool
)var c, d int = 1, 2
var e, f = 123, "liudanbing"//這種不帶聲明格式的只能在函數(shù)體內(nèi)聲明
//g, h := 123, "需要在func函數(shù)體內(nèi)實(shí)現(xiàn)"func main() {g, h := 123, "需要在func函數(shù)體內(nèi)實(shí)現(xiàn)"fmt.Println(x, y, a, b, c, d, e, f, g, h)//不能對(duì)g變量再次做初始化聲明//g := 400_, value := 7, 5 //實(shí)際上7的賦值被廢棄,變量 _ 不具備讀特性//fmt.Println(_) //_變量的是讀不出來(lái)的fmt.Println(value) //5
}
常量?
基本使用
常量是一個(gè)簡(jiǎn)單值的標(biāo)識(shí)符,在程序運(yùn)行時(shí),不會(huì)被修改的量。
常量中的數(shù)據(jù)類型只可以是布爾型、數(shù)字型(整數(shù)型、浮點(diǎn)型和復(fù)數(shù))和字符串型。
- 常量的定義格式:?const identifier [type] = value
- 顯示定義:const b string = "abc"
- 隱式定義:const b = "abc"
例如:
package mainimport "fmt"func main() {const LENGTH int = 10const WIDTH int = 5 var area intconst a, b, c = 1, false, "str" //多重賦值area = LENGTH * WIDTHfmt.Printf("面積為 : %d\n", area)println(a, b, c)
}//運(yùn)行結(jié)果
面積為 : 50
1 false str
常量聲明枚舉
在 Go 語(yǔ)言中沒(méi)有直接的枚舉類型,但是我們可以可以定義一組常量來(lái)表示枚舉值
const (Unknown = 0Female = 1Male = 2
)
// 數(shù)字 0、1 和 2 分別代表未知性別、女性和男性
常量可以用len(), cap(), unsafe.Sizeof()常量計(jì)算表達(dá)式的值。常量表達(dá)式中,函數(shù)必須是內(nèi)置函數(shù),否則編譯不過(guò):
package mainimport "unsafe"
const (a = "abc"b = len(a)c = unsafe.Sizeof(a)
)func main(){println(a, b, c)
}
iota標(biāo)識(shí)符
在我們定義一些常量的時(shí)候,可能需要給它們進(jìn)行賦值。在Go語(yǔ)言當(dāng)中,就可以簡(jiǎn)化這個(gè)賦值操作通過(guò)iota標(biāo)示符,如:
package mainimport "fmt"const (Sunday = iotaMondayTuesdayWednesdayThursdayFridaySaturday
)func main() {today := Mondayfmt.Println("Today is:", today)
}
iota不僅僅用于自增,還可以根據(jù)定義一些百搭是,來(lái)進(jìn)行賦值操作,如:
const (d = iota * 2ef
)func main() {fmt.Println(d) // 0fmt.Println(e) // 2fmt.Println(f) // 4
}
函數(shù)
函數(shù)返回多個(gè)值
Go的函數(shù)可以返回多個(gè)值,例如:
package mainimport "fmt"func swap(x, y string) (string, string) {return y, x
}
func main() {a, b := swap("a", "b")fmt.Println(a, " ", b)
}//執(zhí)行結(jié)果:b a
init函數(shù)和import
init 函數(shù)可在package main中,可在其他package中,可在同一個(gè)package中出現(xiàn)多次,而main 函數(shù)只能在package main中
執(zhí)行順序
golang里面保留了兩個(gè)函數(shù):init函數(shù)(能應(yīng)用于所有的package)和main函數(shù)(只能應(yīng)用于package main)。這兩個(gè)函數(shù)在定義的時(shí)候不能有任何參數(shù)和返回值
雖然一個(gè)package里面可以寫(xiě)任意多個(gè)init函數(shù),但這無(wú)論是對(duì)于可讀性還是以后的可維護(hù)性來(lái)說(shuō),我們都強(qiáng)烈建議用戶在一個(gè)package中每個(gè)文件只寫(xiě)一個(gè)init函數(shù)。go程序會(huì)自動(dòng)調(diào)用init()和main(),所以你不需要在任何地方調(diào)用這兩個(gè)函數(shù)。每個(gè)package中的init函數(shù)都是可選的,但package main就必須包含一個(gè)main函數(shù)
程序的初始化和執(zhí)行都起始于main包。如果main包還導(dǎo)入了其它的包,那么就會(huì)在編譯時(shí)將它們依次導(dǎo)入。有時(shí)一個(gè)包會(huì)被多個(gè)包同時(shí)導(dǎo)入,那么它只會(huì)被導(dǎo)入一次(例如很多包可能都會(huì)用到fmt包,但它只會(huì)被導(dǎo)入一次,因?yàn)闆](méi)有必要導(dǎo)入多次)。當(dāng)一個(gè)包被導(dǎo)入時(shí),如果該包還導(dǎo)入了其它的包,那么會(huì)先將其它包導(dǎo)入進(jìn)來(lái),然后再對(duì)這些包中的包級(jí)常量和變量進(jìn)行初始化,接著執(zhí)行init函數(shù)(如果有的話),依次類推。等所有被導(dǎo)入的包都加載完畢了,就會(huì)開(kāi)始對(duì)main包中的包級(jí)常量和變量進(jìn)行初始化,然后執(zhí)行main包中的init函數(shù)(如果存在的話),最后執(zhí)行main函數(shù)。下圖詳細(xì)地解釋了整個(gè)執(zhí)行過(guò)程:
代碼示例:
package InitLib1import "fmt"func init() {fmt.Println("lib1")
}package InitLib2import "fmt"func init() {fmt.Println("lib2")
}package mainimport ("fmt"_ "GolangTraining/InitLib1"_ "GolangTraining/InitLib2"
)func init() {fmt.Println("libmain init")
}func main() {fmt.Println("libmian main")
}//執(zhí)行結(jié)果:
lib1
lib2
libmain init
libmian main
輸出的順序與我們上面圖給出的順序是一致的。那我們現(xiàn)在就改動(dòng)一個(gè)地方,Lib1包導(dǎo)入Lib2,main包不管:
package InitLib1import ("fmt"_ "GolangTraining/InitLib2"
)func init() {fmt.Println("lib1")
}//輸出結(jié)果:
lib2
lib1
libmain init
libmian main
main包以及Lib1包都導(dǎo)入了Lib2,但是只出現(xiàn)一次,并且最先輸出,說(shuō)明如果一個(gè)包會(huì)被多個(gè)包同時(shí)導(dǎo)入,那么它只會(huì)被導(dǎo)入一次,而先輸出lib2是因?yàn)閙ain包中導(dǎo)入Lib1時(shí),Lib1又導(dǎo)入了Lib2,會(huì)首先初始化Lib2包的東西
注意:如果需要將當(dāng)前的包函數(shù)提供給外部,需要將函數(shù)名首字母大寫(xiě)!!!否則會(huì)引用不了!!!
函數(shù)參數(shù)
函數(shù)的參數(shù)在傳遞的時(shí)候主要是分為兩種類型:值傳遞和指針傳遞
值傳遞:值傳遞是指在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)復(fù)制一份傳遞到函數(shù)中,這樣在函數(shù)中如果對(duì)參數(shù)進(jìn)行修改,將不會(huì)影響到實(shí)際參數(shù)
/* 定義相互交換值的函數(shù) */
func swap(x, y int) int {var temp inttemp = x /* 保存 x 的值 */x = y /* 將 y 值賦給 x */y = temp /* 將 temp 值賦給 y*/return temp;
}
指針傳遞:指針傳遞是指在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)的地址傳遞到函數(shù)中,那么在函數(shù)中對(duì)參數(shù)所進(jìn)行的修改,將影響到實(shí)際參數(shù)
/* 定義交換值函數(shù)*/
func swap(x *int, y *int) {var temp inttemp = *x /* 保持 x 地址上的值 */*x = *y /* 將 y 值賦給 x */*y = temp /* 將 temp 值賦給 y */
}
defer
defer類似于我們Java中的finally,它會(huì)在我們程序執(zhí)行的最后執(zhí)行一些任務(wù)defer語(yǔ)句被用于預(yù)定對(duì)一個(gè)函數(shù)的調(diào)用??梢园堰@類被defer語(yǔ)句調(diào)用的函數(shù)稱為延遲函數(shù)。
defer作用:
- 釋放占用的資源
- 捕捉處理異常
- 輸出日志
如果一個(gè)函數(shù)中有多個(gè)defer語(yǔ)句,它們會(huì)以LIFO(后進(jìn)先出)的順序執(zhí)行:
func Demo(){defer fmt.Println("1")defer fmt.Println("2")defer fmt.Println("3")defer fmt.Println("4")
}
func main() {Demo()
}//運(yùn)行結(jié)果:4 3 2 1
panic和recover
panic(宕機(jī)) 和 recover(恢復(fù))是Go語(yǔ)言的兩個(gè)內(nèi)置函數(shù),這兩個(gè)內(nèi)置函數(shù)用來(lái)處理Go的運(yùn)行時(shí)錯(cuò)誤(runtime errors)。panic 用來(lái)主動(dòng)拋出異常,recover用來(lái)捕獲panic拋出的異常
引發(fā)panic有兩種情況:一種是程序主動(dòng)調(diào)用panic()函數(shù),另一種是程序產(chǎn)生運(yùn)行時(shí)錯(cuò)誤,由運(yùn)行時(shí)檢測(cè)并拋出。例如:
示例1:程序主動(dòng)調(diào)用panic,觸發(fā)宕機(jī),讓程序崩潰:
func funcA() {fmt.Println("func A")
}func funcB() {panic("panic in B")
}func funcC() {fmt.Println("func C")
}
func main() {funcA()funcB()funcC()
}
運(yùn)行結(jié)果:func A
panic: panic in Bgoroutine 1 [running]:
main.funcB(...)/home/wangxm/go_work/src/chapter05/demo.go:12
main.main()/home/wangxm/go_work/src/chapter05/demo.go:20 +0x96
exit status 2
當(dāng)在funcB中主動(dòng)調(diào)用了panic 函數(shù)后,程序發(fā)生宕機(jī)直接退出了,同時(shí)輸出了堆棧和goroutine相關(guān)信息,這讓我們可以看到錯(cuò)誤發(fā)生的位置。當(dāng)panic()觸發(fā)的宕機(jī)發(fā)生時(shí),panic后面的代碼將不會(huì)被執(zhí)行,但是在panic()函數(shù)前面的已經(jīng)執(zhí)行過(guò)的defer語(yǔ)句依然會(huì)在宕機(jī)發(fā)生時(shí)執(zhí)行defer中的延遲函數(shù)。
示例2:在宕機(jī)時(shí)觸發(fā)defer語(yǔ)句延遲函數(shù)的執(zhí)行:
func funcA() {fmt.Println("func A")
}func funcB() {panic("panic in B")
}func funcC() {fmt.Println("func C")
}func funcD() {fmt.Println("func D")
}func main() {defer funcA()defer funcC()fmt.Println("this is main")funcB()defer funcD()
}
運(yùn)行結(jié)果:this is main
func C
func A
panic: panic in Bgoroutine 1 [running]:
main.funcB(...)/home/wangxm/go_work/src/chapter05/demo.go:12
main.main()/home/wangxm/go_work/src/chapter05/demo.go:29 +0xca
exit status 2
從運(yùn)行結(jié)果可以看出,當(dāng)程序流程正常執(zhí)行到funcB()函數(shù)的panic語(yǔ)句時(shí),在panic()函數(shù)被執(zhí)行前,defer語(yǔ)句會(huì)優(yōu)先被執(zhí)行,defer語(yǔ)句的執(zhí)行順序是先進(jìn)后出,所以funcC()延遲函數(shù)先執(zhí)行,funcA()后執(zhí)行,當(dāng)所有已注冊(cè)的defer語(yǔ)句都執(zhí)行完畢,才會(huì)執(zhí)行panic()函數(shù),觸發(fā)宕機(jī),程序崩潰退出,因此程序流程執(zhí)行不到funcD()函數(shù)。
發(fā)生panic后,程序會(huì)從調(diào)用panic的函數(shù)位置或發(fā)生panic的地方立即返回,逐層向上執(zhí)行函數(shù)的defer語(yǔ)句,然后逐層打印函數(shù)調(diào)用堆棧信息,直到被recover捕獲或運(yùn)行到最外層函數(shù)而退出。如本例中,程序從funcB()函數(shù)返回到上層的main()函數(shù)中,然后執(zhí)行已注冊(cè)的defer語(yǔ)句的延遲函數(shù),最后從main函數(shù)中退出,并且打印了退出狀態(tài)碼的值為2
recover()函數(shù)用來(lái)捕獲或者說(shuō)是攔截panic的,阻止panic繼續(xù)向上層傳遞。無(wú)論是主動(dòng)調(diào)用panic()函數(shù)觸發(fā)的宕機(jī)還是程序在運(yùn)行過(guò)程中由Runtime層拋出的異常,都可以配合defer 和 recover 實(shí)現(xiàn)異常捕獲和恢復(fù),讓代碼在發(fā)生panic后能夠繼續(xù)執(zhí)行
Go語(yǔ)言沒(méi)有異常系統(tǒng),其使用panic觸發(fā)宕機(jī)類似于其他語(yǔ)言的拋出異常,而recover的宕機(jī)恢復(fù)機(jī)制就對(duì)應(yīng)try...catch機(jī)制
示例:使用recover捕獲panic異常,恢復(fù)程序的運(yùn)行:
func funcA() {fmt.Println("func A")
}func funcB() {defer func(){//捕獲panic,并恢復(fù)程序使其繼續(xù)運(yùn)行if err := recover(); err != nil {fmt.Println("recover in funcB")}}()panic("panic in B") //主動(dòng)拋出異常
}func funcC() {fmt.Println("func C")
}func funcD() {fmt.Println("func D")
}func main() {defer funcA()defer funcC()fmt.Println("this is main")funcB()defer funcD()
}運(yùn)行結(jié)果:this is main
recover in funcB
func D
func C
func A
當(dāng)recover捕獲到panic時(shí),不會(huì)造成整個(gè)進(jìn)程的崩潰,它會(huì)從觸發(fā)panic的位置退出當(dāng)前函數(shù),然后繼續(xù)執(zhí)行后續(xù)代碼
IF判斷
在Go語(yǔ)言中,if
語(yǔ)句用于條件判斷,它有以下幾種常見(jiàn)的用法和特點(diǎn):
基本用法
- 語(yǔ)法結(jié)構(gòu)
其中if condition {// 當(dāng)條件為真時(shí)執(zhí)行的代碼塊 }
condition
是一個(gè)布爾表達(dá)式,如果condition
的值為true
,則執(zhí)行花括號(hào)內(nèi)的代碼塊。 - 示例
num := 10 if num > 5 {fmt.Println("num大于5") }
帶有else
子句
- 語(yǔ)法結(jié)構(gòu)
if condition {// 當(dāng)條件為真時(shí)執(zhí)行的代碼塊 } else {// 當(dāng)條件為假時(shí)執(zhí)行的代碼塊 }
- 示例
num := 3 if num > 5 {fmt.Println("num大于5") } else {fmt.Println("num小于等于5") }
帶有else if
子句
- 語(yǔ)法結(jié)構(gòu)
if condition1 {// 當(dāng)條件1為真時(shí)執(zhí)行的代碼塊 } else if condition2 {// 當(dāng)條件2為真時(shí)執(zhí)行的代碼塊 } else {// 當(dāng)條件1和條件2都為假時(shí)執(zhí)行的代碼塊 }
- 示例
num := 7 if num > 10 {fmt.Println("num大于10") } else if num > 5 {fmt.Println("num大于5且小于等于10") } else {fmt.Println("num小于等于5") }
在if
語(yǔ)句中聲明和初始化變量
- 語(yǔ)法結(jié)構(gòu)
可以在if
語(yǔ)句的條件判斷部分同時(shí)聲明和初始化一個(gè)變量,這個(gè)變量的作用域僅限于if
語(yǔ)句及其相關(guān)的else
和else if
子句if var_declaration := expression; condition {// 當(dāng)條件為真時(shí)執(zhí)行的代碼塊,且可以使用var_declaration變量 }
- 示例
在這個(gè)例子中,if num := 8; num > 5 {fmt.Println("num大于5,num的值為:", num) }
num
在if
語(yǔ)句中被聲明和初始化,并且只能在if
相關(guān)的代碼塊中使用。如果num
在if
語(yǔ)句外已經(jīng)存在,那么在if
語(yǔ)句中使用這種方式聲明num
會(huì)導(dǎo)致編譯錯(cuò)誤,因?yàn)?code>if語(yǔ)句中這種方式的變量聲明會(huì)被視為一個(gè)新的局部變量聲明
循環(huán)
在Go語(yǔ)言中,有兩種主要的循環(huán)結(jié)構(gòu):for
循環(huán)和range
循環(huán)(range
可以看作是一種特殊的基于for
循環(huán)的便利形式,用于迭代容器類型的數(shù)據(jù))。
for
循環(huán)
-
基本
for
循環(huán)- 語(yǔ)法結(jié)構(gòu)
for initialization; condition; post {// 循環(huán)體 }
initialization
:循環(huán)開(kāi)始前的初始化操作,通常用于聲明和初始化循環(huán)變量。condition
:循環(huán)的條件判斷,只要該條件為true
,循環(huán)就會(huì)繼續(xù)執(zhí)行。post
:每次循環(huán)結(jié)束后執(zhí)行的操作,通常用于更新循環(huán)變量。
- 示例
這個(gè)循環(huán)會(huì)從for i := 0; i < 5; i++ {fmt.Println(i) }
i = 0
開(kāi)始,只要i < 5
就會(huì)執(zhí)行循環(huán)體,每次循環(huán)結(jié)束后i
的值會(huì)增加1
,最終會(huì)打印出0
到4
這幾個(gè)數(shù)字。
- 語(yǔ)法結(jié)構(gòu)
range
循環(huán)
-
用于迭代數(shù)組和切片
- 語(yǔ)法結(jié)構(gòu)
其中for index, value := range arrayOrSlice {// 對(duì)索引和值進(jìn)行處理 }
index
是數(shù)組或切片元素的索引,value
是元素的值。對(duì)于每個(gè)元素,循環(huán)都會(huì)執(zhí)行一次。 - 示例
這個(gè)循環(huán)會(huì)遍歷切片arr := []int{1, 2, 3, 4, 5} for index, value := range arr {fmt.Printf("索引為 %d 的元素值為 %d\n", index, value) }
arr
,并打印出每個(gè)元素的索引和值。
- 語(yǔ)法結(jié)構(gòu)
-
用于迭代字符串
- 字符串在Go語(yǔ)言中可以看作是一個(gè)字節(jié)數(shù)組,所以也可以用
range
循環(huán)來(lái)迭代。 - 語(yǔ)法結(jié)構(gòu)
for index, value := range stringValue {// 對(duì)索引和值進(jìn)行處理// 需要注意的是,這里的價(jià)值可能是一個(gè)字節(jié)(對(duì)于ASCII字符)或者是一個(gè)Unicode碼點(diǎn)的第一個(gè)字節(jié)(對(duì)于非ASCII字符)// 如果要獲取完整的Unicode碼點(diǎn),需要進(jìn)一步處理 }
- 示例
這里使用str := "hello" for index, value := range str {fmt.Printf("索引為 %d 的字符值為 %c\n", index, rune(value)) }
rune
函數(shù)將字節(jié)值轉(zhuǎn)換為Unicode碼點(diǎn)對(duì)應(yīng)的字符,以便正確打印出字符串中的字符。
- 字符串在Go語(yǔ)言中可以看作是一個(gè)字節(jié)數(shù)組,所以也可以用
-
用于迭代映射(map)
- 語(yǔ)法結(jié)構(gòu)
其中for key, value := range mapValue {// 對(duì)鍵和值進(jìn)行處理 }
key
是映射的鍵,value
是對(duì)應(yīng)鍵的值。對(duì)于每個(gè)鍵值對(duì),循環(huán)都會(huì)執(zhí)行一次。 - 示例
這個(gè)循環(huán)會(huì)遍歷映射m := map[string]int{"a": 1, "b": 2, "c": 3} for key, value := range m {fmt.Printf("鍵為 %s 的值為 %d\n", key, value) }
m
,并打印出每個(gè)鍵值對(duì)的鍵和值。
- 語(yǔ)法結(jié)構(gòu)
集合
集合分為slice和map兩種,其中slice是數(shù)組的抽象。
slice
slice是數(shù)組的抽象,Go 數(shù)組的長(zhǎng)度不可改變,在特定場(chǎng)景中這樣的集合就不太適用,Go中提供了一種靈活,功能強(qiáng)悍的內(nèi)置類型切片("動(dòng)態(tài)數(shù)組")
,與數(shù)組相比切片的長(zhǎng)度是不固定的,可以追加元素,在追加時(shí)可能使切片的容量增大
方式一:聲明一個(gè)未指定大小的數(shù)組來(lái)定義切片
var identifier []type
方式二:使用make()函數(shù)來(lái)創(chuàng)建切片
var slice1 []type = make([]type, len)
也可以簡(jiǎn)寫(xiě)為
slice1 := make([]type, len)//也可以指定容量,其中capacity為可選參數(shù)
make([]T, length, capacity)
?make
函數(shù)參數(shù)說(shuō)明
length
- 表示切片初始的長(zhǎng)度,即切片中元素的個(gè)數(shù)。這個(gè)值決定了切片創(chuàng)建后可以直接訪問(wèn)的元素范圍。例如,如果
length
為3
,那么可以通過(guò)切片索引0
、1
、2
來(lái)訪問(wèn)元素
- 表示切片初始的長(zhǎng)度,即切片中元素的個(gè)數(shù)。這個(gè)值決定了切片創(chuàng)建后可以直接訪問(wèn)的元素范圍。例如,如果
capacity
- 表示切片的容量,它是切片底層數(shù)組的大小。切片的容量必須大于等于長(zhǎng)度。容量決定了切片在不重新分配內(nèi)存的情況下能夠擴(kuò)展的最大程度。例如,如果
capacity
為5
,長(zhǎng)度為3
,那么在不重新分配內(nèi)存的情況下,切片可以通過(guò)append
操作最多再容納2
個(gè)元素
- 表示切片的容量,它是切片底層數(shù)組的大小。切片的容量必須大于等于長(zhǎng)度。容量決定了切片在不重新分配內(nèi)存的情況下能夠擴(kuò)展的最大程度。例如,如果
操作方法:
append
函數(shù)
newSlice := append(slice, elements...)
其中slice
是原始切片,elements
是要添加的一個(gè)或多個(gè)元素。append
函數(shù)會(huì)返回一個(gè)新的切片,這個(gè)新切片包含了原始切片的所有元素以及新添加的元素
copy函數(shù)
/* 拷貝 numbers 的內(nèi)容到 numbers1 */copy(numbers1,numbers)
len函數(shù)
//獲取切片長(zhǎng)度
len(slice)
cap函數(shù)
//獲取切片容量
cap(slice)
切片截取:可以通過(guò)設(shè)置下限及上限來(lái)設(shè)置截取切片[lower-bound:upper-bound]
package mainimport "fmt"func main() {/* 創(chuàng)建切片 */numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}printSlice(numbers)/* 打印原始切片 */fmt.Println("numbers ==", numbers)/* 打印子切片從索引1(包含) 到索引4(不包含)*/fmt.Println("numbers[1:4] ==", numbers[1:4])/* 默認(rèn)下限為 0*/fmt.Println("numbers[:3] ==", numbers[:3])/* 默認(rèn)上限為 len(s)*/fmt.Println("numbers[4:] ==", numbers[4:])numbers1 := make([]int, 0, 5)printSlice(numbers1)/* 打印子切片從索引 0(包含) 到索引 2(不包含) */number2 := numbers[:2]printSlice(number2)/* 打印子切片從索引 2(包含) 到索引 5(不包含) */number3 := numbers[2:5]printSlice(number3)}func printSlice(x []int) {fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
運(yùn)行結(jié)果:
len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
numbers == [0 1 2 3 4 5 6 7 8]
numbers[1:4] == [1 2 3]
numbers[:3] == [0 1 2]
numbers[4:] == [4 5 6 7 8]
len=0 cap=5 slice=[]
len=2 cap=9 slice=[0 1]
len=3 cap=7 slice=[2 3 4]
map
映射(map)是Go語(yǔ)言中一種用于存儲(chǔ)鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu)。以下是一些常見(jiàn)的map操作方法:
1. 創(chuàng)建map
- 可以使用
make
函數(shù)創(chuàng)建map,語(yǔ)法為make(map[KeyType]ValueType)
,其中KeyType
是鍵的類型,ValueType
是值的類型。例如m := make(map[string]int)
創(chuàng)建了一個(gè)鍵為字符串類型,值為整數(shù)類型的map。
2. 插入鍵值對(duì)
- 可以直接給map賦值來(lái)插入鍵值對(duì)。例如
m := make(map[string]int); m["key1"] = 1
,這里將鍵為"key1"
,值為1
的鍵值對(duì)插入到map中。
3. 訪問(wèn)鍵值對(duì)
- 使用鍵來(lái)訪問(wèn)map中的值。例如
m := make(map[string]int); m["key1"] = 1; fmt.Println(m["key1"])
會(huì)打印出鍵為"key1"
的值1
。 - 如果訪問(wèn)的鍵不存在于map中,會(huì)返回值類型的零值。例如
m := make(map[string]int); fmt.Println(m["nonexistent_key"])
會(huì)打印出整數(shù)類型的零值0
。
4. 修改鍵值對(duì)
- 可以使用鍵來(lái)修改map中的值。例如
m := make(map[string]int); m["key1"] = 1; m["key1"] = 2; fmt.Println(m["key1"])
會(huì)將鍵為"key1"
的值從1
修改為2
。
5. 刪除鍵值對(duì)
- 使用
delete
函數(shù)來(lái)刪除鍵值對(duì),語(yǔ)法為delete(map, key)
,其中map
是要操作的map,key
是要?jiǎng)h除的鍵。例如m := make(map[string]int); m["key1"] = 1; delete(m, "key1"); fmt.Println(m["key1"])
會(huì)刪除鍵為"key1"
的值,并返回整數(shù)類型的零值0
,因?yàn)樵撴I值對(duì)已經(jīng)被刪除。
6. 檢查鍵是否存在
- 可以使用一種特殊的語(yǔ)法來(lái)檢查鍵是否存在于map中,同時(shí)獲取對(duì)應(yīng)的值。例如
m := make(map[string]int); m["key1"] = 1; value, exists := m["key1"]; fmt.Println(value, exists)
會(huì)打印出1 true
,因?yàn)殒I"key1"
存在于map中,并且對(duì)應(yīng)的值為1
。如果鍵不存在,exists
會(huì)返回false
,value
會(huì)返回值類型的零值。
7. 遍歷map
- 使用
range
來(lái)遍歷map,會(huì)依次獲取map中的鍵值對(duì)。例如m := make(map[string]int); m["key1"] = 1; m["ive had no joy with maps in GoLang!": "Here is an example of a text that could be inserted into a map, though it might not be a very practical one"]; for key, value := range m { fmt.Println(key, value) }
會(huì)遍歷map中的鍵值對(duì)并打印出來(lái)。
8. map的長(zhǎng)度
- 使用
len
函數(shù)可以獲取map的長(zhǎng)度,即map中鍵值對(duì)的個(gè)數(shù)。例如m := make(map[string]int); m["key1"] = 1; m["key2"] = 2; fmt.Println(len(m))
會(huì)打印出2
,因?yàn)檫@個(gè)map中有兩個(gè)鍵值對(duì)
結(jié)構(gòu)體
在 Go 語(yǔ)言中,結(jié)構(gòu)體(struct)是一種復(fù)合數(shù)據(jù)類型,它允許用戶將不同類型的數(shù)據(jù)組合在一起,形成一個(gè)新的類型。以下是關(guān)于結(jié)構(gòu)體的詳細(xì)介紹:
結(jié)構(gòu)體的定義
結(jié)構(gòu)體的定義一個(gè)是使用struct關(guān)鍵字,基礎(chǔ)語(yǔ)法如下:
type StructName struct {Field1 Type1Field2 Type2//...
}
其中,type
是關(guān)鍵字用于定義新的類型,StructName
是結(jié)構(gòu)體的名稱,struct
是結(jié)構(gòu)體的關(guān)鍵字,后面跟著大括號(hào),大括號(hào)內(nèi)是結(jié)構(gòu)體的各個(gè)字段(field),每個(gè)字段由字段名和字段類型組成。例如:
type Person struct {Name stringAge int
}
定義了一個(gè)名為Person的結(jié)構(gòu)體,它包含兩個(gè)字段:Name
(字符串類型)和Age
(整數(shù)類型)
結(jié)構(gòu)體變量的創(chuàng)建和初始化
- 方法一:逐個(gè)字段初始化
可以通過(guò)結(jié)構(gòu)體類型創(chuàng)建結(jié)構(gòu)體變量,并逐個(gè)初始化結(jié)構(gòu)體的字段。例如:
var p Person
p.Name = "Alice"
p.Age = 25
- 方法二:使用結(jié)構(gòu)體字面量初始化
使用結(jié)構(gòu)體字面量可以在創(chuàng)建結(jié)構(gòu)體變量的同時(shí)初始化結(jié)構(gòu)體的字段。有兩種形式: - 按字段順序初始化:
p := Person{"Bob", 30}
這種方式要求按照結(jié)構(gòu)體定義時(shí)字段的順序提供值。
- 指定字段名初始化:
p := Person{Name: "Charlie", Age: 35}
這種方式可以不按照結(jié)構(gòu)體定義時(shí)字段的順序提供值,只要指定正確的字段名和值即可。
結(jié)構(gòu)體的嵌套
- 結(jié)構(gòu)體可以嵌套其他結(jié)構(gòu)體。例如:
type Address struct {City stringStreet stringZipCode int
}type Employee struct {PersonJobTitle stringAddress AddressSalary int
}
這里定義了Address
結(jié)構(gòu)體和Employee
結(jié)構(gòu)體,Employee
結(jié)構(gòu)體嵌套了Person
結(jié)構(gòu)體和Address
結(jié)構(gòu)體。當(dāng)訪問(wèn)嵌套結(jié)構(gòu)體的字段時(shí),可以使用.
操作符進(jìn)行多級(jí)訪問(wèn)。例如:e := Employee{Person{"David", 40}, "Engineer", Address{"New York", "5th Avenue", 10001}, 80000}; fmt.Println(e.Person.Name)
會(huì)打印出David
。
結(jié)構(gòu)體方法
- 可以為結(jié)構(gòu)體定義方法,結(jié)構(gòu)體方法類似于面向?qū)ο笳Z(yǔ)言中的類方法。結(jié)構(gòu)體方法的定義語(yǔ)法如下:
func (s StructName) MethodName() ReturnType {// 方法體
}
其中,(s StructName)
部分稱為接收者(receiver),它表示該方法是屬于StructName
結(jié)構(gòu)體的,s
是接收者變量,可以在方法體中使用接收者變量來(lái)訪問(wèn)結(jié)構(gòu)體的相關(guān)字段。例如:
func (p Person) GetName() string {return p.Name
}
定義了一個(gè)Person
結(jié)構(gòu)體的方法GetName
,它返回Person
結(jié)構(gòu)體的Name
字段的值。
結(jié)構(gòu)體的內(nèi)存布局
- 結(jié)構(gòu)體在內(nèi)存中的布局是按照字段定義的順序依次排列的,每個(gè)字段占用一定的內(nèi)存空間。不同類型的字段在內(nèi)存中所占的空間大小不同,例如,整數(shù)類型通常占用4個(gè)字節(jié)(在32位系統(tǒng)中)或8個(gè)字節(jié)(在62位系統(tǒng)中),字符串類型的內(nèi)存占用則比較復(fù)雜,它包含一個(gè)指針和一些其他信息。
- 當(dāng)結(jié)構(gòu)體嵌套其他結(jié)構(gòu)體時(shí),嵌套的結(jié)構(gòu)體的內(nèi)存空間也是按照其自身的字段定義順序依次排列在主結(jié)構(gòu)體的內(nèi)存空間內(nèi)。
結(jié)構(gòu)體的比較
- 如果結(jié)構(gòu)體的所有字段都是可比較的,那么結(jié)構(gòu)體本身也是可比較的。比較兩個(gè)結(jié)構(gòu)體時(shí),會(huì)逐個(gè)比較結(jié)構(gòu)體的各個(gè)字段。例如:
p1 := Person{"Alice", 25}
p2 := Person{"Alice", 25}
if p1 == p2 {fmt.Println("p1和p2相等")
}
如果結(jié)構(gòu)體中有不可比較的字段(如切片、映射等),那么結(jié)構(gòu)體本身不可比較
接口
接口定義
- 首先我們定義了一個(gè)接口
Shape
:
type Shape interface {Area() float64
}
這個(gè)接口規(guī)定了任何實(shí)現(xiàn)它的類型都必須有一個(gè)Area
方法,該方法沒(méi)有參數(shù)并且返回一個(gè)float64
類型的值。這就像是一個(gè)合同,規(guī)定了實(shí)現(xiàn)這個(gè)接口的類型需要具備計(jì)算面積的能力。
接口實(shí)現(xiàn)
- 然后我們有一個(gè)
Rectangle
結(jié)構(gòu)體:
type Rectangle struct {length float64width float64
}
并且為這個(gè)結(jié)構(gòu)體定義了一個(gè)Area
方法:
func (r Rectangle) Area() float64 {return r.length * r.width
}
通過(guò)定義這個(gè)Area
方法,Rectangle
結(jié)構(gòu)體滿足了Shape
接口的要求,也就是說(shuō)Rectangle
結(jié)構(gòu)體實(shí)現(xiàn)了Shape
接口。這就好比Rectangle
結(jié)構(gòu)體簽署了Shape
接口這個(gè)合同,它具備了按照合同要求計(jì)算面積的能力。
接口使用
- 作為函數(shù)參數(shù):我們定義了一個(gè)函數(shù)
CalculateArea
:
func CalculateArea(s Shape) float64 {return s.Area()
}
在這個(gè)函數(shù)中,參數(shù)s
是Shape
接口類型。這意味著我們可以傳入任何實(shí)現(xiàn)了Shape
接口的類型的值作為參數(shù)。當(dāng)我們調(diào)用這個(gè)函數(shù)時(shí),如果傳入的是Rectangle
結(jié)構(gòu)體的值(因?yàn)?code>Rectangle實(shí)現(xiàn)了Shape
接口),那么在函數(shù)內(nèi)部就會(huì)調(diào)用Rectangle
結(jié)構(gòu)體的Area
方法來(lái)計(jì)算面積。
- 作為變量類型:我們還可以定義一個(gè)
Shape
接口類型的變量s
:
var s Shape
r := Rectangle{length: 5, width: 3}
s = r
fmt.Println(s.Area())
這里首先定義了一個(gè)Shape
接口類型的變量s
,然后創(chuàng)建了一個(gè)Rectangle
結(jié)構(gòu)體的值r
,并將r
賦值給s
。因?yàn)?code>Rectangle實(shí)現(xiàn)了Shape
接口,所以這種賦值是合法的。最后我們可以通過(guò)s
調(diào)用Area
方法來(lái)計(jì)算面積,實(shí)際上是調(diào)用了Rectangle
結(jié)構(gòu)體的Area
方法。
這樣通過(guò)接口,我們可以實(shí)現(xiàn)不同類型之間的通用性和多態(tài)性,使得代碼更加靈活和可維護(hù)。例如,如果我們還有一個(gè)Square
結(jié)構(gòu)體也實(shí)現(xiàn)了Shape
接口,那么我們可以同樣使用CalculateArea
函數(shù)來(lái)計(jì)算它的面積,而不需要為Square
重新定義一個(gè)計(jì)算面積的函數(shù)。
面向?qū)ο蟮奶匦?/h3>
Go 語(yǔ)言雖然不是純粹的面向?qū)ο缶幊陶Z(yǔ)言,但它支持一些面向?qū)ο蟮奶匦?#xff0c;主要包括封裝、繼承和多態(tài),以下是具體介紹:
封裝性
定義:封裝是指將數(shù)據(jù)和操作數(shù)據(jù)的方法組合在一起,并對(duì)外部隱藏?cái)?shù)據(jù)的實(shí)現(xiàn)細(xì)節(jié),只提供必要的接口來(lái)訪問(wèn)和操作數(shù)據(jù)
實(shí)現(xiàn)方式:通過(guò)結(jié)構(gòu)體和方法實(shí)現(xiàn),結(jié)構(gòu)體用于定義數(shù)據(jù)結(jié)構(gòu),將相關(guān)的數(shù)據(jù)組合在一起。例如,定義一個(gè)Rectangle
結(jié)構(gòu)體來(lái)表示矩形的長(zhǎng)和寬:
type Rectangle struct {length float64width float64
}
方法用于操作結(jié)構(gòu)體中的數(shù)據(jù)。例如,為Rectangle
結(jié)構(gòu)體定義一個(gè)計(jì)算面積的方法:
func (r Rectangle) Area() float64 {return r.length * r.width
}
通過(guò)這種方式,Rectangle
結(jié)構(gòu)體的內(nèi)部數(shù)據(jù)(長(zhǎng)和寬)被封裝起來(lái),外部只能通過(guò)Area
方法來(lái)獲取矩形的面積,而無(wú)法直接訪問(wèn)長(zhǎng)和寬的數(shù)據(jù)
看下面的例子會(huì)更加清晰:
package mainimport "fmt"//定義一個(gè)結(jié)構(gòu)體
type T struct {name string
}func (t T) method1() {t.name = "new name1"
}func (t *T) method2() {t.name = "new name2"
}func main() {t := T{"old name"}fmt.Println("method1 調(diào)用前 ", t.name)t.method1()fmt.Println("method1 調(diào)用后 ", t.name)fmt.Println("method2 調(diào)用前 ", t.name)t.method2()fmt.Println("method2 調(diào)用后 ", t.name)
}
運(yùn)行結(jié)果:
method1 調(diào)用前 old name
method1 調(diào)用后 old name
method2 調(diào)用前 old name
method2 調(diào)用后 new name2
當(dāng)調(diào)用t.method1()
時(shí)相當(dāng)于method1(t)
,實(shí)參和行參都是類型 T,可以接受。此時(shí)在method1
()中的t只是參數(shù)t的值拷貝,所以method1
()的修改影響不到main中的t變量。
當(dāng)調(diào)用t.method2()
=>method2(t)
,這是將 T 類型傳給了 *T 類型,go可能會(huì)取 t 的地址傳進(jìn)去:method2(&t)
。所以 method1
() 的修改可以影響 t。
所以說(shuō)下面的代碼保證了封裝性:
func (t T) method1() {t.name = "new name1"
}
繼承
在Go語(yǔ)言中,雖然沒(méi)有像傳統(tǒng)面向?qū)ο笳Z(yǔ)言中那樣的類繼承機(jī)制,但可以通過(guò)結(jié)構(gòu)體嵌套實(shí)現(xiàn)類似繼承的效果。以下是詳細(xì)說(shuō)明:
- 繼承是一種機(jī)制,它允許一個(gè)類型(子類或派生類)繼承另一個(gè)類型(父類或基類)的屬性和方法,從而實(shí)現(xiàn)代碼的復(fù)用和擴(kuò)展。子類可以在繼承父類的基礎(chǔ)上添加自己的特性,并且可以重寫(xiě)父類的某些方法以滿足自身的需求。
Go語(yǔ)言中的實(shí)現(xiàn)方式 - 結(jié)構(gòu)體嵌套
- 假設(shè)我們有一個(gè)
Base
結(jié)構(gòu)體代表基類,它具有一些屬性和方法。例如:
type Base struct {Property1 stringProperty2 int
}func (b Base) Method1() {// 基類方法的實(shí)現(xiàn)
}
- 現(xiàn)在我們想要?jiǎng)?chuàng)建一個(gè)
Derived
結(jié)構(gòu)體來(lái)繼承Base
結(jié)構(gòu)體的特性。我們可以通過(guò)結(jié)構(gòu)體嵌套來(lái)實(shí)現(xiàn):
type Derived struct {BaseAdditionalProperty string
}
- 通過(guò)這種嵌套方式,
Derived
結(jié)構(gòu)體繼承了Base
結(jié)構(gòu)體的所有屬性和方法。例如,Derived
結(jié)構(gòu)體可以直接訪問(wèn)Base
結(jié)構(gòu)體的Property1
和Property2
屬性,并且可以調(diào)用Base
結(jié)構(gòu)體的Method1
方法。
- 在Go語(yǔ)言中,雖然沒(méi)有嚴(yán)格的方法重寫(xiě)語(yǔ)法,但通過(guò)結(jié)構(gòu)體嵌套和方法定義,可以實(shí)現(xiàn)類似的效果。
- 假設(shè)我們想要在
Derived
結(jié)構(gòu)體中重寫(xiě)Base
結(jié)構(gòu)體的Method1
方法。我們可以在Derived
結(jié)構(gòu)體中重新定義一個(gè)Method1
方法:
func (d Derived) Method1() {// 新的方法實(shí)現(xiàn),可能會(huì)調(diào)用原始Base結(jié)構(gòu)體的方法或者完全替代它// 例如,可以先調(diào)用原始Base結(jié)構(gòu)體的方法,然后再添加一些額外的操作d.Base.Method1()// 其他額外操作
}
- 這樣,當(dāng)我們調(diào)用
Derived
結(jié)構(gòu)體的Method1
方法時(shí),執(zhí)行的是我們?cè)?code>Derived結(jié)構(gòu)體中重新定義的方法,而不是Base
結(jié)構(gòu)體的原始方法。
- 雖然結(jié)構(gòu)體嵌套實(shí)現(xiàn)了類似繼承的功能,但它與傳統(tǒng)的繼承機(jī)制還是有一些區(qū)別。例如,在Go語(yǔ)言中,沒(méi)有像其他語(yǔ)言中那樣的類層次結(jié)構(gòu)和多態(tài)性的嚴(yán)格語(yǔ)法定義。
- 這種結(jié)構(gòu)體嵌套的方式在實(shí)際應(yīng)用中需要謹(jǐn)慎使用,要確保代碼的可讀性和可維護(hù)性。如果嵌套過(guò)于復(fù)雜,可能會(huì)導(dǎo)致代碼難以理解和維護(hù)。
多態(tài)
定義:多態(tài)是指不同類型的對(duì)象對(duì)同一方法調(diào)用可以表現(xiàn)出不同的行為。
實(shí)現(xiàn)方式:通過(guò)接口實(shí)現(xiàn)多態(tài),首先定義一個(gè)接口,接口中規(guī)定了一些方法,但沒(méi)有具體的實(shí)現(xiàn)。例如,定義一個(gè)Shape
接口,要求實(shí)現(xiàn)Area
方法:
type Shape interface {Area() float64
}
然后讓不同的結(jié)構(gòu)體實(shí)現(xiàn)這個(gè)接口。例如,Rectangle
和Square
結(jié)構(gòu)體都可以實(shí)現(xiàn)Shape
接口:
func (r Rectangle) Area() float64 {return r.length * r.width
}
func (s Square) Area() float64 {return s.sideLength * s.sideLength
}
當(dāng)使用接口類型的變量時(shí),根據(jù)實(shí)際賦值的結(jié)構(gòu)體不同,調(diào)用Area
方法會(huì)表現(xiàn)出不同的行為。例如:
var s Shape
r := Rectangle{length: 5, width: 3}
s = r
fmt.Println(s.Area()) // 輸出15
s1 := Square{sideLength: 4}
s = s1
fmt.Println(s.Area()) // 輸出16
通過(guò)接口實(shí)現(xiàn)了多態(tài),使得程序可以根據(jù)不同的對(duì)象類型靈活地執(zhí)行相應(yīng)的操作。