mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-12 05:33:04 +08:00
ch10.6.4 OK
This commit is contained in:
173
eBook/10.6.md
173
eBook/10.6.md
@@ -98,11 +98,11 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
** 练习 10.6: employee_salary.go
|
||||
* 练习 10.6:* employee_salary.go
|
||||
|
||||
定义结构体employee,它有一个salary字段,给这个结构体定义一个方法giveRaise来按照指定的百分比增加薪水。
|
||||
|
||||
** 练习 10.7: iteration_list.go
|
||||
* 练习 10.7:* iteration_list.go
|
||||
|
||||
下面这段代码有什么错?
|
||||
|
||||
@@ -169,6 +169,175 @@ First 3 chars: Mon
|
||||
*/
|
||||
```
|
||||
|
||||
## 10.6.2 函数和方法的区别
|
||||
|
||||
函数将变量作为参数:*Function1(recv)*
|
||||
|
||||
方法在变量上被调用:*recv.Method1()*
|
||||
|
||||
在接受者是指针时,方法可以改变接受者的值(或状态),这点函数也可以做到(当参数作为指针传递,即通过引用调用时,函数也可以改变参数的状态)。
|
||||
|
||||
!!不要忘记Method1后边的括号(),否则会引发编译器错误:*method recv.Method1 is not an expression, must be called *!!
|
||||
|
||||
接受者必须有一个显式的名字,这个名字必须在方法中被使用。
|
||||
|
||||
*receiver_type*叫做*(接受者)基本类型*,这个类型必须在和方法同样的包中被声明。
|
||||
|
||||
在Go中,(接受者)类型关联的方法不写在类型结构里面,就像类那样;耦合更加宽松;类型和方法之间的关联由接受者来建立。
|
||||
|
||||
*方法没有和数据定义(结构体)混在一起:它们是正交的类型;表示(数据)和行为(方法)是独立的。*
|
||||
|
||||
## 10.6.2 指针或值作为接受者
|
||||
|
||||
鉴于性能的原因,recv最常见的是一个指向receiver_type的指针(因为我们不想要一个实例的拷贝,如果按值调用的话就会是这样),特别是在receiver类型是结构体时,就更这样了。
|
||||
|
||||
如果想要方法改变接受者的数据,就在接受者的指针类型上定义该方法。否则,就在普通的值类型上定义方法。
|
||||
|
||||
下面的例子pointer_value.go作了说明:change()接受一个指向B的指针,并改变它内部的成员;write()接受通过拷贝接受B的值并只输出B的内容。注意Go为我们做了探测工作,我们自己并没有指出是是否在指针上调用方法,Go替我们做了这些事情。b1是值而b2是指针,方法都支持运行了。
|
||||
|
||||
Listing 10.13—pointer_value.go:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type B struct {
|
||||
thing int
|
||||
}
|
||||
|
||||
func (b *B) change() { b.thing = 1 }
|
||||
|
||||
func (b B) write() string { return fmt.Sprint((b)) }
|
||||
|
||||
func main() {
|
||||
var b1 B // b1是值
|
||||
b1.change()
|
||||
fmt.Println(b1.write())
|
||||
|
||||
b2 := new(B) // b2是指针
|
||||
b2.change()
|
||||
fmt.Println(b2.write())
|
||||
}
|
||||
|
||||
/* 输出:
|
||||
{1}
|
||||
{1}
|
||||
*/
|
||||
```
|
||||
|
||||
试着在write()中改变接受者b的值:将会看到它可以正常编译,但是开始的b没有被改变。
|
||||
|
||||
我们知道方法不需要指针作为接受者,如下面的例子,我们只是需要Point3的值来做计算:
|
||||
|
||||
```go
|
||||
type Point3 struct { x, y, z float }
|
||||
// A method on Point3
|
||||
func (p Point3) Abs float {
|
||||
return math.Sqrt(p.x*p.x + p.y*p.y + p.z*p.z)
|
||||
}
|
||||
```
|
||||
|
||||
这样做稍微有点昂贵,因为Point3是作为值传递给方法的,因此传递的是它的拷贝,这在Go中合法的。也可以在指向这个类型的指针上调用此方法(会自动解引用)。
|
||||
|
||||
假设p3定义为一个指针:* p3 := &Point{ 3, 4, 5}*
|
||||
|
||||
可以这样写: * p3.Abs() 来替代 (*p3).Abs() *
|
||||
|
||||
像例子10.11(method1.go)中接受者类型是*TwoInts的方法AddThem(),它能在类型TwoInts的值上被调用,这是自动间接发生的。
|
||||
|
||||
因此two2.AddThem可以替代(&two2).AddThem()。
|
||||
|
||||
在值和指针上调用方法:
|
||||
|
||||
可以有连接到类型的方法,也可以有连接到类型指针的方法。
|
||||
|
||||
*但是这没关系:对于类型T,如果在*T上存在方法Meth(),并且t是这个类型的变量,那么t.Meth()会被自动转换为(&t).Meth().*
|
||||
|
||||
*指针方法和值方法都可以在指针或非指针上被调用*,如下面程序所示,类型List在值上有一个方法Len(),在指针上有一个方法Append(),但是可以看到两个方法都可以在两种类型的变量上被调用。
|
||||
|
||||
Listing 10.14—methodset1.go:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type List []int
|
||||
|
||||
func (l List) Len() int { return len(l) }
|
||||
func (l *List) Append(val int) { *l = append(*l, val) }
|
||||
|
||||
func main() {
|
||||
// 值
|
||||
var lst List
|
||||
lst.Append(1)
|
||||
fmt.Printf("%v (len: %d)", lst, lst.Len()) // [1] (len: 1)
|
||||
|
||||
// 指针
|
||||
plst := new(List)
|
||||
plst.Append(2)
|
||||
fmt.Printf("%v (len: %d)", plst, plst.Len()) // &[2] (len: 1)
|
||||
}
|
||||
```
|
||||
|
||||
** 10.6.4 方法和未导出字段
|
||||
|
||||
考虑person2.go中的person包:类型Person被明确的导出了,但是它的字段没有被导出。例如在use_person2.go中p.firsetname就是错误的。该如何在另一个程序中修改或者只是读取一个Person的名字呢?
|
||||
|
||||
这可以通过OO语言一个众所周知的技术来完成:提供getter和setter方法。对于setter方法使用Set前缀,对于getter方法只适用成员名。
|
||||
|
||||
Listing 10.15—person2.go:
|
||||
|
||||
```go
|
||||
package person
|
||||
|
||||
type Person struct {
|
||||
firstName string
|
||||
lastName string
|
||||
}
|
||||
|
||||
func (p *Person) FirstName() string {
|
||||
return p.firstName
|
||||
}
|
||||
|
||||
func (p *Person) SetFirstName(newName string) {
|
||||
p.firstName = newName
|
||||
}
|
||||
```
|
||||
|
||||
Listing 10.16—use_person2.go:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"./person"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
p := new(person.Person)
|
||||
// p.firstName undefined
|
||||
// (cannot refer to unexported field or method firstName)
|
||||
// p.firstName = "Eric"
|
||||
p.SetFirstName("Eric")
|
||||
fmt.Println(p.FirstName()) // Output: Eric
|
||||
}
|
||||
```
|
||||
*并发访问对象:*
|
||||
|
||||
|
||||
对象的字段(属性)不应该由2个或2个以上的不同线程在同一时间去改变。如果在程序发生这种情况,为了安全并发访问,可以使用包sync(参考9.3)中的方法。在14.17我们会通过goroutines和channels探索另一种方式。
|
||||
|
||||
** 嵌入类型上的方法和继承
|
||||
// TODO
|
||||
|
||||
|
||||
## 链接
|
||||
- [目录](directory.md)
|
||||
- 上一节:[10.5 匿名字段和内嵌结构体](10.5.md)
|
||||
|
Reference in New Issue
Block a user