golang学习笔记---函数、方法和接口
函数:对应操作序列,是程序的基本组成元素。
函数有具名和匿名之分:
具名函数一般对应于包级的函数,是匿名函数的一种特例,当匿名函数引用了外部
作用域中的变量时就成了闭包函数,闭包函数是函数式编程语言的核心。方法是绑
定到一个具体类型的特殊函数,Go语言中的方法是依托于类型的,必须在编译时静
态绑定
接口:定义了方法的集合,这些方法依托于运行时的接口对象,因此接口对
应的方法是在运行时动态绑定的。
Go程序函数启动顺序的示意图:

要注意的是,在 main.main 函数执行之前所有代码都运行在同一个goroutine,也
就是程序的主系统线程中。
因此,如果某个 init 函数内部用go关键字启动了新的
goroutine的话,新的goroutine只有在进入 main.main 函数之后才可能被执行到。
函数
在Go语言中,函数是第一类对象,我们可以将函数保持到变量中。函数主要有具名
和匿名之分,包级函数一般都是具名函数,具名函数是匿名函数的一种特例
// 具名函数
func Add(a, b int) int {
return a+b
}
// 匿名函数
var Add = func(a, b int) int {
return a+b
}
Go语言中的函数可以有多个参数和多个返回值,参数和返回值都是以传值的方式和
被调用者交换数据。在语法上,函数还支持可变数量的参数,可变数量的参数必须
是最后出现的参数,可变数量的参数其实是一个切片类型的参数。
// 多个参数和多个返回值
func Swap(a, b int) (int, int) {
return b, a
}
// 可变数量的参数
// more 对应 []int 切片类型
func Sum(a int, more ...int) int {
for _, v := range more {
a += v
}
return a
}
当可变参数是一个空接口类型时,调用者是否解包可变参数会导致不同的结果:
package main
import (
"fmt"
)
func main() {
var a = []interface{}{123, "abc"}
Print(a...) // 123 abc
Print(a) // [123 abc]
}
func Print(a ...interface{}) {
fmt.Println(a...)
}
第一个 Print 调用时传入的参数是 a... ,等价于直接调用 Print(123,
"abc") 。第二个 Print 调用传入的是未解包的 a ,等价于直接调
用 Print([]interface{}{123, "abc"}) 。
‘…’ 其实是go的一种语法糖。
它的第一个用法主要是用于函数有多个不定参数的情况,可以接受多个不确定数量的参数。
第二个用法是slice可以被打散进行传递。
不仅函数的参数可以有名字,也可以给函数的返回值命名:
func Find(m map[int]int, key int) (value int, ok bool) {
value, ok = m[key]
return
}
如果返回值命名了,可以通过名字来修改返回值,也可以通过 defer 语句
在 return 语句之后修改返回值:
func Inc() (v int) {
defer func(){ v++ } ()
return 42
}
其中 defer 语句延迟执行了一个匿名函数,因为这个匿名函数捕获了外部函数的
局部变量 v ,这种函数我们一般叫闭包。闭包对捕获的外部变量并不是传值方式
访问,而是以引用的方式访问。
闭包的这种引用方式访问外部变量的行为可能会导致一些隐含的问题:
package main import (
"fmt"
) func main() {
for i := 0; i < 3; i++ {
defer func() { fmt.Println(i) }()
}
}
// Output:
// 3
// 3
// 3
因为是闭包,在 for 迭代语句中,每个 defer 语句延迟执行的函数引用的都是
同一个 i 迭代变量,在循环结束后这个变量的值为3,因此最终输出的都是3。
修复的思路是在每轮迭代中为每个 defer 函数生成独有的变量。可以用下面两种
方式:
package main import (
"fmt"
) func main() {
for i := 0; i < 3; i++ {
i := i // 定义一个循环体内局部变量i
defer func() { fmt.Println(i) }()
}
}
package main import (
"fmt"
) func main() {
for i := 0; i < 3; i++ {
// 通过函数传入i
// defer 语句会马上对调用参数求值
defer func(i int) { fmt.Println(i) }(i)
}
}
不过一般来说,在 for 循环内部执行 defer 语句并不是一个好
的习惯,此处仅为示例,不建议使用。
每个goroutine刚启动时只会分配
很小的栈(4或8KB,具体依赖实现),根据需要动态调整栈的大小,栈最大可以
达到GB级(依赖具体实现,在目前的实现中,32位体系结构为250MB,64位体系结构为1GB)
Go语言中指针不再是固定不变的了(因此
不能随意将指针保持到数值变量中,Go语言的地址也不能随意保存到不在GC控制
的环境中,因此使用CGO时不能在C语言中长期持有Go语言对象的地址
方法
Go语言的方法关联到类型的,这样可以在编译阶段完成方法的静态绑定。
我们可以给任何自
定义类型添加一个或多个方法。每种类型对应的方法必须和类型的定义在同一个包
中,因此是无法给 int 这类内置类型添加方法的(因为方法的定义和类型的定义
不在一个包中)。对于给定的类型,每个方法的名字必须是唯一的,同时方法和函
数一样也不支持重载。
方法是由函数演变而来,只是将函数的第一个对象参数移动到了函数名前面了而
已。因此我们依然可以按照原始的过程式思维来使用方法。通过叫方法表达式的特
性可以将方法还原为普通类型的函数:
Go的接口类型是对其它类型行为的抽象和概括,
因为接口类型不会和特定的实现细
节绑定在一起,通过这种抽象的方式我们可以让对象更加灵活和更具有适应能力。
很多面向对象的语言都有相似的接口概念,但Go语言中接口类型的独特之处在于它
是满足隐式实现的鸭子类型。所谓鸭子类型说的是:只要走起路来像鸭子、叫起来
也像鸭子,那么就可以把它当作鸭子
fmt.Fprintf 函数的签
名如下:
func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)
其中 io.Writer 用于输出的接口, error 是内置的错误接口,
它们的定义如
下:
type io.Writer interface {
Write(p []byte) (n int, err error)
}
type error interface {
Error() string
}
我们可以通过定制自己的输出对象,将每个字符转为大写字符后输出
package main import (
"bytes"
"fmt"
"io"
"os"
) type UpperWriter struct {
io.Writer
} func (p *UpperWriter) Write(data []byte) (n int, err error) {
return p.Writer.Write(bytes.ToUpper(data))
}
func main() {
fmt.Fprintln(&UpperWriter{os.Stdout}, "hello, world")
}
golang学习笔记---函数、方法和接口的更多相关文章
- golang学习笔记--函数和方法
在go中,函数类型是一等类型,这意味着可以吧函数当做一个值来传递和使用. func divide(dividend int,divisor int)(int,error){ //省略部分代码 } 参数 ...
- golang学习笔记13 Golang 类型转换整理 go语言string、int、int64、float64、complex 互相转换
golang学习笔记13 Golang 类型转换整理 go语言string.int.int64.float64.complex 互相转换 #string到intint,err:=strconv.Ato ...
- golang学习笔记8 beego参数配置 打包linux命令
golang学习笔记8 beego参数配置 打包linux命令 参数配置 - beego: 简约 & 强大并存的 Go 应用框架https://beego.me/docs/mvc/contro ...
- golang学习笔记7 使用beego swagger 实现API自动化文档
golang学习笔记7 使用beego swagger 实现API自动化文档 API 自动化文档 - beego: 简约 & 强大并存的 Go 应用框架https://beego.me/doc ...
- golang学习笔记19 用Golang实现以太坊代币转账
golang学习笔记19 用Golang实现以太坊代币转账 在以太坊区块链中,我们称代币为Token,是以太坊区块链中每个人都可以任意发行的数字资产.并且它必须是遵循erc20标准的,至于erc20标 ...
- golang学习笔记14 golang substring 截取字符串
golang学习笔记14 golang substring 截取字符串golang 没有java那样的substring函数,但支持直接根据 index 截取字符串mystr := "hel ...
- golang学习笔记9 beego nginx 部署 nginx 反向代理 golang web
golang学习笔记9 beego nginx 部署 nginx 反向代理 golang web Nginx 部署 - beego: 简约 & 强大并存的 Go 应用框架https://bee ...
- golang学习笔记6 beego项目路由设置
golang学习笔记5 beego项目路由设置 前面我们已经创建了 beego 项目,而且我们也看到它已经运行起来了,那么是如何运行起来的呢?让我们从入口文件先分析起来吧: package main ...
- Java学习笔记之---方法和数组
Java学习笔记之---方法与数组 (一)方法 (1)什么是方法? 方法是解决一类问题的步骤的有序组合 方法包含于类或对象中 方法在程序中被创建,在其他地方被引用 (2)方法的优点 使程序变得更简短而 ...
随机推荐
- achartengine刷新数据
achartengine工具比較强大.偶在闲余时间玩了下,想通过achartengine来模拟股票线性图,于是就针对achartengine中线性图尝试效果,achartengine中包括了非常多图表 ...
- java注解Annotation的使用实例
1.注解的定义 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java. ...
- Oracle导入excel数据快速方法
Oracle导入excel数据快速方法 使用PLSQL Developer工具,这个可是大名鼎鼎的Oracle DBA最常使用的工具. 在单个文件不大的情况下(少于100000行),并且目的 ...
- 隐藏执行批处理bat文件
当kali成功溢出目标以后, 如果我们需要目标主机执行批处理bat的话, 目标机器会出现黑色的弹窗, 用户看到一个黑色弹窗肯定会奇怪, 以下有两种方法让bat文件运行起来: 1:使用bat转化为exe ...
- asp.net判断文件或文件夹是否存在
在上传文件时经常要判断文件夹是否存在,如果存在就上传文件,否则新建文件夹再上传文件 判断语句为 if (System.IO.Directory.Exists(Server.MapPath(" ...
- LintCode: Combination Sum
一个数可以使用多次 图: 节点:x(当前的和,当前要考虑的数a[i]) 边:x-> y1(当前的和,下一个要考虑的数a[i+1]) y2(当前的和+a[i],下一个要考虑的数a[i+1]) BF ...
- 关于testNG和JUnit的对比
关于选择JUnit还是选testNG,这几篇文章,建议读一读: API参考文档: Junit API文档:http://junit.org/junit4/javadoc/latest/index.ht ...
- 【DB】部分MySQL操作记录
工作中涉及到部分统计工作,恰好把之前的有些SQL再熟悉回顾一下. 一.涉及到时间统计部分: 求时间差: ), (SELECT CURDATE())) AS '试用时间'; ), (SELECT CUR ...
- 机器学习-分类器-Adaboost原理
Adaboost原理 Adaboost(AdaptiveBoosting)是一种迭代算法,通过对训练集不断训练弱分类器,然后把这些弱分类器集合起来,构成强分类器.adaboost算法训练的过程中,初始 ...
- Active Directoty域服务安装
运行dcpromo命令,打开“Active Directoty域服务安装向导”