最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

GoLang函數(shù)與面向接口編程全面分析講解

 更新時間:2023年01月27日 11:19:12   作者:Ricky_0528  
這篇文章主要介紹了GoLang函數(shù)與面向接口編程,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧

一、函數(shù)

1. 函數(shù)的基本形式

// 函數(shù)定義:a,b是形參
func add(a int, b int) { 
	a = a + b 
}
var x, y int = 3, 6
add(x, y) // 函數(shù)調(diào)用:x,y是實參
  • 形參是函數(shù)內(nèi)部的局部變量,實參的值會拷貝給形參
  • 函數(shù)定義時的第一個的大括號不能另起一行
  • 形參可以有0個或多個,支持使用可邊長參數(shù)
  • 參數(shù)類型相同時可以只寫一次,比如add(a,b int)
  • 在函數(shù)內(nèi)部修改形參的值,實參的值不受影響
  • 如果想通過函數(shù)修改實參,就需要傳遞指針類型
func change(a, b *int) { 
    *a = *a + *b
    *b = 888
}
var x, y int = 3, 6
change(&x, &y)

slice、map、channel都是引用類型,它們作為函數(shù)參數(shù)時其實跟普通struct沒什么區(qū)別,都是對struct內(nèi)部的各個字段做一次拷貝傳到函數(shù)內(nèi)部

package main
import "fmt"
// slice作為參數(shù),實際上是把slice的arrayPointer、len、cap拷貝了一份傳進來
func sliceChange(arr []int) { 
	arr[0] = 1 // 實際是修改底層數(shù)據(jù)里的首元素
	arr = append(arr, 1) // arr的len和cap發(fā)生了變化,不會影響實參
}
func main() {
	arr := []int{8}
	sliceChange(arr)
	fmt.Println(arr[0])   // 1,數(shù)組元素發(fā)生改變
	fmt.Println(len(arr)) // 1,實際的長度沒有改變
}

關(guān)于函數(shù)返回值

  • 可以返回0個或多個參數(shù)
  • 可以在func行直接聲明要返回的變量
  • return后面的語句不會執(zhí)行
  • 無返回參數(shù)時return可以不寫
// 返回變量c已經(jīng)聲明好了,在函數(shù)中可以直接使用
func returnf(a, b int) (c int) {
    a = a + b
    c = a // 直接使用c
    return // 由于函數(shù)要求有返回值,即使給c賦過值了,也需要顯式寫return
}

不定長參數(shù)實際上是slice類型

// other為不定長參數(shù)可傳遞任意多個參數(shù),a是必須傳遞的參數(shù)
func args(a int, other ...int) int { 
    sum := a
    // 直接當作slice來使用
    for _, ele := range other {
        sum += ele
    }
    fmt.Printf("len %d cap %d\n", len(other), cap(other))
    return sum
}
args(1)
args(1,2,3,4)

append函數(shù)接收的就是不定長參數(shù)

arr = append(arr, 1, 2, 3)
arr = append(arr, 7)
arr = append(arr)
slice := append([]byte("hello "), "world"...) // ...自動把"world"轉(zhuǎn)成byte切片,等價于[]byte("world")...
slice2 := append([]rune("hello "), []rune("world")...) // 需要顯式把"world"轉(zhuǎn)成rune切片

在很多場景下string都隱式的轉(zhuǎn)換成了byte切片,而非rune切片,比如"a中"[1]獲取到的值為228而非"中"

2. 遞歸函數(shù)

最經(jīng)典的斐波那契數(shù)列的遞歸求法

func fibonacci(n int) int {
    if n == 0 || n == 1 {
        return n // 凡是遞歸,一定要有終止條件,否則會進入無限循環(huán)
    }
    return fibonacci(n-1) + fibonacci(n-2) // 遞歸調(diào)用自身
}

3. 匿名函數(shù)

函數(shù)也是一種數(shù)據(jù)類型

func functionArg1(f func(a, b int) int, b int) int { // f參數(shù)是一種函數(shù)類型
	a := 2 * b
	return f(a, b)
}
type foo func(a, b int) int // foo是一種函數(shù)類型
func functionArg2(f foo, b int) int { // type重命名之后,參數(shù)類型看上去簡潔多了
    a := 2 * b
    return f(a, b)
}
type User struct {
    Name string
    bye foo // bye的類型是foo,也就是是函數(shù)類型
    hello func(name string) string // 使用匿名函數(shù)來聲明struct字段的類型為函數(shù)類型
}
ch := make(chan func(string) string, 10)
// 使用匿名函數(shù)向管道中添加元素
ch <- func(name string) string {
	return "hello " + name
}

