[TOC]
# 导包
1. 点操作 有时候会看到如下的方式导入包 import( . “fmt” )
这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调用的fmt.Println(“hello world”) 可以省略的写成Println(“hello world”)
2. 别名操作 别名操作顾名思义可以把包命名成另一个用起来容易记忆的名字
import( f “fmt” ) 别名操作调用包函数时前缀变成了重命名的前缀,即f.Println(“hello world”)
3. _操作 这个操作经常是让很多人费解的一个操作符,请看下面这个import
import ( “database/sql” _ “github.com/ziutek/mymysql/godrv” )
_操作其实只是引入该包。当导入一个包时,它所有的init()函数就会被执行,但有些时候并非真的需要使用这些包,仅仅是希望它的init()函数被执 行而已。这个时候就可以使用_操作引用该包了。即使用_操作引用包是无法通过包名来调用包中的导出函数,而是只是为了简单的调用其init函数()
# 函数定义
~~~
func 函数名(/*参数列表*/) (ol type1, ol2 type2 /*返回类型*/) {
//函数体
return v1, v2 //返回多个值
}
~~~
函数名小写为private,大写为public
参数列表不支持默认参数
返回类型,可以只有类型没有变量名
# 不定参数类型
参数个数为不定数量
~~~
...type只能作为函数的参数类型存在,并且是最后一个参数
~~~
~~~
func 函数名(args ...int) {
}
~~~
~~~
func MyFunc(args ...int) {
//获取用户传递参数的个数
fmt.Println("len(args) = ", len(args))
for i := 0; i<len(args); i++ {
fmt.Printf("args[%d] = %d\n", i, args[i])
}
fmt.Println("==========================")
//返回2个值一个是下标,一个是下标对应的数
for i, data := range args {
fmt.Printf("args[%d] = %d\n", i, data)
}
}
func main() {
MyFunc(1, 2, 3)
}
~~~
参数间传递
~~~
func MyFunc(args ...int) {
//返回2个值一个是下标,一个是下标对应的数
for i, data := range args {
fmt.Printf("args[%d] = %d\n", i, data)
}
fmt.Println("=============================")
}
func test(args ...int) {
MyFunc(args...)
//args[0]~args[2](不包括数字args[2])
MyFunc(args[:2]...)
//从args[2]开始(包括本身),把后面所有元素传递过去
MyFunc(args[2:]...)
}
func main() {
test(1, 2, 3)
}
~~~
# 多个返回值
~~~
func test() (int, int, int) {
return 1, 2, 3
}
func main() {
a, b, c := test()
fmt.Println(a, b, c)
}
~~~
~~~
func MaxAndMin(a, b int) (max, min int) {
if a > b{
max = a
min = b
} else {
max = b
min = a
}
return
}
func main() {
max, min := MaxAndMin(1, 2)
//通过匿名变量丢弃某个返回值
//max, _ := MaxAndMin(1, 2)
fmt.Print(max, min)
}
~~~
# 函数类型
函数也是一种类型,我们可以用type来定义他,他的类型就是所有拥有相同的参数,相同的返回值的一种类型
~~~
type FuncType func(int, int) int //声明一个函数类型, func后面没有函数名
func Add(a, b int) int {
return a + b
}
func main() {
var fTest FuncType
fTest = Add
result := fTest(10, 20)
fmt.Print(result)
}
~~~
# 函数作为参数
传递的函数要形参和返回值类型个数一样就可以
~~~
func sub(a, b int) int {
return a-b
}
func calc(a, b int, op func(int, int)int) int {
return op(a, b)
}
func main() {
sum := calc(300, 200, sub)
fmt.Println(sum)
}
~~~
# 回调函数
回调函数,函数里面有一个参数是函数类型
~~~
type FuncType func(int, int) int //声明一个函数类型, func后面没有函数名
func Add(a, b int) int {
return a + b
}
func Calc(a, b int, fTest FuncType) (result int) {
fmt.Println("calc")
result = fTest(a, b)
return
}
func main() {
a := Calc(1, 2, Add)
fmt.Println("a = ", a)
}
~~~
# defer
**如果有return,就是在reutrn之前执行**
延迟调用,main函数结束前
~~~
func main() {
defer fmt.Println("bbbbbb")
fmt.Println("aaaaaaaa")
}
~~~
输出
~~~
aaaaaaaa
bbbbbb
~~~
如果一个函数有多个defer,他们会以**后进先出**顺序执行,哪怕某个函数或某个延迟调用发生错误,这些调用依旧会被执行
~~~
func test(x int) int {
a := 100 / x
return a
}
func main() {
fmt.Println("aaaaaaaa")
defer fmt.Println("bbbbbb")
defer test(0)
defer fmt.Println("CCCCC")
defer fmt.Println("DDDDDD")
}
~~~
输出
~~~
aaaaaaaa
DDDDDD
CCCCC
bbbbbb
panic: runtime error: integer divide by zero
~~~
**defer和匿名函数结合使用**
~~~
func main() {
a := 10
b := 20
defer func() {
a = 12
b = 12
fmt.Printf("内部a = %d, b = %d\n", a, b)
}()
fmt.Printf("外部部a = %d, b = %d\n", a, b)
}
~~~
输出
~~~
外部部a = 10, b = 20
内部a = 12, b = 12
~~~
~~~
func main() {
a := 10
b := 20
defer func(a, b int) {
fmt.Printf("内部a = %d, b = %d\n", a, b)
}(a, b) //相当于提前放10和20
a = 11
b = 21
fmt.Printf("外部部a = %d, b = %d\n", a, b)
}
~~~
输出
~~~
外部部a = 11, b = 21
内部a = 10, b = 20
~~~
# 获取命令行参数
~~~
func main() {
//接收用户传递的参数,都是以字符串方式传递
list := os.Args
//长度
n := len(list)
fmt.Println(n)
for i, data := range list {
fmt.Printf("args[%d] = %s\n", i, data)
}
}
~~~
# 函数签名
在 Go 语言中,函数可是一等的(first-class)公民,函数类型也是一等的数据类型。这是什么意思呢?
简单来说,这意味着函数不但可以用于封装代码、分割功能、解耦逻辑,还可以化身为普通的值,在其他函数间传递、赋予变量、做类型判断和转换等等,就像切片和字典的值那样。
而更深层次的含义就是:函数值可以由此成为能够被随意传播的独立逻辑组件(或者说功能模块)。
![](https://box.kancloud.cn/03e73f14c70b0155066144ae9354b7c2_954x590.png)
这里,我先声明了一个函数类型,名叫Printer。
注意这里的写法,在类型声明的名称右边的是func关键字,我们由此就可知道这是一个函数类型的声明。
在func右边的就是这个函数类型的参数列表和结果列表。其中,参数列表必须由圆括号包裹,而只要结果列表中只有一个结果声明,并且没有为它命名,我们就可以省略掉外围的圆括号。
书写函数签名的方式与函数声明的是一致的。只是紧挨在参数列表左边的不是函数名称,而是关键字func。这里函数名称和func互换了一下位置而已。
> 函数的签名其实就是函数的参数列表和结果列表的统称,它定义了可用来鉴别不同函数的那些特征,同时也定义了我们与函数交互的方式。
注意,各个参数和结果的名称不能算作函数签名的一部分,甚至对于结果声明来说,没有名称都可以。
只要两个函数的参数列表和结果列表中的元素顺序及其类型是一致的,我们就可以说它们是一样的函数,或者说是实现了同一个函数类型的函数。
严格来说,函数的名称也不能算作函数签名的一部分,它只是我们在调用函数时,需要给定的标识符而已。
我在下面声明的函数printToStd的签名与Printer的是一致的,因此前者是后者的一个实现,即使它们的名称以及有的结果名称是不同的。
- 基础
- 简介
- 主要特征
- 变量和常量
- 编码转换
- 数组
- byte与rune
- big
- sort接口
- 和mysql类型对应
- 函数
- 闭包
- 工作区
- 复合类型
- 指针
- 切片
- map
- 结构体
- sync.Map
- 随机数
- 面向对象
- 匿名组合
- 方法
- 接口
- 权限
- 类型查询
- 异常处理
- error
- panic
- recover
- 自定义错误
- 字符串处理
- 正则表达式
- json
- 文件操作
- os
- 文件读写
- 目录
- bufio
- ioutil
- gob
- 栈帧的内存布局
- shell
- 时间处理
- time详情
- time使用
- new和make的区别
- container
- list
- heap
- ring
- 测试
- 单元测试
- Mock依赖
- delve
- 命令
- TestMain
- path和filepath包
- log日志
- 反射
- 详解
- plugin包
- 信号
- goto
- 协程
- 简介
- 创建
- 协程退出
- runtime
- channel
- select
- 死锁
- 互斥锁
- 读写锁
- 条件变量
- 嵌套
- 计算单个协程占用内存
- 执行规则
- 原子操作
- WaitGroup
- 定时器
- 对象池
- sync.once
- 网络编程
- 分层模型
- socket
- tcp
- udp
- 服务端
- 客户端
- 并发服务器
- Http
- 简介
- http服务器
- http客户端
- 爬虫
- 平滑重启
- context
- httptest
- 优雅中止
- web服务平滑重启
- beego
- 安装
- 路由器
- orm
- 单表增删改查
- 多级表
- orm使用
- 高级查询
- 关系查询
- SQL查询
- 元数据二次定义
- 控制器
- 参数解析
- 过滤器
- 数据输出
- 表单数据验证
- 错误处理
- 日志
- 模块
- cache
- task
- 调试模块
- config
- 部署
- 一些包
- gjson
- goredis
- collection
- sjson
- redigo
- aliyunoss
- 密码
- 对称加密
- 非对称加密
- 单向散列函数
- 消息认证
- 数字签名
- mysql优化
- 常见错误
- go run的错误
- 新手常见错误
- 中级错误
- 高级错误
- 常用工具
- 协程-泄露
- go env
- gometalinter代码检查
- go build
- go clean
- go test
- 包管理器
- go mod
- gopm
- go fmt
- pprof
- 提高编译
- go get
- 代理
- 其他的知识
- go内存对齐
- 细节总结
- nginx路由匹配
- 一些博客
- redis为什么快
- cpu高速缓存
- 常用命令
- Go 永久阻塞的方法
- 常用技巧
- 密码加密解密
- for 循环迭代变量
- 备注
- 垃圾回收
- 协程和纤程
- tar-gz
- 红包算法
- 解决golang.org/x 下载失败
- 逃逸分析
- docker
- 镜像
- 容器
- 数据卷
- 网络管理
- 网络模式
- dockerfile
- docker-composer
- 微服务
- protoBuf
- GRPC
- tls
- consul
- micro
- crontab
- shell调用
- gorhill/cronexpr
- raft
- go操作etcd
- mongodb