函数
前言
在Go中,函数时一等公民,函数是Go最基础的组成成分,但是它也是Go的核心内容,比如启动函数main:
package main import "fmt" func main() { var n,value int a:=make([]int,n) Scanf(&n,&value) for i:=0;i<n;i++: Scanf(&a[i]) }
函数的声明
函数的声明格式如下:
func 函数名(参数列表) 返回值类型(){ 函数体 }
函数可以总结通过关键字func
来声明,也可以说明为一个字面量或者作为一个类型:
//直接声明 func Dosomething(){ } //字面量声明 var Dosomething func() //类型声明 type Dosomething func()
函数签名由函数名称,参数列表,返回值组成,下面是一个完整的例子
func Sum(a, b int) int { return a + b }
函数名称Sum
,有两个int
类型的参数a
,b
,返回值类型为int
。
备注:在Go中不支持函数重载
函数的参数
Go中的参数可以一名也可以没有,只不过在赋值时依旧需要名称
var sum func(int,int,int) int sum=func (inr a,int b,int c) int{ return a+b+c }
而对于一些参数类型相同且接近的参数而言,可以只声明一次类型,如下:
func sum(a,b,c int) int{ return a+b+c }
我们还可以使用变长参数来,不过变长参数要声明在参数列表的末尾
package main import ( "fmt" "math" "math/rand" _"os" ) func max(arg ... int) int{ max:=math.MinInt32 for _, v:=range arg{ if v>max{ max=v } } return max } func main(){ var n int fmt.Scanf("%d",&n) a:=make([]int,n) for i:=0;i<n;i++{ a[i]=rand.Intn(1000) } fmt.Println(max(a...)) }
注意:Go中函数参数是传值传递,我们在传递菜蔬的同时会拷贝参数的值
函数的返回值
在Go中函数的返回值有很多类型,下面给大家依次介绍一下
- 首先正常的返回值形式基本与其他语言相差无几,我们来看一下下面这个小demo:
func sum(a,b int) int{ return a+b }
- 在Go中允许函数拥有多个返回值,但是我们需要给返回值加上括号,如下:
package main import "fmt" func Div (a,b float64) (float64, error){ if b == 0.0 { return 0, fmt.Errorf("除数不能为0") } return a / b, nil } func main() { var a,b float64 fmt.Scan(&a,&b) ans,err:=Div(a,b) if err==nil{ fmt.Println(ans) }else{ fmt.Println(err) } }
- 如果返回值有名称,也需要加上括号。
func Sum(a, b int) (ans int) { return a + b }
- 也可以如下,不管返回值是否命名,优先级最高的永远都是
return
关键字后跟随的值。
func Sum(a, b int) (ans int) { ans = a + b return // 等价于 return ans }
匿名函数
匿名函数只能在函数内部存在,匿名函数可以简单理解为没有名称的函数,例如
func main() { func(a, b int) int { return a + b }(1, 2) }
或者当函数参数是一个函数类型时,这时名称不再重要,可以直接传递一个匿名函数
func main() { DoSum(1, 2, func(a int, b int) int { return a + b }) } func DoSum(a, b int, f func(int, int) int) int { return f(a, b) }
延迟调用
defer的使用
defer
关键字描述的一个匿名函数会在函数返回前指行:
func main() { Do() } func Do() { defer func() { fmt.Println("1") }() fmt.Println("2") }
但是如果有多个defer语句的时候,匿名函数的执行顺序是从前往后吗?答案是否定的,在Go语言中,如果遇见了defer关键字,该匿名函数就会被延迟调用,当有多个匿名函数被延迟调用的时候应该是什么数据结构来储存呢,答案是延迟调用栈,根据栈的性质,我们可以很简单的推断除延迟函数的调用顺序应该是后进先出,接下来我们来看一下下面的这个代码:
func main() { Do() } func Do() { defer func() { fmt.Println("1") }() defer func() { fmt.Println("2") }() defer func() { fmt.Println("3") }() defer func() { fmt.Println("4") }() fmt.Println("2") defer func() { fmt.Println("5") }() }
它的输出为:
2 5 4 3 2 1
备注:延迟调用通常用于释放文件资源,关闭连接等操作,另一个常用的写法是用于捕获panic
方法
前言
方法与函数的区别在于,方法有接受者,而函数没有,但是只有自定义类型才能拥有方法,我们来看一下下面这个简单的示例:
package main import "fmt" type List []int func (i List) Get(index int) int{ return i[index] } func (i List) Set(value,index int){ i[index]=value } func (i List) Len() int{ return len(i) }
先声明了一个类型List
,其底层类型为[]int
,再声明了三个方法Get
,Set
和Len
,方法的长相与函数并无太大的区别,只是多了一小段(i List)
。i
就是接收者,List
就是接收者的类型,接收者就类似于其他语言中的this
或self
,只不过在Go中需要显示的指明。
package main import "fmt" type myList []int func (i myList) Get(index int) int{ return i[index] } func (i myList) Set(value,index int){ i[index]=value } func (i myList) Len() int{ return len(i) } func main(){ var my_List myList =[]int{1,2,3,4,5} fmt.Println(my_List.Get(2)) my_List.Set(100,2) fmt.Println(my_List.Get(2)) }
方法的使用就类似于调用一个类的成员方法,先声明,再初始化,再调用。
值接收者与指针接收者
值接收者
接收者也分两种类型,值接收者和指针接收者,先看一个例子
type MyInt int func (i MyInt) Set(val int) { i = MyInt(val) // 修改了,但是不会造成任何影响 } func main() { myInt := MyInt(1) myInt.Set(2) fmt.Println(myInt) }
上述代码运行过后,会发现myInt
的值依旧是1,并没有被修改成2。方法在被调用时,会将接收者的值传入方法中,上例的接收者就是一个值接收者,可以简单的看成一个形参,而修改一个形参的值,并不会对方法外的值造成任何影响,那么如果通过指针调用会如何呢?
func main() { myInt := MyInt(1) (&myInt).Set(2) fmt.Println(myInt) }
遗憾的是,这样的代码依旧不能修改内部的值,为了能够匹配上接收者的类型,Go会将其解引用,解释为(*(&myInt)).Set(2)
。
指针接收者
稍微修改了一下,就能正常修改myInt
的值。
type MyInt int func (i *MyInt) Set(val int) { *i = MyInt(val) } func main() { myInt := MyInt(1) myInt.Set(2) fmt.Println(myInt) }
现在的接收者就是一个指针接收者,虽然myInt
是一个值类型,在通过值类型调用指针接收者的方法时,Go会将其解释为(&myint).Set(2)
。所以方法的接收者为指针时,不管调用者是不是指针,都可以修改内部的值。
总结
函数的参数传递过程中,是值拷贝的,如果传递的是一个整型,那就拷贝这个整型,如果是一个切片,那就拷贝这个切片,但如果是一个指针,就只需要拷贝这个指针,显然传递一个指针比起传递一个切片所消耗的资源更小,接收者也不例外,值接收者和指针接收者也是同样的道理。在大多数情况下,都推荐使用指针接收者,不过两者并不应该混合使用,要么都用,要么就都不用。
尾语
到这为止,有关于Go语言的基础语法就结束了,从明天开始更新的就是一些涉及到一定技巧的东西了,比如说Go语言的面向对象我们应该如何去实现,Go语言的并发编程等等,同时我也会写有关于Go语言有关于树,链表等数据结构的学习以及刷题笔记,博主目前只是一个大二的学生,说实话视野有限。写这个文章也仅仅为了去记录一些自己学习的知识,如果大家发现文章中有什么错误欢迎斧正,也希望看到照片文章的大佬都可以留下宝贵的建议。唐代药王孙思邈在《备急千金要方·大医精诚》中说:“世有愚者,读方三年,便谓天下无病可治;及治病三年,乃知天下无方可用。谨以此言与君共勉(当然各位高抬贵手给个三连!!!!)