范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

golang学习笔记

  golang学习笔记说明
  $表示需要重点关注的的地方
  包package main //导入包 import (     "fmt"     "math/rand" ) func main() {     fmt.Println("My favorite number is", rand.Intn(10)) }
  函数 package main import "fmt" //函数常规定义 func add(x int, y int) int {     return x + y } func main() {     fmt.Println(add(42, 13)) } package main import "fmt" //当连续两个或多个函数的已命名形参类型相同时,除最后一个类型以外,其它都可以省略。 func add(x, y int) int {     return x + y } func main() {     fmt.Println(add(42, 13)) } package main import "fmt" //$函数可以返回任意数量的返回值 func swap(x, y string) (string, string) {     return y, x } func main() {     a, b := swap("hello", "world")     fmt.Println(a, b) }package main import "fmt" //Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。这里x, y是返回值 func split(sum int) (x, y int) {     x = sum * 4 / 9     y = sum - x     return //没有参数的 return 语句返回已命名的返回值。也就是 直接 返回。 }  func main() {     fmt.Println(split(17)) }
  变量 package main import "fmt"  //var 语句可以出现在包或函数级别 //全局变量 var c, python, java bool  func main() {     var i int     fmt.Println(i, c, python, java) } package main import "fmt"  //变量声明可以包含初始值,每个变量对应一个。 var i, j int = 1, 2  func main() {     //如果初始化值已存在,则可以省略类型;变量会从初始值中获得类型。     var c, python, java = true, false, "no!"     fmt.Println(i, j, c, python, java) } package main import "fmt"  //$注意:函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用。 func main() {     var i, j int = 1, 2     //在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明     k := 3     c, python, java := true, false, "no!"     fmt.Println(i, j, k, c, python, java) }
  基本类型 package main import (     "fmt"     "math/cmplx" ) /** 数据类型: bool string 注意字符串是一个基本类型 int  int8  int16  int32  int64 uint uint8 uint16 uint32 uint64 uintptr  byte // uint8 的别名 rune // int32 的别名     // 表示一个 Unicode 码点 float32 float64 complex64 complex128 注意:int, uint 和 uintptr 在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽。 **/  var (     ToBe   bool       = false     MaxInt uint64     = 1<<64 - 1     z      complex128 = cmplx.Sqrt(-5 + 12i) )  func main() {     fmt.Printf("Type: %T Value: %v ", ToBe, ToBe) //%T输出数据类型     fmt.Printf("Type: %T Value: %v ", MaxInt, MaxInt)     fmt.Printf("Type: %T Value: %v ", z, z) }  运行结果: Type: bool Value: false Type: uint64 Value: 18446744073709551615 Type: complex128 Value: (2+3i)
  零值 package main import "fmt" /** 没有明确初始值的变量声明会被赋予它们的 零值。 常见数据类型零值是: 数值类型为 0, 布尔类型为 false, 字符串为 ""(空字符串)。 **/ func main() {     var i int     var f float64     var b bool     var s string     fmt.Printf("%v %v %v %q ", i, f, b, s) }
  类型转换 package main import (     "fmt"     "math" ) /** 表达式 T(v) 将值 v 转换为类型 T。 一些关于数值的转换:  var i int = 42 var f float64 = float64(i) var u uint = uint(f) 或者,更加简单的形式: i := 42 f := float64(i) u := uint(f) 与 C 不同的是,Go 在不同类型的项之间赋值时需要显式转换。 **/ func main() {     var x, y int = 3, 4     var f float64 = math.Sqrt(float64(x*x + y*y))     var z uint = uint(f)     fmt.Println(x, y, z) }
  类型推导 package main import "fmt" /** 在声明一个变量而不指定其类型时(即使用不带类型的 := 语法或 var = 表达式语法),变量的类型由右值推导得出。 当右值声明了类型时,新变量的类型与其相同: var i int j := i // j 也是一个 int 注意:不过当右边包含未指明类型的数值常量时,新变量的类型就可能是 int, float64 或 complex128 了,这取决于常量的精度: i := 42           // int f := 3.142        // float64 g := 0.867 + 0.5i // complex128 尝试修改示例代码中 v 的初始值,并观察它是如何影响类型的。 **/ func main() {     v := 42.1 // 修改这里!     fmt.Printf("v is of type %T ", v) }
  常量 package main import "fmt" /** 常量的声明与变量类似,只不过是使用 const 关键字。 常量可以是字符、字符串、布尔值或数值。 常量不能用 := 语法声明。 **/ const Pi = 3.14  func main() {     const World = "世界"     fmt.Println("Hello", World)     fmt.Println("Happy", Pi, "Day")      const Truth = true     fmt.Println("Go rules?", Truth) }
  数值常量 package main import "fmt" /** 数值常量是高精度的值。  一个未指定类型的常量由上下文来决定其类型。  再尝试一下输出 needInt(Big) 吧。  (int 类型最大可以存储一个 64 位的整数,有时会更小。)  (int 可以存放最大64位的整数,根据平台不同有时会更少。) **/ const (     // 将 1 左移 100 位来创建一个非常大的数字     // 即这个数的二进制是 1 后面跟着 100 个 0     Big = 1 << 100     // 再往右移 99 位,即 Small = 1 << 1,或者说 Small = 2     Small = Big >> 99 )  func needInt(x int) int { return x*10 + 1 } func needFloat(x float64) float64 {     return x * 0.1 }  func main() {     fmt.Println(needInt(Small))     fmt.Println(needFloat(Small))     fmt.Println(needFloat(Big)) }  结果: 21 0.2 1.2676506002282295e+29
  for package main import "fmt" /** Go 只有一种循环结构:for 循环。 基本的 for 循环由三部分组成,它们用分号隔开: 初始化语句:在第一次迭代前执行 条件表达式:在每次迭代前求值 后置语句:在每次迭代的结尾执行 初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。 一旦条件表达式的布尔值为 false,循环迭代就会终止。  注意:和 C、Java、JavaScript 之类的语言不同,Go 的 for 语句后面的三个构成部分外没有小括号, 大括号 { } 则是必须的。 **/ func main() {     sum := 0     for i := 0; i < 10; i++ {         sum += i     }     fmt.Println(sum) } package main import "fmt" //初始化语句和后置语句是可以省略的 func main() {     sum := 1     for ; sum < 1000; {         sum += sum     }     fmt.Println(sum) }
  for 是 Go 中的 "while" package main import "fmt" //此时你可以去掉分号,因为 C 的 while 在 Go 中叫做 for func main() {     sum := 1     for sum < 1000 {         sum += sum     }     fmt.Println(sum) }
  无限循环 package main //如果省略循环条件,该循环就不会结束,因此无限循环可以写得很紧凑 func main() {     for {     } }
  if package main import (     "fmt"     "math" ) //Go 的 if 语句与 for 循环类似,表达式外无需小括号 ( ) ,而大括号 { } 则是必须的。 func sqrt(x float64) string {     if x < 0 {         return sqrt(-x) + "i"     }     return fmt.Sprint(math.Sqrt(x)) }  func main() {     fmt.Println(sqrt(2), sqrt(-4)) }
  if 的简短语句 package main import (     "fmt"     "math" ) /** 同 for 一样, if 语句可以在条件表达式前执行一个简单的语句。该语句声明的变量作用域仅在 if 之内。 **/ func pow(x, n, lim float64) float64 {     if v := math.Pow(x, n); v < lim {         return v     }     return lim }  func main() {     fmt.Println(         pow(3, 2, 10),         pow(3, 3, 20),     ) }  结果: 9 20
  if 和 else package main import (     "fmt"     "math" ) //在 if 的简短语句中声明的变量同样可以在任何对应的 else 块中使用 func pow(x, n, lim float64) float64 {     if v := math.Pow(x, n); v < lim {         return v     } else {         fmt.Printf("%g >= %g ", v, lim)     }     // 这里开始就不能使用 v 了     return lim }  func main() {     fmt.Println(         pow(3, 2, 10),         pow(3, 3, 20),     ) }
  switch package main import (     "fmt"     "runtime" ) /** switch 是编写一连串 if - else 语句的简便方法。它运行第一个值等于条件表达式的 case 语句。 Go 的 switch 语句类似于 C、C++、Java、JavaScript 和 PHP 中的,不过 Go 只运行选定的 case,而非之后所有的 case。 实际上,Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。 除非以 fallthrough 语句结束,否则分支会自动终止。 Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。 **/ func main() {     fmt.Print("Go runs on ")     switch os := runtime.GOOS; os {     case "darwin":         fmt.Println("OS X.")     case "linux":         fmt.Println("Linux.")     default:         // freebsd, openbsd,         // plan9, windows...         fmt.Printf("%s. ", os)     } }
  switch 的求值顺序 package main import (     "fmt"     "time" ) /** switch 的 case 语句从上到下顺次执行,直到匹配成功时停止。 (例如: switch i { case 0: case f(): } 在 i==0 时 f 不会被调用。) **/ func main() {     fmt.Println("When"s Saturday?")     today := time.Now().Weekday()     switch time.Saturday {     case today + 0:         fmt.Println("Today.")     case today + 1:         fmt.Println("Tomorrow.")     case today + 2:         fmt.Println("In two days.")     default:         fmt.Println("Too far away.")     } }
  没有条件的 switch package main import (     "fmt"     "time" ) /** 没有条件的 switch 同 switch true 一样,这种形式能将一长串 if-then-else 写得更加清晰。 **/ func main() {     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.")     } }
  defer package main import "fmt" /** defer 语句会将函数推迟到外层函数返回之后执行。 推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。 **/ func main() {     defer fmt.Println("world")      fmt.Println("hello") }
  defer 栈 package main import "fmt"  func main() {     fmt.Println("counting")      for i := 0; i < 10; i++ {         defer fmt.Println(i)     }      fmt.Println("done") } 结果: counting done 9 8 7 6 5 4 3 2 1 0
  指针 package main import "fmt" /** Go 拥有指针。指针保存了值的内存地址。 类型 *T 是指向 T 类型值的指针。其零值为 nil。 var p *int & 操作符会生成一个指向其操作数的指针。 i := 42 p = &i * 操作符表示指针指向的底层值。 fmt.Println(*p) // 通过指针 p 读取 i *p = 21         // 通过指针 p 设置 i 这也就是通常所说的"间接引用"或"重定向"。 注意:与 C 不同,Go 没有指针运算。 **/ func main() {     i, j := 42, 2701      p := &i         // 指向 i     fmt.Println(*p) // 通过指针读取 i 的值     *p = 21         // 通过指针设置 i 的值     fmt.Println(i)  // 查看 i 的值      p = &j         // 指向 j     *p = *p / 37   // 通过指针对 j 进行除法运算     fmt.Println(j) // 查看 j 的值 }
  结构体 package main import "fmt" //一个结构体(struct)就是一组字段(field) type Vertex struct {     X int     Y int }  func main() {     fmt.Println(Vertex{1, 2}) }
  结构体字段 package main import "fmt" //结构体字段使用点号来访问 type Vertex struct {     X int     Y int }  func main() {     v := Vertex{1, 2}     v.X = 4     fmt.Println(v.X) }
  结构体指针 package main import "fmt" /** 结构体字段可以通过结构体指针来访问。 如果我们有一个指向结构体的指针 p,那么可以通过 (*p).X 来访问其字段 X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 p.X 就可以。 **/ type Vertex struct {     X int     Y int }  func main() {     v := Vertex{1, 2}     p := &v     p.X = 1e9     fmt.Println(v) }
  结构体文法 package main import "fmt" /** 结构体文法通过直接列出字段的值来新分配一个结构体。 使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。) 特殊的前缀 & 返回一个指向结构体的指针 **/ type Vertex struct {     X, Y int }  var (     v1 = Vertex{1, 2}  // 创建一个 Vertex 类型的结构体     v2 = Vertex{X: 1}  // Y:0 被隐式地赋予     v3 = Vertex{}      // X:0 Y:0     p  = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针) )  func main() {     fmt.Println(v1, p, v2, v3) }
  数组 package main import "fmt" /** 类型 [n]T 表示拥有 n 个 T 类型的值的数组。 表达式:var a [10]int 会将变量 a 声明为拥有 10 个整数的数组。 数组的长度是其类型的一部分,因此数组不能改变大小。这看起来是个限制,不过没关系,Go 提供了更加便利的方式来使用数组。 **/ func main() {     var a [2]string     a[0] = "Hello"     a[1] = "World"     fmt.Println(a[0], a[1])     fmt.Println(a)      primes := [6]int{2, 3, 5, 7, 11, 13}     fmt.Println(primes) }
  切片 package main import "fmt" /** 每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。 类型 []T 表示一个元素类型为 T 的切片。  切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔: a[low : high] 它会选择一个半开区间,包括第一个元素,但排除最后一个元素。  以下表达式创建了一个切片,它包含 a 中下标从 1 到 3 的元素: a[1:4] **/ func main() {     primes := [6]int{2, 3, 5, 7, 11, 13}      var s []int = primes[1:4]     fmt.Println(s) }
  切片就像数组的引用 package main import "fmt" /** 切片并不存储任何数据,它只是描述了底层数组中的一段。 更改切片的元素会修改其底层数组中对应的元素。 与它共享底层数组的切片都会观测到这些修改 **/ func main() {     names := [4]string{         "John",         "Paul",         "George",         "Ringo",     }     fmt.Println(names)      a := names[0:2]     b := names[1:3]     fmt.Println(a, b)      b[0] = "XXX"     fmt.Println(a, b)     fmt.Println(names) } 结果: [John Paul George Ringo] [John Paul] [Paul George] [John XXX] [XXX George] [John XXX George Ringo]
  切片文法 package main import "fmt" /** 切片文法类似于没有长度的数组文法。 这是一个数组文法: [3]bool{true, true, false} 下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片: []bool{true, true, false} **/ func main() {     q := []int{2, 3, 5, 7, 11, 13}     fmt.Println(q)      r := []bool{true, false, true, true, false, true}     fmt.Println(r)      s := []struct {         i int         b bool     }{         {2, true},         {3, false},         {5, true},         {7, true},         {11, false},         {13, true},     }     fmt.Println(s) }
  切片的默认行为 package main import "fmt" /** 在进行切片时,你可以利用它的默认行为来忽略上下界。 切片下界的默认值为 0,上界则是该切片的长度。 对于数组 var a [10]int 来说,以下切片是等价的: a[0:10] a[:10] a[0:] a[:] **/ func main() {     s := []int{2, 3, 5, 7, 11, 13}      s = s[1:4]     fmt.Println(s)      s = s[:2]     fmt.Println(s)      s = s[1:]     fmt.Println(s) }
  切片的长度与容量 package main import "fmt" /** 切片拥有 长度 和 容量。 切片的长度就是它所包含的元素个数。 切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。 切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。 **/ func main() {     s := []int{2, 3, 5, 7, 11, 13}     printSlice(s)      // 截取切片使其长度为 0     s = s[:0]     printSlice(s)      // 拓展其长度     s = s[:4]     printSlice(s)      // 舍弃前两个值     s = s[2:]     printSlice(s) }  func printSlice(s []int) {     fmt.Printf("len=%d cap=%d %v ", len(s), cap(s), s) }  结果: len=6 cap=6 [2 3 5 7 11 13] len=0 cap=6 [] len=4 cap=6 [2 3 5 7] len=2 cap=4 [5 7]
  nil 切片 package main import "fmt" /** 切片的零值是 nil。 nil 切片的长度和容量为 0 且没有底层数组。 **/ func main() {     var s []int     fmt.Println(s, len(s), cap(s))     if s == nil {         fmt.Println("nil!")     } }
  用 make 创建切片 package main import "fmt" /** 切片可以用内建函数 make 来创建,这也是你创建动态数组的方式。 make 函数会分配一个元素为零值的数组并返回一个引用了它的切片: a := make([]int, 5)  // len(a)=5 要指定它的容量,需向 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 **/ func main() {     a := make([]int, 5)     printSlice("a", a)      b := make([]int, 0, 5)     printSlice("b", b)      c := b[:2]     printSlice("c", c)      d := c[2:5]     printSlice("d", d) }  func printSlice(s string, x []int) {     fmt.Printf("%s len=%d cap=%d %v ",         s, len(x), cap(x), x) }
  切片的切片 package main import (     "fmt"     "strings" ) //切片可包含任何类型,甚至包括其它的切片 func main() {     // 创建一个井字板(经典游戏)     board := [][]string{         []string{"_", "_", "_"},         []string{"_", "_", "_"},         []string{"_", "_", "_"},     }      // 两个玩家轮流打上 X 和 O     board[0][0] = "X"     board[2][2] = "O"     board[1][2] = "X"     board[1][0] = "O"     board[0][2] = "X"      for i := 0; i < len(board); i++ {         fmt.Printf("%s ", strings.Join(board[i], " "))     } }
  向切片追加元素 package main import "fmt" /** 为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数。内建函数的文档对此函数有详细的介绍。 func append(s []T, vs ...T) []T append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。 append 的结果是一个包含原切片所有元素加上新添加元素的切片。 当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。 **/ func main() {     var s []int     printSlice(s)      // 添加一个空切片     s = append(s, 0)     printSlice(s)      // 这个切片会按需增长     s = append(s, 1)     printSlice(s)      // 可以一次性添加多个元素     s = append(s, 2, 3, 4)     printSlice(s) }  func printSlice(s []int) {     fmt.Printf("len=%d cap=%d %v ", len(s), cap(s), s) } 结果: len=0 cap=0 [] len=1 cap=1 [0] len=2 cap=2 [0 1] len=5 cap=6 [0 1 2 3 4]
  切片的详细介绍
  https://blog.go-zh.org/go-slices-usage-and-internals 数组
  Go的切片是在数组之上的抽象数据类型,因此在了解切片之前必须要先理解数组。
  数组类型定义了长度和元素类型。例如,  [4]int   类型表示一个四个整数的数组。 数组的长度是固定的, 长度是数组类型的一部分 (  [4]int   和  [5]int   是完全不同的类型)。 数组可以以常规的索引方式访问,表达式  s[n]   访问数组的第 n 个元素。  var a [4]int a[0] = 1 i := a[0] // i == 1
  数组不需要显式的初始化;数组的零值是可以直接使用的,数组元素会自动初始化为其对应类型的零值:  // a[2] == 0, int 类型的零值
  类型  [4]int   对应内存中四个连续的整数:
  Go的数组是值语义。一个数组变量表示整个数组,它不是指向第一个元素的指针(不像 C 语言的数组)。 当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。  (为了避免复制数组,你可以传递一个指向数组的指针,但是数组指针并不是数组。) 可以将数组看作一个特殊的struct,结构的字段名对应数组的索引,同时成员的数目固定。
  数组的字面值像这样:  b := [2]string{"Penn", "Teller"}
  当然,也可以让编译器统计数组字面值中元素的数目:  b := [...]string{"Penn", "Teller"}
  这两种写法,  b   都是对应  [2]string   类型。
  数组虽然有适用它们的地方,但是数组不够灵活,因此在Go代码中数组使用的并不多。 但是,切片则使用得相当广泛。切片基于数组构建,但是提供更强的功能和便利。
  切片类型的写法是  []T   ,  T   是切片元素的类型。和数组不同的是,切片类型并没有给定固定的长度。
  切片的字面值和数组字面值很像,不过切片没有指定元素个数:  letters := []string{"a", "b", "c", "d"}
  切片可以使用内置函数  make   创建,函数签名为:  func make([]T, len, cap) []T
  其中T代表被创建的切片元素的类型。函数  make   接受一个类型、一个长度和一个可选的容量参数。 调用  make   时,内部会分配一个数组,然后返回数组对应的切片。  var s []byte s = make([]byte, 5, 5) // s == []byte{0, 0, 0, 0, 0}
  当容量参数被忽略时,它默认为指定的长度。下面是简洁的写法:  s := make([]byte, 5)
  可以使用内置函数  len   和  cap   获取切片的长度和容量信息。  len(s) == 5 cap(s) == 5
  接下来的两个小节将讨论长度和容量之间的关系。
  切片的零值为  nil   。对于切片的零值,  len   和  cap   都将返回0。
  切片也可以基于现有的切片或数组生成。切分的范围由两个由冒号分割的索引对应的半开区间指定。 例如,表达式  b[1:4]   创建的切片引用数组  b   的第1到3个元素空间(对应切片的索引为0到2)。  b := []byte{"g", "o", "l", "a", "n", "g"} // b[1:4] == []byte{"o", "l", "a"}, sharing the same storage as b
  切片的开始和结束的索引都是可选的;它们分别默认为零和数组的长度。  // b[:2] == []byte{"g", "o"} // b[2:] == []byte{"l", "a", "n", "g"} // b[:] == b
  下面语法也是基于数组创建一个切片:  x := [3]string{"Лайка", "Белка", "Стрелка"} s := x[:] // a slice referencing the storage of x
  一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度, 和容量(片段的最大长度)。
  前面使用  make([]byte, 5)   创建的切片变量  s   的结构如下:
  长度是切片引用的元素数目。容量是底层数组的元素数目(从切片指针开始)。 关于长度和容量和区域将在下一个例子说明。
  我们继续对  s   进行切片,观察切片的数据结构和它引用的底层数组:  s = s[2:4]
  切片操作并不复制切片指向的元素。它创建一个新的切片并复用原来切片的底层数组。 这使得切片操作和数组索引一样高效。因此,通过一个新切片修改元素会影响到原始切片的对应元素。  d := []byte{"r", "o", "a", "d"} e := d[2:] // e == []byte{"a", "d"} e[1] = "m" // e == []byte{"a", "m"} // d == []byte{"r", "o", "a", "m"}
  前面创建的切片  s   长度小于它的容量。我们可以增长切片的长度为它的容量:  s = s[:cap(s)]
  切片增长不能超出其容量。增长超出切片容量将会导致运行时异常,就像切片或数组的索引超 出范围引起异常一样。同样,不能使用小于零的索引去访问切片之前的元素。
  要增加切片的容量必须创建一个新的、更大容量的切片,然后将原有切片的内容复制到新的切片。 整个技术是一些支持动态数组语言的常见实现。下面的例子将切片  s   容量翻倍,先创建一个2倍 容量的新切片  t   ,复制  s   的元素到  t   ,然后将  t   赋值给  s   :  t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0 for i := range s {         t[i] = s[i] } s = t
  循环中复制的操作可以由 copy 内置函数替代。copy 函数将源切片的元素复制到目的切片。 它返回复制元素的数目。  func copy(dst, src []T) int
  copy   函数支持不同长度的切片之间的复制(它只复制较短切片的长度个元素)。 此外,  copy   函数可以正确处理源和目的切片有重叠的情况。
  使用  copy   函数,我们可以简化上面的代码片段:  t := make([]byte, len(s), (cap(s)+1)*2) copy(t, s) s = t
  一个常见的操作是将数据追加到切片的尾部。下面的函数将元素追加到切片尾部, 必要的话会增加切片的容量,最后返回更新的切片:  func AppendByte(slice []byte, data ...byte) []byte {     m := len(slice)     n := m + len(data)     if n > cap(slice) { // if necessary, reallocate         // allocate double what"s needed, for future growth.         newSlice := make([]byte, (n+1)*2)         copy(newSlice, slice)         slice = newSlice     }     slice = slice[0:n]     copy(slice[m:n], data)     return slice }
  下面是  AppendByte   的一种用法:  p := []byte{2, 3, 5} p = AppendByte(p, 7, 11, 13) // p == []byte{2, 3, 5, 7, 11, 13}
  类似  AppendByte   的函数比较实用,因为它提供了切片容量增长的完全控制。 根据程序的特点,可能希望分配较小的活较大的块,或则是超过某个大小再分配。
  但大多数程序不需要完全的控制,因此Go提供了一个内置函数  append   , 用于大多数场合;它的函数签名:  func append(s []T, x ...T) []T
  append   函数将  x   追加到切片  s   的末尾,并且在必要的时候增加容量。  a := make([]int, 1) // a == []int{0} a = append(a, 1, 2, 3) // a == []int{0, 1, 2, 3}
  如果是要将一个切片追加到另一个切片尾部,需要使用  ...   语法将第2个参数展开为参数列表。  a := []string{"John", "Paul"} b := []string{"George", "Ringo", "Pete"} a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])" // a == []string{"John", "Paul", "George", "Ringo", "Pete"}
  由于切片的零值  nil   用起来就像一个长度为零的切片,我们可以声明一个切片变量然后在循环 中向它追加数据:  // Filter returns a new slice holding only // the elements of s that satisfy fn() func Filter(s []int, fn func(int) bool) []int {     var p []int // == nil     for _, v := range s {         if fn(v) {             p = append(p, v)         }     }     return p }可能的"陷阱"
  正如前面所说,切片操作并不会复制底层的数组。整个数组将被保存在内存中,直到它不再被引用。 有时候可能会因为一个小的内存引用导致保存所有的数据。
  例如,  FindDigits   函数加载整个文件到内存,然后搜索第一个连续的数字,最后结果以切片方式返回。  var digitRegexp = regexp.MustCompile("[0-9]+")  func FindDigits(filename string) []byte {     b, _ := ioutil.ReadFile(filename)     return digitRegexp.Find(b) }
  这段代码的行为和描述类似,返回的  []byte   指向保存整个文件的数组。因为切片引用了原始的数组, 导致 GC 不能释放数组的空间;只用到少数几个字节却导致整个文件的内容都一直保存在内存里。
  要修复整个问题,可以将感兴趣的数据复制到一个新的切片中:  func CopyDigits(filename string) []byte {     b, _ := ioutil.ReadFile(filename)     b = digitRegexp.Find(b)     c := make([]byte, len(b))     copy(c, b)     return c }
  Range package main import "fmt" /** for 循环的 range 形式可遍历切片或映射。 当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。 **/ var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}  func main() {     for i, v := range pow {         fmt.Printf("2**%d = %d ", i, v)     } } package main import "fmt" /** 可以将下标或值赋予 _ 来忽略它。 for i, _ := range pow for _, value := range pow 若你只需要索引,忽略第二个变量即可。 for i := range pow **/ func main() {     pow := make([]int, 10)     for i := range pow {         pow[i] = 1 << uint(i) // == 2**i     }     for _, value := range pow {         fmt.Printf("%d ", value)     } }
  映射 package main import "fmt" /** 映射将键映射到值。 映射的零值为 nil 。nil 映射既没有键,也不能添加键。 make 函数会返回给定类型的映射,并将其初始化备用。 **/ type Vertex struct {     Lat, Long float64 }  var m map[string]Vertex  func main() {     m = make(map[string]Vertex)     m["Bell Labs"] = Vertex{         40.68433, -74.39967,     }     fmt.Println(m["Bell Labs"]) }
  映射的文法 package main import "fmt" //映射的文法与结构体相似,不过必须有键名 type Vertex struct {     Lat, Long float64 }  var m = map[string]Vertex{     "Bell Labs": Vertex{         40.68433, -74.39967,     },     "Google": Vertex{         37.42202, -122.08408,     }, }  func main() {     fmt.Println(m) } package main import "fmt" //若顶级类型只是一个类型名,你可以在文法的元素中省略它 type Vertex struct {     Lat, Long float64 }  var m = map[string]Vertex{     "Bell Labs": {40.68433, -74.39967}, //省略Vertex     "Google":    {37.42202, -122.08408}, }  func main() {     fmt.Println(m) }
  修改映射 package main import "fmt" /** 在映射 m 中插入或修改元素: m[key] = elem 获取元素: elem = m[key] 删除元素: delete(m, key) 通过双赋值检测某个键是否存在: elem, ok = m[key] 若 key 在 m 中,ok 为 true ;否则,ok 为 false。  若 key 不在映射中,那么 elem 是该映射元素类型的零值。  同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。  注 :若 elem 或 ok 还未声明,你可以使用短变量声明:  elem, ok := m[key] **/ func main() {     m := make(map[string]int)      m["Answer"] = 42     fmt.Println("The value:", m["Answer"])      m["Answer"] = 48     fmt.Println("The value:", m["Answer"])      delete(m, "Answer")     fmt.Println("The value:", m["Answer"])      v, ok := m["Answer"]     fmt.Println("The value:", v, "Present?", ok) }
  函数值 package main import (     "fmt"     "math" ) /** 函数也是值。它们可以像其它值一样传递。 函数值可以用作函数的参数或返回值。 **/ func compute(fn func(float64, float64) float64) float64 {     return fn(3, 4) }  func main() {     hypot := func(x, y float64) float64 {         return math.Sqrt(x*x + y*y)     }     fmt.Println(hypot(5, 12))      fmt.Println(compute(hypot))     fmt.Println(compute(math.Pow)) }
  函数的闭包 package main import "fmt" /** Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量"绑定"在一起。 例如,函数 adder 返回一个闭包。每个闭包都被绑定在其各自的 sum 变量上。 **/ func adder() func(int) int {     sum := 0     return func(x int) int {         sum += x         return sum     } }  func main() {     pos, neg := adder(), adder()     for i := 0; i < 10; i++ {         fmt.Println(             pos(i),             neg(-2*i),         )     } }
  方法 package main import (     "fmt"     "math" ) /** Go 没有类。不过你可以为结构体类型定义方法。 方法就是一类带特殊的 接收者 参数的函数。 方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。 在此例中,Abs 方法拥有一个名为 v,类型为 Vertex 的接收者。 **/ type Vertex struct {     X, Y float64 }  func (v Vertex) Abs() float64 {     return math.Sqrt(v.X*v.X + v.Y*v.Y) }  func main() {     v := Vertex{3, 4}     fmt.Println(v.Abs()) }
  方法即函数 package main import (     "fmt"     "math" ) /** 记住:方法只是个带接收者参数的函数。 现在这个 Abs 的写法就是个正常的函数,功能并没有什么变化。 **/ type Vertex struct {     X, Y float64 }  func Abs(v Vertex) float64 {     return math.Sqrt(v.X*v.X + v.Y*v.Y) }  func main() {     v := Vertex{3, 4}     fmt.Println(Abs(v)) } package main import (     "fmt"     "math" ) /** 你也可以为非结构体类型声明方法。 在此例中,我们看到了一个带 Abs 方法的数值类型 MyFloat。 你只能为在同一包内定义的类型的接收者声明方法,而不能为其它包内定义的类型(包括 int 之类的内建类型)的接收者声明方法。 (译注:就是接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法。) **/ type MyFloat float64  func (f MyFloat) Abs() float64 {     if f < 0 {         return float64(-f)     }     return float64(f) }  func main() {     f := MyFloat(-math.Sqrt2)     fmt.Println(f.Abs()) }
  指针接收者 package main import (     "fmt"     "math" ) /** 你可以为指针接收者声明方法。 这意味着对于某类型 T,接收者的类型可以用 *T 的文法。(此外,T 不能是像 *int 这样的指针。) 例如,这里为 *Vertex 定义了 Scale 方法。 指针接收者的方法可以修改接收者指向的值(就像 Scale 在这做的)。由于方法经常需要修改它的接收者,指针接收者比值接收者更常用。 试着移除第 16 行 Scale 函数声明中的 *,观察此程序的行为如何变化。 若使用值接收者,那么 Scale 方法会对原始 Vertex 值的副本进行操作。(对于函数的其它参数也是如此。)Scale 方法必须用指针接受者来更改 main 函数中声明的 Vertex 的值。 **/ type Vertex struct {     X, Y float64 }  func (v Vertex) Abs() float64 {     return math.Sqrt(v.X*v.X + v.Y*v.Y) }  func (v *Vertex) Scale(f float64) {     v.X = v.X * f     v.Y = v.Y * f }  func main() {     v := Vertex{3, 4}     v.Scale(10)     fmt.Println(v.Abs()) }
  方法与指针重定向 package main import "fmt" /** 比较前两个程序,你大概会注意到带指针参数的函数必须接受一个指针:  var v Vertex ScaleFunc(v, 5)  // 编译错误! ScaleFunc(&v, 5) // OK 而以指针为接收者的方法被调用时,接收者既能为值又能为指针:  var v Vertex v.Scale(5)  // OK p := &v p.Scale(10) // OK 对于语句 v.Scale(5),即便 v 是个值而非指针,带指针接收者的方法也能被直接调用。 也就是说,由于 Scale 方法有一个指针接收者,为方便起见,Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5)。 **/ type Vertex struct {     X, Y float64 }  func (v *Vertex) Scale(f float64) {     v.X = v.X * f     v.Y = v.Y * f }  func ScaleFunc(v *Vertex, f float64) {     v.X = v.X * f     v.Y = v.Y * f }  func main() {     v := Vertex{3, 4}     v.Scale(2)     ScaleFunc(&v, 10)      p := &Vertex{4, 3}     p.Scale(3)     ScaleFunc(p, 8)      fmt.Println(v, p) } package main import (     "fmt"     "math" ) /** 同样的事情也发生在相反的方向。  接受一个值作为参数的函数必须接受一个指定类型的值:  var v Vertex fmt.Println(AbsFunc(v))  // OK fmt.Println(AbsFunc(&v)) // 编译错误! 而以值为接收者的方法被调用时,接收者既能为值又能为指针:  var v Vertex fmt.Println(v.Abs()) // OK p := &v fmt.Println(p.Abs()) // OK 这种情况下,方法调用 p.Abs() 会被解释为 (*p).Abs()。 **/ type Vertex struct {     X, Y float64 }  func (v Vertex) Abs() float64 {     return math.Sqrt(v.X*v.X + v.Y*v.Y) }  func AbsFunc(v Vertex) float64 {     return math.Sqrt(v.X*v.X + v.Y*v.Y) }  func main() {     v := Vertex{3, 4}     fmt.Println(v.Abs())     fmt.Println(AbsFunc(v))      p := &Vertex{4, 3}     fmt.Println(p.Abs())     fmt.Println(AbsFunc(*p)) }
  选择值或指针作为接收者 package main import (     "fmt"     "math" ) /** 使用指针接收者的原因有二:  首先,方法能够修改其接收者指向的值。  其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效。  在本例中,Scale 和 Abs 接收者的类型为 *Vertex,即便 Abs 并不需要修改其接收者。  通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用。 **/ type Vertex struct {     X, Y float64 }  func (v *Vertex) Scale(f float64) {     v.X = v.X * f     v.Y = v.Y * f }  func (v *Vertex) Abs() float64 {     return math.Sqrt(v.X*v.X + v.Y*v.Y) }  func main() {     v := &Vertex{3, 4}     fmt.Printf("Before scaling: %+v, Abs: %v ", v, v.Abs())     v.Scale(5)     fmt.Printf("After scaling: %+v, Abs: %v ", v, v.Abs()) }
  接口 package main import (     "fmt"     "math" )  /** 接口类型 是由一组方法签名定义的集合。  接口类型的变量可以保存任何实现了这些方法的值。  注意: 示例代码存在一个错误。由于 Abs 方法只为 *Vertex (指针类型)定义,因此 Vertex(值类型)并未实现 Abser。 **/  type Abser interface {     Abs() float64 }  type MyFloat float64  func (f MyFloat) Abs() float64 {     if f < 0 {         return float64(-f)     }     return float64(f) }  type Vertex struct {     X, Y float64 }  func (v *Vertex) Abs() float64 {     return math.Sqrt(v.X*v.X + v.Y*v.Y) }  func main() {     var a Abser     f := MyFloat(-math.Sqrt2)     v := Vertex{3, 4}      a = f  // a MyFloat 实现了 Abser     a = &v // a *Vertex 实现了 Abser      // 下面一行,v 是一个 Vertex(而不是 *Vertex)     // 所以没有实现 Abser。     a = v      fmt.Println(a.Abs()) }
  接口与隐式实现 package main import "fmt" /** 类型通过实现一个接口的所有方法来实现该接口。既然无需专门显式声明,也就没有"implements"关键字。 隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备。 因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。 **/ type I interface {     M() }  type T struct {     S string }  // 此方法表示类型 T 实现了接口 I,但我们无需显式声明此事。 func (t T) M() {     fmt.Println(t.S) }  func main() {     var i I = T{"hello"}     i.M() }
  接口值 package main import (     "fmt"     "math" ) /** 接口也是值。它们可以像其它值一样传递。 接口值可以用作函数的参数或返回值。 在内部,接口值可以看做包含值和具体类型的元组: (value, type) 接口值保存了一个具体底层类型的具体值。 接口值调用方法时会执行其底层类型的同名方法。 **/ type I interface {     M() }  type T struct {     S string }  func (t *T) M() {     fmt.Println(t.S) }  type F float64  func (f F) M() {     fmt.Println(f) }  func main() {     var i I      i = &T{"Hello"}     describe(i)     i.M()      i = F(math.Pi)     describe(i)     i.M() }  func describe(i I) {     fmt.Printf("(%v, %T) ", i, i) }
  底层值为 nil 的接口值 package main import "fmt" /** 即便接口内的具体值为 nil,方法仍然会被 nil 接收者调用。 在一些语言中,这会触发一个空指针异常,但在 Go 中通常会写一些方法来优雅地处理它(如本例中的 M 方法)。 注意: 保存了 nil 具体值的接口其自身并不为 nil。 **/ type I interface {     M() }  type T struct {     S string }  func (t *T) M() {     if t == nil {         fmt.Println("")         return     }     fmt.Println(t.S) }  func main() {     var i I      var t *T     i = t     describe(i)     i.M()      i = &T{"hello"}     describe(i)     i.M() }  func describe(i I) {     fmt.Printf("(%v, %T) ", i, i) }
  nil 接口值 package main import "fmt" /** nil 接口值既不保存值也不保存具体类型。  为 nil 接口调用方法会产生运行时错误,因为接口的元组内并未包含能够指明该调用哪个 具体 方法的类型。 **/ type I interface {     M() }  func main() {     var i I     describe(i)     i.M() }  func describe(i I) {     fmt.Printf("(%v, %T) ", i, i) }  结果: (, ) panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47f4a7]  goroutine 1 [running]: main.main()     /tmp/sandbox3961277646/prog.go:12 +0x67
  空接口 package main import "fmt" /** 指定了零个方法的接口值被称为 *空接口:*  interface{} 空接口可保存任何类型的值。(因为每个类型都至少实现了零个方法。)  空接口被用来处理未知类型的值。例如,fmt.Print 可接受类型为 interface{} 的任意数量的参数。 **/ func main() {     var i interface{}     describe(i)      i = 42     describe(i)      i = "hello"     describe(i) }  func describe(i interface{}) {     fmt.Printf("(%v, %T) ", i, i) }
  类型断言 package main import "fmt" /** 类型断言 提供了访问接口值底层具体值的方式。  t := i.(T) 该语句断言接口值 i 保存了具体类型 T,并将其底层类型为 T 的值赋予变量 t。  若 i 并未保存 T 类型的值,该语句就会触发一个恐慌。  为了 判断 一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。  t, ok := i.(T) 若 i 保存了一个 T,那么 t 将会是其底层值,而 ok 为 true。  否则,ok 将为 false 而 t 将为 T 类型的零值,程序并不会产生恐慌。  请注意这种语法和读取一个映射时的相同之处。 **/ func main() {     var i interface{} = "hello"      s := i.(string)     fmt.Println(s)      s, ok := i.(string)     fmt.Println(s, ok)      f, ok := i.(float64)     fmt.Println(f, ok)      f = i.(float64) // 报错(panic)     fmt.Println(f) }
  类型选择package main import "fmt" /** 类型选择 是一种按顺序从几个类型断言中选择分支的结构。  类型选择与一般的 switch 语句相似,不过类型选择中的 case 为类型(而非值), 它们针对给定接口值所存储的值的类型进行比较。  switch v := i.(type) { case T:     // v 的类型为 T case S:     // v 的类型为 S default:     // 没有匹配,v 与 i 的类型相同 } 类型选择中的声明与类型断言 i.(T) 的语法相同,只是具体类型 T 被替换成了关键字 type。  此选择语句判断接口值 i 保存的值类型是 T 还是 S。在 T 或 S 的情况下,变量 v 会分别按 T 或 S 类型保存 i 拥有的值。在默认(即没有匹配)的情况下,变量 v 与 i 的接口类型和值相同。 **/ func do(i interface{}) {     switch v := i.(type) {     case int:         fmt.Printf("Twice %v is %v ", v, v*2)     case string:         fmt.Printf("%q is %v bytes long ", v, len(v))     default:         fmt.Printf("I don"t know about type %T! ", v)     } }  func main() {     do(21)     do("hello")     do(true) }
  Stringer package main import "fmt" /** fmt 包中定义的 Stringer 是最普遍的接口之一。  type Stringer interface {     String() string } Stringer 是一个可以用字符串描述自己的类型。fmt 包(还有很多包)都通过此接口来打印值。 **/ type Person struct {     Name string     Age  int }  func (p Person) String() string {     return fmt.Sprintf("%v (%v years)", p.Name, p.Age) }  func main() {     a := Person{"Arthur Dent", 42}     z := Person{"Zaphod Beeblebrox", 9001}     fmt.Println(a, z) }
  错误 package main import (     "fmt"     "time" ) /** Go 程序使用 error 值来表示错误状态。  与 fmt.Stringer 类似,error 类型是一个内建接口:  type error interface {     Error() string } (与 fmt.Stringer 类似,fmt 包在打印值时也会满足 error。)  通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 nil 来进行错误处理。  i, err := strconv.Atoi("42") if err != nil {     fmt.Printf("couldn"t convert number: %v ", err)     return } fmt.Println("Converted integer:", i) error 为 nil 时表示成功;非 nil 的 error 表示失败。 **/ type MyError struct {     When time.Time     What string }  func (e *MyError) Error() string {     return fmt.Sprintf("at %v, %s",         e.When, e.What) }  func run() error {     return &MyError{         time.Now(),         "it didn"t work",     } }  func main() {     if err := run(); err != nil {         fmt.Println(err)     } }
  Reader package main import (     "fmt"     "io"     "strings" ) /** io 包指定了 io.Reader 接口,它表示从数据流的末尾进行读取。  Go 标准库包含了该接口的许多实现,包括文件、网络连接、压缩和加密等等。  io.Reader 接口有一个 Read 方法:  func (T) Read(b []byte) (n int, err error) Read 用数据填充给定的字节切片并返回填充的字节数和错误值。在遇到数据流的结尾时,它会返回一个 io.EOF 错误。  示例代码创建了一个 strings.Reader 并以每次 8 字节的速度读取它的输出。 **/ func main() {     r := strings.NewReader("Hello, Reader!")      b := make([]byte, 8)     for {         n, err := r.Read(b)         fmt.Printf("n = %v err = %v b = %v ", n, err, b)         fmt.Printf("b[:n] = %q ", b[:n])         if err == io.EOF {             break         }     } } 结果: n = 8 err =  b = [72 101 108 108 111 44 32 82] b[:n] = "Hello, R" n = 6 err =  b = [101 97 100 101 114 33 32 82] b[:n] = "eader!" n = 0 err = EOF b = [101 97 100 101 114 33 32 82] b[:n] = ""
  Go 程 package main import (     "fmt"     "time" ) /** Go 程(goroutine)是由 Go 运行时管理的轻量级线程。  go f(x, y, z) 会启动一个新的 Go 程并执行  f(x, y, z) f, x, y 和 z 的求值发生在当前的 Go 程中,而 f 的执行发生在新的 Go 程中。  Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。sync 包提供了这种能力,不过在 Go 中并不经常用到,因为还有其它的办法 **/ func say(s string) {     for i := 0; i < 5; i++ {         time.Sleep(100 * time.Millisecond)         fmt.Println(s)     } }  func main() {     go say("world")     say("hello") }
  信道 package main import "fmt" /** 信道是带有类型的管道,你可以通过它用信道操作符 <- 来发送或者接收值。  ch <- v    // 将 v 发送至信道 ch。 v := <-ch  // 从 ch 接收值并赋予 v。 ("箭头"就是数据流的方向。)  和映射与切片一样,信道在使用前必须创建:  ch := make(chan int) 默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。  以下示例对切片中的数进行求和,将任务分配给两个 Go 程。一旦两个 Go 程完成了它们的计算,它就能算出最终的结果。 **/ func sum(s []int, c chan int) {     sum := 0     for _, v := range s {         sum += v     }     c <- sum // 将和送入 c }  func main() {     s := []int{7, 2, 8, -9, 4, 0}      c := make(chan int)     go sum(s[:len(s)/2], c)     go sum(s[len(s)/2:], c)     x, y := <-c, <-c // 从 c 中接收      fmt.Println(x, y, x+y) }
  带缓冲的信道 package main import "fmt" /** 信道可以是 带缓冲的。将缓冲长度作为第二个参数提供给 make 来初始化一个带缓冲的信道:  ch := make(chan int, 100) 仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。  修改示例填满缓冲区,然后看看会发生什么。 **/ func main() {     ch := make(chan int, 2)     ch <- 1     ch <- 2     fmt.Println(<-ch)     fmt.Println(<-ch) }
  range 和 close package main import (     "fmt" ) /** 发送者可通过 close 关闭一个信道来表示没有需要发送的值了。接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,那么在执行完  v, ok := <-ch 之后 ok 会被设置为 false。  循环 for i := range c 会不断从信道接收值,直到它被关闭。  *注意:* 只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序恐慌(panic)。  *还要注意:* 信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有需要发送的值时才有必要关闭,例如终止一个 range 循环。 **/ func fibonacci(n int, c chan int) {     x, y := 0, 1     for i := 0; i < n; i++ {         c <- x         x, y = y, x+y     }     close(c) }  func main() {     c := make(chan int, 10)     go fibonacci(cap(c), c)     for i := range c {         fmt.Println(i)     } }
  select 语句 package main import "fmt" /** select 语句使一个 Go 程可以等待多个通信操作。 select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。 **/ func fibonacci(c, quit chan int) {     x, y := 0, 1     for {         select {         case c <- x:             x, y = y, x+y         case <-quit:             fmt.Println("quit")             return         }     } }  func main() {     c := make(chan int)     quit := make(chan int)     go func() {         for i := 0; i < 10; i++ {             fmt.Println(<-c)         }         quit <- 0     }()     fibonacci(c, quit) }
  默认选择 package main import (     "fmt"     "time" ) /** 当 select 中的其它分支都没有准备好时,default 分支就会执行。  为了在尝试发送或者接收时不发生阻塞,可使用 default 分支:  select { case i := <-c:     // 使用 i default:     // 从 c 中接收会阻塞时执行 } **/ func main() {     tick := time.Tick(100 * time.Millisecond)     boom := time.After(500 * time.Millisecond)     for {         select {         case <-tick:             fmt.Println("tick.")         case <-boom:             fmt.Println("BOOM!")             return         default:             fmt.Println("    .")             time.Sleep(50 * time.Millisecond)         }     } }
  sync.Mutex package main import (     "fmt"     "sync"     "time" ) /** 我们已经看到信道非常适合在各个 Go 程间进行通信。  但是如果我们并不需要通信呢?比如说,若我们只是想保证每次只有一个 Go 程能够访问一个共享的变量,从而避免冲突?  这里涉及的概念叫做 *互斥(mutual*exclusion)* ,我们通常使用 *互斥锁(Mutex)* 这一数据结构来提供这种机制。  Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:  Lock Unlock 我们可以通过在代码前调用 Lock 方法,在代码后调用 Unlock 方法来保证一段代码的互斥执行。参见 Inc 方法。  我们也可以用 defer 语句来保证互斥锁一定会被解锁。参见 Value 方法。 **/ // SafeCounter 的并发使用是安全的。 type SafeCounter struct {     v   map[string]int     mux sync.Mutex }  // Inc 增加给定 key 的计数器的值。 func (c *SafeCounter) Inc(key string) {     c.mux.Lock()     // Lock 之后同一时刻只有一个 goroutine 能访问 c.v     c.v[key]++     c.mux.Unlock() }  // Value 返回给定 key 的计数器的当前值。 func (c *SafeCounter) Value(key string) int {     c.mux.Lock()     // Lock 之后同一时刻只有一个 goroutine 能访问 c.v     defer c.mux.Unlock()     return c.v[key] }  func main() {     c := SafeCounter{v: make(map[string]int)}     for i := 0; i < 1000; i++ {         go c.Inc("somekey")     }      time.Sleep(time.Second)     fmt.Println(c.Value("somekey")) }

迪巴拉的双性恋女友长啥样?上赛季刚刚转会罗马便成了罗马当家球星跟随阿根廷获得世界杯冠军并在决赛对阵法国的比赛中完成关键解围并罚进点球大战中关键一球的阿根廷的颜值担当世界最佳梅西的超级替补替补保罗。迪巴拉,因王皓上任首要任务!清洗男乒世界前三球员,败人品球员不能留国乒竞聘工作已经正式结束,目前可以确定国乒这次规模如此之大,不仅仅局限于教练组,最终的目标肯定是对于运动员进行调整,王皓不负众望担任了国乒男队主教练,俗话说新官上任三把火,王皓上任史蒂夫纳什7秒跑轰征服全联盟,他的无冠遗憾被写进风中!他是跑轰型控位的鼻祖,他是飘逸的凤凰城之子。他荣誉无数,两届MVP,5次助攻王,7次最佳阵容,8次全明星以及入选名人堂。但这对于他而言都不是最重要的。在太阳队史上曾有着这样一位球员大爆冷!雷霆残阵打崩联盟第1,单场轰150分杜兰特威少当年没做到北京时间1月4日,雷霆主场150比117爆冷击败联盟第一的凯尔特人。雷霆头号球星亚历山大缺席此战。但是被视为弱旅的雷霆大爆发打崩了凯尔特人。雷霆创造多项神迹,单场轰下150分创造雷CBA三消息吉林超外61分三双,西热力江被打服,北控欲签雅尼斯大家好呀,我是北柠,各位小伙伴们要养成先看后赞的习惯哦!吉林队这个赛季依旧只有一名外援琼斯可以使用,但是琼斯的能力可非同一般,他一个人可以抵上其他球队的两名外援了,而且琼斯就像是不国乒队员都用啥球拍,你知道吗?以前的乒乓器材市场可谓是百家争鸣,百家齐放,外国品牌有亚萨卡斯帝卡挺拔蝴蝶,尼塔库,尤拉,国内最有名的是红双喜世奥的银河729拍里奥等等。随着大球时代特别是塑料球的到来,球弹性降低官宣!乒协公布国乒教练员竞聘最终结果,24位教练大名单尘埃落定1月4日消息,中国乒协公布了国乒教练员竞聘结果。24位教练员大名单终于尘埃落定。我们一起来看看。众所周知,在第一波公布的名单中,4位教练组负责人已经确定。其中李隼担任国乒总教练,王谁过年不吃顿饺子,上海山西组团给宁波拜年谁过年不吹顿饺子,但宁波这一吃就是两顿!昨晚,宁波竟然战胜了上海,这是一场令人震惊的爆冷,因为两队根本就不在一个级别,进入2023年之前,宁波整整10个月没有赢过一场球,连续29连2023年苹果新品预测1iPhone15系列全系采用USBC接口,Pro和Ultra会有更快的数据传输速度。15和15Plus采用A16处理器,摄像头提升到4800W像素,灵动岛取代刘海。15Pro和1英超最新积分榜阿森纳战平纽卡斯尔,曼联3球大胜与曼城只差1分1月4日凌晨,英超联赛第19轮展开较量,积分榜前4的三支球队率先开打,阿森纳主场00战平纽卡斯尔,将领先优势扩大到8分。曼联30击败伯恩茅斯,积分追平第三位的纽卡斯尔。布莱顿41客CBA最新积分榜广东13连胜10291天津稳坐第二,天津2连败排18北京时间2023年1月4日,20222023赛季CBA常规赛第22轮继续进行,第2比赛日的首场比赛中广东大益茶经过4节比赛的鏖战,最终10291拿下弱旅天津先行者迎来13连胜,本场
秋未走,冬已至生活有温度秋天的树叶刚要开始飘散,冬的动作就来了,宣布进入冬的管辖时间。也即告诉靓女俊男,天气变冷,该穿棉衣秋裤了。秋天下架的树叶色彩斑斓,铺满一地,景致一点不比夏天的花儿逊色,进杨贵妃到底死没有?日本影星山口百惠我是贵妃的后人古代四大美女,羞花之貌,脂雪之肤,百媚之态,精通音律,能歌善舞是她雍容华贵,帝王爱情,红颜祸水,凄惨而死亦是她。这些关键词集于杨玉环一身,也大致概括了她的一生。从一骑红尘妃子笑,无杀完董卓之后,再看王允是什么行为,才会明白王允的毒辣立身以立学为先,立学以读书为本。读书立学立身是一个递进的关系。读书让人博学,博学才能自知自明和自觉,这才有了立学的基础。不博学没有比较,没有比较发现不了自己的优势,发现不了自己的优在清朝末年,刽子手的收入怎么样,砍一颗头相当于现在多少钱?物以稀为贵,这句话放在任何方面都是真理,就比如职业也是如此。我们看美国阿拉斯加的捕蟹人,虽然一年只工作几天的时间,但每天的收入却能达到两三万,工作完成之后,可以获得十几万美元的收入口头语胡说八道中的胡说何解?其八道又是哪八道?日常生活中,我们经常会听到这样的对话,甲乙,你这纯属胡说八道,根本没有科学依据,火星上一片荒芜,人怎么可能在上面生存?乙表示不服你才胡说八道呢,马斯克听说过吗,就是造特斯拉电动汽车太空理发视频来了!王亚平变身发型师进入太空的这段时间里,神舟十三号航天员保持天地同步作息,其间翟志刚王亚平还协同配合,给叶光富理了发。这是2021年10月31日下午4点左右的一段画面。画面中,三名航天员正在做理发前解放双手,拒绝繁琐,塞宾SmartMike无线麦克小身材大能量作为自媒体爱好者,之前一直都是拿着手机支架或者自拍杆来完成视频的拍摄,虽然能解决基本的拍摄需求,但是想要让视频质量更加完美,观众们更清晰的听到我的声音,单靠手机确实有些力不从心。市高清ampampamp大动态,沉浸式还原的新标杆BampampampWZepplin飞艇无线音箱关于蓝牙音箱说点你不知道的英国宝华韦健(BowersWilkins)是一个总能打破陈规并推出创新形态产品与领先技术的音响品牌。自1966年诞生以来,宝华韦健的鹦鹉螺音箱800旗舰系更轻的20W苹果充电器,兼容性还很强,RavPower苹果20W充电器上手经过一个多月的等待,多数朋友应该还没有收到今年的iPhone13新机,不过在等待新手机的时候,大家也有必要先入手一些配件,像是手机壳耳机之类的,当然了,这里面最重要的还是充电器,特有滋润的生活更美好,舒乐氏蓝岭雾加湿器评测生活在南方,虽说不像北方那样一到冬天就特别干燥,但是我依然烦恼着,因为每到这个季节,身上的皮肤就特别的瘙痒难耐,而且一抓就掉白色皮屑。我也到网上查找看过,说这是属于季节性瘙痒症,它巧用模板让你的文字鲜活起来表达感受感慨感慨的原因感慨的启发养儿方知父母恩,懂时已是中年人。小时候,衣来伸手,饭来张口,有需求只要喊一声爸妈,仿佛所有问题都能迎刃而解。直到自己成家立业,才知道油米价贵直到自己