在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
关键字描述的一个匿名函数会在函数返回前指行:
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语言有关于树,链表等数据结构的学习以及刷题笔记,博主目前只是一个大二的学生,说实话视野有限。写这个文章也仅仅为了去记录一些自己学习的知识,如果大家发现文章中有什么错误欢迎斧正,也希望看到照片文章的大佬都可以留下宝贵的建议。唐代药王孙思邈在《备急千金要方·大医精诚》中说:“世有愚者,读方三年,便谓天下无病可治;及治病三年,乃知天下无方可用。谨以此言与君共勉(当然各位高抬贵手给个三连!!!!)