Golang 函数基本形式

函数的意义是少出现代码冗余和便利代码的维护,其实也就是模块化编程。

为完成某一个功能的程序指令(语句)的集合,称为函数。

在go中函数分为自定义函数和系统函数。 

函数是⼀等公⺠


与其他主要编程语⾔的差异

1. 可以有多个返回值
2. 所有参数都是值传递: slice map channel 会有传引⽤的错觉
(go语言全部都是传值,切片底层对应的是数组,切片本身是一个数据结构,在这个数据结构里面包含了指向数组的指针,即便是传值,这个结构被复制到函数里面了,在操作指针指向具体值的时候其实操作的是同一块空间,这就会有一种传引用的错觉,其实是结构被复制了,结构里面的指针指向的依然是同一个后端的数组,所以才有这个错觉)
3. 函数可以作为变量的值
4. 函数可以作为参数和返回值
func timeSpent(inner func(int) int) func(int) int {
	return func(n int) int {
		start := time.Now()
		//调用函数
		ret := inner(n)
		fmt.Println("time spent:", time.Since(start).Seconds())
		return ret
	}
}


func slowFun(op int) int {
	time.Sleep(time.Second * 3)
	return op
}


func main() {
	t := timeSpent(slowFun)
	fmt.Println(t(10))

}

函数的基本形式


//函数定义。a,b是形参
func argf(a int, b int) { 
	a = a + b 
}

var x, y int = 3, 6
argf(x, y) //函数调用。x,y是实参
  • 形参是函数内部的局部变量,实参的值会拷贝给形参。
  • 函数定义时的第一个的大括号不能另起一行。
  • 形参可以有0个或多个。
  • 参数类型相同时可以只写一次,比如argf(a,b int)。
  • 在函数内部修改形参的值,实参的值不受影响。

如果想通过函数修改实参,就需要指针类型。 

func argf(a, b *int) { 
    *a = *a + *b
    *b = 888
}
var x, y int = 3, 6
argf(&x, &y)

传引用和传引用的指针


slice、map、channel都是引用类型,它们作为函数参数时其实跟普通struct没什么区别,都是对struct内部的各个字段做一次拷贝传到函数内部。(go 语言里面只有一条原则,只要是函数传递

参,都是传的拷贝)

比如slice作为函数参数,slice就是一个结构体,把这个结构体作为参数传递给函数,就是对结构体里面的每一个成员变量都进行一次拷贝。只不过结构体的成员变量包含指针。 

下面可以看到修改的是底层的数组,可以影响到形参,但是append会影响,生成新的切片,那么就和原来的切片没有关系了。 

package main

import "fmt"

func slice_arg_1(arr []int) { //slice作为参数,实际上是把slice的arrayPointer、len、cap拷贝了一份传进来
	arr[0] = 1           //修改底层数据里的首元素
	arr = append(arr, 1) //arr的len和cap发生了变化,不会影响实参
}

func main() {
	arr := []int{8}
	slice_arg_1(arr)
	fmt.Println(arr[0])   //1
	fmt.Println(len(arr)) //1
}

关于函数返回值


  • 可以返回0个或多个参数。
  • 可以在func行直接声明要返回的变量。
  • return后面的语句不会执行。
  • 无返回参数时return可以不写。

不定长参数


Go 语言中的可变长参数允许调用方传递任意多个相同类型的参数

函数定义
func append(slice []Type, elems ...Type) []Type
调用方法
myArray := []string{}
myArray = append(myArray, "a","b","c")
func variable_ength_arg(a int, other ...int) int { 
    sum := a
    for _, ele := range other {//不定长参数实际上是slice类型
        sum += ele
    }
    fmt.Printf("len %d cap %d\n", len(other), cap(other))
    return sum
}
variable_ength_arg(1)
variable_ength_arg(1,2,3,4)

append函数接收的就是不定长参数。 在很多场景下string都隐式的转换成了byte切片,而非rune切片,比如"a中"[1]是228而非"中"。

arr = append(arr, 1, 2, 3)
arr = append(arr, 7)
arr = append(arr)


slice := append([]byte("hello "), "world"...) 
//...自动把"world"转成byte切片,等价于[]byte("world")...


slice2 := append([]rune("hello "), []rune("world")...) 
//需要显式把"world"转成rune切片
func test(slice ...int){
	slice = append(slice,1)
	fmt.Println(slice,len(slice),cap(slice))
    fmt.Printf("%p\n",slice)
}

func main()  {
	slice1 := make([]int,6,6)
	test(slice1...)
	fmt.Printf("%p\n",slice1)
	fmt.Println(slice1)
}


[0 0 0 0 0 0 1] 7 12
0xc000060060
0xc0000160f0
[0 0 0 0 0 0]

