(这篇文章是我在看李文塔的Go语言核心编程这本书记录下来的,算是个总结吧,方便以后复习用)
一、数据类型
1.1 布尔型、字符串
1.2 整型
unit8、unit16、unit32、unit64、int8、int16、int32、int64、byte(类似uint8)、rune(类似int32)、int、uintptr(无符号整型,存放一个指针)
1.3 浮点型
float32、float64、complex64、complex128
1.4 派生类型
指针类型(Pointer)、数组类型、结构化类型(struct)、Channel类型、函数类型、切片类型、接口类型(interface)、Map类型
1.5 查看一个变量的数据类型
fmt.println(reflect.TypeOf(x))
1.6 当使用等号 = 将一个变量的值负值给另一个变量时,如j=i,实际上是在内存中将i的值进行了拷贝,可以通过&i来获取i的内存地址
1.7 同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续
的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,
每个字都指示了下一个字所在的内存地址。
当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。
如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个
例子中,r2 也会受到影响。
1.8 常量
常量是一个简单值的标识符,在程序运行时,不会被修改的量。
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
显式类型定义:
const b string = "abc"
隐式类型定义:
const b = "abc"
常量还可以用作枚举:
const (
Unknown = 0
Female = 1
Male = 2
)
1.9 自增长
在 golang 中,一个方便的习惯就是使用 iota 标示符,它简化了常量用于增长数
字的定义,给以上相同的值以准确的分类。
const (
CategoryBooks = iota // 0
CategoryHealth // 1
CategoryClothing // 2
)
我们可以使用下划线跳过不想要的值
type AudioOutput int
const (
OutMute AudioOutput = iota // 0
OutMono // 1
OutStereo // 2
_
_
OutSurround // 5
)
1.10 for循环
for a := 0; a < 10; a++ {
fmt.Printf("a 的值为: %d\n", a)
}
for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:
for key, value := range oldMap {
newMap[key] = value
}
1.11 goto语句
LOOP: for a < 20 {
if a == 15 {
/* 跳过迭代 */
a = a + 1
goto LOOP
}
fmt.Printf("a的值为 : %d\n", a)
a++
}
这段代码跳过了a=15时的循环
1.12 函数
值传递
值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对
参数进行修改,将不会影响到实际参数。
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
/* 定义相互交换值的函数 */
func swap(x, y int) int {
var temp int
temp = x /* 保存 x 的值 */
x = y /* 将 y 值赋给 x */
y = temp /* 将 temp 值赋给 y*/
return temp;
}
引用传递
引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数
所进行的修改,将影响到实际参数。
/* 定义交换值函数*/
func swap(x *int, y *int) {
var temp int
temp = *x /* 保持 x 地址上的值 */
*x = *y /* 将 y 值赋给 x */
*y = temp /* 将 temp 值赋给 y */
}
1.13 main函数和init函数
Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数
(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。
虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的
可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数。
Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个
package中的init函数都是可选的,但package main就必须包含一个main函数。
程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在
编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次
(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多
次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进
来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的
话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常
量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行
main函数。
2.1 指针
一个指针变量可以指向任何一个值的内存地址它指向那个值的内存地址。
类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:
var ip int / 指向整型*/
var fp float32 / 指向浮点型 */
2.2 指针使用流程:
定义指针变量。
为指针变量赋值。
访问指针变量中指向地址的值。
在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。
package main
import "fmt"
func main() {
var a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */
ip = &a /* 指针变量的存储地址 */
fmt.Printf("a 变量的地址是: %x\n", &a )
/* 指针变量的存储地址 */
fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
}
2.3 声明数组
Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:
var variable_name [SIZE] variable_type
以上为一维数组的定义方式。数组长度必须是整数且大于 0。例如以下定义了数组
balance 长度为 10 类型为 float32:
var balance [10] float32
2.4 初始化数组
以下演示了数组初始化
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
2.5 结构体
go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同
的数据类型。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
//定义结构体类型
type Books struct {
title string
author string
subject string
book_id int
}
2.6 切片(Slice)
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种
灵活,功能强悍的内置类型切片 (“动态数组”) ,与数组相比切片的长度是不固定
的,可以追加元素,在追加时可能使切片的容量增大。
2.7 切片定义
你可以声明一个未指定大小的数组来定义切片:
var identifier []type
切片不需要说明长度。
或使用make()函数来创建切片:
var slice1 []type = make([]type, len)
也可以简写为
slice1 := make([]type, len)
也可以指定容量,其中capacity为可选参数。
make([]T, length, capacity)
这里 len 是数组的长度并且也是切片的初始长度。
2.8 切片初始化
s :=[] int {1,2,3 }
直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3
s := arr[:]
初始化切片s,是数组arr的引用
s := arr[startIndex:endIndex]
将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片
s := arr[startIndex:]
缺省endIndex时将表示一直到arr的最后一个元素
s := arr[:endIndex]
缺省startIndex时将表示从arr的第一个元素开始
s1 := s[startIndex:endIndex]
通过切片s初始化切片s1
s :=make([]int,len,cap)
通过内置函数make()初始化切片s,[]int 标识为其元素类型为int的切片
2.9 append()
func main() {
var numbers []int
printSlice(numbers)
/* 允许追加空切片 */
numbers = append(numbers, 0)
printSlice(numbers)
/* 向切片添加一个元素 */
numbers = append(numbers, 1)
printSlice(numbers)
/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)
printSlice(numbers)
/* 创建切片 numbers1 是之前切片的两倍容量*/
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)
printSlice(numbers1)
}
2.10 封装
type data struct {
val int
}
//封装的时候一定要是 基于指针类型的方法,否则不是同一个对象。
func (p_data* data)set(num int) {
p_data.val = num
}
func (p_data* data)show() {
fmt.Println(p_data.val)
}
func main() {
p_data := &data{4}
p_data.set(5)
p_data.show()
}
2.11 继承
type parent struct {
val int
}
type child struct {
parent
num int
}
func main() {
var c child
c = child{parent{1}, 2}
fmt.Println(c.num)
fmt.Println(c.val)
}
2.12 多态
type act interface {
write()
}
type xiaoming struct {
}
type xiaofang struct {
}
func (xm *xiaoming) write() {
fmt.Println("xiaoming write")
}
func (xf *xiaofang) write() {
fmt.Println("xiaofang write")
}
func main() {
var w act;
xm := xiaoming{}
xf := xiaofang{}
w = &xm
w.write()
w = &xf
w.write()
}
2.13 接口
接口类型是对其它类型行为的抽象和概括;因为接口类型不会和特定的实现细节绑
定在一起,通过这种抽象的方式我们可以让我们的函数更加灵活和更具有适应能力。
很多面向对象的语言都有相似的接口概念,但Go语言中接口类型的独特之处在于它
是满足隐式实现的。也就是说,我们没有必要对于给定的具体类型定义所有满足的
接口类型;简单地拥有一些必需的方法就足够了。这种设计可以让你创建一个新的
接口类型满足已经存在的具体类型却不会去改变这些类型的定义;当我们使用的类
型来自于不受我们控制的包时这种设计尤其有用。
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一
起,任何其他类型只要实现了这些方法就是实现了这个接口。
2.14 接口实例
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phone.call()
phone = new(IPhone)
phone.call()
}
2.15 类型断言
golang的语言中提供了断言的功能。golang中的所有程序都实现了interface{}的接
口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有
了interface{}的接口,这种做法和java中的Object类型比较类似。那么在一个数据通
过func funcName(interface{})的方式传进来的时候,也就意味着这个参数被自动的
转为interface{}的类型。
2.16 断言的使用
func funcName(a interface{}) string {
value, ok := a.(string)
if !ok {
fmt.Println("It is not ok for type string")
return ""
}
fmt.Println("The value is ", value)
return value
}
func main() {
var a int = 10
funcName(a)
}
2.17 断言的使用
func sqlQuote(x interface{}) string {
if x == nil {
return "NULL"
} else if _, ok := x.(int); ok {
return fmt.Sprintf("%d", x)
} else if _, ok := x.(uint); ok {
return fmt.Sprintf("%d", x)
} else if b, ok := x.(bool); ok {
if b {
return "TRUE"
}
return "FALSE"
} else if s, ok := x.(string); ok {
return sqlQuoteString(s) // (not shown)
} else {
panic(fmt.Sprintf("unexpected type %T: %v", x, x))
}
}
2.18 写文件操作
import (
"fmt"
"os"
)
func main() {
myFile := "./abc.txt"
fout, err := os.Create(myFile)
//fout, err := os.OpenFile(myFile, os.O_CREATE, 0644)
if err != nil {
fmt.Println(err)
return
}
for i := 0; i < 10; i++ {
outstr := fmt.Sprintf("%s:%d\n", "Hello world",i)
fout.WriteString(outstr)
fout.Write([]byte("abcd\n"))
}
fout.Close()
}
2.19 读文件操作
fin, err := os.Open(filename)
if err != nil {
fmt.Println(err)
}
defer fin.Close()
buf := make([]byte, 1024) //开辟1024个字节的slice 作为缓冲
for {
n, _ := fin.Read(buf)
if n == 0 {
//0 表示到达EOF
break
}
os.Stdout.Write(buf)
}
2.20 文件复制
func main() {
fi, err := os.Open("/home/itcast/abc.txt")//打开输入*File
if err != nil { panic(err) }
defer fi.Close()
fo, err := os.Create("/home/itcast/abc_new.txt")//创建输出*Fi
le
if err != nil { panic(err) }
defer fo.Close()
buf := make([]byte, 1024)
for {
n, err := fi.Read(buf)//从input.txt读取
if err != nil && err != io.EOF { panic(err) }
if n == 0 { break }
if n2, err := fo.Write(buf[:n]); err != nil {//写入output.txt,直到错误
panic(err)
} else if n2 != n {
panic("error in writing")
}
}
}
2.21 文件复制2
func main() {
b, err := ioutil.ReadFile("input.txt")//读文件
if err != nil { panic(err) }
err = ioutil.WriteFile("output.txt", b, 0644)//写文件
if err != nil { panic(err) }
}
3.1 函数
函数是 Go 程序源代码的基本构造单位,一个函数的定义包括如下几个部分 函数声明关
键字 也町、 函数名、参数列表、返回列表和函数体。函数名遵循标识符的命名规则, 首字母的
大小写决定该函数在其他包的可见性:大写时其他包可见,小写时只有相同的包可以访问;函
数的参数和返回值需要使用“()”包裹,如果只有一个返回值,而使用的是非命名的参数,则返回参数()
可以省略。
Go 函数实参到形参的传递永远是值拷贝 有时函数调用后实参指向的值发生了变化,那是
因为参数传递的是指针值的拷贝,实参是一个指针变量,传递给形参的是这个指针变量的副本,
二者指向同 地址 本质上参数传递仍然是值拷贝。
3.2 不定参数
所有的不定参数类型必须是相同的
不定参数必须是函数的最后 参数。
不定参数名在函数体 内相当于切片,对切片的操作同样适合对不定参数的操作 例如
func sum(arr ...int) (sum int) {
for , v : = range arr { // 此时 arr 就相当于切片,可以使用 range
sum += v
}
return
}
切片可以作为参数传递给不定参数,切片名后要加上"…"
array := [...]int{1,2,3,4}
sum(array...)
数纽不可以作为实参传递给不定参数的函数
3.3 函数签名
函数类型有叫函数签名,可以用fmt.Printf("%T\n",sum)来打印函数签名
两个函数类型相同的条件是:拥有相同的形参列表和返回值列表(列表元素的次序、个数
和类型都相同),形参名可以不同
3.4 匿名函数
匿名函数可以看作函数字面量 所有直接使用函
数类型变量的地方都可以由匿名函数代替。医名函数可以直接赋值给函数变量,可以当作实参,
可以作为返回值,还可以直接被调用
//匿名 函数被直接赋值函数变量
var sum= func(a , b int) int {
return a + b
}
//匿名函数作为实参
doinput(func(x , y int) int {
return x + y
} , 1 , 2)
3.5 闭包
闭包是由函数及其相关引用环境组合而成的实体,一般通过在匿名函数中引用外部函数的
局部变量或包全局变量构成
多次调用该函数,返回的多个闭包所引用的外部变量是多个副本,原因是每次调用函
数都会为局部变量分配内存
用一个闭包函数多次,如果该闭包修改了其引用的外部变量,则每一次调用该闭包对
该外部变量都有影响,因为闭包函数共享外部引用。
4.1 命名类型
类型可以通过标识符来表示,这种类型称为命名类型。
使用 type 声明的是命名类型
4.2 未命名类型
一个类型由预声明类型、关键字和操作符组合而成,这个类型称为未命名类型。未命名类
型又称为类型字面量(Type Literal),本书中的未命名类型和类型字面量二者等价。
Go 语言的基本类型中的复合类型:数组( array )、 切片( li ce )、 字典( map )、通道( channel )、
指针(pointer 函数字面量( function )、结构( struct )和接口( interface 〕都属于类型字面量,
也都是未命名类型
所以*int、[]int、[2]int、map[k]v都是未命名类型
使用 struct 字面量声明的是未命名类型
4.3 类型可赋值
var b T2 = a
a可以复制给变量b必须满足如下条件之一
*(1)T1和T2的类型相同
*(2)T1和T2具有相同的底层类型,并且T1和T2里面至少有一个未命名类型
*(3)T2是接口类型,T1是具体类型,并且T1和T2里面至少有一个未命名类型
*(4)T1和T2都是通道类型,他们拥有相同的元素类型,并且T1和T2里面至少有一个未命名类型
*(5)a是一个字面常量值,可以用来表示类型T的值
4.4 类型强制转换
由于 Go 是强类型的语言,如果不满足自动转换的条件,则必须进行强制类型转换
非常量类型的变量 x 可以强制转化并传递给类型 T 需要满足如下任一条件
*(1)x可以直接复制给T类型变量
*(2)x的类型和T具有相同的底层类型
*(3)x 的类型和T 都是未命名的指针类型,并且指针指向的类型具有相同的底层类型
*(4)x 的类型和T 都是整型,或者都是浮点型
*(5)x 的类型和T 都是复数类型
*(6)x 是整数值或口byte 类型的值,T是 string 类型。
*(7)x 是一个字符串,T[] byte [] rune
4.5 自定义类型
前面介绍命名类型时提到了自定义类型。用户自定义类型使用关键字type,,其语法格式是
type newtype oldtype oldtype 可以是自定义类型、预声明类型、未命名类型中的任意一种。
4.6 类型方法
type T struct {
a int
}
func (t T) Get () int {
return t.a
}
func (t *T)Set(int i){
t.a = i
}
t.Get()
4.7 方法值
方法值(method value)其实是一个带有闭包的函数变量,其底层实现原理和带有闭包的匿名函数类似,
接收值被隐私地绑定到方法值得闭包环境中。后续调用不需要在显示地传递接受者。
f := t.Set()
f(2)
4.8 方法表达式
t := T{a=1}
(T).Get(t)与 f := T.Get; f(t)等价
(*T).Set(&t,10)与f2 := T.Set;f3(&t,10)等价
优先使用方法值
4.9 组合和方法集
结构类型( struct )为 Go 提供了强大的类型扩展,主要体现在两个方面
struct 可以嵌入任意其它类型的字段
struct 可以嵌套自身的指针类型的字段 这
4.10 组合
从前面讨论的命名类型的方法可知,使用 type 定义的新类型不会继承原有类型的方法,有
个特例就是命名结构类型,命名结构类型可以嵌套其他的命名类型的宇段,外层的结构类型是
可以调用嵌入字段类型的方法,这种调用既可以是显式的调用,也可以是隐式的调用。这就是
Go 的“继承”,准确地说这就是 Go 的“组合”。
不推荐在多层的 struct 类型中内嵌多个同名的宇段;但是井不反对 tru ct 定义和内嵌字段同
名方法的用法,因为这提供了一种编程技术 使得 struc 能够重写 内嵌字段的方法,提供面向
对象编程中子类覆盖父类的同名方法的功能
4.11 函数类型
使用 func Functio ~ame ()语法格式定义的函数我们
称为“有名函数”,这里所谓的有名是指函数在定义时指定了“函数名”;与之对应的是“匿名
函数”,所谓的医名函数就是在定义时使用 func ()语法格式, 没有指定函数名。通常所说的函
数就是指“有名函数”。
5.1 接口声明
Go 的接口分为接口字面量类型和接口命名类型 接口的声明使用 interface 关键字
接口字面量类型的声明语法如下:
interface{
MethodSignature1
}
接口命名类型使用type关键字声明,
type interfaceName interface{
MethodSignature1
}
接口定义大括号内可以是方法声明的集合,也可以嵌入另一个接口类型匿名字段,还可以
是二者的混合。接口支持嵌入匿名接口宇段,就是一个接口定义里面可以包括其他接口,
声明新接口类型的特点
(1)接口的命名一般以“er ”结尾
(2)接口定义的内部方法声明不需要 func 引导。
(3)在接口定义中,只有方法声明没有方法实现。
5.2 接口初始化
单纯地声明一个接口变量没有任何意义,接口只有被初始化为具体的类型时才有意义
。没有初始化的接口变量,其默认值是 nil
5.3 实例赋值接口
如果具体类型实例的方法集是某个接口的方法集的超集,则称该具体类型实现了接口,可
以将该具体类型的实例直接赋值给接口类型的变量 ,此时编译器会进行静态的类型检查。接口
被初始化后,调用接口的方法就相当于调用接口绑定的具体类型的方法,这就是接口调用的语义
5.4 接口变量赋值接口变量
已经初始化的接口类型变量a直接赋值给另一个接口变量b,要求b的方法集是a的方法集的子集。
6.1 Goroutine
goroutine不同于thread,threads是操作系统中的对于一个独立运行实例的描述,不
同操作系统,对于thread的实现也不尽相同;但是,操作系统并不知道goroutine的
存在,goroutine的调度是有Golang运行时进行管理的。启动thread虽然比process
所需的资源要少,但是多个thread之间的上下文切换仍然是需要大量的工作的(寄
存器/Program Count/Stack Pointer/…),Golang有自己的调度器,许多goroutine
的数据都是共享的,因此goroutine之间的切换会快很多,启动goroutine所耗费的资
源也很少,一个Golang程序同时存在几百个goroutine是很正常的。
channel,即“管道”,是用来传递数据(叫消息更为合适)的一个数据结构,即可以
从channel里面塞数据,也可以从中获取数据。channel本身并没有什么神奇的地
方,但是channel加上了goroutine,就形成了一种既简单又强大的请求处理模型,
即N个工作goroutine将处理的中间结果或者最终结果放入一个channel,另外有M个
工作goroutine从这个channel拿数据,再进行进一步加工,通过组合这种过程,从
而胜任各种复杂的业务模型。
当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫它main
goroutine。新的goroutine会用go语句来创建。在语法上,go语句是一个普通的函
数或方法调用前加上关键字go。go语句会使其语句中的函数在一个新创建的
goroutine中运行。而go语句本身会迅速地完成。
*go 的执行是非阻塞的,不会等待
*go 后面的函数的返回值会被忽略
*调度器不能保证多个goroutine的执行次序
*没有父子goroutine的概念,所有的goroutine是平等地被调度和执行的
*Go 程序在执行时会单独为 main 函数 创建一个goroutine ,遇到其他 go 关键字时再去创建其他的 goroutine
runtime.GOMAXPROCS(0)表示查询当前GOMAXPROCS(默认为cup的个数)
runtime.NumGoroutine()表示查询当前程序使用的goroutine数量
6.2 chan通道
通道是有类型的 ,可以简单地把它理解为有类型的管道。声明 个简单的通道语句是 chan
data Type ,但是简单声明 个通道变量没有任何意义, a并没有初始化,其值是 nil Go 语言
提供一个内置函数 make 来创建通道。例如:
创建 个元缓冲的通道,通道存放元素的类型为 data type
make(chan datatype )
创建一个有 个缓冲的通道,通道存放元素的类型为 data type
make(chan datatype, 10)
操作不同的chan会引发三种行为
panic
*向已经关闭的通道写入数据会导致panic
*重复关闭通道会导致panic
阻塞
*向未初始化的通道写入数据或读取数据会导致当前goroutine的永久堵塞
*向缓冲区已满的通道写入数据会导致阻塞
*通道中没有数据,读取该通道会导致 goroutine 阻塞。
非阻塞
*读取己经关闭的通道不会引发阻塞,而是立即返回通道元素类型的零值,可以使用comrna , ok 语法判断通道是否己经关闭。
*向有缓冲且没有满的通道读/写不会引发阻塞
6.3 WaitGroup
WaitGroup 用来等待多个 goroutine 完成, main goroutine 调用 Add 设置需要等待 goroutine
的数目,每一个 goroutine 结束时调用 Done(), Wait()被 main 用来等待所有的 goroutine 完成
var wg sync . WaitGroup
func main() {
wg.Add(1)
go worker()
wg.Wait()
println("结束了")
}
func worker(){
fmt.Println("Working......")
for i:=0;i<100;i++{
print(".")
}
defer wg.Done()
}
6.4 select
Go 语言借用多路复用的概念,提供了select关键字,用于多路监昕多个通道。当监听的通道没有状态是可读或可写的,select是阻塞的;
只要监听的通道中有 一个状态是可读或可写的,则 select 就不会阻塞,而是进入处理就
绪通道的分支流程。如果监听的通道有多个可读或可写的状态, select随机选取一个处理。
a := make(chan int,1)
for i:=0;i<10;i++{
select {
case a <-i:
case x := <-a:
println(x)
}
}
6.5 扇入(Fan in)和扇出(Fan out)
扇入是指将多路通道聚合到一条通道中
处理 Go 语言最简单的扇入就是使用 select 聚合多条通道服务;所谓的扇出是指将一条通道发
散到多条通道中处理,在 Go 语言里面具体实现就是使用 关键字启动多个 goroutine 并发处理。
6.6 通知退出机制
读取己经关闭的通道不会引起阻塞,也不会导致panic,而是立即返回该通道存储类型的零值
关闭select监听的某个通道能使select立即感知这种通知,然后进行相应的处理。
func main() {
done := make(chan struct{})
ch := GenerateIntA(done)
fmt.Println(<-ch)
fmt.Println(<-ch)
close(done)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
func GenerateIntA(done chan struct{}) chan int{
ch := make(chan int)
go func(){
Lable:
for {
select {
case ch <- rand.Int():
case <-done:
break Lable
}
}
close(ch)
}()
return ch
}
6.7 管道
通道可以分为两个方向,一个是读,另一个是写,假如一个函数的输入参数和输出参数都
是相 同的 chan 类型, 该函数可以调用自己,最终形成一个调用链。当然多个具有相同参数类
型的函数也能组成一个调用链,这很像 UNIX 系统的管道,是一个有类型的管道。
6.8 future模式
编程中经常遇到在一个流程中需要调用多个子调用的情况,这些子调用相互之间没有依赖,
如果串行地调用,则耗时会很长,此时可以使用go并发编程中的future模式
future模式的基本原理是
*使用chan作为函数参数
*启动goroutine调用函数
*通过chan传入参数
*通过chan异步获取结果
6.9 context标准库
Go 中的 goroutine 之间没有父与子的关系,也就没有所谓子进程退出后的通知机制,多个
goroutine 都是平行地被调度,多个 goroutine 如何协作工作涉及通信、同步、通知和退出四个方面。
通信: chan 通道当然是 goroutine 之间通信的基础, 注意这里的通信主要是指程序的数据通信
同步:不带缓冲的 chan 提供了一个天然的同步等待机制:当然 sync WaitGroup 也为
go routine 协同工作提供一 种同步等待机制
通知:这个通知和上面通信的数据不一样,通知通常不是业务数据,而是管理、控制流数
据。要处理这个也好办,在输入端绑定两个 chan 个用于业务流数据,另一个用于异常通知
数据,然后通过 select 收敛进行处理。这个方案可以解决简单的问题,但不是一个通用的解决方案。
退出 goroutine 之间没有父子关系,如何通知 goroutine 退出?可以通过 个单独的通
道,借助通道和 select 的广播机制( close channel to broadcast )实现退出
6.10 context设计的目的
context 库的设计目的就是跟踪 goroutin 调用树,并在这些 gouroutine 调用树中传递通知和元数据
*退出通知机制–通知可以传递给整个 goroutine 调用树上的每一个 goroutine
*传递数据–数据可以传递给整个goroutine调用数上的每一个goroutine
6.11 context的工作机制
第一个创建 Context goroutine
被称为 root 节点。 root 节点负责创建一个实现context接口的具体对象 并将该对象作为参数
递到其新拉起 goroutin ,下游的 goroutine 继续封装该对象,再传递 到更下游的goroutine
Context 对象在传递的过程中最终形成一个树状的数据结构,这样通过位于 root 节点(树的根节
点〉 Context 对象就能遍历整个 Context 对象树 ,通知和消息就可以通过 root 节点传递出去实
现了上游 goroutine 对下游 goroutin 的消息传递。
7.1 反射的基本概念
Go的反射基础是接口和类型系统。Go的反射巧妙地借助了实例到接口的转换所使用的数据结构,首先将实例传递给内部的空接口,实际上是将一个实例类型
转换为接口可以表述的数据结构eface,反射基于这个转换后的数据结构来访问和操作实例的值和类型。在接口章节我们知道实例传递给interface{}类型。
编译器会进行一个内部转换,自动创建相关类型数据结构。
7.2 TypeOf
Go的refelct包通过refelct.TypeOf()返回一个Type类型的接口,使用者通过接口来获取对象的类型信息。
TypeOf的通用方法
Name() 返回包含包名的类型名字,未命名返回空
Kind() 返回底层的基础类型
Implements(u Type)bool 确定当前类型是否实现了u接口类型
AssignableTo(u Type)bool 判断当前类型的实例能否赋值给type为u的类型变量
ConvertibleTo(u Type)bool 判断当前类型的实例是否能强制转换为u类型变量
Comparable()bool 判断当前类型是否支持比较(等于或者不等于)
NumMethod()int 返回一个类型方法的个数
MethodByName(String)(Method,bool) 通过名称获取方法method
PkgPath()string 返回类型的包路径,如果类型是预声明类型或未命名类型,则返回空字符串
Size()uintptr 返回存放该实例需要多大的字节空间
不同类型专有的方法
Elem()Type 返回类型元素的类型,只适用于Array、Chan、Map、Ptr、Slice类型
Bits()int 返回数值类型内存占用的位数
struct类型专用方法
NumField()int 返回字段数目
Field(i int) StructField 通过索引获取Struct字段
FieldByIndex(index []int) StructField
FieldByName(name string) (StructField,bool) 通过名字获取sturct字段
func类型专用方法
IsVariadic()bool 函数是否是不定参数函数
NumIn() int 输入参数个数
NumOut() int 返回值个数
In(i int)Type 返回第i个输入参数类型
Out(i int) Type 返回第i个返回值类型
Key() Type 返回map Key的类型
t := reflect.TypeOf(i)
v := reflect.ValueOf(&i)
fmt.Println("Type",t)
fmt.Println("Value",v)
for i:=0;i
7.3 基础类型
Type接口有一个Kind()方法,返回一个整数枚举值,不同的值代表不同的类型。这里的类型是一个抽象的概念,我们暂且称之为“基础类型”,比如
所有的几个都归为一种基础类型struct,所有的函数都归为一种基础类型func
type Kind uint
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
底层类型和基础类型的区别在于,基础类型是抽象的类型划分,底层类型是针对每一个具
体的类型来定义的,比如不同的 struct 类型在基础类型上都划归为 sturct 类型,但不同的 struct
底层类型是不一样的。
7.4 从实例到value 直接使用ValueOf()函数
从实例到Type 直接使用TypeOf()函数
从Type到Value Type里面只有类型信息,所以直接从一个Type接口变量里面是无法获取实例的Value的,但可以通过该Type构建一个新实例的Value
*New返回的是一个Value,该Value的type为PrtTo(typ),即Value的Type是指定typ的指针类型
func New(typ Type) Value
*Zero 返回的是一个 typ 类型的零值,注意返回的 Value 不能寻址 值不可改变
func Zero(typ Type) Value
从Value到Type 从反射对象 Value到 Type 可以直接调用 Value 的方法,因为 Value 内部存放着到 Type 类型的指针。
*function (v Value) Type() Type
从Valeu 到实例 Value 本身就包含类型和值信息, reflect 提供了丰富的方法来实现从飞Value 到实例的转换。
*该方法最通用,用来将 Value 转换为空接口,该空接口内部存放具体类型实例,可以使用接口类型查询去还原为具体的类型
func (v Value )Interface () (Interface {})
*Value 自身也提供丰富的方法,直接将 Value 转换为简单类型实例,如果类型不匹配,则直接引起 panic
func (v Value) Bool () bool
func (v Value) Float() float64
func (v Value) Int() int64
func (v Value) Uint() uint64
7.5 Type指针和值得相互转换
指针类型Type到值类型Type
//t 必须是 Array Chan Map Ptr, Slice ,否则会引起 panic
//Elem 返回的是其内部元素的 Type
t .Elem() Type
值类型Type到指针类型Type
//PtrTo 返回的是指向 t的指针型 Type
func PtrTo(t Type) Type
7.6 Value 的可修改行
通过 Can Set 判断是否能修改
func (v Value ) CanSet( ) bool
通过 Set 进行修改
func (v Value ) Set(x Value )
//下面是代码示例
type Student struct {
Name string "学生姓名"
Age int `a:"1111"b:"3333"`
}
func main() {
s:= Student{
Name: "张三",
Age: 20,
}
typeToValue(&s)
}
func typeToValue(i interface{}){
v := reflect.ValueOf(i)
fmt.Println(v.CanSet(),v.Elem().FieldByName("Name").CanSet())
name := "nihao"
vc := reflect.ValueOf(name)
v.Elem().FieldByName("Name").Set(vc)
fmt.Println(v.Elem().FieldByName("Name"))
}