博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
教女朋友写方法 -- 就要学习 Go 语言
阅读量:2288 次
发布时间:2019-05-09

本文共 5561 字,大约阅读时间需要 18 分钟。

刚接触 Go 语言的函数和方法时,我产生过这样的疑惑:为什么会严格区分这两者的概念?学完之后才知道,不像别的语言(Java、PHP等)函数即方法,方法即函数,Go 语言中两者还是有很大区别的。

方法定义

定义方法与函数类似,区别在于:方法定义时,在 func 和方法名之间会增加一个额外的参数。如下:

func (receiver Type) methodName(...Type) Type {
...}

(receiver Type) 是增加的额外参数,receiver 称为接收者,Type 可以是任意合法的类型,包括:结构体类型或新定义的类型。可以说,方法 methodName 属于类型 Type。方法名后面的参数和返回值是可选的。

type Employee struct {
FirstName,LastName string}func (e Employee) fullName() string {
return e.FirstName + " " + e.LastName}func main() {
e := Employee{
FirstName:"Jim", LastName:"Green", } fmt.Println(e.fullName())}

Jim Green

采用 Type.methodName(…) 语法调用类型的方法。

上面的代码,定义了一个不用传参、返回值为 string类型的方法 fullName。它属于结构体类型 Employee,我们可以使用 e.fullName() 调用。fullName 中的 e 就是接收者,在方法内部可以访问结构体的每一个成员。

现在,我们应该很清楚,方法与函数的区别:方法属于某一种类型,且有接收者

值接收者和指针接收者

到目前为止,创建的方法使用的都是值接收者,还可以通过下面的语法创建指针接收者的方法:

func (receiver *Type) methodName(...Type) Type {
...}

值接收者和指针接收者,最大区别在于:在方法中修改指针接收者的值会影响到调用者的值,而值接收者就不会。一个是值的副本,一个是指针的副本,而指针的副本指向的还是原来的值。

type Employee struct {
FirstName,LastName string age int}func (e Employee)changeFirstName(name string) {
e.FirstName = name fmt.Println("changeFirstName",e)}func (e *Employee)changeAge(age int) {
e.age = age}func main() {
e := Employee{
FirstName:"Jim", LastName:"Green", age:30, } fmt.Println("changebefore",e) e.changeFirstName("firstName") fmt.Println("changeName",e) (&e).changeAge(18) fmt.Println("changeAge",e)}

changebefore {
Jim Green 30}changeFirstName {
firstName Green 30}changeName {
Jim Green 30}changeAge {
Jim Green 18}

上面的代码,方法 changeFirstName() 使用的是值接收者,在方法中修改结构体的成员 FirstName 没有影响到原来的值;而方法 changeAge() 使用的是指针接收者,在方法中修改结构体成员 age,原来的值也被改变了。

不知道你有没有注意到,上面的代码中,调用指针接收者的方法时使用的是指针:(&e).changeAge(18) 。其实,平时编写代码的时候,可以写成:e.changeAge(18),编译器会自动帮我们转成指针,以满足接收者的要求。同理,e.changeFirstName(“firstName”) 也可以写成 (&e).changeFirstName(“firstName”) ,但这样写就复杂,一般不这么做。

我们应该考虑不同的场景使用值接收者还是指针接收者,如果在方法中发生的改变对调用者可见或者变量拷贝成本比较高的,就应该考虑使用指针接收者,其他情况建议使用值接收者。例如:大变量 A,占用内存大,使用值接收者的话拷贝成本高且效率低,这时就应该考虑使用指针接收者。

嵌套结构体的方法

我们这里讲双层嵌套的结构体,外层称为父结构体,结构体成员称为子结构体,例如:

type Contact struct {
phone,adress string}type Employee struct {
FirstName,LastName string contact Contact}

Employee 是一个嵌套的结构体类型,称为父结构体,成员变量 contact 也是一个结构体,类型是 Contact,称为子结构体。

父结构体的方法,非匿名的成员结构体
type Contact struct {
phone,adress string}type Employee struct {
FirstName,LastName string contact Contact}func (e *Employee)changePhone(newPhone string){
e.contact.phone = newPhone // 注意访问方式}func main() {
e := Employee{
FirstName:"Jim", LastName:"Green", contact:Contact{
phone:"111", adress:"HangZhou", }, } fmt.Println("before:",e) e.changePhone("222") fmt.Println("after:",e)}

before: {
Jim Green {
111 HangZhou}}after: {
Jim Green {
222 HangZhou}}

上面的代码,e 是嵌套结构体,在方法 changePhone() 中修改 contact 的成员 phone,注意修改的代码。

