• [golang]go中函数和方法的区分


    说明

    在很多的语言当中,函数就是方法,例如Java。但是在go语言当中,函数和方法不太一样,有明确的概念区分。go中,函数是指不属于任何结构体、类型的方法,也就是说,函数是没有接收者的;而方法有接收者。我们在go中说的方法要么属于一个结构体,要么属于一个新定义的类型。

    函数

    函数和方法,虽然在go中的概念不相同,但是二者的定义非常类似。函数的定义声明没有接收者,所以我们可以直接在go文件中,go的包里面声明定义函数。

    func main() {
    	sum := add(2,3)
    	fmt.Println(sum)
    }
    
    func add(x,y int) int {
    	return x + y
    }
    

    在上面的代码中,我们定义了一个加法函数,它的函数签名是func add(a,b int) int,没有接收者。直接是在go的包中被定义,定义完成后可以直接调用。

    在上面的代码中add函数的名称采用了小写的形式,所以根据go语言的规定它的作用域只限于函数声明的包内使用,在其他的包内并不能使用。如果希望这个函数在其他的go的包当中被使用就需要将函数名改为以大写字母开头,这样这个函数就可以被其他的包调用,这也是go当中标识符首字母大写的作用。

    func Add(a,b int) int {
    	return a + b
    }
    

    上述的函数Add因为首字母大写,所以这个函数可以被其他的包调用。

    方法

    方法的声明和函数非常的类似,区别是:方法在定义的时候,会在func 和 方法名之间增加一个参数,这个参数就是接收者,这样我们定义的这个方法就会和接收者绑定在一起,可以称之为是接收者的方法。

    type student struct {
    	name string
    }
    
    func (s student) SayHello() {
    	fmt.Println("hello,world")
    }
    

    在上面的代码中,我们给结构体student定义了一个方法,此时结构体student就是接收者,而SayHello就是属于student这个结构体的方法。

    那么这个方法该如何使用呢?

    zhangsan := student{"张三"}
    zhangsan.sayHello()
    

    上面的代码中,我们先需要设置一个变量为student类型,在初始化之后就可以调用SayHello方法。

    在go语言当中,有两种类型的接收者:值接收者和指针接收者。在上面的代码中我们使用的是值接收者,相当于在调用时,使用的是值接收者的一个副本,所以在方法中对该值的任何操作,都不会影响原来的类型变量。

    func main() {
    	p:=person{name:"张三"}
    	p.modify() //值接收者,修改无效
    	fmt.Println(p.String())
    }
    
    type person struct {
    	name string
    }
    
    func (p person) String() string{
    	return "the person name is "+p.name
    }
    
    func (p person) modify(){
    	p.name = "李四"
    }
    

    在上面的代码中,打印出来的结果依旧是张三,对其进行的修改是没有效果的。如果我们使用一个指针作为接收者,那么就会有作用了。因为指针接收者传递的是一个指向原值指针的副本,指针的副本,指向的还是原来类型的值,所以在修改的时候,同时也会影响原来类型变量的值。

    func main() {
    	p:=person{name:"张三"}
    	p.modify() //指针接收者,修改有效
    	fmt.Println(p.String())
    }
    
    type person struct {
    	name string
    }
    
    func (p person) String() string{
    	return "the person name is "+p.name
    }
    // 改用指针接收者
    func (p *person) modify(){
    	p.name = "李四"
    }
    

    在上面的代码中,只是将接收者改为了指针接收者,就完成了修改。

    在上面的例子中,有没有发现,我们在调用指针接收者方法的时候,使用的也是一个值的变量,并不是一个指针,如果我们使用下面的也是可以的。

    p:=person{name:"张三"}
    (&p).modify() //指针接收者,修改有效
    

    这样也是可以的。如果我们没有这么强制使用指针进行调用,Go的编译器自动会帮我们取指针,以满足接收者的要求。

    同样的,如果是一个值接收者的方法,使用指针也是可以调用的,Go编译器自动会解引用,以满足接收者的要求,比如例子中定义的String()方法,也可以这么调用:

    p:=person{name:"张三"}
    fmt.Println((&p).String())
    

    总之,方法的调用,既可以使用值,也可以使用指针,我们不必要严格的遵守这些,Go语言编译器会帮我们进行自动转义的,这大大方便了我们开发者。

    不管是使用值接收者,还是指针接收者,一定要搞清楚类型的本质:对类型进行操作的时候,是要改变当前值,还是要创建一个新值进行返回?这些就可以决定我们是采用值传递,还是指针传递。

    可变参数

    函数方法的参数,可以是任意多个,这种我们称之为可以变参数,比如我们常用的fmt.Println()这类函数,可以接收一个可变的参数。

    func main() {
    	fmt.Println("1","2","3")
    }
    

    可以变参数,可以是任意多个。我们自己也可以定义可以变参数,可变参数的定义,在类型前加上省略号…即可。

    func main() {
    	print("1","2","3")
    }
    
    func print (a ...interface{}){
    	for _,v:=range a{
    		fmt.Print(v)
    	}
    	fmt.Println()
    }
    

    例子中我们自己定义了一个接受可变参数的函数,效果和fmt.Println()一样。

    可变参数本质上是一个数组,所以我们向使用数组一样使用它,比如例子中的 for range 循环。

  • 相关阅读:
    GitHub超详细图文攻略
    HTML5本地存储——IndexedDB二:索引
    HTML5 indexedDb 数据库
    js 对象 浅拷贝 和 深拷贝
    《黑客大曝光》实践部分——sql注入(7/8)
    Linux内核设计第五周——扒开系统调用三层皮(下)
    读书笔记——《黑客大曝光》(6/8)
    《linux内核设计与实现》读书笔记第五章——系统调用
    Linux内核设计第四周——扒开系统调用三层皮
    《linux内核设计与实现》读书笔记第一、二章
  • 原文地址:https://www.cnblogs.com/liujunhang/p/12535036.html
Copyright © 2020-2023  润新知