4. 閉包

閉包(Closure)是引用了自由變量的函數(shù),自由變量將和函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境,閉包復(fù)制的是原對象的指針

package main
import "fmt"
func sub() func() {
	i := 10
	fmt.Printf("%p\n", &i)
	b := func() {
		fmt.Printf("i addr %p\n", &i) // 閉包復(fù)制的是原對象的指針
		i-- // b函數(shù)內(nèi)部引用了變量i
		fmt.Println(i)
	}
	return b // 返回了b函數(shù),變量i和函數(shù)b將一起存在,即使已經(jīng)離開函數(shù)sub()
}
// 外部引用函數(shù)參數(shù)局部變量
func add(base int) func(int) int {
	return func(i int) int {
		fmt.Printf("base addr %p\n", &base)
		base += i
		return base
	}
}
func main() {
	b := sub()
	b()
	b()
	fmt.Println()
	tmp1 := add(10)
	fmt.Println(tmp1(1), tmp1(2))
	// 此時tmp1和tmp2不是一個實體了
	tmp2 := add(100)
	fmt.Println(tmp2(1), tmp2(2))
}

5. 延遲調(diào)用defer

  • defer用于注冊一個延遲調(diào)用(在函數(shù)返回之前調(diào)用)
  • defer典型的應(yīng)用場景是釋放資源,比如關(guān)閉文件句柄,釋放數(shù)據(jù)庫連接等
  • 如果同一個函數(shù)里有多個defer,則后注冊的先執(zhí)行,相當于是一個棧
  • defer后可以跟一個func,func內(nèi)部如果發(fā)生panic,會把panic暫時擱置,當把其他defer執(zhí)行完之后再來執(zhí)行這個
  • defer后不是跟func,而直接跟一條執(zhí)行語句,則相關(guān)變量在注冊defer時被拷貝或計算
func basic() {
    fmt.Println("A")
    defer fmt.Println(1) fmt.Println("B")
    // 如果同一個函數(shù)里有多個defer,則后注冊的先執(zhí)行
    defer fmt.Println(2)
    fmt.Println("C")
}
func deferExecTime() (i int) {
	i = 9
	// defer后可以跟一個func
	defer func() {
		fmt.Printf("first i=%d\n", i) // 打印5,而非9,充分理解“defer在函數(shù)返回前執(zhí)行”的含義,不是在“return語句前執(zhí)行defer”
	}()
	defer func(i int) {
		fmt.Printf("second i=%d\n", i) // 打印9
	}(i)
	defer fmt.Printf("third i=%d\n", i) // 打印9,defer后不是跟func,而直接跟一條執(zhí)行語句,則相關(guān)變量在注冊defer時被拷貝或計算
	return 5
}

6. 異常處理

go語言沒有try catch,它提倡直接返回error

func divide(a, b int) (int, error) {
    if b == 0 {
        return -1, errors.New("divide by zero")
    }
    return a / b, nil
}
// 函數(shù)調(diào)用方判斷error是否為nil,不為nil則表示發(fā)生了錯誤
if res, err := divide(3, 0); err != nil {
    fmt.Println(err.Error())
}

Go語言定義了error這個接口,自定義的error要實現(xiàn)Error()方法

// 自定義error
type PathError struct {
    path string
    op string
    createTime string
    message string
}
// error接口要求實現(xiàn)Error() string方法
func (err PathError) Error() string {
	return err.createTime + ": " + err.op + " " + err.path + " " + err.message
}

何時會發(fā)生panic:

  • 運行時錯誤會導(dǎo)致panic,比如數(shù)組越界、除0
  • 程序主動調(diào)用panic(error)

panic會執(zhí)行什么:

  • 逆序執(zhí)行當前goroutine的defer鏈(recover從這里介入)
  • 打印錯誤信息和調(diào)用堆棧
  • 調(diào)用exit(2)結(jié)束整個進程
func soo() {
	fmt.Println("enter soo")
	// 去掉這個defer試試,看看panic的流程,把這個defer放到soo函數(shù)末尾試試
	defer func() {
		// recover必須在defer中才能生效
		if err := recover(); err != nil {
			fmt.Printf("soo panic:%s\n", err)
		}
	}()
	fmt.Println("regist recover")
	defer fmt.Println("hello")
	defer func() {
		n := 0
		_ = 3 / n // 除0異常,發(fā)生panic,下一行的defer沒有注冊成功
		defer fmt.Println("how are you")
	}()
}

二、面向接口編程

1. 接口的基本概念

接口是一組行為規(guī)范的集合

