go基础知识_持续更新
2022-06-08 22:09:55 2 举报
AI智能生成
涵盖 go 基础知识
作者其他创作
大纲/内容
配置环境
go mod
go mod 命令
go mod
go mod
init
初始化生成 go.mod文件
go mod 配置
GO111MODULE
GO111MODULE=on
go命令行会使用modules,而一点也不会去GOPATH目录下查找
GO111MODULE=auto
默认值,go命令行将会根据当前目录来决定是否启用module功能
当前目录在GOPATH/src之外且该目录包含go.mod文件
当前文件在包含go.mod文件的目录下面
GO111MODULE=off
go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH模式来查找
go mod 文件
go.mod
go.mod
go.sum 文件
记录 dependency tree
go.mod
相关配置命令
go list -m -u all
检查可以升级的package
go get -u need-upgrade-package
升级后会将新的依赖版本更新到go.mod *
go get -u
升级所有依赖
将会升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号)
go get -u=patch
升级到最新的修订版本
go get package@version
将会升级到指定的版本号version
replace
替换无法直接获取的package
modules 可以通过在 go.mod 文件中使用 replace 指令替换成github上对应的库
replace (
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
)
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
)
go list -m -u all
列出当前模块及其所有依赖项
go mod download
下载依赖
go mod vendor
导入依赖
module versioning 版本控制
https://www.jianshu.com/p/760c97ff644c
类库依赖
环境变量
GOPATH
GOROOT
go path
代码开发必须在go path src目录下
依赖手动管理
依赖包没有版本可言
govendor
解决了包依赖,一个配置文件就管理
依赖包全都下载到项目vendor下,每个项目都把有一份
go mod 解决方式
golang 1.11 新加的特性
模块是相关Go包的集合
modules是源代码交换和版本控制的单元
go命令直接支持使用modules,包括记录和解析对其他模块的依赖性
modules替换旧的基于GOPATH的方法来指定在给定构建中使用哪些源文件
配置
go env -w GOBIN=/Users/youdi/go/bin
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct // 使用七牛云的
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct // 使用七牛云的
包 package
限制条件
所有源文件的第一行非注释语句
不能包含空白字符
每个目录中只能定义一个package
包所在的目录中可能有一些文件名是以_test.go为后缀的Go源文件
以_test为后缀包名的测试外部扩展包都由go test命令独立编译,普通包和测试的外部扩展包是相互独立的
以_test为后缀包名的测试外部扩展包都由go test命令独立编译,普通包和测试的外部扩展包是相互独立的
一些依赖版本号的管理工具会在导入路径后追加版本号信息,例如“gopkg.in/yaml.v2”
包的名字并不包含版本号后缀,而是yaml
包路径
$GOROOT/src/
$GOPATH/pkg/mod/
当前路径
go.mod模块名声明中的相对路径
包的查找
包的导入规则
用圆括号组合导入包路径
import ("fmt"; "math")
import (
"fmt"
"math"
)
import (
"fmt"
"math"
)
可以定义别名
import (
"a/b/c"
c1 "x/y/c" // 将导入的包c定义别名为 c1
格式化 "fmt" // 将导入的包fmt定义别名为 格式化
m "math" // 将导入的包math定义别名为 m
)
"a/b/c"
c1 "x/y/c" // 将导入的包c定义别名为 c1
格式化 "fmt" // 将导入的包fmt定义别名为 格式化
m "math" // 将导入的包math定义别名为 m
)
想同时导入两个有着名字相同的包,例如math/rand包和crypto/rand包,
那么导入声明必须至少为一个同名包指定一个新的包名以避免冲突
那么导入声明必须至少为一个同名包指定一个新的包名以避免冲突
import (
"crypto/rand"
mrand "math/rand" // alternative name mrand avoids conflict
)
"crypto/rand"
mrand "math/rand" // alternative name mrand avoids conflict
)
引用包名是导入包路径的最后一个目录中定义的唯一包的名称
定义的包名与目录同名时,直接引用即可
定义的包名与目录同名时,直接引用即可
// 引用普通名称的导入包
c.hello()
// 引用定义别名的包
格式化.Println(m.Pi)
c.hello()
// 引用定义别名的包
格式化.Println(m.Pi)
定义的包名与所在目录名称不同时,导入包路径仍为目录所在路径,引用包名为定义的包名称
// 源文件路径: proj/my-util/util.go
// 定义包名: util
package util
// 定义包名: util
package util
// 导入util包路径
import "proj/my-util"
// 引用util包
util.doSomething()
import "proj/my-util"
// 引用util包
util.doSomething()
静态导入,在导入的包路径之前增加一个小数点 .
// 类似C中的include 或Java中的import static
import . "fmt"
// 然后像使用本包元素一样使用fmt包中可见的元素,不需要通过包名引用
Println("no need package name")
import . "fmt"
// 然后像使用本包元素一样使用fmt包中可见的元素,不需要通过包名引用
Println("no need package name")
导入包但不直接使用该包,在导入的包路径之前增加一个下划线 _
被导入的包中的init函数会在导入的时候执行
被导入的包中的init函数会在导入的时候执行
// 如果当前go源文件中未引用过log包,将会导致编译错误
import "log" // 错误
import . "log" // 静态导入未使用同样报错
// 在包名前面增加下划线表示导入包但是不直接使用它,被导入的包中的init函数会在导入的时候执行
import _ "github.com/go-sql-driver/mysql"
import "log" // 错误
import . "log" // 静态导入未使用同样报错
// 在包名前面增加下划线表示导入包但是不直接使用它,被导入的包中的init函数会在导入的时候执行
import _ "github.com/go-sql-driver/mysql"
包内元素的可见性 Accessability
名称首字符为Unicode包含的大写字母的元素是被导出的,对外部包是可见的
Unicode包含的大写字母
首字为非大写字母的元素只对本包可见(同包跨源文件可以访问,子包不能访问)
internal包(内部包)
internal包及其子包中的导出元素只能被与internal同父包的其他包访问
规范导入包路径
导入包后,定义/声明的常量、变量、类型,作用域是当前包范围,可以被当前包中所有函数调用
go tools
gofmt
gofmt main.go
格式化 main.go 文件的源码输出到终端
gofmt -w main.go
格式化 main.go 文件并回写到原文件
gofmt -w Tasks
格式化 Tasks 目录下的所有文件并且将格式化内容保存到对应源文件
godoc
会从$GOROOT目录下的标准库(library)和$GOPATH/src 目录下的所有项目中的代码注释中生成文档
web
godoc --http=:8080
net/http 包的文档可以通过 localhost:8080/pkg/net/http 查看
命令行
godoc -v 可以查看 godoc启动扫描详情
godoc 可以直接查看 go的标准库文档
go doc net/http
在终端输出 net/http 的文档
go test
golang 内置 testing 支持
以_test.go为后缀名的源文件在执行go build时不会被构建成包的一部分
运行 gotest 的时候执行 file_test.go 里面的用例
go test 包名
或者
cd 某个包目录下 && go test
或者
cd 某个包目录下 && go test
*_test.go文件中,有三种类型的函数
测试函数
是以Test为函数名前缀的函数,用于测试程序的一些逻辑行为是否正确
go test命令会调用这些测试函数并报告测试结果是PASS或FAIL
基准测试(benchmark)函数
以Benchmark为函数名前缀的函数,它们用于衡量一些函数的性能
go test命令会多次运行基准测试函数以计算一个平均的执行时间
示例函数
以Example为函数名前缀的函数,提供一个由编译器保证正确性的示例文档
相关命令
go test -run=”French|Canal”
有3个测试函数包含不同的测试文件,这3个函数分别是:
TestFrenchPalindrome
TestCanalPalindrome
TestChinaPalindrome
TestFrenchPalindrome
TestCanalPalindrome
TestChinaPalindrome
上面这个正则的函数名是TestFrenchPalindrome 和TestCanalPalindrome
go build
执行 go build构建应用程序,编译器会把所有 *.go 除了*_test.go 进行编译
想给编译的文件指定名称,使用 go build -o tasks
- 构建时间(整个应用+依赖库,编译为一个二进制文件)
- 交叉编译(跨平台)
- 交叉编译(跨平台)
```sh
env GOOS=darwin GOARCH=386 go build -o tasks.app
env GOOS=windows GOARCH=386 go build -o tasks.exe
env GOOS=linux GOARCH=amd64 go build -o tasks
```
env GOOS=darwin GOARCH=386 go build -o tasks.app
env GOOS=windows GOARCH=386 go build -o tasks.exe
env GOOS=linux GOARCH=amd64 go build -o tasks
```
限制于约束
Go的编译不是以go文件为最小单位的,而是以包为最小单位的
将一个特别的构建注释参数放在包声明语句之前可以提供更多的构建过程控制
// +build linux darwin
go build只在编译程序对应的目标操作系统是Linux或Mac OS X时才编译这个文件
// +build ignore
该构建注释则表示不编译这个文件
go build common_crawler 过程
在任何目录执行
go会在环境变量找到GOPATH/src,然后再拼接common_crawler
寻找依赖包的时候,go会在 GOPATH/src 和 GOROOT/src下找依赖包
go install
编译包文件(无main包),将编译后的包文件放到 pkg 目录下($GOPATH/pkg)
编译生成可执行文件(有main包),将可执行文件放到 bin 目录($GOPATH/bin)
加上 -i 参数都会安装依赖包到pkg并且编译二进制执行文件
go run
在命令行直接运行应用的入口文件或者直接执行某个文件
会自动编译(编译后文件在临时目录,运行结束会自动删除)
go get
go 内置的 package 管理工具,可以直接从 github 等代码管理站点获取包
go clean
_obj/
_test/
_testmain.go
test.out
_test/
_testmain.go
test.out
build.out
*.[568ao]
DIR(.exe)
IDR . test(.exe)
MAINFILE(*.exe)
*.[568ao]
DIR(.exe)
IDR . test(.exe)
MAINFILE(*.exe)
go fix
参数
-diff : 不将修正后的内容写入文件,而只打印修正前后的内容的对比信息到标准输出
-r : 只对目标源码文件做有限的修正操作
-force : 使用此标记后,即使源码文件中的代码已经与Go语言的最新版本相匹配了,也会强行执行指定的修正操作
说明
这里所说的版本即Go语言的版本
这里所说的版本即Go语言的版本
是命令go tool fix的简单封装
对旧程序调用的代码更换为对新程序调用的代码、把旧的语法更换为新的语法,等等
会把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码
代码包的所有Go语言源码文件不包括其子代码包中的文件
gops
Go语言程序查看和诊断工具
依次列出 PID,PPID,程序名称,编译使用的 Go 版本号,程序路径
gops 还能进行程序的诊断
https://www.cnblogs.com/snowInPluto/p/7785651.html
其他
go version 获取 go 安装包版本
go env 获取 golang 环境变量
go list 当前目录下的包
go env 获取 golang 环境变量
go list 当前目录下的包
第三方类库
第三方类库下载
go get 链接
go mod tidy
代理
https://goproxy.io/zh/
在Linux 或 macOS下
# 启用 Go Modules 功能
export GO111MODULE=on
# 配置 GOPROXY 环境变量
export GOPROXY=https://goproxy.io
在windows下
# 启用 Go Modules 功能
$env:GO111MODULE="on"
# 配置 GOPROXY 环境变量
$env:GOPROXY="https://goproxy.io"
# 启用 Go Modules 功能
export GO111MODULE=on
# 配置 GOPROXY 环境变量
export GOPROXY=https://goproxy.io
在windows下
# 启用 Go Modules 功能
$env:GO111MODULE="on"
# 配置 GOPROXY 环境变量
$env:GOPROXY="https://goproxy.io"
https://github.com/chromedp/chromedp
协议 CDP
相关语言
Pyppeteer
它是谷歌chrome官方无头框架puppeteer的python版本 https://miyakogi.github.io/pyppeteer/reference.html
Puppeeer
它是一个Node库,可以使用Node.js来实现控制Chrome或Chromiumhttp://www.puppeteerjs.com/
Chromedp
是由Go语言编写,支持Chrome DevTools Protocol的一个库,不需要依赖其他的外界服务(如Selenium和PhantomJS) https://github.com/chromedp/chromedp
Chromedp使用
chromedp.NewContext() 初始化chromedp的上下文,后续这个页面都使用这个上下文进行操作
chromedp.Run() 运行一个chrome的一系列操作
chromedp.Navigate() 将浏览器导航到某个页面
chromedp.WaitVisible() 等候某个元素可见,再继续执行。
chromedp.Click() 模拟鼠标点击某个元素
chromedp.Value() 获取某个元素的value值
chromedp.ActionFunc() 再当前页面执行某些自定义函数
chromedp.Text() 读取某个元素的text值
chromedp.Evaluate() 执行某个js,相当于控制台输入js
network.SetExtraHTTPHeaders() 截取请求,额外增加header头
chromedp.SendKeys() 模拟键盘操作,输入字符
chromedp.Nodes() 根据xpath获取某些元素,并存储进入数组
chromedp.NewRemoteAllocator
chromedp.OuterHTML() 获取元素的outer html
chromedp.Screenshot() 根据某个元素截图
page.CaptureScreenshot() 截取整个页面的元素
chromedp.Submit() 提交某个表单
chromedp.WaitNotPresent() 等候某个元素不存在,比如“正在搜索。。。”
chromedp.Tasks{} 一系列Action组成的任务
chromedp.Run() 运行一个chrome的一系列操作
chromedp.Navigate() 将浏览器导航到某个页面
chromedp.WaitVisible() 等候某个元素可见,再继续执行。
chromedp.Click() 模拟鼠标点击某个元素
chromedp.Value() 获取某个元素的value值
chromedp.ActionFunc() 再当前页面执行某些自定义函数
chromedp.Text() 读取某个元素的text值
chromedp.Evaluate() 执行某个js,相当于控制台输入js
network.SetExtraHTTPHeaders() 截取请求,额外增加header头
chromedp.SendKeys() 模拟键盘操作,输入字符
chromedp.Nodes() 根据xpath获取某些元素,并存储进入数组
chromedp.NewRemoteAllocator
chromedp.OuterHTML() 获取元素的outer html
chromedp.Screenshot() 根据某个元素截图
page.CaptureScreenshot() 截取整个页面的元素
chromedp.Submit() 提交某个表单
chromedp.WaitNotPresent() 等候某个元素不存在,比如“正在搜索。。。”
chromedp.Tasks{} 一系列Action组成的任务
github.com/BurntSushi/toml
TOML 的目标是成为一个极简的配置文件格式
TOML 被设计成可以无歧义地被映射为哈希表,从而被多种语言解析
https://github.com/BurntSushi/toml
github.com/stretchr/testify
golang的测试框架
package
assert package
assert虽然也标记为case失败,但case不会退出,而是继续往下执行
require package
require的函数会直接导致case结束
常用的stretchr/testify框架函数
github.com/Shopify/sarama
Sarama is a Go library for Apache Kafka 0.8, and up
doc
https://pkg.go.dev/github.com/Shopify/sarama
github.com/bsm/sarama-cluster
Cluster extensions for Sarama, the Go client library for Apache Kafka 0.9 [DEPRECATED]
deprecated
This repository has been archived by the owner. It is now read-only.
github.com/confluentinc/confluent-kafka-go
kafka官网推荐的golang package
github.com/gin-gonic/gin
一个轻量级的 WEB 框架
支持 RestFull 风格 API
支持 GET,POST,PUT,PATCH,DELETE,OPTIONS 等 http 方法
支持文件上传,分组路由,Multipart/Urlencoded FORM
支持 JsonP,参数处理等等功能
doc
https://gin-gonic.com/docs/
github.com/golang/protobuf
Go support for Protocol Buffers
github.com/gorilla/websocket
Gorilla WebSocket is a Go implementation of the WebSocket protocol.
github.com/jehiah/go-strftime
go implementation of strftime
github.com/jonboulle/clockwork
A simple fake clock for Go
github.com/lestrrat-go/file-rotatelogs
Provide an io.Writer that periodically rotates log files from within the application. Port of File::RotateLogs from Perl to Go
github.com/lestrrat-go/strftime
Fast strftime for Go
github.com/onsi/ginkgo
基于Go语言的BDD(Behavior Driven Development)测试框架,一般用于Go服务的集成测试
·
Ginkgo's Preferred Matcher Library
github.com/rs/xid
xid is a globally unique id generator thought for the web
github.com/sirupsen/logrus
Structured, pluggable logging for Go
Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger
基础语法-类型
基础类型
数字类型
有符号整数 int8 int16 int32 int64 int
无符号整数类型
uintptr
浮点数
float32 、 float64
bool 类型
var b bool
b = 1 // 编译错误
b = bool (1) // 编译错误
string
一个引用类型
一个部分是指针,指向了底层的一个字节数组
一个部分是字节数组的长度
分类
解释型
非解释型
相关字符类型
复数
complex64 complex128
常量
常量可以是字符、字符串、布尔或数值类型的值,数值常量是高精度的值
const x int = 3
const y,z int = 1,2
const (
a byte = 'A'
b string = "B"
c bool = true
d int = 4
e float32 = 5.1
f complex64 = 6 + 6i
)
const y,z int = 1,2
const (
a byte = 'A'
b string = "B"
c bool = true
d int = 4
e float32 = 5.1
f complex64 = 6 + 6i
)
根据常量值自动推导类型
const a = 0 // a int
const (
b = 2.3 // b float64
c = true // c bool
)
const (
b = 2.3 // b float64
c = true // c bool
)
常量组内定义时复用表达式
常量组内定义的常量只有名称时,其值会根据上一次最后出现的常量表达式计算相同的类型与值
常量组内定义的常量只有名称时,其值会根据上一次最后出现的常量表达式计算相同的类型与值
const (
a = 3 // a = 3
b // b = 3
c // c = 3
d = len("asdf") // d = 4
e // e = 4
f // f = 4
g,h,i = 7,8,9 // 复用表达式要一一对应
x,y,z // x = 7, y = 8, z = 9
)
a = 3 // a = 3
b // b = 3
c // c = 3
d = len("asdf") // d = 4
e // e = 4
f // f = 4
g,h,i = 7,8,9 // 复用表达式要一一对应
x,y,z // x = 7, y = 8, z = 9
)
自动递增枚举常量 iota
iota的枚举值可以赋值给数值兼容类型
每个常量单独声明时,iota不会自动递增
每个常量单独声明时,iota不会自动递增
const a int = iota // a = 0
const b int = iota // b = 0
const c byte = iota // c = 0
const d uint64 = iota // d = 0
const b int = iota // b = 0
const c byte = iota // c = 0
const d uint64 = iota // d = 0
常量组合声明时,
iota
每次引用会逐步自增,初始值为0,步进值为1 const (
a uint8 = iota // a = 0
b int16 = iota // b = 1
c rune = iota // c = 2
d float64 = iota // d = 3
e uintptr = iota // e = 4
)
a uint8 = iota // a = 0
b int16 = iota // b = 1
c rune = iota // c = 2
d float64 = iota // d = 3
e uintptr = iota // e = 4
)
即使iota不是在常量组内第一个开始引用,也会按组内常量数量递增
const (
a = "A"
b = 'B'
c = iota // c = 2
d = "D"
e = iota // e = 4
)
a = "A"
b = 'B'
c = iota // c = 2
d = "D"
e = iota // e = 4
)
枚举的常量都为同一类型时,可以使用简单序列格式(组内复用表达式)
const (
a = iota // a int32 = 0
b // b int32 = 1
c // c int32 = 2
)
a = iota // a int32 = 0
b // b int32 = 1
c // c int32 = 2
)
枚举序列中的未指定类型的常量会跟随序列前面最后一次出现类型定义的类型
const (
a byte = iota // a uint8 = 0
b // b uint8 = 1
c // c uint8 = 2
d rune = iota // d int32 = 3
e // e int32 = 4
f // f int32 = 5
)
a byte = iota // a uint8 = 0
b // b uint8 = 1
c // c uint8 = 2
d rune = iota // d int32 = 3
e // e int32 = 4
f // f int32 = 5
)
iota自增值只在一个常量定义组合中有效,跳出常量组合定义后iota初始值归0
const (
a = iota // a int32 = 0
b // b int32 = 1
c // c int32 = 2
)
const (
e = iota // e int32 = 0 (iota重新初始化并自增)
f // f int32 = 1
)
a = iota // a int32 = 0
b // b int32 = 1
c // c int32 = 2
)
const (
e = iota // e int32 = 0 (iota重新初始化并自增)
f // f int32 = 1
)
定制iota序列初始值与步进值 (通过组合内复用表达式实现)
const (
a = (iota + 2) * 3 // a int32 = 6 (a=(0+2)*3) 初始值为6,步进值为3
b // b int32 = 9 (b=(1+2)*3)
c // c int32 = 12 (c=(2+2)*3)
d // d int32 = 15 (d=(3+2)*3)
)
a = (iota + 2) * 3 // a int32 = 6 (a=(0+2)*3) 初始值为6,步进值为3
b // b int32 = 9 (b=(1+2)*3)
c // c int32 = 12 (c=(2+2)*3)
d // d int32 = 15 (d=(3+2)*3)
)
指针 Pointer
通过取地址操作符&获取指向值/引用对象的指针
内置函数new(T)分配了一个零初始化的 T 值,并返回指向它的指针
使用*读取/修改指针指向的值
func main() {
i := new(int)
*i = 3
println(i, *i) // 0xc208031f80 3
i = new(int)
println(i, *i) // 0xc208031f78 0
}
指针使用点号来访问结构体字段
结构体字段/方法可以通过结构体指针来访问,通过指针间接的访问是透明的
fmt.Println(s.A)
fmt.Println((*s).A)
fmt.Println((*s).A)
跨层指针元素的使用
在指针引用多层对象时,指针是针对引用表达式的最后一位元素
运算符
位运算
&
|
^
位清除 &^
<<
>>
逻辑运算符 == 、!= 、 > 、 >= 、 <=
算术运算符 + 、-、* 、 / 、% 、++ 、--
运算符优先级
进制
基础语法-复合类型
数组 Array
数组是值类型
默认零值不是nil,传递参数时会进行复制
默认零值不是nil,传递参数时会进行复制
var a [3]int = [3]int{0, 1, 2} // a = [0 1 2]
var b [3]int = [3]int{} // b = [0 0 0]
var c [3]int
c = [3]int{}
c = [3]int{0,0,0} // c = [0 0 0]
d := [3]int{} // d = [0 0 0]
fmt.Printf("%T\t%#v\t%d\t%d\n", d, d, len(d), cap(d)) // [3]int [3]int{0, 0, 0} 3 3
var b [3]int = [3]int{} // b = [0 0 0]
var c [3]int
c = [3]int{}
c = [3]int{0,0,0} // c = [0 0 0]
d := [3]int{} // d = [0 0 0]
fmt.Printf("%T\t%#v\t%d\t%d\n", d, d, len(d), cap(d)) // [3]int [3]int{0, 0, 0} 3 3
使用...自动计算数组的长度
var a = [...]int{0, 1, 2}
// 多维数组只能自动计算最外围数组长度
x := [...][3]int{{0, 1, 2}, {3, 4, 5}}
y := [...][2][2]int{{{0,1},{2,3}},{{4,5},{6,7}}}
// 通过下标访问数组元素
println(y[1][1][0])
// 多维数组只能自动计算最外围数组长度
x := [...][3]int{{0, 1, 2}, {3, 4, 5}}
y := [...][2][2]int{{{0,1},{2,3}},{{4,5},{6,7}}}
// 通过下标访问数组元素
println(y[1][1][0])
初始化指定索引的数组元素,未指定初始化的元素保持默认零值
var a = [3]int{2:3}
var b = [...]string{2:"c", 3:"d"}
var b = [...]string{2:"c", 3:"d"}
切片 Slice
引用类型,所以未初始化时的默认零值是nil,长度与容量都是
内存结构
有 3 个域的结构体:指向相关数组的指针,切片长度以及切片容量
初始化、声明
声明格式: var slice1 []type(不需要说明长度)
初始化格式:var slice1 []type = arr1[start:end]
注:slice1是由数组 arr1 从 start 索引到 end-1 索引之间的元素构成的子集
初始化格式:var slice1 []type = arr1[start:end]
注:slice1是由数组 arr1 从 start 索引到 end-1 索引之间的元素构成的子集
//slice1 等于完整的 arr1 数组
slice1 = &arr1
slice1 []type := arr1[:]
slice1 []type :=arr1[0:len(arr1)]
//切片和数组同时初始化
s := []int{1,2,3}
相当于 s := [3]int{1,2,3}[:]
//切片间赋值
s2 := s[:]
slice1 = &arr1
slice1 []type := arr1[:]
slice1 []type :=arr1[0:len(arr1)]
//切片和数组同时初始化
s := []int{1,2,3}
相当于 s := [3]int{1,2,3}[:]
//切片间赋值
s2 := s[:]
内置函数make初始化slice,第一参数是slice类型,第二参数是长度,第三参数是容量(省略时与长度相同)
var b = make([]int, 0)
fmt.Printf("%T\t%#v\t%d\t%d\n", b, b, len(b), cap(b)) // []int []int{} 0 0
var c = make([]int, 3, 10)
fmt.Printf("%T\t%#v\t%d\t%d\n", c, c, len(c), cap(c)) // []int []int{} 3 10
var a = new([]int)
fmt.Printf("%T\t%#v\t%d\t%d\n", a, a, len(*a), cap(*a)) // *[]int &[]int(nil) 0 0
fmt.Printf("%T\t%#v\t%d\t%d\n", b, b, len(b), cap(b)) // []int []int{} 0 0
var c = make([]int, 3, 10)
fmt.Printf("%T\t%#v\t%d\t%d\n", c, c, len(c), cap(c)) // []int []int{} 3 10
var a = new([]int)
fmt.Printf("%T\t%#v\t%d\t%d\n", a, a, len(*a), cap(*a)) // *[]int &[]int(nil) 0 0
make 的使用方式:
var slice1 []type = func make([]T, len, cap)
注:
T元素的类型
len 作为切片初始长度
cap 作为相关数组的长度,是个可选参数
//这样分配一个有 50 个 int 值的数组,并且创建了一个长度为 10,容量为 50 的 切片 v,该 切片 指向数组的前 10 个元素。
var v []int = make([]int, 10, 50)
等价于
var p *[]int = new([50]int)[0:10]
追加/修改元素
复制
sl_from := []int{1, 2, 3}
sl_to := make([]int, 10)
n := copy(sl_to, sl_from)
追加
sl3 := []int{1, 2, 3}
sl3 = append(sl3, 4, 5, 6)
fmt.Println(sl3)
//从字符串生成字节切片
var b []byte
var s string
b = append(b, s...)
重组
切片在达到容量上限后可以扩容
sl = sl[0:len(sl)+1]
切片移动
切片只能向后移动,
//,将 s2 向后移动一位, 末尾并没有移动
s2 = s2[1:]
// 切片不能被重新分片以获取数组的前一个元素,会导致编译错误。
s2 = s2[-1:]
基于一个切片创建一个切片
s5 := []int {1,2,3,4,5,6,7,8,9,10} // 创建一个有10个长度和容量的切片
s6 := s5[:5]
fmt.Println(len(s6), cap(s6), s6) // 5 10 [1 2 3 4 5]
s7 := s5[3:5:8] // 截取3~4两位,容量截取到第7位
fmt.Println(len(s7), cap(s7), s7) // 2 5 [4 5]
s6 := s5[:5]
fmt.Println(len(s6), cap(s6), s6) // 5 10 [1 2 3 4 5]
s7 := s5[3:5:8] // 截取3~4两位,容量截取到第7位
fmt.Println(len(s7), cap(s7), s7) // 2 5 [4 5]
http://www.zbpblog.com/blog-221.html
对比数组
切片的内存回收
举个例子:
实现将一个文件加载到内存,然后搜索其中所有的数字并返回一个切片的函数FindDigits()
下面代码虽然可以顺利运行,但返回的 []byte 指向的底层是整个文件的数据。
只要该返回的切片不被释放,垃圾回收器就不能释放整个文件所占用的内存。
因此,导致因为一点点有用的数据却占用了整个文件的内存
var digitRegexp = regexp.MustCompile("[0-9]+")
func FindDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
return digitRegexp.Find(b)
}
想要避免上面的问题,可以通过拷贝我们需要的部分到一个新的切片中
func FindDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
b = digitRegexp.Find(b)
c := make([]byte, len(b))
copy(c, b)
return c
}
上面这段代码只能找到第一个匹配正则表达式的数字串。要想找到所有的数字,可以尝试下面这段代码:
func FindFileDigits(filename string) []byte {
fileBytes, _ := ioutil.ReadFile(filename)
b := digitRegexp.FindAll(fileBytes, len(fileBytes))
c := make([]byte, 0)
for _, bytes := range b {
c = append(c, bytes...)
}
return c
}
结构体 Struct
内存结构
结构体是值类型
传递时会复制值,其默认零值
声明、定义
匿名结构体
匿名结构体声明时省略了type关键字,并且没有名称
// 声明变量a为空的匿名结构体类型
var a struct{}
// 声明变量b为包含一个字段的匿名结构体类型
var b struct{ x int }
// 声明变量c为包含两个字段的匿名结构体类型
var c struct {
u int
v bool
}
var a struct{}
// 声明变量b为包含一个字段的匿名结构体类型
var b struct{ x int }
// 声明变量c为包含两个字段的匿名结构体类型
var c struct {
u int
v bool
}
结构体初始化
type S struct {
A int
B, c string
}
A int
B, c string
}
通过结构体字段的值作为列表来新分配一个结构体
var s S = S{0, "1", "2"}
使用 Name: 语法
var s S = S{B: "1", A: 0}
字典/映射 Map
map是引用类型
声明初始化使用
未初始化的map值为 nil 长度为0,并且不能赋值元素
var m map[int]int
m[0] = 0 // × runtime error: assignment to entry in nil map
fmt.Printf("type: %T\n", m) // map[int]int
fmt.Printf("value: %#v\n", m) // map[int]int(nil)
fmt.Printf("value: %v\n", m) // map[]
fmt.Println("is nil: ", nil == m) // true
fmt.Println("length: ", len(m)) // 0,if m is nil, len(m) is zero
m[0] = 0 // × runtime error: assignment to entry in nil map
fmt.Printf("type: %T\n", m) // map[int]int
fmt.Printf("value: %#v\n", m) // map[int]int(nil)
fmt.Printf("value: %v\n", m) // map[]
fmt.Println("is nil: ", nil == m) // true
fmt.Println("length: ", len(m)) // 0,if m is nil, len(m) is zero
var map6 map[string]int
fmt.Println(map6 == nil) // true
map6["num1"] = 1 // 报错,不能为一个没有指向哈希表的map变量进行元素赋值
fmt.Println(map6)
// var声明后,正确的定义方式是
var map6 map[string]int
map6 = map[string]int{} // 先给map6赋值一个空的哈希表,底层创建出一个哈希表,此时才可以为map6添加元素。或者直接在赋值这里创一个有元素的哈希表
map6["num1"] = 1
fmt.Println(map6)
fmt.Println(map6 == nil) // true
map6["num1"] = 1 // 报错,不能为一个没有指向哈希表的map变量进行元素赋值
fmt.Println(map6)
// var声明后,正确的定义方式是
var map6 map[string]int
map6 = map[string]int{} // 先给map6赋值一个空的哈希表,底层创建出一个哈希表,此时才可以为map6添加元素。或者直接在赋值这里创一个有元素的哈希表
map6["num1"] = 1
fmt.Println(map6)
直接赋值初始化map
map2 := map[string]string {
"name" : "zbp",
"age" : "24", // 这里必须要有逗号,反则会报错
}
n := map[string]S{
"a":S{0,1},
"b":{2,3}, // 类型名称可省略
}
"name" : "zbp",
"age" : "24", // 这里必须要有逗号,反则会报错
}
n := map[string]S{
"a":S{0,1},
"b":{2,3}, // 类型名称可省略
}
读取、添加、修改、删除元素
m[0] = 3 // 修改m中key为0的值为3
m[4] = 8 // 添加到m中key为4值为8
a := n["a"] // 获取n中key为“a“的值
b, ok := n["c"] // 取值, 并通过ok(bool)判断key对应的元素是否存在.
delete(n, "a") // 使用内置函数delete删除key为”a“对应的元素
m[4] = 8 // 添加到m中key为4值为8
a := n["a"] // 获取n中key为“a“的值
b, ok := n["c"] // 取值, 并通过ok(bool)判断key对应的元素是否存在.
delete(n, "a") // 使用内置函数delete删除key为”a“对应的元素
json
基础类库
json.Marshal函数
将切片或者结构体转化为json字符串
json.MarshalIndent函数
返回格式化后的json字符串,该函数有两个额外的字符串参数用于表示每一行输出的前缀和每一个层级的缩进
json.Unmarshal函数
可以将json字符串转为指定的结构体类型
第一参传入一个byte类型的内容
第二参传入一个指定的结构体类型的指针用于接收格式化为struct后的json数据
只返回错误信息
不能在要json化的结构体中使用首字母小写的成员
标准类库
Text和HTML模板
档案分类编辑
添加档案分类
encoding/xml
logrus包和seelog包
archive/zip包
log
Logger类型
type Logger struct {
mu sync.Mutex // ensures atomic writes; protects the following fields
prefix string // prefix on each line to identify the logger (but see Lmsgprefix)
flag int // properties
out io.Writer // destination for output
buf []byte // for accumulating text to write
}
mu sync.Mutex // ensures atomic writes; protects the following fields
prefix string // prefix on each line to identify the logger (but see Lmsgprefix)
flag int // properties
out io.Writer // destination for output
buf []byte // for accumulating text to write
}
预定义的“标准”logger
Print系列(Print|Printf|Println)
Fatal系列(Fatal|Fatalf|Fatalln)
Panic系列(Panic|Panicf|Panicln)
创建新的Logger
logger := log.New(os.Stdout, "<PS>", log.Lshortfile|log.Ldate|log.Ltime)
logger.Println("这是自定义的logger记录的日志。")
IO 及文件操作
io
io.Reader
io.Writer
Write方法会先把内容写入到缓冲区中,不会直接刷到磁盘,只有在所有Write都结束之后才会写入到磁盘。
如果希望能够每次写入后都进行刷盘的话,可以每次调用完Write后调用f.Sync()方法。
所以如果是复制一个大文件例如几G的视频的时候,要注意要手动调用Sync方法刷盘,否则内存可能会被撑爆。
如果希望能够每次写入后都进行刷盘的话,可以每次调用完Write后调用f.Sync()方法。
所以如果是复制一个大文件例如几G的视频的时候,要注意要手动调用Sync方法刷盘,否则内存可能会被撑爆。
ioutil
fmt
F开头的函数可以传入io.Writer和io.Reader指定从哪个可读或可写对象中读取或输出
f结尾的函数可以用占位符格式化如 Fprintf Sprintf Printf Fscanf Sscanf Scanf
S开头的函数不打印而是只返回给变量
ln结尾的函数会在打印时换行
Fscanln与Fscan的区别在于,前者会把换行当作结束符空格当作分隔符,后者会把换行和空格都当作分隔符
bufio
path和filepath包
func IsAbs(path string) bool // 是否时绝对路径
func Abs(path string) (string, error) // 返回绝对路径,这个是filepath包的函数
func Rel(basepath, targpath string) (string, error) // 返回targpath相对于basepath的相对路径
func Join(elem ...string) string // 拼接路径
func Split(path string) (dir, file string) // 将路径分割为目录和文件名部分
func Clean(path string) string // 简化一个路径(去掉重复的/和.和..)
func Walk(root string, walkFn WalkFunc) error // 遍历一个路径下的表层文件,并将这些文件放到walkFn这个回调函数处理
func Abs(path string) (string, error) // 返回绝对路径,这个是filepath包的函数
func Rel(basepath, targpath string) (string, error) // 返回targpath相对于basepath的相对路径
func Join(elem ...string) string // 拼接路径
func Split(path string) (dir, file string) // 将路径分割为目录和文件名部分
func Clean(path string) string // 简化一个路径(去掉重复的/和.和..)
func Walk(root string, walkFn WalkFunc) error // 遍历一个路径下的表层文件,并将这些文件放到walkFn这个回调函数处理
文件系统操作
os
文件(非目录的文件)用文件对象os.*File来表示
type File struct {
*file // os specific
}
type file struct {
pfd poll.FD
name string
dirinfo *dirInfo // nil unless directory being read
appendMode bool // whether file is opened for appending
}
*file // os specific
}
type file struct {
pfd poll.FD
name string
dirinfo *dirInfo // nil unless directory being read
appendMode bool // whether file is opened for appending
}
os.FileInfo
type FileInfo interface {
Name() string // base name of the file
Size() int64 // length in bytes for regular files; system-dependent for others
Mode() FileMode // file mode bits
ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir()
Sys() interface{} // underlying data source (can return nil)
}
Name() string // base name of the file
Size() int64 // length in bytes for regular files; system-dependent for others
Mode() FileMode // file mode bits
ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir()
Sys() interface{} // underlying data source (can return nil)
}
container
函数、方法、接口
函数 function
函数声明 Declare
声明格式
func name(parameter-list) (result-list) {
body
}
body
}
使用关键字func声明函数,函数可以没有参数或接受多个参数
func f0() {/*...*/}
func f1(a int) {/*...*/}
func f2(a int, b byte) {/*...*/}
可变数量的参数: 在函数参数类型之前使用...声明该参数
可变参数只能声明为函数的最后一个参数
func f3(a ...int) {/*...*/}
func f4(a int, b bool, c ...string) {/*...*/}
func f4(a int, b bool, c ...string) {/*...*/}
当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略
func f0(a,b,c int) {/*...*/}
func f1() (a,b,c int) {/*...*/}
func f2(a,b int, c,d byte) (x,y int, z,s bool) {/*...*/}
func f1() (a,b,c int) {/*...*/}
func f2(a,b int, c,d byte) (x,y int, z,s bool) {/*...*/}
返回值
函数可以返回任意数量的返回值
func f0() {
return
}
func f1() int {
return 0
}
func f2() (int, string) {
return 0, "A"
}
return
}
func f1() int {
return 0
}
func f2() (int, string) {
return 0, "A"
}
函数返回结果参数,可以像变量那样命名和使用
func f() (a int, b string) {
a = 1
b = "B"
return // 即使return后面没有跟变量,关键字在函数结尾也是必须的
// 或者 return a, b
}
a = 1
b = "B"
return // 即使return后面没有跟变量,关键字在函数结尾也是必须的
// 或者 return a, b
}
func demo1() (r1 int, r2 int){
r1 =1
r2 = 2
return
}
r1 =1
r2 = 2
return
}
会默认返回 r1 和 r2, 这种返回方式叫做 bare return,
bare return 可以减少代码的重复,但是使得代码可读性降低
bare return 可以减少代码的重复,但是使得代码可读性降低
参数和返回值在函数调用的时候就会自动声明
函数的签名
形式参数列表和返回值列表中的变量类型一一对应
函数传参
传值
传指针
引用传递
引用传递
Go语言没有默认参数
内建(内置)函数
defer 函数
执行到return
执行到return语句之后,但真正返回一个值之前的时候
发生panic异常的时候
在f所在函数释放之前执行
用于记录函数的执行情况和信息
使用defer来查看或者改变函数的返回值
func append
func append(slice []Type, elems ...Type) []Type
slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)
slice = append(slice, anotherSlice...)
func cap
func cap(v Type) int
func close
func close(c chan<- Type)
对于已关闭的信道,语句:
x, ok := <-c // ok值为false
x, ok := <-c // ok值为false
func complex
func complex(r, i FloatType) ComplexType
func imag
func imag(c ComplexType) FloatType
func copy
func copy(dst, src []Type) int
a, b, c := []byte{1, 2, 3}, make([]byte, 2), 0
fmt.Println("a:", a, " b:", b, " c: ", c) // a: [1 2 3] b: [0 0] c: 0
c = copy(b, a)
fmt.Println("a:", a, " b:", b, " c: ", c) // a: [1 2 3] b: [1 2] c: 2
b = make([]byte, 5)
c = copy(b, a)
fmt.Println("a:", a, " b:", b, " c: ", c) // a: [1 2 3] b: [1 2 3 0 0] c: 3
s := "ABCD"
c = copy(b, s)
fmt.Println("s:", s, " b:", b, " c: ", c) // s: ABCD b: [65 66 67 68 0] c: 4
fmt.Println("a:", a, " b:", b, " c: ", c) // a: [1 2 3] b: [0 0] c: 0
c = copy(b, a)
fmt.Println("a:", a, " b:", b, " c: ", c) // a: [1 2 3] b: [1 2] c: 2
b = make([]byte, 5)
c = copy(b, a)
fmt.Println("a:", a, " b:", b, " c: ", c) // a: [1 2 3] b: [1 2 3 0 0] c: 3
s := "ABCD"
c = copy(b, s)
fmt.Println("s:", s, " b:", b, " c: ", c) // s: ABCD b: [65 66 67 68 0] c: 4
func delete
func delete(m map[Type]Type1, key Type)
m := map[int]string{
0: "A",
1: "B",
2: "C",
}
delete(m, 1)
fmt.Println(m) // map[2:C 0:A]
delete(m, 3) // 此行代码执行没有任何操作,也不会报错。
0: "A",
1: "B",
2: "C",
}
delete(m, 1)
fmt.Println(m) // map[2:C 0:A]
delete(m, 3) // 此行代码执行没有任何操作,也不会报错。
func len
func len(v Type) int
func make
func make(Type, size IntegerType) Type
func new
func new(Type) *Type
panic函数
func panic(v interface{})
func println
func println(args ...Type)
func real
func real(c ComplexType) FloatType
Recover函数
func recover() interface{}
初始化函数 init
是用于程序执行前做包的初始化工作的函数
init函数的声明没有参数和返回值
一个package或go源文件可以包含零个或多个init函数
init函数被自动调用,在main函数之前执行,不能在其他函数中调用,显式调用会报错该函数未定义
所有init函数都会被自动调用,调用顺序
主函数 main
匿名函数(函数闭包 Closure)
方法 Method
面向对象编程
方法不区分静态方法和实例方法
Go 方法是作用在接收者(receiver)上的一个函数;
类似于oop编程中的self或者this,但是go中的接收器名词可以自定义,不一定非得定义为this或者self,一般会定义为类名的小写或者首字母
特性
方法是和类型相关联而不是和某一个对象相关联
为某一个类型定义一个方法,那么使用该类型声明的变量就都可以调用这个方法,但是不能专门为一个变量定义方法
定义一个以结构体类型为底层类型的新类型来定义一个类
结构体中的成员当成是对象的成员属性
为这个新类型定义的方法看成是类方法
方法的接收者receiver是类型的值时,编译器会隐式的生成一个同名方法,其接收者receiver为该类型的指针,反过来却不会
方法值
func (point *Point) Distance(anotherPoint *Point) float64 {
return math.Hypot(point.X-anotherPoint.X, point.Y-anotherPoint.Y)
}
func main(){
x := Point{ X:0, Y:0}
y := Point{ X:3, Y:4}
distance := (&x).Distance
fmt.Println(distance(&y))
}
方法表达式
func (point *Point) Distance(anotherPoint *Point) float64 {
return math.Hypot(point.X-anotherPoint.X, point.Y-anotherPoint.Y)
}
func main(){
x := Point{ X:0, Y:0}
y := Point{ X:3, Y:4}
distance := (*method.Point).Distance
//distance的第一参接收器,所以&x是接收器。其实distance(&x, &y) 与 (&x).Distance(&y)等价
fmt.Println(distance(&x, &y))
}
return math.Hypot(point.X-anotherPoint.X, point.Y-anotherPoint.Y)
}
func main(){
x := Point{ X:0, Y:0}
y := Point{ X:3, Y:4}
distance := (*method.Point).Distance
//distance的第一参接收器,所以&x是接收器。其实distance(&x, &y) 与 (&x).Distance(&y)等价
fmt.Println(distance(&x, &y))
}
go 设计原则
一个新类型只要有一个方法使用指针作为接收器,那么这个新类型的其他所有方法也要用指针而不是变量值作为接收器
接收器
定义方法的一般格式如下:
func (recv receiver_type) methodName(parameter_list)(return_value_list) { ... }
继承
匿名字段为值类型时:值的方法会传递给结构体的值,指针的方法会传递给结构体的指针
匿名字段为指针类型时:指针的方法会传递给值和指针
匿名字段为接口类型时:方法会传递给值和指针
接口 Interface
接口定义格式
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}
Method1(param_list) return_type
Method2(param_list) return_type
...
}
接口嵌套接口
接口实现的条件
A.一个类必须实现这个接口的所有方法才算实现了这个接口
*ByteCouter和io.Writer是同一类型,和io.ReadWriter不是
B.实现方法的时候的接收器才是和这个接口是同一类型
C.当一个类型除了实现了一个接口的所有方法之外还有其他方法的话,
当把这个类型声明为接口类型的话,这个类型的其他方法会被隐藏而无法使用
当把这个类型声明为接口类型的话,这个类型的其他方法会被隐藏而无法使用
D. 空接口 interface{} 可以被任何类型的方法实现,但是由于空接口没有指定任何方法,
所以实现了空接口的类型不能调用它自己原有的所有方法
所以实现了空接口的类型不能调用它自己原有的所有方法
接口值
两个接口值相等
空接口
函数签名
类型断言
语法
x.(T),T是要断言的类型,x是接口值
关键点
断言只能用于接口值变量不能用于普通的变量,否则会报错。
我们可以用1个或者2个变量接收断言的返回结果
用途
判断对象(这个对象必须是一个接口值)是否有某一个或某多个方法
并发 Concurrency
协程 Goroutine
一个程序有多个goroutine在运行,其中任何一个goroutine发生panic异常都会终止所有的goroutine,然后程序结束
由Go运行时环境管理的轻量级线程
使用关键字go调用一个函数/方法,启动一个新的协程goroutine
Channels
定义
特点
一种引用类型,它对应着底层的一种数据结构
是引用类型,使用make函数来初始化。
未初始化的channel零值是nil,且不能用于发送和接收值
未初始化的channel零值是nil,且不能用于发送和接收值
c0 := make(chan int) // 不带缓冲的int类型channel
c1 := make(chan *int, 10) // 带缓冲的*int类型指针channel
c1 := make(chan *int, 10) // 带缓冲的*int类型指针channel
操作符<-用于指定channel的方向,发送或接收
如果未指定方向,则为双向channel
如果未指定方向,则为双向channel
var c0 chan int // 可用来发送和接收int类型的值
var c1 chan<- int // 可用来发送int类型的值
var c2 <-chan int // 可用来接收int类型的值
var c1 chan<- int // 可用来发送int类型的值
var c2 <-chan int // 可用来接收int类型的值
<-ch // 这样也可以,相当于弹出一个值但不赋给任何变量
channel的关闭一般都是在发送消息的goroutine,而不是在接收消息的goroutine
发送
带缓存
带缓冲的channel在缓冲区已满时发送方会阻塞,直到接收方从channel中取出值
不带缓存
无缓冲的channe中有值时发送方会阻塞,直到接收方从channel中取出值
关闭的
导致panic异常
接收
接收方在channel中无值会一直阻塞
如果goroutine 1从一个带缓存的channel(channel有消息的话)接收消息,则不会阻塞;如果channel为空就会阻塞
关闭的
(对带不带缓存的channel都相同)
(对带不带缓存的channel都相同)
如果channel中还有消息,则可以不阻塞的接收到这些消息
如果channel中没有消息,则会不阻塞的不停的接收到相应类型的零值,不会引发panic
select
一个没有任何case的select语句写作select{},会永远地等待下去
运行到select时,所有的case的channel都阻塞,则运行default的代码块
运行到select时,所有的case的channel都阻塞,而且没有default这一项,则select会阻塞直到有一个channel可读或可写
运行到select时,有多个channel都处于可接收或可发送的状态,select会随机选择一个channel执行
channel的使用
传递指针更高效
传递信号使用 空struct
超时检测
结合 Timer 限制频率
使用 _,ok 判断channel是否关闭
for range
channel 的设计模式
互斥锁
基于共享内存(使用互斥锁的例子)
临界区
原理
反射 reflect
reflect.TypeOf(x)
Kind()
Field(x)
reflect.ValueOf(x)
Elem
Kind()
Field
Type
Interface
NumFiled
Method
慎用反射
基于反射的代码是比较脆弱的。对于每一个会导致编译器报告类型错误的问题,在反射中都有与之相对应的误用问题,不同的是编译器会在构建时马上报告错误,而反射则是在真正运行到的时候才会抛出panic异常,可能是写完代码很久之后了,而且程序也可能运行了很长的时间
即使对应类型提供了相同文档,但是反射的操作不能做静态类型检查,而且大量反射的代码通常难以理解。总是需要小心翼翼地为每个导出的类型和其它接受interface{}或reflect.Value类型参数的函数维护说明文档
于反射的代码通常比正常的代码运行速度慢一到两个数量级
语句 Statement
分号/括号 ; {
Go是采用语法解析器自动在每行末尾增加分号
需要手工增加分号:
for循环使用分号把初始化、条件和遍历元素分开
if/switch的条件判断带有初始化语句时使用分号分开初始化语句与判断语句
在一行中有多条语句时,需要增加分号
控制语句(if,for,switch,select)、函数、方法 的左大括号不能单独放在一行, 语法解析器会在大括号之前自动插入一个分号,导致编译错误
条件语句 if
if语句 小括号 ( )是可选的,而大括号 { } 是必须的
if (i < 0 || i > 10) {
println(i)
}
if i < 0 {
println(i)
} else if i > 5 && i <= 10 {
println(i)
} else {
println(i)
}
println(i)
}
if i < 0 {
println(i)
} else if i > 5 && i <= 10 {
println(i)
} else {
println(i)
}
可以在条件之前执行一个简单的语句,由这个语句定义的变量的作用域仅在 if / else if / else 范围之内
if i := 0; (i < 1) { // 编译通过.
println(i)
}
if i := 0; i < 0 { // 使用gofmt格式化代码会自动移除代码中不必要的小括号( )
println(i)
} else if i == 0 {
println(i)
} else {
println(i)
}
println(i)
}
if i := 0; i < 0 { // 使用gofmt格式化代码会自动移除代码中不必要的小括号( )
println(i)
} else if i == 0 {
println(i)
} else {
println(i)
}
if语句作用域范围内定义的变量会覆盖外部同名变量
a, b := 0, 1
if a, b := 3, 4; a > 1 && b > 2 {
println(a, b) // 3 4
}
println(a, b) // 0 1
if a, b := 3, 4; a > 1 && b > 2 {
println(a, b) // 3 4
}
println(a, b) // 0 1
if判断语句类型断言
// 如果传入x类型为int,则可以直接获取其值
a := x.(int)
println(a)
// 如果传入x类型不是byte,则会产生恐慌panic
b := x.(byte)
println(b)
a := x.(int)
println(a)
// 如果传入x类型不是byte,则会产生恐慌panic
b := x.(byte)
println(b)
分支选择 switch
switch存在分支选择对象时,case分支支持单个常量、常量列表
switch x {
case 0:
println("single const")
case 1, 2, 3:
println("const list")
default:
println("default")
}
case 0:
println("single const")
case 1, 2, 3:
println("const list")
default:
println("default")
}
分支选择对象之前可以有一个简单语句,case语句的大括号可以省略
switch x *= 2; x {
case 4: {
println("single const")
}
case 5, 6, 7: {
println("const list")
}
default: {
println("default")
}
}
case 4: {
println("single const")
}
case 5, 6, 7: {
println("const list")
}
default: {
println("default")
}
}
switch只有一个简单语句,没有分支选择对象时,case分支支持逻辑表达式语句
switch x /= 3; {
case x == 8:
println("expression")
case x >= 9:
println("expression")
default:
println("default")
}
case x == 8:
println("expression")
case x >= 9:
println("expression")
default:
println("default")
}
switch没有简单语句,没有分支选择对象时,case分支支持逻辑表达式语句
switch {
case x == 10:
println("expression")
case x >= 11:
println("expression")
default:
println("default")
}
case x == 10:
println("expression")
case x >= 11:
println("expression")
default:
println("default")
}
switch类型分支,只能在switch语句中使用的.(type)获取对象的类型
switch中每个case分支默认带有break效果,一个分支执行后就跳出switch,不会自动向下执行其他case
使用fallthrough强制向下继续执行后面的case代码
在类型分支中不允许使用fallthrough语句
循环语句 for
Go只有一种循环结构:for 循环
可以让前置(初始化)、中间(条件)、后置(迭代)语句为空,或者全为空
for i := 0; i < 10; i++ {...}
for i := 0; i < 10; {...} // 省略迭代语句
for i := 0; ; i++; {...} // 省略条件语句
for ; i < 10; i++ {...} // 省略初始化语句
for i := 0; ; {...} // 省略条件和迭代语句, 分号不能省略
for ; i < 10; {...} // 省略初始化和迭代语句, 分号可省略
for ; ; i++ {...} // 省略初始化和条件语句, 分号不能省略
for i < 10 {...}
for ; ; {...} // 分号可省略
for {...}
for i := 0; i < 10; {...} // 省略迭代语句
for i := 0; ; i++; {...} // 省略条件语句
for ; i < 10; i++ {...} // 省略初始化语句
for i := 0; ; {...} // 省略条件和迭代语句, 分号不能省略
for ; i < 10; {...} // 省略初始化和迭代语句, 分号可省略
for ; ; i++ {...} // 省略初始化和条件语句, 分号不能省略
for i < 10 {...}
for ; ; {...} // 分号可省略
for {...}
for语句中小括号 ( )是可选的,而大括号 { } 是必须的
Go的for each循环for range
a := [5]int{2, 3, 4, 5, 6}
for k, v := range a {
fmt.Println(k, v) // 输出:0 2, 1 3, 2 4, 3 5, 4 6
}
for k := range a {
fmt.Println(k) // 输出:0 1 2 3 4
}
for _ = range a {
fmt.Println("print without care about the key and value")
}
for k, v := range a {
fmt.Println(k, v) // 输出:0 2, 1 3, 2 4, 3 5, 4 6
}
for k := range a {
fmt.Println(k) // 输出:0 1 2 3 4
}
for _ = range a {
fmt.Println("print without care about the key and value")
}
循环的继续、中断、跳转
for k, v := range s {
if v == 3 {
continue // 结束本次循环,进入下一次循环中
} else if v == 5 {
break // 结束整个for循环
} else {
goto SOMEWHERE // 跳转到标签指定的代码处
}
}
if v == 3 {
continue // 结束本次循环,进入下一次循环中
} else if v == 5 {
break // 结束整个for循环
} else {
goto SOMEWHERE // 跳转到标签指定的代码处
}
}
for range只支持遍历数组、数组指针、slice、string、map、channel类型
通道选择 select
任意某个通信可以进行,它就执行,其他被忽略
Select用于当前goroutine从一组可能的通讯中选择一个进一步处理
如果任意一个通讯都可以进一步处理,则从中随机选择一个,执行对应的语句
否则在没有默认分支(default case)时,select语句则会阻塞,直到其中一个通讯完成
Go 不会重新对 channel 或值进行求值
select 每个 case 都必须是一个通信
要么是发送要么是接收
要么是发送要么是接收
ch1, ch2 := make(chan int), make(chan int)
// 因为没有值发送到select中的任一case的channel中,此select将会阻塞
select {
case <-ch1:
println("channel 1")
case <-ch2:
println("channel 2")
}
// 因为没有值发送到select中的任一case的channel中,此select将会阻塞
select {
case <-ch1:
println("channel 1")
case <-ch2:
println("channel 2")
}
ch1, ch2 := make(chan int), make(chan int)
// 因为没有值发送到select中的任一case的channel中,此select将会执行default分支
select {
case <-ch1:
println("channel 1")
case <-ch2:
println("channel 2")
default:
println("default")
}
// 因为没有值发送到select中的任一case的channel中,此select将会执行default分支
select {
case <-ch1:
println("channel 1")
case <-ch2:
println("channel 2")
default:
println("default")
}
select只会执行一次case分支的逻辑,与for组合使用实现多次遍历分支
func main() {
for {
select {
case <-time.Tick(time.Second):
println("Tick")
case <-time.After(5 * time.Second):
println("Finish")
default:
println("default")
time.Sleep(5e8)
}
}
}
for {
select {
case <-time.Tick(time.Second):
println("Tick")
case <-time.After(5 * time.Second):
println("Finish")
default:
println("default")
time.Sleep(5e8)
}
}
}
延迟执行 defer
defer语句调用函数,将调用的函数加入defer栈,栈中函数在defer所在的主函数返回时执行,执行顺序是先进后出/后进先出
package main
func main() {
defer print(0)
defer print(1)
defer print(2)
defer print(3)
defer print(4)
for i := 5; i <= 9; i++ {
defer print(i)
}
// 输出:9876543210
}
func main() {
defer print(0)
defer print(1)
defer print(2)
defer print(3)
defer print(4)
for i := 5; i <= 9; i++ {
defer print(i)
}
// 输出:9876543210
}
defer在函数返回后执行,可以修改函数返回值
package main
func main() {
println(f()) // 返回: 15
}
func f() (i int) {
defer func() {
i *= 5
}()
return 3
}
func main() {
println(f()) // 返回: 15
}
func f() (i int) {
defer func() {
i *= 5
}()
return 3
}
defer用于释放资源
释放锁
mu.Lock()
defer mu.Unlock()
defer mu.Unlock()
关闭channel
ch <- "hello"
defer close(ch)
defer close(ch)
关闭IO流
f, err := os.Open("file.xxx")
defer f.Close()
defer f.Close()
关闭数据库连接
db, err := sql.Open("mysql","user:password@tcp(127.0.0.1:3306)/hello")
if err != nil {
log.Fatal(err)
}
defer db.Close()
if err != nil {
log.Fatal(err)
}
defer db.Close()
defer用于恐慌的截获
panic用于产生恐慌,recover用于截获恐慌,recover只能在defer语句中使用, 直接调用recover是无效的
跳转语句 goto
goto用于在一个函数内部运行跳转到指定标签的代码处,不能跳转到其他函数中定义的标签
goto模拟循环
package main
func main() {
i := 0
loop:
i++
if i < 5 {
goto loop
}
println(i)
}
func main() {
i := 0
loop:
i++
if i < 5 {
goto loop
}
println(i)
}
阻塞语句
永久阻塞语句
// 向一个未初始化的channel中写入数据会永久阻塞
(chan int)(nil) <- 0
// 从一个未初始化的channel中读取数据会永久阻塞
<-(chan struct{})(nil)
for range (chan struct{})(nil){}
// select无任何选择分支会永久阻塞
select{}
(chan int)(nil) <- 0
// 从一个未初始化的channel中读取数据会永久阻塞
<-(chan struct{})(nil)
for range (chan struct{})(nil){}
// select无任何选择分支会永久阻塞
select{}
使用tips
面向对象设计
封装
继承
用组合实现;内嵌包含想要的行为(字段和方法)的类型;
数据和方法是一种松耦合的正交关系,可以不混在一起定义,数据和方法是独立的令具有更大的灵活性。
多重继承可以通过一个类型内嵌多个类型( 接口方法继承要内嵌类型的指针)来实现
多态
都是必须提供一个指定方法集的实现,但更加灵活通用;
不需要显式地声明类型是否满足某个接口,任何提供了接口方法实现代码的类型都隐式地实现了该接口,而不用显式地声明
命名规范
变量命名
变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:numShips 和 startDate。
但如果你的全局变量希望能够被外部包所使用,则需要将首个单词的首字母也大写
对于布尔值而言,建议以 is 或者 Is 开头的 isSorted、isFinished、isVisible
函数、方法命名
当某个函数需要被外部包调用的时候才使用大写字母开头,并遵循 Pascal 命名法;
否则就遵循骆驼命名法,即第一个单词的首字母小写,其余单词的首字母大写。
返回某个对象的函数或方法的名称一般都是使用名词,没有 Get... 之类的字符。
修改某个对象,则使用 SetName,有必须要的话可以使用大小写混合的方式,如 MixedCaps 或 mixedCaps,而不是使用下划线来分割多个名称。
接口类型
接口的名字由方法名加 [e]r 后缀组成,
例如 Printer、Reader、Writer、Logger、Converter 等等。
还有一些不常用的方式(当后缀 er 不合适时),比如 Recoverable,此时接口名以 able 结尾,或者以 I 开头(像Java 中那样)。
错误类型
错误类型以 “Error” 结尾
错误变量以 “err” 或 “Err” 开头。
panic
标准库中有许多包含Must 前缀的函数,像 regexp.MustComplie 和 template.Must;即当正则表达式或模板中转入的转换字符串导致错误时,这些函数会 panic
测试
编程规范
广泛使用一个自定义类型时,最好为它定义 String() 方法
它会被用在 fmt.Printf() 中生成默认的输出:等同于使用格式化描述符 %v 产生的输出
Go 语言中的接口都很简短,通常它们会包含 0 个、最多 3 个方法
Go 库的原则是在包的内部使用了 panic,在它的对外接口(API)中也必须用 recover 处理成返回显式的错误
1)在包内部,总是应该从 panic 中 recover:不允许显式的超出包范围的 panic ()
2)向包的调用者返回错误值(而不是 panic)
调试范例
//定义函数变量
where := log.Print
where := func() {
_, file, line, _ := runtime.Caller(1)
log.Printf("%s:%d", file, line)
}
//调用
where()
// some code
where()
// some more code
where()
where := log.Print
where := func() {
_, file, line, _ := runtime.Caller(1)
log.Printf("%s:%d", file, line)
}
//调用
where()
// some code
where()
// some more code
where()
0 条评论
下一页