Go語言有一些讓人影響深刻的核心特性核心特性,比如:以消息傳遞模式的并發、獨特的_符號、defer 、函數和方法、值傳遞等等,可以查看這篇文章《Go語言-讓我印象深刻的13個特性》。首先要記住一些核心特性的用法。
Goroutine就是這種協程特性的實現。Goroutine 是通過通信來共享內存,而不是共享內存來通信。通過共享內存來控制并發,會使編程變得更復雜,容易引入更多的問題。
Goroutine是由Go的運行時調度和管理。Go程序會智能地將 Goroutine 中的任務合理地分配給每個CPU,它在語言層面已經內置了調度和上下文切換的機制,不需要程序員去操作各種方法實現調度。
在Go語言中,當需要讓某個任務并發執行時,只需要把這個任務包裝成一個函數,開啟一個Goroutine去執行就可以了。如下,只需要在調用函數時,在前面加上go關鍵字。
func hello_go() { fmt.Println("hello go!!!")}func main() { go hello_go() fmt.Println("main done!!!") time.Sleep(time.Second)}
在Go語言中接口interface是一種類型。Go語言的接口比較松散,只要是實現了接口定義的方法,就是實現了這個接口,無需使用implement等關鍵字去聲明。
定義接口:
// 定義接口type Sayer interface { say()}// 定義結構體type dog struct {}type cat struct {}// 定義方法func (d dog) say() { fmt.Println("狗叫")}func (c cat) say() { fmt.Println("貓叫")}
空接口可以存儲任意類型:
// 比如定義一個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("類型斷言失敗") }}
接口特性:
// 定義接口type Sayer interface { say()}// 定義結構體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 // 聲明一個接口類型的變量 c := cat{} // 實例化cat d := dog{} // 實例化dog x = c // cat賦值給接口類型 x.say() // 打印:貓叫 x = d // dog賦值給接口類型 x.say() // 打印:狗叫}
// 定義接口type Sayer interface { say()}type Mover interface { move()}// 定義結構體type dog struct {}// 定義方法func (d dog) say() { fmt.Println("狗叫")}func (d dog) move() { fmt.Println("狗移動")}func main(t *testing.T) { var x Sayer var y Mover var d = dog{} x = d y = d x.say() y.move()}
// 定義接口type Mover interface { move()}type Sayer interface { say()}// 定義結構體type dog struct {}// 定義方法func (d *dog) say() { fmt.Println("狗叫")}func (d dog) move() { fmt.Println("狗移動")}func TestProgram(t *testing.T) { var x Sayer var y Mover //var d = dog{} var d = &dog{} x = d // x不可以接收 dog類型,因為golang 不會 將值類型 轉換 為指針類型 y = d // y可以接受 *dog類型,因為golang 會 將指針類型 轉換 為值類型 x.say() y.move()}
_是特殊標識符,用來忽略結果。
buf := make([]byte, 1024)f, _ := os.Open("/Users/***/Desktop/text.txt")
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)}
關鍵字 defer 用于注冊延遲調用。這些調用直到 return 前才被執。可以用來做資源清理,常用來關閉資源。defer 是先進后出。
func main() { arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} for _, v := range arr { defer fmt.Println("循環:", v) } fmt.Println("主流程跑完") time.Sleep(time.Second * 3) // 等待3秒后,執行defer,輸出時先輸出10,最后輸出1,因為是先進后出}
bool // 布爾int, int8, int16, int32, int64 // 整數uint, uint8, uint16, uint32, uint64 // 0 和正整數float32, float64 //浮點數string // 字符串complex64, complex128 // 數學里的復數array // 固定長度的數組struct // 結構體string // 字符串slice // 序列數組map // 映射chan // 管道interface // 接口 或 任意類型func // 函數
append // 追加元素到數組copy // 用于復制和連接slice,返回復制的數目len // 求長度,比如string、array、slice、map、channelcap // capacity是容量的意思,用于返回某個類型的最大容量(只能用于切片和 map)delete // 從map中刪除key對應的valuepanic // 拋出異常(panic和recover:用來做錯誤處理)recover // 接受異常make // 分配內存,返回Type本身(只能應用于slice, map, channel)new // 分配內存,主要用來分配值類型,比如int、struct。返回指向Type的指針close // 關閉channel
// 申明變量var name string// 申明常量const pi = 3.1415const e = 2.7182// 或const ( pi = 3.1415 e = 2.7182 )// 申明并且初始化n := 10
數組的長度固定:
var arr1 = [5]int{1, 2, 3, 4, 5}// 或arr2 := [...]struct { name string age int8}{ {"yangling", 1}, {"baily", 2},}
切片的長度不固定:
// 1.聲明切片var s1 []ints2 := []int{}var s3 = make([]int, 0)// 向切片中添加元素s1 = append(s1, 2, 3, 4)// 從切片中按照索引獲取切片s1[low:high]// 循環for index, element := range s1 { fmt.Println("索引:", index, ",元素:", element)}
scoreMap := make(map[string]int)scoreMap["張三"] = 90scoreMap["李四"] = 100userInfo := map[string]string{ "username": "baily", "password": "111111",}// 如果key存在ok 為true,v為對應的值;// 如果key不存在ok 為false,v為值類型的零值v, ok := scoreMap["李四"]if ok { fmt.Println(v)} else { fmt.Println("查無此人")}// 循環for k, v := range scoreMap { fmt.Println(k, v)}//將王五從map中刪除delete(scoreMap, "王五")
不同的使用方式,可能返回指針,也可能返回值。
// 定義結構體type Student struct { name string age int}func main() { // 使用結構體 // 方式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會隨機執行一個可運行的case。如果沒有case可運行,它將阻塞,直到有case可運行。
func main() { var c1 = make(chan int) go func() { time.Sleep(time.Second * 10) c1 <- 1 }() // 此處會一直等到10S到期,通道里有值才會繼續往下走。 // 如果增加了 time.After(time.Second * 3) ,則最多3秒則結束 // 如果這2個case都不行,會走default,也可以不設置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("無數據") }}
// 正常函數func test(x int, y int, s string) (int, string) { n := x + y return n, fmt.Sprintf(s, n)}// 匿名函數func main() { getSqrt := func(a float64) float64 { return math.Sqrt(a) } fmt.Println(getSqrt(4))}
在Go語言中,閉包是一種函數值,它引用了其函數體外部的變量。閉包允許函數訪問并處理其外部范圍內的變量,即使函數已經返回了,這些外部變量也會被保留在閉包內。
所以說,一個閉包由兩部分組成:函數體 和 與其相關的引用外部變量的環境。
當一個函數被定義在另一個函數內部時,并且引用了外部函數的變量,就會創建一個閉包。這個閉包函數可以隨時訪問和修改外部函數中的變量,即使外部函數已經執行完畢。
func main() { // 外部函數定義并返回內部函數 add := adder() // 通過閉包調用內部函數,increment是閉包函數 fmt.Println(add(1)) // 輸出:1 fmt.Println(add(2)) // 輸出:3 fmt.Println(add(3)) // 輸出:6}// 外部函數,返回一個閉包函數func adder() func(int) int { sum := 0 // 外部函數中的變量 // 閉包函數 return func(x int) int { sum += x // 閉包函數使用了外部函數中的變量 return sum }}
type error interface { //只要實現了Error()函數,返回值為string的都實現了err接口 Error() string}
使用 panic 拋出錯誤,然后在defer中通過recover捕獲異常。
func main() { testPanic()}func testPanic() { defer func() { if err := recover(); err != nil { fmt.Println(err.(string)) } }() panic("拋出異常")}
// 隱式地返回2個值func getCircleArea(radius float32) (area float32, err error) { if radius < 0 { // 構建個異常對象 err = errors.New("半徑不能為負") return } area = 3.14 * radius * radius return}func main() { area, err := getCircleArea(-5) if err != nil { fmt.Println(err) } else { fmt.Println(area) }}
可以使用匿名字段:
type Person struct { name string age int}type Student struct { Person id int addr string}func main() { s1 := Student{ Person{"baily", 20}, 1, "南京市雨花臺區南京南站", } fmt.Println(s1)}
如果對象內部嵌套的對象有同名字段的情況,只取對象自己的字段:
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 }
一個方法就是一個包含了接受者的函數,接受者可以是 類型或者結構體 的值或者指針。
type Test struct{}// 多參數、多返回值func (t Test) method1(x, y int) (z int, err error) { return}// 多參數、多返回值func (t *Test) method2(x, y int) (z int, err error) { return}
當方法作用于值接收者時,Go語言會在代碼運行時將接收者的值復制一份。在值接收者的方法中可以獲取接收者的成員值,但修改操作只是針對復制出來的副本,無法修改接收者本身。
而指針接受者,在修改成員時,會修改接受者本身。
// SetAge 設置p的年齡// 使用指針接收者func (p *Person) SetAge(newAge int) { p.age = newAge}// SetAge2 設置p的年齡// 使用值接收者func (p Person) SetAge2(newAge int) { p.age = newAge}func main() { p := new(Person) p.age = 11 p.SetAge(22) // 對象p的age會被改變 fmt.Println(p.age) p.SetAge2(33) // 對象p的age不會被改變 fmt.Println(p.age)}
什么時候應該使用指針接受者?
TCP編程:
// 處理函數func process(conn net.Conn) { defer conn.Close() // 關閉連接 for { reader := bufio.NewReader(conn) var buf [128]byte n, err := reader.Read(buf[:]) // 讀取數據 if err != nil { fmt.Println("讀取客戶端數據失敗:", err) break } recvStr := string(buf[:n]) fmt.Println("收到client端發來的數據:", recvStr) conn.Write([]byte("回復客戶端:" + recvStr)) // 發送數據 }}func main() { listen, err := net.Listen("tcp", "127.0.0.1:9587") if err != nil { fmt.Println("啟動監聽異常:", err) return } for { conn, err := listen.Accept() // 建立連接 if err != nil { fmt.Println("沒有連接:", err) continue } go process(conn) // 啟動一個goroutine處理連接 }}
var wg sync.WaitGroupfunc hello_wg(i int) { defer wg.Done() // goroutine結束就登記-1 fmt.Println("hello_wg!", i)}func main() { for i := 0; i < 10; i++ { wg.Add(1) // 啟動一個goroutine就登記+1 go hello_wg(i) time.Sleep(time.Second) } wg.Wait() // 等待所有登記的goroutine都結束}
Go語言的并發模型是CSP(Communicating Sequential Processes),通過通信共享內存,而不是通過共享內存而實現通信。
func recv(c chan int) { ret := <-c fmt.Println("接收成功", ret)}func main() { c := make(chan int) go recv(c) // 啟用goroutine從通道接收值 c <- 10 fmt.Println("發送成功") }
func main() { var c1 = make(chan int) go func() { time.Sleep(time.Second * 10) c1 <- 1 }() // 此處會一直等到10S到期,通道里有值才會繼續往下走。 // 如果增加了 time.After(time.Second * 3) ,則最多3秒則結束 // 如果這2個case都不行,會走default,也可以不設置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("無數據") }}
多個go協程操作同一個資源時,會發生并發問題,需要加鎖解決。有互斥鎖,還有讀寫鎖。
func add() { for i := 0; i < 5000; i++ { // 如果不加鎖,此處會有并發問題 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結尾,方法以Test開頭,方法入參t *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("已經找到") } else { panic("沒找到") }}
本文鏈接:http://www.www897cc.com/showinfo-26-55119-0.htmlGo語言的常用基礎
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: Vue 新一代開發者工具正式開源!
下一篇: 通過Uri加載raw目錄下的文件