zoukankan      html  css  js  c++  java
  • GO学习之 函数的进阶

    一.函数的调用机制

    1.函数的调用过程

    函数的局部变量与全局变量

    举例:

    package main
    
    import "fmt"
    
    // 一个test函数
    func test(n1 int){
        n1 += 1
        fmt.Println("n1=",n1)
    }
    
    // 一个get函数
    func getSum(n1 int, n2 int) int {
        //
        sum := n1 + n2
        return sum
    }
    
    func main(){
        // 调用 函数
        n1 := 10
        test(n1)
        get_sum := getSum(1, 2)
        fmt.Println("n1=",n1)
        fmt.Println("get_sum",get_sum)
    
    }
    

    程序在运行时,在调用函数时,会在栈空间中分配给函数一个空间。当调用完毕后,空间被进行回收。

    return 语句:

    注意go 语言return支持多个返回值

    基本语法:

    func 函数名(形参列表) (返回值类型列表){

    语句....

    return 返回值列表

    }

    1)如果返回多个值时,在接收时,希望忽略某个返回值,则使用’_’符号表示占位忽略

    2)如果返回值只有一个 (返回值类型列表)可以不写 ()

    多个返回值与 _ 占位举例:

    package main
    
    import "fmt"
    // 返回两个值的和和查
    func getSumAndSub(a int, b int) (int, int){
        sum := a + b
        sub := a - b
        return sum,sub
    }
    
    func main(){
        // 调用 函数
        n1 := 10
        test(n1)
        get_sum := getSum(1, 2)
        fmt.Println("n1=",n1)
        fmt.Println("get_sum",get_sum)
    
        // 返回多个值
        re1,re2 := getSumAndSub(3,2)
        fmt.Println(re1)
        fmt.Println(re2)
    
        // 希望忽略某个返回值,用 _ 符号表示占位符
        _,re3 := getSumAndSub(5,4)
        fmt.Println(re3)
    }
    

     

    2.函数的递归调用

    基本介绍

    一个函数在函体内又调用了本身,我们称为递归调用。

    举例:

    package main
    
    import (
        "fmt"
    )
    
    func test(n int) {
        if n > 2 {
            n--
            test(n) // 把test()函数全部带入
        }
        fmt.Println("n=", n)
    }
    
    func main() {
        test(4)
    }
    

    举例2

    package main
    
    import (
        "fmt"
    )
    func test2(n int) {
        if n > 2 { // 注意执行过if 不在执行else
            n--
            test(n) // 把test()函数全部带入
        } else {
            fmt.Println("n=", n)  //  n = 2
        }   
    }
    
    func main() {
        test2(4)   
    }
    

    函数递归需要遵守的重要原则:

    1_执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)

    2_函数的局部变量时独立的,不受相互影响

    3_递归必须向退出递归的条件逼近,有穷性

    4_当一个函数执行完毕,或遇到return,就会返回,遵守谁调用,就将结果返回给谁。

    同时当函数执行完毕或者返回时,该函数本身也会被销毁。

    递归经典练习题:

    练习01:斐波那契数

    package main
    
    import "fmt"
    
    func fb(n int ) int {
        if (n == 2 || n == 1) {
            return 1
        } else {
            return fb(n-1) + fb(n-2)
        }
    }
    
    func main(){
        //
        num := fb(5)
        fmt.Println("num:",num)  // 输出第几个的斐波那契数
    }
    

    练习02:求函数值

    已知f(1) = 3;f(n) = 2*f(n-1) + 1;

    请使用递归的思想编程,求出f(n)的值

    package main
    
    import "fmt"
    
    func test(n int) int {
        if n == 1 {
            return 3
        } else {
            return 2 * test(n-1) + 1
        }
    }
    
    func main(){
        fmt.Println("f(1)=",test(1))  // 3
        fmt.Println("f(5)=",test(5))  // 63
    
    }
    

    练习03:猴子吃桃吃一半多一个,10天剩一个,原来桃子多少

    package main
    
    import "fmt"
    func test(n int) int {
        if n == 10 {
            return 1
        } else {
            return 2 * (test(n+1) + 1)
        }
    }
    
    
    func main(){
        fmt.Println(test(5))
    }
    

    3.函数的主要事项和细节

    1_函数的形参可以是多个,返回列表也可以是多个

    2_形参列表和返回列表的数据类型可以是值类型和引用类型

    3_函数的命名遵循标识符命名规范,首字母不能是数字。首字母大写包能被本包问号和其他问号使用,类似pubilc,首字母小写只能在本包使用,其他文件不能使用,类似private

    4_函数中的变量是局部的,函数外不生效

    5_基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改不会影响到原来的值。

    6_如果希望函数内的变量能修改函数外的变量,可以传入变的地址&,函数内以指针的方式操作变量。从效果看类似引用。

    7_GO函数不能重载,函数名相同不支持

    8_GO中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。

    package main
    
    import "fmt"
    
    func getSum(n1 int,n2 int) int {
        return n1 + n2
    }
    
    func main(){
        // 在GO中,函数也是一种数据类型,
        // 可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。
        a := getSum
        fmt.Printf("a的类型%T,getSum类似是%T
    ",a,getSum)
        res := getSum(10,40)
        fmt.Println("res=",res)
    
    }
    

    9_函数既然是一种数据类型,因此在GO中,函数可以作为形参,并且调用。

    package main
    
    import "fmt"
    
    func getSum(n1 int,n2 int) int {
        return n1 + n2
    }
    // 函数既然是一种数据类型,因此在GO中,函数可以作为形参,并且调用。
    func myFun(funvar func(int, int) int, num1 int , num2 int) int {
        return funvar(num1, num2)
    }
    
    func main(){
        // 在GO中,函数也是一种数据类型,
        // 可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。
        a := getSum
        fmt.Printf("a的类型%T,getSum类似是%T
    ",a,getSum)
        res := getSum(10,40)
        fmt.Println("res=",res)
    
        res2 := myFun(getSum, 50, 60)
        fmt.Println("res2=",res2)
    
    }
    

    11_为了简化数据类型定义,GO支持自定义数据类型。

    基本语法:type自定义数据类型名 数据类型  // 理解:相当于一个别名

    举例:type myint int // 这时myint就等价于int来使用

    package main
    
    import "fmt"
    
    func main(){
    
        type myInt int   // 给int取别名 ,在go中myInt和int虽然都是int类型,但是go认为myInt和int还是不同的类型
        var num1 myInt
        var num2 int
    
        fmt.Printf("num1类型%T,num2类型%T",num1,num2) // 打印:num1类型main.myInt,num2类似int
    
    }
    

    举例02type mySum func(int, int) int // 这时mySum就等价一个函数类型 func(int,int) int

    package main
    
    import "fmt"
    
    // 案例2
    type myFunType func(int, int) int // 这时myFun就是func(int,int) int 类型
    
    func getSum(n1 int, n2 int) int {
        return n1 + n2
    }
    
    func myFun(funvar myFunType, num1 int, num2 int) int {
        return funvar(num1, num2)
    }
    
    func main() {
    
        type myInt int // 给int取别名 ,在go中myInt和int虽然都是int类型,但是go认为myInt和int还是不同的类型
        var num1 myInt
        var num2 int
    
        fmt.Printf("num1类型%T,num2类型%T
    ", num1, num2) // 打印:num1类型main.myInt,num2类似int
    
        res := myFun(getSum, 500, 600)
        fmt.Println(res)  \ 1100
    
    }
    

     

    12_支持对返回值命名

    package main
    
    import "fmt"
    
    func getSumAndSub(n1 int, n2 int) (sum int, sub int) {
        sum = n1 + n2
    
        sub = n1 - n2
    
        return
    }
    
    func main() {
        //
        a, b := getSumAndSub(3, 2)
        fmt.Println(a, b)
    
    }
    

    13.使用_标识符,忽略返回值

    14.GO中支持可变参数

    语法格式:

    // 支持0到多个参数

    func sum(args...int) sum int {

    }

    // 支持1到多个参数

    func sum(n1 int,args...int) sum int {

    }

    说明:

    1args slice 切片(类似数组),通过args[index] 可以访问到各个值。

    举例:编写一个函数sum ,可以求出1到多个int的和

    package main
    
    import "fmt"
    
    func sum(n1 int,args...int) int {
        sum := n1
        for i := 0; i < len(args); i ++ {
            sum += args[i]  // args[0] 表示第一个元素的值 其他以此类推
        } 
    
        return sum
    }
    
    func main(){
        // 编写一个函数sum ,可以求出1到多个int的和
        res := sum(5,6,7,8,1,2,3)
        fmt.Println(res)
    
    }
    

    练习:使用函数对两个值进行交换

    package main
    
    import "fmt"
    
    func swap(n1 *int, n2 *int) { // 指针接收地址进行值的相应交换
        // 定义一个临时变量
        t := *n1
        *n1 = *n2
        *n2 = t
    
    }
    
    func main() {
        a := 10
        b := 20
        swap(&a, &b) // 传入的是地址
        fmt.Printf("a=%v,b=%v", a, b)
    
    }
    

    .常用函数的介绍

    1.init函数

    基本介绍:

    每一个源文件都可以包含一个init函数,该函数在main函数执行前,被GO运行框架调用,也就是说init会在main函数前被调用。

    package main
    
    import "fmt"
    
    func init() {
        fmt.Println("run init")
    }
    
    func main(){
        fmt.Println("run main")
    }
    // 打印结果:
    // run init
    // run main
    

    init函数的注意事项和细节

    (1)如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程全局变量定义--init函数-->main函数

    package main
    
    import "fmt"
    
    var age = test()
    
    func test() int {
        fmt.Println("run test")
        return 90
    }
    
    func init() {
        fmt.Println("run init")
    }
    
    func main(){
        fmt.Println("run main")
        fmt.Println("age=",age)
    }
    
    // 得出结论:先执行变量定义,再执行test-->init -- > main
    // 打印结果:
    // run test
    // run init
    // run main
    // age= 90
    

    (2)init 函数最主要的作用就是完成一些初始化的工作。(就是定义一个初始化的变量)

    (3)当一个main函数从另外文件引入包的时候,优先执行包中的init函数

    2.匿名函数

    GO支持匿名函数,如果我们某个函数只希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用。

    匿名函数使用方式1

    在定义匿名函数时就直接调用(这种方式只能调用一次)

    举例01:

    package main
    
    import "fmt"
    
    func main() {
        // 在定义匿名函数时就直接调用
        // 使用匿名函数求两个数和
        res := func (n1 int,n2 int) int {
            return n1 + n2
        }(10,20)
    
        fmt.Println(res)
    
    }
    

    匿名函数使用方式2

    将匿名函数赋值给一个变量(函数变量),再通过该变量来调用匿名函数

    举例02

    package main
    
    import "fmt"
    
    func main() {
    
        // 将匿名函数func (n1 int n2 int) int 赋给 a 变量
        // 则 a 的数据类型就是函数类型 此时 我们可以通过a完成调用
        a := func (n1 int,n2 int) int {
            return n1 - n2
        }
    
        // 可以多次调用  多态???
        res2 := a(30,10)
        fmt.Println(res2)
        res3 := a(40,10)
        fmt.Println(res3)
    
    }
    

    全局匿名函数

    如果将匿名函数赋值给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在整个程序中有效。

    举例:

    package main
    
    import "fmt"
    
    var (
        // Func1 为全局匿名函数
        Func1 = func(n1 int,n2 int) int {
            return n1 * n2
        }
    )
    
    func main() {
        // 全局匿名函数的使用
        res4 := Func1(4,5)
        fmt.Println(res4)
    
    }
    

    3.闭包

    基本介绍:

    闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)。

    举例:

    package main
    
    import "fmt"
    
    // 累加器
    func Addupper() func(int) int {
        // 匿名函数与外面的n构成一个闭包
        var n int = 10
        return func(x int) int {  // 返回是一个函数   匿名函数引用到外面变量n
            n = n + x
            return n
        }
    
    }
    
    func main() {
        //
        f := Addupper()
        fmt.Println(f(1))  // 11
        fmt.Println(f(2))  // 13
    
    }
    

    返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成一个整体,构成闭包。

    可以理解为:闭包是一个类 ,函数是操作,n是字段。

    当我们反复的调用f函数时,因为n只初始化一次

    搞清楚闭包的关机,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和引用的变量共同构成闭包。

    一个闭包的例子:

    package main
    
    import "fmt"
    import "strings"
    
    func makeSuffix(suffix string) func(string) string {
        // (1)编写一个函数 makeSuffix (suffix string) 可以接收一个文件后缀名比如后缀名(.jpg)
        // 并返回一个闭包
        // (2)调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀比如后缀名(.jpg),则返回
        // 文件名.jpg,如果已经有.jpg后缀,则返回原文件名
    
        // (3) 要求使用闭包的方式完成
        // (4)strings.HasSuffix  这个函数可以判断是否存有指定的后缀
        return func(name string) string {
            if !strings.HasSuffix(name, suffix) {
                return name + suffix
            }
            return name
        }
    }
    
    func main() {
        // .jpg的后缀
        f := makeSuffix(".jpg")
        // 实例引用
        fmt.Println("new_name:", f("today"))
    
        fmt.Println("new_name:", f("today.jpg"))
        
    
    }
    
    // 以上代码说明:返回的匿名函数和maksSuffix(suffix string)的suffix 变量组成一个闭包
    // 返回的函数引用到suffix变量
    

    4.函数中defer

    为什么需要defer

    在函数中,需要创建资源(比如:数据库连接,文件句柄,锁等),为了在函数执行完毕后,及时的释放资源,GO的设计提供defer(延时机制)

    defer的基本介绍

    1)当go执行到一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入到一个defer栈中(一个独立的栈),然后继续执行函数下一个语句。

    2)当函数执行完毕后,在defer栈中,依次从栈取出语句执行(注:遵循栈,先入后出的机制),所以同学们看到前面案例输出的顺序。

    3defer将语句放入到栈时,也会将相关的值拷贝同时入栈

    举例:

    package main
    
    import "fmt"
    
    func sum(n1 int, n2 int) int {
        // defer 的作用会让其本来要执行的语句暂时不执行,将要执行的语句压入到一个defer栈(独立栈)
        // 暂时不执行
        // 当函数执行完毕后,再执行defer栈中语句:先入后出执行
        defer fmt.Println("n1=", n1) // defer
        defer fmt.Println("n2=", n2) // defer
        res := n1 + n2
        fmt.Println("in_res=", res)
        return res
    }
    
    func main() {
        res := sum(5, 7)
        fmt.Println("out_res=", res)
        // 结果:
        // in_res= 12
        // n2= 7
        // n1= 5
        // out_res= 12
    }
    

    defer的最佳实践在于,当函数执行完毕后,可以及时的释放创建的资源(比如:打开文件,立即defer就不用考虑什么时候去close,数据库连接也是如此)。在defer以后可以继续使用创建的资源,当函数执行完毕后可以自动从defer中取出并进行关闭资源。

    比如:

    例子01:

    func test() {

    // 关闭文件资源

    file = openfile(文件名)

    def file.close()

    // 使用文件句柄相关操作

    }

    例子02

    func test() {

    // 释放数据库资源

    connect = openDatabse()

    defer connect.close()

    // 操作数据库

    }

    三.函数参数的传递方式

    基本介绍:

    值类型参数默认就是值传递,而引用类型参数默认就是引用传递。

    1.两种传递方式

    1)值传递

    基本数据类型(int float,bool,string,数组和结构体struct

    2)引用传递(指针传递)

    指针,slice切片,map,管道chan,interface等都是引用类型

    不管是值传递还是引用传递,传递函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定的数据大小,数据越大效率越低。

    2.值传递和引用传递使用特点

    1)值类型默认是值传递:变量直接存储值,内存通常在栈中分配。

    2)引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何引用这个地址时,该地址对应的数据空间就成为一个垃圾,有GC来回收。

    3)如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。

    四.变量的作用域

    说明:

    1) 函数内声明/定义的变量叫局部变量,作用域仅限于函数内部。

    2)函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效。

    3)如果变量是在一个代码块,比如for /if 中,那么这个变量的作用域就在该代码块中。

    例子:局部变量与全局变量

    package main
    
    import "fmt"
    
    var age int = 50
    var Name string = "zero"
    
    // 函数
    func test() {
        // age 和 Name 的作用域只在test函数内
        age := 10
        Name := "hsz"
        fmt.Println("age=", age)
        fmt.Println("Name=", Name)
    }
    
    func main() {
        test()
        fmt.Println("age=", age)   // 打印是test() 函数外的age变量
        fmt.Println("Name=", Name) // 打印是test()函数外的Name变量
        // 最后输出结果为:
        // age= 10
        // Name= hsz
        // age= 50
        // Name= zero
    }
    

    GO语言中:赋值语句不能在函数体外,如:Name := “hsz” 是错误的。

    四.函数的练习

    1.金字塔

      // 打印金字塔案例   使用函数的方式

     // 1   2   3   4    5

    package main
    
    import "fmt"
    
    func Jinzita(totalLevel int){
    
        // 表示层数
        for i :=1;i <= totalLevel; i++ {
            // 在打印 *前先打印空格
            for k:=1; k <= totalLevel - i; k++ {
                fmt.Print(" ")
            }
    
            // j 表示每层打印多少
            for j :=1; j <= 2 * i - 1; j++ {
                fmt.Print("*")
            }
            fmt.Println()
        }
    }
    
    func main() {
        var n int
        fmt.Println("请输入金字塔层数:")
        fmt.Scanln(&n)
        Jinzita(n)
    }
    

    2.九九乘法表

    用函数的方式打印九九乘法表

    package main
    
    import "fmt"
    
    func NightNight(){
        // 打印九九乘法表
        for i:=1;i<=9;i++{
            for j := 1; j <= i; j++{
                fmt.Printf("%v*%v=%v",i,j,i*j)
            }
            fmt.Println()
        }
    }
    
    func main(){
        NightNight()
    }
    
  • 相关阅读:
    [EasyUI美化换肤]更换EasyUi图标
    [干货来袭]MSSQL Server on Linux预览版安装教程(先帮大家踩坑)
    SignalR系列续集[系列8:SignalR的性能监测与服务器的负载测试]
    对百度WebUploader开源上传控件的二次封装,精简前端代码(两句代码搞定上传)
    EntityFramework的多种记录日志方式,记录错误并分析执行时间过长原因(系列4)
    采用EntityFramework.Extended 对EF进行扩展(Entity Framework 延伸系列2)
    android ViewFlipper(翻转视图) 使用
    Android 下拉列表Spinner 使用
    Android选项卡TabHost功能和用法
    【android,eclipse解决】eclipse insert "}" to complete ClassBodyR.java
  • 原文地址:https://www.cnblogs.com/hszstudypy/p/12827086.html
Copyright © 2011-2022 走看看