Signed-off-by: Tinywan <756684177@qq.com>
pull/11/head
Tinywan 7 years ago
parent 741d9b9f8a
commit c0d3cff238

@ -0,0 +1,10 @@
package main
// interface define 元音
type VowelsFinder interface {
FindVowels() []rune
}
func main() {
}

@ -0,0 +1,19 @@
package main
import (
"fmt"
)
func main() {
byteSlice := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9}
str := string(byteSlice)
fmt.Println(str)
byteSlice2 := []byte{67, 97, 102, 195, 169} //decimal equivalent of {'\x43', '\x61', '\x66', '\xC3', '\xA9'}
str2 := string(byteSlice2)
fmt.Println(str2)
runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
str3 := string(runeSlice)
fmt.Println(str3)
}

@ -0,0 +1,17 @@
package main
import (
"fmt"
"unicode/utf8"
)
func length(s string) {
fmt.Printf("length of %s is %d\n", s, utf8.RuneCountInString(s))
}
func main() {
word1 := "Señor"
length(word1)
word2 := "Pets"
length(word2)
}

@ -0,0 +1,42 @@
package main
import (
"fmt"
)
// 打印字符串中的字节
func printBytes(s string) {
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
// 打印字符串中的字符
func printChars(s string) {
runes := []rune(s)
for i := 0; i < len(runes); i++ {
fmt.Printf("%c", runes[i])
}
}
func main() {
name := "Hello World"
printBytes(name)
fmt.Printf("\n")
printChars(name)
fmt.Printf("\n")
// 特殊字符
name = "Señor"
printBytes(name)
fmt.Printf("\n")
printChars(name)
// 打印汉字
fmt.Printf("\n")
name = "萬少波"
printBytes(name)
fmt.Printf("\n")
printChars(name)
}

@ -9,9 +9,9 @@
string 类型单独提取为一篇教程是因为在 Go 中string 的实现方式同其他语言的不同。
## 什么是字符串?
## 访问字符串中的字节
在 Go 中字符串是 byte 数组。可以通过将内容放在双引号 "" 之间的方式来创建一个字符串。让我们看一个简单的例子,该例子创建并打印一个字符串:
因为字符串是字节数组,因此可以访问一个字符串中的字节。
```golang
package main
@ -20,26 +20,21 @@ import (
"fmt"
)
// 打印字符串中的字节
func printBytes(s string) {
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func main() {
name := "Hello World"
fmt.Println(name)
printBytes(name)
}
```
上面输出:`Hello World`。
Go 中的字符串符合 Unicode 标准,并以 UTF-8 编码。
## 如何创建 map
可以通过将键和值的类型传递给内置函数 `make` 来创建一个 `map`。语法为:`make(map[KeyType]ValueType)`。(译者注:`map` 的类型表示为 `map[KeyType]ValueType`)例如:
```golang
personSalary := make(map[string]int)
```
在上面的程序中len(s) 返回字符串中的字节数,我们用一个 for 循环以 16 进制打印这些字节。%x 格式化指示符用来以 16 进制打印参数。上面的程序打印48 65 6c 6c 6f 20 57 6f 72 6c 64。它们是 "Hello World" 以UTF-8方式编码的Unicode值。对 Unicode 字符集和 UTF-8 编码有一个基本的了解会更好的理解 string 类型。我原文作者建议大家阅读https://naveenr.net/unicode-character-set-and-utf-8-utf-16-utf-32-encoding/ 来学习什么是 Unicode 和 UTF-8。
上面的代码创建了一个名为 `personSalary` 的 map。其中键的类型为 string值的类型为 int。
**map 的 0 值为 `nil`。试图给一个 nil map 添加元素给会导致运行时错误。因此 map 必须通过 make 来初始化** (译者注:也可以使用速记声明来创建 map见下文
让我们修改上面的程序以打印字符串中的字符:
```golang
package main
@ -48,39 +43,34 @@ import (
"fmt"
)
func main() {
var personSalary map[string]int
if personSalary == nil {
fmt.Println("map is nil. Going to make one.")
personSalary = make(map[string]int)
func printBytes(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
```
上面的程序中,`personSalary` 为 `nil`,因此使用 make 初始化它。程序的输出为:`map is nil. Going to make one`.
## 向 map 中插入元素
插入元素给 map 的语法与数组相似。下面的代码插入一些新的元素给 `map personSalary`
```golang
package main
import (
"fmt"
)
func printChars(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%c ",s[i])
}
}
func main() {
personSalary := make(map[string]int)
personSalary["steve"] = 12000
personSalary["jamie"] = 15000
personSalary["mike"] = 9000
fmt.Println("personSalary map contents:", personSalary)
name := "Hello World"
printBytes(name)
fmt.Printf("\n")
printChars(name)
}
```
在第 16 行的 printChars 函数中,%c 格式化指示符用来打印字符串中的字符。上面的程序输出为:
上面的程序输出:`personSalary map contents: map[steve:12000 jamie:15000 mike:9000]`。
```golang
48 65 6c 6c 6f 20 57 6f 72 6c 64
Hello World
```
也可以在声明时初始化一个数组:
虽然上面的程序看起来是一种合法的打印字符串中各个字符的方法,但是这里有一个严重的错误。让我们深入这段代码看看究竟是哪里不对。
```golang
package main
@ -89,27 +79,41 @@ import (
"fmt"
)
func main() {
personSalary := map[string]int {
"steve": 12000,
"jamie": 15000,
func printBytes(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
personSalary["mike"] = 9000
fmt.Println("personSalary map contents:", personSalary)
}
```
上面的程序在声明 personSalary 的同时向其中插入了两个元素。接着插入了一个以 "mike" 为键的元素。程序的输出为:
func printChars(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%c ",s[i])
}
}
func main() {
name := "Hello World"
printBytes(name)
fmt.Printf("\n")
printChars(name)
fmt.Printf("\n")
name = "Señor"
printBytes(name)
fmt.Printf("\n")
printChars(name)
}
```
上面程序的输出为:
```golang
personSalary map contents: map[steve:12000 jamie:15000 mike:9000]
48 65 6c 6c 6f 20 57 6f 72 6c 64
Hello World
53 65 c3 b1 6f 72
Señor
```
`string` 并不是可以作为键的唯一类型其他所有可以比较的类型比如布尔类型整型浮点型复数类型都可以作为键。如果你想了解更多关于可比较类型的话请参阅http://golang.org/ref/spec#Comparison_operators
## 访问 map 中的元素
## rune
现在我们已经添加了一些元素给 map现在让我们学习如何从 map 中提取它们。根据键获取值的语法为:`map[key]`,例如:
rune 是 Go 中的内置类型,它是 int32 的别名。在 Go 中rune 表示一个 Unicode 码点。无论一个码点会被编码为多少个字节,它都可以表示为一个 rune。让我们修改上面的程序使用 rune 来打印字符串中的字符。
```golang
package main
@ -118,56 +122,45 @@ import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
func printBytes(s string) {
for i:= 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
personSalary["mike"] = 9000
employee := "jamie"
fmt.Println("Salary of", employee, "is", personSalary[employee])
}
```
上面的程序非常简单。员工 `jamie` 的工资被取出并打印。程序的输出为:`Salary of jamie is 15000`。
如果一个键不存在会发生什么?`map` 会返回值类型的 `0 `值。比如如果访问了 `personSalary` 中的不存在的键,那么将返回 `int` 的 0 值,也就是 0。
```golang
package main
import (
"fmt"
)
func printChars(s string) {
runes := []rune(s)
for i:= 0; i < len(runes); i++ {
fmt.Printf("%c ",runes[i])
}
}
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
employee := "jamie"
fmt.Println("Salary of", employee, "is", personSalary[employee])
fmt.Println("Salary of joe is", personSalary["joe"])
name := "Hello World"
printBytes(name)
fmt.Printf("\n")
printChars(name)
fmt.Printf("\n\n")
name = "Señor"
printBytes(name)
fmt.Printf("\n")
printChars(name)
}
```
上面的程序输出为:
在上面的程序中,第 14 行,字符串被转换为 tune 切片。然后我们遍历该切片并打印其中的字符。程序的输出如下:
```golang
Salary of jamie is 15000
Salary of joe is 0
48 65 6c 6c 6f 20 57 6f 72 6c 64
Hello World
53 65 c3 b1 6f 72
Señor
```
上面的输出是正确的。这正是我们想要的结果。
上面的程序返回 `joe` 的工资为` 0`。我们没有得到任何运行时错误说明键 joe 在 `personSalary` 中不存在。
## 使用 range for 遍历字符串
我们如何检测一个键是否存在于一个 map 中呢?可以使用下面的语法:
```golang
value, ok := map[key]
```
上面的语法可以检测一个特定的键是否存在于 map 中。如果 `ok``true`则键存在value 被赋值为对应的值。如果 `ok``false`,则表示键不存在。
上面的程序是遍历字符串中字符的一个正确方式。但是 Go 提供了一种更简单的方式来做到这一点:使用 range for。
```golang
package main
@ -176,30 +169,31 @@ import (
"fmt"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
func printCharsAndBytes(s string) {
for index, rune := range s {
fmt.Printf("%c starts at byte %d\n", rune, index)
}
personSalary["mike"] = 9000
newEmp := "joe"
value, ok := personSalary[newEmp]
if ok == true {
fmt.Println("Salary of", newEmp, "is", value)
} else {
fmt.Println(newEmp,"not found")
}
func main() {
name := "Señor"
printCharsAndBytes(name)
}
```
在上面的程序中,第 15 行,`ok` 应该为 `false` ,因为 `joe` 不存在。因此程序的输出为:
在上面的程序中,第 8 行通过使用 range for 遍历字符串。range 返回一个 rune (在 byte 数组中)的位置,以及 rune 本身。上面的程序输出为:
```golang
joe not found
S starts at byte 0
e starts at byte 1
ñ starts at byte 2
o starts at byte 4
r starts at byte 5
```
range for 可用于遍历 map 中所有的元素(译者注:这里 range 操作符会返回 map 的键和值)。
从上面的输出可以看到,ñ 占两个字节:)
## 通过 byte 切片创建字符串
```golang
package main
@ -209,32 +203,14 @@ import (
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("All items of a map")
for key, value := range personSalary {
fmt.Printf("personSalary[%s] = %d\n", key, value)
}
byteSlice := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9}
str := string(byteSlice)
fmt.Println(str)
}
```
在上面的程序中byteSlice 是 "Café" 经过 UTF-8 编码后得到的切片(用 16 进制表示) 。上面的程序输出为Café。
上面的程序输出如下:
```golang
All items of a map
personSalary[mike] = 9000
personSalary[steve] = 12000
personSalary[jamie] = 15000
```
值得注意的是,因为 map 是无序的,因此对于程序的每次执行,不能保证使用 range for 遍历 map 的顺序总是一致的。
## 删除元素
`delete(map, key) `用于删除 map 中的 key。delete 函数没有返回值。
如果我们换成对应的十进制数程序会正常工作吗答案是Yes。让我们测试一下
```golang
package main
@ -244,29 +220,14 @@ import (
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
byteSlice := []byte{67, 97, 102, 195, 169}//decimal equivalent of {'\x43', '\x61', '\x66', '\xC3', '\xA9'}
str := string(byteSlice)
fmt.Println(str)
}
personSalary["mike"] = 9000
fmt.Println("map before deletion", personSalary)
delete(personSalary, "steve")
fmt.Println("map after deletion", personSalary)
}
```
上面的程序删除以 `steve` 为键的元素。程序输出为:
```golang
map before deletion map[steve:12000 jamie:15000 mike:9000]
map after deletion map[mike:9000 jamie:15000]
```
上面的程序同样输出Café。
## map 的大小
用内置函数 `len` 获取 map 的大小:
## 通过 rune 切片创建字符串
```golang
package main
@ -275,164 +236,89 @@ import (
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println("length is", len(personSalary))
runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
str := string(runeSlice)
fmt.Println(str)
}
```
在上面的程序中runeSlice 包含了字符串 "Señor" 的 Unicode 码点(以 16 进制表示。程序的输出为Señor。
上面程序中,`len(personSalary) `获取 `personSalary` 的大小。上面的程序输出:`length is 3`。
## map 是引用类型
## 字符串的长度
utf8 包 提供了 func RuneCountInString(s string) (n int) 来获取字符串的长度,该方法接受一个字符串作为参数,并返回该字符串中 rune 的数量。
与切片一样map 是引用类型。当一个 map 赋值给一个新的变量,它们都指向同一个内部数据结构。因此改变其中一个也会反映到另一个:
(译者注: RuneCountInString 返回字符串中 Unicode 字符的个数,而 len 返回字符串中 byte 的个数,注意两者的区别。
```golang
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
func length(s string) {
fmt.Printf("length of %s is %d\n", s, utf8.RuneCountInString(s))
}
personSalary["mike"] = 9000
fmt.Println("Original person salary", personSalary)
newPersonSalary := personSalary
newPersonSalary["mike"] = 18000
fmt.Println("Person salary changed", personSalary)
func main() {
word1 := "Señor"
length(word1)
word2 := "Pets"
length(word2)
}
```
上面的程序中,第 14 行,`personSalary` 赋值给 `newPersonSalary`。下一行,将 `newPersonSalary``mike` 的工资改为 `18000`。那么在 `personSalary``mike` 的工资也将变为 `18000`。程序的输出如下
上面程序的输出为
```golang
Original person salary map[steve:12000 jamie:15000 mike:9000]
Person salary changed map[jamie:15000 mike:18000 steve:12000]
length of Señor is 5
length of Pets is 4
```
将 map 作为参数传递给函数也是一样的。在函数中对 map 的任何修改都会影响在调用函数中看到。
## 比较 map
## 字符串是不可变的
map 不能通过 `== `操作符比较是否相等。`== `操作符只能用来检测 map 是否为 nil
在 Go 中字符串是不可变的。字符串一旦被创建就无法改变。
```golang
package main
func main() {
map1 := map[string]int{
"one": 1,
"two": 2,
}
map2 := map1
import (
"fmt"
)
if map1 == map2 {
func mutate(s string)string {
s[0] = 'a'//any valid unicode character within single quote is a rune
return s
}
func main() {
h := "hello"
fmt.Println(mutate(h))
}
```
上面的程序将会报错:`invalid operation: map1 == map2 (map can only be compared to nil)`。
比较两个 map 是否相等的方式是一一比较它们的元素是否相等。我会鼓励你为此编写一个程序,使其工作:)
我(原文作者)已经将我们讨论的所有概念汇总到一个程序中,你可以从 [github](https://github.com/golangbot/arraysandslices) 下载。
上面的程序中,第 8 行我们试图改变字符串的第一个字符为 a。因为字符串是不可变的因此这是非法的将会报错main.go:8: cannot assign to s[0]。
## 知识扩展
[Go编程基础视频教程笔记](https://study.163.com/course/courseLearn.htm?courseId=306002#/learn/video?lessonId=421019&courseId=306002)
为了改变一个字符串中的字符,我们需要先把字符串转换为 rune 切片,然后修改切片中的内容,最后将这个切片转换回字符串。
```golang
package main
import (
"fmt"
"sort"
)
func main(){
// 方式一
var m map[int]string // 声明一个map
fmt.Println(m)
m = map[int]string{} // 初始化一个map
fmt.Println(m)
// 方式二
var m2 map[int]string = map[int]string{}
fmt.Println(m2)
// 方式三
m3 := map[int]string{}
fmt.Println(m3)
// 设置、获取、删除
m3[1] = "Tinywan"
a := m3[1]
fmt.Println(m3) // map[1:Tinywan]
fmt.Println(a) // Tinywan
delete(m3,1) // 删除一个map
fmt.Println(m3) // map[]
// 复杂map 的操作
var m5 map[int]map[int]string // 定义
m5 = make(map[int]map[int]string) // 通过 make 初始化 最外层的 map
m5[1] = make(map[int]string) // 针对外层value 的map进行初始化
m5[1][1] = "OK"
m_a := m5[1][1] // 取出map 的值赋予一个变量
fmt.Println(m_a) // OK
// 判断一个map 有没有被初始化,使用多返回值判断
m_b, ok := m5[2][1]
// 判断是否被初始化操作
if !ok {
m5[2] = make(map[int]string)
func mutate(s []rune) string {
s[0] = 'a'
return string(s)
}
m5[2][1] = "OK b"
m_b,ok = m5[2][1]
fmt.Println(m_b, ok) // OK b true
// 迭代操作
s_map := make([]map[int]string,5) // 以 map 为元素的slice 使用 make 创建一个切片,元素的slic
for _,v := range s_map {
v = make(map[int]string) // v 是值的拷贝
v[1] = "OK"
fmt.Println(v);
func main() {
h := "hello"
fmt.Println(mutate([]rune(h)))
}
fmt.Println(s_map)
```
// 针对一个 map 直接操作
for i := range s_map {
s_map[i] = make(map[int]string)
s_map[i][1] = "OK"
fmt.Println(s_map[i]);
}
fmt.Println(s_map)
// map 的间接排序
// map 集合
map01 := map[int]string{1:"a", 2:"b", 3:"n", 4:"c", 5:"p", 6:"f"}
// 切片
slice01 := make([]int, len(map01))
i := 0
for k, _ := range map01 {
slice01[i] = k
i++
}
在上面的程序中,第 7 行 mutate 函数接受一个 rune 切片作为参数。然后将该切片的第一个元素改为 a最后再转换回字符串并返回。该函数在程序中的第 13 行被调用。h 被转换为一个 rune 切片传递给 mutate。程序的输出为aello。
fmt.Println(slice01) // 返回的是一个无序的数组:[5 6 1 2 3 4] [3 4 5 6 1 2]
sort.Ints(slice01)
fmt.Println(slice01) // 有序的数组:[1 2 3 4 5 6]
}
```
字符串的介绍到此为止。感谢阅读。
希望你喜欢阅读。请留下宝贵的意见和反馈:)

@ -0,0 +1,476 @@
17 - 方法
========================
上一节:[第十六篇 结构体](/docs/golang_tutorial_16.md)
下一节:[第十八篇 接口一](/docs/golang_tutorial_18.md)
这是本Golang系列教程的第17篇。
## 什么是方法?
一个方法只是一个函数,它有一个特殊的接收者(`receiver`)类型,该接收者放在 `func` 关键字和函数名之间。接收者可以是结构体类型或非结构体类型。可以在方法内部访问接收者。
通过下面的语法创建一个方法:
```golang
func (t Type) methodName(parameter list) {
}
```
上面的代码片段创建了一个名为 `methodName` 的方法,该方法有一个类型为 Type 的接收者。
## 案例
让我们编写一个简单的程序,它创建一个结构体类型的方法并调用它。
```golang
package main
import (
"fmt"
)
type Employee struct {
name string
salary int
currency string
}
/*
displaySalary() method has Employee as the receiver type
*/
func (e Employee) displaySalary() {
fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}
func main() {
emp1 := Employee {
name: "Sam Adolf",
salary: 5000,
currency: "$",
}
emp1.displaySalary() //Calling displaySalary() method of Employee type
}
```
[在 Playground 中运行](https://play.golang.org/p/3khFtFJdbee)
上面程序的第 6 行,我们为 `Employee` 创建了一个名为 `displaySalary` 的方法。在 `displaySalary()` 方法内部可以访问它的接收者 `e` (类型为 `Employee`)。在第 17 行,我们使用接收者 `e`,并打印它的 `name``currency` 以及 `salary`
程序的输出为:
```golang
Salary of Sam Adolf is $5000
```
## 为什么使用方法而不是函数?
上面的程序可以使用函数而不是方法重写如下:
```golang
package main
import (
"fmt"
)
type Employee struct {
name string
salary int
currency string
}
/*
displaySalary() method converted to function with Employee as parameter
*/
func displaySalary(e Employee) {
fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}
func main() {
emp1 := Employee{
name: "Sam Adolf",
salary: 5000,
currency: "$",
}
displaySalary(emp1)
}
```
[在 Playground 中运行](https://play.golang.org/p/xKqdal-DqHT)
在上面的程序中,我们使用 `displaySalary` 函数替换了方法,并将 `Employee` 结构体作为参数传给它。该程序的输出与上面的程序输出一样:`Salary of Sam Adolf is $5000`。
那么为什么我们可以用函数完成同样的工作,却还要使用方法呢?这里有几个原因,我们一个一个地看。
* [Go 不是一个纯面向对象的编程语言](https://golang.org/doc/faq#Is_Go_an_object-oriented_language),它不支持 `class` 类型。因此通过在一个类型上建立方法来实现与 `class` 相似的行为。
* 同名方法可以定义在不同的类型上,但是 Go 不允许同名函数。假设我们有一个 `Square``Circle` 两个结构体。在 `Square``Circle` 上定义同名的方法是合法的,比如下面的程序:
```golang
package main
import (
"fmt"
"math"
)
type Rectangle struct {
length int
width int
}
type Circle struct {
radius float64
}
func (r Rectangle) Area() int {
return r.length * r.width
}
func (c Circle) Area() float64 {
return math.Pi * c.radius * c.radius
}
func main() {
r := Rectangle{
length: 10,
width: 5,
}
fmt.Printf("Area of rectangle %d\n", r.Area())
c := Circle{
radius: 12,
}
fmt.Printf("Area of circle %f", c.Area())
}
```
程序的输出为:
```golang
Area of rectangle 50
Area of circle 452.389342
```
接口正是应用了这一点(译者注:*相同的方法名可以用在不同的接收者类型上*)。我们将在下面的教程中讨论接口的细节。
## 指针接收者 vs. 值接收者
目前为止我们看到的都是以值作为接收者。以指针为接收者也是可以的。两者的区别在于,以指针作为接收者时,方法内部对其的修改对于调用者是可见的,但是以值作为接收者却不是。让我们通过下面的程序帮助理解。
```golang
package main
import (
"fmt"
)
type Employee struct {
name string
age int
}
/*
Method with value receiver
*/
func (e Employee) changeName(newName string) {
e.name = newName
}
/*
Method with pointer receiver
*/
func (e *Employee) changeAge(newAge int) {
e.age = newAge
}
func main() {
e := Employee{
name: "Mark Andrew",
age: 50,
}
fmt.Printf("Employee name before change: %s", e.name)
e.changeName("Michael Andrew")
fmt.Printf("\nEmployee name after change: %s", e.name)
fmt.Printf("\n\nEmployee age before change: %d", e.age)
(&e).changeAge(51)
fmt.Printf("\nEmployee age after change: %d", e.age)
}
```
上面的程序中, `changeName` 方法有一个值接收者 (`e Employee`),而 `changeAge` 方法有一个指针接收者 (`e *Employee`)。在 `changeName` 中改变 `Employee` 的字段 `name` 的值对调用者而言是不可见的,因此程序在调用 `e.changeName("Michael Andrew")` 方法之前和之后,打印的 `name` 是一致的。而因为 `changeAge` 的接受者是一个指针 (`e *Employee`),因此通过调用方法 `(&e).changeAge(51)` 来修改 `age` 对于调用者是可见的。 程序的输出如下:
```golang
Employee name before change: Mark Andrew
Employee name after change: Mark Andrew
Employee age before change: 50
Employee age after change: 51
```
在上面的程序第36行我们用 `(&e).changeAge(51)` 来调用 `changeAge` 方法。因为 `changeAge` 有一个指针类型的接收者我们必须使用 `(&e)` 来调用。这不是必须的Go允许我们省略 `&` 符号,因此可以只写为 `e.changeAge(51)`。Go 将 `e.changeAge(51)` 解析为 `(&e).changeAge(51)`
下面的程序使用 `e.changeAge(51)` 来替代 `(&e).changeAge(51)`。它与上面的程序的打印结果是一样的。
程序的输出为:
```golang
package main
import (
"fmt"
)
type Employee struct {
name string
age int
}
/*
Method with value receiver
*/
func (e Employee) changeName(newName string) {
e.name = newName
}
/*
Method with pointer receiver
*/
func (e *Employee) changeAge(newAge int) {
e.age = newAge
}
func main() {
e := Employee{
name: "Mark Andrew",
age: 50,
}
fmt.Printf("Employee name before change: %s", e.name)
e.changeName("Michael Andrew")
fmt.Printf("\nEmployee name after change: %s", e.name)
fmt.Printf("\n\nEmployee age before change: %d", e.age)
e.changeAge(51)
fmt.Printf("\nEmployee age after change: %d", e.age)
}
```
## 何时使用指针接收者,何时使用值接收者?
一般来讲,指针接收者可用于对接收者的修改应该对调用者可以见的场合。
指针接收者也可用于拷贝结构体代价较大的场合。考虑一个包含较多字段的结构体,若使用值作为接收者则必须拷贝整个结构体,这样的代价很大。这种情况下使用指针接收者将避免结构体的拷贝,而仅仅是指向结构体指针的拷贝。
其他情况下可以使用值接收者。
## 匿名字段函数
匿名字段的方法可以被包含该匿名字段的结构体的变量调用,就好像该匿名字段的方法属于包含该字段的结构体一样。
```golang
package main
import (
"fmt"
)
type address struct {
city string
state string
}
func (a address) fullAddress() {
fmt.Printf("Full address: %s, %s", a.city, a.state)
}
type person struct {
firstName string
lastName string
address
}
func main() {
p := person{
firstName: "Elon",
lastName: "Musk",
address: address {
city: "Los Angeles",
state: "California",
},
}
p.fullAddress() //accessing fullAddress method of address struct
}
```
在上面的程序中第32行我们通过 `p.fullAddress()` 调用了 `address` 的方法 `fullAddress()`。像 `p.address.fullAddress()` 这样的直接调用是不必要的。程序的输出为:
```golang
Full address: Los Angeles, California
```
## 方法的值接收者 vs. 函数的值参数
这是很多新手遇到的问题。我们将尽可能把它说明白。
当一个函数有一个值参数时,它只接受一个值参数。
当一个方法有一个值接收者时,它可以接受值和指针接收者。
让我们通过程序说明这一点。
```golang
package main
import (
"fmt"
)
type rectangle struct {
length int
width int
}
func area(r rectangle) {
fmt.Printf("Area Function result: %d\n", (r.length * r.width))
}
func (r rectangle) area() {
fmt.Printf("Area Method result: %d\n", (r.length * r.width))
}
func main() {
r := rectangle{
length: 10,
width: 5,
}
area(r)
r.area()
p := &r
/*
compilation error, cannot use p (type *rectangle) as type rectangle
in argument to area
*/
//area(p)
p.area()//calling value receiver with a pointer
}
```
第12行函数 `func area(r rectangle)` 接受一个值参数,而方法 `func (r rectangle) area()` 接受一个值接收者。
在第25行我们传递了一个值来调用 `area` 函数 `area(r)`,它将工作。同样地,我们通过值接收者调用 `area` 方法 `r.area()` 它也可以工作。
在第28行我们创建了一个指向 `r` 的指针 `p`。如果我们试图将这个指针传递给只接受值的 area 函数那么编译器将报错:`compilation error, cannot use p (type *rectangle) as type rectangle in argument to area`.。这是我们预期的。
现在来到了微妙的地方第35行 `p.area()` 使用指针接收者 `p` 调用了接受一个值接收者的方法 `area` 。这是完全合法的。原因是对于 `p.area()`Go 将其解析为 `(&p).area()`,因为 `area` 方法必须接受一个值接收者。这是为了方便 Go 给我们提供的语法糖。
程序的输出为:
```golang
Area Function result: 50
Area Method result: 50
Area Method result: 50
```
## 方法的指针接收者 vs. 函数的指针参数
与值参数相似,一个接受指针参数的函数只能接受指针,而一个接收者为指针的方法可以接受值接收者和指针接收者。
```golang
package main
import (
"fmt"
)
type rectangle struct {
length int
width int
}
func perimeter(r *rectangle) {
fmt.Println("perimeter function output:", 2*(r.length+r.width))
}
func (r *rectangle) perimeter() {
fmt.Println("perimeter method output:", 2*(r.length+r.width))
}
func main() {
r := rectangle{
length: 10,
width: 5,
}
p := &r //pointer to r
perimeter(p)
p.perimeter()
/*
cannot use r (type rectangle) as type *rectangle in argument to perimeter
*/
//perimeter(r)
r.perimeter()//calling pointer receiver with a value
}
```
在上面的程序中第12行定义了一个函数 `perimeter`该函数接受一个指针作为参数而17行定义了一个方法有一个指针接收者。
27行我们通过指针参数调用 `perimeter` 函数在第28行我们通过一个指针接收者调用 `perimeter` 方法。一切都好。
在被注释掉的第33行我们试图通以一个值 `r` 调用 `perimeter` 函数。这是非法的,因为一个接受指针为参数的函数不能接受一个值作为参数。如果去掉注释运行程序,则编译将报错:`main.go:33: cannot use r (type rectangle) as type *rectangle in argument to perimeter.`。
在第35行我们通过一个值接收者 `r` 调用接受一个指针接收者的 `perimeter` 方法。这是合法的,`r.perimeter()` 这一行将被 Go 解析为 `(&r).perimeter()`。这是为了方便 Go 给我们提供的语法糖。程序的输出为:
```golang
perimeter function output: 30
perimeter method output: 30
perimeter method output: 30
```
## 定义非结构体类型的方法
现在我们定义的都是结构体类型的方法。同样可以定义非结构体类型的方法,不过这里需要注意一点。为了定义某个类型的方法,接收者类型的定义与方法的定义必须在同一个包中。目前为止,我们定义的结构体和相应的方法都是在`main`包中的,因此没有任何问题。
```golang
package main
func (a int) add(b int) {
}
func main() {
}
```
在上面的程序中第3行我们试图添加一个方法 `add` 给内置类型 `int`。这是不允许的,因为定义方法 add 所在的包和定义类型 `int` 的包不是同一个包。这个程序将会报编译错误:`cannot define new methods on non-local type int`。
使其工作的方法为定义内置类型的别名,然后以这个新类型为接收者创建方法。
```golang
package main
import "fmt"
type myInt int
func (a myInt) add(b myInt) myInt {
return a + b
}
func main() {
num1 := myInt(5)
num2 := myInt(10)
sum := num1.add(num2)
fmt.Println("Sum is", sum)
}
```
上面的程序中第5行我们创建了新的类型一个 `int` 的别名 `myInt`。在第7行我们定义了一个方法 add`myInt` 作为接收者。
程序的输出为: `Sum is 15`
我已经创建了一个程序,其中包含了目前为止所讨论的所有概念,可以在 github 上找到它。
希望你喜欢阅读。请留下宝贵的意见和反馈:)
Loading…
Cancel
Save