// 定義接口,通常接口名以er結(jié)尾
type Transporter interface {
    // 接口里面只定義方法,不定義變量
    move(src string, dest string) (int, error) // 方法名 (參數(shù)列表) 返回值列表
    whistle(int) int // 參數(shù)列表和返回值列表里的變量名可以省略
}

只要結(jié)構(gòu)體擁有接口里聲明的所有方法,就稱該結(jié)構(gòu)體“實現(xiàn)了接口”,一個struct可以同時實現(xiàn)多個接口

// 定義結(jié)構(gòu)體時無需要顯式聲明它要實現(xiàn)什么接口
type Car struct {
    price int
}
func (car Car) move(src string, dest string) (int, error) {
    return car.price, nil
}
func (car Car) whistle(n int) int {
    return n
}

接口值有兩部分組成, 一個指向該接口的具體類型的指針和另外一個指向該具體類型真實數(shù)據(jù)的指針

car := Car{"寶馬", 100}
var transporter Transporter
transporter = car

2. 接口的使用

func transport(src, dest string, transporter Transporter) error {
	 _,err := transporter.move(src, dest)
	return err
}
var car Car // Car實現(xiàn)了Transporter接口
var ship Shiper	// Shiper實現(xiàn)了Transporter接口
transport("北京", "天津", car)
transport("北京", "天津", ship)

3. 接口的賦值

// 方法接收者是值
func (car Car) whistle(n int) int {
}
// 方法接收者用指針,則實現(xiàn)接口的是指針類型
func (ship *Shiper) whistle(n int) int {
}
car := Car{}
ship := Shiper{}
var transporter Transporter
transporter = car 
transporter = &car // 值實現(xiàn)的方法,默認指針同樣也實現(xiàn)了
transporter = &ship // 但指針實現(xiàn)的方法,值是沒有實現(xiàn)的

4. 接口嵌入

type Transporter interface {
	whistle(int) int
}
type Steamer interface {
    Transporter // 接口嵌入,相當于Transporter接口定義的行為集合是Steamer的子集
    displacement() int
}

5. 空接口

空接口類型用interface{}表示,注意有{}

var i interface{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->} 

空接口沒有定義任何方法,因此任意類型都實現(xiàn)了空接口

var a int = 5
i = a
func square(x interface{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->}){<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->} // 該函數(shù)可以接收任意數(shù)據(jù)類型

注意:slice的元素、map的key和value都可以是空接口類型,map中的key可以是任意能夠用==操作符比較的類型,不能是函數(shù)、map、切片,以及包含上述3中類型成員變量的的struct,map的value可以是任意類型

6. 類型斷言

// 若斷言成功,則ok為true,v是具體的類型
if v, ok := i.(int); ok {
	fmt.Printf("i是int類型,其值為%d\n", v)
} else {
	fmt.Println("i不是int類型")
}

當要判斷的類型比較多時,就需要寫很多if-else,更好的方法是使用switch i.(type),這也是標準的寫法

switch v := i.(type) { // 隱式地在每個case中聲明了一個變量v
case int:  // v已被轉(zhuǎn)為int類型
	fmt.Printf("ele is int, value is %d\n", v)
	// 在 Type Switch 語句的 case 子句中不能使用fallthrough
case float64: // v已被轉(zhuǎn)為float64類型
	fmt.Printf("ele is float64, value is %f\n", v)
case int8, int32, byte: // 如果case后面跟多種type,則v還是interface{}類型
	fmt.Printf("ele is %T, value is %d\n", v, v)
}

7. 面向接口編程

電商推薦流程

為每一個步驟定義一個接口

type Recaller interface {
    Recall(n int) []*common.Product // 生成一批推薦候選集
}
type Sorter interface {
    Sort([]*common.Product) []*common.Product // 傳入一批商品,返回排序之后的商品
}
type Filter interface {
    Filter([]*common.Product) []*common.Product // 傳入一批商品,返回過濾之后的商品
}
type Recommender struct {
    Recallers []recall.Recaller
    Sorter sort.Sorter
    Filters []filter.Filter
}

使用純接口編寫推薦主流程

