go基础语法结束篇 ——函数与方法

简介: go基础语法结束篇 ——函数与方法

函数

前言

在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类型的参数ab,返回值类型为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,再声明了三个方法GetSetLen,方法的长相与函数并无太大的区别,只是多了一小段(i List)i就是接收者,List就是接收者的类型,接收者就类似于其他语言中的thisself,只不过在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语言有关于树,链表等数据结构的学习以及刷题笔记,博主目前只是一个大二的学生,说实话视野有限。写这个文章也仅仅为了去记录一些自己学习的知识,如果大家发现文章中有什么错误欢迎斧正,也希望看到照片文章的大佬都可以留下宝贵的建议。唐代药王孙思邈在《备急千金要方·大医精诚》中说:“世有愚者,读方三年,便谓天下无病可治;及治病三年,乃知天下无方可用。谨以此言与君共勉(当然各位高抬贵手给个三连!!!!)

相关文章
|
3天前
|
Go
go函数
go函数
29 10
|
1天前
|
编译器 Go 索引
Go数组、多维数组和切片(动态数组),及常用函数len(),cap(),copy(),append()在切片中的使用
本文介绍了Go语言中数组、多维数组和切片(动态数组)的基本概念和操作,包括数组的定义、初始化、访问,多维数组的定义和访问,以及切片的创建、使用和扩容。同时,还讲解了切片中常用的函数len()、cap()、copy()和append()的使用方法。
|
1月前
|
存储 Ubuntu Go
在Ubuntu 16.04上安装Go 1.6的方法
在Ubuntu 16.04上安装Go 1.6的方法
34 1
|
1月前
|
存储 Ubuntu Go
在Ubuntu 18.04上安装Go的方法
在Ubuntu 18.04上安装Go的方法
24 1
|
1月前
|
存储 Ubuntu Linux
在Ubuntu 14.04上安装Go 1.6的方法
在Ubuntu 14.04上安装Go 1.6的方法
40 1
|
1月前
|
Prometheus Cloud Native Go
Go 1.22 标准库 slices 新增函数和一些旧函数增加新特性
Go 1.22 标准库 slices 新增函数和一些旧函数增加新特性
|
27天前
|
设计模式 Java 数据库连接
|
27天前
|
Go 开发者
|
1月前
|
SQL 安全 测试技术
[go 面试] 接口测试的方法与技巧
[go 面试] 接口测试的方法与技巧
|
30天前
|
存储 机器学习/深度学习 设计模式
Go从入门到放弃之函数
Go从入门到放弃之函数