From c0d3cff238718c387b5a498673a196c7ecea7c36 Mon Sep 17 00:00:00 2001 From: Tinywan <756684177@qq.com> Date: Mon, 20 Aug 2018 18:08:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tinywan <756684177@qq.com> --- demo/interface/demo01.go | 10 + demo/string/byte.go | 19 ++ demo/string/length.go | 17 ++ demo/string/rune.go | 42 ++++ docs/golang_tutorial_14.md | 444 ++++++++++++-------------------- docs/golang_tutorial_18-1.md | 476 +++++++++++++++++++++++++++++++++++ 6 files changed, 729 insertions(+), 279 deletions(-) create mode 100644 demo/interface/demo01.go create mode 100644 demo/string/byte.go create mode 100644 demo/string/length.go create mode 100644 demo/string/rune.go create mode 100644 docs/golang_tutorial_18-1.md diff --git a/demo/interface/demo01.go b/demo/interface/demo01.go new file mode 100644 index 0000000..6187199 --- /dev/null +++ b/demo/interface/demo01.go @@ -0,0 +1,10 @@ +package main + +// interface define 元音 +type VowelsFinder interface { + FindVowels() []rune +} + +func main() { + +} diff --git a/demo/string/byte.go b/demo/string/byte.go new file mode 100644 index 0000000..28b566a --- /dev/null +++ b/demo/string/byte.go @@ -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) +} diff --git a/demo/string/length.go b/demo/string/length.go new file mode 100644 index 0000000..013a9c5 --- /dev/null +++ b/demo/string/length.go @@ -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) +} diff --git a/demo/string/rune.go b/demo/string/rune.go new file mode 100644 index 0000000..a9afb5a --- /dev/null +++ b/demo/string/rune.go @@ -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) +} diff --git a/docs/golang_tutorial_14.md b/docs/golang_tutorial_14.md index 46fb1d4..7d29344 100644 --- a/docs/golang_tutorial_14.md +++ b/docs/golang_tutorial_14.md @@ -9,37 +9,32 @@ string 类型单独提取为一篇教程是因为在 Go 中,string 的实现方式同其他语言的不同。 -## 什么是字符串? +## 访问字符串中的字节 -在 Go 中字符串是 byte 数组。可以通过将内容放在双引号 "" 之间的方式来创建一个字符串。让我们看一个简单的例子,该例子创建并打印一个字符串: +因为字符串是字节数组,因此可以访问一个字符串中的字节。 ```golang package main -import ( - "fmt" +import ( + "fmt" ) -func main() { - name := "Hello World" - fmt.Println(name) +// 打印字符串中的字节 +func printBytes(s string) { + for i := 0; i < len(s); i++ { + fmt.Printf("%x ", s[i]) + } } -``` -上面输出:`Hello World`。 -Go 中的字符串符合 Unicode 标准,并以 UTF-8 编码。 - -## 如何创建 map? - -可以通过将键和值的类型传递给内置函数 `make` 来创建一个 `map`。语法为:`make(map[KeyType]ValueType)`。(译者注:`map` 的类型表示为 `map[KeyType]ValueType`)例如: - -```golang -personSalary := make(map[string]int) +func main() { + name := "Hello World" + printBytes(name) +} ``` +在上面的程序中,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 -``` - -上面的程序返回 `joe` 的工资为` 0`。我们没有得到任何运行时错误说明键 joe 在 `personSalary` 中不存在。 - -我们如何检测一个键是否存在于一个 map 中呢?可以使用下面的语法: +48 65 6c 6c 6f 20 57 6f 72 6c 64 +Hello World +53 65 c3 b1 6f 72 +Señor +``` +上面的输出是正确的。这正是我们想要的结果。 -```golang -value, ok := map[key] -``` +## 使用 range for 遍历字符串 -上面的语法可以检测一个特定的键是否存在于 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, - } - 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 printCharsAndBytes(s string) { + for index, rune := range s { + fmt.Printf("%c starts at byte %d\n", rune, index) } +} +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, - } - personSalary["mike"] = 9000 - fmt.Println("map before deletion", personSalary) - delete(personSalary, "steve") - fmt.Println("map after deletion", personSalary) - + byteSlice := []byte{67, 97, 102, 195, 169}//decimal equivalent of {'\x43', '\x61', '\x66', '\xC3', '\xA9'} + str := string(byteSlice) + fmt.Println(str) } ``` +上面的程序同样输出:Café。 -上面的程序删除以 `steve` 为键的元素。程序输出为: - -```golang -map before deletion map[steve:12000 jamie:15000 mike:9000] -map after deletion map[mike:9000 jamie:15000] -``` - -## 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) } -``` - -上面程序中,`len(personSalary) `获取 `personSalary` 的大小。上面的程序输出:`length is 3`。 +``` +在上面的程序中,runeSlice 包含了字符串 "Señor" 的 Unicode 码点(以 16 进制表示)。程序的输出为:Señor。 -## map 是引用类型 +## 字符串的长度 +utf8 包 提供了 func RuneCountInString(s string) (n int) 来获取字符串的长度,该方法接受一个字符串作为参数,并返回该字符串中 rune 的数量。 -与切片一样,map 是引用类型。当一个 map 赋值给一个新的变量,它们都指向同一个内部数据结构。因此改变其中一个也会反映到另一个: +(译者注: RuneCountInString 返回字符串中 Unicode 字符的个数,而 len 返回字符串中 byte 的个数,注意两者的区别。 ) ```golang package main import ( "fmt" + "unicode/utf8" ) +func length(s string) { + fmt.Printf("length of %s is %d\n", s, utf8.RuneCountInString(s)) +} func main() { - personSalary := map[string]int{ - "steve": 12000, - "jamie": 15000, - } - personSalary["mike"] = 9000 - fmt.Println("Original person salary", personSalary) - newPersonSalary := personSalary - newPersonSalary["mike"] = 18000 - fmt.Println("Person salary changed", personSalary) + + 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] -``` - -将 map 作为参数传递给函数也是一样的。在函数中对 map 的任何修改都会影响在调用函数中看到。 +length of Señor is 5 +length of Pets is 4 +``` -## 比较 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 } -``` - -上面的程序将会报错:`invalid operation: map1 == map2 (map can only be compared to nil)`。 - -比较两个 map 是否相等的方式是一一比较它们的元素是否相等。我会鼓励你为此编写一个程序,使其工作:) - -我(原文作者)已经将我们讨论的所有概念汇总到一个程序中,你可以从 [github](https://github.com/golangbot/arraysandslices) 下载。 +func main() { + h := "hello" + fmt.Println(mutate(h)) +} +``` -## 知识扩展 +上面的程序中,第 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" +import ( + "fmt" ) -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) - } - 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); - } - 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++ - } - - 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] +func mutate(s []rune) string { + s[0] = 'a' + return string(s) } -``` +func main() { + h := "hello" + fmt.Println(mutate([]rune(h))) +} +``` + +在上面的程序中,第 7 行 mutate 函数接受一个 rune 切片作为参数。然后将该切片的第一个元素改为 a,最后再转换回字符串并返回。该函数在程序中的第 13 行被调用。h 被转换为一个 rune 切片传递给 mutate。程序的输出为:aello。 + +字符串的介绍到此为止。感谢阅读。 希望你喜欢阅读。请留下宝贵的意见和反馈:) \ No newline at end of file diff --git a/docs/golang_tutorial_18-1.md b/docs/golang_tutorial_18-1.md new file mode 100644 index 0000000..2ce93dc --- /dev/null +++ b/docs/golang_tutorial_18-1.md @@ -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 上找到它。 + +希望你喜欢阅读。请留下宝贵的意见和反馈:) \ No newline at end of file