Go - 29 Go 结构体之方法
基本介绍
在某些情况下,我们需要声明(定义)方法,比如Person结构体,除了有一些字段外(年龄,姓名...), Person结构体还有一些行为比如:可以说话,跑步...通过学习,还可以做算术题,这时就要用方法才能完成;
Golang中的方法是作用在指定的数据类型上的(即,和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct;
基本使用:
type Person struct {
Name string
Age int
}
func (p Person) eat() {
fmt.Println(p.Name, "可以吃东西~~~")
}
func main() {
// 结构体的方法
var p Person
p.Name = "mary"
p.Age = 30
// 调用方法
p.eat() // mary 可以吃东西~~~
}
上面的一个说明:
1.eat方法和Person 类型绑定;
2.eat方法只能通过Person类型的变量来调用,而不能直接调用,也不能使用其他类型变量来调用。
3.func (p Person)eat() {} ---> p 表示哪个Person变量调用,这个p就是他的副本,这点和函数传参非常相似。
4. p这个名字,由程序员指定,不是固定;
方法的调用和传参机制
1.在通过一个变量去调用方法时,其调用机制和函数一样
2.不一样的地方是,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地址拷贝)
方法的声明(定义)
func (recevier type) methodName (参数列表) (返回值列表){
方法体
return 返回值
}
参数列表,表示方法输入;
receiver type : 表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型;
receiver type: type 表示可以是结构体,也可以是其他的自定义类型;
receiver: 就是type类型的一个变量(实例),比如:Person结构体的一个变量(实例)
返回值列表,表示返回的值,可以多个
方法主体,表示为了实现某一功能代码块
return 语句不是必须的(有返回值列表就必须有return);
方法注意事项和细节
1.结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式;
2.如果程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
3.Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定)因此自定义类型,都可以有方法,而不仅仅是struct, 比如int,float32等都可以有方法;
4.方法的访问范围控制的规则,和函数一样,方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其他包访问;
5.如果一个类型实现了String() 这个方法,那么fmt.Println 默认会调用这个变量的String() 进行输出;
示例:
type Circle struct {
Radius float64
}
// 为了提高效率,通常我们方法和结构体的指针类型绑定
func (c *Circle) area() float64 {
// 因为 c 是指针,因此我们标准的访问其字段方式 是 (*c).Radius
// return 3.14 * (*c).Radius * (*c).Radius
// (*c).radius 等价 c.radius
c.Radius = 10
return 3.14 * c.Radius * c.Radius
}
// 创建一个Circle 变量
var c Circle
// res := (&c).area()
// 编译器底层做了优化 (&c).area() 等价 c.area()
// 因为编译器会自动的给加上 &c
c.Radius = 7.0
res := c.area()
fmt.Println("面积是:", res) // 面积是: 314
fmt.Println("c.Radius==", c.Radius) // c.Radius== 10
实现String方法:
// 让student 结构体实现 String方法
type Student struct {
Name string
Age int
}
func (s *Student) String() string {
str := fmt.Sprintf("Name=[%v] Age=[%v]", s.Name, s.Age)
return str
}
var s Student
s.Name = "smith"
s.Age = 30
// 没实现 String方法时,会打印出 &{smith 30}, 但是实现以后 Name=[smith] Age=[30]
fmt.Println(&s)
练习:
结构体实现乘法表打印与二维数组简单转置方法
// 结构体 MethodUtils
type MethodUtils struct {
// 结构体也可以没有字段
}
// 打印乘法表
func (mu MethodUtils) cfb(n int) {
for i := 1; i <= n; i ++ {
for j := 1; j <= i; j ++ {
fmt.Printf("%v * %v = %v ", j, i, i * j)
}
fmt.Println()
}
}
// 转置
func (mu MethodUtils) zz(ewz [3][3]int) {
// 原始打印
for _, v := range ewz {
for _, v2 := range v {
fmt.Printf("%v ", v2)
}
fmt.Println()
}
// 转置打印
for i := 0 ; i < len(ewz); i++ {
for j := 0; j < len(ewz[i]); j++ {
fmt.Printf("%v ", ewz[j][i])
}
fmt.Println()
}
}
// 实例化
var mu MethodUtils
// 方法调用
mu.cfb(9)
var ewz = [3][3]int {{1,2,3},{4,5,6},{7,8,9}}
mu.zz(ewz)
方法与函数的区别
1.调用方式不一样;
函数的调用方式: 函数名(实参列表)
方法调用方式: 变量.方法名(实参列表)
2.对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然;
3.对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以;
不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法或函数定义的时候和哪个类型绑定的:
如果和值类型,比如(p Person) 则是值拷贝;如果是和指针类型,比如是(p *Person) 则是地址拷贝;