Go基础系列:Go中的方法
Go方法简介
Go中的struct结构类似于面向对象中的类。面向对象中,除了成员变量还有方法。
Go中也有方法,它是一种特殊的函数,定义于struct之上(与struct关联、绑定),被称为struct的receiver。
它的定义方式大致如下:
type mytype struct{}
func (recv mytype) my_method(para) return_type {}
func (recv *mytype) my_method(para) return_type {}
这表示my_method()
函数是绑定在mytype这个struct type上的,是与之关联的,是独属于mytype的。所以,此函数称为"方法"。所以,方法和字段一样,也是struct类型的一种属性。
其中方法名前面的(recv mytype)
或(recv *mytype)
是方法的receiver,具有了receiver的函数才能称之为方法,它将函数和type进行了关联,使得函数绑定到type上。至于receiver的类型是mytype
还是*mytype
,后面详细解释。
定义了属于mytype的方法之后,就可以直接通过mytype来调用这个方法:
mytype.my_method()
来个实际的例子,定义一个名为changfangxing的struct类型,属性为长和宽,定义属于changfangxing的求面积的方法area()。
package main
import "fmt"
type changfangxing struct {
length float64
width float64
}
func (c *changfangxing) area() float64 {
return c.length * c.width
}
func main() {
c := &changfangxing{
2.5,
4.0,
}
fmt.Printf("%f\n",c.area())
}
方法的一些注意事项
1.方法的receiver type并非一定要是struct类型,type定义的类型别名、slice、map、channel、func类型等都可以。但内置简单数据类型(int、float等)不行,interface类型不行。
package main
import "fmt"
type myint int
func (i *myint) numadd(n int) int {
return n + 1
}
func main() {
n := new(myint)
fmt.Println(n.numadd(4))
}
以slice为类型,定义属于它的方法:
package main
import "fmt"
type myslice []int
func (v myslice) sumOfSlice() int {
sum := 0
for _, value := range v {
sum += value
}
return sum
}
func main() {
s := myslice{11, 22, 33}
fmt.Println(s.sumOfSlice())
}
2.struct结合它的方法就等价于面向对象中的类。只不过struct可以和它的方法分开,并非一定要属于同一个文件,但必须属于同一个包。所以,没有办法直接在int、float等内置的简单类型上定义方法,真要为它们定义方法,可以像上面示例中一样使用type定义这些类型的别名,然后定义别名的方法。
3.方法有两种类型:(T Type)
和(T *Type)
,它们之间有区别,后文解释。
4.方法就是函数,所以Go中没有方法重载(overload)的说法,也就是说同一个类型中的所有方法名必须都唯一。但不同类型中的方法,可以重名。例如:
func (a *mytype1) add() ret_type {}
func (a *mytype2) add() ret_type {}
5.type定义类型的别名时,别名类型不会拥有原始类型的方法。例如mytype上定义了方法add(),mytype的别名new_type不会有这个方法,除非自己重新定义。
6.如果receiver是一个指针类型,则会自动解除引用。例如,下面的a是指针,它会自动解除引用使得能直接调用属于mytype1实例的方法add()。
func (a *mytype1) add() ret_type {}
a.add()
7.(T Type)
或(T *Type)
的T,其实就是面向对象语言中的this或self,表示调用该实例的方法。如果愿意,自然可以使用self或this,例如(self Type)
,但这是可以随意的。
8.方法和type是分开的,意味着实例的行为(behavior)和数据存储(field)是分开的,但是它们通过receiver建立起关联关系。
方法和函数的区别
其实方法本质上就是函数,但方法是关联了类型的,可以直接通过类型的实例去调用属于该实例的方法。
例如,有一个type person,如果定义它的方法setname()和定义通用的函数setname2(),它们要实现相同的为person赋值名称时,参数不一样:
func (p *person) setname(name string) {
p.name = name
}
func setname2(p *person,name string) {
p.name = name
}
通过函数为person的name赋值,必须将person的实例作为函数的参数之一,而通过方法则无需声明这个额外的参数,因为方法是关联到person实例的。
值类型和指针类型的receiver
假如有一个person struct:
type person struct{
name string
age int
}
有两种类型的实例:
p1 := new(person)
p2 := person{}
p1是指针类型的person实例,p2是值类型的person实例。虽然p1是指针,但它也是实例。在需要访问或调用person实例属性时候,如果发现它是一个指针类型的变量,Go会自动将其解除引用,所以p1.name
在内部实际上是(*p1).name
。同理,调用实例的方法时也一样,有需要的时候会自动解除引用。
除了实例有值类型和指针类型的区别,方法也有值类型的方法和指针类型的区别,也就是以下两种receiver:
func (p person) setname(name string) { p.name = name }
func (p *person) setage(age int) { p.age = age }
setname()方法中是值类型的receiver,setage()方法中是指针类型的receiver。它们是有区别的。
首先,setage()方法的p是一个指针类型的person实例,所以方法体中的p.age
实际上等价于(*p).age
。
再者,方法就是函数,Go中所有需要传值的时候,都是按值传递的,也就是拷贝一个副本。
setname()中,除了参数name string
需要拷贝,receiver部分(p person)
也会拷贝,而且它明确了要拷贝的对象是值类型的实例,也就是拷贝完整的person数据结构。但实例有两种类型:值类型和指针类型。(p person)
无视它们的类型,因为receiver严格规定p是一个值类型的实例。所以无论是指针类型的p1实例还是值类型的p2实例,都会拷贝整个实例对象。对于指针类型的实例p1,前面说了,在需要的时候,Go会自动解除引用,所以p1.setname()
等价于(*p1).setname()
。
也就是说,只要receiver是值类型的,无论是使用值类型的实例还是指针类型的实例,都是拷贝整个底层数据结构的,方法内部访问的和修改的都是实例的副本。所以,如果有修改操作,不会影响外部原始实例。
setage()中,receiver部分(p *person)
明确指定了要拷贝的对象是指针类型的实例,无论是指针类型的实例p1还是值类型的p2,都是拷贝指针。所以p2.setage()
等价于(&p2).setage()
。
也就是说,只要receiver是指针类型的,无论是使用值类型的实例还是指针类型的实例,都是拷贝指针,方法内部访问的和修改的都是原始的实例数据结构。所以,如果有修改操作,会影响外部原始实例。
那么选择值类型的receiver还是指针类型的receiver?一般来说选择指针类型的receiver。
下面的代码解释了上面的结论:
package main
import "fmt"
type person struct {
name string
age int
}
func (p person) setname(name string) {
p.name = name
}
func (p *person) setage(age int) {
p.age = age
}
func (p *person) getname() string {
return p.name
}
func (p *person) getage() int {
return p.age
}
func main() {
// 指针类型的实例
p1 := new(person)
p1.setname("longshuai1")
p1.setage(21)
fmt.Println(p1.getname()) // 输出""
fmt.Println(p1.getage()) // 输出21
// 值类型的实例
p2 := person{}
p2.setname("longshuai2")
p2.setage(23)
fmt.Println(p2.getname()) // 输出""
fmt.Println(p2.getage()) // 输出23
}
上面分别创建了指针类型的实例p1和值类型的实例p2,但无论是p1还是p2,它们调用setname()方法设置的name值都没有影响原始实例中的name值,所以getname()都输出空字符串,而它们调用setage()方法设置的age值都影响了原始实例中的age值。
嵌套struct中的方法
当内部struct嵌套进外部struct时,内部struct的方法也会被嵌套,也就是说外部struct拥有了内部struct的方法。
例如:
package main
import (
"fmt"
)
type person struct{}
func (p *person) speak() {
fmt.Println("speak in person")
}
// Admin exported
type Admin struct {
person
a int
}
func main() {
a := new(Admin)
// 直接调用内部struct的方法
a.speak()
// 间接调用内部stuct的方法
a.person.speak()
}
当person被嵌套到Admin中后,Admin就拥有了person中的属性,包括方法speak()。所以,a.speak()
和a.person.speak()
都是可行的。
如果Admin也有一个名为speak()的方法,那么Admin的speak()方法将掩盖内部struct的person的speak()方法。所以a.speak()
调用的将是属于Admin的speak(),而a.preson.speak()
将调用的是person的speak()。
验证如下:
func (a *Admin) speak() {
fmt.Println("speak in Admin")
}
func main() {
a := new(Admin)
// 直接调用内部struct的方法
a.speak()
// 间接调用内部stuct的方法
a.person.speak()
}
输出结果为:
speak in Admin
speak in person
嵌入方法的第二种方式
除了可以通过嵌套的方式获取内部struct的方法,还有一种方式可以获取另一个struct中的方法:将另一个struct作为外部struct的一个命名字段。
例如:
type person struct {
name string
age int
}
type Admin struct {
people *person
salary int
}
现在Admin除了自己的salary属性,还指向一个person。这和struct嵌套不一样,struct嵌套是直接外部包含内部,而这种组合方式是一个struct指向另一个struct,从Admin可以追踪到其指向的person。所以,它更像是链表。
例如,person是Admin type中的一个字段,person有方法speak()。
package main
import (
"fmt"
)
type person struct {
name string
age int
}
type Admin struct {
people *person
salary int
}
func main() {
// 构建Admin实例
a := new(Admin)
a.salary = 2300
a.people = new(person)
a.people.name = "longshuai"
a.people.age = 23
// 或a := &Admin{&person{"longshuai",23},2300}
// 调用属于person的方法speak()
a.people.speak()
}
func (p *person) speak() {
fmt.Println("speak in person")
}
或者,定义一个属于Admin的方法,在此方法中应用person的方法:
func (a *Admin) sing(){
a.people.speak()
}
然后只需调用a.sing()
就可以隐藏person的方法。
多重继承
因为Go的struct支持嵌套多个其它匿名字段,所以支持"多重继承"。这意味着外部struct可以从多个内部struct中获取属性、方法。
例如,照相手机cameraPhone是一个struct,其内嵌套Phone和Camera两个struct,那么cameraPhone就可以获取来自Phone的call()方法进行拨号通话,获取来自Camera()的takeAPic()方法进行拍照。
面向对象的语言都强烈建议不要使用多重继承,甚至有些语言本就不支持多重继承。至于Go是否要使用"多重继承",看需求了,没那么多限制。
重写String()方法
fmt包中的Println()、Print()和Printf()的%v
都会自动调用String()方法将待输出的内容进行转换。
可以在自己的struct上重写String()方法,使得输出这个示例的时候,就会调用它自己的String()。
例如,定义person的String(),它将person中的name和age结合起来:
package main
import (
"fmt"
"strconv"
)
type person struct {
name string
age int
}
func (p *person) String() string {
return p.name + ": " + strconv.Itoa(p.age)
}
func main() {
p := new(person)
p.name = "longshuai"
p.age = 23
// 输出person的实例p,将调用String()
fmt.Println(p)
}
上面将输出:
longshuai: 23
一定要注意,定义struct的String()方法时,String()方法里不要出现fmt.Print()、fmt.Println以及fmt.Printf()的%v
,因为它们自身会调用String(),会出现无限递归的问题。
Go基础系列:Go中的方法的更多相关文章
- Java基础系列--03_Java中的方法描述
方法 (1)方法的定义:就是完成特定功能的代码块. 注意:在很多语言里面有函数的定义,而在Java中,函数被称为方法. (2)格式: 修饰符 返回值类型 方法名(参数类型 参数名1,参数类型 参数名2 ...
- 10.翻译:EF基础系列---EF中的持久性
原文链接:http://www.entityframeworktutorial.net/EntityFramework4.3/persistence-in-entity-framework.aspx ...
- 8.翻译:EF基础系列----EF中实体的状态
原文链接:http://www.entityframeworktutorial.net/basics/entity-states.aspx 在实体的生命周期中,EF API维护着每一个实体的状态,对于 ...
- 带你学够浪:Go语言基础系列 - 10分钟学方法和接口
文章每周持续更新,原创不易,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) 对于一般的语言使用者来说 ,20% 的语言特性就能够满 ...
- 7.翻译:EF基础系列---EF中的实体类型
原文地址:http://www.entityframeworktutorial.net/Types-of-Entities.aspx 在Entity Framework中有两种实体类型:一种是POCO ...
- 5.翻译:EF基础系列---EF中的上下文类
原文地址:http://www.entityframeworktutorial.net/basics/context-class-in-entity-framework.aspx EF中的上下文类是一 ...
- Java第三阶段学习(十一、Servlet基础、servlet中的方法、servlet的配置、ServletContext对象)
一.Servlet简介 1.什么是servlet: sun公司提供的一套规范(接口),用来处理客户端请求.响应给浏览器的动态资源.但servlet的实质就是java代码,通过java的API动态的向 ...
- Java基础:HashMap中putAll方法的疑惑
最近回顾了下HashMap的源码(JDK1.7),当读到putAll方法时,发现了之前写的TODO标记,当时由于时间匆忙没来得及深究,现在回顾到了就再仔细思考了下 @Override public v ...
- Java基础系列(29)- 方法的重载
方法的重载 重载就是在一个类中,有相同的函数名称,但形参不同的函数 方法重载的规则: 方法名称必须相同 参数列表必须不同(个数不同.或类型不同.参数排列顺序不同等) 方法的返回类型可以相同也可以不相同 ...
- 【前端基础系列】理解bind方法使用与实现
方法描述 bind()方法创建一个新函数,当被调用时,将其this关键字设置为提供的值. 语法说明 fn.bind(thisArg,arg1,arg2,..) 参数说明 thisArg:当绑定函数被调 ...
随机推荐
- ie9上传文件
兼容ie9文件上传,解决ie9下提示下载或保存 如果不考虑ie9兼容性,实现[上传图片]大致的思路如下: 由于公司是将所有上传的图片都放到[代理服务器]里.所以[上传图片]其实是上传到[代理服务器]. ...
- bzoj3237(cdq+并查集)
这题一眼lct,然而 #include<iostream> #include<cstdio> #include<cmath> #include<cstring ...
- kubernetes CRD学习笔记
前言 最近在极客时间订阅了kubernetes的专栏,这篇文章是想记录一下自己学习CRD(custom resource definition)的过程,加深一下记忆. 准备工作 首先安装一下我们用的g ...
- Exception in thread "main" java.lang.UnsupportedClassVersionError: org/apache/ma ven/cli/Maven
此异常是由于jdk版本和maven版本不一致导致的 在maven官网上的说明是这样的:Maven 3.3+ require JDK 1.7 or above to execute - they sti ...
- 学习Python第四天
关于剩下的数据类型:字符串 字符串是有序的,不可变的(不可变的意思是指将变量a重新赋值后不会覆盖原来的值,而是在内存中开辟了一块新的内存地址,存储变量的值) 字符串的各种方法: 1,将字符串中的大写变 ...
- java web 开发手册
W3School离线手册(2017.03) 提取密码: b2fo JavaScript高级程序设计第三版 提取密码: cscv CSS4.2.4 参 ...
- CentOS---zabbix使用sendEamil发送报警
一.sendEmail简介 sendEmail是一个轻量级,命令行的SMTP邮件客户端.如果你需要使用命令行发送邮件,那么sendEmail是非常完美的选择:使用简单并且功能强大.这个被设计用在php ...
- java继承多态和抽象类接口
一.继承 通过扩展一个已有的类,并继承该类的属性和行为,来创建一个新的类.已有的称为父类,新的类称为子类(父类派生子类,子类继承父类).(1)继承的优点: ①代码的可重用性: ②父类的属性 ...
- wordpress背景添加跟随鼠标动态线条特效
今天看到别人的博客,在鼠标移动背景时会出现线条动态特效 感觉挺有意思的,还有另一种,在背景点击时会跳出字符特意去找了方法,以为需要添加代码的,结果只要安装个插件就可以了,所以说wordpress就是方 ...
- 双十一福利,阿里云1核2G一年最低只要99
活动期间,新用户可任选1款购买,限购1台,拼团后团内每增加1名新购用户,全团再享10%优惠 (50%封顶),买贵返差 又是只给新用户的福利,比上次的699一年的活动要差一点.如果之前注册了没下手的用户 ...