tip: 無論是變量、方法還是struct的訪問權限控制都是通過命名控制的,命名的首字母是大寫就相當于java中的public,小寫的話就是private,(private只有本包可以訪問)
1 go的變量聲明
普通變量
特點: 變量類型后置
方式1:
var num int = 1
方式2:
num := 1 // := 會幫我們自動匹配數據類型(最常用)
方式3:
var num = 1
常量
還是通過const關鍵字聲明(和c是一樣的)const (//聲明常數列表ZHANGSAN = 1LISI = 2....
)并且我們可以使用iota關鍵字進行簡寫
const (ZHANGSAN = iota + 1 //iota默認是0,每次被使用后都會+1LiSI //等同于LISI = iota + 1, 但是公式沒有發生變化所以可以省略
)常量使用:
fmt.println("ZHANGSNA = ", ZHANGSAN)
2 go的函數/方法聲明
特點: func (綁定struct名 類型) 函數名(形參名 形參類型, …) 返回類型 {函數體}
func print(s string) string {....
}go當中的方法是可以有多個返回值的:
func play() (string, string) {return zhangsan, lisi
}//多返回值接收
name1, name2 := play()
3.對象
聲明
特點: go語言的對象是一種組合概念,由結構體+一系列方法構成
type關鍵字:定義自定義類型type person struct {name stringage int
}func (p *person) setName(name string) {p.name = name
}上述代碼就等同于java中的如下代碼:
private class person {private String name;private int age;private void setName(String name) {this.name = name;}
}"(p *person)"代表這個方法綁定person這個struct為什么要使用"*person"?
因為go中的對象(struct)是值類型(也就是java中的基本類型),因此不傳入指針的話修改也不生效(需要一點c的指針基礎)
類繼承
type human struct {name stringage int
}type man struct {human // 就代表man繼承了human對象sex int
}//man的創建
man1 := man{human{"zhangsan", 18}, 1}
4 init方法
特點: 導包時執行的初始化方法
package p1import "fmt"func init() {fmt.println("p1, init")
}如果我們引入這個包,那么就會執行p1的init方法
5 指針
go中的指針和c是一樣的
6 defer關鍵字
特點: 在defer所在的代碼塊執行完后執行,類似java的finally或c++的析構函數
使用場景: 文件關閉、連接關閉、資源釋放等
func print() {defer fmt.println("2..")fmt.println("1..")
}
輸出:先1后2//defer可以有多個,通過棧結構存儲
func print() {defer fmt.println("2..") //2進棧defer fmt.println("3..") //3進棧fmt.println("1..")
}
輸出:先1后3再2tip:defer與return的執行順序,因為defer是在所在方法的生命周期結束后才會執行
func deferCall() {fmt.println("defer...")
}
func returnCall() {fmt.println("return...")
}
func deferAndReturn() {defer deferCall()return returnCall()
}
func main() {deferAndReturn()
}
輸出: return...defer...
7 數組與動態數組
聲明
固定數組(常用)
arr := [3]int{1,2,3} 動態數組(也叫切片slice)
arr := []int{1, 2, 3} 或 //雖然我們只添加了三個初始值,但是我們還可以往后繼續添加
arr1 := make([]int, 3) //開辟大小為3的數組,但是沒有設置初始值
arr2 := make([]int, 3, 5) //開辟一個大小為3,容量為5的切片(arr[3]和arr[4]實際還不可以訪問)
容量的概念就是我們聲明了這個空間但是我沒創建(也就是我告訴你這有但是現在還沒有),大小就是現在實際有的切片擴容:當容量不夠時,會自動擴容為當前容量的2倍,沒指定容量的話就是切片大小對動態數組的操作與python中的切片操作類似。tip:
go當中的固定數組是一種直接類型,不像是java中的引用類型,所以[3]int 和 [4]int不是同一類型的,所以在使用固定數組作為方法形參的時候如果想要改變內容應該傳入指針。
而動態數組是引用類型,type(arr)得到的是[]int.
遍歷
arr := []int{1,2,3,4}方式1:
for index, value := range arr {fmt.println("下標是:", index)fmt.println("內容是:", value)
}方式2:
for i := 1, i < len(arr), i++ {fmt.println(arr[i])
}
8 map
聲明
var myMap map[String]string //[]內是key類型,外是value類型//開辟空間
myMap := make(map[string]string, 10)//有初始值創建
myMap := map[string]string{"one": "zhangsan""two": "lisi"
}
使用
myMap := make(map[string]string)
//增
myMap["one"] = "zhangsan"
myMap["two"] = "lisi"//刪
delete(myMap, "one")//改
myMap["two"] = "wangwu"//查
value := myMap["two"]// 遍歷
for key, value := range myMap {}
9 多態
特點: 實現接口
type animal interface {show()play()
}
// cat實現了animal接口, 是通過實現接口方法
type cat struct {name string
}
func (c *cat) show() {fmt.println("這是一只貓")
}
func (c *cat) play() {fmt.println("貓在玩耍")
}
10.interface{}
特點: 通用數據類型,幾乎所有類型都實現了interface{}
func call(this interface{}) {fmt.println("this is ", this)// 類型斷言value, res := this.(string) // 判斷this是不是是tring類型,會返回兩個數據一個是this本身一個是判斷結果(bool)
}type human struct {name string
}func main() {human1 := human{"zhangsan"}//call 方法可以接收任何類型call(human1)call(1)call("lisi")
}
11 pair
任何變量都會有一個piar對變量描述,這個piar結構如下:<type, value>,type代表變量的類型,value代表變量存儲的值。
type又分為static type(基本類型,如:int、char這些)和concrete type(引用類型,如:切片)
12 反射
go提供了reflect包實現反射功能,其中有兩個核心方法TypeOf()和ValueOf(), 分別用來獲取參數對象的類型與值。
反射基本使用
package mainimport ("fmt""reflect"
)type User struct {Name stringAge int
}func (this User) Call() { //這里寫*User會識別不到方法,原因在后面fmt.Println("User is call...")fmt.Printf("%v\n", this)
}func main() {user := User{"zhangsan", 18}DoFiledAndMethod(user)
}func DoFiledAndMethod(input interface{}) {// 獲取input信息t := reflect.TypeOf(input)fmt.Println("type:", t.Name())v := reflect.ValueOf(input)fmt.Println("value:", v)// 遍歷結構體的所有字段for i := 0; i < t.NumField(); i++ {field := t.Field(i)value := v.Field(i).Interface()fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)}methodNum := t.NumMethod()fmt.Printf("the User has %d methods\n", methodNum)// 通過type調用方法for i := 0; i < t.NumMethod(); i++ {method := t.Method(i)fmt.Printf("%s: %v\n", method.Name, method.Type)}
}
Go 的方法集規則
Go 的方法集規則如下:
- 值類型(
T
) 的方法集包含所有接收者為T
的方法。 - 指針類型(
*T
) 的方法集包含所有接收者為T
和*T
的方法。
因此:
User
值類型的方法集:只有接收者為User
的方法。*User
指針類型的方法集:接收者為User
和*User
的方法。
13 標簽
在 Go 語言中,標簽(Tags) 是結構體字段的元數據,通常用于編碼/解碼(如 JSON、XML)、ORM 映射(如 GORM)、驗證(如 validator
)等場景。標簽以反引號 `` 包裹,格式為 key:“value”,多個標簽用空格分隔。
package mainimport ("fmt""encoding/json"
)type User struct {Name string `json:"name"`Age int `json:"age"`
}func main() {user := User{"zhangsan", 18}// 結構體 -> jsonjsonData, _ := json.Marshal(user)fmt.Println(string(jsonData))//json -> 結構體myUser := User{}err := json.Unmarshal(jsonData, &myUser)if(err != nil) {fmt.Println("err: ", err)return}fmt.Printf("%v\n", myUser)
}輸出:
{"name":"zhangsan","age":18}
{zhangsan 18}
14 協程
協程(Goroutine) 是一種輕量級的并發執行單元,由 Go 運行時(runtime)管理,用于實現高并發編程。
go語言對于協程的設計是將原先線程中用戶態部分分離出來了,這樣不需要頻繁的切換,而原線程中的內核態部分成為新的線程復雜管理協程。
協程結構:
GMP模型控制:
關鍵組件:
- G (Goroutine)
- 包含執行棧、程序計數器(PC)、狀態(運行/阻塞)。
- 通過
go
關鍵字創建,輕量且可動態擴縮。
- P (Processor)
- 協程調度器,綁定到 M 上運行。
- 維護一個本地 Goroutine 隊列(優先調度本地隊列)。
- M (Machine)
- 操作系統線程,真正執行計算的單元。
- 由 P 分配任務,空閑時會被回收或休眠。
調度流程:
1. G 創建 → 放入 P 的本地隊列(或全局隊列)。//只用本地隊列滿會放入全局隊列
2. M 獲取 G → 從綁定的 P 的隊列中取 G 執行。
3. G 阻塞(如 I/O)→ 該阻塞協程從本地隊列移除,M解綁P,由M負責該阻塞協程,解綁后的P綁定一個新的線程,P可以正常工作去調度其他G。
4. G 就緒 → 被放回隊列,等待 M 執行。
Goroutine 解決了線程的哪些痛點?
(1) 高創建和切換成本
- 線程問題:每個線程需要分配較大的棧(1MB+),創建和切換涉及內核態操作,開銷大。
- Goroutine 方案:初始棧僅 2KB,由 Go 運行時在用戶態調度,切換成本極低。
(2) 并發數量受限
- 線程問題:受限于內存和內核調度,通常只能創建幾千個線程。
- Goroutine 方案:輕量級設計,單機可輕松支持百萬級并發,還有協程調度器的存在支持多對多的調度關系。
(3) 共享內存導致的競態問題
- 線程問題:多線程需通過鎖(
mutex
)同步,易引發死鎖、數據競爭。 - Goroutine 方案:通過 Channel 通信,避免顯式鎖,更安全。
(4) 阻塞導致線程浪費
- 線程問題:一個線程阻塞(如 I/O 操作)時,線程池中的線程被占用,影響整體吞吐量。
- Goroutine 方案:阻塞時,Go 運行時會自動掛起該 Goroutine,復用線程執行其他任務。
協程的使用
package mainimport ("fmt""time"
)
func print() {i := 0for {i++fmt.Printf("new Goroutine: i = %d\n", i)time.Sleep(1 * time.Second)}
}func main() {go print() // 創建協程執行print方法i := 0for {i++fmt.Printf("main Goroutine: i = %d\n", i)time.Sleep(1 * time.Second)}
}// 也可以通過匿名調用的方式
func main() {go func () {i := 0for {i++fmt.Printf("new Goroutine: i = %d\n", i)time.Sleep(1 * time.Second)}}() // 括號中填入方法的參數,我這是無參就沒填i := 0for {i++fmt.Printf("main Goroutine: i = %d\n", i)time.Sleep(1 * time.Second)}
}//執行結果
main Goroutine: i = 1
new Goroutine: i = 1
new Goroutine: i = 2
main Goroutine: i = 2
main Goroutine: i = 3
new Goroutine: i = 3
new Goroutine: i = 4
main Goroutine: i = 4
15 channel
用于協程間通信的一種方式,分為無緩沖channel和有緩沖channel
無緩沖channel:channel中同一時間只能存在一個數據
package mainimport ("fmt""time"
)// 1
func main() { // 主協程c := make(chan int)fmt.Println("main Goroutine try to read from channel")num := <-cfmt.Println(num)go func (){ //子協程c <- 1fmt.Println("already write")}()
}// 2
func main() {c := make(chan int)go func (){c <- 1fmt.Println("already write")}()fmt.Println("main Goroutine not read from channel")i := 0for {i++fmt.Println(i)time.Sleep(1 * time.Second)}
}輸出:
// 1
fmt.Println("main Goroutine try to read from channel")
1// 2
main Goroutine not read from channel
1
2
3
4
5
子協程向channel中寫入數據后會在此阻塞直到數據被取走,同樣如果主協程想要從channel中讀取數據但目前channel中沒有數據主協程也會阻塞等待。
有緩沖channel:其與阻塞隊列的功能非常相似,允許存在多個數據在channel中,如果從空channel中獲取元素或向滿channel中發送元素則會觸發阻塞。
// 聲明有緩沖channel
ch := make(chan int 3) //創建緩沖大小為3的有緩沖channel
雖然channel和阻塞隊列功能類似但他倆在實現上還是有很大的不同,感興趣的可以去了解一下。
關閉channel:
channel一旦關閉就無法向channel中發送數據,但如果緩沖區內還有數據則還可以讀取。