0
点赞
收藏
分享

微信扫一扫

Go 函数基本形式


函数的基本形式

//函数定义。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函数

Go 函数基本形式_go语言

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

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语言_02

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


回调函数(Callback)

Go 函数基本形式_golang_03

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

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


闭包

Go 函数基本形式_函数体_04

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

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


传值还是传指针

• Go 语言只有一种规则-传值


• 函数内修改参数的值不会影响函数外原始变量的值


• 可以传递指针参数将变量地址传递给调用函数,Go 语言会复制该指针作为函数内的地址,但指向同一地址



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

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

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

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

举报

相关推荐

0 条评论