fmt.Println("i's value is ", i) fmt.Printf("i's type is %T, i's value is %v \n", i, i)
格式文档:fmt
package main
;import
, 可单导入 import “fmt”
, 也可多导入 import (“fmt”;“math/rand”)
;import “math/rand”; rand.xxx
;func add(x int, y int) int
, 后置原因:关于go的声明语法func add(x, y int) int
var a, b, c bool
var a, b, c = true, 10, 'hello!'
0
,false
或“”
:=
来省略 var
: a, b, c := true, 10, 'hello!'
bool string int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr byte // uint8 的别名 rune // int32 的别名 表示一个 Unicode 码点 float32 float64 complex64 complex128
T(x)
, var f float64 = 3.14 var i int = int(f)
const Pi = 3.14
for
循环,没有小括号for i := 0; i < 10; i++ { sum += i }
可省略
sum := 0 for ; sum < 10; { sum += sum }
可进一步省略(形似while, go语言没有while)
sum := 0 for sum < 10 { sum += sum }
进一步无限循环
for { }
if
也没有小括号,并且可类似 for
在条件表达式前有一个简单语句, 作用域延伸至elseif v := math.Pow(2, 10); v < 1000{ fmt.Println(v) } else { fmt.Println("%v < 1000", v) }
switch
语法, case
默认自带 break, 不会往下一个 case 走,除非显式调用 fallthrough
。case
可带变量或表达式。从上到下顺次执行,一旦匹配成功时停止,不会执行之后的case表达式(除非 fallthrough
)。switch
省略表达式时,等同于 switch true
, 这种形式能将一长串 if-then-else 写得更漂亮t := time.Now() switch { case t.Hour() < 12: fmt.Println("Good morning!") case t.Hour() < 17: fmt.Println("Good afternoon.") default: fmt.Println("Good evening.") }
var p *int = &i
, 指针零值为 nil
, go没有指针运算。struct
与 C/C++ 类似。 结构体指针可直接用点号获取结构字段值 p.X
var a [10]int
,操作类似C语言。长度必须声明且不能改变。var s []int = a[1:4]
, 范围左闭右开。范围可缺省,a[:]
,等价于 a[0:10]
。len(s)
和 容量 cap(s)
, 可以通过重新切片来扩展一个切片的容量s = s[:0]
。nil
make
来创建“动态”数组, make 函数会分配一个元素为零值的数组并返回一个引用了它的切片 b := make([]int, 0, 5) // len(b)=0, cap(b)=5 b = b[:cap(b)] // len(b)=5, cap(b)=5 b = b[1:] // len(b)=4, cap(b)=4
append
函数来追加切片元素,当底层数组长度不够时,append
会默认分配更长的数组并指给切片。range
配合 for
循环切片 var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} for i, v := range pow { fmt.Printf("2**%d = %d\n", i, v) }
第一个变量i
表示下标,第二个v
标识值,可用_
来忽略其中一个:for _, v := range pow
,或只留一个变量来表示下标for i := range pow
。
var m map[string]int
, 可用 make 初始化 m := make(map[string]int)
m[key] = elem
; 获取 elem = m[key]
; 删除 delete(m, key)
elem, ok = m[key]
若 key 在 m 中,ok 为 true ;否则,ok 为 false, elem 为该值类型的零值。
func adder() func(int) int { //<oak>: 这类型后置也有点绕眼 sum := 0 //<oak>: 这sum的作用域也有点绕脑 return func(x int) int { sum += x return sum } } func main() { pos, neg := adder(), adder() for i := 0; i < 3; i++ { fmt.Println(pos(i), neg(-2*i)) } } /* 输出 0 0 1 -2 3 -6 */
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { // reciever (v Vertex) 放置在 func 与 函数名之间 return math.Sqrt(v.X*v.X + v.Y*v.Y) } // 本质上,方法与函数没有区别,即这个方法与 func Abs(v Vertex) float64 没有本质区别,仅仅是可以使用语法糖 v.Abs() func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) }
只能为在同一包内定义的类型的接收者声明方法,而不能为其它包内定义的类型(包括 int 之类的内建类型)的接收者声明方法。
v.scale()
等效于 (&v).scale()
;反过来也成立,对于指针p := &v
, p.Abs()
等效于 (*p).Abs()
。t := i.(T)
或 t, ok := i.(T)
,判断一个接口值是否保存了一个特定的类型var i interface{} = "hello" s, ok := i.(string) fmt.Println(s, ok) // hello true
switch v := i.(type) { // 这里 type 为固定关键字 case T: // v 的类型为 T case S: // v 的类型为 S default: // 没有匹配,v 与 i 的类型相同 }
func split(sum int) (x, y int)
,没有参数的 return
语句返回已命名的返回值。defer
语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。i, err := strconv.Atoi("42") if err != nil { fmt.Printf("couldn't convert number: %v\n", err) return } fmt.Println("Converted integer:", i)
←
ch := make(chan int, 1) // 创建int值信道, 第二个参数为缓冲区大小 ch <- v // 将 v 发送至信道 ch。 v := <-ch // 从 ch 接收值并赋予 v
信道缓冲区(默认1)满时阻塞发送端,空时阻塞接收端。这让 goroutine 可以在没有显式的锁或竞态变量的情况下进行同步。
close
关闭一个信道,只有发送者可关闭信道。v, ok := <-ch
之后 ok
会被设置为 false
。
for i := range ch
会不断从信道接收值,直到它被关闭default
, 则在所有分支阻塞时执行 default。sync.Mutex
互斥锁Go 运行时负责调度 Goroutines。Goroutines 的调度是协作式的,而线程不是。
这意味着每次一个线程发生切换,你都需要保存/恢 复所有寄存器,包括16个通用寄存器、PC (程序计数器)、SP(栈指针)、段寄存器( segment register )、16个 XMM 寄存器、FP 协处理器状态、X AVX 寄存器以及所有 MSR 等。
而当另一个 Goroutine 被调度时,只需要保存/恢复三个寄存器,分别是 PC、SP 和 DX。
Go 调度器和任何现代操作 系统的调度器都是 O(1) 复杂度的,这意味着增加线程 /goroutines 的数量不会增加切换时间,但改变寄存器的代价是不可忽视的。