The way to Go 要点知识
1. 基本结构和数据类型
1.1 go程序的基本结构和要素
- 一个应用程序可以包含不同的包,而且即使你只使用
main
包也不必把所有的代码都写在一个巨大的文件里:你可以用一些较小的文件,并且在每个文件非注释的第一行都使用package main
来指明这些文件都属于main
包。 - Go 中的包模型采用了显式依赖关系的机制来达到快速编译的目的,编译器会从后缀名为
.o
的对象文件(需要且只需要这个文件)中提取传递依赖类型的信息。 - 如果
A.go
依赖B.go
,而B.go
又依赖C.go
:
编译C.go
,B.go
, 然后是A.go
.
为了编译A.go,
编译器读取的是B.o
而不是C.o
. - .如果包名不是以
.
或/
开头,如"fmt"
或者"container/list"
,则 Go 会在全局文件
进行查找;如果包名以./
开头,则 Go 会在相对目录
中查找;如果包名以/
开头(在 Windows 下也可以这样使用),则会在系统的绝对路径
中查找。 - 给导入的包起别名
package mainimport fm "fmt" // alias3func main() {fm.Println("hello, world")
}
- 包的分级声明和初始化
你可以在使用import
导入包之后定义或声明0 个或多个常量 (const)、变量 (var) 和类型 (type
),这些对象的作用域都是全局的
(**在本包范围内**
),所以可以被本包中所有的函数调用.
1.2 变量
1.当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0
,float32(64) 为 0.0
,bool 为 false
,string 为空字符串
,指针为 nil
。记住,所有的内存在 Go 中都是经过初始化的。
1.3 基本类型和运算符
1.两个类型相同的值可以使用相等 ==
或者不等 !=
运算符来进行比较并获得一个布尔型的值
var aVar = 10
aVar == 5 -> false
aVar == 10 -> true
2 .位清除 &^
:将指定位置上的值设置为 0
x=15 y=4
x &^ y
x : 1111
y: 0100
x &^ y: 1011
y
中为1
的位 对应于x
的位清0
,若y
中其他位为0
对应x
中其他位不变。
1.4 运算符与优先级
二元运算符的运算方向均是从左至右
优先级 运算符7 ^ !6 * / % << >> & &^5 + - | ^4 == != < <= >= >3 <-2 &&1 ||
2. 控制结构
2.1 fmt 包 最简单的打印函数也有 2 个返回值:
count, err := fmt.Println(x) // number of bytes printed, nil or 0, error
当打印到控制台
时,可以将该函数返回的错误忽略
;但当输出到文件流、网络流
等具有不确定因素的输出对象时,应该始终检查是否有错误发生
2.2 使用goto 执行循环打印
i := 0
START:fmt.Printf("The counter is at %d\n", i)i++if i < 4 {goto START}
2.3 标签与goto
for
、switch
或select
语句都可以配合标签 (label) 形式的标识符使用。- 如果您必须使用 goto,应当只使用
正序
的标签(标签位于 goto 语句之后),但注意标签和 goto 语句之间不能出现
定义新变量的语句,否则会导致编译失败
// compile error goto2.go:8: goto TARGET jumps over declaration of b at goto2.go:8
package mainimport "fmt"func main() {a := 1goto TARGET // compile errorb := 9TARGET: b += afmt.Printf("a is %v *** b is %v", a, b)
}
3.函数
3.1 介绍
1.函数可以将其他函数调用作为它的参数,只要这个被调用函数的返回值个数、返回值类型和返回值的顺序与调用函数所需求的实参是一致的,例如:
假设 f1
需要 3
个参数 f1(a, b, c int)
,同时 f2
返回 3
个参数 f2(a, b int) (int, int, int)
,就可以这样调用 f1:f1(f2(a, b))
。
3.2 函数参数与返回值
1.事实上,任何一个有返回值(单个或多个)的函数都必须以 return
或 panic
(参考 第 13 章)结尾
2.在函数调用时,像切片 (slice)、字典 (map)、接口 (interface)、通道 (channel)
这样的引用类型都是默认使用引用传递
(即使没有显式的指出指针)
3.如果一个函数需要返回四到五个值
,我们可以传递一个切片
给函数(如果返回值具有相同类型
)或者是传递一个结构体
(如果返回值具有不同的类型
)。因为传递一个指针允许直接修改变量的值,消耗也更少
3.2.1 命名的返回值
返回值有名称与无名称
package mainimport "fmt"var num int = 10
var numx2, numx3 intfunc main() {numx2, numx3 = getX2AndX3(num)PrintValues()numx2, numx3 = getX2AndX3_2(num)PrintValues()
}func PrintValues() {fmt.Printf("num = %d, 2x num = %d, 3x num = %d\n", num, numx2, numx3)
}
//无名称
func getX2AndX3(input int) (int, int) {return 2 * input, 3 * input
}
//有名称
func getX2AndX3_2(input int) (x2 int, x3 int) {x2 = 2 * inputx3 = 3 * input// return x2, x3return
}
3.3 传递变长参数
1.如果函数的最后一个参数是采用 ...type
的形式,那么这个函数就可以处理一个变长的参数,这个长度可以为 0
,这样的函数称为变参函数
。
func myFunc(a, b, arg ...int) {}
eg.
func Greeting(prefix string, who ...string)
Greeting("hello:", "Joe", "Anna", "Eileen")
在 Greeting() 函数
中,变量 who
的值为 []string{"Joe", "Anna", "Eileen"}
。
2.如果参数被存储在一个 slice 类型
的变量 slice
中,则可以通过 slice...
的形式来传递参数,调用变参函数
。
package mainimport "fmt"func main() {x := min(1, 3, 2, 0)fmt.Printf("The minimum is: %d\n", x)slice := []int{7,9,3,5,1}// min函数调用:slice... x = min(slice...)fmt.Printf("The minimum in the slice is: %d", x)
}func min(s ...int) int {if len(s)==0 {return 0}min := s[0]for _, v := range s {if v < min {min = v}}return min
}
3.一个接受变长参数的函数
可以将这个参数
作为其它函数的参数
进行传递
func F1(s ...string) {F2(s...)F3(s)
}func F2(s ...string) { }
func F3(s []string) { }
- 但是如果变长参数的类型并不是都相同的呢?
使用结构体
或空接口
4.1 结构体
函数F1()
可以使用正常的参数a 和 b
,以及一个没有任何初始化的Options
结构:F1(a, b, Options {})
。如果需要对选项进行初始化
,则可以使用F1(a, b, Options {par1:val1, par2:val2})
。
4.2 空接口
如果一个变长参数的类型没有被指定
,则可以使用默认的空接口 interface{}
,这样就可以接受任何类型
的参数(详见第 11.9 节 )。该方案不仅可以用于长度未知的参数,还可以用于任何不确定类型的参数。一般而言我们会使用一个for-range
循环以及switch
结构对每个参数的类型进行判断。
func typecheck(..,..,values … interface{}) {for _, value := range values {switch v := value.(type) {case int: …case float64: …case string: …case bool: …default: …}}
}
3.4 defer
多个defer的执行顺序为“后进先出
”;
return
在 defer
之前
defer、return、返回值
三者的执行逻辑应该是:
(1) return最先执行
,return负责将结果写入返回值
中;
(2) 接着
defer开始执行
一些收尾工作;
(3) 最后函数携带当前返回值退出
。
package mainimport ("io""log"
)func func1(s string) (n int, err error) {defer func() {log.Printf("func1(%q) = %d, %v", s, n, err)}()return 7, io.EOF
}func main() {func1("Go")
}
3.5 内置函数
3.6 递归函数 计算 斐波那契数列
package mainimport "fmt"func main() {result := 0for i := 0; i <= 10; i++ {result = fibonacci(i)fmt.Printf("fibonacci(%d) is: %d\n", i, result)}
}func fibonacci(n int) (res int) {if n <= 1 {res = 1} else {res = fibonacci(n-1) + fibonacci(n-2)}return
}
3.7 将函数作为参数
函数
可以作为其它函数的参数
进行传递,然后在其它函数内调用执行,一般称之为回调
。
package mainimport ("fmt"
)func main() {callback(1, Add)
}func Add(a, b int) {fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}func callback(y int, f func(int, int)) {f(y, 2) // this becomes Add(1, 2)
}
//The sum of 1 and 2 is: 3
3.8 闭包
我们不希望给函数起名字的时候,可以使用匿名函数,例如:func(x, y int) int { return x + y }
。
这样的一个函数不能够独立存在(编译器会返回错误:non-declaration statement outside function body),但可以被赋值于某个变量
,即保存函数的地址到变量中
:fplus := func(x, y int) int { return x + y }
,然后通过变量名对函数进行调用:fplus(3,4)
。
当然,您也可以直接对匿名函数进行调用
:func(x, y int) int { return x + y } (3, 4)
。
匿名函数同样被称之为闭包
(函数式语言的术语):它们被允许调用定义在其它环境下的变量
。闭包可使得某个函数捕捉到一些外部状态,例如:函数被创建时的状态。另一种表示方式为:一个闭包继承了函数所声明时的作用域。这种状态(作用域内的变量)都被共享到闭包的环境中,因此这些变量可以在闭包中被操作,直到被销毁,详见第 6.9 节 中的示例。闭包经常被用作包装函数
:它们会预先定义好 1 个或多个参数以用于包装,详见下一节中的示例。另一个不错的应用就是使用闭包来完成更加简洁的错误检查(详见第 16.10.2 节)。
3.9 应用闭包:将函数作为返回值
//函数返回类型为 func(b int)int
func Add2() func(b int) int {return func(b int) int {return b + 2}
}
func Adder(a int) func(b int) int {return func(b int) int {return a + b}
}
func main() {p2 := Add2()fmt.Printf("Call Add2 for 3 gives: %v\n", p2(3)) //输出5adder := Adder(1)fmt.Printf("The result is: %v\n", adder(3))
}
输出结果
Call Add2 for 3 gives: 5
The result is: 4
package mainimport "fmt"func main() {var f = Adder()fmt.Print(f(1), " - ")fmt.Print(f(20), " - ")fmt.Print(f(300))
}func Adder() func(int) int {var x intreturn func(delta int) int {x += deltareturn x}
}
输出结果:
1 - 21 - 321
三次调用函数 f 的过程中函数 Adder() 中变量 delta 的值分别为:1、20 和 300。
我们可以看到,在多次调用中,变量 x 的值是被保留的,即 0 + 1 = 1,然后 1 + 20 = 21,最后 21 + 300 = 321:闭包函数保存并积累其中的变量的值,不管外部函数退出与否,它都能够继续操作外部函数中的局部变量
一个返回值为另一个函数
的函数
可以被称之为工厂函数
,这在您需要创建一系列相似的函数的时候非常有用:书写一个工厂函数而不是针对每种情况都书写一个函数。下面的函数演示了如何动态返回追加后缀的函数:
func MakeAddSuffix(suffix string) func(string) string {return func(name string) string {if !strings.HasSuffix(name, suffix) {return name + suffix}return name}
}
现在,我们可以生成如下函数:
addBmp := MakeAddSuffix(".bmp")
addJpeg := MakeAddSuffix(".jpeg")
然后调用它们:
addBmp("file") // returns: file.bmp
addJpeg("file") // returns: file.jpeg
3.10 闭包调试
//skip是要提升的堆栈帧数,0-当前函数,1-上一层函数,.... 2 一般为调用A函数的位置,where()在A函数里
where := func() {_, file, line, _ := runtime.Caller(2)log.Printf("%s:%d", file, line)
}
where()
// some code
where()
// some more code
where()
3.11 计算函数执行时间
start := time.Now()
longCalculation()
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)
7.数组与切片
7.1声明和初始化
7.1.3多维数组 二维数组为例
// multidim_array.go
package mainimport "fmt"const (WIDTH = 1920HEIGHT = 1080// WIDTH = 5// HEIGHT = 4
)type pixel intvar screen [WIDTH][HEIGHT]pixelfunc main() {for y := 0; y < HEIGHT; y++ {for x := 0; x < WIDTH; x++ {screen[x][y] = 0}}fmt.Println(screen)for row := range screen {for column := range screen[0] {screen[row][column] = 1}}fmt.Println(screen)
}/* Output for WIDTH = 5 and HEIGHT = 4:
[[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]]
[[1 1 1 1] [1 1 1 1] [1 1 1 1] [1 1 1 1] [1 1 1 1]]
*/
7.2 切片
1.对于每一个切片(包括 string),以下状态总是成立
的
s == s[:i] + s[i:] // i是一个整数且: 0 <= i <= len(s)
len(s) <= cap(s)
2 .len cap
package main
import "fmt"
从切片s的索引位置low到high处所获得的切片,len=high-low,cap=max-low
func main() {var arr1 [6]intvar slice1 []int = arr1[2:5] // item at index 5 not included!// load the array with integers: 0,1,2,3,4,5for i := 0; i < len(arr1); i++ {arr1[i] = i}// print the slicefor i := 0; i < len(slice1); i++ {fmt.Printf("Slice at %d is %d\n", i, slice1[i])}fmt.Printf("The length of arr1 is %d\n", len(arr1))fmt.Printf("The length of slice1 is %d\n", len(slice1))fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))// grow the sliceslice1 = slice1[0:4]for i := 0; i < len(slice1); i++ {fmt.Printf("Slice at %d is %d\n", i, slice1[i])}fmt.Printf("The length of slice1 is %d\n", len(slice1))fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))// grow the slice beyond capacity//slice1 = slice1[0:7 ] // panic: runtime error: slice bound out of range
}
输出:
Slice at 0 is 2
Slice at 1 is 3
Slice at 2 is 4
The length of arr1 is 6
The length of slice1 is 3 high-low=5-2=3
The capacity of slice1 is 4 没有指明默认max=6 max-low=6-2=4
Slice at 0 is 2
Slice at 1 is 3
Slice at 2 is 4
Slice at 3 is 5 超出slice1范围 继承 arr1中的5
The length of slice1 is 4 high-low=4-0=4
The capacity of slice1 is 4 cap(slice1)=4 max(slice1)-low(slice1)=4-0=4
7.2.2 将切片传递给函数
计算数组元素和
func sum(a []int) int {s := 0for i := 0; i < len(a); i++ {s += a[i]}return s
}func main() {var arr = [5]int{0, 1, 2, 3, 4}sum(arr[:])
}
1.所以下面两种方法可以生成相同的切片
:
make([]int, 50, 100)
new([100]int)[0:50]
7.2.4 new 和make 区别
new(T)
为每个新的类型 T
分配一片内存,初始化为 0
并且返回类型为 *T
的内存地址:这种方法 返回一个指向类型为 T
,值为 0 的地址的指针
,它适用于值类型如数组和结构体
(参见第 10 章);它相当于 &T{}。
make(T)
返回一个类型为 T 的初始值
,它只适用于 3 种内建的引用类型:切片
、map
和 channel
(参见第 8 章和第 13 章)。
换言之,new() 函数分配内存
,make() 函数初始化
7.3 For-range 结构
1.如果你只需要索引,你可以忽略
第二个变量,例如:
for ix := range seasons {fmt.Printf("%d", ix)
}
// Output: 0 1 2 3
7.4 切片重组 (reslice)
改变切片长度的过程称之为切片重组 reslicing
- 将切片扩展
1 位
可以这么做:
//切片可以反复扩展直到占据整个相关数组。
sl = sl[0:len(sl)+1]
- 切片扩展例子
package main
import "fmt"func main() {slice1 := make([]int, 0, 10)// load the slice, cap(slice1) is 10:for i := 0; i < cap(slice1); i++ {slice1 = slice1[0:i+1]slice1[i] = ifmt.Printf("The length of slice is %d\n", len(slice1))}// print the slice:for i := 0; i < len(slice1); i++ {fmt.Printf("Slice at %d is %d\n", i, slice1[i])}
}
输出
The length of slice is 1
The length of slice is 2
The length of slice is 3
The length of slice is 4
The length of slice is 5
The length of slice is 6
The length of slice is 7
The length of slice is 8
The length of slice is 9
The length of slice is 10
Slice at 0 is 0
Slice at 1 is 1
Slice at 2 is 2
Slice at 3 is 3
Slice at 4 is 4
Slice at 5 is 5
Slice at 6 is 6
Slice at 7 is 7
Slice at 8 is 8
Slice at 9 is 9
7.5 切片的复制与追加
append()
在大多数情况下很好用,但是如果你想完全掌控整个追加过程,你可以实现一个这样的 AppendByte()
方法:
func AppendByte(slice []byte, data ...byte) []byte {m := len(slice)n := m + len(data)if n > cap(slice) { // if necessary, reallocate// allocate double what's needed, for future growth.newSlice := make([]byte, (n+1)*2)copy(newSlice, slice)slice = newSlice //因为还想要使用slice切片}slice = slice[0:n] //切片扩容copy(slice[m:n], data) //将data切片赋值给slice里的[m:n]元素return slice
}
练习 7.9
给定一个切片 s []int
和一个 int 类型的因子 factor
,扩展 s
使其长度为 len(s) * factor
package mainimport "fmt"var s []intfunc main() {s = []int{1, 2, 3}fmt.Println("The length of s before enlarging is:", len(s))fmt.Println(s)s = enlarge(s, 5)fmt.Println("The length of s after enlarging is:", len(s))fmt.Println(s)
}func enlarge(s []int, factor int) []int {ns := make([]int, len(s)*factor)// fmt.Println("The length of ns is:", len(ns))copy(ns, s)//fmt.Println(ns)s = ns//fmt.Println(s)//fmt.Println("The length of s after enlarging is:", len(s))return s
}
练习 7.10
写一个函数 InsertStringSlice()
将切片插入到另一个切片的指定位置
package mainimport ("fmt"
)func main() {s := []string{"M", "N", "O", "P", "Q", "R"}in := []string{"A", "B", "C"}res := InsertStringSlice(s, in, 0) // at the frontfmt.Println(res) // [A B C M N O P Q R]res = InsertStringSlice(s, in, 3) // [M N O A B C P Q R]fmt.Println(res)
}func InsertStringSlice(slice, insertion []string, index int) []string {result := make([]string, len(slice)+len(insertion))at := copy(result, slice[:index])at += copy(result[at:], insertion)copy(result[at:], slice[index:])return result
}
练习 7.11
写一个函数 RemoveStringSlice()
将从 start
到 end
索引的元素从切片中移除
// remove_slice.go
package mainimport ("fmt"
)func main() {s := []string{"M", "N", "O", "P", "Q", "R"}res := RemoveStringSlice(s, 2, 4)fmt.Println(res) // [M N Q R]
}func RemoveStringSlice(slice []string, start, end int) []string {result := make([]string, len(slice)-(end-start))at := copy(result, slice[:start])copy(result[at:], slice[end:])return result
}
练习 7.12
用顺序函数过滤容器:s
是前 10
个整型的切片。构造一个函数 Filter
,第一个参数是 s
,第二个参数是一个 fn func(int) bool
,返回满足函数 fn 的元素切片
。通过 fn
测试方法测试当整型值是偶数时
的情况。
package mainimport ("fmt"
)func main() {s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}s = Filter(s, even)fmt.Println(s)
}// Filter returns a new slice holding only
// the elements of s that satisfy f()
func Filter(s []int, fn func(int) bool) []int {var p []int // == nilfor _, i := range s {if fn(i) {p = append(p, i)}}return p
}func even(n int) bool {if n%2 == 0 {return true}return false
}
/* [0 2 4 6 8] */
7.6 字符串、数组和切片的应用
7.6.1 从字符串生成字节切片
1.假设 s
是一个字符串
(本质上是一个字节数组
),那么就可以直接通过 c := []byte(s)
来获取一个字节的切片 c
。另外,您还可以通过 copy() 函数
来达到相同的目的:copy(dst []byte, src string)
。
2. 您还可以将一个字符串
追加到某一个字节切片的尾部
:
var b []byte
var s string
b = append(b, s...)
7.6.4 修改字符串中的某个字符
Go 语言中的字符串是不可变的
,也就是说 str[index]
这样的表达式是不可以被放在等号左侧的
.
例如,将字符串 "hello"
转换为 "cello"
:
s := "hello"
c := []byte(s) //字符串转字节数组
c[0] = 'c' //修改数组中的元素值
s2 := string(c) // s2 == "cello" //字节数组转字符串
7.6.5 字节数组对比函数
下面的 Compare()
函数会返回两个字节数组字典顺序的整数对比结果,即 0 if a == b, -1 if a < b, 1 if a > b
func Compare(a, b[]byte) int {for i:=0; i < len(a) && i < len(b); i++ {switch {case a[i] > b[i]:return 1case a[i] < b[i]:return -1}}// 数组的长度可能不同switch {case len(a) < len(b):return -1case len(a) > len(b):return 1}return 0 // 数组相等
}
7.6.6 搜索及排序切片和数组
标准库提供了 sort 包
来实现常见的搜索
和排序
操作。您可以使用 sort 包中的函数 func Ints(a []int)
来实现对 int 类型的切片排序
。例如 sort.Ints(arri)
,其中变量 arri
就是需要被升序排序的数组或切片
。为了检查某个数组是否已经被排序
,可以通过函数 IntsAreSorted(a []int) bool
来检查,如果返回 true
则表示已经被排序
。
类似的,可以使用函数 func Float64s(a []float64)
来排序 float64 的元素,或使用函数 func Strings(a []string)
排序字符串元素。
想要在数组或切片
中搜索一个元素
,该数组或切片必须先被排序
(因为标准库的搜索算法使用的是二分法
)。然后,您就可以使用函数 func SearchInts(a []int, n int) int
进行搜索,并返回对应结果的索引值
。
7.6.7 append() 函数常见操作
1.将切片 b
的元素追加到切片 a
之后:a = append(a, b...)
2.复制切片 a
的元素到新的切片 b
上:
b = make([]T, len(a))
copy(b, a)
3.删除位于索引 i
的元素:a = append(a[:i], a[i+1:]...)
4.切除切片 a
中从索引 i
至 j
位置的元素:a = append(a[:i], a[j:]...)
5.为切片 a
扩展 j
个元素长度:a = append(a, make([]T, j)...)
6.在索引 i
的位置插入元素 x:a = append(a[:i], append([]T{x}, a[i:]...)...)
7.在索引 i
的位置插入长度为 j
的新切片:a = append(a[:i], append(make([]T, j), a[i:]...)...)
8.在索引 i
的位置插入切片 b
的所有元素:a = append(a[:i], append(b, a[i:]...)...)
9.取出位于切片 a
最末尾的元素 x:x, a = a[len(a)-1], a[:len(a)-1]
10.将元素 x
追加到切片 a:a = append(a, x)
因此,您可以使用切片和 append()
操作来表示任意可变长度
的序列。
如果您需要更加完整的方案,可以学习一下 Eleanor McHugh
编写的几个包:slices
、chain
和 lists
。
练习 7.12
编写一个函数,要求其接受两个参数
,原始字符串 str
和分割索引 i
,然后返回两个分割后的字符串
package mainimport "fmt"func main() {str := "Google"for i := 0; i <= len(str); i++ {a, b := Split(str, i)fmt.Printf("The string %s split at position %d is: %s / %s\n", str, i, a, b)}}func Split(s string, pos int) (string, string) {return s[0:pos], s[pos:]
}
练习7.13
假设有字符串 str
,那么 str[len(str)/2:] + str[:len(str)/2]
的结果是什么?
package mainimport "fmt"func main() {str := "Google"str2 := Split2(str)fmt.Printf("The string %s transformed is: %s\n", str, str2)
}func Split2(s string) string {mid := len(s) / 2return s[mid:] + s[:mid]
}// Output: The string Google transformed is: gleGoo
练习 7.14
编写一个程序,要求能够反转字符串
,即将 “Google” 转换成 “elgooG”(提示:使用 []byte 类型的切片
)。
package mainimport "fmt"func reverse(s string) string {runes := []rune(s)n, h := len(runes), len(runes)/2for i := 0; i < h; i++ {runes[i], runes[n-1-i] = runes[n-1-i], runes[i]}return string(runes)
}func main() {// reverse a string:str := "Google"sl := []byte(str)var rev [100]bytej := 0for i := len(sl) - 1; i >= 0; i-- {rev[j] = sl[i]j++}str_rev := string(rev[:])fmt.Printf("The reversed string is -%s-\n", str_rev)// variant: "in place" using swappingstr2 := "Google"sl2 := []byte(str2)for i, j := 0, len(sl2)-1; i < j; i, j = i+1, j-1 {sl2[i], sl2[j] = sl2[j], sl2[i]}fmt.Printf("The reversed string is -%s-\n", string(sl2))// variant: using [] int for runes (necessary for Unicode-strings!):s := "My Test String!"fmt.Println(s, " --> ", reverse(s))
}/* Output:
The reversed string is -elgooG-
The reversed string is -elgooG-
My Test String! --> !gnirtS tseT yM
*/
练习7.15
编写一个程序,要求能够遍历一个字符数组,并将当前字符和前一个字符不相同的字符拷贝至另一个数组
// Q29_uniq.go
package mainimport ("fmt"
)var arr []byte = []byte{'a', 'b', 'a', 'a', 'a', 'c', 'd', 'e', 'f', 'g'}func main() {arru := make([]byte, len(arr)) // this will contain the unique itemsixu := 0 // index in arrutmp := byte(0)for _, val := range arr {if val != tmp {arru[ixu] = valfmt.Printf("%c ", arru[ixu])ixu++}tmp = val}// fmt.Println(arru)
}
练习 7.16
编写一个程序,使用冒泡排序的方法排序一个包含整数的切片
// Q14_Bubblesort.go
package mainimport ("fmt"
)func main() {sla := []int{2, 6, 4, -10, 8, 89, 12, 68, -45, 37}fmt.Println("before sort: ", sla)// sla is passed via call by value, but since sla is a reference type// the underlying slice is array is changed (sorted)bubbleSort(sla)fmt.Println("after sort: ", sla)
}func bubbleSort(sl []int) { //每次将最大的元素移到最后for pass := 1; pass < len(sl); pass++ { //循环多少次 总共3个数 就要循环2次for i := 0; i < len(sl)-pass; i++ { //每一次 要便利多少个元素if sl[i] > sl[i+1] {sl[i], sl[i+1] = sl[i+1], sl[i]}}}
}
练习7.17
在函数式编程语言中,一个 map-function 是指能够接受一个函数原型和一个列表,并使用列表中的值依次执行函数原型,公式为:map ( F(), (e1,e2, . . . ,en) ) = ( F(e1), F(e2), ... F(en) )。
编写一个函数 mapFunc 要求接受以下 2 个参数:
一个将整数乘以 10 的函数
一个整数列表
最后返回保存运行结果的整数列表
。
//2.函数功能 将放入的数组中的每个元素乘以10,最终以切片输出
func main() {list := []int{1, 2, 3, 4, 5, 6, 7}mf := func(num int) int {return num * 10}fmt.Println(mapFunc(mf, list))
}
func mapFunc(mf func(int) int, list []int) []int {result := make([]int, len(list))for i, value := range list {value = mf(value)//这里不用append 原因是[0 0 0 0 0 0 0 10 20 30 40 50 60 70] append函数是追加 相当于在创建//长度为7的result切片后,又增添了7个元素result[i] = value }return result
}
八、map
8.1.1 概念
1.未初始化的 map 的值是 nil
2.key 可以是任意可以用 ==
或者 !=
操作符比较的类型,比如 string、int、float32(64
)。所以数组、切片和结构体不能作为 key
(译者注:含有数组切片的结构体不能作为 key,只包含内建类型的 struct 是可以作为 key 的
),但是指针和接口类型可以
。如果要用结构体作为 key
可以提供 Key() 和 Hash() 方法
,这样可以通过结构体的域计算出唯一的数字或者字符串的 key。
3. map 也可以用函数
作为自己的值
,这样就可以用来做分支结构(详见第 5 章):key 用来选择要执行的函数
。
4. mapAssigned
也是 mapLit
的引用,对 mapAssigned
的修改
也会影响
到 mapLit 的值
5. 不要使用 new()
,永远用 make() 来构造 map
6. 为了说明值可以是任意类型
的,这里给出了一个使用 func() int 作为值的 map
package main
import "fmt"func main() {mf := map[int]func() int{1: func() int { return 10 },2: func() int { return 20 },5: func() int { return 50 },}fmt.Println(mf)
}
map[1:0x10903be0 5:0x10903ba0 2:0x10903bc0] 整型都被映射到函数地址
8.1.3 用切片作为 map 的值
mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)
8.2 测试键值对是否存在及删除元素
- 为了解决
key1 不存在
还是它对应的value 就是空值
,我们可以这么用:
val1, isPresent = map1[key1]
package main
import "fmt"func main() {var value intvar isPresent boolmap1 := make(map[string]int)map1["New Delhi"] = 55map1["Beijing"] = 20map1["Washington"] = 25value, isPresent = map1["Beijing"]if isPresent {fmt.Printf("The value of \"Beijing\" in map1 is: %d\n", value)} else {fmt.Printf("map1 does not contain Beijing")}value, isPresent = map1["Paris"]fmt.Printf("Is \"Paris\" in map1 ?: %t\n", isPresent)fmt.Printf("Value is: %d\n", value)// delete an item:delete(map1, "Washington")value, isPresent = map1["Washington"]if isPresent {fmt.Printf("The value of \"Washington\" in map1 is: %d\n", value)} else {fmt.Println("map1 does not contain Washington")}
}
输出结果:
The value of "Beijing" in map1 is: 20
Is "Paris" in map1 ?: false
Value is: 0 (空值)
map1 does not contain Washington
8.4 map 类型的切片
1.假设我们想获取一个 map 类型的切片
,我们必须使用两次 make() 函数
,第一次分配切片
,第二次分配切片中每个 map 元素
// 3.创造map类型切片
func main() {items := make([]map[int]int, 5)for i := range items {items[i] = make(map[int]int, 1)items[i][1] = iitems[i][2] = i * 10}fmt.Println(items)
}
输出结果:[map[1:0 2:0] map[1:1 2:10] map[1:2 2:20] map[1:3 2:30] map[1:4 2:40]]
8.5 map 的排序
如果你想为 map 排序
,需要将 key(或者 value
)拷贝到一个切片
,再对切片排序(使用 sort 包
,详见第 7.6.6 节),然后可以使用切片的 for-range 方法打印出所有的 key 和 value
。
// the telephone alphabet:
package main
import ("fmt""sort"
)var (barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23,"delta": 87, "echo": 56, "foxtrot": 12,"golf": 34, "hotel": 16, "indio": 87,"juliet": 65, "kili": 43, "lima": 98}
)func main() {fmt.Println("unsorted:")for k, v := range barVal {fmt.Printf("Key: %v, Value: %v / ", k, v)}keys := make([]string, len(barVal))i := 0for k, _ := range barVal {keys[i] = ki++}sort.Strings(keys)fmt.Println()fmt.Println("sorted:")for _, k := range keys {fmt.Printf("Key: %v, Value: %v / ", k, barVal[k])}
}
输出结果:
unsorted:
Key: bravo, Value: 56 / Key: echo, Value: 56 / Key: indio, Value: 87 / Key: juliet, Value: 65 / Key: alpha, Value: 34 / Key: charlie, Value: 23 / Key: delta, Value: 87 / Key: foxtrot, Value: 12 / Key: golf, Value: 34 / Key: hotel, Value: 16 / Key: kili, Value: 43 / Key: lima, Value: 98 /
sorted:
Key: alpha, Value: 34 / Key: bravo, Value: 56 / Key: charlie, Value: 23 / Key: delta, Value: 87 / Key: echo, Value: 56 / Key: foxtrot, Value: 12 / Key: golf, Value: 34 / Key: hotel, Value: 16 / Key: indio, Value: 87 / Key: juliet, Value: 65 / Key: kili, Value: 43 / Key: lima, Value: 98 /
但是如果你想要一个排序的列表,那么最好使用结构体切片,这样会更有效
type name struct {key stringvalue int
}
8.6 将 map 的键值对调
这里对调是指调换 key
和 value
package main
import ("fmt"
)var (barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23,"delta": 87, "echo": 56, "foxtrot": 12,"golf": 34, "hotel": 16, "indio": 87,"juliet": 65, "kili": 43, "lima": 98}
)func main() {invMap := make(map[int]string, len(barVal))for k, v := range barVal {invMap[v] = k}fmt.Println("inverted:")for k, v := range invMap {fmt.Printf("Key: %v, Value: %v / ", k, v)}
}
如果value值不确定
,一种解决方法就是仔细检查唯一性并且使用多值 map
,比如使用 map[int][]string
类型
9 包
通过使用 unsafe 包
中的方法来测试你电脑上一个整型变量占用多少个字节
。
func main() {var i int = 10size := unsafe.Sizeof(i)fmt.Println("The size of an int is: ", size)
}
9.3 锁和 sync 包
sync.Mutex
是一个互斥锁
,它的作用是守护在临界区入口来确保同一时间只能有一个线程进入临界区
import "sync"type Info struct {mu sync.Mutex// ... other fields, e.g.: Str string
}
------------------------------------
func Update(info *Info) {info.mu.Lock()// critical section:info.Str = // new value// end critical sectioninfo.mu.Unlock()
}
在 sync 包
中还有一个 RWMutex 锁
:它能通过 RLock()
来允许同一时间多个线程对变量进行读操作,但是只能一个线程进行写操作
。如果使用 Lock()
将和普通的 Mutex 作用相同
。包中还有一个方便的 Once 类型变量
的方法 once.Do(call)
,这个方法确保被调用函数只能被调用一次
。
9.4 精密计算和 big 包
大的整型数字
是通过 big.NewInt(n)
来构造的,其中 n 为 int64 类型
整数。而大有理数
是通过 big.NewRat(n, d)
方法构造。n(分子)和 d(分母)都是 int64 型整数
。因为 Go 语言不支持运算符重载
,所以所有大数字类型都有像是 Add() 和 Mul() 这样的方法。它们作用于作为 receiver 的整数和有理数,大多数情况下它们修改 receiver 并以 receiver 作为返回结果
。
// big.go
package mainimport ("fmt""math""math/big"
)func main() {// Here are some calculations with bigInts:im := big.NewInt(math.MaxInt64)in := imio := big.NewInt(1956)ip := big.NewInt(1)ip.Mul(im, in).Add(ip, im).Div(ip, io)fmt.Printf("Big Int: %v\n", ip)// Here are some calculations with bigInts:rm := big.NewRat(math.MaxInt64, 1956)rn := big.NewRat(-1956, math.MaxInt64)ro := big.NewRat(19, 56)rp := big.NewRat(1111, 2222)rq := big.NewRat(1, 1)rq.Mul(rm, rn).Add(rq, ro).Mul(rq, rp)fmt.Printf("Big Rat: %v\n", rq)
}/* Output:
Big Int: 43492122561469640008497075573153004
Big Rat: -37/112
*/
9.6 为自定义包使用 godoc
- 生成你项目的所有函数索引通过网页形式
godoc -http=:8080
- url
http://localhost:8080
10.0 结构 (struct) 与方法 (method)
10.2 使用工厂方法创建结构体实例
10.2.1 结构体工厂
// file结构体
type File struct {fd int // 文件描述符name string // 文件名
}
//下面是这个结构体类型对应的工厂方法,它返回一个指向结构体实例的指针:
func NewFile(fd int, name string) *File {if fd < 0 {return nil}return &File{fd, name}
}
调用方式f := NewFile(10, "./test.txt")
2. 如何强制使用工厂方法
type matrix struct {...
}func NewMatrix(params) *matrix {m := new(matrix) // 初始化 mreturn m
}
这样子 使得new(matrix)
变得私有
package main
import "matrix"
...
wrong := new(matrix.matrix) // 编译失败(matrix 是私有的)
right := matrix.NewMatrix(...) // 实例化 matrix 的唯一方式
- 结构体用
new
map用make
10.4 带标签的结构体
在一个变量上调用 reflect.TypeOf()
可以获取变量的正确类型,如果变量是一个结构体类型
,就可以通过 Field
来索引结构体的字段,然后就可以使用 Tag 属性
package mainimport ("fmt""reflect"
)type TagType struct { // tagsfield1 bool "An important answer"field2 string "The name of the thing"field3 int "How much there are"
}func main() {tt := TagType{true, "Barak Obama", 1}for i := 0; i < 3; i++ {refTag(tt, i)}
}func refTag(tt TagType, ix int) {ttType := reflect.TypeOf(tt)ixField := ttType.Field(ix)fmt.Printf("%v\n", ixField.Tag)
}
输出结果:
An important answer
The name of the thing
How much there are
10.5 匿名字段和内嵌结构体
10.5.1 定义
结构体可以包含一个或多个 匿名(或内嵌)字段
,即这些字段没有显式的名字
,只有
字段的类型是必须的
,此时类型就是字段的名字
。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体。
package mainimport "fmt"type innerS struct {in1 intin2 int
}type outerS struct {b intc float32int // anonymous fieldinnerS //anonymous field
}func main() {outer := new(outerS)outer.b = 6outer.c = 7.5outer.int = 60outer.in1 = 5outer.in2 = 10fmt.Printf("outer.b is: %d\n", outer.b)fmt.Printf("outer.c is: %f\n", outer.c)fmt.Printf("outer.int is: %d\n", outer.int)fmt.Printf("outer.in1 is: %d\n", outer.in1)fmt.Printf("outer.in2 is: %d\n", outer.in2)// 使用结构体字面量outer2 := outerS{6, 7.5, 60, innerS{5, 10}}fmt.Println("outer2 is:", outer2)
}
10.5.3 命名冲突
type A struct {a int}
type B struct {a, b int}type C struct {A; B}
var c C
解决办法使用 c.A.a 或 c.B.a type D struct {B; b float32}
var d D使用 d.b 是没问题的:它是 float32,而不是 B 的 b。如果想要内层的 b 可以通过 d.B.b 得到
10.6 方法
1.Go 方法是作用在接收者 (receiver)
上的一个函数,接收者是某种类型的变量。因此方法是一种特殊类型的函数
2.一个类型加上它的方法
等价于面向对象中的一个类
。一个重要的区别是:在 Go 中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的
3. 如果方法不需要使用 recv
的值,可以用 _
替换它,比如:
func (_ receiver_type) methodName(parameter_list) (return_value_list) { ... }
- 下面是
非结构体类型
上方法的例子:
package mainimport "fmt"type IntVector []intfunc (v IntVector) Sum() (s int) {for _, x := range v {s += x}return
}func main() {fmt.Println(IntVector{1, 2, 3}.Sum()) // 输出是6
}
- 不能为
简单类型
定义方法,解决方法可以定义一个别名
1. 会报错 //cannot define new methods on non-local type int
func (a int) Add (b int){ //方法fmt.Println(a+b)
}
2. 解决方法
type myInt intfunc (a myInt) Add (b myInt){ //方法fmt.Println(a+b)
}
func main() {var aa,bb myInt = 3,4aa.Add(bb)}
10.6.2 函数和方法的区别
1.receiver_type
叫做 (接收者)基本类型,这个类型必须在和方法同样的包
中被声明
10.6.5 内嵌类型的方法和继承
- 它展示了
内嵌结构体上的方法
可以直接在外层类型的实例上调用
package mainimport ("fmt""math"
)// 8. 内嵌类型的方法继承
type Point struct {x, y float64
}func (r *Point) Abs() float64 {return math.Sqrt(r.x*r.x + r.y*r.y)
}type NamePoint struct {Pointname string
}func (r *NamePoint) Abs() float64 {return r.y
}
func main() {namePoint := new(NamePoint)namePoint.x = 3namePoint.y = 4fmt.Println(namePoint.Abs()) //覆写方法Abs() 输出 4 == r.yfmt.Println(namePoint.Point.Abs()) //输出 5 == math.Sqrt(r.x*r.x + r.y*r.y)
}
输出结果
4
5
练习10.8 继承
type Engine interface {Start()Stop()
}
type CC struct {}func (receiver *CC) Start() {}
func (receiver *CC) Stop() {}type Car struct {wheelCount intEngine
}// define a behavior for Car
func (car Car) numberOfWheels() int {return car.wheelCount
}type Mercedes struct {Car //anonymous field Car
}// a behavior only available for the Mercedes
func (m *Mercedes) sayHiToMerkel() {fmt.Println("Hi Angela!")
}func (c *Car) Start() {fmt.Println("Car is started")
}func (c *Car) Stop() {fmt.Println("Car is stopped")
}func (c *Car) GoToWorkIn() {// get in carc.Start()// drive to workc.Stop()// get out of car
}func main() {c := CC{}//m := Mercedes{Car{4, nil}}//能用 c代替nil的原因是 CC{}实例也实现了start and stop 方法,因此可以替换nil (在engine位置)m := Mercedes{Car{4, &c}}fmt.Println("A Mercedes has this many wheels: ", m.numberOfWheels())m.GoToWorkIn()m.sayHiToMerkel()
}
#10.6.6 如何在类型中嵌入功能
主要有两种方法来实现在类型中嵌入功能:
A:聚合(或组合
):包含一个所需功能类型的具名字段
。
B:内嵌
:内嵌(匿名地)所需功能类型
,像前一节 10.6.5 所演示的那样。
A:
package mainimport ("fmt"
)type Log struct {msg string
}type Customer struct {Name stringlog *Log
}func main() {c := new(Customer)c.Name = "Barak Obama"c.log = new(Log)c.log.msg = "1 - Yes we can!"// shorterc = &Customer{"Barak Obama", &Log{"1 - Yes we can!"}}// fmt.Println(c) &{Barak Obama 1 - Yes we can!}c.Log().Add("2 - After me the world will be a better place!")//fmt.Println(c.log)fmt.Println(c.Log())}func (l *Log) Add(s string) {l.msg += "\n" + s
}func (l *Log) String() string {return l.msg
}func (c *Customer) Log() *Log {return c.log
}
B:
package mainimport ("fmt"
)type Log struct {msg string
}type Customer struct {Name stringLog
}func main() {c := &Customer{"Barak Obama", Log{"1 - Yes we can!"}}c.Add("2 - After me the world will be a better place!")fmt.Println(c)}func (l *Log) Add(s string) {l.msg += "\n" + s
}func (l *Log) String() string {return l.msg
}func (c *Customer) String() string {return c.Name + "\nLog:" + fmt.Sprintln(c.Log.String())
}
10.6.7 多重继承
多重继承指的是类型获得多个父类型行为的能力,它在传统的面向对象语言中通常是不被实现的(C++ 和 Python 例外)。因为在类继承层次中,多重继承会给编译器引入额外的复杂度。但是在 Go 语言中,通过在类型中嵌入所有必要的父类型,可以很简单的实现多重继承。
作为一个例子,假设有一个类型 CameraPhone
,通过它可以 Call(
),也可以 TakeAPicture()
,但是第一个方法属于类型 Phone
,第二个方法属于类型 Camera
。
package mainimport ("fmt"
)type Camera struct{}func (c *Camera) TakeAPicture() string {return "Click"
}type Phone struct{}func (p *Phone) Call() string {return "Ring Ring"
}type CameraPhone struct {CameraPhone
}func main() {cp := new(CameraPhone)fmt.Println("Our new CameraPhone exhibits multiple behaviors...")fmt.Println("It exhibits behavior of a Camera: ", cp.TakeAPicture())fmt.Println("It works like a Phone too: ", cp.Call())
}
- 比如:我们想定义自己的
Integer 类型,
并添加一些类似转换成字符串的方法
,在 Go 中可以如下定义:
type Integer int
func (i *Integer) String() string {return strconv.Itoa(int(*i))
}
备注 goop包(需要更多面向对象的能力)
10.7 类型的 String() 方法和格式化描述符
当定义了一个有很多方法的类型时,十之八九你会使用 String() 方法
来定制类型的字符串形式的输出,换句话说:一种可阅读性和打印性的输出。如果类型定义了 String() 方法
,它会被用在 fmt.Printf()
中生成默认
的输出:等同于使用格式化描述符 %v
产生的输出。还有 fmt.Print()
和 fmt.Println()
也会自动使用 String() 方法
。
package mainimport ("fmt""strconv"
)type TwoInts struct {a intb int
}
type Person struct {firstName stringlastName string
}func main() {two1 := new(TwoInts)two1.a = 12two1.b = 10fmt.Printf("two1 is: %v\n", two1)fmt.Println("two1 is:", two1)fmt.Printf("two1 is: %T\n", two1)fmt.Printf("two1 is: %#v\n", two1)person := new(Person)person = &Person{"LOVE", "STORY"}fmt.Println(person)
}
func (r *Person) String() string {return fmt.Sprintf("(陕西省西安市的%s的%s)", r.firstName, r.lastName)
}
func (tn *TwoInts) String() string {return "(" + strconv.Itoa(tn.a) + "/" + strconv.Itoa(tn.b) + ")"
}
输出结果:
two1 is: (12/10)
two1 is: (12/10)
two1 is: *main.TwoInts
two1 is: &main.TwoInts{a:12, b:10}
(陕西省西安市的LOVE的STORY)
格式化描述符 %T 会给出类型的完全规格,%#v 会给出实例的完整输出
备注 自定义 string
不要在 String() 方法
里面调用涉及 String() 方法的方法
,它会导致意料之外的错误,比如下面的例子,它导致了一个无限递归调用
(TT.String() 调用 fmt.Sprintf
,而 fmt.Sprintf 又会反过来调用 TT.String()
),很快就会导致内存溢出:
type TT float64func (t TT) String() string {return fmt.Sprintf("%v", t)
}
t.String()
练习10.15
为 int 定义别名类型 TZ
,定义一些常量表示时区,比如 UTC
,定义一个 map
,它将时区的缩写映射为它的全称,比如:UTC -> "Universal Greenwich time"
。为类型 TZ 定义 String() 方法
,它输出时区的全称
// Output:
// Eastern Standard time
// Universal Greenwich time
// Central Standard time
package mainimport "fmt"type TZ intconst (HOUR TZ = 60 * 60UTC TZ = 0 * HOUREST TZ = -5 * HOURCST TZ = -6 * HOUR
)var timeZones = map[TZ]string{UTC: "Universal Greenwich time",EST: "Eastern Standard time",CST: "Central Standard time"}func (tz TZ) String() string { // Method on TZ (not ptr)if zone, ok := timeZones[tz]; ok {return zone}return ""
}func main() {fmt.Println(EST) // Print* knows about method String() of type TZfmt.Println(0 * HOUR)fmt.Println(-6 * HOUR)
}/* Output:
Eastern Standard time
Universal Greenwich time
Central Standard time
*/
10.8 垃圾回收和 SetFinalizer
1.比如当内存资源不足时调用 runtime.GC()
,它会在此函数执行的点上立即释放一大片内存
,此时程序可能会有短时的性能下降(因为 GC 进程在执行)
。
11 接口与反射
11.1 接口是什么
package mainimport "fmt"type stockPosition struct {ticker stringsharePrice float32count float32
}/* method to determine the value of a stock position */
func (s stockPosition) getValue() float32 {return s.sharePrice * s.count
}type car struct {make stringmodel stringprice float32
}/* method to determine the value of a car */
func (c car) getValue() float32 {return c.price
}/* contract that defines different things that have value */
type valuable interface {getValue() float32
}func showValue(asset valuable) {fmt.Printf("Value of the asset is %f\n", asset.getValue())
}func main() {var o valuable = stockPosition{"GOOG", 577.20, 4}showValue(o)o = car{"BMW", "M3", 66500}showValue(o)
}
练习11.1 simple_interface
// simple_interface.go
package mainimport ("fmt"
)type Simpler interface {Get() intPut(int)
}type Simple struct {i int
}func (p *Simple) Get() int {return p.i
}func (p *Simple) Put(u int) {p.i = u
}func fI(it Simpler) int {it.Put(5)return it.Get()
}func main() {var s Simplefmt.Println(fI(&s)) // &s is required because Get() is defined with a receiver type pointer
}// Output: 5
11.2 接口嵌套
type ReadWrite interface {Read(b Buffer) boolWrite(b Buffer) bool
}type Lock interface {Lock()Unlock()
}type File interface {ReadWriteLockClose()
}
11.3 类型断言:如何检测和转换接口变量的类型
如果转换合法,v
是 varI 转换到类型 T 的值
,ok
会是 true
;否则 v
是类型 T 的零值
,ok 是 false,也没有运行时错误发生。
if v, ok := varI.(T); ok { // checked type assertionProcess(v)return
}
// varI is not of type T
package mainimport ("fmt""math"
)type Square struct {side float32
}type Circle struct {radius float32
}type Shaper interface {Area() float32
}func main() {var areaIntf Shapersq1 := new(Square)sq1.side = 5areaIntf = sq1// Is Square the type of areaIntf?if t, ok := areaIntf.(*Square); ok {fmt.Printf("The type of areaIntf is: %T\n", t)}if u, ok := areaIntf.(*Circle); ok {fmt.Printf("The type of areaIntf is: %T\n", u)} else {fmt.Println("areaIntf does not contain a variable of type Circle")}
}func (sq *Square) Area() float32 {return sq.side * sq.side
}func (ci *Circle) Area() float32 {return ci.radius * ci.radius * math.Pi
}
11.4 类型判断:type-switch
变量 t
得到了 areaIntf 的值和类型
switch t := areaIntf.(type) {case *Square:fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle:fmt.Printf("Type Circle %T with value %v\n", t, t)
case nil:fmt.Printf("nil value: nothing to check?\n")
default:fmt.Printf("Unexpected type %T\n", t)
}
2.下面的代码片段展示了一个类型分类函数,它有一个可变长度参数
,可以是任意类型的数组
,它会根据数组元素的实际类型执行不同的动作
func classifier(items ...interface{}) {for i, x := range items {switch x.(type) {case bool:fmt.Printf("Param #%d is a bool\n", i)case float64:fmt.Printf("Param #%d is a float64\n", i)case int, int64:fmt.Printf("Param #%d is a int\n", i)case nil:fmt.Printf("Param #%d is a nil\n", i)case string:fmt.Printf("Param #%d is a string\n", i)default:fmt.Printf("Param #%d is unknown\n", i)}}
}
练习 11.4 simple_interface2.go:
package mainimport "fmt"type Simpler interface {Get() intSet(int)
}type Simple struct {i int
}func (p *Simple) Get() int {return p.i
}func (p *Simple) Set(u int) {p.i = u
}type RSimple struct {i intj int
}func (r *RSimple) Get() int {return r.j
}
func (r *RSimple) Set(n int) {r.j = n
}
func fI(it Simpler) int {switch it.(type) {case *RSimple:it.Set(50)return it.Get()case *Simple:it.Set(100)return it.Get()default:return 99}return 0
}
func main() {var s Simplefmt.Println(fI(&s))var R RSimplefmt.Println(fI(&R))var a Simplerfmt.Println(fI(a))
}
输出代码:
100
50
99
11.5 测试一个值是否实现了某个接口
假定 v
是一个值,然后我们想测试它是否实现了 Stringer 接口
,可以这样做
type Stringer interface {String() string
}if sv, ok := v.(Stringer); ok {fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v
}
实例:有Simpler接口 此接口有 Get 和 Set方法 有一个结构体 Simple实现了此接口,自定义类型Mystring没实现此接口,现在对值
进行测试,看他是否实现了此接口
1.类型断言只适用于接口,因此我们拿空接口来接收值
2.注意方法的接收者是否有 * 号,如果有 * 号 那么要将该值的指针传入
type Simpler interface {Get() intSet(int)
}
type Simple struct {i int
}func (p *Simple) Get() int {return p.i
}
func (p *Simple) Set(u int) {p.i = u
}
type Mystring string
func main() {var r Mystring = "123"var s Simple = Simple{i: 1}var i interface{}i = &rif sv, ok := i.(Simpler); ok {fmt.Println(ok, "-", sv.Get()) //note sv not a}fmt.Println("上面没有 下面有")var j interface{}j = &s //指针if sv, ok := j.(Simpler); ok { //括号里为接口名称fmt.Println(ok, "-", sv.Get()) //note sv not a}
}
输出结果:
上面没有 下面有
true - 1
11.6 使用方法集与接口
package mainimport ("fmt"
)type List []intfunc (l List) Len() int {return len(l)
}func (l *List) Append(val int) {*l = append(*l, val)
}type Appender interface {Append(int)
}func CountInto(a Appender, start, end int) {for i := start; i <= end; i++ {a.Append(i)}
}type Lener interface {Len() int
}func LongEnough(l Lener) bool {return l.Len()*10 > 42
}func main() {// A bare valuevar lst List// compiler error:// cannot use lst (type List) as type Appender in argument to CountInto:// List does not implement Appender (Append method has pointer receiver)// CountInto(lst, 1, 10)if LongEnough(lst) { // VALID: Identical receiver typefmt.Printf("- lst is long enough\n")}// A pointer valueplst := new(List)CountInto(plst, 1, 10) // VALID: Identical receiver typeif LongEnough(plst) {// VALID: a *List can be dereferenced for the receiverfmt.Printf("- plst is long enough\n")}
}
在 lst
上调用 CountInto
时会导致一个编译器错误,因为 CountInto
需要一个 Appender
,而它的方法 Append
只定义在指针上
。 在 lst
上调用 LongEnough
是可以的,因为 Len
定义在值上。
在 plst
上调用 CountInto
是可以的,因为 CountInto
需要一个 Appender
,并且它的方法 Append
定义在指针
上。 在 plst
上调用 LongEnough
也是可以的,因为指针会被自动解引用
。
Go 语言规范定义了接口方法集的调用规则
:
1.类型 *T
的可调用方法集包含接受者为 *T 或 T
的所有方法集
2.类型 T
的可调用方法集包含接受者为 T
的所有方法
3.类型 T 的可调用方法集不
包含接受者为 *T
的方法
The way to Go 要点知识相关推荐
- 网络安全基础要点知识介绍
本文章只为了方便查阅. 文章目录 网络安全 网络安全问题概述 两类密码体制 数字签名 鉴别 报文鉴别 实体鉴别 密钥分配 对称密钥的分配 公钥的分配 互联网使用的安全协议 运输层安全协议 参考文献 网 ...
- helloworld讲解cocos2d-x的编程思路与要点
用helloworld讲解cocos2d-x的编程思路与要点 本文以cocos2d-x的helloworld为例,讲解cocos2d-x引擎的特点和要点,2.2为了展示新功能,把包括屏幕自适应在内的新 ...
- android 距离右边距,APP界面设计中间距与边距的要点
原标题:APP界面设计中间距与边距的要点 广州UI设计别样设计表示,无论是做网页设计.后台系统设计.还是APP界面设计,都要遵守一定的设计规范,俗话说的好,无规矩不成方圆,在无需创新的情况下遵守设计规 ...
- 一文搞定深度学习建模预测全流程(Python)
作者 | 泳鱼 来源 | 算法进阶 本文详细地梳理及实现了深度学习模型构建及预测的全流程,代码示例基于python及神经网络库keras,通过设计一个深度神经网络模型做波士顿房价预测.主要依赖的Pyt ...
- 计算机对文字信息交流方式案例,《信息交流的方式》题本梳理_教师资格面试初中信息技术...
<信息交流的方式>题本梳理_教师资格面试初中信息技术,中公讲师为大家进行录制教师资格面试备考系列视频,希望对各位考生有所帮助.以下为<信息交流的方式>题本梳理_教师资格面试初中 ...
- 从tcp到netty(一)
发现自己近一年有些毛病,自己也算是研习了不少的源代码,看了不少的技术书籍,但是自己就是记忆力不行,总是过段时间就会忘记,忘记之后还得从头开始啃源码.啃书籍.而且有些重要技术点也会遗忘,导致再学习的时候 ...
- 最新Linux教程发布下载【最后更新4月12日
以下是Linux爱好者最新发布的Linux书籍,本贴定期更新,欢迎下载. 红帽(RedHat)授权Linux认证培训中心.只要你敢来,我就敢让你过! 二本Linux测试题目考察 http://www. ...
- Facebook大公开:解决NLG模型落地难题!工业界的新一波春天?
文 | 小喂老师 编 | 小轶 作为NLP领域的"三高"用户(高产.高能.高钞),FaceBook最近(2020年11月)又发表了一篇高水准文章,目前已被COLING-2020接收 ...
- UE4 iOS游戏开发
iOS要点知识 iOS 快速入门 iOS设备的兼容性 iOS Packaged Game Size
最新文章
- Ibatisnet示例:npetshop学习一
- [Win] 利用Memory DC抽取EXE的图标并保存为BMP文件
- 数据分析---《Python for Data Analysis》学习笔记【04】
- [Swift算法]巴比伦法(牛顿迭代法)求平方根
- 利用SSH 反向代理 ,实现跨局域网连接家里的linux 主机 (树莓派)
- Openlayers中使用Overlay实现点击要素显示html内容弹窗并且动态更改弹窗内容
- 划分数据集代码(按照4:1的比例)以及根据各自文件名写入txt文件
- linux c 获取时间戳 打印时间戳
- 为什么NOLOCK查询提示是个不明智的想法
- 宝新金融首席经济学家:区块链应用主要方向开始转向实体经济领域的商业场景
- 实战篇:教你建设企业销售分析系统
- markdown与latex:单行式子中连加连乘i放在下面\displaystyle
- html 播放wav,js播放wav文件(源码)
- excel学习-添加控件
- 2022年我国城镇污水处理运营市场空间可达730亿元
- CSS外边距重叠和高度坍塌完美解决
- WUST-CTF2020(武汉科技大学第一届WUST-CTF网络安全竞赛)WP
- Handler同步屏障
- 09-mumu模拟器调键盘,回车键
- linux挂载4tb硬盘分区,centos7挂载新加4T硬盘到home
热门文章
- 关于Unity碰撞检测失败的冷门原因
- EOS代码架构及分析(一)
- ‘SHIT’上最全有限状态机设计研究(一)-状态机介绍
- matlab怎样编程形成软件_MATLAB程序设计语言(1)-入门 – MATLAB中文论坛
- Ubuntu使用gym保存视频报错“Unknown encoder ‘libx264‘”
- 可可西里观后感(转)-保护藏羚羊
- 黑客攻破美一女孩房间安全摄像头并称自己是圣诞老人
- 听说,要把南京大学的大门拆了
- 战神引挚手游数据库解析mysql/mir
- linux下tp驱动程序,Linux安装TP-Link TL-WN722N 驱动