前言
go语言诞生条件
- 更快的开发速度
- 优越的多核工作
- 更好的网络编程环境
- 享受开发过程
go语言追求短小精悍
第一章 Go语言的起源发展与普及
起源与发展
- 起源于07年,09年正式发布,Robert Griesemer, Rob Pike, Ken Thompson
- 2019/11/10 完全开源Linux和MacOSX
- 2010/1/8 当选2009年年度语言
- 2010/5 谷歌开始使用
- 2011/5/5 Google App Engine支持Go语言
- 几个网址
- 官网 golang.org
- github.com/golang/go
*
语言的主要特性和发展的环境和影响因素
- go语言追求快速编译(Java),高效执行(C++),易于开发(动态语言)
- go语言是一门类型安全和内容安全的编程语言
- go语言存在指针,但是不允许指针运算
- 对于并发编程,网络编程支持极好-goroutine
- go语言依赖管理采用分包进行
- go语言和Java一样有GC机制
- go install部署第三方包
指导设计原则
- go语言关键字25个
- go语言编码规范-https://golang.org/ref/spec
语言特性
- go语言在本质上支持并发编程
- go语言没有类的概念,用interface来实现多态
- go语言是类型安全的语言,使用静态类型
- go语言是强类型语言
- go语言支持交叉编译
- go语言代码和字符串的编码方式都是utf-8,支持国际化
语言用途
- go语言用于web服务器,存储集群,分布式场景,游戏服务
- go语言的目标是时限CEP, complex Event Process
- go语言不适合开发实时性要求高的软件
特性缺失
- 不支持函数重载和操作符重载
- 不支持隐式转换
- 不支持类和类型的继承
- 不支持变体类型
- 不支持动态加载代码
- 不支持动态链接库
- 不支持泛型
- 通过recover和panic来替代异常机制
- 不支持静态变量
小结
- 简化问题,易于学习
- 内存管理,语法简洁,易于使用
- 快速编译,高效开发
- 高效执行
- 并发支持,轻松驾驭
- 静态类型
- 标准类库,规范统一
- 易于部署
第二章 安装与运行环境
平台与架构
- 编译器支持的平台:
- Linux
- FreeBSD
- Mac OS X
- 编译器
- gc
- gccgo
Go环境变量
- $GOROOT:go的安装位置
- $GOARCH:机器的处理器架构
- $GOOS:操作系统
- $GOBIN:编译器和链接器的安装位置
- $GOPATH:包含多个Go语言远吗文件,包文件和可执行文件,src,pkg,bin三个目录
- $GOARM:ARM专用
- $GOMAXPROCS:设置可用的处理器核数
Linux安装go
Linux下通过
$HOME/.bashrc
来自定义环境- 首先配置GOROOT:
export GOROOT=$HOME/go
- 配置path:
export PATH=$PATH:$GOROOT/bin
- 配置工作路径:
export GOPATH=$HOME/Applications/Go
source ~/.bashrc
生效go env
查看
- 首先配置GOROOT:
go源码获取
12345wget https://storage.googleapis.com/golang/go<VERSION>.src.tar.gztar -zxvf go<VERSION>.src.tar.gzsudo mv go $GOROOTcd $GOROOT/src./all.bashgo version查看版本信息
安装目录
.
├── CONTRIBUTING.md
├── CONTRIBUTORS
├── PATENTS
├── VERSION
├── api
├── bin // 可执行文件
├── doc // 文档
├── favicon.ico
├── lib // 文档模板
├── misc // 编辑器相关内容
├── pkg
├── robots.txt
├── src // 完整源码
└── test
go runtime
- runtime相当于Java的虚拟机,负责内存分配,垃圾回收,栈处理,goroutine,channel,slice,map,reflect等
- go的gc采用标记-清除回收器
第三章 Go开发环境的基本要求
调试器
- 打印语句,print,println,fmt.Println,fmt.Printf
- %+v, %#v,%T打印实例的不同信息
- 使用panic获取堆栈信息
- defer关键字来跟踪代码执行
- go build 编译
- go install 编译并安装
格式化代码 - gofmt
gofmt -r '(a) -> a' –w *.go
gofmt -r 'a[n:len(a)] -> a[n:]' –w *.go
- 参考-http://golang.org/cmd/gofmt/
代码文档 - go doc
- go doc package 获取包的文档注释,例如:go doc fmt 会显示使用 godoc 生成的 fmt 包的文档注释。
- go doc package/subpackage 获取子包的文档注释,例如:go doc container/list。
- go doc package function 获取某个函数在某个包中的文档注释,例如:go doc fmt Printf 会显示有关 fmt.Printf() 的使用说明
第四章 语言的核心结构和技术
文件名、关键字与标识符
- 文件名以
.go
结尾 _
空白标识符- go的25个关键字
关键字 | ||||
---|---|---|---|---|
break | default | func | interface | selcet |
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
- 36个预定义标识符,函数
预定义标识符 | ||||||||
---|---|---|---|---|---|---|---|---|
append | bool | byte | cap | close | complex | complex64 | complex128 | unit16 |
copy | false | float32 | float64 | image | int | int8 | int16 | uint32 |
int32 | int64 | iota | len | make | new | nil | panic | unit64 |
println | real | recover | string | true | uint | uint8 | uintptr |
- go程序一般由关键字,常量,变量,运算符,类型和函数组成
- 分隔符
(), [], {}
- 标点
.、,、;、:、...
等,但是不用;
结尾
Go程序的基本结构要素
包-package
- 包是结构化的一种方式,每个程序都有包的概念组成,一个包可以由多个.go文件组成
- 在源文件的第一行指明这个文件属于哪个包
- 一个包的源文件必须一起编译,包是编译的单元
- 对一个包进行更改或者重新编译,所有引用了这个包的客户端程序都必须全部重新编译
- go中包模型采用显示依赖关系机制来打到快速变异的目的
包引入
1234567import "fmt"import "os"import ("fmt""os")包名不是以
.
或者/
开头,会在全局文件进行查找,以./
开头会在相对目录中查找,以/
开头会在绝对路径中查找- 可见性规则:标识符的大小写区别
- 包可以用别名进行区分
函数
定义格式
123func functionName(parameter_list) (return_value_list) {…}go语言是以
;
作为语句的结尾,只是由编译器自动完成- 内部包命名遵循驼峰命名法,外部调用的遵循Pascal命名法
- 程序正常退出代码为0,异常退出代码为1
注释
//
- 单行注释/* */
- 多行注释- 每个包都应该有注释,package关键字之前的注释默认为对包的说明
- 全局作用于的类型、变量、常量、函数、对象都应该有一个合理的注释
类型
- 类型定义了某个变量的值的集合和可对其进行操作的集合
- 基础类型:int、float、bool、string、
- 复合类型:struct、array、slice、map、channel
- 描述行为的:interface
- 函数可以作为类型返回
go程序的一般结构
- 完成包的导入
- 常量,变量,类型的定义或声明
- 如果存在init函数,对改函数进行定义,每个含有该函数的文件都会先执行init函数
- 如果包为main包,则定义main函数
定义其他的函数,首先是类型的方法,函数名字可以按照字母顺序来排列
go语言执行顺序:
- 按顺序导入所有被main包引入的其他包
- 如果包中引入了其他包,递归执行包的引入
- 最新引入的包首先开始常量和变量的初始化
- init
- main
类型抓换
- 显示转换
a := b(a)
常量
- const关键字定义
- 类型:bool,int,float,complex,string
const Pi = 3.1415926
- 常量的值必须在编译时就能够确定
- 数字型的常量是没有大小和符号的,不会溢出
- iota特性
变量
实例
var a string
( 1234a intb boolstr string)变量被声明之后,系统会自动赋给该变量零值0,0.0, false,’’, nil
- 驼峰命名法
- 全局变量和局部变量
变量的声明和赋值可以一起进行
1234567891011var a = 15var b = falsevar str = "Go says hello to the world!"var (a = 15b = falsestr = "Go says hello to the world!"numShips = 50city string)通过runtime获取运行环境
1234567891011121314package mainimport ("fmt""runtime""os")func main() {var goos string = runtime.GOOSfmt.Printf("The operating system is: %s\n", goos)path := os.Getenv("PATH")fmt.Printf("Path is %s\n", path)}
值类型和引用类型
- 指针 &i
- 一个引用类型的变量r1存储的是r1的值所在的内存地址,或内存地址中第一个字所在的位置
- go语言中指针,slice,maps,channel都是引用类型
打印
- fmt.Printf
- %s-字符串
- %v-类型默认输出格式
- %T-类型
:=
赋值操作符
a := 50
,类型会自动判断- 函数体内的首选方案,但是不能用于全局变量
a, b, c := 5, 5.5, "5"
- a, b = b, a 交换值
_
抛弃不要的值
init函数
- 不能被人为调用
- 执行优先级高于main
- 每个源文件只包含一个init函数
- init中可以定义变量,检查数据,调用后台goroutine
基本类型和运算符
bool类型
var b bool = true
==
,!=
- go语言只有类型相同的两个值才可以进行比较
!、&&、||
,其中&&
和||
- 布尔值的命名推荐以is开头,如isSorted,isFinished等
数字类型
- int,uint - 4个字节或者8个字节
uintptr - 存放一个指针的长度
整数:
- int8(-128 -> 127)
- int16(-32768 -> 32767)
- int32(-2,147,483,648 -> 2,147,483,647)
- int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
- 无符号整数:
- uint8(0 -> 255)
- uint16(0 -> 65,535)
- uint32(0 -> 4,294,967,295)
- uint64(0 -> 18,446,744,073,709,551,615)
浮点型(IEEE-754 标准):
- float32(+- 1e-45 -> +- 3.4 * 1e38)
- float64(+- 5 1e-324 -> 107 1e308
前缀0表示8进制数
- 前缀0x表示16进制数
- e表示10的次方
- go语言中变量之间不可以混用,常量可以,变量之间需要显示转换
格式化占位符
- %d - 整数
- %x,%X - 16进制数字
- %g - 浮点数
- %f - 输出浮点数
- %e - 输出科学计数法数
- %n.mg - 数字n精确到m位
位运算 - 用于整数类型的变量,并且要求变量等长
- 按位与
&
- 按位或
|
- 按位异或
^
- 位清除
&^
将指定位置上的值设置为0 - 按位补足
^
- 位左移
<<
- 位右移
>>
- 按位与
逻辑运算符
- ==
- !=
- <
- <=
- >
=
算数运算符
- +
- -
- *
- /
- %
- -=
- +=
- *=
- /=
- ++,–等操作可以用于语句,但是不可以用于表达式 a[i] = b[i++]是不允许的
随机数 - rand包
运算符优先级
|
|
类型别名
type TZ int
TZ是int的别名- 别名不会拥有原类型的方法
字符串
- utf-8编码中每个字符占据字节数不确定,go中字符可能占据1-4个字节
- go语言支持两种字面值
- 解释字符串
\n
\r
\t
\u
\\
- 非解释字符串-普通字符串
- 解释字符串
- 获取字符串的字节内容:str[i]
- 拼接符
+
- 效率: bytes.Buffer > strings.Join > +
strings和strconv包
前缀和后缀
HasPrefix - 前缀
1234// HasPrefix tests whether the string s begins with prefix.func HasPrefix(s, prefix string) bool {return len(s) >= len(prefix) && s[0:len(prefix)] == prefix}HasSuffix - 后缀
|
|
字符串包含关系
|
|
判断子字符串或字符在父字符串中出现的位置
|
|
字符串替换
|
|
重复字符串
|
|
修改字符串大小写
- strings.ToLower(s) string
- strings.ToUpper(s) string
修剪字符串
- strings.TrimSpcae(s)-剔除字符串开头和结尾的空白
- strings.Trim(s, “cut”)-剔除字符串开头和结尾的指定字符
- TrimLeft,TrimRight
分割字符串
- strings.Fields(s) - 根据空白字符分割
- strigns.Split(s, sep) - 根据sep来分割字符串
拼接slice到字符串
- strings.Join(sl []string, sep string) string - 将slice使用分隔符sep来拼接成一个字符串
从字符串中读取内容
- strings.NewReader(str)
- Read()
- ReadByte()
- ReadRune()
字符串和其他类型的转换
- strconv.Itoa(i int) string - int转换成字符串
- strconv.FormatFloat() string
- strconv.Atoi(s string) (int, error) 字符串到int
- strconv.ParseFloat(s string, bitSize int) (f float, err error)
时间和日期
- time包
- time.Now
- time.Day()
- time.Minute()
- time.Duration()
指针
- 取地址符
&
- 一个指针变量可以指向任何一个值的内存地址
- 通过指针来实现对原来变量值的修改
- 指针可以用来传递变量的引用,而不是变量的值的拷贝,节省开销
第五章 控制结构
5.1 if-else结构
1234567
if condition1 { // do something} else if condition2 { // do something else} else { // catch-all or default}
- 判断一个字符串是否为空
|
|
- 判断go运行的操作系统
|
|
测试多返回值函数的错误
- 程序应该在最接近的位置检查所有相关的错误
|
|
switch结构
- 使用fallthrough表示执行完当前分支之后继续执行后续分支
- 分支之间没有break关键字
|
|
- 无初始值
|
|
- 包含一个初始化语句
|
|
for结构
- 基于计数器的迭代
|
|
- 基于条件判断的迭代
|
|
- 无线循环 - 服务器
|
|
- for-range结构
|
|
break与continue
- break关键字可以退出for循环
- break只会退出一层
- continue会忽略剩余的循环体跳入下一次循环
标签与goto
- 标签作用于外部循环
|
|
- goto和标签可以来模仿循环
|
|
第六章 函数
6.1 简介
- 函数是基本的代码块
- main写在文件的前面
- 函数主要是为了代码重用
- 最后一行或者return退出函数
- go中包含三种函数
- 普通带有名字的函数
- 匿名函数或者lambda函数
- 方法
- 出了main,init函数之外,其他所有函数都可以有参数和返回值
- 参数,返回值,及其类型称为函数签名-和其他语言一致
- 函数可以将其他函数作为它的参数,类型相符合就可以
- go语言不允许函数重载
- 函数是一等公民
- 函数可以相互比较,引用相同就相等
- go语言不支持泛型
6.2 函数参数与返回值
- 函数可以接收参数供自己使用,也可以返回零个或者多个值
- go语言默认值传递
- go语言通过指针来实现引用传递,实际上指针也是变量类型,有自己的地址和值,只是指针的值指向一个变量的地址,按引用传递可以理解为按值传递,传递的值就是指针的值。
- slice,map,interface,channel默认引用传递
|
|
命名的返回值
|
|
- 尽量使用命名返回值:会使得代码清晰,更简短,可读性更好。
空白符
- 空白符用来丢弃一些不需要的值
|
|
改变外部变量
- 指针传递不仅可以节省内存,而且赋予函数改变外部变量的能力
|
|
6.3 传递变长参数
|
|
6.4 defer和追踪
- defer语句在函数结束之前才开始执行,适用于资源的释放等操作,类似于finally
- defer的语句会按顺序接收参数,只是执行在最后
|
|
- 多个defer语句,类似栈,先出现后执行
- 关闭文件流,解锁,打印最终报告,关闭数据库连接
- defer可以实现代码追踪,在进入和离开函数时打印日志
|
|
6.5 内置函数
名称 | 说明 |
---|---|
close | 用于管道通信 |
len | 返回某个类型的长度或数量,如字符串,数组,切片,map和管道 |
cap | 返回某个类型的最大容量,如切片,map |
new | 用于值类型和用户定义的类型的创建 |
make | 用于内置类型的创建 |
copy,append | 用于复制和连接切片 |
panic,recover | 错误处理机制 |
print, println | 底层打印函数 |
complex, real imag | 复数操作 |
6.6 递归函数
- 自身调用
|
|
- 递归的一个问题在于栈溢出
- go语言中可以使用相互调用的递归函数
|
|
6.7 将函数作为参数
- go语言中函数可以作为其他函数的参数进行传递,然后在其他函数内调用执行,称为回调函数
|
|
|
|
6.8 闭包
- 不给函数起名,而是将函数指针赋值给某个变量,通过这个变量来实现函数的调用
|
|
- 匿名函数可以被赋值给变量作为值来使用
- defer关键字和匿名函数配合使用来改变函数的命名返回值
- 匿名函数被称为闭包,被允许调用定义在其他环境的变量。
- 一个函数闭包继承了函数所声明时的作用域
闭包应用-将函数作为返回值
|
|
|
|
- x中的值是可以被保存的
- 在闭包中使用到的变量可以在函数体内声明,也可以在函数体外声明
使用闭包调试
- runtime中的函数Caller提供了文件和行数的信息,可以用闭包来实现调试定位
|
|
计算函数执行时间
|
|
通过内存缓存来提升性能
- 内存缓存来避免重复计算,如斐波那契,空间换时间
|
|
第七章 数组和切片
声明和初始化
- 数组长度是数组类型的一种
- 数组长度最大为2Gb
- 数组声明
var identifier [len]type
- 数组声明之后所有元素会默认为零值
- 数组遍历用for循环,range更好
- 数组可以用new来创建,创建之后返回的是数组的地址,而不是数组的值
数组常量
var arr = [5]{18, 10, 12, 19, 23}
var arr = [...]{18, 10, 12, 19, 23}
var arr = [5]string{3: "aa", 4: "bb"}
- {“”. “”, “”, aa, bb}
多维数组
|
|
将数组传递给函数
- 为了降低内存的消耗,传递数组指针或者切片
|
|
7.2 切片
- 切片是对数组一个连续片段的引用
- 切片可以索引,可以len
- 切片是一个长度可变的数组
- cap函数来计算切片的最大容量 0 <= len(s) <= cap(s)
- 切片是引用,不耗内存,效率更高
- 声明格式
var identifier []type
- 切片在未初始化之前是nil,长度为0
- 切片的初始化格式是:
var slice1 []type = arr1[start:end]
前闭后开 - 切片可以扩展到其上限
- 切片初始化:
var x = []int{2, 3, 4, 7, 11}
- 切片只能向后移
- 不要用指针指向切片,因为切片本身就是一个指针
将切片传递给函数
- 与传递数组相比,节省内存开销,同时函数具备了改变外部变量的能力
|
|
make创建切片
var slice []type = make(type[], len)
,在创建切片的同时创建好相关数组- make需要两个参数,元素类型和元素个数
make和new的区别
- new为每个新的类型T分配一片内存,初始化零值并返回类型为*T的内存地址,适用于数组和结构体
- make返回一个类型为T的初始值,适合创建slice,map和channel
- new分配内存,make函数初始化
bytes包
|
|
- 字符串串联效率更高
7.3 for-range结构
|
|
- idx是数组或者切片的索引
- value是索引对应的值
_忽略不需要的idx或者value
多维切片下的for-range
|
|
7.4 切片重组
slice1 := make([]type, start_length, capacity)
- 切片可以反复扩展直到占据整个数组
|
|
7.5 切片的复制与追加
- 先创建再拷贝
|
|
- append方法追加切片并返回新的切片
7.6 字符串、数组和切片的应用
字符串生成切片 -
c := []byte(s)
获取字符串的某一部分 - substr := str[start:end]
字符串和切片的内存结构,字符串是一个双字结构,地址指针和长度值
go语言中字符串是不可变的,但是可以通过切片操作来改变字符串
append函数常见操作
- 切片追加 -
a = append(a, b...)
- 切片复制 -
b = make([]Tm len(a)) copy(b, a)
- 删除索引i的元素 -
a = append(a[:i], a [i+1:])
- 切除切片 a 中从索引 i 至 j 位置的元素 -
a = append(a[:i], a[j:]...)
- 为切片 a 扩展 j 个元素长度 -
a = append(a, make([]T, j)...)
- 在索引 i 的位置插入元素 x -
a = append(a[:i], append([]T{x}, a[i:]...)...)
- 在索引 i 的位置插入长度为 j 的新切片 -
a = append(a[:i], append(make([]T, j), a[i:]...)...)
- 在索引 i 的位置插入切片 b 的所有元素 -
a = append(a[:i], append(b, a[i:]...)...)
- 取出位于切片 a 最末尾的元素 x -
x, a = a[len(a)-1], a[:len(a)-1]
- 将元素 x 追加到切片 a -
a = append(a, x)
- 切片追加 -
底层数组没有切片引用的时候会被回收
第八章 Map
8.1 声明和初始化
声明
var map1 map[keyType]valueType
var map1 map[string]int
- map可以动态增长
- 未初始化的map是nil
- key可以是string,int,float
- value可以是任意类型的
|
|
- map是引用类型,内存用make方法来分配,不要用new
map的初始化 -
map := make(map[string]float32)
map容量 - cap会自动+1扩张
测试键值对是否存在及删除元素
|
|
delete(map1, key1)
for key, value := range map1 {}
map类型的切片
|
|
8.5 map排序
- 将key或者value拷贝到切片
- 对切片排序
- 根据切片打印map
第九章 包 package
9.1 标准库概述
- unsafe:C/C++中调用的类型不安全的包
- syscall-os-os/exec:底层接口包
- archive/tar,zip-compress:文件压缩解压缩
- fmt:格式化输入输出
- io:基本输入输出
- bufio:缓冲输入输出
- path/filepath:操作在当前系统中的目标文件名路径
- flag:对命令行参数的操作
- strings:字符串操作
- strconv:字符串类型转换
- unicode:Unicode类型字符串功能
- regexp:正则
- bytes:字符型分片操作
- index/suffixarray:子字符串快速查询
- math:基本数学函数
- math/cmath:对复数的操作
- math/rand:随机数
- sort:排序
- math/big:大数
- container-list-ring-heap:集合操作
- list:双链表
- ring:环形链表
- time:日期和时间的基本操作
- log:记录程序运行时产生的日志
- encoding/json:读取并解码和写入并编码json数据
- encoding/xml:xml解析器
- text/template:数据驱动模板
- net:网络的基本操作
- http:可扩展的http服务器和基础客户端
- html:html5解析器
- runtime:go程序运行时的交互操作
- reflect:反射
9.2 regexp包
- 简单模式使用Match方法
ok, _ := regexp.Match(pattern, []byte(searchIn))
ok, _ := regexp.MatchString(pattern, searchIn)
- 通过Compile方法返回Regrep对象进行匹配
|
|
9.3 锁和sync包
- sync.Mutex是一个互斥锁,保证临界区同一个时间只有一个线程进入
|
|
- RWMutex锁:可以通过RLock方法来允许同一时间多个线程读取变量,一个线程写变量
精密计算和big包
- 整数的高精度计算提供了big包
|
|
自定义包和可见性
- import导入包,路径名或者URL地址
- 导入外部安装包:
go install codesite/..
,安装在GOROOT/src/目录下 - 程序的初始化在于导入包,初始化main包然后调用main函数
- init函数不能被调用
godoc
- 命令行执行
godoc -http=:6060 -goroot="."
其中.
是当前路径,会在127.0.0.1:6060中看到本地go的注释
go install
- go install是go中自动包安装工具
go installl uc
会将uc.a包安装到pkg目录下- 也可以用gomake来安装
- go test测试
git打包和安装
- 安装到GitHub
|
|
- 从GitHub安装 -
go get github.com/xxx/xx
外部库的使用
- 下载并安装外部库-go install
- GOPATH是否有,包会被下载到
$GOPATH/PKG/"machine_arch"/
第十章 结构和方法
- go通过类型别名和结构体的形式支持用户自定义类型
- 结构体是复合类型,new来创建
- 数据称为字段fields
10.1 结构体定义
- 结构体的定义方法:
|
|
- 数组可以看做使用下标的结构体
- new函数给结构体分配内存,返回已分配内存的指针
- var t T,此时t是T的一个实例/对象
- 结构体打印和%v效果一样
- 结构体变量和指针都可以通过
.
来选择字段值 p := Point{}
是实例,p := &Point{}是指针
|
|
go的内存布局,结构体和其字段在内存中是连续存在的,性能更好,空间换时间
递归结构体,链表,树的实现
结构体可以和他别名进行类型转换
10.2 使用工厂方法创建结构体实例
- 工厂方法以new或者New开头
|
|
- 推荐使用工厂方法来创建结构体,结构体字段就可以定义为私有的,OO思想
10.3 使用自定义包中的结构体
直接import就OK了
10.4 带标签的结构体
- 结构体中的字段出了名字和类型之外,还有一个可选的标签
- 标签是附属于字段的字符串,文档注释等操作
- reflect可以取到tag
|
|
10.5 匿名字段和内嵌结构体
- 字段没有名字,只有类型
- 匿名字段可以是结构体
|
|
实际上一个结构体对每一个类型最多有一个匿名字段,通过struct.type获取
内嵌结构体的概念类似于继承
内外的结构体命名冲突,外层的名字会覆盖内层的名字,同级别冲突报错
10.6 方法
go方法是作用在接收者上的一个函数,接收者是某个类型的变量
接收者可以是任何对象除了interface
go中类型的代码和绑定在它上面的方法的代码可以不放置在一起,只要是同一个包就可以
方法集
方法不允许重载
定义格式
func (_ receiver_type) methodName(parameter_list) (return_value_list) { ... }
接收者可以理解为Java中的this
函数和方法的区别
- 函数将变量作为参数,方法在变量上被调用
- 接收者需要显示命名
- receiver_type必须和方法同包
- 类型和方法之间的关联由接收者来建立
- 方法没有和结构体混合,表明数据和方法是独立的
指针或值作为接收者
一般来说接收者都是类型的指针,效率更高
指针方法和值方法都可以在指针或非指针上被调用
未导出的结构体的对象通过setter\getter来修改和获取
内嵌类型的方法和继承
- 匿名类型被内嵌时,其可见方法也同样被内嵌,等价于外部结构继承了这些方法
|
|
- 方法可以被外层结构体覆盖
如何在类型中嵌入功能
两种方法
- 聚合:包含一个所需要功能的具体字段
- 内嵌:内嵌需要的功能类型
聚合
|
|
- 内嵌
|
|
- 内嵌的类型不需要指针
多重继承
- 多重继承指的是一个类型获得多个父类型的能力
|
|
其他语言和GO的类型和方法的比较
- go中,类型就是类,代码复用和多态更好
- go中,代码通过组合和委托实现,多态通过接口的使用来实现
10.7 类型的String方法和格式化描述符
- 定义了string()方法的类型,会被fmt.Println()中生成默认的输出,等同于%v产生的输出
- 注意不要在String方法里面调用涉及String()方法的方法,放置造成无限递归
10.8 垃圾回收和SetFinalizer
- go语言有自己的垃圾回收器
- 通过runtime包访问GC进程,
runtime.GC()
- SetFinalizer可以在对象被移除前进行一些操作
runtime.SetFinalizer(obj, func(obj *typeObj))
第11章 接口与反射
- 接口定义了一组方法
- go中的接口都很简短,0-3个方法
- go语言中接口可以有值,为多字数据结构,值为nil
- 类型来实现接口找那个的方法
- 类型不需要显示声明它实现了哪个接口:接口被隐式的实现,多个类型可以实现同一个接口
- 实现某个接口的类型可以有其他的方法
- 一个类型可以实现多个接口
- 接口类型可以包含一个实例的引用,该实例的类型实现了此接口,接口是动态类型
|
|
接口的多态实例,父类引用持有子类变量。
|
|
- 标准库中的例子-Reader
|
|
11.2接口嵌套接口
|
|
11.3 类型断言:如何检测和转换接口变量的类型
- 一个接口类型的变量可以包含任何类型的值,类型断言来检测某个时刻该接口变量的值
v := varI.(T)
varI为接口变量
|
|
11.4 类型判断: type-switch
- 不允许fallthrought关键字
|
|
11.5 测试一个值是否实现了某个接口
|
|
- 接口是一种契约,实现类型必须满足它,描述了类型的行为,规定类型可以做什么。
- 使用接口使得代码更具有普适性
11.6 使用方法集与接口
- 作用于变量上的方法实际上是不区分变量是值还是指针的,但是如果变量是接口类型时,由于接口变量中村的具体指是不可以寻址的,会编译错误
- 在指针上调用方法时,必须有和方法定义时相同的接收者类型或者可以从具体类型P直接进行辨识的类型
- 指针方法可以通过指针调用
- 值方法可以通过值调用
- 接收者是值的方法可以通过指针调用,因为指针会首先被解引用
- 接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址
- 将一个值赋值给接口时,编译器会进行类型检查
小结
- 类型
*T
的可调用方法集合包含接受者为*T或T的所有方法集 - 类型T的可调用方法集包含接受者为T的所有方法
11.7 Sorter接口排序
- Sort函数接收一个Sorter接口类型的参数,Sorter接口定义了len,less,swap方法
|
|
|
|
11.8 读和写
- io包中提供了用于读和写的接口
io.Reader
和io.Writer
|
|
- 只要实现了读写接口,这个对象就是可读写的
- bufio里面提供了带缓冲的读写
11.9 空接口
定义
- 空接口不包含任何方法,对实现不做要求
type Any interface{}
- 类似Java中的Object类
- 空接口可以被赋予任何值
- 每个interface变量占据两个字长,一个用来存储包含的类型,一个用来存储包含的数据或者数据指针
|
|
11.9.2 构建通过类型或者包含不通类型变量的数组
|
|
- Vector里面可以存放任何类型的变量
- Vector中存储的所有元素都是Element类型,得到袁术类型需要用类型断言
11.9.3 复制数据切片到空接口切片
|
|
11.9.4 通用类型的节点数据结构
|
|
11.9.5 接口到接口
- 一个接口的值可以赋值给另一个接口,只要底层实现了必要的方法
- 运行时才会检查两个接口是否可以进行赋值,不可以会报错
11.10 反射包
- 反射是用程序检查其所拥有的结构,类型的一种能力
- 反射可以在运行时检查类型和变量,比如大小,方法并动态的调用这些方法
- 反射包中Type用来表示一个Go类型,Value为go提供了反射的接口
reflect.TypeOf reflect.ValueOf
检查对象的类型和值- 反射可以从接口值反射到对象,也可以从对象反射回接口
- 反射包中的常量
|
|
- Kind方法总是返回底层类型
11.10.2 通过反射修改值
|
|
|
|
- 结构体中只有被导出字段(大写的字段)才是可以设置的
|
|
11.11 Printf和反射
- Printf函数声明为
|
|
...
为空接口类型,Printf可以知道每个参数的类型
11.12 接口和动态类型
11.12.1 Go的动态类型
- go语言中数据和方法是松耦合的
- go中的接口任何提供了接口方法实现代码的类型都隐式的实现了该接口
- duck typing,能做什么比是什么更加重要
|
|
11.12.2 动态方法调用
- 当一个变量被赋值给一个接口类型的变量时,编译器会检查其是否实现了该接口的所有函数,空接口可以使用类型断言来判断
- go提供了动态语言的优点,却没有其他动态语言在运行时随时会报错的缺点
- Go 的接口提高了代码的分离度,改善了代码的复用性,使得代码开发过程中的设计模式更容易实现。用 Go 接口还能实现 依赖注入模式。
11.12.3 接口的提取
- 接口的提取是非常有用的设计模式,可以减少需要的类型和方法数量
- 一些拥有共同行为的对象可以抽象出接口
- 整个设计可以持续演进,不用废弃之前的决定
- 类型要实现某个接口,本身不用改变,在类型上实现新的方法就可以
11.12.4 显示的指明类型实现了某个接口
- 结构体中添加具有描述性名字的方法
|
|
11.12.5 空接口和函数重载
- go语言是不允许函数重载的
- go中通过将最后一个参数换成…interface{}来实现函数重载,允许传递任意数量任意类型的参数给函数
|
|
11.12.6 接口的继承
- 当一个类型嵌套另外一个类型的指针时,这个类型就可以使用另一个类型的所有方法
- 多态用的越多,代码就相对越少
- 类型可以通过继承多个接口来实现多重继承
- 添加新的接口是非常容易的,因为已有的类型不用变动,仅需要实现新的接口即可
11.13 go中面向对象总结
- go中没有类,而是松耦合的类型,方法对接口的实现
- 封装:go的可见层级为两个,包范围内可见和全局可见
- 继承:用组合实现,内嵌一个或者多个想要的行为的类型,可以多重嵌套
- 多态:用接口实现,某个类型的实例可以赋值给它所实现的任意接口类型的变量。类型和接口是松耦合的,多重继承可以通过实现多个接口来实现。
11.14 结构体、集合和高阶函数
|
|