Golang函数

函数

1.函数定义

样例代码如下:
package mymath
import "errors"

func Add(a int, b int) (ret int, err error) { 
 if a < 0 || b < 0 { // 假设这个函数只支持两个非负数字的加法
 err = errors.New("Should be non-negative numbers!") 
 return 
 } 
 return a + b, nil // 支持多重返回值
}
如果参数列表中若干个相邻的参数类型的相同,比如上面例子中的a和b,则可以在参数列表中省略前面变量的类型声明,如下所示:
func Add(a, b int)(ret int, err error) { 
 // ... 
}
如果返回值列表中多个返回值的类型相同,也可以用同样的方式合并。如果函数只有一个返回值,也可以这么写:
func Add(a, b int) int { 
 // ... 
}

2.函数调用

函数调用非常方便,只要事先导入了该函数所在的包,就可以直接按照如下所示的方式调用函数:
import "mymath"// 假设Add被放在一个叫mymath的包中
 // ... 
c := mymath.Add(1, 2)

注意:小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。这个规则也适用于类型和变量的可见性。


3.不定参数

出现这里的原因是因为,就是Go它是不支持函数的重载的,所以是不能写同名但是参数不同的函数的,所以要用到不定参数

1.不定参数类型

不定参数是指函数传入的参数个数为不定数量。为了做到这点,首先需要将函数定义为接受不定参数类型:
func myfunc(args ...int) { 
 for _, arg := range args { 
 fmt.Println(arg) 
 } 
}

这段代码的意思是,函数myfunc()接受不定数量的参数,这些参数的类型全部是int,所以它可以用如下方式调用:

myfunc(2, 3, 4) 
myfunc(1, 3, 7, 13)

注意: 形如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数。

从内部实现机理上来说,类型...type本质上是一个数组切片,也就是[]type,这也是为什么上面的参数args可以用for循环来获得每个传入的参数。

假如没有...type这样的语法,上面的代码将这么写
func myfunc2(args []int) { 
 for _, arg := range args { 
 fmt.Println(arg) 
 } 
}

调用就会这样调用:

myfunc2([]int {1,3,5,7})

2.不定参数的传递

假设有另一个变参函数叫做myfunc3(args ...int),下面的例子演示了如何向其传递变参:
func myfunc(args ...int) { 
 // 按原样传递
 myfunc3(args...) 
 // 传递片段,实际上任意的int slice都可以传进去
 myfunc3(args[1:]...) 
}

3.任意类型的不定参数

之前的例子中将不定参数类型约束为int,如果希望传任意类型,可以指定类型为interface{}。下面是Go语言标准库中fmt.Printf()的函数原型:
func Printf(format string, args ...interface{}) { 
 // ... 
} 

用interface{ }传递任意类型数据是Go语言的惯例用法。使用interface{}仍然是类型安全的,这和 C++ 不太一样。

import "fmt" 
func MyPrintf(args ...interface{}) { 
 for _, arg := range args { 
 switch arg.(type) { //家人们,这个***.(type)只能在switch的外部使用!!!
 case int: 
 fmt.Println(arg, "is an int value.") 
 case string: 
 fmt.Println(arg, "is a string value.") 
 case int64: 
 fmt.Println(arg, "is an int64 value.") 
 default: 
 fmt.Println(arg, "is an unknown type.") 
 } 
 } 
} 
func main() { 
 var v1 int = 1 
 var v2 int64 = 234 
 var v3 string = "hello" 
 var v4 float32 = 1.234
 MyPrintf(v1, v2, v3, v4) 
}

该程序的输出结果为:

1 is an int value. 
234 is an int64 value. 
hello is a string value. 
1.234 is an unknown type.

4.多返回值

Go语言的函数或者成员的方法可以有多个返回值,这个特性能够使我们写出比其他语言更优雅、更简洁的代码

可以同时返回读取的字节数和错误信息。如果读取文件成功,则返回值中的n为读取的字节数,err为nil,否则err为具体的出错信息:
func (file *File) Read(b []byte) (n int, err Error) 

同样,从上面的方法原型可以看到,我们还可以给返回值命名,就像函数的输入参数一样。返回值被命名之后,它们的值在函数开始的时候被自动初始化为空。在函数中执行不带任何参数的return语句时,会返回对应的返回值变量的值。


5.匿名函数与闭包

