go-learn-note
Table of Contents
第一章 类型
变量
- Go是静态类型语言,不能在运行期改变变量的类型
- 在函数外,使用var来定义变量,初始值为0;如果提供初始化值,可省略变量类型,由编译
器自动推导
package main import "fmt" var globalX int var globalY = "abc" func main() { fmt.Println(globalX) fmt.Println(globalY) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0 // // abc // ////////////////////////////////////////////////////
- 在函数内部,还可以使用`:=`初始化,需要注意是新创建的local变量,还是更改了global
变量!
package main import "fmt" var globalX int func main() { localX := 123 fmt.Println(globalX) fmt.Println(localX) // Be careful! `:=` can also change the global value globalX := 456 fmt.Println(globalX) // Error !You can not REDEFINE one variable // localX := 789 // But of cause, you can change it value localX = 987 fmt.Println(localX) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0 // // 123 // // 456 // // 987 // ////////////////////////////////////////////////////
- 定义多个变量的方式有如下几种
package main import "fmt" var x, y, z int var s, n = "abc", 123 var ( a int b float32 ) func main() { n, s := 0x1234, "Hello, World!" fmt.Println(x, s, n) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0 Hello, World! 4660 // ////////////////////////////////////////////////////
- 多变量赋值是先计算相关值,然后从左到右依次赋值
package main import "fmt" func main() { data, i := [3]int{0, 1, 2}, 0 fmt.Println(i) fmt.Println(data) // When assigning multiple values in one line // You should: // [1] make sure all the value: (i => 0) and (data[i] => data[0]) // [2] assign value base on [1]: i = 2, data[0] = 100 i, data[i] = 2, 100 fmt.Println(i) fmt.Println(data) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0 // // [0 1 2] // // 2 // // [100 1 2] // ////////////////////////////////////////////////////
- "_"是特殊变量,用户忽略占位符
package main import "fmt" func test() (int, string) { return 1, "abc" } func main() { _, s := test() fmt.Println(s) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // o // // abc // ////////////////////////////////////////////////////
- "_"的占位符作用还可以体现在规避'编译器认为没使用的局部变量是error'
var s string // global variable unused is OK func main() { // Error ! => i declared and not used i := 0 // use following to block previous error // _ = i }
- 前面介绍过,一个变量是无法重定义(redefine)的,但是两个变量却可以重定义!这个是
go特别的地方:
- 可以在同一个block里面重定义已定义的变量(但是要两个变量一块的情况):这个也是
可以理解的,这是由go的特性决定的.比如下面的例子,我们s已经定义了,我们想再定义
一个y,而且两个定义写到一句里面(其实函数返回两个值就是这种情况).你不能苛求
我定义y的时候,能把s的值也写对啊,所以s就可以redefine了!
s := "abc" // same level, can redefine if define two variable at once s, y := "hello", 20
- 不同的block里面的就不存在所谓的"重定义"了,因为即便是变量名相同,也其实是两个 不同的变量了
- 可以在同一个block里面重定义已定义的变量(但是要两个变量一块的情况):这个也是
可以理解的,这是由go的特性决定的.比如下面的例子,我们s已经定义了,我们想再定义
一个y,而且两个定义写到一句里面(其实函数返回两个值就是这种情况).你不能苛求
我定义y的时候,能把s的值也写对啊,所以s就可以redefine了!
- 重定义例子如下
package main import "fmt" func main() { s := "abc" fmt.Println(&s) s, y := "hello", 20 fmt.Println(&s, y) { s, z := 1000, 30 fmt.Println(&s, z) } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0x82023e080 // // 0x82023e080 20 // // 0x82023e0b0 30 // ////////////////////////////////////////////////////
常量
- 常亮必须是编译期可以确定的数字,字符串,布尔值
package main import "fmt" const x, y int = 1, 2 const s = "Hello, World!" const ( a, b = 10, 100 c bool = false ) func main() { // one day const, always const const x = "xxx" // only x is used // unused const variable will not incur Error fmt.Println(x) }
- 常量还可以是len, cap, unsafe.Sizeof等编译器可确定结果的函数返回值
package main import ( "fmt" "unsafe" ) func main() { const ( a = "abc" b = len(a) c = unsafe.Sizeof(b) ) fmt.Println(a, b, c) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // abc 3 8 // ////////////////////////////////////////////////////
- 常量和iota是好朋友,iota能够提供"自增枚举值":
- 提供从0开始的自增枚举值是最简单的
package main import "fmt" func main() { const ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Saturday ) fmt.Println(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0 1 2 3 4 5 6 // ////////////////////////////////////////////////////
- 当前行的规则和"前面一行"的规则相同. 所以MB也是 1 << (10 * iota) 只不过iota是2了
package main import "fmt" func main() { const ( _ = iota // itoa = 0 KB int64 = 1 << (10 * iota) // itoa = 1 MB // Same with KB, but itoa = 2 here GB TB ) fmt.Println(KB, MB, GB, TB) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 1024 1048576 1073741824 1099511627776 // ////////////////////////////////////////////////////
- 还可以在一组赋值中,多次使用iota
package main import "fmt" func main() { const ( A, B = iota, iota << 10 C, D ) fmt.Println(A, B, C, D) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0 0 1 1024 // ////////////////////////////////////////////////////
- 如果iota的自增被打断,需要"显示"恢复,所谓显示,就是再输入一次iota
package main import "fmt" func main() { const ( A = iota B C = "c" D // same with previous E = iota // return to interrupted increasing number F ) fmt.Println(A, B, C, D, E, F) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0 1 c c 4 5 // ////////////////////////////////////////////////////
- 如果想让函数的参数"只有枚举类型和常量"可以访问,而普通变量无法轻易的作为函
数参数,那么可以使用"枚举+自定义类型"的方式
package main import "fmt" type Color int const ( Black Color = iota Red Blue ) func test(c Color) { fmt.Println("Color number is ", c) } func main() { c := Black test(c) // Const is auto convert test(1) x := 1 _ = x // Error! can not use x (type int) as type Color in function argument // test(x) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Color number is 0 // // Color number is 1 // ////////////////////////////////////////////////////
- 提供从0开始的自增枚举值是最简单的
基本类型
- go的类型更加明确,支持Unicode,支持常用数据结构,简单的几个类型如下
Type Length Default Range bool 1 false byte 1 0 unit8 rune 4 0 Unicode Code Point, int32 int,uint 4 or 8 0 32bit or 64bit float32 4 0.0 float64 8 0.0
引用类型
- 引用类型(reference type)包括slice, map和channel.它们有复杂的内部结构使用的
时候需要:
- 申请内存
- 初始化相关属性
- 内置函数new计算类型大小,为期分配零值内存,返回指针.而make是分配内存,初始化成
员列表,返回对象而非指针
package main import "fmt" func main() { a := []int{0, 0, 0} a[1] = 10 fmt.Println(a) // make return object b := make([]int, 3) b[1] = 10 fmt.Println(b) // new return the pointer c := new([]int) fmt.Println(*c) }
类型转换
- 不支持隐式类型转换,即便是从窄转向宽也不行
var b byte = 100 // var n int = b // Error: implicit convertation is not supported var n int = int(b) // ok for explicit convertation
- 同样不能将其他类型当成bool值使用
a := 100 // Error: non-bool a (type int) used as if condition if a { // }
字符串
- 字符串是immutable类型,内部指针指向UTF-8字节数组:
- 默认值是空字符串""
- 用索引号访问某个字节,如s[i]
- 不能用序列号获取字节元素指针,&s[i]非法
- 不可变类型,无法修改字节数组
- 字节数组尾部不包含NULL
- 使用索引号访问字符(byte)
package main import "fmt" func main() { s := "abc" fmt.Println(s[0] == '\x61', s[1] == 'b', s[2] == 0x63) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // true true true // ////////////////////////////////////////////////////
- 使用"`"定义不做转义处理的原始字符串,支持跨行
package main import "fmt" func main() { s := `a b\r\n\x00 c` fmt.Println(s) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // a // // b\r\n\x00 // // c // ////////////////////////////////////////////////////
- 连接字符串时, "+"必须在上一行的末尾
package main import "fmt" func main() { // '+' should be at the tail of the line s := "Hello, " + "World!" fmt.Println(s) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Hello, World! // ////////////////////////////////////////////////////
- 支持用两个索引号返回子串,子串依然指向原字节数组
package main import "fmt" func main() { s := "Hello, World!" fmt.Println(s[:5]) fmt.Println(s[7:]) fmt.Println(s[1:5]) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Hello // // World! // // ello // ////////////////////////////////////////////////////
- 单引号字符常量表示Unicode Code Point
package main import "fmt" func main() { fmt.Printf("%T\n", 'a') var c1, c2 rune = '\u6211', '们' fmt.Println(c1 == '我', string(c2) == "\xe4\xbb\xac") } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // int32 // // true true // ////////////////////////////////////////////////////
- 要修改字符串,可以先将其转换成[]rune或者[]byte,完成后再转换成为string,无论
哪种转换,都会重新分配内存,并复制字节数组
package main import "fmt" func main() { s := "abcd" bs := []byte(s) bs[1] = 'B' fmt.Println(string(bs)) u := "电脑" us := []rune(u) us[1] = '话' fmt.Println(string(us)) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // aBcd // // 电话 // ////////////////////////////////////////////////////
- for 循环遍历也会使用byte和rune两种方式
package main import "fmt" func main() { s := "abc汉字" for i := 0; i < len(s); i++ { fmt.Printf("%c, ", s[i]) } fmt.Println() for _, r := range s { fmt.Printf("%c,", r) } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // a, b, c, æ, ±, , å, , , // // a,b,c,汉,字, // ////////////////////////////////////////////////////
指针
- 支持指针类型*T, 指针的指针**T, 以及包含包名前缀的*<package>.T
- go的指针的特点有如下:
- 默认值nil,没有NULL常量
- 操作符"&"取变量地址, "*"透过指针访问目标对象
- 不支持指针运算,不支持"->"运算符,直接使用"."访问目标成员
- 例子如下
package main import "fmt" func main() { type data struct{ a int } var d = data{1234} var p *data p = &d fmt.Printf("%p, %v\n", p, p.a) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0xc082008310, 1234 // ////////////////////////////////////////////////////
- 不能对指针做加减法等运算
x := 1234 p := &x p++
- 可以在unsafe.Pointer和任意类型指针间进行转换
package main import ( "fmt" "unsafe" ) func main() { x := 0x12345678 p := unsafe.Pointer(&x) n := (*[4]byte)(p) for i := 0; i < len(n); i++ { fmt.Printf("%X ", n[i]) } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 78 56 34 12 // ////////////////////////////////////////////////////
- 返回局部变量指针是安全的,编译器会根据需要将其分配在GC heap上
func test() *int { x := 100 return &x }
- 将Point而转换成uintptr,可变相实现指针运算
package main import ( "fmt" "unsafe" ) func main() { d := struct { s string x int }{"abc", 100} p := uintptr(unsafe.Pointer(&d)) p += unsafe.Offsetof(d.x) p2 := unsafe.Pointer(p) px := (*int)(p2) *px = 200 fmt.Printf("%#v\n", d) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // struct { s string; x int }{s:"abc", x:200} // ////////////////////////////////////////////////////
自定义类型
- 类型其实可以分成两大类:
- 命名类型,包括bool, int, string
- 未命名类型,包括array, slice, map
- 具有相同声明的未命名类型被视为同一类型,比如:
- 具有相同基类型的指针
- 具有相同元素类型和长度的array
- 具有相同元素类型的slice
- 具有相同键值类型的map
- 具有相同元素类型和传输方向的channel
- 具有相同字段序列(字段名,类型,标签,顺序)的匿名struct
- 签名相同(参数和返回值,不包括参数名称)的function
- 方法集相同(方法名,方法签名相同,和次序无关)的interface
- a和b就不被认为是相同类型(不可以相互赋值)
package main func main() { var a struct { x int `a` } var b struct { x int `ab` } _ = a _ = b // Error! cannot use a (type struct { x int "a" }) //as type struct { x int "ab" } in assignment // b = a }
- 可用type在全局或函数内定义新类型
package main import "fmt" func main() { type bigint int64 var x bigint = 100 fmt.Println(x) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 100 // ////////////////////////////////////////////////////
- 新类型不是原类型的别名,除拥有相同的数据类型结构外,它们之间没有任何关系,不
会持有原类型任何信息。除非目标类型是未命名类型,否则必须显示转换
package main import "fmt" func main() { x := 1234 type bigint int64 var b bigint = bigint(x) var b2 int64 = int64(b) type myslice []int var s myslice = []int{1, 2, 3} var s2 []int = s fmt.Println(b2) fmt.Println(s2) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 1234 // // [1 2 3] // ////////////////////////////////////////////////////
第二章 表达式
保留字
- 所有保留字如下
break default func case defer go chan else goto const fallthrough if continue for import interface select map struct package switch range type return var
运算符
- 几个常规的运算符.需要特别注意x$^y,其意义是把x中的某些bit上的值,值为零(无论
原来是1是0), 至于哪些位置y中所有为1,是由y中有哪些位置为1来决定的
package main import "fmt" func main() { a := 0x011 b := 0x101 fmt.println(a & b) fmt.println(0x0001) fmt.println(a | b) fmt.println(0x0111) fmt.println(a ^ b) fmt.println(0x0110) // clear 1 to 0 on 0th and 3th from 0x101 for 0x011 fmt.println(a &^ b) fmt.println(0x010) } //////////////////////////////////////////////////// // <===================output===================> // // 1 // // 1 // // 273 // // 273 // // 272 // // 272 // // 16 // // 16 // ////////////////////////////////////////////////////
- 不支持运算符重载
- 尤其要注意的是"++", "–"是语句而非表达式,这样做的原因,是为了防止一些低级错误
n := 0 p := &n // b := n++ // syntax error // if n++ == 1 {} // syntax error // ++n // syntax error n++ *p++ // (*p)++
- 没有"~",取反也用"^"
package main import "fmt" func main() { x := 1 fmt.println(x) fmt.println(^x) } //////////////////////////////////////////////////// // <===================output===================> // // 1 // // -2 // ////////////////////////////////////////////////////
初始化
- 初始化复合类型,必须使用type,左括号必须在类型的尾部,而不是另起一行
package main import "fmt" func main() { a := struct{ x int }{100} b := []int{1, 2, 3} fmt.println(a) fmt.println(b) } //////////////////////////////////////////////////// // <===================output===================> // // {100} // // [1 2 3] // ////////////////////////////////////////////////////
- 初始化以','分割,可以分几行,但是最后一行必须是以','或者'}'结尾,这里可以放心 因为gofmt会帮助你.
控制流
if
- if的写法和c有如下区别:
- 条件表达式括号省略了
- 可以定义局部变量
- 左括号必须在最后
- 注意!不支持三元操作符 "a > b ? a : b"
for (while)
- go中只有for,没有while:
- 常见的for循环,各种语言都有
s := "abc" for i, n := 0, len(s); i < n; i++ { fmt.println(s[i]) }
- 替代while (n > 0)
n := len(s) for n > 0 { fmt.println(s[n]) n-- }
- 替代while(true)
for { fmt.println(s) }
- 常见的for循环,各种语言都有
range
- range操作的核心是返回(key,value), (key, value)并不是只有map有,其他的几种类
型如果能使用range的话,也有其`key`和`value`
1st value (key) 2nd value(value) string index s[index] array/slice index s[index] map key value channel element - range的使用方法如下, 不想使用的部分使用`_`
package main import "fmt" func main() { // string/array/slice they are the same s := "abc" for i, j := range s { fmt.printf("%d %c\n", i, j) } for _, c := range s { fmt.println(c) } //map m := map[string]int{"a": 1, "b": 2} for k, v := range m { fmt.println(k, v) } } //////////////////////////////////////////////////// // <===================output===================> // // 0 a // // 1 b // // 2 c // // 97 // // 98 // // 99 // // a 1 // // b 2 // ////////////////////////////////////////////////////
- range会涉及到"值拷贝",也就是说:
- 如果range所处理的类型是值, 那么key, value返回的都是copy value,典型的例子
就是array,下面的例子中`a`虽然已经开始被置为999,但是因为其"原始值"1已经
在range调用那一刻被拷贝出来了,所以a= v + 100的时候, v还是1
package main import "fmt" func main() { // array is not reference type, everytime range will // copy the value to i and v a := [3]int{0, 1, 2} for i, v := range a { if i == 0 { a[1], a[2] = 999, 999 fmt.println(a) // change a's value succesfully } a[i] = v + 100 // use a's copied value to construct the final version } fmt.println(a) } //////////////////////////////////////////////////// // <===================output===================> // // [0 999 999] // // [100 101 102] // ////////////////////////////////////////////////////
- 所以我们最好让range处理引用类型的容器,array的好基友slice就是引用类型的
"数组", 下面的例子中
package main import "fmt" func main() { // slice here is reference type, no copy happened when ranging s := []int{1, 2, 3, 4, 5} for i, v := range s { if i == 0 { // change s's size will not deduced the iteration times s = s[:3] s[2] = 100 } fmt.println(i, v) // s[2] will changed } fmt.println(s) // s[2] will changed } //////////////////////////////////////////////////// // <===================output===================> // // 0 1 // // 1 2 // // 2 100 // // 3 4 // // 4 5 // // [1 2 100] // ////////////////////////////////////////////////////
- 如果range所处理的类型是值, 那么key, value返回的都是copy value,典型的例子
就是array,下面的例子中`a`虽然已经开始被置为999,但是因为其"原始值"1已经
在range调用那一刻被拷贝出来了,所以a= v + 100的时候, v还是1
switch
- switch表达式可以是任意类型,不限于常量:
- 可以省略break,默认自动终止
package main import "fmt" func main() { x := []int{1, 2, 3} i := 2 switch i { case x[1]: fmt.println("a") case 1, 3: fmt.println("b") default: fmt.println("c") } } //////////////////////////////////////////////////// // <===================output===================> // // a // ////////////////////////////////////////////////////
- 可以继续下一个(不是全部)分支,使用fallthrough, 但是条件不再判断(也就是自
动运行下一个分支)
package main import "fmt" func main() { x := 10 switch x { case 10: fmt.println("a") fallthrough case 0: fmt.println("b") case -1: fmt.println("c") } } //////////////////////////////////////////////////// // <===================output===================> // // a // // b // ////////////////////////////////////////////////////
- 可以省略break,默认自动终止
- switch省略条件表达式,或者把条件表达式作为初始化条件,可以把switch作为if
elseif else来用
switch { case x[1] > 0: fmt.println("a") case x[1] < 0: fmt.println("b") default: fmt.println("c") }
goto, break, continue
- goto和c里面差不多,只不过标签必须使用大写
package main import "fmt" func main() { var i int for { fmt.println(i) i++ if i > 2 { goto break } } break: fmt.println("break") } //////////////////////////////////////////////////// // <===================output===================> // // 0 // // 1 // // 2 // // break // ////////////////////////////////////////////////////
- break用于在for, switch, select里面跳出当前循环, continue是进入下一次循环, 只能用于for循环
第三章 函数
函数定义
- bgo首先去掉了几个不太实用的函数feature:
- 不支持嵌套(nested)
- 不支持重载(overload)
- 不支持默认参数(default parameter)
- go函数有如下特点:
- 不需要声明原形
- 支持不定长变参数
- 支持多返回值
- 支持命名返回参数
- 支持匿名函数和闭包
- 使用func定义函数,格式你懂的
func test(x, y int, s string) (int, string) { n := x + y return n, fmt.sprintf(s, n) }
- 函数是first-class 对象.可以作为参数传递.可以使用type将复杂函数定义为函数类型
package main import "fmt" func test(fn func() int) int { return fn() } type formatfunc func(s string, x, y int) string func format(fn formatfunc, s string, x, y int) string { return fn(s, x, y) } func main() { s1 := test(func() int { return 100 }) s2 := format(func(s string, x, y int) string { return fmt.sprintf(s, x, y) }, "%d, %d", 10, 20) fmt.println(s1) fmt.println(s2) } //////////////////////////////////////////////////// // <===================output===================> // // 100 // // 10, 20 // ////////////////////////////////////////////////////
变参类型
- 变参其实就是最后一个参数是slice(只能有一个),而且使用slice变量作为参数的时候,
得写成'x…'
package main import "fmt" func test(s string, n ...int) string { var x int for _, i := range n { x += i } return fmt.sprintf(s, x) } func main() { fmt.println(test("sum: %d", 1, 2, 3)) s := []int{1, 2, 3, 4, 5} fmt.println(test("sum: %d", s...)) } //////////////////////////////////////////////////// // <===================output===================> // // sum: 6 // // sum: 15 // ////////////////////////////////////////////////////
返回值
- 不能使用容器接收多返回值,只能使用多个变量.不像要的参数使用"_"忽略
package main import "fmt" func test() (int, int) { return 1, 2 } func main() { // error => multiple-value test() in single-value context // s := make([]int, 2) // s = test() x, _ := test() fmt.println(x) } //////////////////////////////////////////////////// // <===================output===================> // // 1 // ////////////////////////////////////////////////////
- 多返回值作为实参:
- 理所当然可以看成是slice,所以可以作为n…类型的实参.
package main import "fmt" func test() (int, int) { return 1, 2 } func sum(n ...int) int { var x int for _, i := range n { x += i } return x } func main() { fmt.println(sum(test())) } //////////////////////////////////////////////////// // <===================output===================> // // 3 // ////////////////////////////////////////////////////
- 参数有x,y int类型的情况下,也可以使用多返回值作为实参
package main import "fmt" func test() (int, int) { return 1, 2 } func add(x, y int) int { return x + y } func main() { fmt.println(add(test())) } //////////////////////////////////////////////////// // <===================output===================> // // 3 // ////////////////////////////////////////////////////
- 理所当然可以看成是slice,所以可以作为n…类型的实参.
- 如果返回值写"名字",那么就等同于设置了这个"名字"的局部变量,return后面啥都没
有的话,就是隐式返回.
package main import "fmt" func add(x, y int) (z int) { z = x + y return } func main() { fmt.println(add(1, 2)) } //////////////////////////////////////////////////// // <===================output===================> // // 3 // ////////////////////////////////////////////////////
匿名函数
- 匿名函数也是神通广大,可以:
- 赋值给变量:
package main import "fmt" func main() { fns := [](func(x int) int){ func(x int) int { return x + 1 }, func(x int) int { return x + 2 }, } fmt.println(fns[0](100)) } //////////////////////////////////////////////////// // <===================output===================> // // 101 // ////////////////////////////////////////////////////
- 作为结构字段:
package main import "fmt" func main() { d := struct { fn func() string }{ fn: func() string { return "hello, world" }, } fmt.println(d.fn()) } //////////////////////////////////////////////////// // <===================output===================> // // hello, world // ////////////////////////////////////////////////////
- 在channel里面传送:
package main import "fmt" func main() { fc := make(chan func() string, 2) fc <- func() string { return "hello world" } fmt.println((<-fc)()) } //////////////////////////////////////////////////// // <===================output===================> // // hello world // ////////////////////////////////////////////////////
- 赋值给变量:
延迟调用
- 关键字defer用于注册延迟调用,这些调用知道return之前才执行,通常用于释放资源或
错误处理
package main import ( "fmt" "os" ) func test() error { f, err := os.Create("test.txt") if err != nil { return err } defer f.Close() f.WriteString("Hello World") return nil } func main() { err := test() if err != nil { fmt.Println("Error") } }
- 可以使用下面的方法"延迟读取"
package main import "fmt" func test() { x, y := 10, 20 // take x as parameter for anoymous function, saved x's value // as 10. defer func(i int) { fmt.Println("defer:", i, y) }(x) x += 10 y += 100 fmt.Println("x = ", x, "y = ", y) } func main() { test() } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // x = 20 y = 120 // // defer: 10 120 // ////////////////////////////////////////////////////
错误处理
- 没有结构化异常, 使用panic抛出错误,然后使用recover捕获错误
package main import "fmt" func test() { defer func() { if err := recover(); err != nil { fmt.Println(err.(string)) // err is interface{} type and now convert it to string } }() panic("panic error!") } func main() { test() } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // panic error! // ////////////////////////////////////////////////////
- 上面有err转化成string的部分,因为panic, recover的参数都是interface{}类型,因
此可以抛出任何类型的对象
func panic(v interface{}) func recover() interface{}
- 延迟调用中引发的错误,可以被后续延迟调用捕获,但仅最后一个错误可以被捕获
package main import "fmt" func test() { defer func() { fmt.Println(recover()) }() defer func() { panic("defer panic") }() panic("test panic") } func main() { test() } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // defer panic // ////////////////////////////////////////////////////
- 捕获函数recover必须在两个地方:
- 匿名函数:
package main func test() { defer func() { recover() }() panic("test panic") } func main() { test() }
- 函数体内才能起作用.
package main func except() { recover() } func test() { defer except() panic("test panic") } func main() { test() }
- 匿名函数:
- 其他方式(不在函数里面,或者是两层函数),总是失败,recover()会返回nil
package main import "fmt" func test() { defer recover() defer fmt.Println(recover()) defer func() { func() { fmt.Println("defer inner") recover() }() }() panic("test panic") } func main() { test() } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // defer inner // // <nil> // // panic: test panic // ////////////////////////////////////////////////////
- 除了使用panic引发中断性错误外,还可以使用error类型来返回错误,error类型其实是
一个接口,定义如下
type error interface { Error() string }
- 我们可以使用errors.New来创建一个error类型的instance,使用方法如下
package main import "fmt" import "errors" var ErrDivByZero = errors.New("hfeng: division by zero") func div(x, y int) (int, error) { if y == 0 { return 0, ErrDivByZero } return x / y, nil } func main() { switch z, err := div(10, 0); err { case nil: fmt.Println(z) case ErrDivByZero: panic(err) } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // panic: hfeng: division by zero // ////////////////////////////////////////////////////
第四章 数据
Array
- 数组和以往认知的数组有很大的不同:
- 数组是值类型,赋值和传参数会复制整个数组,而不是指针(所以有时候传递数组给函 数的话,代价很大)
- 数组长度必须是常量,且是类型的组成部分.int和int是不同类型
- 支持"==", "!="操作符
- 支持数组[n]*T, 数组指针*[n]T (这里看出类型在后的好处了)
- 和c语言相比,go的数组初始化方法非常的多样化
package main import "fmt" func main() { a := [3]int{1, 2} b := [...]int{1, 2, 3, 4} c := [5]int{2: 100, 4: 200} d := [...]struct { name string age uint8 }{ {"user1", 10}, {"user2", 20}, } fmt.Println(a) fmt.Println(b) fmt.Println(c) fmt.Println(d) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // [1 2 0] // // [1 2 3 4] // // [0 0 100 0 200] // // [{user1 10} {user2 20}] // ////////////////////////////////////////////////////
- 多维数组初始化方法如下, 需要注意的是,只支持第一维度的设置为"…"
package main import "fmt" func main() { a := [2][3]int{{1, 2, 3}, {4, 5, 6}} b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} fmt.Println(a) fmt.Println(b) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // [[1 2 3] [4 5 6]] // // [[1 1] [2 2] [3 3]] // ////////////////////////////////////////////////////
- 值拷贝行为会造成性能的下降,因为会复制整个数组
package main import "fmt" func test(x [2]int) { fmt.Printf("x: %p\n", &x) x[1] = 1000 } func main() { a := [2]int{} fmt.Printf("a: %p\n", &a) test(a) fmt.Println(a) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // a: 0xc082008310 // // x: 0xc082008350 // // [0 0] // ////////////////////////////////////////////////////
- 内置的len和cap对于数组来说都是返回数组长度(元素数量),因为不可能有空间而不放
数据
package main import "fmt" func main() { a := [2]int{} fmt.Println(len(a), cap(a)) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 2 2 // ////////////////////////////////////////////////////
Slice
- slice是go语言对于array的一种补充,因为array是值传递的,为了减少值传递作为参数 等情况下带来的性能问题,go特地发明了array的引用类型"替身"–slice
- slice在go的源代码里面其实就是1指针数组2长度3容量的一个组合
struct Slice { byte* array; uintgo len; uintgo cap; };
- 我们通过下面一个例子来看看slice和其"被绑定"数组的关系:
- 先看源代码:
data := [...]int{0, 1, 2, 3, 4, 5, 6} slice := data[1:4:5] // [low:high:max]
- 再来看看两者的关系(图例)
+- low high -+ +- max len = high - low | | | cap = max - low +---+---+---+---+---+---+---+ +---------+---------+---------+ data | 0 | 1 | 2 | 3 | 4 | 5 | 6 | slice | pointer | len = 3 | cap = 4 | +---+---+---+---+---+---+---+ +---------+---------+---------+ |<--- len ---->| | | | | | |<----- cap ------->| | | | +-------<<<-------- slice.array pointer ---<<<-----+
- 先看源代码:
- 使用[low:high:max]方法从已有数组创建slice的情况,不一定每次都全面的提供low,
high, max.使用默认值的情况总计如下:
- 代码
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
- 从data创建slice的各种默认值组合
expression slice len cap comment data[:6:8] [0 1 2 3 4 5] 6 8 low is array's low data[5:] [5 6 7 8 9] 5 5 high is array's high, max is array{low, high} data[:3] [0 1 2] 3 10 low is array's low, max is array{low, high} data[:] [0 1 2 3 4 5 6 7 8 9] 10 10
- 代码
- 如果slice是从某个数组创建的话,那么对于slice的读写操作其实就是对于底层数组的
读写
package main import "fmt" func main() { data := [...]int{0, 1, 2, 3, 4, 5} s := data[2:4] s[0] += 100 s[1] += 200 fmt.Println(s) fmt.Println(data) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // [102 203] // // [0 1 102 203 4 5] // ////////////////////////////////////////////////////
- slice如果创建的时候还要带个拖油瓶的"数组"那用起来太麻烦了,go还是可以"直接创
建"slice的
package main import "fmt" func main() { s1 := []int{0, 1, 2, 3, 8: 100} // Note! there are nothing in [] fmt.Println(s1, len(s1), cap(s1)) s2 := make([]int, 6, 8) // make to create, len and cap as parameter fmt.Println(s2, len(s1), cap(s1)) s3 := make([]int, 6) // len == cap here fmt.Println(s3, len(s3), cap(s3)) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // [0 1 2 3 0 0 0 0 100] 9 9 // // [0 0 0 0 0 0] 9 9 // // [0 0 0 0 0 0] 6 6 // ////////////////////////////////////////////////////
- 直接创建slice是非常"经济"的一件事情,因为大部分情况下都是用slice的,实在想使
用数组还可以使用指针来直接访问数组
package main import "fmt" func main() { s := []int{0, 1, 2, 3} p := &s[2] // *int, got underlying array pointer *p += 100 fmt.Println(s) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // [0 1 102 3] // ////////////////////////////////////////////////////
- 我们看到了[]里面没有东西是slice的特点,所以[][]T的类型是'[]T的数组'
package main import "fmt" func main() { data := [][]int{ []int{1, 2, 3}, []int{100, 200}, []int{11, 22, 33, 44}, } fmt.Println(data) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // [[1 2 3] [100 200] [11 22 33 44]] // ////////////////////////////////////////////////////
- 可以直接更改struct数组(或slice)的内部成员
package main import "fmt" func main() { d := [5]struct { x int }{} s := d[:] d[1].x = 10 s[2].x = 20 fmt.Println(d) fmt.Printf("%p, %p\n", &d, &d[0]) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // [{0} {10} {20} {0} {0}] // // 0xc082003f80, 0xc082003f80 // ////////////////////////////////////////////////////
reslice
- 所谓reslice是基于已有slice创建新的slice对象.以便在cap允许范围内调整属性,每
次获得新的slice,其len,cap等属性都会变化:
- 代码如下:每次创建的新slice其[low,high,max]都会有所改动.
package main import "fmt" func main() { s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Println(s, len(s), cap(s)) s1 := s[2:5] fmt.Println(s1, len(s1), cap(s1)) s2 := s1[2:6:7] fmt.Println(s2, len(s2), cap(s2)) // s3 := s2[3:6] // Error out of range } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // [0 1 2 3 4 5 6 7 8 9] 10 10 // // [2 3 4] 3 8 // // [4 5 6 7] 4 5 // ////////////////////////////////////////////////////
- 图例如下最后s2已经变成4,5,6,7, size为4, cap为7了,所以你的s3超过了我s2的cap
+-+-+-+-+-+-+-+-+-+-+ data |0|1|2|3|4|5|6|7|8|9| +-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ s1 |2|3|4| | | | | | len = 3, cap = 8 +-+-+-+-+-+-+-+-+ +-+-+-+-+-+ s2 |4|5|6|7| | len = 4, cap = 5 +-+-+-+-+-+ +-+-+-+ s3 |7|8|X| error: slice bounds out of range +-+-+-+
- 代码如下:每次创建的新slice其[low,high,max]都会有所改动.
- 新的对象,依然是指向底层数组
package main import "fmt" func main() { s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s1 := s[2:5] s1[2] = 100 s2 := s1[2:6] s2[3] = 200 fmt.Println(s) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // [0 1 2 3 100 5 6 200 8 9] // ////////////////////////////////////////////////////
append
- 向slice尾部添加数据,返回新的slice对象,简单点说,就是在array[high..cap]之间
写数据
package main import "fmt" func main() { data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s := data[:3] s2 := append(s, 100, 200) fmt.Println(data) fmt.Println(s) fmt.Println(s2) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // [0 1 2 100 200 5 6 7 8 9] // // [0 1 2] // // [0 1 2 100 200] // ////////////////////////////////////////////////////
- 我们也很容易想到,如果超过cap的限制,那么肯定不能再使用原来的数组了,而是要申
请新的数组,当然,这个过程是自动的
package main import "fmt" func main() { data := [...]int{0, 1, 2, 3, 4, 10: 0} s := data[:2:3] s = append(s, 100, 200) // append two, exceed cap fmt.Println(s, data) fmt.Println(&s[0], &data[0]) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // [0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0] // // 0x8201ea210 0x8201ee180 // ////////////////////////////////////////////////////
copy
- 函数copy在两个slice之间复制数据.其实就是把srcSlice的底层数组保存一份到
destSlice的底层数组上,然后srcSlice就可以释放了
package main import "fmt" func main() { data1 := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s1 := data1[:] fmt.Println(s1) data2 := [...]int{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} s2 := data2[:] fmt.Println(s2) copy(s1, s2) // copy s1 from s2 fmt.Println(s1) fmt.Println(s2) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // [0 1 2 3 4 5 6 7 8 9] // // [10 11 12 13 14 15 16 17 18 19 20] // // [10 11 12 13 14 15 16 17 18 19] // // [10 11 12 13 14 15 16 17 18 19 20] // ////////////////////////////////////////////////////
Map
- map是引用类型.键是支持(==, !=)的类型(比如number, string, pointer, array,
struct), 值可以是任意类型
package main import "fmt" func main() { m := map[int]struct { name string age int }{ 1: {"user1", 10}, 2: {"user2", 20}, } fmt.Println(m[1].name) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // user1 // ////////////////////////////////////////////////////
- 预先给make函数一个合理的元素数量参数,有助于提高性能,因为这会事先分配一大块
内存,而不是后续频繁扩张
m := make(map[string]int, 1000)
- 常见的查询(注意m["a"]是返回两个值的,不过通常不用第二个返回值error),删除等操
作如下
package main import "fmt" func main() { m := map[string]int{ "a": 1, } if v, ok := m["a"]; ok { // m["a"] will return two value! fmt.Println(v) } fmt.Println(m["c"]) m["b"] = 2 delete(m, "c") fmt.Println(len(m)) for k, v := range m { fmt.Println(k, v) } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 1 // // 0 // // 2 // // a 1 // // b 2 // ////////////////////////////////////////////////////
- 从map中取回的value是一个临时值,所以对其成员的修改是没有意义的, 所以更改map
内容的方法看起来有些奇怪
package main import "fmt" func main() { type user struct{ name string } m := map[int]user{ 1: {"user1"}, } fmt.Println(m) // m[1].name = "Tom" // cannot assign to m[1].name u := m[1] u.name = "Tom" m[1] = u fmt.Println(m) m2 := map[int]*user{ 1: &user{"user1"}, } fmt.Println(m2[1]) m2[1].name = "Jack" fmt.Println(m2[1]) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // map[1:{user1}] // // map[1:{Tom}] // // &{user1} // // &{Jack} // ////////////////////////////////////////////////////
Struct
- struct也是值类型,赋值和传参数都会复制全部内容.可用'_'定义补位字段
package main import "fmt" type Node struct { _ int id int data *byte next *Node } func main() { n1 := Node{ id: 1, data: nil, } n2 := Node{ id: 2, data: nil, next: &n1, } fmt.Println(n1) fmt.Println(n2) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // {0 1 <nil> <nil>} // // {0 2 <nil> 0x82024a020} // ////////////////////////////////////////////////////
- 初始化的时候,要包含全部的成员
package main import "fmt" type User struct { name string age int } func main() { u1 := User{"Tom", 20} fmt.Println(u1) // u2 := User{"Tom"} // too few values in struct initializer } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // {Tom 20} // ////////////////////////////////////////////////////
- struct里面还可以有匿名struct
package main import "fmt" type File struct { name string size int attr struct { perm int ower int } } func main() { f := File{ name: "test.txt", size: 1025, } var attr = struct { perm int ower int }{2, 0755} fmt.Println(f) f.attr = attr fmt.Println(f) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // {test.txt 1025 {0 0}} // // {test.txt 1025 {2 493}} // ////////////////////////////////////////////////////
匿名字段
- 匿名字段,就是某个struct里面的字段和"另外一个类型名字相同(可以是任意类型)"
package main import "fmt" type User struct { name string } type Manager struct { User title string } func main() { m := Manager{ User: User{"Tom"}, title: "Administrator", } fmt.Println(m) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // {{Tom} Administrator} // ////////////////////////////////////////////////////
- 嵌套匿名类,访问起来,就是逐级访问
package main import "fmt" type Resource struct { id int name string } type Classify struct { id int } type User struct { Resource Classify name string } func main() { u := User{ Resource{1, "people"}, Classify{100}, "Jack", } fmt.Println(u.name) fmt.Println(u.Resource.name) // fmt.Println(u.id) // ambiguous selector u.id fmt.Println(u.Classify.id) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Jack // // people // // 100 // ////////////////////////////////////////////////////
- 不能同时嵌套同一类型和其指针类型,因为他们名字相同
type Resource struct { id int } type User struct { *Resource // Resource // Error: duplicate field Resource name string }
面向对象
- 面向对象的三大特征(继承,多肽,封装)里面, GO仅仅支持封装
- 尽管匿名字段的内存布局和行为特征,类似继承,但是没有class关键字,也没有多肽,继承
package main import "fmt" type User struct { id int name string } type Manager struct { User title string } func main() { m := Manager{User{1, "Tom"}, "Administrator"} fmt.Println(m) // cannot use m (type Manager) as type User in assignment, // no Polymorphism here // var u User = m } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // {{1 Tom} Administrator} // ////////////////////////////////////////////////////
第五章 方法
方法定义
- 方法总是绑定对象实例,并将这个实例作为第一实际参数(receiver)
- 方法有其如下特点:
- 只能为当前包内存在的命名类型定义方法
- 参数receiver可任意命名,如方法中没有使用这个receiver,也可以只定义不给名字
- 参数receiver可以是类型T或是T* (其中T不能是接口或者指针)
- 不支持方法重载
- 可用实例value或者pointer调用全部方法,编译器自动转换
- go没有ctor和dtor,一般使用"简单工厂模式"返回对象实例
package main import "fmt" type Queue struct { elements []interface{} } func NewQueue() *Queue { return &Queue{make([]interface{}, 10)} } func (*Queue) Push(e interface{}) error { panic("not implemented") } func (self *Queue) length() int { return len(self.elements) } func main() { q := NewQueue() fmt.Printf("%d\n", q.length()) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 10 // ////////////////////////////////////////////////////
- 方法其实就是"第一个参数为receiver的函数", 而且receiver为T的时候是copy by value
而receiver为T*的时候,是copy by reference
package main import "fmt" type Data struct { x int } func (self Data) ValueTest() { // Pass by Value, &self is changing fmt.Printf("Value: %p\n", &self) } func (self *Data) PointerTest() { // Pass by reference, self is always the same fmt.Printf("Pointer: %p\n", self) } func main() { d := Data{} p := &d fmt.Printf("Data: %p\n", p) d.ValueTest() d.PointerTest() p.ValueTest() p.PointerTest() } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Data: 0x82024c220 // // Value: 0x82024c240 // // Pointer: 0x82024c220 // // Value: 0x82024c250 // // Pointer: 0x82024c220 // ////////////////////////////////////////////////////
匿名字段
- 一个struct内部有匿名字段的话,这个struct可以直接访问这个匿名字段的函数,就像
访问自己的函数一样.(用组合的方式实现了继承)
package main import "fmt" type User struct { id int name string } type Manager struct { User } func (self *User) ToString() string { return fmt.Sprintf("User: %p, %v", self, self) } func main() { m := Manager{User{1, "Tom"}} fmt.Printf("Manager: %p\n", &m) fmt.Println(m.ToString()) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Manager: 0x820258000 // // User: 0x820258000, &{1 Tom} // ////////////////////////////////////////////////////
- 继承的好处当然就是可以override 函数,我们通过为两个不同的struct(两者有相互包
含的关系)定义名字相同的方法来实现了override
package main import "fmt" type User struct { id int name string } type Manager struct { User title string } func (self *User) ToString() string { return fmt.Sprintf("User: %p, %v", self, self) } func (self *Manager) ToString() string { return fmt.Sprintf("Manager: %p, %v", self, self) } func main() { m := Manager{User{1, "Tom"}, "Administrator"} fmt.Println(m.ToString()) fmt.Println(m.User.ToString()) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Manager: 0x8201ec0c0, &{{1 Tom} Administrator} // // User: 0x8201ec0c0, &{1 Tom} // ////////////////////////////////////////////////////
方法集
- 每个类型和其方法之间的关系:
- 类型T方法集包含全部receiver T方法
- 类型*T方法集包含全部receiver T + *T方法
- 如果类型S包含匿名字段T,则S方法包含T 方法
- 如果类型S包含匿名字段*T,则S方法包含T + *T方法
- 不管嵌入T或者*T, *S方法总能包含T + *T方法
表达式
- 方法有如下两种"等价"的表达方式
- instance.method(args…)
- <type>.func(instance, args)
- 其实也很好理解,因为方法定义的时候,就和func一样(只不过前面多个type,可以是T也 可以是*T), 而调用的时候,则可以一来instance.method, 二来<T>.func(instance)
- method的调用例子如下:
- 使用*T的情况:
package main import "fmt" type User struct { id int name string } func (self *User) Test() { fmt.Printf("%p, %v\n", self, self) } func main() { u := User{1, "Tom"} u.Test() mValue := u.Test mValue() //use *T mExpression := (*User).Test //&u is the intance of (*T) mExpression(&u) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0x8201e41a0, &{1 Tom} // // 0x8201e41a0, &{1 Tom} // // 0x8201e41a0, &{1 Tom} // ////////////////////////////////////////////////////
- 使用T的情况, 我们可以看到这种情况下是pass-by-value
package main import "fmt" type User struct { id int name string } func (self User) Test() { fmt.Printf("%p, %v\n", &self, self) } func main() { u := User{2, "Jacky"} u.Test() mValue := u.Test mValue() mExpression := (User).Test mExpression(u) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 0x8201e41a0, {2 Jacky} // // 0x8201e4200, {2 Jacky} // // 0x8201e4240, {2 Jacky} // ////////////////////////////////////////////////////
- 使用*T的情况:
第六章 接口
接口定义
- 接口是一个或者多个方法签名的集合
- 任何类型的方法只要拥有与接口A"相同的"全部方法,就表示它"实现"了这个接口.注意, 实现某接口并不需要在该类型上显示添加接口声明
- 判断两个方法是否相同.主要是看:
- 名称相同
- 参数列表相同
- 返回值相同
- go里面的接口也有如下的特点:
- 接口命名是以er结尾
- 接口只有方法签名,没有实现
- 接口没有数据字段
- 接口中可以嵌入其他接口
- 类型可以"实现"多个接口
- 下面是一个接口的例子
package main import "fmt" type Stringer interface { String() string } type Printer interface { Stringer Print() } type User struct { id int name string } func (self *User) String() string { return fmt.Sprintf("user %d, %s", self.id, self.name) } func (self *User) Print() { fmt.Println(self.String()) } func main() { var t Printer = &User{1, "Tom"} t.Print() } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // user 1, Tom // ////////////////////////////////////////////////////
- 空接口interface{}没有任何方法的签名,这也就意味着任何的类型都是实现了空接口.
其作用类似面向对象语言中的根对象object (也类似c语言里面的void*)
package main import "fmt" func Print(v interface{}) { fmt.Printf("%T: %v\n", v, v) } func main() { Print(1) Print("Hello World!") } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // int: 1 // // string: Hello World! // ////////////////////////////////////////////////////
- 匿名接口可以作为变量类型, 变量类型也可以是strcut的一部分, 下面例子中匿名
interface类型类型的变量s是Tester的唯一成员. 而User实现了这个interface的唯一
函数String(),那么就我们说s和User的类型是一样的
package main import "fmt" type Tester struct { s interface { String() string } } type User struct { id int name string } func (self *User) String() string { return fmt.Sprintf("user %d, %s", self.id, self.name) } func main() { t := Tester{&User{1, "Tom"}} fmt.Println(t.s.String()) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // user 1, Tom // ////////////////////////////////////////////////////
执行机制
- 接口对象由两个指针组成:
- 接口返回临时对象, 数据指针持有的是目标对象的只读复制品,比如下例中的i其为
interface u直接返回的临时对象.更改原interface不会影响i
package main import "fmt" type User struct { id int name string } func main() { u := User{1, "Tom"} var i interface{} = u u.id = 2 u.name = "Jack" fmt.Printf("%v\n", u) fmt.Printf("%v\n", i.(User)) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // {2 Jack} // // {1 Tom} // ////////////////////////////////////////////////////
- 只有使用指针才能修改其状态
package main import "fmt" type User struct { id int name string } func main() { u := User{1, "Tom"} var vi, pi interface{} = u, &u // vi.(User).name = "Jack" // Error: cannot assign to vi.(User).name pi.(*User).name = "Jack" fmt.Printf("%v\n", vi.(User)) fmt.Printf("%v\n", pi.(*User)) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // {1 Tom} // // &{1 Jack} // ////////////////////////////////////////////////////
- 接口返回临时对象, 数据指针持有的是目标对象的只读复制品,比如下例中的i其为
interface u直接返回的临时对象.更改原interface不会影响i
接口转换
- 利用类型推断,可以判断某个对象是否"实现"了某个接口
package main import "fmt" type User struct { id int name string } func (self *User) String() string { return fmt.Sprintf("%d, %s", self.id, self.name) } func main() { var o interface{} = &User{1, "Tom"} ////////////////////////////////// // Source Code for golang // // type Stringer interface { // // String() string // // } // ////////////////////////////////// if i, ok := o.(fmt.Stringer); ok { fmt.Println(i) } u := o.(*User) // u := o.(User) // panic: interface conversion: interface is *main.User, not main.User fmt.Println(u) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 1, Tom // // 1, Tom // ////////////////////////////////////////////////////
- 还可以使用switch做批量类型判断
package main import "fmt" type User struct { id int name string } func main() { var o interface{} = &User{1, "Tom"} switch v := o.(type) { case nil: fmt.Println("nil") case fmt.Stringer: // interface fmt.Println(v) case func() string: // func fmt.Println(v()) case *User: // struct fmt.Printf("%d, %s\n", v.id, v.name) default: fmt.Println("unknown") } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 1, Tom // ////////////////////////////////////////////////////
- 超集接口(父类)可以转换为子集接口(子类),这也是模仿OO的特性
package main import "fmt" type Stringer interface { String() string } type Printer interface { String() string Print() } type User struct { id int name string } func (self *User) String() string { return fmt.Sprintf("%d, %v", self.id, self.name) } func (self *User) Print() { fmt.Println(self.String()) } func main() { var o Printer = &User{1, "Tom"} var s Stringer = o fmt.Println(s.String()) } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 1, Tom // ////////////////////////////////////////////////////
接口检查
- 可以使用如下的办法(转换nil 为Data类型的nil)来检查Data类型到底实现了Stringer没有
package main import "fmt" type Data struct { id int } func (self Data) String() string { return "" } func main() { // fmt.Stringer is interface type, _ is variable, but we don't // care about it. var _ fmt.Stringer = (*Data)(nil) // IF String() is not implemented => // *Data does not implement fmt.Stringer (missing String method) }
- 还可以让函数"typedef"成type,然后来"实现"接口.很绕口的使用方法
package main import "fmt" type Tester interface { Do() } // Regard function AS type type FuncDo func() // the function implement the interface! // then call itself! func (self FuncDo) Do() { self() } func main() { // t is Tester type // FuncDo implement the Do(), it is also the Tester type var t Tester = FuncDo(func() { fmt.Println("Hello World") }) t.Do() } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // Hello World // ////////////////////////////////////////////////////