zoukankan      html  css  js  c++  java
  • Go函数和方法

     一、函数

        函数是基本的代码块,用于执行一个任务。

        go语言至少有个main()函数

        1)函数定义

    func function_name( [parameter list] ) [return_types] {
       函数体
    }

          func:声明这是一个函数
          function_name:函数名称,函数名和参数列表一起构成了函数签名
          parameter list:参数列表,注意类型在变量名之后
          return_types:返回类型,不是必须的,当没有返回值时,可以不指定返回类型,也可以返回多个值,如(string,string)
          函数体:函数定义的代码集合

        

         常用函数用法:

    // 函数多参无返回值
    func func_name(a,b int, c string){}
    // 函数无参无返回值
    func func_name(){}
    // 单个返回值
    func func_name(s string) string{}
    // 多个返回值
    func func_name (s string) (string,int){}
    // 命名返回参数
    func func_name(s string) (result string){
        ...
        result=1
        return
    }
    // 可变参数,可变参数只能做为函数参数存在,并且是最后一个参数,本质上是slice
    func func_name(s string,args ...int){}
    
    // 匿名函数,调用:f(1,2)
    f := func(x,y int) int {
        return x + y
    }

         注意:

          ①:Go函数不支持重载

          ②:一个包中不能有两个名字一样的函数

          ③:当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他的类型可以省略

          ④:函数可以返回任意数量的返回值

          ⑤:使用关键字 func 定义函数,左大括号依旧不能另起一行

      

         2)函数参数

          函数如果使用参数,该变量可称为函数的形参。

          形参就像定义在函数体内的局部变量。

          调用函数,传递过来的变量就是函数的实参,可以通过两种方式来传递参数:

    传递类型描述
    值传递 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
    引用传递 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

          默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数

          注意:

            ①:无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低

            ②:map、slice、chan、指针、interface默认以引用的方式传递

          可变参数:

            Golang 可变参数本质上就是 slice。只能有一个,且必须是最后一个。

             在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可

          任意类型的不定参数:

            就是函数的参数和每个参数的类型都不是固定的

            用空接口:interface{}传递任意类型数据是Go语言的惯例用法,而且interface{}是类型安全的

        3)递归函数

          递归,就是在运行的过程中调用自己

          Go语言支持递归,注意在使用递归时,要设置退出条件,避免陷入死循环

    func recursion() {
       recursion() /* 函数调用自身 */
    }
    
    func main() {
       recursion()
    }

          构成递归需具备的条件

            ①:子问题须与原始问题为同样的事,且更为简单

            ②: 不能无限制地调用本身,须有个出口,化简为非递归状况处理

        4)defer

          Go语言的 defer 语句会将其后面跟随的语句进行延迟处理

          在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。

          关键字 defer 的用法类似于面向对象编程语言 Java 和 C# 的 finally 语句块,它一般用于释放某些已分配的资源,典型的例子就是对一个互斥解锁,或者关闭一个文件

          defer关键字特性:

            ①:关键字 defer 用于注册延迟调用

            ②:这些defer调用直到所在函数 return 前才被执行。因此,可以用来做资源清理

            ③:多个defer语句,defer 所在的函数返回后,将按照后进先出的顺序执行 defer 保存的延迟调用函数,也就是说,后定义的defer函数先执行

            ④:defer 延迟调用函数可以读取并分配给返回函数的命名返回值

            ⑤:defer语句中的变量,在defer声明时就决定了

          defer用途:

            ①:关闭文件句柄

            ②:锁资源释放

            ③:数据库连接释放

            ④:panic捕获

        5)init函数和mian函数

          ①:init函数

            go语言中init函数用于包(package)的初始化,该函数是go语言的一个重要特性

            init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等

            每个包可以拥有多个init函数,包的每个源文件也可以拥有多个init函数

            对同一个go文件的init()调用顺序是从上到下的。

            对同一个包中不同文件是按文件名字符串比较“从小到大”顺序调用各文件中的init()函数

            不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序

            init函数不能被其他函数调用,而是在main函数执行之前,自动被调用

          ②:main函数

            Go语言程序的默认入口函数(主函数)

        func main(){
            //函数体
        }

        6)函数表达式

          使用函数表达式实现三目运算

        // 函数表达式,实现三目运算
        // 格式:func() returnType {...}()
        i := 1
        j := 2
        k := func() int {
            if i > j {
                return i
            }
            return j
        }()
        fmt.Println(k)

          带参数的函数表达式:

        h := func(a, b int) int {
            if a > b {
                return a
            }
            return b
        }(i, j)

        7)匿名函数

          匿名函数是指不需要定义函数名的一种函数实现方式,匿名函数由一个不带函数名的函数声明和函数体组成。

          匿名函数的优越性在于可以直接使用函数内的变量,不必声明

          在Go里面,函数可以像普通变量一样被传递或使用。Golang匿名函数可赋值给变量,做为结构字段,或者在 channel 里传送。

          如:

        getSqrt := func(a float64) float64 {
            return math.Sqrt(a)
        }
        fmt.Println(getSqrt(4))

          上面先定义了一个名为getSqrt 的变量,初始化该变量时和之前的变量初始化有些不同,使用了func,func是定义函数的,可是这个函数和上面说的函数最大不同就是没有函数名,也就是匿名函数。这里将一个函数当做一个变量一样的操作

          

        8)闭包

          什么是闭包?闭包就是能够读取其他函数内部变量的函数。

          通常只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁

           Go语言支持闭包,通过匿名函数的方式,如:

    // 创建函数a,返回另外一个函数。该函数的目的是在闭包中递增i的变量
    func a() func() int {
        i := 0
        b := func() int {
            i++
            fmt.Println(i)
            return i
        }
        return b
    }
    
    func main() {
        c := a()
        c() // 1
        c() // 2
        c() // 3
    
        a() //不会输出i
    }

        9) new和make函数

          在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间。要分配内存,就引出来今天的new和make。 Go语言中new和make是内建的两个函数,主要用来分配内存

          

          ①:new

            new函数的签名:  func new(Type) *Type

            Type表示参数类型,*type表示类型指针,new函数返回一个指向该类型内存地址的指针

            new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。

            如var a *int只是声明了一个指针变量a但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。

            应该按照如下方式使用内置的new函数对a进行初始化之后就可以正常对其赋值了:

    func main() {
        var a *int
        a = new(int)
        *a = 10
        fmt.Println(*a)
    }

          new引发panic异常:

    //定义一个结构体,age字段为指针
    type Student struct {
        age *int
    }
    
    //获取结构体对象指针
    func getStudent() *Student {
        s := new(Student) // panic,因为new只会为结构体Student申请一片内存空间,不会为结构体中的指针age申请内存空间
        return s
    }

          ②:make

            make函数的签名:func make(t Type, size ...IntegerType) Type

            make也是用于分配内存的,区别于new,它只用于slice、map以及channel的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没必要返回他们的指针了。

            make函数是无可替代的,我们在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作

          new函数和make函数的区别

            相同点:

              底层都是通过mallocgc申请内存

            不同点:

              ①:make 返回值是”引用类型“,new 返回值是指针类型

              ②:make仅用于初始化 slice,map 和 chan;new 可用于初始化任意类型(new并不常用) 

            

     

          

     二、方法

        Go 语言中同时有函数和方法。一个方法就是一个包含了接收者的函数。

        方法可以将类型和方法封装在一起,实现强耦合。

        接收者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集

        Go语言中的方法时一种作用于特定类型变量的函数。这种特定类型变量叫做接收者,接收者的概念类似于Java语言中的this,和Python语言中的self。只不过Go语言中需要将this显式的声明出来。

        方法的定义格式如下:

        func (接收者变量 接收者类型) 方法名(参数列表) (返回类型) {
           方法体
        }

        方法示例:

    // 定义结构体
    type User struct {
        name    string
        gender  string
        address string
        age     int
    }
    
    // 接收User类型的方法
    // 值类型的接收者
    func (user User) getName() string {
        return user.name
    }
    
    // 指针类型的接收者
    func (user *User) setName(name string) {
        user.name = name
    }
    
    func main() {
        user := &User{
            name:    "yangyongjie",
            age:     27,
            gender:  "male",
            address: "nanjing",
        }
        name := user.getName() // 该方法只能User结构体类型的变量或指针才能调用
        fmt.Println(name)      // yangyongjie
    
        user1 := &User{}
        user1.setName("yyj")
        fmt.Println(user1.name) // yyj
    
    }

        值类型的接收者和值类型的接收者方法的区别:

          指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式十分接近于Java语言中的this,和Python语言中的self

          当方法作用于值类型的接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但是修改操作只是针对副本,无法修改接收者变量本身。

        如:

    import "fmt"
    
    // 定义结构体
    type User struct {
        name    string
        gender  string
        address string
        age     int
    }
    
    // 值类型的接收者
    func (user User) setAddress(address string) {
        user.address = address
    }
    
    // 指针类型的接收者
    func (user *User) setName(name string) {
        user.name = name
    }
    
    func main() {
        user := &User{
            name:    "yangyongjie",
            age:     27,
            gender:  "male",
            address: "nanjing",
        }
        
        // 接收值类型的方法,user变量本身值没有被修改
        user.setAddress("beijing")
        fmt.Println(user.address) // nanjing
    
        // 接收指针类型的方法,user变量本身值没有被修改
        user.setName("yyj")
        fmt.Println(user.name) // yyj
    
    }

          什么时候应该使用指针类型接收者?

            ①:需要修改接收者中的值

            ②:接收者是拷贝代价比较大的大对象

            ③:保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者

      结构体变量和结构体指针的理解:

        结构体指针指向的是结构体变量的内存地址

        结构体变量是结构体类型的变量本身的值

    END.

  • 相关阅读:
    Redis使用
    Linux下安装Tomcat
    Linux安装MySQL
    jquery的clone方法bug的修复select,textarea的值丢失
    sql 跨服务器查询数据
    无法执行该操作,因为链接服务器 "xxxxx" 的 OLE DB 访问接口 "SQLNCLI" 无法启动分布式事务
    web服务器决定支持多少人同时在线的因素
    2016最新的中国省市区三级数据库表.sql mssql
    sqlserver2008R2数据库自动备份脚本
    sql server 2008 windows验证改混合登陆中SqlServer身份验证用户名密码
  • 原文地址:https://www.cnblogs.com/yangyongjie/p/15722438.html
Copyright © 2011-2022 走看看