Go語言學(xué)習(xí)筆記(狂神說)
視頻地址:https://www.bilibili.com/video/BV1ae41157o9
1、聊聊Go語言的歷史
聊聊Go語言的歷史-KuangStudy-文章
2、Go語言能做什么
下面列舉的是原生使用Go語言進(jìn)行開發(fā)的部分項目。
Docker
Docker 是一種操作系統(tǒng)層面的虛擬化技術(shù),可以在操作系統(tǒng)和應(yīng)用程序之間進(jìn)行隔離,也可以稱之為容器。Docker 可以在一臺物理服務(wù)器上快速運(yùn)行一個或多個實例。例如,啟動一個 CentOS 操作系統(tǒng),并在其內(nèi)部命令行執(zhí)行指令后結(jié)束,整個過程就像自己在操作系統(tǒng)一樣高效。
項目鏈接:https://github.com/docker/docker
Go語言
Go語言自己的早期源碼使用C語言和匯編語言寫成。從 Go 1.5 版本后,完全使用Go語言自身進(jìn)行編寫。Go語言的源碼對了解Go語言的底層調(diào)度有極大的參考意義,建議希望對Go語言有深入了解的讀者讀一讀。
項目鏈接:https://github.com/golang/go
Kubernetes
Google 公司開發(fā)的構(gòu)建于 Docker 之上的容器調(diào)度服務(wù),用戶可以通過 Kubernetes 集群進(jìn)行云端容器集群管理。系統(tǒng)會自動選取合適的工作節(jié)點(diǎn)來執(zhí)行具體的容器集群調(diào)度處理工作。其核心概念是 Container Pod(容器倉)。
項目鏈接:https://github.com/kubernetes/kubernetes
etcd
一款分布式、可靠的 KV 存儲系統(tǒng),可以快速進(jìn)行云配置。由 CoreOS 開發(fā)并維護(hù)鍵值存儲系統(tǒng),它使用Go語言編寫,并通過 Raft 一致性算法處理日志復(fù)制以保證強(qiáng)一致性。
項目鏈接:https://github.com/coreos/etcd
beego
beego 是一個類似 Python 的 Tornado 框架,采用了 RESTFul 的設(shè)計思路,使用Go語言編寫的一個極輕量級、高可伸縮性和高性能的 Web 應(yīng)用框架。
項目鏈接:https://github.com/astaxie/beego
martini
一款快速構(gòu)建模塊化的 Web 應(yīng)用的Go語言框架。
項目鏈接:https://github.com/go-martini/martini
codis
國產(chǎn)的優(yōu)秀分布式 Redis 解決方案??梢詫?codis 理解成為 Web 服務(wù)領(lǐng)域的 Nginx,它實現(xiàn)了對 Redis 的反向代理和負(fù)載均衡。
項目鏈接:https://github.com/CodisLabs/codis
delve
Go語言強(qiáng)大的調(diào)試器,被很多集成環(huán)境和編輯器整合。
項目鏈接:https://github.com/derekparker/delve
哪些大公司在用
Go語言是谷歌在 2009 年發(fā)布的一款編程語言,自面世以來它以高效的開發(fā)效率和完美的運(yùn)行速度迅速風(fēng)靡全球,被譽(yù)為“21 世紀(jì)的C語言”。
現(xiàn)在越來越多的公司開始使用Go語言開發(fā)自己的服務(wù),同時也誕生了很多使用Go語言開發(fā)的服務(wù)和應(yīng)用,比如 Docker、k8s 等,下面我們來看一下,有哪些大公司在使用Go語言。
作為創(chuàng)造了Go語言的 google 公司,當(dāng)然會力挺Go語言了。Google 有很多基于 Go 開發(fā)的開源項目,比如 kubernets,docker,大家可以參考《哪些項目使用Go語言開發(fā)》一節(jié)了解更多的Go語言開源項目。
Facebook 也在使用Go語言,為此他們還專門在 Github 上建立了一個開源組織 facebookgo。大家可以通過 https://github.com/facebookgo 訪問查看 facebook 開源的項目,其中最具代表性的就是著名平滑重啟工具 grace。
騰訊
騰訊在 15 年就已經(jīng)做了 Docker 萬臺規(guī)模的實踐。因為騰訊主要的開發(fā)語言是 C/C++ ,所以在使用Go語言方面會方便很多,也有很多優(yōu)勢,不過日積月累的 C/C++ 代碼很難改造,也不敢動,所以主要在新業(yè)務(wù)上嘗試使用 Go。
百度
百度主要在運(yùn)維方面使用到了Go語言,比如百度運(yùn)維的一個 BFE 項目,主要負(fù)責(zé)前端流量的接入,其次就是百度消息通訊系統(tǒng)的服務(wù)器端也使用到了Go語言。
七牛云
七牛云算是國內(nèi)第一家選Go語言做服務(wù)端的公司。早在 2011 年,當(dāng)Go語言的語法還沒完全穩(wěn)定下來的情況下,七牛云就已經(jīng)選擇將 Go 作為存儲服務(wù)端的主體語言。
京東
京東云消息推送系統(tǒng)、云存儲,以及京東商城的列表頁等都是使用Go語言開發(fā)的。
小米
小米對Go語言的支持,在于運(yùn)維監(jiān)控系統(tǒng)的開源,它的官方網(wǎng)址是 http://open-falcon.org/ 此外,小米互娛、小米商城、小米視頻、小米生態(tài)鏈等團(tuán)隊都在使用Go語言。
360
360 對Go語言的使用也不少,比如開源的日志搜索系統(tǒng) Poseidon,大家可以通過.
https://github.com/Qihoo360/poseidon 查看,還有 360 的推送團(tuán)隊也在使用Go語言。
除了上面提到的,還有很多公司開始嘗試使用Go語言,比如美團(tuán)、滴滴、新浪等。
Go語言的強(qiáng)項在于它適合用來開發(fā)網(wǎng)絡(luò)并發(fā)方面的服務(wù),比如消息推送、監(jiān)控、容器等,所以在高并發(fā)的項目上大多數(shù)公司會優(yōu)先選擇 Golang 作為開發(fā)語言。
Go語言代碼清爽
Go語言語法類似于C語言,因此熟悉C語言及其派生語言([C++]、[C#]、Objective-C 等)的人都會迅速熟悉這門語言。
C語言的有些語法會讓代碼可讀性降低甚至發(fā)生歧義。Go語言在C語言的基礎(chǔ)上取其精華,棄其糟粕,將C語言中較為容易發(fā)生錯誤的寫法進(jìn)行調(diào)整,做出相應(yīng)的編譯提示。
去掉循環(huán)冗余括號
Go語言在眾多大師的豐富實戰(zhàn)經(jīng)驗的基礎(chǔ)上誕生,去除了C語言語法中一些冗余、煩瑣的部分。下面的代碼是C語言的數(shù)值循環(huán):
// C語言的for數(shù)值循環(huán)for(int a =0;a<10;a++){// 循環(huán)代碼}
在Go語言中,這樣的循環(huán)變?yōu)椋?/p>
for a :=0; a<10; a++ {// 循環(huán)代碼}
for 兩邊的括號被去掉,int 聲明被簡化為:=
,直接通過編譯器右值推導(dǎo)獲得 a 的變量類型并聲明。
去掉表達(dá)式冗余括號
同樣的簡化也可以在判斷語句中體現(xiàn)出來,以下是C語言的判斷語句:
if(表達(dá)式){// 表達(dá)式成立}
在Go語言中,無須添加表達(dá)式括號,代碼如下:
if表達(dá)式{// 表達(dá)式成立}
強(qiáng)制的代碼風(fēng)格
Go語言中,左括號必須緊接著語句不換行。其他樣式的括號將被視為代碼編譯錯誤。這個特性剛開始會使開發(fā)者有一些不習(xí)慣,但隨著對Go語言的不斷熟悉,開發(fā)者就會發(fā)現(xiàn)風(fēng)格統(tǒng)一讓大家在閱讀代碼時把注意力集中到了解決問題上,而不是代碼風(fēng)格上。
同時Go語言也提供了一套格式化工具。一些Go語言的開發(fā)環(huán)境或者編輯器在保存時,都會使用格式化工具對代碼進(jìn)行格式化,讓代碼提交時已經(jīng)是統(tǒng)一格式的代碼。
不再糾結(jié)于 i++ 和 ++i
C語言非常經(jīng)典的考試題為:
int a, b;
a = i++;
b = ++i;
這種題目對于初學(xué)者簡直摸不著頭腦。為什么一個簡單的自增表達(dá)式需要有兩種寫法?
在Go語言中,自增操作符不再是一個操作符,而是一個語句。因此,在Go語言中自增只有一種寫法:
i++
如果寫成前置自增++i
,或者賦值后自增a=i++
都將導(dǎo)致編譯錯誤。
3、Go語言環(huán)境安裝
下載地址:https://studygolang.com/dl
下載完成后一路next即可!
接下來去配置環(huán)境變量。Go語言需要一個安裝目錄,還需要一個工作目錄。即GOROOT和GOPATH。
在**GOPATH**對應(yīng)的目錄下,需要建三個文件夾。src、pkg、bin。
接下來改一下用戶變量:
打開cmd進(jìn)行驗證:
4、GoLand安裝
如果是學(xué)生的話,可以通過大學(xué)郵箱每次免費(fèi)申請1年的正版軟件
如果是個人學(xué)習(xí)使用,可以通過一些公眾號等自行下載破解~
5、HelloWorld
新建一個Hello.go文件
package main
import "fmt"
func main(){
fmt.Println("Hello World!")
}
打開當(dāng)前文件夾的cmd
用GoLang編寫HelloWorld
在Go語言里,命名為main 的包具有特殊的含義。Go語言的編譯程序會試圖把這種名字的包編譯為二進(jìn)制可執(zhí)行文件。所有用Go語言編譯的可執(zhí)行程序都必須有一個名叫main的包。一個可執(zhí)行程序有且僅有一個main包。
6、基礎(chǔ)語法
6.1、注釋
package main
import "fmt"
// 我是單行注釋
/*
我是多行注釋
這是main函數(shù),是程序的入口
*/
func main() {
fmt.Println("Hello,World!")
}
6.2、變量
package main
import "fmt"
func main() {
//name就是變量
var name string = "kuangshen"
name = "zhangsan"
fmt.Println(name)
}
6.3、變量的定義
Go語言是靜態(tài)類型語言,就是所有的類型我們都需要明確的去定義
我們聲明一個變量一般是使用var關(guān)鍵字
var name type
- 第一個var是聲明變量的關(guān)鍵字,是固定的寫法,大家記住即可
- 第二個name,就是我們變量的名字,你可以按照自己的需求給它定一個名字
- 第三個type,就是用來代表變量的類型
//定義一個字符串變量 name
var name String
//定義一個數(shù)字類型變量 int
var age int
批量定義變量
package main
import "fmt"
func main() {
var (
name string
age int
addr string
)
fmt.Println(name, age, addr)
}
如果沒有顯式的給變量賦值,系統(tǒng)自動賦予它該類型的默認(rèn)值
- 整形和浮點(diǎn)型變量的默認(rèn)值為0和0.0
- 字符串變量的默認(rèn)值為空字符串
- 布爾型變量默認(rèn)為false
- 切片、函數(shù)、指針變量的默認(rèn)為nil
6.4、變量初始化
變量初始化的標(biāo)準(zhǔn)格式
// = 是賦值,將右邊的值賦給左邊的變量
var name string = "ss"
短變量聲明并初始化
package main
import "fmt"
func main() {
name := "kuangshen"
age := 3
fmt.Printf("name:%s,age:%d",name,age)
}
這是Go語言的推導(dǎo)聲明寫法,編譯器會自動根據(jù)右值的類型推斷出左值的類型
因為簡潔和靈活的特點(diǎn),簡短變量聲明被廣泛用于大部分的局部變量的聲明和初始化
注意:
由于使用了 := ,而不是賦值的 = ,因此推導(dǎo)聲明寫法的左值**必須是沒有被定義**過的,若定義過,將會發(fā)生編譯錯誤
打印變量類型
fmt.Printf("%T,%T", name, age)
// string,int
6.5、打印內(nèi)存地址
func main() {
var num int = 5
fmt.Printf("num:%d的內(nèi)存地址為:%p\n", num, &num)
num = 100
fmt.Printf("num:%d的內(nèi)存地址為:%p\n", num, &num)
}
/*
num:5的內(nèi)存地址為:0xc0000a6058
num:100的內(nèi)存地址為:0xc0000a6058
*/
6.6、變量交換
func main() {
/*
傳統(tǒng)交換
a = 100;
b = 200
temp = 0;
temp = a;
a = b;
b = temp;
*/
var a int = 100
var b int = 200
b, a = a, b
fmt.Println(a, b)
}
/*
200 100
*/
6.7、匿名變量
匿名變量的特點(diǎn)是一個下畫線"__",本身就是一個特殊的標(biāo)識符,被稱為空白標(biāo)識符。它可以像其他標(biāo)識符那樣用于變量的聲明或賦值(任何類型都可以賦值給它),但任何賦給這個標(biāo)識符的值都將被拋棄,因此這些值不能在后續(xù)的代碼中使用,也不可以使用這
個標(biāo)識符作為變量對其它變量進(jìn)行賦值或運(yùn)算。使用匿名變量時,只需要在變量聲明的地方使用下畫線替換即可。
func main() {
a, _ := test()
fmt.Println(a)
}
func test() (int, int) {
return 100, 200
}
假如在未來我們需要接收一個對象的某個屬性,其他屬性就使用匿名變量接收。
匿名變量不占用內(nèi)存空間,不會分配內(nèi)存,且匿名變量與匿名變量之間也不會因為多次聲明而無法使用。
6.8、變量的作用域
一個變量(常量、類型或函數(shù))在程序中都要一定的作用范圍,稱之為作用域。
了解變量的作用域?qū)ξ覀儗W(xué)習(xí)Go語言來說是比較重要的,因為Go語言會在編譯時檢查每個變量是否使用過,一旦出現(xiàn)未使用的變量,就會報編譯錯誤。如果不能理解變量的作用域,就有可能會帶來一些不明所以的編譯錯誤。
局部變量
在函數(shù)體內(nèi)聲明的變量稱為局部變量,作用域只在函數(shù)體內(nèi)。
func main() {
// a就是局部變量
a, _ := test()
fmt.Println(a)
}
全局變量
在函數(shù)體外聲明的變量稱之為全局變量,全局變量只需要在一個源文件中定義,就可以在所有源文件中使用,當(dāng)然,不包含這個全局變量的源文件需要使用"import"關(guān)鍵字引入全局變量所在的源文件之后才能使用這個全局變量。
全局變量聲明必須以var 關(guān)鍵字開頭,如果想要在外部包中使用全局變量的首字母必須大寫。
var name string = "s"
func main() {
var name string = "a"
fmt.Println(name)
}
/*
a //就近原則
*/
6.9、常量
在Go語言中,我們使用**const**關(guān)鍵字來定義常量,常量的值不允許改變,一般用大寫表示。
func main() {
const URL string = "192.168.1.1" //顯式定義
const URL2 = "192.168.1.1" //隱式定義
const a, b, c = 3.14, "www.baidu.com", true //同時定義
fmt.Println(a, b, c)
}
iota,特殊常量,可以認(rèn)為是一個可以被編譯器修改的常量。iota是go語言的常量計數(shù)器
iota在const關(guān)鍵字出現(xiàn)時將被重置為0(const內(nèi)部的第一行之前),const 中每新增一行常量聲明將使iota計數(shù)一次(iota可理解為const語句塊中的行索引)。
iota可以被用作枚舉值:
func main() {
const (
a = iota
b = iota
c = iota
)
fmt.Println(a, b, c)
}
/*
0 1 2
*/
只要在同一組里,iota的值都會從0開始增加。
func main() {
const (
a = iota
b
c
d = "haha"
e
f = 100
g
h = iota
i
)
const (
j = iota
k
)
fmt.Println(a, b, c, d, e, f, g, h, i)
}
/*
0 1 2 haha haha 100 100 7 8 0 1
*/
7、基本數(shù)據(jù)類型
Go語言是一種靜態(tài)類型的編程語言,在Go編程語言中,數(shù)據(jù)類型用于聲明函數(shù)和變量。數(shù)據(jù)類型的出現(xiàn)是為了把數(shù)據(jù)分成所需內(nèi)存大小不同的數(shù)據(jù),編程的時候需要用大數(shù)據(jù)的時候才需要申請大內(nèi)存,就可以充分利用內(nèi)存。編譯器在進(jìn)行編譯的時候,就要知道每個值的類型,這樣編譯器就知道要為這個值分配多少內(nèi)存,并且知道這段分配的內(nèi)存表示什么。
7.1、布爾型
布爾型的值只可以是常量true或者false。
默認(rèn)值是false。
func main() {
var b1 bool
var b2 bool
b1 = true
b2 = false
fmt.Printf("%T,%t\n", b1, b1)
fmt.Printf("%T,%t\n", b2, b2)
}
/*
bool,true
bool,false
*/
7.2、數(shù)字類型
整型int和浮點(diǎn)型float32、float64,Go語言支持整型和浮點(diǎn)型數(shù)字,并且支持復(fù)數(shù),其中位的運(yùn)算采用補(bǔ)碼。
Go也有基于架構(gòu)的類型,例如:unit無符號、int有符號
func main() {
var i1 int8
var i2 uint8
i1, i2 = 100, 200
fmt.Println(i1)
fmt.Println(i2)
}
浮點(diǎn)型,Go語言中默認(rèn)是float64
func main() {
var money float64 = 3.14
fmt.Printf("%T,%f\n", money, money) //默認(rèn)6位小數(shù)打印,四舍五入
}
常見的別名
7.3、字符與字符串
字符與字符串
func main() {
var str string
str = "Hello,Go"
fmt.Printf("%T,%s\n", str, str)
v1 := 'A'
v2 := "A"
fmt.Printf("%T,%s\n", v1, v1)
fmt.Printf("%T,%s\n", v2, v2)
}
/*
string,Hello,Go
int32,%!s(int32=65) //可以看到單引號定義的變量實際是int32類型,實際上打印的是對應(yīng)ASCII表中的十進(jìn)制值
string,A
*/
部分用法
func main() {
// 拼接
fmt.Printf("aaa" + "bbb\n")
// 轉(zhuǎn)義
fmt.Printf("hello\"bbb\n")
// 換行
fmt.Printf("hello\nbbb")
// Tab
fmt.Printf("hello\tbbb")
}
/*
aaabbb
hello"bbb
hello
bbb
hello bbb
*/
7.4、數(shù)據(jù)類型的轉(zhuǎn)換
在必要以及可行的情況下,一個類型的值可以被轉(zhuǎn)換成另一種類型的值。由于Go語言不存在隱式類型轉(zhuǎn)換,因此所有的類型轉(zhuǎn)換都必須顯式的聲明。
類型B的值 = 類型B(類型A的值)
func main() {
a := 3.14
b := int(a)
fmt.Println(a)
fmt.Println(b)
}
/*
3.14
3 //直接舍去小數(shù)
*/
盡量小轉(zhuǎn)大,因為大轉(zhuǎn)小會造成精度丟失。
8、運(yùn)算符
8.1、算數(shù)運(yùn)算符
8.2、關(guān)系運(yùn)算符
8.3、邏輯運(yùn)算符
8.4、位運(yùn)算符
8.5、賦值運(yùn)算符
這里其實學(xué)過其他語言的話對這些運(yùn)算符應(yīng)該都認(rèn)識,如果學(xué)過C語言,其實Go語言的很多格式和C是一樣的。
9、流程控制
9.1、if語句
進(jìn)行一次判斷,根據(jù)判斷的結(jié)果執(zhí)行不同的代碼段。
/*
if 判斷條件 { //判斷條件的括號可加可不加
滿足條件執(zhí)行的代碼
} else {
不滿足執(zhí)行的代碼
}
*/
package main
import "fmt"
func main() {
score := 90
name := "zhuwenjie"
// 測試1
if score >= 90 && score <= 100 {
fmt.Println("A")
} else if score >= 80 && score < 90 {
fmt.Println("B")
} else if score >= 70 && score < 80 {
fmt.Println("C")
} else if score >= 60 && score < 70 {
fmt.Println("D")
} else {
fmt.Println("不及格")
}
fmt.Println()
// 嵌套if
if score >= 90 {
if name == "zhuwenjie" {
fmt.Println("正確")
} else {
fmt.Println("姓名不正確")
}
} else {
fmt.Println("成績不正確")
}
}
9.2、switch和fallthrough
switch語句用于基于不同條件執(zhí)行不同動作,每一個case分支都是唯一的,從上至下逐一測試,直到匹配為止。
package main
import "fmt"
func main() {
score := 90
switch score {
case 90:
fmt.Println("A")
fallthrough // fallthrough 會無視下一個case的條件,直接執(zhí)行
case 80:
fmt.Println("B")
case 60,70:
fmt.Println("B")
default:
fmt.Println("D)
}
}
/*
A
B
*/
switch語句的執(zhí)行從上至下,直到找到匹配項,找到之后也不需要再加break,switch默認(rèn)情況下case最后自帶break語句。
9.3、for語句
for循環(huán)是一個循環(huán)控制結(jié)構(gòu),可以執(zhí)行指定次數(shù)的循環(huán)。
package main
import "fmt"
func main() {
for i := 1; i <= 9; i++ { //打印99乘法表
for j := 1; j <= i; j++ {
fmt.Printf("%d*%d=%d\t", j, i, j*i)
}
fmt.Println()
}
}
9.4、break與continue
break 結(jié)束當(dāng)前整個循環(huán)
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
if i == 5 {
break
}
fmt.Println(i)
}
}
/*
1
2
3
4
*/
continue 結(jié)束本次循環(huán)
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
if i == 5 {
continue
}
fmt.Println(i)
}
}
/*
1
2
3
4
6
7
8
9
10
*/
9.5、遍歷字符串
package main
import "fmt"
func main() {
score := 90
name := "zhuwenjie"
println(len(name)) // len(str) 獲得字符串的長度
fmt.Printf("%c,%d\n", name[1], name[1])
for i := 0; i < len(name); i++ {
fmt.Printf("%c,%d\n", name[i], name[i])
}
// i是下標(biāo),v是對應(yīng)元素
for i, v := range name { // for i, v := range name 其中i是下標(biāo),v是該下標(biāo)對應(yīng)的值
fmt.Printf("%d,%c,%d\n", i, v, v)
}
}
10、函數(shù)
什么是函數(shù)
- 函數(shù)是基本的代碼塊,用于執(zhí)行一個任務(wù)
- Go程序至少要有一個main函數(shù)
- 通過函數(shù)來劃分功能,邏輯上每個函數(shù)執(zhí)行指定的任務(wù)
- 函數(shù)聲明告訴了編譯器函數(shù)的名稱、返回類型和參數(shù)
10.1、函數(shù)的聲明
Go語言中函數(shù)定義格式如下:
func function_name( [parameter list] ) [return_types] {
函數(shù)體
}
- 無參無返回值函數(shù)
package main
func main() {
printInfo()
}
func printInfo() {
println("我執(zhí)行了printInfo函數(shù)")
}
- 有一個參數(shù)的函數(shù)
package main
func main() {
printInfo(5)
}
func printInfo(a int) {
println(a)
}
- 有兩個參數(shù)的函數(shù)
package main
func main() {
println(add(1, 2))
}
func add(a int, b int) int {
c := a + b
return c
}
- 有一個返回值的函數(shù)
package main
func main() {
println(add(1, 2))
}
func add(a int, b int) int {
c := a + b
return c
}
- 有多個返回值的函數(shù)
package main
func main() {
add, del := addAndDel(1, 2)
println(add, del)
}
func addAndDel(a int, b int) (int, int) {
c := a + b
d := a - b
return c, d
}
10.2、形參和實參
package main
func main() {
// 形參與實參要一一對應(yīng),包括類型和順序
add, del := addAndDel(1, 2)
println(add, del)
}
// addAndDel 計算兩個數(shù)相加和相減的結(jié)果
// 形式參數(shù): a b 定義函數(shù)時,用來接收外部傳入數(shù)據(jù)的參數(shù)
// 實際參數(shù): 1 2 調(diào)用函數(shù)時,傳給形參的實際數(shù)據(jù)
func addAndDel(a int, b int) (int, int) {
c := a + b
d := a - b
//一個函數(shù)在定義時如果有返回值,那么函數(shù)中必須使用return語句
return c, d
}
10.3、可變參數(shù)
概念:一個參數(shù)的參數(shù)類型確定,但個數(shù)不確定,就可以使用可變參數(shù)。
func myfunc(arg ...int){}
// arg ...int 告訴編譯器這個函數(shù)接收不定數(shù)量的參數(shù),類型全是int
package main
func main() {
println(getSum(1, 3, 5, 7, 9))
}
func getSum(nums ...int) int {
sum := 0
for _, num := range nums {
sum += num
}
return sum
}
注意事項:
- 如果一個函數(shù)的參數(shù)是可變參數(shù),同時還有其他參數(shù),可變參數(shù)要放在參數(shù)列表的最后
- 一個函數(shù)的參數(shù)列表中最多只能有一個可變參數(shù)
10.4、參數(shù)傳遞
按照數(shù)據(jù)的存儲特點(diǎn)來分:
- 值類型的數(shù)據(jù):操作的是數(shù)據(jù)本身,基礎(chǔ)數(shù)據(jù)類型、array、struct…
- 引用類型的數(shù)據(jù):操作的是數(shù)據(jù)的地址,slice、map、chan…
10.4.1、值傳遞
package main
import "fmt"
func main() {
//值傳遞
arr := [4]int{1, 2, 3, 4}
fmt.Println(arr)
update(arr, 1, 100)
fmt.Println(arr)
}
// 更改數(shù)組中某個元素的值
func update(arr [4]int, j int, target int) {
arr[j] = target
fmt.Println(arr)
}
/*
[1 2 3 4] //未修改前數(shù)組的值
[1 100 3 4] //update方法里修改后的值
[1 2 3 4] //修改后數(shù)組的值
*/
這里學(xué)過C的話其實很容易明白,C語言里本身就有值傳遞和引用傳遞,程序會為每一個函數(shù)開辟一個空間,因此如果是值傳遞的類型,在這個函數(shù)里改變的其實是原來數(shù)據(jù)的拷貝,因此對原來的數(shù)據(jù)并沒有造成影響。
10.4.2、引用傳遞
package main
import "fmt"
func main() {
//引用傳遞
arr := []int{1, 2, 3, 4}
fmt.Println(arr)
update(arr, 1, 100)
fmt.Println(arr)
}
// 更改切片中某個元素的值
func update(arr []int, j int, target int) {
arr[j] = target
fmt.Println(arr)
}
/*
[1 2 3 4]
[1 100 3 4]
[1 100 3 4]
*/
可以看到函數(shù)里對于切片數(shù)據(jù)的更改也同步到了原切片上,對于引用數(shù)據(jù)類型來說,傳遞的其實是這個參數(shù)對應(yīng)的地址,因此對這個參數(shù)的修改能同步到原函數(shù)里。
10.5、函數(shù)變量的作用域
主要分為全局變量和局部變量,使用時遵循就近原則。
- 定義在函數(shù)內(nèi)的是局部變量
- 定義在函數(shù)外的是全局變量
10.6、遞歸函數(shù)
定義:一個函數(shù)自己調(diào)用自己,就叫做遞歸函數(shù)
注意:遞歸函數(shù)一定要有出口,否則會形成死循環(huán)
/*
以求1~5的和為例 getSum(i)
則getSum(5) = getSum(4) + 5
getSum(4) = getSum(3) + 4
getSum(3) = getSum(2) + 3
getSum(2) = getSum(1) + 2
getSum(1) = 1
*/
package main
func main() {
println(getSum(5))
}
func getSum(num int) int {
if num == 1 {
return 1
} else {
return getSum(num-1) + num
}
}
10.7、defer
defer語義:推遲、延遲
在Go語言中,使用defer關(guān)鍵字來延遲一個函數(shù)或者方法的執(zhí)行。
package main
import "fmt"
func main() {
f("1")
fmt.Println("2")
defer f("3")
fmt.Println("4")
}
func f(s string) {
fmt.Println(s)
}
/*
1
2
4
3
*/
defer函數(shù)或方法:該函數(shù)或方法的執(zhí)行被延遲
- 如果一個函數(shù)中添加了多個defer語句,當(dāng)函數(shù)執(zhí)行到最后時,這些defer語句會按照逆序執(zhí)行,最后該函數(shù)返回。特別是當(dāng)進(jìn)行一些打開資源的操作時,遇到錯誤需要提前返回,在返回前需要關(guān)閉相應(yīng)的資源,不然容易造成資源泄露問題。
- 如果有很多調(diào)用defer,那么defer用的是后進(jìn)先出(棧)模式。
package main
import "fmt"
func main() {
f("1")
defer f("2")
defer f("3")
fmt.Println("4")
}
func f(s string) {
fmt.Println(s)
}
/*
1
4
3
2
*/
需要注意的是,在執(zhí)行到defer語句的時候,函數(shù)里的形參就已經(jīng)傳遞進(jìn)去了,只是函數(shù)被延遲執(zhí)行了。
package main
import "fmt"
func main() {
a := 10
fmt.Println("begin a=", a)
defer f(a)
a++
fmt.Println("end a=", a)
}
func f(s int) {
fmt.Println(s)
}
/*
begin a= 10
end a= 11
10
*/
常用場景:
-
對象.close() 臨時文件的刪除
-
- 文件.open
- defer 文件.close
- 對文件進(jìn)行操作
-
Go語言中關(guān)于異常的處理,使用panic()和recover()
-
- panic 函數(shù)用于引發(fā)恐慌,導(dǎo)致函數(shù)中斷執(zhí)行
- recover 函數(shù)用于恢復(fù)程序的執(zhí)行,recover() 語法上要求必須在 defer 中執(zhí)行
10.8、函數(shù)本質(zhì)的探究
函數(shù)本身也是有數(shù)據(jù)類型的,func()
package main
import "fmt"
func main() {
fmt.Printf("%T", fs) // func()
}
func fs() {
}
既然函數(shù)本身有數(shù)據(jù)類型,那么它就可以作為變量,可以賦值。
package main
import "fmt"
func main() {
fmt.Printf("%T\n", fs)
//定義函數(shù)類型的變量
var ff func(int, int) = fs
ff(1, 2)
//看看ff和fs是否相同
fmt.Println(ff)
fmt.Println(fs)
}
func fs(a int, b int) {
fmt.Println(a + b)
}
/*
func(int, int)
3
0x107fe40
0x107fe40
*/
函數(shù)在Go語言中是復(fù)合類型,可以看做是一種特殊的變量。
函數(shù)名 ():調(diào)用返回結(jié)果
函數(shù)名:指向函數(shù)體的內(nèi)存地址,一種特殊類型的指針變量
10.9、匿名函數(shù)推導(dǎo)
package main
import "fmt"
func main() {
// 函數(shù)正常執(zhí)行
f1()
// 匿名函數(shù)就是沒有名字的函數(shù)
f2 := f1
f2()
// 匿名函數(shù),函數(shù)體后增加一個()執(zhí)行,通常只能執(zhí)行一次
func() {
fmt.Println("我是一個匿名函數(shù)")
}()
// 將匿名函數(shù)賦值,單獨(dú)進(jìn)行調(diào)用
f3 := func() {
fmt.Println("我是一個匿名函數(shù)...")
}
f3()
// 定義帶參數(shù)的匿名函數(shù)
func(a int, b int) {
fmt.Println(a, b)
}(1, 2)
// 定義帶返回值的匿名函數(shù)
r1 := func(a int, b int) int {
return a + b
}(10, 20) //帶了()就是函數(shù)調(diào)用
fmt.Println(r1)
}
func f1() {
fmt.Println("我是f1函數(shù)")
}
/*
我是f1函數(shù)
我是f1函數(shù)
我是一個匿名函數(shù)
我是一個匿名函數(shù)...
1 2
30
*/
Go語言是支持函數(shù)式編程:
- 將匿名函數(shù)作為另外一個函數(shù)的參數(shù),回調(diào)函數(shù)
- 將匿名函數(shù)作為另外一個函數(shù)的返回值,可以形成閉包結(jié)構(gòu)
10.10、回調(diào)函數(shù)
fun1(),fun2()
將fun1函數(shù)作為fun2函數(shù)的參數(shù)
fun2函數(shù):叫做高階函數(shù),接收了一個函數(shù)作為參數(shù)文章來源:http://www.zghlxwxcb.cn/news/detail-614442.html
fun1函數(shù):叫做回調(diào)函數(shù),作為另外一個函數(shù)的參數(shù)文章來源地址http://www.zghlxwxcb.cn/news/detail-614442.html
package main
import "fmt"
func main() {
// 正常執(zhí)行
r1 := add(1, 2)
fmt.Println(r1)
// 封裝
r2 := oper(3, 4, add) //加
fmt.Println(r2)
r3 := oper(8, 4, sub) //減
fmt.Println(r3)
r4 := oper(8, 4, func(i int, i2 int) int { //乘
return i * i2
})
fmt.Println(r4)
r5 := oper(8, 4, func(i int, i2 int) int { //除
if i2 == 0 {
fmt.Println("除數(shù)不能為0")
return 0
}
return i / i2
})
fmt.Println(r5)
}
// 高階函數(shù)
func oper(a int, b int, fun func(int, int) int) int {
r := fun(a, b)
return r
}
func add(a int, b int) int {
return a + b
}
func sub(a int, b int) int {
return a - b
}
/*
3
7
4
32
2
*/
10.11、閉包的理解
package main
import "fmt"
/*
一個外層函數(shù)中,有內(nèi)層函數(shù),這個內(nèi)層函數(shù)會操作外層函數(shù)的局部變量
且該外層函數(shù)的返回值就是該內(nèi)層函數(shù)
這個內(nèi)層函數(shù)和外層函數(shù)的局部變量,統(tǒng)稱為閉包結(jié)構(gòu)
此時局部變量的聲明周期就會發(fā)生變化,正常的局部變量會隨著函數(shù)的調(diào)用而創(chuàng)建,隨著函數(shù)的結(jié)束而銷毀
但是閉包中外層函數(shù)的局部變量并不會隨著外層函數(shù)的結(jié)束而銷毀,因為內(nèi)層函數(shù)還在繼續(xù)使用
*/
func main() {
r1 := increment()
fmt.Println(r1)
fmt.Println(r1())
v2 := r1()
fmt.Println(v2)
fmt.Println(r1())
fmt.Println(r1())
fmt.Println(r1())
r2 := increment()
fmt.Println(r2())
}
func increment() func() int {
// 局部變量i
i := 0
// 定義一個匿名函數(shù),給變量自增并返回
fun := func() int {
i++
return i
}
return fun
}
/*
0x8ae800
1
2
3
4
5
1
*/
到了這里,關(guān)于Go語言學(xué)習(xí)筆記(狂神說)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!