Go語言的常用基礎(chǔ)
一、核心特性
Go語言有一些讓人影響深刻的核心特性核心特性,比如:以消息傳遞模式的并發(fā)、獨(dú)特的_符號(hào)、defer 、函數(shù)和方法、值傳遞等等,可以查看這篇文章《Go語言-讓我印象深刻的13個(gè)特性》。首先要記住一些核心特性的用法。
1.Goroutine
- 協(xié)程:獨(dú)立的棧空間,共享堆空間,比線程更輕量。
- 線程:一個(gè)線程上可以跑多個(gè)協(xié)程。
- Go語言獨(dú)有的協(xié)程,讓程序員非常方便的使用并發(fā)編程,從而保留更多的心智去思考業(yè)務(wù)和創(chuàng)新。筆者認(rèn)為這一點(diǎn)是Go語言最大的特性。
Goroutine就是這種協(xié)程特性的實(shí)現(xiàn)。Goroutine 是通過通信來共享內(nèi)存,而不是共享內(nèi)存來通信。通過共享內(nèi)存來控制并發(fā),會(huì)使編程變得更復(fù)雜,容易引入更多的問題。
Goroutine是由Go的運(yùn)行時(shí)調(diào)度和管理。Go程序會(huì)智能地將 Goroutine 中的任務(wù)合理地分配給每個(gè)CPU,它在語言層面已經(jīng)內(nèi)置了調(diào)度和上下文切換的機(jī)制,不需要程序員去操作各種方法實(shí)現(xiàn)調(diào)度。
在Go語言中,當(dāng)需要讓某個(gè)任務(wù)并發(fā)執(zhí)行時(shí),只需要把這個(gè)任務(wù)包裝成一個(gè)函數(shù),開啟一個(gè)Goroutine去執(zhí)行就可以了。如下,只需要在調(diào)用函數(shù)時(shí),在前面加上go關(guān)鍵字。
func hello_go() {
fmt.Println("hello go!!!")
}
func main() {
go hello_go()
fmt.Println("main done!!!")
time.Sleep(time.Second)
}
2.接口
在Go語言中接口interface是一種類型。Go語言的接口比較松散,只要是實(shí)現(xiàn)了接口定義的方法,就是實(shí)現(xiàn)了這個(gè)接口,無需使用implement等關(guān)鍵字去聲明。
定義接口:
// 定義接口
type Sayer interface {
say()
}
// 定義結(jié)構(gòu)體
type dog struct {
}
type cat struct {
}
// 定義方法
func (d dog) say() {
fmt.Println("狗叫")
}
func (c cat) say() {
fmt.Println("貓叫")
}
空接口可以存儲(chǔ)任意類型:
// 比如定義一個(gè)map類型的對象
var obj = map[string]interface{}
使用類型斷言判斷空接口中的值:
// x:表示類型為interface{}的變量
// T:表示斷言x可能是的類型。
x.(T)
func main() {
var x interface{}
x = 123
//v, ok := x.(int)
v, ok := x.(string)
if ok {
fmt.Println(v)
} else {
fmt.Println("類型斷言失敗")
}
}
接口特性:
- 接口類型變量能夠存儲(chǔ)所有實(shí)現(xiàn)了該接口的實(shí)例。 如下,Sayer類型的變量能夠存儲(chǔ)dog和cat類型的變量。
// 定義接口
type Sayer interface {
say()
}
// 定義結(jié)構(gòu)體
type dog struct {
}
type cat struct {
}
// 定義方法
func (d dog) say() {
fmt.Println("狗叫")
}
func (c cat) say() {
fmt.Println("貓叫")
}
func main(t *testing.T) {
var x Sayer // 聲明一個(gè)接口類型的變量
c := cat{} // 實(shí)例化cat
d := dog{} // 實(shí)例化dog
x = c // cat賦值給接口類型
x.say() // 打?。贺埥? x = d // dog賦值給接口類型
x.say() // 打印:狗叫
}
- 一個(gè)類型可以同時(shí)實(shí)現(xiàn)多個(gè)接口,接口間彼此獨(dú)立。
// 定義接口
type Sayer interface {
say()
}
type Mover interface {
move()
}
// 定義結(jié)構(gòu)體
type dog struct {
}
// 定義方法
func (d dog) say() {
fmt.Println("狗叫")
}
func (d dog) move() {
fmt.Println("狗移動(dòng)")
}
func main(t *testing.T) {
var x Sayer
var y Mover
var d = dog{}
x = d
y = d
x.say()
y.move()
}
- 使用值接收者實(shí)現(xiàn)接口 和 使用指針接收者實(shí)現(xiàn)接口 有什么區(qū)別?值接受者實(shí)現(xiàn)時(shí) 可以用 指針類型賦值過去,但 指針接受者實(shí)現(xiàn)時(shí) 無法用 值類型賦值過去。
// 定義接口
type Mover interface {
move()
}
type Sayer interface {
say()
}
// 定義結(jié)構(gòu)體
type dog struct {
}
// 定義方法
func (d *dog) say() {
fmt.Println("狗叫")
}
func (d dog) move() {
fmt.Println("狗移動(dòng)")
}
func TestProgram(t *testing.T) {
var x Sayer
var y Mover
//var d = dog{}
var d = &dog{}
x = d // x不可以接收 dog類型,因?yàn)間olang 不會(huì) 將值類型 轉(zhuǎn)換 為指針類型
y = d // y可以接受 *dog類型,因?yàn)間olang 會(huì) 將指針類型 轉(zhuǎn)換 為值類型
x.say()
y.move()
}
3.下劃線
_是特殊標(biāo)識(shí)符,用來忽略結(jié)果。
buf := make([]byte, 1024)
f, _ := os.Open("/Users/***/Desktop/text.txt")
4.Go語言中的指針
- &:用于獲取變量的地址,其實(shí)就是所謂的指針類型**(地址類型)
- :用于獲取指針?biāo)赶虻闹?
func main() {
a := 10
fmt.Printf("type of a: %T\n", a)
b := &a // 取變量a的地址,將指針保存到b中
fmt.Printf("type of b: %T\n", b)
c := *b // 取出 指針b 所指向的值
fmt.Printf("type of c: %T\n", c)
fmt.Printf("value of c: %v\n", c)
}
5.new和make的區(qū)別
- 二者都是用來做內(nèi)存分配的。
- make只用于slice、map、channel的初始化,返回的還是這三個(gè)引用類型本身。這里的引用有別于指針,他是對 slice、map、channel 值的間接訪問,并不是一個(gè)指向 slice、map、channel 的指針。
- new用于類型的內(nèi)存分配,并且內(nèi)存對應(yīng)的值為類型零值,返回的是指向類型的指針。指針是一個(gè)變量,存儲(chǔ)了值的內(nèi)存地址。
6.defer延遲調(diào)用
關(guān)鍵字 defer 用于注冊延遲調(diào)用。這些調(diào)用直到 return 前才被執(zhí)??梢杂脕碜鲑Y源清理,常用來關(guān)閉資源。defer 是先進(jìn)后出。
func main() {
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for _, v := range arr {
defer fmt.Println("循環(huán):", v)
}
fmt.Println("主流程跑完")
time.Sleep(time.Second * 3)
// 等待3秒后,執(zhí)行defer,輸出時(shí)先輸出10,最后輸出1,因?yàn)槭窍冗M(jìn)后出
}
二、常用類型和內(nèi)置函數(shù)
1.常用類型
bool // 布爾
int, int8, int16, int32, int64 // 整數(shù)
uint, uint8, uint16, uint32, uint64 // 0 和正整數(shù)
float32, float64 //浮點(diǎn)數(shù)
string // 字符串
complex64, complex128 // 數(shù)學(xué)里的復(fù)數(shù)
array // 固定長度的數(shù)組
struct // 結(jié)構(gòu)體
string // 字符串
slice // 序列數(shù)組
map // 映射
chan // 管道
interface // 接口 或 任意類型
func // 函數(shù)
2.常用內(nèi)置函數(shù)
append // 追加元素到數(shù)組
copy // 用于復(fù)制和連接slice,返回復(fù)制的數(shù)目
len // 求長度,比如string、array、slice、map、channel
cap // capacity是容量的意思,用于返回某個(gè)類型的最大容量(只能用于切片和 map)
delete // 從map中刪除key對應(yīng)的value
panic // 拋出異常(panic和recover:用來做錯(cuò)誤處理)
recover // 接受異常
make // 分配內(nèi)存,返回Type本身(只能應(yīng)用于slice, map, channel)
new // 分配內(nèi)存,主要用來分配值類型,比如int、struct。返回指向Type的指針
close // 關(guān)閉channel
三、變量、常量
// 申明變量
var name string
// 申明常量
const pi = 3.1415
const e = 2.7182
// 或
const (
pi = 3.1415
e = 2.7182
)
// 申明并且初始化
n := 10
四、數(shù)據(jù)結(jié)構(gòu)
1.數(shù)組
數(shù)組的長度固定:
var arr1 = [5]int{1, 2, 3, 4, 5}
// 或
arr2 := [...]struct {
name string
age int8
}{
{"yangling", 1},
{"baily", 2},
}
2.切片
切片的長度不固定:
// 1.聲明切片
var s1 []int
s2 := []int{}
var s3 = make([]int, 0)
// 向切片中添加元素
s1 = append(s1, 2, 3, 4)
// 從切片中按照索引獲取切片
s1[low:high]
// 循環(huán)
for index, element := range s1 {
fmt.Println("索引:", index, ",元素:", element)
}
3.Map
scoreMap := make(map[string]int)
scoreMap["張三"] = 90
scoreMap["李四"] = 100
userInfo := map[string]string{
"username": "baily",
"password": "111111",
}
// 如果key存在ok 為true,v為對應(yīng)的值;
// 如果key不存在ok 為false,v為值類型的零值
v, ok := scoreMap["李四"]
if ok {
fmt.Println(v)
} else {
fmt.Println("查無此人")
}
// 循環(huán)
for k, v := range scoreMap {
fmt.Println(k, v)
}
//將王五從map中刪除
delete(scoreMap, "王五")
4.結(jié)構(gòu)體
不同的使用方式,可能返回指針,也可能返回值。
// 定義結(jié)構(gòu)體
type Student struct {
name string
age int
}
func main() {
// 使用結(jié)構(gòu)體
// 方式1,返回的是值
var stu1 Student
stu1.name = "baily"
stu1.age = 1
fmt.Println("baily1:", stu1)
// 方式2,返回的是值
var stu2 = Student{
name: "baily",
age: 1,
}
fmt.Println("baily2:", stu2)
// 方式3,返回的是指針
stu3 := &Student{
name: "baily",
age: 1,
}
fmt.Println("baily3指針:", stu3)
fmt.Println("baily3值:", *stu3)
// 方式4,返回的是指針
var stu4 = new(Student)
stu4.name = "baily"
stu4.age = 1
fmt.Println("baily4指針:", stu4)
fmt.Println("baily4值:", *stu4)
}
五、流程控制
流程控制包括:if、switch、for、range、select、goto、continue、break。主要記下select,其他的跟別的語言類似。主要用于等待資源、阻塞等待等等。
select 語句類似于 switch 語句,但是select會(huì)隨機(jī)執(zhí)行一個(gè)可運(yùn)行的case。如果沒有case可運(yùn)行,它將阻塞,直到有case可運(yùn)行。
func main() {
var c1 = make(chan int)
go func() {
time.Sleep(time.Second * 10)
c1 <- 1
}()
// 此處會(huì)一直等到10S到期,通道里有值才會(huì)繼續(xù)往下走。
// 如果增加了 time.After(time.Second * 3) ,則最多3秒則結(jié)束
// 如果這2個(gè)case都不行,會(huì)走default,也可以不設(shè)置default
select {
case i, ok := <-c1:
if ok {
fmt.Println("取值", i)
}
case <-time.After(time.Second * 3):
fmt.Println("request time out")
default:
fmt.Println("無數(shù)據(jù)")
}
}
六、函數(shù)和閉包
1.函數(shù)
// 正常函數(shù)
func test(x int, y int, s string) (int, string) {
n := x + y
return n, fmt.Sprintf(s, n)
}
// 匿名函數(shù)
func main() {
getSqrt := func(a float64) float64 {
return math.Sqrt(a)
}
fmt.Println(getSqrt(4))
}
2.閉包
在Go語言中,閉包是一種函數(shù)值,它引用了其函數(shù)體外部的變量。閉包允許函數(shù)訪問并處理其外部范圍內(nèi)的變量,即使函數(shù)已經(jīng)返回了,這些外部變量也會(huì)被保留在閉包內(nèi)。
所以說,一個(gè)閉包由兩部分組成:函數(shù)體 和 與其相關(guān)的引用外部變量的環(huán)境。
當(dāng)一個(gè)函數(shù)被定義在另一個(gè)函數(shù)內(nèi)部時(shí),并且引用了外部函數(shù)的變量,就會(huì)創(chuàng)建一個(gè)閉包。這個(gè)閉包函數(shù)可以隨時(shí)訪問和修改外部函數(shù)中的變量,即使外部函數(shù)已經(jīng)執(zhí)行完畢。
func main() {
// 外部函數(shù)定義并返回內(nèi)部函數(shù)
add := adder()
// 通過閉包調(diào)用內(nèi)部函數(shù),increment是閉包函數(shù)
fmt.Println(add(1)) // 輸出:1
fmt.Println(add(2)) // 輸出:3
fmt.Println(add(3)) // 輸出:6
}
// 外部函數(shù),返回一個(gè)閉包函數(shù)
func adder() func(int) int {
sum := 0 // 外部函數(shù)中的變量
// 閉包函數(shù)
return func(x int) int {
sum += x // 閉包函數(shù)使用了外部函數(shù)中的變量
return sum
}
}
七、異常
1.內(nèi)置接口error
type error interface { //只要實(shí)現(xiàn)了Error()函數(shù),返回值為string的都實(shí)現(xiàn)了err接口
Error() string
}
2.異常處理
使用 panic 拋出錯(cuò)誤,然后在defer中通過recover捕獲異常。
func main() {
testPanic()
}
func testPanic() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err.(string))
}
}()
panic("拋出異常")
}
3.返回異常
// 隱式地返回2個(gè)值
func getCircleArea(radius float32) (area float32, err error) {
if radius < 0 {
// 構(gòu)建個(gè)異常對象
err = errors.New("半徑不能為負(fù)")
return
}
area = 3.14 * radius * radius
return
}
func main() {
area, err := getCircleArea(-5)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(area)
}
}
八、面向?qū)ο蠛头椒?/h3>11.面向?qū)ο?/h4>
可以使用匿名字段:
type Person struct {
name string
age int
}
type Student struct {
Person
id int
addr string
}
func main() {
s1 := Student{
Person{"baily", 20},
1,
"南京市雨花臺(tái)區(qū)南京南站",
}
fmt.Println(s1)
}
如果對象內(nèi)部嵌套的對象有同名字段的情況,只取對象自己的字段:
type Person struct {
name string
age int
}
type Student struct {
Person
id int
addr string
name string
}
func main() {
var s Student
s.name = "baily"
s.Person.name = "baily-parent"
fmt.Println(s) // 打印出 baily
}
2.方法
一個(gè)方法就是一個(gè)包含了接受者的函數(shù),接受者可以是 類型或者結(jié)構(gòu)體 的值或者指針。
type Test struct{}
// 多參數(shù)、多返回值
func (t Test) method1(x, y int) (z int, err error) {
return
}
// 多參數(shù)、多返回值
func (t *Test) method2(x, y int) (z int, err error) {
return
}
3.指針接受者 和 值接受者的區(qū)別
當(dāng)方法作用于值接收者時(shí),Go語言會(huì)在代碼運(yùn)行時(shí)將接收者的值復(fù)制一份。在值接收者的方法中可以獲取接收者的成員值,但修改操作只是針對復(fù)制出來的副本,無法修改接收者本身。
而指針接受者,在修改成員時(shí),會(huì)修改接受者本身。
// SetAge 設(shè)置p的年齡
// 使用指針接收者
func (p *Person) SetAge(newAge int) {
p.age = newAge
}
// SetAge2 設(shè)置p的年齡
// 使用值接收者
func (p Person) SetAge2(newAge int) {
p.age = newAge
}
func main() {
p := new(Person)
p.age = 11
p.SetAge(22) // 對象p的age會(huì)被改變
fmt.Println(p.age)
p.SetAge2(33) // 對象p的age不會(huì)被改變
fmt.Println(p.age)
}
什么時(shí)候應(yīng)該使用指針接受者?
- 需要修改接收者中的值
- 接收者是拷貝代價(jià)比較大的大對象
- 保證一致性,如果有某個(gè)方法使用了指針接收者,那么其他的方法也應(yīng)該使用指針接收者。
九、網(wǎng)絡(luò)編程
TCP編程:
// 處理函數(shù)
func process(conn net.Conn) {
defer conn.Close() // 關(guān)閉連接
for {
reader := bufio.NewReader(conn)
var buf [128]byte
n, err := reader.Read(buf[:]) // 讀取數(shù)據(jù)
if err != nil {
fmt.Println("讀取客戶端數(shù)據(jù)失?。?, err)
break
}
recvStr := string(buf[:n])
fmt.Println("收到client端發(fā)來的數(shù)據(jù):", recvStr)
conn.Write([]byte("回復(fù)客戶端:" + recvStr)) // 發(fā)送數(shù)據(jù)
}
}
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:9587")
if err != nil {
fmt.Println("啟動(dòng)監(jiān)聽異常:", err)
return
}
for {
conn, err := listen.Accept() // 建立連接
if err != nil {
fmt.Println("沒有連接:", err)
continue
}
go process(conn) // 啟動(dòng)一個(gè)goroutine處理連接
}
}
十、并發(fā)編程
1.使用sync.WaitGroup
var wg sync.WaitGroup
func hello_wg(i int) {
defer wg.Done() // goroutine結(jié)束就登記-1
fmt.Println("hello_wg!", i)
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1) // 啟動(dòng)一個(gè)goroutine就登記+1
go hello_wg(i)
time.Sleep(time.Second)
}
wg.Wait() // 等待所有登記的goroutine都結(jié)束
}
2.使用channel解決并發(fā)
Go語言的并發(fā)模型是CSP(Communicating Sequential Processes),通過通信共享內(nèi)存,而不是通過共享內(nèi)存而實(shí)現(xiàn)通信。
func recv(c chan int) {
ret := <-c
fmt.Println("接收成功", ret)
}
func main() {
c := make(chan int)
go recv(c) // 啟用goroutine從通道接收值
c <- 10
fmt.Println("發(fā)送成功")
}
3.select
func main() {
var c1 = make(chan int)
go func() {
time.Sleep(time.Second * 10)
c1 <- 1
}()
// 此處會(huì)一直等到10S到期,通道里有值才會(huì)繼續(xù)往下走。
// 如果增加了 time.After(time.Second * 3) ,則最多3秒則結(jié)束
// 如果這2個(gè)case都不行,會(huì)走default,也可以不設(shè)置default
select {
case i, ok := <-c1:
if ok {
fmt.Println("取值", i)
}
case <-time.After(time.Second * 3):
fmt.Println("request time out")
default:
fmt.Println("無數(shù)據(jù)")
}
}
4.互斥鎖
多個(gè)go協(xié)程操作同一個(gè)資源時(shí),會(huì)發(fā)生并發(fā)問題,需要加鎖解決。有互斥鎖,還有讀寫鎖。
func add() {
for i := 0; i < 5000; i++ {
// 如果不加鎖,此處會(huì)有并發(fā)問題
lock.Lock() // 加鎖
x = x + 1
lock.Unlock() // 解鎖
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
十一、單元測試
文件以_test.go結(jié)尾,方法以Test開頭,方法入?yún) *testing.T。
func TestProgram(t *testing.T) {
split := strings.Split("a,b,c", ",")
defer func() {
if err := recover(); err != nil {
fmt.Println("異常:", err)
}
}()
findElement(split, "a")
}
// 查找元素
func findElement(split []string, target string) {
flag := false
for _, e := range split {
if e == target {
flag = true
break
}
}
if flag {
fmt.Println("已經(jīng)找到")
} else {
panic("沒找到")
}
}
十二、常用命令
- go env:用于打印Go語言的環(huán)境信息。
- go build:用于編譯Go程序。例如,go build filename.go 會(huì)將 filename.go 編譯成可執(zhí)行文件。
- go run:用于直接運(yùn)行Go程序。例如,go run filename.go 會(huì)編譯并運(yùn)行 filename.go 文件中的程序。
- go test:用于運(yùn)行測試文件或者測試包。例如,go test 會(huì)運(yùn)行當(dāng)前目錄下所有的測試文件。
- go get:用于下載并安裝包。例如,go get github.com/example/package 會(huì)下載 github.com/example/package 包并將其安裝在 $GOPATH/src 下。
- go mod:用于管理依賴和模塊。例如,go mod init 用于初始化一個(gè)新的模塊,并生成 go.mod 文件。
- go vet:用于靜態(tài)檢查Go代碼中的錯(cuò)誤。例如,go vet filename.go 會(huì)檢查 filename.go 文件中的錯(cuò)誤。
- go install 命令用于編譯并安裝Go程序,它會(huì)編譯指定的包或源文件,并將生成的可執(zhí)行文件安裝到 $GOPATH/bin 目錄下。