一、函数

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

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

    1)函数定义

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

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

    Go函数和方法

     常用函数用法:

// 函数多参无返回值
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.

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-10-30
  • 2021-08-09
  • 2021-06-04
  • 2021-07-27
  • 2022-12-23
猜你喜欢
  • 2021-10-17
  • 2021-11-17
  • 2022-01-19
  • 2022-01-09
  • 2022-12-23
  • 2022-12-23
  • 2021-09-06
相关资源
相似解决方案