💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ## golang 介绍 [官网](https://golang.org/) [百度百科](http://baike.baidu.com/link?url=OIpBeQaglgo2VoUMWgk2JOTfK-NfnC-iHiZ0uxznAHlK1c8CZP4vq7V86s66vSdiy8wlr3ptrJOjCyL91gv6kWmlEygxH1tkXuZcBPvvt_x7X7c8-vKVpxh-Wn0br7O8dxSCvJJc5VyLSDbStB6WP_) > Go语言于2009年11月正式宣布推出,成为开放源代码项目,并在Linux及Mac OS X平台上进行了实现,后追加Windows系统下的实现... **注意** 在接下来的学习中请确保go环境已经安装好,以及GOPATH环境变量和IED也已经准备好。 1. 测试Go版本 `win + r -> cmd -> go version`显示版本信息表示成功。 ![](https://box.kancloud.cn/9b05b7f9db7ba7355cff454176c75cd0_312x58.png) 2. 测试Go环境`win + r -> cmd -> go env`注意观察`GOPATH` ![](https://box.kancloud.cn/b6a864cd69ca382c9c4261a6726242ae_470x417.png) 3. GOPATH下最终会存在3个目录 ![](https://box.kancloud.cn/35de774047ae406b8c8e98224b8c9d7e_409x131.png) * bin(存放编译后生成的可执行文件) * pkg(存放编译后生成的包文件) * src(存放项目源码) ## 第一个Go程序 1. 在`GOPATH/src`目录下新建`hello.go`文件 2. 编写代码如下 ~~~ package main import ( "fmt" ) func main() { fmt.Println("hello , world") } ~~~ 3. 在此处打开命令窗口输入`go run hello.go` ![](https://box.kancloud.cn/f814781d18f3364947a725524188c3b7_175x21.png) > 一个golang 版本的helloword就完成了。 ## 关键字 > Go内置关键字(25个均为小写) |1|2|3|4|5| |--- | --- | --- | --- | --- | |break|default|func|interface|select| |case|defer|go|map|struct| |chan|else|goto|package|switch| |const|fallthrough|if|range|type| |continue |for|import|return|var| ## Go注释方法 ~~~ // :单行注释 /* */:多行注释 ~~~ ## 程序结构 1. Go程序是通过 `package` 来组织的(与`Java`类似) 2. 只有 `package` 名称为 `main` 的包可以包含 `main` 函数 3. 一个可执行程序**有且仅有**一个 `main` 包 4. 通过 `import` 关键字来导入其它非 `main` 包 5. 通过 `const` 关键字来进行常量的定义 6. 通过在函数体外部使用 `var` 关键字来进行全局变量的声明与赋值 7. 通过 `type` 关键字来进行定义类型的别名,结构(`struct`)或接口(`interface`)的声明 8. 通过 `func` 关键字来进行函数的声明 ![](https://box.kancloud.cn/872389245bc078becb0d34302d14b7fa_354x486.png) ## 包package ### 导入package ![](https://box.kancloud.cn/e049a0573ec028bd91890a13354040cf_166x79.png) ### package 导入合并 ![](https://box.kancloud.cn/0a7ca234f4401c682003ea972c589ace_159x98.png) **注意** * 导入包之后,就可以使用格式 `PackageName.FuncName`来对包中的函数进行调用 * 如果导入包之后未调用其中的函数或者类型将会报出编译错误: ![](https://box.kancloud.cn/2832b1621f2353b4f556ab5ca7166f47_424x60.png) ### package 别名 ![](https://box.kancloud.cn/2fad40a8d000f532a3ec37fc5850261e_321x220.png) >当使用第三方包时,包名可能会非常接近或者相同,此时就可以使用 别名来进行区别和调用 ### 省略调用 ![](https://box.kancloud.cn/1bf369737dce6f9a37b90cb07dd0897f_295x192.png) ![](https://box.kancloud.cn/f75106ee17520d02b94bf5daaf8201f0_300x72.png) ### 可见性规则 Go语言中,使用**大小写**来决定该 常量、变量、类型、接口、结构或函数是否可以被外部包所调用: ![](https://box.kancloud.cn/322fc0912f6895b4fadeba4dad0fd92e_250x113.png) **注意** * 首字母大写即为`public` * 首字母小写即为`private` #### 课堂作业 既然导入多个包时可以进行简写,那么声明多个 常量、全局变量 或一般类型(非接口、非结构)是否也可以用同样的方法呢?请自行尝试 ~~~ //当前程序的包名称 package main import "fmt" // 常量 const ( ZName = "zxysilent" ZEmail = "zxysilent@goxmail.com" ZPhone = 18284151024 ) // 包级变量(全局变量) var ( name = "zxysilent" email = "zxysilent@foxmail.com" phone = 18284151024 ) // 类型别名 type ( INT int STR string ) // 程序入口 func main() { fmt.Println("hello , word") } ~~~ ## Go类型 ### 基本类型 * 布尔型:bool - 长度:1字节 - 取值范围:true, false - 注意事项:不可以用数字代表true或false * 整型:int/uint - 根据运行平台可能为32或64位 * 8位整型:int8/uint8 - 长度:1字节 - 取值范围:-128~127/0~255 * 字节型:byte(uint8别名) * 16位整型:int16/uint16 - 长度:2字节 - 取值范围:-32768~32767/0~65535 * 32位整型:int32(rune)/uint32 - 长度:4字节 - 取值范围:-2^32/2~2^32/2-1/0~2^32-1 * 64位整型:int64/uint64 - 长度:8字节 - 取值范围:-2^64/2~2^64/2-1/0~2^64-1 * 浮点型:float32/float64 - 长度:4/8字节 - 小数位:精确到7/15小数位 * 复数:complex64/complex128 - 长度:8/16字节 * 足够保存指针的 32 位或 64 位整数型:uintptr * 其它值类型: - array、struct、string * 引用类型: - slice、map、chan * 接口类型:inteface * 函数类型:func ### 类型零值 >零值并不等于空值,而是当变量被声明为某种类型后的默认值, 通常情况下值类型的默认值为`0`,`bool`为`false`,`string`为空字符串 ## 变量&常量 ### 变量 #### 单个变量的声明与赋值 * 变量的声明格式:`var <变量名称> <变量类型>` * 变量的赋值格式:`<变量名称> = <表达式>` * 声明的同时赋值:`var <变量名称> [变量类型] = <表达式>` ~~~ //当前程序的包名称 package main import "fmt" // 程序入口 func main() { // 变量的申明 var a int //变量的赋值 a = 123 // 变量声明的同时赋值 var b int = 234 // 类型可省略,编译器可以自行推断 var c = 345 //最简单的 申明和赋值 d := 456 fmt.Println(a, b, c, d) } ~~~ ![](https://box.kancloud.cn/22bac1898614fcbbbf79a3b2bfbcb31b_524x129.png) #### 多个变量的声明与赋值 * 全局变量的声明可使用 `var()` 的方式进行简写 * 全局变量的声明不可以省略` var`,但可使用并行方式 * 所有变量都可以使用类型推断 * 局部变量**不**可以使用` var() `的方式简写,只能使用并行方式 > 全局 ~~~ package main var ( // 常规方式 a = "hello" // 并行方式 c, d = 1, 2 //syntax error: unexpected := //e:=5 ) //syntax error: non-declaration statement outside function body //e:=5 // 程序入口 func main() { } ~~~ > 局部 ~~~ package main import "fmt" func main() { // 多个变量的声明 var a, b, c int // 多个变量的赋值 a, b, c = 1, 2, 3 // 多个变量的声明同时赋值 var d, e, f int = 4, 5, 6 // 多个变量的省略类型的声明赋值(编译器推断类型 var g, h, i = 7, 8, 9 // 最简单的多个变量声明赋值 j, k, l := 10, 11, 12 fmt.Println(a, b, c, d, e, f, g, h, i, j, k, l) } ~~~ ![](https://box.kancloud.cn/2fefd9744a80249fb15f2f4200cd84c4_350x34.png) #### 变量的类型转换 * Go中不存在隐式转换,所有类型转换必须显式声明 * 转换只能发生在两种相互兼容的类型之间 * 类型转换的格式: `<ValueA> [:]= <TypeOfValueA>(<ValueB>)` ~~~ package main import "fmt" func main() { // 在相互兼容的两种类型之进行转换 var a float32 = 1.1 b := int(a) // 不兼容的转换无法通过编译(compile) var c bool = false d := int(c) fmt.Println(a, b, c, d) } ~~~ ![](https://box.kancloud.cn/06e0b0749602a43a3fd4456e7c3d2bc2_496x49.png) #### 课堂作业 请尝试运行以下代码,看会发生什么,并思考为什么 ~~~ package main import "fmt" func main() { var a int = 65 b := string(a) fmt.Println(b) } ~~~ ![](https://box.kancloud.cn/d6eec5539e913771e535ff93381d6720_391x30.png) > string() 表示将数据转换成文本格式,因为计算机中存储的任何东西 本质上都是数字,因此此函数自然地认为我们需要的是用数字65表示 的文本 A。 ### 常量 #### 常量的定义 * 常量的值在编译时就已经确定 * 常量的定义格式与变量基本相同 * 等号右侧必须是常量或者常量表达式 * 常量表达式中的函数必须是内置函数 ~~~ package main import "fmt" // 定义单个常量 const a int = 1 const b = 'A' const ( text = "12345" lens = len(text) num = b * 100 ) // 同时定义多个常量 const i, j, k = 1, false, "str" const ( d, e, f = i, !j, len(k) ) func main() { fmt.Println(a, b, text, lens, num, i, j, k, d, e, f) } ~~~ ![](https://box.kancloud.cn/0176de65c3212a82e10933dc1aa269de_357x38.png) #### 常量的初始化规则与枚举 * 在定义常量组时,如果不提供初始值,则表示将使用上行的表达式 * 使用相同的表达式不代表具有相同的值 * iota是常量的计数器,从0开始,组中每定义1个常量自动递增1 * 通过初始化规则与iota可以达到枚举的效果 * 每遇到一个const关键字,iota就会重置为0 ~~~ package main import "fmt" const ( a = "A" //"A" b //"A c = iota //2 d //3 ) func main() { fmt.Println(a, b, c, d) } ~~~ ![](https://box.kancloud.cn/3f55cc361585f8aa5d3c7a53587cdb31_332x37.png) ~~~ import "fmt" // 星期枚举 const ( // 第一个不可省略表达式 Monday = iota Tuesday Wednerday Thursday Friday Saturday Sunday ) func main() { fmt.Println(Monday, Tuesday, Wednerday, Thursday, Friday, Saturday, Sunday) } ~~~ ![](https://box.kancloud.cn/712655b1563a5140afce272c063f45ea_341x38.png) ### 运算符 **注意**Go中的运算符均是从左至右结合 优先级(从高到低) 1. `^ ! ` (一元运算符) 2. `* / % << >> & &^` 3. `+ - | ^ ` (二元运算符) 4. `== != < <= >= >` 5. `<- ` (专门用于channel) 6. `&&` 7. `||` ### 其他 #### 指针 Go虽然保留了指针,但与其它编程语言不同的是,在Go当中不支持指针运算以及`->`运算符,而直接采用`.`选择符来操作指针目标对象的成员 操作符`&`取变量地址,使用`*`通过指针间接访问目标对象 默认值为 `nil `而非 `NULL` ``` package main func main() { var i = 0 var p = &i println(*p) var p1 *int println(p1==nil) } ``` ![](https://box.kancloud.cn/d8ce3b829f1e3cb5efefe79de66d0bc9_323x46.png) #### 递增递减语句 在Go当中,`++` 与 `-- `是作为语句而并不是作为表达式 ``` package main func main() { var i = 0 var i1=i++ println(i1) } ``` ![](https://box.kancloud.cn/acbc0de159df10b650cda9b6b69c4ba0_488x35.png) ## 流程控制 ### if * 条件表达式没有括号 * 左大括号必须和条件语句或`else`在同一行 ``` package main func main() { // 简短申明赋值 a,b:=1,2 // 没有小括号 if a>b{ println(b) }else{ print(a) } } ``` * 支持一个初始化表达式(可以是并行方式) * 支持单行模式 ``` package main func main() { if a,b:=1,2;a>b{ println(b) }else{ print(a) } } ``` * 初始化语句中的变量为`block`级别,同时隐藏外部同名变量 ``` package main func main() { var a =true if a,b:=1,2;a>b{ println(b) }else{ print(a) } println(a) } ``` ### for * Go只有`for`一个循环语句关键字,但支持3种形式 * 初始化和步进表达式可以是多个值 * 条件语句每次循环都会被重新检查,因此**不建议**在条件语句中使用函数,尽量提前计算好条件并以变量或常量代替 * 左大括号必须和条件语句在同一行 ``` package main func main() { for{ //死循环 } } ``` ``` package main func main() { flag := 1 //while for flag < 5 { flag++ println(flag) } } ``` ``` package main func main() { //index:=1 // for ;index < 5;index++ { // println(index) // } for idx:=0;idx<5;idx++{ println(idx) } } ``` ### switch * 可以使用任何类型或表达式作为条件语句 * 不需要写`break`,一旦条件符合自动终止 * 如希望继续执行下一个`case`,需使用`fallthrough`语句 * 支持一个初始化表达式(可以是并行方式),右侧需跟分号 * 左大括号必须和条件语句在同一行 ``` package main func main() { swh := 1 switch swh { case 0: println(0) case 1: { println(1) println("OK") } default: println("default") } } ``` ``` package main func main() { switch swh:=1;{ case swh > 0: println(0) fallthrough case swh == 1: { println("OK") } default: println("default") } } ``` ### goto, break, continue > 跳转语句 * 三个语法都可以配合标签使用 * 标签名区分大小写,若不使用会造成编译错误 * `break`与`continue`配合标签可用于多层循环的跳出**标签同级** * `goto`是**调整执行位置**,与其它2个语句配合标签的结果并不相同 ``` package main func main() { FLG: for{ for i:=0;i<10;i++{ if i>2{ break FLG }else{ println(i) } } } } ``` ``` package main func main() { FLG: for i := 0; i < 10; i++ { for { println(i) continue FLG } } } ``` #### 课堂作业 将上面中的continue替换成goto,程序运行的结果还一样吗? ## Array * 定义数组的格式:`var <varName> [n]<type>` **n>=0** * **数组长度也是类型的一部分**,因此具有不同长度的数组为不同类型 * 数组之间可以使用`==`或`!=`进行比较,但不可以使用`<`或`>` * 可以使用`new`来创建数组,此方法返回一个指向数组的指针 * 数组在Go中为**值类型** * 注意区分指向数组的指针和指针数组 * Go支持多维数组 ``` package main import "fmt" func main() { var arr1 [5]int = [5]int{} arr1[1] = 99 var arr2 = [4]int{} // paintln 只能输出简单类型 //println(arr1) fmt.Println(arr1) // 不同的类型不能比较 //invalid operation: arr1 == arr2 (mismatched types [5]int and [4]int) // if arr1 == arr2 { // fmt.Println("arr1==arr2") // } //指向数组的指针 var arr3 = new([3]int) fmt.Println(&arr2, arr3) // 由编译器推断数组大小 arr4 := [...]int{1, 2, 3, 4, 5, 6, 10: 9} fmt.Println(arr4, len(arr4)) // 值类型 copy arr5 := arr4 fmt.Printf("%p,%p\n", &arr4[1], &arr5[1]) //arr6:=[2][3]int{} //多维数组 arr6 := [2][3]int{ {1, 2, 3}, {4, 5, 6}, } fmt.Println(arr6) } ``` ### range * 完整使用方式 `for k,v:=range arr{ /* do something*/}` * 索引方式 `for item:=range { /* do something*/}` * 值方式 `for _,v:=range arr{/* do something*/}` ``` package main import "fmt" func main() { var arr = [10]int{2, 3, 4, 5, 6, 7, 8, 9} for k,v:=range arr{ fmt.Println(k,v) //i:=0 //fmt.Printf("i:%p\n",&i) fmt.Printf("%p,%p\n",&k,&v) } println("oth") for item:=range arr{ fmt.Println(item) } for _,v:=range arr{ fmt.Println(v) } } ``` ![](https://box.kancloud.cn/3ce68d810665591af031a100e332e438_369x100.png) #### 课堂作业 选择排序 ``` package main import "fmt" func main() { //选择排序 arr := [...]int{1, 3, 2, 8, 5, 7, 9} fmt.Println(arr) for i := 0; i < len(arr)-1; i++ { for j := i + 1; j < len(arr); j++ { if arr[i] > arr[j] { // 交换两个变量的值,不用定义第三个变量 arr[i], arr[j] = arr[j], arr[i] } } } fmt.Println(arr) } ``` ## 切片Slice * 其本身并不是数组,它指向底层的数组 ``` type slice struct{ len int cap int data point } ``` ![](https://box.kancloud.cn/e9e02517d67ebb089065440ef219d7cf_391x224.png) * 作为变长数组的替代方案,可以关联底层数组的局部或全部 * **引用类型** * 可以直接创建或从底层数组获取生成 * 如果多个`slice`指向相同底层数组,其中一个的值改变会影响全部 * 使用`len()`获取元素个数,`cap()`获取容量 ``` package main import "fmt" func main() { var arr = [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // 一般方式 // var s1 []int =[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} var s1 = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Println(s1) // 通过数组 // var s2 = arr[a:b]// a:b a<=x<b 0<x<len() // var s2 = arr[:] // var s2 = arr[0:len(arr)] var s2 = arr[0:3] //0,1,2 fmt.Println(s2) var s3 = arr[2:4] //2,3 fmt.Println(s3) // 引用 s2[2]=99 fmt.Println(s2) fmt.Println(s3) fmt.Println(len(s3),cap(s3)) } ``` ![](https://box.kancloud.cn/3b17569f85f25d7b5c3c31069eb88473_291x120.png) * 一般使用`make()`创建 ` make([]T, len, cap)` > 其中`cap`可以省略,则和`len`的值相同 * `len`表示存数的元素个数,`cap`表示容量 * `append` `copy` ``` package main import "fmt" func main() { // make创建 slice var s1 = make([]int, 2, 4) fmt.Println(s1, len(s1), cap(s1)) //[0 0] 2 4 // 省略cap则和len值相同 var s2 = make([]int, 2) fmt.Println(s2, len(s2), cap(s2)) //[0 0] 2 2 // append 可能会返回新的slice for i:=0;i<10;i++{ s1=append(s1,i,i+1) fmt.Printf("%p\n",s1)//fmt.Printf("%p\n",&s1) } // append 一个slice s1=append(s1,s2...) s3:=[]int{1,2,3,4,5} s4:=[]int{7,8,9} //copy(s3,s4) //fmt.Println(s3)//[7 8 9 4 5] copy(s4,s3) fmt.Println(s4)//[1 2 3] } ``` ![](https://box.kancloud.cn/7e9c8372ac234dee205e6caad7ed61c6_277x227.png) **注意** > reslice时索引以被slice的切片为准 > 索引不可以超过新slice的切片的`len()`值 > 索引越界不会导致底层数组的重新分配而是引发错误 >`append`只会在`slice`尾部追加元素 >可以将一个slice追加在另一个slice尾部 >如果最终长度未超过追加到slice的容量则返回原始slice >如果超过追加到的slice的容量则将重新分配数组并拷贝原始数据 >%p: `&sliceNmae`,`sliceName` ## map * 类似其它语言中的哈希表或者字典,以`key-value`形式存储数据 * `key`必须是支持`==`或`!=`比较运算的类型,**不**可以是函数、`map`或`slice` * `map`使用`make()`创建,支持 `:=` 这种简写方式 * `make([keyType]valueType, cap)`,`cap`表示容量,可省略 * 超出容量时会自动扩容,但尽量提供一个合理的初始值 * 使用`len()`获取元素个数 * 键值对不存在时自动添加,使用`delete()`删除某键值对 * 使用`for range `对`map`和`slice`进行迭代操作 ``` package main import "fmt" func main() { // 创建 map //var m map[int]string = make(map[int]string, 4) //var m = make(map[int]string, 4) m := make(map[int]string, 4) fmt.Println(m,len(m)) m[2]="m2" m[10]="m10" fmt.Println(m) fmt.Println(m[1]) //没有会返回零值 res,ok:=m[1] fmt.Println(res,ok) res,ok=m[2] fmt.Println(res,ok) // 删除元素 delete (m,2) fmt.Println(m) m[1]="m1" m[2]="m2" m[3]=`m3` //遍历 for k:=range m{ fmt.Print(k) fmt.Printf("\t%p\n",&k) } } ``` > rang 循环随机 **注意** > 若对未初始化的map 不可进行赋值操作 ``` package main import "fmt" func main() { var m map[int]string fmt.Println(m[1]) delete(m,2) m[3]=`3` } ``` ![](https://box.kancloud.cn/60e3af3ffff46d7acc8b591bebd2461d_368x86.png) #### 课堂作业 根据在 for range 部分讲解的知识,尝试将类型为`map[int]string` 的键和值进行交换,变成类型`map[string]int` ``` package main import "fmt" func main() { //字面赋值 var m1 = map[int]string{ 1: "item1", 2: "item2", 3: "item3", 4: "item4", 5: "item5", 6: "item6", } fmt.Println(m1) var m2 = make(map[string]int) for k, v := range m1 { m2[v] = k } fmt.Println(m2) } ``` ## function * 定义函数使用关键字 func,且左大括号不能另起一行 * 函数也可以作为一种类型使用 * Go 函数 **不**支持嵌套、重载和默认参数 * 支持以下特性: > 无需声明原型 不定长度变参 多返回值 命名返回值参数 匿名函数、闭包 ``` func funcName(参数列表 )(返回参数列){ //do something } ``` ``` // 多个同类型变量可简写,一个返回值可以省略括号 //func add(a int ,b int)(int){ //func add(a, b int) (int) { func add(a, b int) int { return a + b } ``` ``` package main import "fmt" func main() { // 函数作为一种类型 add := func(a, b int) { fmt.Println(a + b) } add(10, 20) f := newFunc(100) fmt.Println(f(10)) fmt.Println(f(100)) // 不定参数 params(1, 2, 3) params(1) params() // 多返回值 //a10, a100 := mutRtn(10) //fmt.Println(a10, a100) // 只接收想要的值 a10, _ := mutRtn(10) fmt.Println(a10) _, n := named(100) fmt.Println(n) // 匿名函数 func(x int) { fmt.Println("你不知道我的名字", x) }(10) } // 函数作为类型 func newFunc(x int) func(int) int { return func(t int) int { return x + t } } // 不定参数 func params(x ...int) { fmt.Println(x) } // 多返回值 func mutRtn(a int) (int, int) { return a * 10, a * 100 } // 命名返回值 func named(a int) (x int, y int) { x = 10 * a y = 20 * a return } ``` ## defer * 函数体执行结束后按照调用顺序的**反顺序**逐个执行 * 常用于资源清理、文件关闭、解锁以及记录时间等操作 * 支持匿名函数的调用 * 如果函数体内某个变量作为defer时匿名函数的参数,则在定义defer时即已经获得了拷贝,否则则是引用某个变量的地址 ``` package main import "fmt" func main() { defer fmt.Println("hello defer") for i := 0; i < 5; i++ { func() { fmt.Println(i) }() defer func() { fmt.Println("defer:", i) }() //defer func(x int) { // fmt.Println("defer-:", x) //}(i) } } ``` * 通过与匿名函数配合可在return之后修改函数计算结果 ``` package main import "fmt" func main() { res := test(10) fmt.Println(res) res1 := test1(10) fmt.Println(res1) } func test(i int) int { defer func() { i++ }() return i * 10 } func test1(i int) (r int) { defer func() { r++ }() r = i * 10 return } ``` * 即使函数发生严重错误也会执行 * go 没有异常机制,但有 panic/recover 模式来处理错误 * panic 可以在任何地方引发,但recover只有在defer调用的函数中有效 ``` package main import "fmt" func main() { // exit defer func() { if err := recover(); err != nil { fmt.Println(err) } }() panic("提前终止程序") } ``` ## struct * 结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员 * 使用 `type <Name> struct{} `定义结构,名称遵循可见性规则(大/小写字母) * 支持指向自身的指针类型成员 * 支持匿名结构,可用作成员或定义成员变量 * 可以使用字面值对结构进行初始化 * 允许直接通过指针来读写结构成员 * 相同类型的成员可进行直接拷贝赋值 * 支持 == 与 !=比较运算符,但不支持 > 或 < * 支持匿名字段,本质上是定义了以某个类型名为名称的字段 * 嵌入结构作为匿名字段看起来像继承,但不是继承 * 可以使用匿名字段指针 ## method * Go 中虽没有class,但依旧有method * 通过显示说明receiver来实现与某个类型的组合 * 只能为同一个包中的类型定义方法 * Receiver 可以是类型的值或者指针 * 不存在方法重载 * 可以使用值或指针来调用方法,编译器会自动完成转换 * 从某种意义上来说,方法是函数的语法糖,因为receiver其实就是 * 方法所接收的第1个参数(Method Value vs. Method Expression) * 如果外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法 * 类型别名不会拥有底层类型所附带的方法 * 方法可以调用结构中的非公开字段 ## interface * 接口是一个或多个方法签名的集合 * 只要某个类型拥有该接口的所有方法签名,即算实现该接口,无需显示声明实现了哪个接口 * 接口只有方法声明,没有实现,没有数据字段 * 接口可以匿名嵌入其它接口,或嵌入到结构中 * 将对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针 * 只有当接口存储的类型和对象都为nil时,接口才等于nil * 接口调用不会做receiver的自动转换 * 接口同样支持匿名字段方法 * 接口也可实现类似OOP中的多态 * 空接口可以作为任何类型数据的容器 ## 更多 ### 类型断言 * 通过类型断言的`valur,ok`可以判断接口中的数据类型 * 使用`type switch`则可针对空接口进行比较全面的类型判断 ### 反射reflection * 反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息 * 反射会将匿名字段作为独立字段(匿名字段本质) * interface.data 是 settable即 pointer-interface则可利用反射修改对象状态 * 通过反射可以“动态”调用方法 ### 并发 * 并发不是并行,并发主要由切换时间片来实现“同时”运行,并行则是直接利用多核实现多线程的运行 ## Channel * Channel 是 goroutine 沟通的桥梁,大都是阻塞同步的 * 通过 make 创建,close 关闭 * Channel 是引用类型 * 可以使用 for range 来迭代不断操作 channel * 可以设置单向或双向通道 * 可以设置缓存大小,在未被填满前不会发生阻塞 ## Select * 可处理一个或多个 channel 的发送与接收 * 同时有多个可用的 channel时按随机顺序处理 * 可用空的 select 来阻塞 main 函数 * 可设置超时