func (rec *Recommender) Rec() []*common.Product {
	RecallMap := make(map[int]*common.Product, 100)
	// 順序執(zhí)行多路召回
	for _, recaller := range rec.Recallers {
		products := recaller.Recall(10) // 統(tǒng)一設(shè)置每路最多召回10個商品
		for _, product := range products {
			RecallMap[product.Id] = product // 把多路召回的結(jié)果放到map里,按Id進行排重
		}
	}
	// 把map轉(zhuǎn)成slice
	RecallSlice := make([]*common.Product, 0, len(RecallMap))
	for _, product := range RecallMap {
		RecallSlice = append(RecallSlice, product)
	}
	SortedResult := rec.Sorter.Sort(RecallSlice) // 對召回的結(jié)果進行排序
	// 順序執(zhí)行多種過濾規(guī)則
	FilteredResult := SortedResult
	for _, filter := range rec.Filters {
		FilteredResult = filter.Filter(FilteredResult)
	}
	return FilteredResult
}

面向接口編程,在框架層面全是接口。具體的實現(xiàn)由不同的開發(fā)者去完成,每種實現(xiàn)單獨放到一個go文件里,大家的代碼互不干擾。通過配置選擇采用哪種實現(xiàn),也方便進行效果對比

到此這篇關(guān)于GoLang函數(shù)與面向接口編程全面分析講解的文章就介紹到這了,更多相關(guān)GoLang函數(shù)與面向接口內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用Go語言實現(xiàn)benchmark解析器

    使用Go語言實現(xiàn)benchmark解析器

    這篇文章主要為大家詳細介紹了如何使用Go語言實現(xiàn)benchmark解析器并實現(xiàn)及Web UI 數(shù)據(jù)可視化,文中的示例代碼講解詳細,需要的小伙伴可以參考一下
    2025-04-04
  • 完美解決go Fscanf 在讀取文件時出現(xiàn)的問題

    完美解決go Fscanf 在讀取文件時出現(xiàn)的問題

    這篇文章主要介紹了完美解決go Fscanf 在讀取文件時出現(xiàn)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-03-03
  • golang使用RSA加密和解密的實現(xiàn)示例

    golang使用RSA加密和解密的實現(xiàn)示例

    在Golang中RSA加密和解密是一個常見的操作,本文主要介紹了golang使用RSA加密和解密的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下
    2025-03-03
  • 談?wù)剬olang IO讀寫的困惑

    談?wù)剬olang IO讀寫的困惑

    這篇文章主要介紹了談?wù)剬olang IO讀寫的困惑,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • 詳解go語言中sort如何排序

    詳解go語言中sort如何排序

    我們的代碼業(yè)務(wù)中很多地方需要我們自己進行排序操作,本文主要介紹了詳解go語言中sort如何排序,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • go依賴注入管理工具wire的使用方法

    go依賴注入管理工具wire的使用方法

    本文主要介紹了如何使用go wire管理依賴,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-09-09
  • GoLang完整實現(xiàn)快速列表

    GoLang完整實現(xiàn)快速列表

    這篇文章主要介紹了GoLang完整實現(xiàn)快速列表,列表是一種非連續(xù)的存儲容器,由多個節(jié)點組成,節(jié)點通過一些 變量 記錄彼此之間的關(guān)系,列表有多種實現(xiàn)方法,如單鏈表、雙鏈表等
    2022-12-12
  • 詳解Go語言中net/http包的使用

    詳解Go語言中net/http包的使用

    Go語言內(nèi)置的?net/http?包十分的優(yōu)秀,提供了HTTP客戶端和服務(wù)端的實現(xiàn),本文主要就來和大家聊聊net/http包的使用,感興趣的可以了解一下
    2023-07-07
  • GoFrame框架garray對比PHP的array優(yōu)勢

    GoFrame框架garray對比PHP的array優(yōu)勢

    這篇文章主要為大家介紹了GoFrame框架garray對比PHP的array優(yōu)勢詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06
  • 一文帶你了解Go語言中的單元測試

    一文帶你了解Go語言中的單元測試

    寫過單元測試的開發(fā)人員應(yīng)該理解,單元測試最核心的價值是為了證明:為什么我寫的代碼是正確的?也就是從邏輯角度幫你檢查你的代碼。本文就來和大家詳細聊聊Go語言中的單元測試,需要的可以參考一下
    2022-07-07

最新評論

西峡县| 丰城市| 灌云县| 加查县| 溧阳市| 沁阳市| 丰顺县| 新化县| 盐源县| 贵定县| 白沙| 苏尼特左旗| 济宁市| 仲巴县| 剑河县| 牟定县| 贡嘎县| 黄大仙区| 永顺县| 溧水县| 杭锦旗| 肥东县| 饶阳县| 万荣县| 雅江县| 新乡市| 仪征市| 台南市| 香格里拉县| 治多县| 临颍县| 连山| 柳河县| 康乐县| 拉萨市| 东至县| 呼图壁县| 阿克苏市| 宝坻区| 安福县| 英超|