6.4. 第2节:方法
2 方法
2.1基本方法创建
在介绍面向对象时,讲过可以通过属性和方法(函数)来描述对象。
什么是方法呢?
方法,大家可以理解成就是函数,但是在定义使用方面与前面讲解的函数还是有区别的。
我们先定义一个传统的函数:
这个函数非常简单,下面定义一个方法,看一下在语法与传统的函数有什么区别:
方法的定义:
type Integer int :表示的意思是给int类型指定了一个别名叫Integer,别名可以随便起,只要符合GO语言的命名规则就可以。
指定别名后,后面可以用Integer来代替int 来使用。
func (a Integer) Test(b Integer) Integer{
}
表示定义了一个方法,方法的定义与函数的区别
第一:在关键字后面加上( a Integer), 这个在方法中称之为接收者,所谓的接受者就是接收传递过来的第一个参数,然后复制a, a的类型是Integer ,由于Integer是int的别名,所以a的类型为int
第二:在表示参数的类型时,都使用了对应的别名。
通过方法的定义,可以看出方法其实就是给某个类型绑定的函数。在该案例中,是为整型绑定的函数,只不过在给整型绑定函数(方法)时,一定要通过type来指定一个别名,因为int类型是系统已经规定好了,无法直接绑定函数,所以只能通过别名的方式。
第三:调用方式不同
var result Interger=3
表示定义一个整型变量result,并赋值为3.
result.Test( 3)
通过result变量,完成方法的调用。因为,Test( )方法,是为int类型绑定的函数,而result变量为int类型。所以可以调用Test( )方法。result变量的值会传递给Test( )方法的接受者,也就是参数a, 而实参Test( 3),会传递形参b.
当然,我们也可以将Test( )方法,理解成是为int类型扩展了,追加了的方法。因为系统在int类型时,是没有改方法的。
通过以上的定义,发现方法其实方法就是函数的语法糖
。
在以上案例中,Test( )方法是为int类型绑定的函数,所以任何一个整型变量,都可以调用该方法。
例如:
2.2给结构体添加方法
上面给整型创建了一个方法,那么直接通过整型变量加上“点”,就可以调用该方法了。
大家想一下,如果给结构体(类)加上了方法,那么根据结构体(类)创建完成对象后,是不是就可以通过对象加上“点”,就可以完成方法的调用,这与调用类中定义的属性的方式是完全一样的。这样就完成了通过方法与属性来描述一个对象的操作。(大家自己回想一下,前面在讲解对象的概念时,一直在说其具有的属性与行为(方法))
给结构体添加方法,语法如下:
给结构体添加方法的方式与前面给int类型添加方法的方式,基本一致。唯一不同的是,不需要给结构体指定别名,因为结构体Student就是相当于其所有成员属性的别名(id,name,score),所以这里不要在给结构体Student创建别名。
调用方式:根据结构体(类)创建的对象,完成了方法的调用。
PrintShow( )方法的作用,只是将结构体的成员(属性)值打印出来,如果要修改其对应的值,应该怎么做呢?
这时,大家肯定相当了指针,将方法的接收者,修改成对应的指针类型。
具体修改如下:
在创建方法时,接收者类型为指针类型,所以在调用方法时,创建一个结构体变量,同时将结构体变量的地址,传递给方法的接收者,然后调用EditInfo( )方法,完成要修改的数据传递。
在使用方法是,要注意如下几个问题:
第一:只要接收者类型不一样,这个方法就算同名,也是不同方法,不会出现重复定义函数的错误
但是,如果接收者类型一样,但是方法的参数不一样,是会出现错误的。
也就是,在GO中没有方法重载(所谓重载,指的是方法名称一致,参数类型,个数不一致)。
第二:关于接收者不能为指针类型。
2.3指针变量的方法值
接收者为普通变量,非指针,值传递
接收者为指针变量,引用传递
在上面的案例中,我们定义了两个方法,一个是PrintShow( ), 该方法的接收者为普通方法,一个EditInfo( )方法,该方法的接收者为指针变量,那么大家思考这么一个问题:定义一个结构体指针变量,能否调用PrintShow( )方法呢?如下所示:
通过测试,发现是可以调用的。
为什么结构体指针变量,可以调用PrintShow( )方法呢?
原因是:先将指针stu, 转换成*stu在调用。
等价如下代码:
所以,如果结构体变量是一个指针变量,它能够调用哪些方法,这些方法就是一个集合,简称方法集
如果是普通的结构体变量能否调用EditInfo( )方法。
是可以调用的,原因是:将普通的结构体类型的变量转换成(&stu)在调用EditInfo( )方法。
这样的好处是非常灵活,创建完对应的对象后,可以随意调用方法,不需要考虑太多指针的问题。
代码例子:
package main
import "fmt"
//1、定义函数类型 定义结构体名称
//2、为已存在的数据类型起别名
type Int int
//对象的方法
//func (对象)方法(参数列表)(返回值列表){
// 代码体
//}
func (a Int) add(b Int) Int {
fmt.Println(a)
fmt.Println(b)
return a + b
}
func main() {
//将源文件编译成可执行程序
//编译过程
//1、预处理 包展开 替换数据类型 去掉注释
//2、编译 如果代码有错会提示 如果没错会编译成汇编文件
//3、汇编 将汇编文件转成二进制文件
//4、链接 将支持的库链接到程序中 变成可执行程序
//类型别名会在编译时进行转换
var a Int = 10
var b Int =20
//var b Int = 20
//包.函数 结构体.成员 对象.方法
//sum:=a.add(b)
//对象.方法
//想要使用方法 必须是相同类型的对象才可以
sum:=b.add(a)
fmt.Println(sum)
//var b int = 20
//fmt.Println(a + 10)
//fmt.Printf("%T\n", a)
}
使用列子:
package main
import "fmt"
type student5 struct {
name string
age int
sex string
score int
addr string
}
//函数
func Print() {
fmt.Println("hello world")
}
//结构体类型可以作为对象类型
func (s student5) Print() {
fmt.Printf("%p\n",&s)
fmt.Printf("姓名:%s\n", s.name)
fmt.Printf("年龄:%d\n", s.age)
fmt.Printf("性别:%s\n", s.sex)
fmt.Printf("成绩:%d\n", s.score)
fmt.Printf("地址:%s\n", s.addr)
}
func main() {
//创建对象
stu := student5{"贾政", 58, "男", 80, "贾府"}
fmt.Printf("%p\n",&stu)
//对象.方法
stu .Print()
fmt.Println(stu)
stu1 := student5{"贾玲", 29, "女", 99, "北京"}
stu1.Print()
//打印错误日志使用函数
//print()
//函数调用
//Print()
//对象的方法名可以和函数名重名 但是相同的对象方法名不能重名
//fmt.Println(stu.Print)
//fmt.Println(stu1.Print)
//打印函数在代码区的地址
fmt.Println(Print)
}
方法的内存模型:
package main
import "fmt"
type student6 struct {
name string
age int
sex string
score int
addr string
}
//对象不同方法名相同 不会冲突
//在方法调用中 方法的接收者为指针类型
//指针类型 和普通类型表示是相同对象的类型
func (s *student6) Print() {
s.name = "黄月英"
s.score = 99
//fmt.Println(*s)
}
func main() {
stu := student6{"貂蝉", 22, "女", 80, "徐州"}
//地址传递(*student6) 值传递(student6)
//一般建议选择地址传递
stu.Print()
fmt.Println(stu)
}