就像面向对象一样,可以给结构体(类)添加方法,结构体方法是通过接收者实现的
一、方法的定义
定义一个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() }