【Go学习】Go的函数

【Go学习】Go的函数

函数是基本的代码块,用于执行一个任务,是构成代码执行的逻辑结构。
在Go语言中,函数的基本组成为:关键字func、函数名、参数列表、返回值、函数体和返回语句。

函数定义

函数其实在之前已经见过了,第一次执行hello world程序的main()其实就是一个函数,而且是一个比较特殊的函数。每个go程序都是从名为main的package包的main()函数开始执行包的概念不是这里的重点,以后做单独说明。同时main()函数是无参数,无返回值的。
Go函数的完成定义如下:

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

定义解析:
从Go的函数定义可以看出,Go的返回值是放在函数名和参数后面的,这点和C及Java的差别还是很多大的。

func:Go的函数声明关键字,声明一个函数。
function_name:函数名称,函数名和参数列表一起构成了函数签名。
parameter list:参数列表,指定的是参数类型、顺序、及参数个数。参数是可选的,即函数可以不包含参数。参数就像一个占位符,这是参数被称为形参,当函数被调用时,将具体的值传递给参数,这个值被称为实际参数。
return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。这里需要注意的是Go函数支持多返回值。有些功能不需要返回值,这种情况下 return_types 不是必须的。
函数体:函数定义的代码集合,表示函数完成的动作。

函数调用

Go的函数调用只要通过函数名然后向函数传递参数,函数就会执行并返回值回来。就像之前调用Println()输出信息一样。
这里提一点,如果函数和调用不在同一个包(package)内,需要先通过import关键字将包引入–import “fmt”。函数Println()就属于包fmt。
这里可能注意到Println()函数命名是首字母是大写的。在Go语言中函数名字的大小写不仅仅是风格,更直接体现了该函数的可见性。这和其他语言对于函数或方法的命名规定可能有很大不同,像Java就推荐是驼峰的写法,C也不建议函数名首字母是大写。但是在Go中,如果首字母不大写,你可能会遇到莫名其妙的编译错误, 比如你明明导入了对应的包,Go编译器还是会告诉你无法找到这个函数。
因此在Go中,需要记住一个规则:

小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。

同时这个规则也适用于变量的可见性,即首字母大写的变量才是全局的。

package main 

import "fmt"

func min(num1 int,num2 int) int {
    if num1 > num2 {
        return num2
    }else{
        return num1
    }
}


func main() {
    var a int = 123 
    var b int = 234
    ret := min(a,b)

    fmt.Printf("Min Value is : %d\n",ret)
}

这里写图片描述
上面定义了一个函数min(),用于比较两个数,并返回其中较大的一个。最终通过main() 函数中调用 min()函数执行。
这里关于函数的参数列表有一个简便写法,当连续两个或多个函数的已命名形参类型相同时,除最后一个类型以外,其它都可以省略。
就像上面的func min(num1 int, num2 int) int {}定义,可以简写成

func min(num1 , num2 int) int {}

多返回值

前面定义函数时说过,Go的函数支持多返回值,这与C、C++和Java等开发语言极大不同。这个特性能够使我们写出比其他语言更优雅、更简洁的代码,比如File.Read()函 数就可以同时返回读取的字节数和错误信息。如果读取文件成功,则返回值中的n为读取的字节 数,err为nil,否则err为具体的出错信息:

func (file *File) Read(b []byte) (n int, err Error) 

一个简单的例子如下:

package main

import "fmt"

func swap(x, y string) (string, string) {
    return y, x
}

func main() {
    a, b := swap("hello", "world")
    fmt.Println(a, b)
}

这里写图片描述
上面实现了简单的字符串交换功能,代码实现上十分的简洁,因为支持多返回值,所以不需要想Java需要构建一个可以保存多个值得数据结构。
而且可以发现,对于返回值如果是同一类型,可以不定义变量名称,虽然代码看上去是简洁了很多,但是命名后的返回值可以让代码更清晰,可读性更强。

如果调用方调用了一个具有多返回值的方法,但是却不想关心其中的某个返回值,可以简单 地用一个下划线“_”来跳过这个返回值。就像上面的例子,如果我们只关注第一个返回值则可以写成:

a, _ := swap("hello", "world")

若值关注第二返回值则可以写成:

_, b := swap("hello", "world")

函数参数

函数定义时指出,函数定义时有参数,该变量可称为函数的形参。形参就像定义在函数体内的局部变量。
但当调用函数,传递过来的变量就是函数的实参,函数可以通过两种方式来传递参数:

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

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

值传递

Go中int类型保存的的是一个数字类型,下面定义一个交换函数swap(),用于交换两个参数的值。

package main

import "fmt"

