Golang——秒懂函数、参数、可变参数、匿名函数、回调函数、内置函数
函數:
函數是結構化編程的最小模塊單元。它將復雜的算法過程分解為若干較小任務,隱藏相關 細節,使得程序結構更加清晰,易于維護。函數被設計成相對獨立,通過接收輸入參數完成一段算法指令,輸出或存儲相關結果。因此,函數還是代碼復用和測試的基本單元。
關鍵字func用于定義函數
- 函數必須先定義,后調用,定義的過程成為函數定義,
- 函數創建后并不是直接可以運行的,需要手動使用后,才執行,該過程成為函數調用
Go函數特點:
- 無需前置聲明
- 不支持命名嵌套定義(nested)
- 不支持同名函數重載(overload)
- 不支持默認參數
- 支持可變參數
- 支持多返回值
- 支持命名返回值
- 支持匿名函數和閉包
函數只能判斷是否為nil,不支持其他比較操作
func a() {} func b() {} func main() {println(a == nil)println(a == b) // 無效操作:a == b (func can only be compared to nil }參數:
函數傳參都是值傳遞,實參將自己的地址值拷貝一份給形參。如果參數是引用類型,那么就會改變實參的值(A棧幀修改B棧幀的值)
定義格式:
參數:由數據類型和變量名組成 數據類型 變量名
例: int a
普通格式調用:
函數名(參數1,參數2)函數值格式調用:
變量 := 對象.函數名函數表達式格式調用:
變量 := 類名.函數名函數調用時,參數的數量與類型必須與函數定義中的設置相匹配,否則程序將報錯。函數的返回值通常會使用變量接收,否則該返回值將無意義
演示:
func main() {person := Person{24}// 普通格式person.show()// 函數值格式p1 := person.showp1()// 函數表達式格式p2 := (*Person).showp2(&person) }type Person struct {age int }不支持有默認值的可選參數,不支持命名實參。調用時必須按簽名順序傳遞指定類型和數量的實參,就算以“_”命名也不能忽略。參數列表中,相鄰的同類型參數可合并
func main() {test(1, 2, "abc") // 報錯: not enough arguments in call to test 給bool變量賦值就可以了 }func test(x, y int, s string, _ bool) *int {return nil }參數可視作函數的局部變量,因此不能在相同層次定義同名變量
func add(x, y int) int {x := 100 // 錯誤:':=' 的左側沒有新變量var y int // 錯誤:未使用的變量 'y'return x + y }不管是指針、引用類型,還是其他類型參數,都是值拷貝傳遞,無非是拷貝目標對象,還是拷貝指針自身而已。在函數調用時,會為形參和返回值分配內存空間, 并將實參數據拷貝到形參內存。
func main() {a := 0x100p := &aprintln(&p, p) //輸出實參p的地址。test(p) }func test(x *int) {println(&x, x) //輸出形參x的地址。 }輸出:
從輸出結果可以看出,盡管實參和形參都指向同一目標,但指針自身依然被復制。
要實現傳出參數,通常建議使用返回值。當然,二級指針也是常用的手法
func main() {var p *inttest(&p)println(*p) }func test(p **int) {x := 100 * p = &x }如果函數參數過多,建議將其重構為一個復合類型參數,也算是變向實現可選參數和命名實參功能
func main() {opt := newOption()opt.port = 8085server(opt) }type serverOption struct {address stringport intpath stringtimeout time.Durationlog *log.Logger }func newOption() *serverOption {return &serverOption{address: "0.0.0.0",port: 8080,path: "/var/test",timeout: time.Second * 5,log: nil,} }func server(option *serverOption) {}形參和實參:
形參是指函數定義中的參數,實參則是函數調用時所傳遞的參數。形參類同函數局部變量,而實參則函數外部對象,可以是常量、變量、表達式或函數等。
形參:
函數定義中的參數,顧名思義就是只有一個形式,沒有具體的數值
等同于變量定義格式,例如:var num int
實參:
函數調用中的參數,顧名思義就是有實際的參數數值
等同于使用變量或常量,例如:var num int =10
傳遞的形參即使在函數中操作,也不會影響原來的數據值
func main() {a, b := 1, 2Demo12(a, b)fmt.Println("main:", a, b) //main: 1 2 } func Demo12(a, b int) {a, b = b, afmt.Println("Demo12:", a, b) //Demo12: 2 1可變參數(不定參數):
寫函數的時候如果不知道形參長度,可以使用可變參數傳遞
變參本質上就是一個slice,它只能接收相同類型的參數值,且必須放在參數列表的最后
圖解:
演示:
變參是slice,那么參數復制的僅是slice struct自身,不包括底層數組,因此我們可以修改原數據。如果需要,可使用內置函數copy復制slice數據
func test(a ...int) {for i := range a {a[i] += 100} } func main() {a := []int{10, 20, 30}test(a...)fmt.Println(a) }返回值:
函數可以有返回值,也可以有返回值,也可以定義多個返回值。使用return關鍵字定義
如果沒有定義返回值,但是寫了return,那么就相當于終止了函數,跳出循環的意思
方式一:
func main() {// 定義變量接收返回值并執行打印r := Demo06(1, 2)println(r) }// 要在參數后面加上返回值的數據類型 func Demo06(a int, b int) int {// 將a+b的結果賦值給sum并返回,需要在函數里再定義返回值sum := a + breturn sum }方式二:
func main() {// 定義變量接收返回值并執行打印r := Demo07(1, 2)println(r) }// 要在參數后面加上返回值和返回值的數據類型,不需要在函數里再定義返回值 func Demo07(a int, b int) (sum int) {sum = a + breturn sum }方式三:
func main() {// 定義變量接收返回值并執行打印r := Demo08(1, 2)println(r) }// 要在參數后面加上返回值和返回值的數據類型,不需要在函數里再定義返回值 func Demo08(a int, b int) (sum int) {sum = a + b// 如果在函數上已經指定了返回值,在函數中可以省略返回值,直接returnreturn }函數返回多個返回值:
func main() {// 定義變量接收返回值a, b := Demo09()fmt.Println("a = ", a, "\n b = ", b) }// 在函數上只寫返回值類型,在函數中寫返回值 func Demo09() (int, int) {a := 1b := 2// 返回兩個變量的值return a, b }有返回值的函數,必須有明確的return終止語句
func test(x int) int {if x > 0 {return -1} else if x < 0 {return -1} } // 錯誤:missing return at end of function特例情況是panic,或者無break的死循環
func test(x int) int {for {break} }借鑒自動態語言的多返回值模式,讓函數得以返回更多狀態,尤其是error模式。
import "errors" func div(x, y int) (int, error) { //多返回值列表必須使用括號。if y == 0 {return 0, errors.New("division by zero")}return x / y, nil }相同類型的多返回值可用作調用實參,或直接返回
func main() {log(test()) //多返回值直接用作實參。 } func test() (int, error) {return div(5, 0) //多返回值直接用作return結果。 }func div(x, y int) (int, error) {if y == 0 {return 0, errors.New("division by zero")}return x / y, nil }func log(x int, err error) {fmt.Println(x, err) }返回值命名:
從這個簡單的示例就可看出,命名返回值讓函數聲明更加清晰,同時也會改善幫助文檔和 代碼編輯器提示。
func paging(sql string, index int) (count int, pages int, err error) {}命名返回值和參數一樣,可當做函數局部變量使用,最后由return隱式返回
func div(x, y int) (z int, err error) {if y == 0 {err = errors.New("division by zero")return}z = x / yreturn // 相當于 “return z, err”。 }只是這些特殊的局部變量會被不同層級的同名變量遮蔽,造成潛在錯誤。好在編譯器會自動檢查此類狀況,只需改為顯式return返回即可
func add(x, y int) (z int) {{z := x + y //新定義的同名局部變量,同名遮蔽。return // 錯誤:z is shadowed during return (改成 “return z” 即可)}return }除遮蔽外,我們還必須對全部返回值命名,否則編譯器會稀里糊涂
func test() (int, s string, e error) {return 0, "", nil // 錯誤:cannot use 0 (type int) as type string in return argument }函數重寫:
子類中出現與父類一模一樣的函數時,會使用就近原則,子類優先使用子類的函數,要想通過子類的對象調用父類的函數,需要使用子類對象.父類.函數名
函數重寫的應用場景:
當子類需要父類的功能,而功能主體子類有自己特有內容時,可以重寫父類中的函數,這樣,即沿襲了父類的功能,又定義了子類特有的內容
演示:
func main() {stu := Student{}stu.show() // 子類的Show函數stu.Person.show() // 父類的Show函數 }type Person struct {age int }type Student struct {Personname stringage intscore float64 }func (student Student) show() {fmt.Println("子類的Show函數") }func (person *Person) show() {fmt.Println("父類的Show函數") }匿名函數:
匿名函數是指沒有定義名字符號的函數
匿名函數除沒有名字外,其他和普通函數完全相同。最大的區別是,我們可在函數內部定義匿名函數,形成類似嵌套函數的效果。匿名函數可直接調用,保存到變量,作為參數或返回值
格式:
func(形參) {函數體}(實參)直接執行:
func main() {func(s string) {fmt.Println(s)}("我是實參,上面的s,是形參,我會被打印不") }有返回值匿名函數:
func main() {add := test()println(add(1, 2)) }func test() func(int, int) int {return func(x, y int) int {return x + y} }作為第一類對象,不管是普通函數,還是匿名函數都可作為struct字段,或通過channel傳遞
func testStruct() {type calc struct {mul func(x, y int) int}x := calc{mul: func(x, y int) int {return x * y},}println(x.mul(2, 3)) } func testChannel() {c := make(chan func(int, int) int, 2)c <- func(x, y int) int {return x + y}println((<-c)(1, 2)) }回調函數:
本質就是函數類型的指針,通過指針,在特定位置,滿足條件后,系統會自動調用函數。
普通函數調用格式:
func aaa(){}func main(){aaa()// 普通函數是通過函數名+括號調用的 }回調函數調用格式:
func handler(w http.ResponseWriter, r *http.Request) {}func main(){http.HandleFunc("/itzhuzhu", handler)// 只需要將函數名傳入即可 }內置函數:
len:用來計算長度的,string、arr、slice、map、channel都可以
new:用來分配值內存的,int、float32、struct返回值是指針
make:用來分配引用類型內存的,slice、map、channel
直接定義變量的流程是:開辟內存空間->將數據存儲到內存空間
適用new定義變量的流程是:開啟指針內存空間->指向數據的內存地址
總結
以上是生活随笔為你收集整理的Golang——秒懂函数、参数、可变参数、匿名函数、回调函数、内置函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三维数组设置索引_python3三维数据
- 下一篇: Golang——枚举(iota)的使用