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)方法的优点 使程序变得更简短而 ...
随机推荐
- Linux网络编程:基于UDP的程序开发回顾篇
基于无连接的UDP程序设计 同样,在开发基于UDP的应用程序时,其主要流程如下: 对于面向无连接的UDP应用程序在开发过程中服务端和客户端的操作流程基本差不多.对比面向连接的TCP程序,服务端少了 ...
- css背景全屏-视差
<!DOCTYPE html> <html> <head> <title></title> <style> *{margin:0 ...
- Linux下安装配置SNMP服务
一.安装snmp服务 1.检查系统是否已经安装snmp的rpm包 以下是安装snmp服务需要的rpm包: libsensors3-2.10.6-55.el5.i386.rpm lm_sensors-2 ...
- Android Webservices 返回多行多列数据(Dataset)
对于之前从事.net或者java开发人员,习惯了从后台获取网格数据(多行多列DataTable),但转行从事android开发,难免会不习惯 Android调用Webservice时,如果返回值是一个 ...
- js 判断是否是空对象
主要思路 我们要考虑到的主要有:js原生对象,宿主对象(浏览器上面的). 首先对于宿主对象 主要判断是DOM 对象 和是否是window 对象 是否是DOM对象 value.nodeType 是否存 ...
- MongoDB numa系列问题三:overcommit_memory和zone_reclaim_mode
内核参数overcommit_memory : 它是 内存分配策略 可选值:0.1.2.0:表示内核将检查是否有足够的可用内存供应用进程使用:如果有足够的可用内存,内存申请允许:否则,内存申请失败,并 ...
- SpringMVC学习笔记八:文件上传下载(转)
转自:http://www.cnblogs.com/WJ-163/p/6269409.html 一.关键步骤 ①引入核心JAR文件 SpringMVC实现文件上传,需要再添加两个jar包.一个是文件上 ...
- 〖Linux〗git push orgin master不能解析域名的解决方法
错误信息: $ git push origin master ssh: Could not resolve hostname bitbucket.org: Name or service not kn ...
- jqPlot图表插件学习之阴阳烛图
一.准备工作 首先我们需要到官网下载所需的文件: 官网下载(笔者选择的是jquery.jqplot.1.0.8r1250.zip这个版本) 然后读者需要根据自己的情况新建一个项目并且按照如下的方式加载 ...
- 10、java5线程池之返回结果的任务之Callable与Future
JDK文档描述Callable: public interface Callable<V>返回结果并且可能抛出异常的任务.实现者定义了一个不带任何参数的叫做 call 的方法. Calla ...