函数

现代计算机的进程执行模型大部分是基于“堆栈”的,编译器不需要对函数做过多的转换就能让其在栈上运行

2.1 基本概念

2.1.1 函数定义

一个函数的定义包含如下几个部分: 函数声明关键字func、函数名、参数列表、返回列表和函数体。

首字母的大小写决定该函数在其他包的可见:大写时其他包可见,小写时只有相同的包可以访问。

  1. 函数可以没有输入参数、也可以没有返回值(默认返回0)

    1func A(){
    2}
    3
    4func B() int{
    5  return 1
    6}
    
  2. 多个相同类型的参数可以使用简写模式

    1func add(a,b int) int{ // a int, b int 简写为 a,b int
    2  return a+b
    3}
    
  3. 支持有名的返回值,参数名相当于函数体最外层的局部变量,命名返回值变量会被初始化为类型零值最后的return可以不带参数名直接返回。

    1func add(a,b int) (sum int){ // sum 相当于函数内部的局部变量,被初始化为0
    2  sum = a+b
    3  return   // return sum 的简写模式
    4
    5  // sum := a+b  
    6  // return sum // 需要显式的调用return sum
    7}
    
  4. 不支持默认值参数??

  5. 不支持函数重载

  6. 不支持函数嵌套,严格的说不支持命名函数的嵌套定义,但支持匿名函数

    1func add(a, b int) (sum int){
    2  anonymous := func(x,y int) int{
    3	return x+y
    4  }
    5
    6  return anonymous(a,b)
    7}
    

2.1.2 多值返回

定义多值返回的返回参数列表时候要使用“()”,支持命名参数的返回

1func swap(a,b int) (int, int){
2  return b,a
3}

习惯用法:

如果多值返回有错误类型,则一般将错误类型作为最后一个返回值

2.1.2 实参到形参的传递

Go 函数实参到形参的传递永远是值拷贝,有时候函数调用后,实参指向的值发生了变化,那是因为参数传递的是指针值的拷贝,实参是一个指针变量,传递给形参的是这个指针变量的副本,二者指向同一个地址,本质上参数传递仍然是值拷贝。

 1package main
 2
 3import "fmt"
 4
 5func chvalue(a int) int { // 实参传递给形参的是值拷贝
 6	a = a + 1
 7	return a
 8}
 9
10func chpointer(a *int) { // 实参传递给形参的仍然是只拷贝,只不过复制的是a的地址
11	*a = *a + 1
12	return
13}
14func main() {
15	a := 10
16
17	chvalue(a)
18	fmt.Println(a)
19
20	chpointer(&a)
21	fmt.Println(a)
22}

2.1.4 不定参数

Go 函数支持不定数目的形式参数,不定参数声明使用param …type的语法格式

  1. 所有的不定参数类型必须是相同的

  2. 不定参数必须是函数的最后一个参数

  3. 不定参数名在函数体重相当于切片,对切片的操作同样适合不定参数的操作

    1func sum(arr ...int)(sum int){
    2  for _,v := range arr{  // 此时arr 就相当于切片,可以使用range访问
    3    sum +=v
    4  }
    5  return 
    6}
    
  4. 切片可以作为参数传递给不定参数,切片名后面要加上“…”

     1func sum(arr ...int) (sum int){
     2  for _,v := range arr{
     3    sum += v
     4  }
     5  return 
     6}
     7
     8func main(){
     9  slice :=[]{1,2,3,4}
    10  array := [...]{1,2,3,4}
    11  // 数组不可以作为实参传递给不定参数的函数
    12  sum(slice...)
    13}
    

    注意 slice 和 array定义的区别

  5. 形参为不定参数的函数和为切片的函数类型不相同

     1func suma(arr ...int)(sum int){
     2	for _,v := range arr{
     3		sum +=v
     4	}
     5	return
     6}
     7
     8func sumb(arr [4]int) (sum int) {
     9	for _,v := range arr{
    10		sum +=v
    11	}
    12	return
    13}
    14
    15func main() {
    16	slice := []int{2,3,4,5}
    17	array := [4]int{2,3,4,5}
    18
    19	println(suma(slice...))
    20	println(sumb(array))
    21}
    22
    23// suma 和sumb 不一样
    

2.2 函数签名和匿名函数

2.2.1 函数签名

函数类型又叫做函数签名,一个函数的类型就是函数定义首行去掉函数名,参数名和{,可以使用fmt.Printf%T格式化参数打印函数的类型

1func add(a,b int )(sum int){
2	sum = a +b
3	return sum
4}
5
6func main() {
7	fmt.Printf(" func type is:%T",add )  //  func type is:func(int, int) int
8}

两个函数类型相同的条件是: 拥有相同的形参列表和返回值列表(列表元素的次序,个数和类型都相同),形参名可以不同。一下两个函数类型是完全一样的

1func add(a,b int) int{ return a+b}
2func sub(x int, y int)(z int){z = x-y; return z}

可以使用type定义函数类型,函数类型变量可以作为函数的参数或返回值。

 1package main
 2
 3import "fmt"
 4
 5
 6func add(a,b int )(sum int){
 7	sum = a +b
 8	return sum
 9}
10
11func sub(a,b int) int  {
12	return b-a
13}
14
15type Op func(int, int) int // 定义一个函数类型,输入是两个int,返回值是一个int类型
16
17
18func do(f Op, a,b int) int{ // 定义一个函数,第一个参数是函数类型Op
19	return f(a,b) // 函数类型变量可以直接用来进行函数调用
20}
21
22
23func main() {
24	a := do(add, 1,2)  
25	fmt.Println(a) // 3
26
27	s := do(sub, 1,2) // 1
28	fmt.Println(s)
29}

函数类型和map、slice、chan一样,实际函数类型变量和函数名都可以当做指针变量,该指针指向函数代码的开始位置。通常说函数变量类型是一种引用类型,未初始化的函数类型的变量默认是nil

Go 中函数是“第一公民(first class)”。有名函数的函数名可以看作函数类型的常量,可以直接使用函数名调用函数。也可以直接赋值给函数类型变量,后续通过该变量来调用该函数。

1func sum(a,b int) int{
2  return a+b
3}
4func main(){
5  sum(3,4)  // 直接调用
6  f := sum  // 有名函数可以直接赋值给变量
7  f(1,2)
8}

2.2.2 匿名函数

https://zhuanlan.zhihu.com/p/71829933

https://eddycjy.com/posts/go/go1.16-3/

https://segmentfault.com/a/1190000037679588