父结构体的方法,匿名的成员结构体
type Contact struct {
phone,adress string}type Employee struct {
FirstName,LastName string Contact}func (e *Employee)changePhone(newPhone string){
// e.Contact.phone = newPhone // 方式一 e.phone = newPhone // 方式二}func main() {
e := Employee{
FirstName:"Jim", LastName:"Green", Contact:Contact{
phone:"111", adress:"HangZhou", }, } fmt.Println("before:",e) e.changePhone("222") fmt.Println("after:",e)}

结果与上面的一样。

上面的代码,Contact 是一个匿名成员结构体。在方法 changePhone() 中修改成员 phone,注意修改的两种方式。

子结构体的方法且非匿名
type Contact struct {
phone,adress string}type Employee struct {
FirstName,LastName string contact Contact}func (c *Contact)changePhone(newPhone string){
c.phone = newPhone}func main() {
e := Employee{
FirstName:"Jim", LastName:"Green", contact:Contact{
phone:"111", adress:"HangZhou", }, } fmt.Println("before:",e) e.contact.changePhone("222") // 注意调用方式,采用 . fmt.Println("after:",e)}

结果与上面的一样。

上面的代码,我们基于结构体类型 Contact 创建了方法 changePhone(),在方法中修改成员 phone,注意调用方法的方式。

子结构体的方法且匿名
type Contact struct {
phone,adress string}type Employee struct {
FirstName,LastName string Contact}func (c *Contact)changePhone(newPhone string){
c.phone = newPhone}func main() {
e := Employee{
FirstName:"Jim", LastName:"Green", Contact:Contact{
phone:"111", adress:"HangZhou", }, } fmt.Println(e) // e.Contact.changePhone("222") // 方式一 e.changePhone("222") // 方式二 fmt.Println(e)}

结果与上面的一样。

上面的代码,成员结构体 Contact 是匿名的,在方法 changePhone() 中修改成员 phone,注意调用方法的方式。

上面四个例子,希望能够帮助大家更好理解嵌套结构体的方法!好,我们接着往下。

非结构体类型的方法

目前为止,都是在结构体上定义方法。文章开始提到了,可以在 Go 任一合法类型上定义方法,但是,有个问题:必须保证类型和方法定义在同一个包里。之前,结构体和方法都定义在 main 包,所以可以运行。

package mainimport "fmt"func (i int)echo(){
fmt.Println(i)}func main() {
}

上面的代码,基于 int 类型创建了方法 echo(),由于 int 类型与方法 echo() 定义在不同的包内,所以编译出错:cannot define new methods on non-local type int

那如何解决呢?你可能会想到,在 main 包内创建 int 类型别名,对!就是这样:

package mainimport "fmt"type myInt intfunc (i myInt) echo ()  {
fmt.Println(i)}func main() {
var a myInt a = 20 a.echo()}

输出:20

上面的代码,基于类型别名 myInt 创建了方法 echo,保证了类型和方法都 main 包。

为何需要方法

上面提到的例子,都是可以通过函数的方法实现的,回头想想,Go 既然有了函数,为何需要方法呢?

  • Go 不是纯粹的的语言且不支持类,通过类型的方法可以实现和类相似的功能,又不会像类那样显得很“重”;
  • 同名的方法可以定义在不同的类型上,但是函数名不允许相同。
type Rect struct {
width int height int}type Circle struct {
radius float64}func (r Rect) Area() int {
return r.width * r.height}func (c Circle) Area() float64 {
return math.Pi * c.radius * c.radius}func main() {
rect := Rect{
5, 4} cir := Circle{
5.0} fmt.Printf("Rect Area %d\n", rect.Area()) fmt.Printf("Circle Area %0.2f\n", cir.Area())}

Rect Area 20Circle Area 78.54

上面的代码,在结构体 Rect 和 Circle 分别定义了同名的 Area() 方法,计算矩形和圆的面积。

学完这篇文章之后,相信你已经学会如何使用方法了,我们下一节再见!

转载地址:http://fbfnb.baihongyu.com/

你可能感兴趣的文章
E1,T1, PRI, Trunk
查看>>
Top的VIRT是什么
查看>>
Linux内核调度器 CFS调优
查看>>
CPU-bound(计算密集型) 和I/O bound(I/O密集型)
查看>>
美国生活小常识
查看>>
从美国回国探亲可带礼品大汇总
查看>>
比较全面的航空公司行李规定以及行李问题咨询
查看>>
基于AWS的自动化部署实践
查看>>
同时使用ColorKey以及顶点Alpha效果
查看>>
Cisco SIP支持的标准
查看>>
走向 Linux 2.6
查看>>
VOIP术语详解
查看>>
p2p语音通信和一般voip通信质量比较
查看>>
VOIP术语总结
查看>>
SIP压力测试/DOS攻击利器
查看>>
Makefile文件里的shell语法
查看>>
Hadoop学习笔记一 简要介绍
查看>>
MySQL存储引擎选择和比较
查看>>
查看MYSQL表占用空间状态
查看>>
动态库路径配置- /etc/ld.so.conf文件
查看>>