实数
三个语句效果一样:
days := 365.2425
var days=365.2425
var days float64 = 365.2425 //只要含有小数部分,那么它的类型就是浮点类型float64
浮点类型
float64(双精度):64位、8字节、double(默认)。math操作的都是这种,首选!
float32(单精度):32位、4字节、单精度。应用于处理大量数据
如果数是指数形式,默认是float64
零值
未赋值默认是零
打印
Print、Println:尽可能的多显示几位小数
Printf:格式化输出,
fmt.Printf("%v\n",an) %v:通用格式。%f:浮点。 %.3f:小数后三位。
%T:打印数据类型。
%x:打印十六进制
%4.3f:小数点后三位,总体长度最少4位(包括小数点)不足则左边填充空格,超过不舍弃
%04.3f:小数点后三位,总体长度最少4位(包括小数点)不足则左边填充0,超过不舍弃
%b:打印二进制。 %08b:宽度8位
an:=32
fmt.Print(an+32,"F\n") //64F
%+v:把字段名显示出来
浮点类型精度不准
a:=0.1
a+=0.2
fmt.Println(a==0.3) //false
fmt.Println(math.Abs(a-0.3)<0.0001) //true
go cap和len的区别
首先要搞清楚容量和长度的区别:
容量是指底层数组的大小,长度指可以使用的大小
容量的用处在哪?在与当你用 appen d扩展长度时,如果新的长度小于容量,不会更换底层数组,否则,go 会新申请一个底层数组,拷贝这边的值过去,把原来的数组丢掉。也就是说,容量的用途是:在数据拷贝和内存申请的消耗与内存占用之间提供一个权衡。
而长度,则是为了帮助你限制切片可用成员的数量,提供边界查询的。所以用 make 申请好空间后,需要注意不要越界【越 len 】
整数类型
10种类型,取值范围有8种
有符号:int 可能是32位或者64位
无符号:uint可能是32位或者64位
大量数据用int64 uint 64。uint8表示颜色
Int8:-128-127
math.MaxInt16 //只针对于架构无关的
math.MinInt64
整数环绕
var m uint8=255//0-255
m++
fmt.Println(m) //0
十六进制
var b uint8= 0x00
包
time
future:=time.Unix(176362383612.0) //该时间到1970.1.1的秒数
math
b:=math.Trunc(a/10) //若a=18则b=1
os
os.Exit():程序退出
encoding/json
json 包的 Marshal 函数可以将 struct 中的数据转化为 JSON 格式。(例子 21.9)
•Marshal 函数只会对 struct 中被导出的字段(只有大写字母才能被导出)进行编码。
很大数
int64——》uint64--〉--》float64--〉big包
如果数是指数形式,默认是float64
big包
big.Int:较大的整数
a=big.NewInt(2828) //int64-->big,但括号内的数可能超过int64
所以:
a:=new(big.Int)
a.SetString("2828282828288282828",10) //10表示10进制
a.Div(a,b) //a=a/b
等式两边都为big.Int
big.Float:任意精度
big.Rat:分数
较大数值的常量
比较大的数直接使用
const distance= 99 //untyped 无类型
const di uint64 = 99 //uint64
类型间转换
连接两个字符串,使用+运算符号,但+前后类型须一致
整型和浮点型也不能混着用
a:=2
b:=float64(a)
无符号和有符号 不同大小 的整数类型之间需要转换
字符串转换
想把数值转换为string,前提是能转换为code point--》
-
strconv.Itoa(a)
-
Sprintf会返回一个string,相当于转成字符串
-
a, err := strconv.Atoi("10")
if err!= nil{ //nil相当于null,如果为nil相当于没错误
fmt.Println(err.Error())
}
布尔类型的转换
不能使用string(false) int(false) bool(1) bool("tesm")
1和0不是true或false
函数
声明使用func关键字,函数的参数可以多个参数
func Intn(n int) int
调用:1.包名.函数名(实参)
2.同一个包下直接:函数名(实参)
其他包可以调用大些开头的函数、变量或其他标志符
函数的参数如果多个参数类型都一样:func Ab(a,b int64) Time
函数的返回值也可以多个:func Atoi(s string) (i int, err error) 或func Atoi(s string) (int, error)
fmt.Println(a...interface{}) (n int ,err error) : ...表示参数数量可变 a的类型为interface{},为空接口,所有类型都实现这个,所以可以接受任何类型的参数
->fmt.Println(18,"seconds")
方法
type声明新类型
type a float64
var b a=20 //b为float64类型,a为类型别名
不同类型无法混用:var c float64 =10
b+=c //报错
type a float64
type e float64
关联:func (k a) d() e{
} //(k a) :只能有一个接收者,但不能是int float64等预类型, d():方法名,可以加参数或者不加
关联方法的调用:
var f a=20
g=f.d()
一等函数
函数遇到小括号才执行
声明函数类型
•为函数声明类型有助于精简和明确调用者的代码。
•例如:type sensor func() kelvin
•所以:func measureTemperature(samples int, s func() kelvin)
•可以精简为:func measureTemperature(samples int, s sensor)
闭包和匿名函数
Go 闭包的概念及使用细节_NGC_2070的博客-CSDN博客
Go中闭包实现_杨宣yx的博客-CSDN博客
•匿名函数就是没有名字的函数,在 Go 里也称作函数字面值。
•因为函数字面值需要保留外部作用域的变量引用,所以函数字面值都是闭包的。
•(例子)
•闭包(closure)就是由于匿名函数封闭并包围作用域中的变量而得名的。
函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。--》解决办法,闭包:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。
function f1(){var n=999;function f2(){alert(n); // 999}}
在上面的代码中,函数 f2 就被包括在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。但是反过来就不行,f2 内部的局部变量,对 f1 就是不可见的。
子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了嘛。
上面代码中的f2函数,就是闭包。
func(){
fmt.Println("abcd")}() //相当于直接执行
数组
•数组中的每个元素都可以通过[ ] 和一个从 0 开始的索引进行访问。(例子 16.1)
•数组的长度可由内置函数 len 来确定。
•在声明数组时,未被赋值元素的值是对应类型的零值。(例子)
•复合字面值(composite literal)是一种给复合类型初始化的紧凑语法。
•Go 的复合字面值语法允许我们只用一步就完成数组声明和数组初始化两步操作:
•
•可以在复合字面值里使用 … 作为数组的长度,这样 Go 编译器会为你算出数组的元素数量(例子)
•无论哪种方式,数组的长度都是固定的。
遍历数组
•for 循环
•使用 range
数组的复制
•无论数组赋值给新的变量还是将它传递给函数,都会产生一个完整的数组副本。(例子 16.6)
•数组也是一种值,函数通过值传递来接受参数。相当于也是复制一份,修改后不会改变原来数组的值,所以数组作为函数的参数就非常低效。(例子 16.7)
•数组的长度也是数组类型的一部分。
•尝试将长度不符的数组作为参数传递,将会报错。
•函数一般使用 slice 而不是数组作为参数。
Slice 指向数组的窗口
•假设 planets 是一个数组,那么 planets[0:4] 就是一个切片,它切分出了数组里前 4 个元素。(左闭右开)
•切分数组不会导致数组被修改,它只是创建了指向数组的一个窗口或视图,这种视图就是 slice 类型。
•忽略掉 slice 的起始索引,表示从数组的起始位置进行切分;
•忽略掉 slice 的结束索引,相当于使用数组的长度作为结束索引。
•注意:slice 的索引不能是负数。
•(例子 17.2)
•如果同时省略掉起始和结束索引,那就是包含数组所有元素的一个 slice。
•切分数组的语法也可以用于切分字符串(例子)
•切分字符串时,索引代表的是字节数而非 rune 的数。(例子)
•Go 里面很多函数都倾向于使用 slice 而不是数组作为参数。
•想要获得与底层数组相同元素的 slice,那么可以使用 [:] 进行切分
•切分数组并不是创建 slice 唯一的方法,可以直接声明 slice:
•例如ab:=[]string{"a","b","c',"d"}
append 函数
•append 函数也是内置函数,它可以将元素添加到 slice 里面。
a=append(a,"b")
a=append(a,"b","c","d","e")
b:= []string{"b","c","d","e","b","c","d","e"}
c:= a[0:4]
d:=append(b,"f") //此时b:"b","c","d","e","f","c","d","e" d:"b","c","d","e","f"
使用 make 函数对 slice 进行预分配
•当 slice 的容量不足以执行 append 操作时,Go 必须创建新数组并复制旧数组中的内容。
•但通过内置的 make 函数,可以对 slice 进行预分配策略。
•尽量避免额外的内存分配和数组复制操作。
a:=make([]string,10)//长度和容量都为10
a:=make([]string,1,10)//长度为1,容量为10
声明可变参数的函数
•声明 Printf、append 这样的可变参数函数,需要在函数的最后一个参数前面加上 … 符号。
func terraform(prefix string, worlds ...string) []string {
newWorlds := make([]string, len(worlds))
for i := range worlds {
newWorlds[i] = prefix + " " + worlds[i]
}
return newWorlds
}
func main() {
twoWorlds := terraform("New", "Venus", "Mars")
fmt.Println(twoWorlds)
planets := []string{"Venus", "Mars", "Jupiter"}
newPlanets := terraform("New", planets...)
fmt.Println(newPlanets)
}
go machinery框架https://github.com/RichardKnop/machinery
goroutine
go语言:go(goroutine)如何控制异步_你是我的幸运儿的博客-CSDN博客_go 异步的异步&spm=1018.2226.3001.4187
sync.WaitGroup用法说明_你是我的幸运儿的博客-CSDN博客
go 运行错误expected 'package', found 'EOF'解决
只要将文件保存一下,再运行就ok了。
在vscode 插件搜索code runner, 然后安装
安装成功之后重启vscode就会发现右上角有一个三角的图标,点击就能运行程序啦。
goroutine
•只需在调用前面加一个 go 关键字。
time.second表示1秒
main函数主线路走完之后无论其他分支线路(goroutine)是否走完,都直接结束,所以要WaitGroup
•每次使用 go 关键字都会产生一个新的 goroutine。
•表面上看,goroutine 似乎在同时运行,但由于计算机处理单元有限,其实技术上来说,这些 goroutine 不是真的在同时运行。
•计算机处理器会使用“分时”技术,在多个 goroutine 上轮流花费一些时间。
在使用 goroutine 时,各个 goroutine 的执行顺序无法确定。
•向 goroutine 传递参数就跟向函数传递参数一样,参数都是按值传递的(传入的是副本)
通道 channel
•通道(channel)可以在多个 goroutine 之间安全的传值。
•通道可以用作变量、函数参数、结构体字段…
•创建通道用 make 函数,并指定其传输数据的类型
• c := make(chan int) //chan是关键字
•Go 允许在没有值可供发送的情况下通过 close 函数关闭通道
•例如 close(c)
•通道被关闭后无法写入任何值,如果尝试写入将引发 panic。
•尝试读取被关闭的通道会获得与通道类型对应的零值。
•注意:如果循环里读取一个已关闭的通道,并没检查通道是否关闭,那么该循环可能会一直运转下去,耗费大量 CPU 时间
•执行以下代码可得知通道是否被关闭:
•v, ok := <- c
常用模式
•从通道里面读取值,直到它关闭为止。
•可以使用 range 关键字达到该目的
通道 channel 发送、接收
•通道(channel)可以在多个 goroutine 之间安全的传值。
•通道可以用作变量、函数参数、结构体字段…
•创建通道用 make 函数,并指定其传输数据的类型
• c := make(chan int)
•使用左箭头操作符 <- 向通道发送值 或 从通道接收值
•向通道发送值:c <- 99
•从通道接收值:r := <- c
•发送操作会等待直到另一个 goroutine 尝试对该通道进行接收操作为止。
•执行发送操作的 goroutine 在等待期间将无法执行其它操作
•未在等待通道操作的 goroutine 让然可以继续自由的运行
•执行接收操作的 goroutine 将等待直到另一个 goroutine 尝试向该通道进行发送操作为止。
使用 select 处理多个通道
•等待不同类型的值。
•time.After 函数,返回一个通道,该通道在指定时间后会接收到一个值(发送该值的 goroutine 是 Go 运行时的一部分)。
timeout:=time.After(2*time.Second)
•select 和 switch 有点像。
•该语句包含的每个 case 都持有一个通道,用来发送或接收数据。
•select 会等待直到某个 case 分支的操作就绪,然后就会执行该 case 分支。
•(例子 30.5、30.6)
•注意:即使已经停止等待 goroutine,但只要 main 函数还没返回,仍在运行的 goroutine 将会继续占用内存。
time.Sleep(time.Duration(rand.Int(4000))*time.Millisecond)
•select 语句在不包含任何 case 的情况下将永远等下去。
nil 通道
•如果不使用 make 初始化通道,那么通道变量的值就是 nil(零值)
•对 nil 通道进行发送或接收不会引起 panic,但会导致永久阻塞。
•对 nil 通道执行 close 函数,那么会引起 panic
•nil 通道的用处:
•对于包含 select 语句的循环,如果不希望每次循环都等待 select 所涉及的所有通道,那么可以先将某些通道设为 nil,等到发送值准备就绪之后,再将通道变成一个非 nil 值并执行发送操作。
阻塞和死锁
•当 goroutine 在等待通道的发送或接收时,我们就说它被阻塞了。
•除了 goroutine 本身占用少量的内存外,被阻塞的 goroutine 并不消耗任何其它资源。
•goroutine 静静的停在那里,等待导致其阻塞的事情来解除阻塞。
•当一个或多个 goroutine 因为某些永远无法发生的事情被阻塞时,我们称这种情况为死锁。而出现死锁的程序通常会崩溃或挂起。
•(引发死锁的例子):
c:=make(chan int)
⬅️c
Golang错误处理机制(error 与 panic)
Golang错误处理机制(error 与 panic)_Marvellous丶的博客-CSDN博客_panic和error和error区别&spm=1018.2226.3001.4187
并发状态
•共享值
•竞争条件(race condition)
同时读不会影响
Go 的互斥锁(mutex)
•mutex = mutual exclusive
•Lock(),Unlock()
•sync 包
•(例子 31.1)
•互斥锁定义在被保护的变量之上
package main
import "sync"
var mu sync.Mutex
func main() {
mu.Lock()
defer mu.Unlock() //defer:整个main函数返回时才把锁放回去
}
Golang中的defer关键字的用法、原理以及它的坑
Golang中的defer关键字的用法、原理以及它的坑_会写代码的鱼的博客-CSDN博客关键字&spm=1018.2226.3001.4187
GO--defer关键字的作用_噢可爱滴蓝晶晶的博客-CSDN博客关键字&spm=1018.2226.3001.4187
在golang当中,defer代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用。因此,defer通常用来释放函数内部变量.通常用来释放一些需要释放的额资源,例如文件句柄等。
defer使用的三条规则: 1、当defer被声明时,其参数就会被实时解析 2、 defer执行顺序为先进后出 3、defer可以读取有名返回值
互斥锁的隐患
•死锁
•为保证互斥锁的安全使用,我们须遵守以下规则:
1.尽可能的简化互斥锁保护的代码
2对每一份共享状态只使用一个互斥锁
长时间运行的工作进程
•工作进程(worker)
•通常会被写成包含 select 语句的 for 循环。
for{
select{
}
}
range
Go语言中的range关键字使用起来非常的方便,它允许你遍历某个slice或者map,并通过两个参数(index和value),分别获取到slice或者map中某个元素所在的index以及其值。
比如像这样的用法:
for index, value := range mySlice {undefined fmt.Println("index: " + index) fmt.Println("value: " + value) } range遍历map时顺序无法保证
事件循环和 goroutine
•事件循环(event loop)
•中心循环(central loop)
•Go 通过提供 goroutine 作为核心概念,消除了对中心循环的需求。
map
•声明 map,必须指定 key 和 value 的类型:
•数组、int、float64 等类型在赋值给新变量或传递至函数/方法的时候会创建相应的副本
•但 map 不会,即传递时或赋值时map和其赋值的元素是指向同一个地址
使用 make 函数对 map 进行预分配
•除非你使用复合字面值来初始化 map,否则必须使用内置的 make 函数来为 map 分配空间。
•创建 map 时,make 函数可接受一个或两个参数
•第二个参数用于为指定数量的 key 预先分配空间
使用 make 函数创建的 map 的初始长度为 0,及len
将 map 用作 set
•Set 这种集合与数组类似,但元素不会重复
Go 语言里没有提供 set 集合
用map的key不重复这个特性:set:=make(map[float64]bool)
结构类型(struct)
•为了将分散的零件组成一个完整的结构体,Go 提供了 struct 类型。
•struct 允许你将不同类型的东西组合在一起。
•声明结构(例子21.1)
有点像java里面的类(class)
变量(只能在声明范围内用)
var curiosity struct {
lat float64
long float64
}
类型(可复用)
type location struct {
name string
lat float64
long float64
}
通过复合字面值初始化 struct
•两种形式:
•通过成对的字段和值进行初始化(例子 21.3)
•按字段定义的顺序进行初始化(例子 21.4)
•
•打印 struct(例子 21.5)
•%v,打印出 {-4.5895 137.4417}
%+v,打印出 {lat:-4.5895 long:137.4417}
struct的赋值相当于struct 的复制,互不影响
将 struct 编码为 JSON
•JSON(JavaScript Object Notation,JavaScript 对象表示法)
•常用于 Web API
•
•json 包的 Marshal 函数可以将 struct 中的数据转化为 JSON 格式。(例子 21.9)
•Marshal 函数只会对 struct 中被导出的字段(只有大写字母才能被导出)进行编码。
•Go 语言中的 json 包要求 struct 中的字段必须以大写字母开头,类似 CamelCase 驼峰型命名规范。
•但有时候我们需要 snake_case 蛇形命名规范,那么该怎么办?
•可以为字段注明标签,使得 json 包在进行编码的时候能够按照标签里的样式修改字段名。(例子 21.10):type location struct {
json:"latitude" xml:" "
json:"longitude"
}
将方法关联到 struct
•方法可以被关联到你声明的类型上(例子 22.1,22.2)
构造函数
•可以使用 struct 复合字面值来初始化你所要的数据。
•但如果 struct 初始化的时候还要做很多事情,那就可以考虑写一个构造用的函数。(例子 22.3)
•Go 语言没有专用的构造函数,但以 new 或者 New 开头(new本包使用,New其他包使用)的函数,通常是用来构造数据的。例如 newPerson(),NewPerson()
•(例子)
New 函数
•有一些用于构造的函数的名称就是 New(例如 errors 包里面的 New 函数)。
•这是因为函数调用时使用 包名**.函数名** 的形式。
•如果该函数叫 NewError,那么调用的时候就是 errors.NewError(),这就不如 errors.New() 简洁
rune
Golang rune数据类型_小镇程序员的博客-CSDN博客_golang rune类型
golang ----rune数据类型_weixin_33901641的博客-CSDN博客
组合
•在面向对象的世界中,对象由更小的对象组合而成。
•术语:对象组合或组合
•Go 通过结构体实现组合(composition)。
•Go 提供了“嵌入”(embedding)特性,它可以实现方法的转发(forwarding)
•组合是一种更简单、灵活的方式。
转发方法
•Go 可以通过 struct 嵌入 来实现方法的转发。
•在 struct 中只给定字段类型,不给定字段名即可。(例子 23.4)会自动生成跟字段类型一样的字段名
•在 struct 中,可以转发任意类型。(例子 23.5)
type report struct {
int
temperature
location
}
继承 还是 组合?
•Favor object composition over class inheritance.
•优先使用对象组合而不是类的继承。
•
•Use of classical inheritance is always optional; every problem that it solves can be solved another way.
•对传统的继承不是必需的;所有使用继承解决的问题都可以通过其它方法解决。
接口
•接口关注于类型可以做什么,而不是存储了什么。
•接口通过列举类型必须满足的一组方法来进行声明。
•在 Go 语言中,不需要显式声明接口。
•(例子 24.1)(例子 24.2)(例子 24.3)
•Go 语言的接口都是隐式满足的。
•(例子 24.8)(例子 24.9)(例子 24.10)(例子 24.11)
接口类型
•为了复用,通常会把接口声明为类型。
•按约定,接口名称通常以 er 结尾。
•(例子 24.4)(例子 24.5)(例子 24.6)
•
•接口可以与 struct 嵌入 特性一同使用。(例子 24.7)
•同时使用组合和接口将构成非常强大的设计工具。
满足接口
•Go 标准库导出了很多只有单个方法的接口。
•Go 通过简单的、通常只有单个方法的接口……来鼓励组合而不是继承,这些接口在各个组件之间形成了简明易懂的界限。
---- Rob Pike
•例如 fmt 包声明的 Stringer 接口:
•
•(例子 24.12)
•标准库中常用接口还包括:io.Reader,io.Writer,json.Marshaler...
指针
& 和 * 符号
•变量会将它们的值存储在计算机的 RAM 里,存储位置就是该变量的内存地址。
•& 表示地址操作符,通过 & 可以获得变量的内存地址。(例子 26.1)
•& 操作符无法获得字符串/数值/布尔字面值的地址。
•&42,&“hello”这些都会导致编译器报错
•* 操作符与 & 的作用相反,它用来解引用,提供内存地址指向的值。(例子 26.2)
•C 语言中的内存地址可以通过例如 address++ 这样的指针运算进行操作,但是在 Go 里面不允许这种不安全操作。
指针类型
•指针存储的是内存地址。(例子 26.3)
•指针类型和其它普通类型一样,出现在所有需要用到类型的地方,如变量声明、函数形参、返回值类型、结构体字段等。(例子 26.4)
•将 * 放在类型前面表示声明指针类型
•将 * 放在变量前面表示解引用操作
•两个指针变量持有相同的内存地址,那么它们就是相等。
指向结构的指针
•与字符串和数值不一样,复合字面量的前面可以放置 &。(例子 26.6)
访问字段时,对结构体进行解引用并不是必须的。(例子 26.7)
指向数组的指针
•和结构体一样,可以把 & 放在数组的复合字面值前面来创建指向数组的指针。
•(例子 26.8)
•数组在执行索引或切片操作时会自动解引用。没有必要写 (*superpower)[0] 这种形式。
•与 C 语言不一样,Go 里面数组和指针是两种完全独立的类型。
•Slice 和 map 的复合字面值前面也可以放置 & 操作符,但是 Go 并没有为它们提供自动解引用的功能。
实现修改
•Go 语言的函数和方法都是按值传递参数的,这意味着函数总是操作于被传递参数的副本。
•当指针被传递到函数时,函数将接收传入的内存地址的副本。之后函数可以通过解引用内存地址来修改指针指向的值。
虽然是传的是副本,但是修改还是会修改原来的值,因为指的是同一个地址。
指针接收者
•方法的接收者和方法的参数在处理指针方面是很相似的。
•(例子 26.11)
•(例子 26.12)
•Go 语言在变量通过点标记法进行调用的时候,自动使用 & 取得变量的内存地址。
•所以不用写 (&nathan).birthday() 这种形式也可以正常运行。
•(例子 26.13)
(例子 26.14)
内部指针
•Go 语言提供了 内部指针 这种特性。
•它用于确定结构体中指定字段的内存地址。
•(例子 26.15)
•& 操作符不仅可以获得结构体的内存地址,还可以获得结构体中指定字段的内存地址。
•(例子 26.16)
修改数组
(例子 26.17)函数通过指针对数组的元素进行修改。
隐式的指针
•Go 语言里一些内置的集合类型就在暗中使用指针。
•map 在被赋值或者呗作为参数传递的时候不会被复制。
•map 就是一种隐式指针。
•这种写法就是多此一举:func demolish(planets *map[string]string)
•map 的键和值都可以是指针类型
需要将指针指向 map 的情况并不多见
slice 指向数组
•之前说过 slice 是指向数组的窗口,实际上 slice 在指向数组元素的时候也使用了指针。
•每个 slice 内部都会被表示为一个包含 3 个元素的结构,它们分别指向:
•数组的指针
•slice 的容量
•slice 的长度
•当 slice 被直接传递至函数或方法时,slice 的内部指针就可以对底层数据进行修改。
•指向 slice 的显式指针的唯一作用就是修改 slice 本身:slice 的长度、容量以及起始偏移量。
•(例子 26.18)
指针和接口
•(例子 26.19)
•本例中,无论 martian 还是指向 martian 的指针,都可以满足 talker 接口。
•如果方法使用的是指针接收者,那么情况会有所不同。
•(例子 26.20)
nil
•Nil 是一个名词,表示“无”或者“零”。
•在 Go 里,nil 是一个零值。
•如果一个指针没有明确的指向,那么它的值就是 nil
•除了指针,nil 还是 slice、map 和接口的零值。
•Go 语言的 nil,比以往语言中的 null 更为友好,并且用的没那么频繁,但是仍需谨慎使用。