匿名函数是指不需要定义函数名的一种函数实现方式

1.匿名函数

匿名函数由一个不带函数名的函数声明和函数体组成,如下所示:
func(a, b int, z float64) bool { 
 return a*b <int(z) 
}
匿名函数可以直接赋值给一个变量或者直接执行:
f := func(x, y int) int { 
 return x + y 
} 
func(ch chan int) { 
 ch <- ACK 
} (reply_chan) // 花括号后直接跟参数列表表示函数调用

2.闭包

基本概念

闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)。

闭包的价值

闭包的价值在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,就是说这些函数可以存储到变量中作为参数传递给其他函数,最重要的是能够被函数动态创建和返回。

Go语言中的闭包

Go语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在,如代码所示:

 var j int = 5 
 a := func()(func()) { 
 var i int = 10 
 return func() { 
 fmt.Printf("i, j: %d, %d\n", i, j) 
 } 
 }() 
 a() 
 j *= 2 
 a() 
}
上述例子的执行结果是:
i, j: 10, 5 
i, j: 10, 10

在上面的例子中,变量a指向的闭包函数引用了局部变量i和j,i的值被隔离,在闭包外不能被修改,改变j的值以后,再次调用a,发现结果是修改过的值。在变量a指向的闭包函数中,只有内部的匿名函数才能访问变量i,而无法通过其他途径访问到,因此保证了i的安全性。


6.一些题外话

众所周知,一般的实参传入函数之中是无法影响实参本来的值的,例如:
func add(a,b int)(err error,result int){
    if a<0||b<0{
        err = error.New("Should be non-negative numbers!")
        return //会默认返回error和result,error就是那一句话,因为result没有指定别的值,默认为0 
    }
    return nil,a+b
}
func main(){
    a,b := 10,20
    err,result := add(a,b)
    fmt.println(err,"\n",result)
    c,d := -1,-2
    err,result := add(c,d)
    fmt.println(err,"\n",result)
    fmt.println(a,b,c,d)
}
上述代码的执行结果是:
<nil>
30

Should be non-negative numbers!
0

1020-1-2
可以看到,在main栈中的abcd并不受函数的影响,如果我们想改变main中的abcd的值,我们可以选择传入参数的地址,比如:
func change(num *int){ //参数的类型为指针
    *num = 30
}
func main(){
    var num int = 10
    change(&num)       //调用的change函数时传入的是num的地址
    fmt.println(num)
}
以上代码的输出结果如下:
30

这样就可以改变原栈帧的变量了(其实主要是存放的地址固定是这个,我直接改变对应地址存放内容的值了)


7.继续补充(看网课学的)

1.

函数其实也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了,可以通过该变量对函数进行函数调用,代码如下:
func test(num int){
    fmt.println(num)
}
func test01(num int,str string){

}
func main(){
    a := test //变量就是一个函数类型的变量
    //a的类型是func(int),test函数类型是func(int)

    b := test01 //b的类型就是func(int,string)

    //通过该变量可以对函数进行调用
    a(10) //等价于test(10)
}
最后的输出也可以说明:
10

2.

既然函数是一种数据类型,因此在Go中函数也可以作为形参,并且调用:
//定义一个函数,把另一个函数作为形参
func test(num int){
    fmt.println(num)
}
func test02(num1 int,num2 float32,testFunc func(int)){//注意看这里的testFunc函数的类型,与上面那段代码提到的是一样的
    fmt.println("test02")
}
func main(){
    a := test
    test02(10,3.1,test)
    test02(10,3.1,a)
}
以上代码的输出为:
test02
test02

说明调用成功,且a的类型就是func(int)


3.

Go语言支持自定义类型

基本语法:type 自定义数据名 数据类型
具体代码如下:
// 自定义数据类型:(相当于起别名):给int类型取别名为myInt类型
type myInt int
var num1 myInt = 30
fmt.println(num1)
以上代码的输出结果为:
30

注意:

虽然是起别名,但是在go中编译识别的时候还是认为myInt和int不是同一种类型

以下代码就会报错:
type myInt int
var num1 myInt
var num2 int
num2 = num1

如果想要不报错,就要交num1转换为int类型,可以用到函数int(num1)

同理,函数也可以取别名:
type myFunc func(int)
//那么第二小节的test02代码就可以写成
func test02(num1 int,num2 float32,myFunc func(int)){
    //可以写成这样
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值