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

你可能感兴趣的:(Go,golang,开发语言,后端)