m := map[string]int {"a":1, "b":2}其实是make的语法糖,它等价于
var m map[string]int = make(map[string]int, 2)
m = {"a":1, "b":2}内建函数 make 用来为 slice,map 或 chan 类型分配内存和初始化一个对象(注意:只能用在这三种类型上),跟 new 类似,第一个参数也是一个类型而不是一个值,跟 new 不同的是,make 返回类型的引用而不是指针,而返回值也依赖于具体传入的类型。
make的作用是初始化内置的数据结构,也就是我们在前面提到的切片、哈希表和 Channelnew的作用是根据传入的类型分配一片内存空间并返回指向这片内存空间的指针
Channel
func f1(c chan int, x int) // 无向channel
func f2(c chan<- int, x int) // 只写channel,函数内只能向channel写数据,不能读,send-only channel
func f3(c <-chan int, x int) // 只读channel,receive-only channelUnbuffered channel
一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作,当发送的值通过Channels成功传输之后,两个goroutine可以继续执行后面的语句。反之,如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。
Goroutine
Hint
main函数也是一个goroutine,称之为主goroutine.
不管一个channel是否被关闭,当它没有被引用时将会被Go语言的垃圾自动回收器回收。(不要将关闭一个打开文件的操作和关闭一个channel操作混淆。对于每个打开的文件,都需要在不使用的时候调用对应的Close方法来关闭文件。)
select
C 语言的 select 系统调用可以同时监听多个文件描述符的可读或者可写的状态,Go 语言中的 select 也能够让 Goroutine 同时等待多个 Channel 可读或者可写,在多个文件或者 Channel状态改变之前,select 会一直阻塞当前线程或者 Goroutine。
select 是与 switch 相似的控制结构,与 switch 不同的是,select 中虽然也有多个 case,但是这些 case 中的表达式必须都是 Channel 的收发操作。
在通常情况下,select 语句会阻塞当前 Goroutine 并等待多个 Channel 中的一个达到可以收发的状态。但是如果 select 控制结构中包含 default 语句,那么这个 select 语句在执行时会遇到以下两种情况:
- 当存在可以收发的 Channel 时,直接处理该 Channel 对应的
case; - 当不存在可以收发的 Channel 时,执行
default中的语句;
runtime.LockOSThread 的意义
With the Go threading model, calls to C code, assembler code, or blocking system calls occur in the same thread as the calling Go code, which is managed by the Go runtime scheduler.
The
os.LockOSThread()mechanism is mostly useful when Go has to interface with some foreign library (a C library for instance). It guarantees that several successive calls to this library will be done in the same thread.This is interesting in several situations:
- a number of graphic libraries (OS X Cocoa, OpenGL, SDL, …) require all the calls to be done on a specific thread (or the main thread in some cases).
- some foreign libraries are based on thread local storage (TLS) facilities. They store some context in a data structure attached to the thread. Or some functions of the API provide results whose memory lifecycle is attached to the thread. This concept is used in both Windows and Unix-like systems. A typical example is the errno global variable commonly used in C libraries to store error codes. On systems supporting multi-threading, errno is generally defined as a thread-local variable.
- more generally, some foreign libraries may use a thread identifier to index/manage internal resources.
接口
先看几点说明,cf. https://www.topgoer.com/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/%E6%8E%A5%E5%8F%A3.html
接口是一个或多个方法签名的集合。 任何类型的方法集中只要拥有该接口’对应的全部方法’签名。 就表示它 “实现” 了该接口,无须在该类型上显式声明实现了哪个接口。 这称为Structural Typing。 所谓对应方法,是指有相同名称、参数列表 (不包括参数名) 以及返回值。 当然,该类型还可以有其他方法。
- 接口只有方法声明,没有实现,没有数据字段。
- 接口可以匿名嵌入其他接口,或嵌入到结构中。
- 对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针。
- 只有当接口存储的类型和对象都为nil时,接口才等于nil。
- 接口调用不会做receiver的自动转换。
- 接口同样支持匿名字段方法。
- 接口也可实现类似OOP中的多态。
- 空接口可以作为任何类型数据的容器。
- 一个类型可实现多个接口。
- 接口命名习惯以 er 结尾。
type Cat struct {}
type Sayer interface {
Say()
}
func (c *Cat) Say() { println("meow") }
var c Cat = Cat{}
c.Say()
var iface Sayer = c // error: Cat does not implement Sayer (method Say has pointer receiver)
var iface Sayer = &c // ok
c := &Cat{}
var iface Sayer := c // ok
iface = Sayer(c) // ok
// a := b 其实是 var a <b.Type> = b 的语法糖 如上,Cat以指针为接收器实现了Say,那么转换为接口类型时,必须以指针的形式传递对象,不能以结构体本身。
| 结构体实现接口 | 结构体指针实现接口 | |
|---|---|---|
| 结构体初始化变量 | 通过 | 不通过 |
| 结构体指针初始化变量 | 通过 | 通过 |
如果上面用结构体本身实现Say,那就随便怎么传,因为传指针可以通过解引用获得结构体。所以,以结构体实现接口,仍可以用指针传递给interface。
谨记,interface也是一种类型。在Go语言中接口(interface)是一种类型,一种抽象的类型。为了保护你的Go语言职业生涯,请牢记接口(interface)是一种类型。既然interface是一种类型,那必然有这种类型的变量,这就叫做接口类型变量。
接口类型变量
接口类型变量能够存储所有实现了该接口的实例。
// Sayer 接口
type Sayer interface {
say()
}
type dog struct {}
type cat struct {}
// dog实现了Sayer接口
func (d dog) say() {
fmt.Println("汪汪汪")
}
// cat实现了Sayer接口
func (c cat) say() {
fmt.Println("喵喵喵")
}
func main() {
var x Sayer // 声明一个Sayer类型的变量x
a := cat{} // 实例化一个cat
b := dog{} // 实例化一个dog
x = a // 可以把cat实例直接赋值给x
x.say() // 喵喵喵
x = b // 可以把dog实例直接赋值给x
x.say() // 汪汪汪
}值接收者和指针接收者实现接口的区别
我们有一个Mover接口和一个dog结构体。
type Mover interface {
move()
}
type dog struct {}值接收者实现接口
func (d dog) move() {
fmt.Println("狗会动")
}
func main() {
var x Mover
var wangcai = dog{} // 旺财是dog类型
x = wangcai // x可以接收dog类型
var fugui = &dog{} // 富贵是*dog类型
x = fugui // x可以接收*dog类型
x.move()
}从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是dog结构体还是结构体指针*dog类型的变量都可以赋值给该接口变量。因为Go语言中有对指针类型变量求值的语法糖,dog指针fugui内部会自动求值*fugui。
指针接收者实现接口
同样的代码我们再来测试一下使用指针接收者有什么区别:
func (d *dog) move() {
fmt.Println("狗会动")
}
func main() {
var x Mover
var wangcai = dog{} // 旺财是dog类型
x = wangcai // error: x不可以接收dog类型
var fugui = &dog{} // 富贵是*dog类型
x = fugui // x可以接收*dog类型
}空接口
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
空接口类型的变量可以存储任意类型的变量。
func main() {
// 定义一个空接口x
var x interface{}
s := "pprof.cn"
x = s
fmt.Printf("type:%T value:%v\n", x, x)
i := 100
x = i
fmt.Printf("type:%T value:%v\n", x, x)
b := true
x = b
fmt.Printf("type:%T value:%v\n", x, x)
}空接口的应用
空接口作为函数的参数
使用空接口实现可以接收任意类型的函数参数。
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}空接口作为map的值
使用空接口实现可以保存任意值的字典。
// 空接口作为map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "李白"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)类型断言
空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?
接口值
一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别称为接口的动态类型和动态值。
我们来看一个具体的例子:
var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil
请看下图分解:

想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:
x.(T)
其中:
x:表示类型为interface{}的变量
T:表示断言x可能是的类型。
该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败。
举个例子:
func main() {
var x interface{}
x = "pprof.cn"
v, ok := x.(string)
if ok {
fmt.Println(v)
} else {
fmt.Println("类型断言失败")
}
}上面的示例中如果要断言多次就需要写多个if判断,这个时候我们可以使用switch语句来实现:
func justifyType(x interface{}) {
switch v := x.(type) {
case string:
fmt.Printf("x is a string,value is %v\n", v)
case int:
fmt.Printf("x is a int is %v\n", v)
case bool:
fmt.Printf("x is a bool is %v\n", v)
default:
fmt.Println("unsupport type!")
}
}因为空接口可以存储任意类型值的特点,所以空接口在Go语言中的使用十分广泛。
关于接口需要注意的是,只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。