浅谈Go语言函数与方法的区别

Python08

浅谈Go语言函数与方法的区别,第1张

    前段时间,我们实验室用go作为后台开发语言开发了一个web项目,由于这是自己第一次使用go语言进行开发,在开发过程中,一味着追求完成任务,在编码的时候没有太注重性能,虽然勉强实现了功能,但是对go语言的理解还是比较浅显的。下面来谈谈自己对go语言中函数与方法的理解。

普通函数:

    go函数可以返回多个值

    值传递: 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样函数中如果对参数进行修改,将不会影响到实际参数

    引用传递: 引用传递是指在调用函数将实际参数的地址传递到函数中,那么在函数中对参数进行的修改,将影响到实际参数。

一般来说go语言函数的 接收者(也就是形参)一般放在函数名后面 ,不能将指针类型的数据直接传递,也就是说函数形参如果是值类型,调用者必须使用值作为实参过来,如果函数形参是指针类型,则函数调用者需使用指针作为实参来调用。

普通方法:

接收者是在func关键字后面,而不是在函数名称后面,接收者可以是自己定义的一个类型,这个类型可以是struct、interface,一个方法就是一个包含了接收者的函数,接收者可以是命名类型或者是结构体类型的一个值或者是一个指针。

下面是一个例子来说明方法和函数的区别(重点)

大家经常用"=="来比较两个变量是否相等。但是golang中的"=="有很多细节的地方,跟php是不一样的。很多时候不能直接用"=="来比较,编译器会直接报错。

golang中基本类型的比较规则和复合类型的不一致,先介绍下golang的变量类型:

golang中的基本类型

比较的两个变量类型必须相等。而且,golang没有隐式类型转换,比较的两个变量必须类型完全一样,类型别名也不行。如果要比较,先做类型转换再比较。

复合类型是逐个字段,逐个元素比较的。需要注意的是, array 或者struct中每个元素必须要是可比较的,如果某个array的元素 or struct的成员不能比较(比如是后面介绍的slice,map等),则此复合类型也不能比较。

逐个成员比较类型和值。每个对应成员的比较遵循基本类型变量的比较规则。

但是如果struct中有不可比较的成员类型时:

可以看到,struct中有slice这种不可比较的成员时,整个struct都不能做比较,即使没有对slice那个成员赋值(slice默认值为nil)

slice和map的比较规则比较奇怪,我们先说普通的变量引用类型&val和channel的比较规则。

引用类型变量存储的是某个变量的内存地址。所以引用类型变量的比较,判断的是这两个引用类型存储的是不是同一个变量。

上面看起来比较废话,但是得理解引用类型的含义。不然对判断规则还是不清楚。

slice类型不可比较,只能与零值nil做比较。

关于slice类型不可比较的原因,后面会专门写文章做讨论。

map类型和slice一样,不能比较,只能与nil做比较。

接口类型的变量,包含该接口变量存储的值和值的类型两部分组成,分别称为接口的动态类型和动态值。 只有动态类型和动态值都相同时,两个接口变量才相同:

而且接口的动态类型必须要是可比较的,如果不能比较(比如slice,map),则运行时会报panic。因为编译器在编译时无法获取接口的动态类型,所以编译能通过,但是运行时直接panic:

golang的func作为一等公民,也是一种类型,而且不可比较

上面说过,map和slice是不可比较类型,但是有没有特殊的方法来对slice和map做比较呢,有

reflect.DeepEqual函数可以用来比较两个任意类型的变量

对map类型做比较:

对slice类型做比较:

对struct类型做比较:

可以发现,只要变量的类型和值相同的话,reflect.DeepEqual比较的结果就为true

直接看用例:

结果为:

1, golang的类型再定义和类型别名

2,golang的slice和map为什么不可以比较

1, https://medium.com/golangspec/equality-in-golang-ff44da79b7f1

2, https://studygolang.com/articles/19144

3, https://juejin.im/post/5d5ff27d518825637965f3f3

在接触到go之前,我认为函数和方法只是同一个东西的两个名字而已(在我熟悉的c/c++,python,java中没有明显的区别),但是在golang中者完全是两个不同的东西。官方的解释是,方法是包含了接收者的函数。到底什么意思呢。

首先函数的格式是固定的,func+函数名+ 参数 + 返回值(可选) + 函数体。例

func main()

{

fmt.Println("Hello go")

}

在golang中有两个特殊的函数,main函数和init函数,main函数不用介绍在所有语言中都一样,它作为一个程序的入口,只能有一个。init函数在每个package是可选的,可有可无,甚至可以有多个(但是强烈建议一个package中一个init函数),init函数在你导入该package时程序会自动调用init函数,所以init函数不用我们手动调用,l另外它只会被调用一次,因为当一个package被多次引用时,它只会被导入一次。

package main

import (

"demo/mypackage"

"fmt"

)

func main() {

fmt.Println("Hello go.... I = ", mypackage.I)

}

运行结果:

我们可以看到,程序为我们自动调用了两个init函数,并且是按照顺序调用的。

下面来看方法。

package main

import "fmt"

type myint int

//乘2

func (p *myint) mydouble() int {

*p = *p * 2

return 0

}

//平方

func (p myint) mysquare() int {

p = p * p

fmt.Println("mysquare p = ", p)

return 0

}

func main() {

var i myint = 2

i.mydouble()

fmt.Println("i = ", i)

i.mysquare()

fmt.Println("i = ", i)

}

运行结果:

我们可以看到方法和函数的区别,方法在func关键字后是接收者而不是函数名,接收者可以是自己定义的一个类型,这个类型可以是struct,interface,甚至我们可以重定义基本数据类型。我们可以给他一些我们想要的方法来满足我们的实际工程中的需求,就像上面一样我重定义了int并给了它一个乘2和平法的方法,这里我们要注意一个细节,接收者是指针和非指针的区别,我们可以看到当接收者为指针式,我们可以通过方法改变该接收者的属性,但是非指针类型缺做不到。

这里的接收者和c++中的this指针有一些相似,我们可以把接受者当作一个class,而这些方法就是类的成员函数,当接收者为指针类型是就是c++中的非const成员函数,为非指针时就是const成员函数,不能通过此方法改变累的成员变量。