func main() {

    var a int = 100
    var b int = 200

    fmt.Printf("交换前 a 的值为 : %d\n", a)
    fmt.Printf("交换前 b 的值为 : %d\n", b)

    /* 通过调用函数来交换值 */
    swap(a, b)

    fmt.Printf("交换后 a 的值 : %d\n", a)
    fmt.Printf("交换后 b 的值 : %d\n", b)
}

/* 定义相互交换值的函数 */
func swap(x, y int) int {
    var temp int

    temp = x /* 保存 x 的值 */
    x = y    /* 将 y 值赋给 x */
    y = temp /* 将 temp 值赋给 y*/

    return temp
}

这里写图片描述

引用传递

通过前面的介绍可以知道,Go的指针类型是对变量地址的引用。
将上面的swap()做些修改,参数接受两个指针类型,然后做交换。

package main

import "fmt"

func main() {

    var a int = 100
    var b int = 200

    fmt.Printf("交换前 a 的值为 : %d\n", a)
    fmt.Printf("交换前 b 的值为 : %d\n", b)

    /* 调用 swap() 函数
     * &a 指向 a 指针,a 变量的地址
     * &b 指向 b 指针,b 变量的地址
     */
    swap(&a, &b)

    fmt.Printf("交换后 a 的值 : %d\n", a)
    fmt.Printf("交换后 b 的值 : %d\n", b)
}

/* 定义相互交换值的函数 */
func swap(x, y *int) {
    var temp int

    temp = *x /* 保存 x 的值 */
    *x = *y   /* 将 y 值赋给 x */
    *y = temp /* 将 temp 值赋给 y*/

}

这里写图片描述
可以发现,最终传进来的参数指在执行交换函数swap()后也被修改了,这是因为参数最终指向的都是地址的引用,所有引用被修改了,值也就相应的变了。

不定参数

顾名思义,不定参数就是函数的参数不是固定的。这个在C和Java里都有。在之前的代码中,包fmt下面的 fmt.Println()函数也是参数不定的。

不定参数类型

先看一个函数的定义:

func myfunc(args ...int) {
}

可以看出,上面的定义和之前的函数定义最大的不同就是,他的参数是以“…type”的方式定义的,这和Java的语法有些类似,也是用”…”实现。需要说明的是“…type”在Go中只能作为参数的形式出现,而且只能作为函数的最后一个参数。
从内部实现机理上来说,类型“…type“本质上是一个数组切片,也就是[]type,这点可以从下面的一个小程序验证一下。

package main

import "fmt"

func myfunc(args ...int){
    fmt.Println(args)

    for arg := range args {
        fmt.Println(arg)
    }
}


func main() {
    a,b,c,d,e := 1,2,3,4,5
    myfunc(a,b,c,d,e)   
}

这里写图片描述

从上面的结果可以看出,类型“…type“本质上是一个数组切片,也就是[]type,所以参数args可以用for循环来获得每个传入的参数。
这是Go的一 个语法糖(syntactic sugar),即这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说,使用语法糖能够增加程序的可读性,从而减少程序出错的机会。

不定参数的传递

同样是上面的myfunc(args …int)函数为例,在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可。

package main

import "fmt"

func myfunc(args ...int){
    fmt.Println(args)

    for arg := range args {
        fmt.Println(arg)
    }
}


func main() {
    arr := []int{100, 200, 300}

    myfunc(arr...)
    myfunc(arr[:2]...)
}

这里写图片描述

任意类型的不定参数

上面的例子在定义不定参数时,都有一个要求,参数的类型是一致的。那么如果函数的参数类型不一致,如何使用不定参数方式来定义。在Go中,要实现这个需求需要引入一个新的类型–interface{}。看名字可以看出,这种类型实际上就是接口。关于Go的接口这里只做引出。
看一下之前常用的Printf函数的定义,位置在Go的src目录下的print.go文件中。

func Println(a ...interface{}) (n int, err error) {
    return Fprintln(os.Stdout, a...)
}

其实用interface{}传递任意类型数据是Go语言的惯例用法,而且interface{}是类型安全的。
看下面的例子:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    arr := []int{100, 200, 300}
    myfunc(100, "abc", arr)

}

func myfunc(args ...interface{}) {
    fmt.Println(args)
    for _, arg := range args {
        fmt.Println(arg)
        fmt.Println(reflect.TypeOf(arg))
        fmt.Println("=======")
    }
}

这里写图片描述

匿名函数

匿名函数是指不需要定义函数名的一种函数实现方式。1958年LISP首先采用匿名函数
在Go里面,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。
匿名函数由一个不带函数名的函数声明和函数体组成。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
直接看一个例子:

package main

import (
    "fmt"
    "math"
)

func main() {
    getSqrt := func(a float64) float64 {
        return math.Sqrt(a)
    }
    fmt.Printf(" getSqrt(%d) = %f\n",4,getSqrt(4))

}

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

参考:
http://blog.csdn.net/mungo/article/details/52481285

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值