切片本质是结构体,本质上是对len改变了,改变len是改变了拷贝,并不影响原先那个,但是底层数组确实发生了改变。👇 

	x := make([]int,2,4)
    fmt.Println(len(x),cap(x),x)
	fmt.Printf("%p\n",x)
	update(x)


func update(slice []int){
	slice[0] = 0
	slice[1] = 1
	slice = append(slice,3,4)
	fmt.Println(cap(slice),len(slice),slice)
	fmt.Printf("%p",slice)
}



2 4 [0 0]
0xc0000b8020

4 4 [0 1 3 4]
0xc0000b8020

Main 函数


每个 Go 语言程序都应该有个 main package
Main package 里的 main 函数是 Go 语言程序入口

go语言里面最重要的就是main函数,每个程序都需要有个程序入口,你要告诉底层的编译器说从何编译,然后运行程序的时候main函数告诉底层的编译器,如何编译,运行程序的时候先执行哪个方法,go语言做了一定的约定,任何的go语言程序首先要有一个main package,main package里面的main方法就是go语言的程序入口。

在编译的时候发现有一个main函数,那么就会构建一个可执行文件去运行这个程序。

package main
func main() {
    args := os.Args
    if len(args) != 0 {
    println("Do not accept any argument")
    os.Exit(1) }
    println("Hello world") 
}

参数解析


请注意 main 函数与其他语言不同,没有类似 java 的 []string args 参数
Go 语言如何传入参数呢?
方法1:
             l fmt.Println("os args is:", os.Args)
方法2:
             l name := flag.String("name", "world", "specify the name you want to say hi")
             l flag.Parse()

运行任何程序的时候,都需要给其一些参数,很多时候需要给程序一些入参,

init函数


 有时候,在做程序运行之前,需要有一些初始化的动作,要给一些变量赋值,或者有预先要去运行的程序要去跑,这个时候就需要做一些初始化的操作。

init函数和main函数一样,也是一种特殊的函数,init函数会在main函数之前运行。

首先程序的入口是main方法,在main包去运行main方法的时候,会去做一系列的事情,首先会去看import了哪些包,如果发现main package引用了package1,那么程序就会跳转到package1,去做package1的初始化,也就是一个包有针对其他包依赖的话,先要跳转到其他包的初始化,直到最后一个包里面,最后一个包init方法结束了之后,回到引用的包还是常量变量初始化。

main方法当中还是以同样的顺序去初始化常量,变量,以及执行init方法,最后才执行main。

包中含有init方法,包被多个包所依赖,那么init方法只会被执行一次。 

package main

import (
	"fmt"
	_ "github.com/cncamp/golang/examples/module1/init/a"
	_ "github.com/cncamp/golang/examples/module1/init/b"
)

func init()  {
	fmt.Println("main init")
}

func main()  {
fmt.Println("main function")
}

init from b
init from a
main init
main function

返回值


多值返回
                 • 函数可以返回任意数量的返回值
命名返回值
                • Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。
                • 返回值的名称应当具有一定的意义,它可以作为文档使用。
                • 没有参数的 return 语句返回已命名的返回值。也就是直接返回。
调用者忽略部分返回值
                result, _ = strconv.Atoi(origStr)

内置函数


go语言提供了内置的函数,go语言保留的关键字其实非常的少,它的内置函数也不多,如上所示。

回调函数(Callback)


函数本身可以是变量,那么这个函数可以作为参数传给另外一个函数,在另外一个函数里面去调用这个参数函数。上面可以看到可以将函数名字作为变量传到了行参。

所谓的回调函数就是在函数体里面,入参传了一个函数进来,然后在函数体里面调用了这个被传进来的参数。

闭包


匿名函数,没有名字,可以在定义的时候就直接运行了。

什么场景下会使用匿名函数呢?也就是闭包。一个最常用的场景就是程序运行出现错误的时候,我们要去恢复,一般是通过关键字defer,defer在函数退出的时候执行,我们要有一个程序逻辑,在某些特定场景下运行的,但是我没有必要为整个逻辑定义函数,通常这样就可以使用闭包来做。

传值还是传指针


Go 语言只有一种规则-传值
函数内修改参数的值不会影响函数外原始变量的值
可以传递指针参数将变量地址传递给调用函数,Go 语言会复制该指针作为函数内的地址,但指向同一地址

go语言在传递指针参数的时候,在go语言函数内部,它一样会复制这个指针地址,这两个变量指向的是同一个内存地址,所以在函数体里面去修改指针变量的时候,它其实是修改了那块内存。

从函数体外面来看就感觉那个变量发生了变化。

思考:当我们写代码的时候,函数的参数传递应该用 struct还是 pointer?

这个需要分场景了,如果是使用指针,好处是传递的是指针地址,就涉及不到一些值的拷贝。从效率上来看这种情况可能会高一些,但是如果你传递的是struct,那么这个struct传递到函数体里面以后,这个函数一退出,之前的那些临时变量就立马可以被回收了。它对GC更加友好一些。 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值