var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
// 忽略
for _, value := range pow
for i, _ := range pow
// 忽略第二个,可以忽略_
for i := range pow
}
练习:切片
实现 Pic。它应当返回一个长度为 dy 的切片,其中每个元素是一个长度为 dx,元素类型为 uint8 的切片。当你运行此程序时,它会将每个整数解释为灰度值(好吧,其实是蓝度值)并显示它所对应的图像。
(提示:需要使用循环来分配 [][]uint8 中的每个 []uint8;请使用 uint8(intValue) 在类型之间转换;你可能会用到 math 包中的函数。)
package main
import "golang.org/x/tour/pic"
func Pic(dx, dy int) [][]uint8 {
result := make([][]uint8, dy)
for x := range result {
item := make([]uint8, dx)
for y := range item {
item[y] = uint8((x + y) / 2)
}
result[x] = item
}
return result
}
func main() {
pic.Show(Pic)
}
感觉x^y最好看
映射map
将键映射到值。零值为nil,没有键也不能添加键。
type Vertex struct { // 设定Vertex结构体
Lat, Long float64
}
func main() {
m := make(map[string]Vertex) // 新建一个map,key是string,value是Vertex
m["Bell Labs"] = Vertex{ // 给map 的key赋值
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"]) // {40.68433 -74.39967}
}
// 或者也可以直接定义:
var m = map[string]Vertex{ // [string]Vertex是为了表明key和value类型吧
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
// 也可以这么定义
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
// 删除
delete(m, key)
// 检验值是否存在
elem, ok := m[key]
// 若 key 在 m 中,ok 为 true ;否则,ok 为 false, elem也是零值
练习:映射
实现 WordCount。它应当返回一个映射,其中包含字符串 s 中每个“单词”的个数。函数 wc.Test 会对此函数执行一系列测试用例,并输出成功还是失败。
package main
import (
// "fmt"
"runtime"
"strings"
)
import (
"golang.org/x/tour/wc"
)
func WordCount(s string) map[string]int {
str := strings.Fields(s)
m := make(map[string]int)
for _, elem := range str {
if _, ok := m[elem]; ok {
m[elem] += 1
} else {
m[elem] = 1
}
runtime.Gosched()
}
return m
}
func main() {
result := WordCount("I am learning Go!")
fmt.Println(result)
wc.Test(WordCount)
}
函数
函数也是值,可以像值一样被传递。
闭包
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
练习:斐波纳契闭包
package main
import "fmt"
// 返回一个“返回int的函数”
func fibonacci() func() int {
first := 0
second := 1
return func() int {
result := first
first, second = second, (first + second) // 这种赋值方式可以保留second的值
return result
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
方法和接口
方法
就是给结构体加一个方法。
type Vertex struct { // 结构体
X, Y float64
}
func (v Vertex) Abs() float64 { // 在结构体下面,加一个Abs()方法,返回float64
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs()) // 调用结构体的方法,结果是5
}
// 如果是正常的函数,是这么写的
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))
}
// 非结构体,也可以有方法
// 感觉就是在某种type下面增加一种方法。
// 但类型定义和方法声明必须在同一包内。且不能为内建类型声明方法。
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())
}
这些都是在值下面增加方法。因此处理的都是输入的值的副本
如果用指针,就是在直接操作值本身。
func (v Vertex) Scale(f float64) { // 这是在操作值的副本
v.X = v.X * f
v.Y = v.Y * f
}
// 函数结束后,副本就会被销毁,v的两个值都不会变
func (v *Vertex) Scale(f float64) { // 这是在操作指针
v.X = v.X * f
v.Y = v.Y * f
}
// 直接修改了指向的内容,函数结束后v的两个值都乘f了
func main() {
v := Vertex{3, 4}
v.Scale(10) // 使用的时候不需要加&
fmt.Println(v.Abs())
}
// 用函数写:
type Vertex struct {
X, Y float64
}
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func Scale(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
Scale(&v, 10) // 传进去的是指针,要加一个&
fmt.Println(Abs(v))
}
// 定义两个类型
type MyFloat float64
type Vertex struct {
X, Y float64
}
f := MyFloat(-math.Sqrt2) // f是MyFloat的一个实例
v := Vertex{3, 4}
b := f
b = v // 这样会报错,因为b在f的时候,类型已经定义为MyFloat了,不能重复定义
// 定义一个空接口
type Abser interface {} // 一定要有个括号
var a Abser // 定义a为接口类型,就什么东西都能赋值了,类似于any
// 这可以用来处理未知类型的值
a = f
a = v
a = &v
// 如果a想要用某个方法,可以在下面定义
type Abser interface {
Abs() float64
}
// 不过需要赋值的类型,下面得有这个方法,不然会报错
func (f MyFloat) Abs() float64 { // myfloat下面增加一个abs()方法
...
}
func (v *Vertex) Abs() float64 { // vertex下面增加一个abs()方法,需要传入地址
...
}
a = f
a = v
a = &v
a.Abs()
// 能赋值也能调用方法
// 如果展开赋值就是这样:
var b Abser = Vertex{3, 4}
// 没办法用:=定义类型
// b := Vertex{3, 4} // 这就变成Vertex类型了,而不是Abser
一个防御输入值为nil的方法:
type T struct {
S string
}
func (t *T) M() { // 增加一个函数
if t == nil { // 如果是nil
fmt.Println("<nil>") // 就做处理
return
}
fmt.Println(t.S)
}
如果值是nil,那么调用其底下方法,就会产生panic报错。
因为不知道该用哪个类型的方法。【可能因为nil下面并没有这个方法】
func main() {
var i I // nil
i.M() // panic
}
类型断言
语法:t := i.(T),断言i为T类型。
var i interface{} = "hello" // i是任意类型,此处赋值为string
s := i.(string) // 断言为 string
fmt.Println(s)
f = i.(float64) // 断言为float64
fmt.Println(f) // 断言错,就会报错(panic)
// 避免断言错的报错,就要收第二个参数,即断言是否正确
s, ok := i.(string)
fmt.Println(s, ok)
f, ok := i.(float64)
fmt.Println(f, ok)
类型选择
从几个类型断言中选择分支的结构。
就是一种根据不同输入类型来选择操作的switch结构。类型用type表示
switch v := i.(type) { // 括号里是 type
case int: // 如果输入类型是int
fmt.Printf("Twice %v is %v\n", v, v*2)
case string: // 如果输入类型是string
fmt.Printf("%q is %v bytes long\n", v, len(v))
default: // 默认情况,v和i的接口类型和值相同
fmt.Printf("I don't know about type %T!\n", v)
}
stringer
fmt默认的打印方法,是调用结构体下面的String() 方法。
因此可以通过修改String()的方法,来修改该结构体的打印值。
【感觉这种能操纵默认方法的操作,非常有毒】
type Person struct {
Name string
Age int
}
func main() {
a := Person{"123", 3}
b := Person{"234", 5}
fmt.Println(a, b) // {123 3} {234 5}
}
// 修改默认的String()方法
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
// 就会根据这个String()来打印,因为fmt默认调用的是这个方法
...
fmt.Println(a, b) // 123 (3 years) 234 (5 years)
...
// 如果把String()改成;
func (p Person) String() string {
return fmt.Sprintln("abc")
}
// 结果会打印
// abc
// 其实如果想改变该结构体的输出方法,完全可以通过加一个函数,而不是直接改变默认函数
func (p Person) PrintABC() string {
return fmt.Sprintln("abc")
}
...
fmt.Println("z.PrintABC()", z.PrintABC())
...
package main
import (
"fmt"
"strconv"
)
func (p IPAddr) String() string {
lastIndex := len(p) - 1
var s string
for i, v := range p {
s += strconv.Itoa(int(v)) // uint8 > int > string
if i != lastIndex { // 加点
s += "."
}
}
return s
}
func main() {
hosts := map[string]IPAddr{
"loopback": {127, 0, 0, 1},
"googleDNS": {8, 8, 8, 8},
}
for name, ip := range hosts {
fmt.Printf("%v: %v\n", name, ip)
}
}
感觉加点可以不用那么麻烦,最后删掉一位字符串即可
func (p IPAddr) String() string {
var s string
for _, v := range p {
s += strconv.Itoa(int(v)) + "." // 每个后面都加一个点
}
return s[:len(s)-1] // 最后少返回一位
}
package main
import (
"io"
"os"
"strings"
)
type rot13Reader struct {
r io.Reader
}
func rot13(b byte) byte {
switch {
case 'A' <= b && b <= 'M':
b = b + 13
case 'M' < b && b <= 'Z':
b = b - 13
case 'a' <= b && b <= 'm':
b = b + 13
case 'm' < b && b <= 'z':
b = b - 13
}
return b
}
func (mr rot13Reader) Read(b []byte) (int, error) {
n, e := mr.r.Read(b)
for i := 0; i < n; i++ {
b[i] = rot13(b[i])
}
return n, e
}
func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}
图像
image包
type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}
练习:图像
package main
import (
"image"
"image/color"
"golang.org/x/tour/pic"
)
type Image struct{}
func (i Image) ColorModel() color.Model {
return color.RGBAModel
}
func (i Image) Bounds() image.Rectangle {
return image.Rect(0, 0, 200, 200)
}
func (i Image) At(x, y int) color.Color {
return color.RGBA{R: uint8(x), G: uint8(y), B: uint8(255), A: uint8(255)}
}
func main() {
m := Image{}
pic.ShowImage(m)
}
并发
go routine
goroutine是由 Go 运行时管理的轻量级线程。
go f(x, y, z)
会启动一个新的 Go 程并执行
f(x, y, z)
在新的 Go 程中只负责执行 f ,其他的求值发生在当前的 Go 程中。
Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。
信道
信道是带有类型的管道,你可以通过它用信道操作符 <- 来发送或者接收值。
ch <- v // 将 v 发送至信道 ch。
v := <-ch // 从 ch 接收值并赋予 v。
(“箭头”就是数据流的方向。)
和映射与切片一样,信道在使用前必须创建:
ch := make(chan int)
默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 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)
}
还要注意: 信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有需要发送的值时才有必要关闭,例如终止一个 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)
}
_,ok:=<-c
fmt.Println(ok)
}