就像面向对象一样,可以给结构体(类)添加方法,结构体方法是通过接收者实现的
一、方法的定义
定义一个Person结构体
type Person struct {
Name string // Name字段
Age int // Age字段
City string // City字段
}
那么就可以给这个结构体添加方法
// Sayname的接收者类型是Person结构体,所以是Person结构体的方法
func (person Person) Sayname() string {
return fmt.Sprintf("Name: %v", person.Name)
}
// Infod的接收者类型是*Person结构体指针,所以是*Person结构体的方法
func (person *Person) Info string {
return fmt.Sprintf("Name: %v, Age: %v, City: %v", person.Name, person.Age, person.City)
}
然后可以基于Person结构初始化并赋值,最后调用方法
func main() {
// 初始化一个Person类型对象p
p := Person{"Bob", 20, "beijing"}
// 对象调用info方法, 把变量p传给person参数
fmt.Println(p.Sayname()) // Name: Bob
// 初始化一个Person类型的指针对象v
v := &Person{"Alice", 35, "Shanghai"}
// 对象调用Info方法,把指针变量v传给person参数
fmt.Println(v.Info()) // Name: Alice, Age: 35, City: Shanghai
}
// 求圆的面积
package main
import "fmt"
// 声明一个Circle的结构体
type Circle struct {
radius float64 // radius(半径)字段
}
// 为结构体添加一个area方法,求圆的面积
func (c Circle) area() float64 {
return c.radius * c.radius * 3.14
}
func main() {
// 初始化结构体c
c := Circle{5.2}
// 调用area方法求面积
fmt.Println(c.area()) // 84.9056
}
// 求矩形的面积
package main
import "fmt"
// 添加一个Rect结构体
type Rect struct {
width, length float64
}
// 添加一个area方法,计算矩形的面积
func (r *Rect) area() float64 {
return (*r).length * (*r).width
}
func main() {
// 初始化结构体变量r
r := &Rect{1.2, 2.4}
// 结构体变量调用area方法
fmt.Println(r.area()) // 2.88
}
package main
import "fmt"
// 来一个结构体
type Student struct {
Id int
Name string
Age int
Score float64
}
// 为结构体增加info方法
func (s *Student) info() string {
return fmt.Sprintf("Id: [%v] Name: [%v] Age: [%v] Score: [%v]",
(*s).Id, (*s).Name, (*s).Age, (*s).Score)
}
func main() {
// 声明一个Student结构体变量
student := &Student{1, "Bob", 25, 96.5}
// 结构体变量调用info方法
fmt.Println(student.info())
}
package main
import "fmt"
type Person struct {
Name string
sex int
height float64 // m
weight float64 // kg
}
// 打招呼方法
func (p *Person) Sayhello() {
fmt.Printf("%v: How are you.\n", p.Name)
}
// 计算BMI值
func (p *Person) Bmi() float64 {
return p.weight / (p.height * p.height)
}
// 修改年龄方法
func (p *Person) Setheight(height float64) {
if height < 0 {
panic("身高必须大于0")
}
p.height = height
}
func main() {
ming := &Person{"Ming", 1, 1.75, 65}
ming.Sayhello()
fmt.Println(ming.Bmi())
ming.Setheight(-1)
fmt.Println(ming.height)
}
二、方法使用细节和注意事项
1. 结构体类型是值类型, 在方法调用中遵守值类型的传递机制,是值拷贝传递方式
package main
import "fmt"
type Person struct {
Name string
Age int
}
// 为Person结构体实现change方法
func (p Person) change() {
p.Name = "Alice"
p.Age = 20
}
func main() {
// v是Person结构变量
v := Person{Name: "Bob", Age: 10}
fmt.Println(v.Name, v.Age) // Bob 10 (修改前)
// 变量v调用change方法, 将v作为实参传给p, p是v的一个值拷贝, p修改不会影响v(它们在内存是两个不同的区域)
v.change()
fmt.Println(v.Name, v.Age) // Alice 20 (修改后), 说明不能修改
}
2. 如果希望在方法中修改结构体变量的值,可以通过结构体指针的方式处理
package main
import "fmt"
type Person struct {
Name string
Age int
}
// 为Person结构体实现change方法, p是结构体指针变量
func (p *Person) change() {
(*p).Name = "Alice"
(*p).Age = 20
}
func main() {
// 结构体指针变量p
p := Person{Name: "Bob", Age: 10}
fmt.Println(p.Name, p.Age) // Bob 10 (修改前)
// 通过change方法修改Name和Age属性
p.change()
fmt.Println(p.Name, p.Age) // Alice 20 (修改后)
}
3. golang中的方法作用在指定的数据类型上(即: 和指定的数据类型绑定),因此自定义类型都可以有方法,而不仅仅是struct,比如int, float32等都可以绑定方法。 有点像扩展内置数据类型,给内置数据类型添加方法
package main
import "fmt"
// 定义一个int类型的别名integer
type integer int
// 给integer类型添加print方法
func (i integer) print() {
fmt.Println("i =", i)
}
// 给integer类型添加add方法
func (i integer) add(x integer) integer {
return i + x
}
func main() {
// 声明一个integer类型的变量s
var s integer
s.print() // 调用print方法
// 声明两个integer类型的变量x, y
var x, y integer = 1, 2
fmt.Println(x.add(y)) // 3
fmt.Println(y.add(x)) // 3
}
4. 方法名和变量名可见性规则一样,首字母小写只能在同一个包访问,首字母大写可以在其它包访问;方法只能由该类型的变量调用
5. 如果一个结构体实现了Srting方法,那么fmt.Println默认会调用这个方法输出(有点像python中的__str__或__reper__)
// 方式一: 通过指针
package main
import "fmt"
type Person struct {
Name string
Age int
}
// 为Person结构体实现String方法
func (p *Person) String() string {
return fmt.Sprintf("[Name] %v [Age] %v", (*p).Name, (*p).Age)
}
func main() {
// 结构体指针变量p
p := &Person{Name: "Bob", Age: 10}
fmt.Println(p) // [Name] Bob [Age] 10 说明调用的是Sprintf方法
}
// 方式二
package main
import "fmt"
type Person struct {
Name string
Age int
}
// 为Person结构体实现String方法
func (p Person) String() string {
return fmt.Sprintf("[Name] %v [Age] %v", p.Name, p.Age)
}
func main() {
// 结构体指针变量p
p := Person{Name: "Bob", Age: 10}
fmt.Println(p) // [Name] Bob [Age] 10 说明调用的是Sprintf方法
}
三、函数与方法的使用区别
两者在传参时需要注意
对于函数:如果函数的形参要求传指针类型,那就必须传指针类型,反之亦然 (要求传什么类型就只传什么类型)
package main
import "fmt"
// 函数的参数是非指针类型,不能传指针
func test(a, b int) int {
return a + b
}
// 函数的参数是指针类型,必须传指针
func test01(a, b *int) int {
return *a + *b
}
func main() {
x, y := 10, 20
fmt.Println(test(&x, &y)) // 报错,不能传指针类型
fmt.Println(test(x, y)) // 30 正确
fmt.Println(test01(&x, &y)) // 正确
fmt.Println(test01(x, y)) // 报错,需要传指针
}
对于方法: 如果方法接收者是值类型, 可以传值或指针类型,但都是按照值类型操作, 如果方法接收者是指针类型,也可以传值或指针类型,但都是按指针类型操作。也就是说最终取决于接收者的类型。这点要特别注意:
package main
import "fmt"
// 定义一个Person结构体
type Person struct {
Name string
Age int
City string
}
// 添加一个Sayname方法,接收者是值类型
func (p Person) Sayname() {
p.Name = "Alice"
fmt.Printf("[Name]: %v\n", p.Name)
}
// 添加一个Sayage方法,接收者是指针类型
func (p *Person) Sayage() {
p.Age = 200
fmt.Printf("[Age]: %v\n", p.Age)
}
func main() {
p := Person{"Bob", 20, "Shanghai"}
// 1. 结构体变量p调用Sayname方法,接收者类型是值类型,内部对Name字段进行修改,不会修改原先的p.Name属性
p.Sayname() // [Name]: Alice
// 2. 结构体指针变量p调用Sayname方法,接收者类型是指针类型,内部对Name字段进行修改,也不会修改原先的p.Name属性( 为什么? )
// 因为接收者是值类型, 即使传指针类型当成值类型
(&p).Sayname() // [Name]: Alice
fmt.Println(p.Name) // Bob
结论:对于1和2调用结果是等价的,因为接收者是值类型,怎么都是按照值类型操作
// 3. 通过结构体变量p调用Sayage方法,接收者是指针类型, 内部对Age字段修改,也会修改原先的p.Age属性( 为什么 ?)
// p只是结构体变量,但调用时会把它转换成结构体指针(&p)
p.Sayage() // [Age]: 200
// 4. 通过指针变量&p调用Sayage方法,接收者是指针类型,内部对Age字段修改,也会修改原先的p.Age属性
(&p).Sayage() // [Age]: 200
fmt.Println(p.Age) // 200
结论:对于3和4调用结果是等价的,因为接收者是指针类型,怎么都是按照指针类型操作
}
四、工厂函数
go里面没有像其它语言中的构造函数, 可以通过工厂函数来实现构造实例。是实现封装和隐藏的一种方式
package main
import "fmt"
// person结构,小写开头表示私有(封装与隐藏)
type person struct {
Name string
City string
Sex int
}
func (p *person) Sayhello() {
fmt.Println("hello: ", p.Name)
}
// 工厂函数,返回结构体类型指针
func NewPerson(name, city string, sex int) *person {
return &person{name, city, sex}
}
func main() {
var ming *person
ming = NewPerson("ming", "beijing", 23) // 通过工厂函数创建出实例
fmt.Println(ming.Name, ming.City)
ming.Sayhello()
}