一、结构体变量方法基础
结构体变量不仅仅时拥有自己的属性,而且还拥有自己的方法,比如一个人不仅仅拥有姓名、年龄,他还有一些行为,这些行为在结构体变量中就可以理解为方法。Golang中的方法是与具体的数据类型进行绑定的,因此也不仅仅时结构体(struct)拥有自己的方法。
(一)快速入门
1、方法的声明
func(receiver type) methodName(参数列表) (返回值列表){ 方法体 return 返回值 }
- 参数列表:表示方法输入
- receiver type:表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型
- receiver type:type可以时结构体,也可以是其它的自定义类型
- receiver:是type类型的一个实例
- 返回值列表:表示返回的值,可以是多个
- 方法体:表示为了实现某一功能代码块
- return 语句不是必须的
比如:
package main import "fmt" type User struct { Name string Age int } func (user User) getUser() { fmt.Println(user.Name, user.Age) } func main() { var user User user.Name = "bily" user.getUser() }
- func (user User) getUser() {} 表示User结构体的方法,方法名称为getUser
- (a A)体现getUser方法和User类型绑定
- getUser方法只能通过User实例进行调用
- main函数中的user实例与getUser方法传入的user实例不是同一个,方法中是拷贝的,结构体是值传递
2、实例
package main import "fmt" type CalSum struct { n1 int n2 int } // 结构体调用方法,该方法可以有入参参数、返回值 func (cal CalSum) getSum() int { res := cal.n1 + cal.n2 return res } func main() { // 生成一个结构体变量 cal := CalSum{ 1, 2, } // 调用方法 res := cal.getSum() fmt.Println(res) }
(二)深入理解结构体变量方法
1、方法调用机制
方法与函数类似,但是最大的不同就是方法会将调用者当作参数传递给方法,比如上图中会将Cal当作参数传递给getSum方法,并且需要注意的是如果是值类型则进行值拷贝,如果是指针类型则进行引用传递,这与形参类型有关。
默认的就是值类型传递,这也就意味着方法中结构体变量是拷贝main函数中的,所以效率较低,为了提高效率可以进行引用传递。
package main import "fmt" type CalSum struct { n1 int n2 int } // 结构体调用方法,该方法可以有入参参数、返回值 func (cal *CalSum) getSum() int { res := cal.n1 + cal.n2 return res } func main() { // 生成一个结构体变量 cal := CalSum{ 1, 2, } /* 调用方法, 因为方法的形参是指针类型,所以应该是结构体变量地址调用 */ res := (&cal).getSum() // .的运算优先级高,所以(&cal)需要带括号 fmt.Println(res) // 3 /* 但是编译器在底层进行了优化,所以(&cal).getSum() 可以写成cal.getSum() */ res1 := cal.getSum() fmt.Println(res1) /* 此时main中的结构体变量与方法中传入的结构体变量相同,内存中存在形式也与值传递不同 */ }
2、方法不是结构体独有的
方法不是struct类型独有的,比如int、float系列类型的都可以拥有方法:
package main import "fmt" type integer int // integer类型方法 func (i *integer) change() { *i = *i + 5 } func main() { var i integer = 5 i.change() fmt.Println(i) // 10 }
3、方法与函数的区别
- 调用方式不同,函数是通过 函数名(实参列表) ,方法是通过 变量.方法名(实参列表)
- 对于普通函数,接收者为值类型,不能将指针类型的数据传递,反之亦然
- 对于方法,接收者为值类型时,可以直接用指针类型的变量调用方法,反之亦然
- 真正决定时值拷贝还时地址拷贝是看这个方法是和什么样的类型进行绑定,如果时值类型 (user User)则是值拷贝,如果是(user *User)则是地址拷贝
二、工厂模式
(一)引出问题
如果现在有一个model包,里面有一个结构体变量:
package model // 定义一个结构体 type User struct { Name string Age int }
如果在其它包,比如main包中去创建User结构体实例,这时没问题的,因为User结构体首字母是大写的,但是如果User是小写首字母这就是私有变量这能在本包访问,如果解决跨包访问私有变量的问题呢?这就需要使用工厂模式,简单的说就是定一个公有方法对外提供接口的方式。
(二)方案解决
1、正常方案
- User.go
package model // 定义一个结构体 type User struct { Name string Age int }
- main.go
package main import ( "fmt" "go_tutorial/day12/factorMode/model" ) func main() { user := model.User{Name: "lily", Age: 20} fmt.Println(user) }
- 目录结构
├─factorMode ├─main │ main.go │ └─model user.go
上面User.go中的结构体变量是大写首字母是没问题,但是如果小写就会出现私有变量无法跨包问题,此时使用工厂模式。
2、工厂模式解决私有变量跨包问题
- user.go
package model // 定义一个私有结体 type user struct { Name string Age int } // 工厂方法,它包可以调用该方法 func NewUser(name string, age int) *user { return &user{ Name: name, Age: age, } }
- main.go
package main import ( "fmt" "go_tutorial/day12/factoryMode01/model" ) func main() { user := model.NewUser("lily", 20) fmt.Println(*user) // {lily 20} fmt.Println(user.Name, user.Age) // lily 20 }
- 目录结构
├─factorMode ├─main │ main.go │ └─model user.go
3、工厂模式解决私有字段跨包问题
上述是私有结构体变量跨包问题,但是如果是结构体中的字段首字母小写私有变量,那又如何解决呢?
- user.go
... // 私有字段name工厂方法 func GetName(u *user) string { return u.name } ...
- main.go
... func main() { user := model.NewUser("lily", 20) fmt.Println(model.GetName(user)) //lily }
三、实例演练
创建一个Box结构体,在其中:
- 声明三个字段分别表示立方体的长、宽、高,并且从终端获取
- 声明一个方法获取立方体体积
- 创建一个Box结构体变量,打印给定尺寸的立方体的体积
package main import "fmt" type Cell struct { length float64 width float64 height float64 } func (cell *Cell) GetVolume() float64 { return cell.length * cell.width * cell.height } func main() { // 创建一个立方体结构体变量 var cell Cell // 为每个字段赋值 fmt.Print("请输入立方体的长:") fmt.Scanln(&cell.length) fmt.Print("请输入立方体的宽:") fmt.Scanln(&cell.width) fmt.Print("请输入立方体的高:") fmt.Scanln(&cell.height) // 调用结构体变量的方法 res := cell.GetVolume() fmt.Println(res) }