Go语言快速上手I

喜欢Go的函数式编程, 统一的Google代码风格, 这点我很赞同一致使用Google编程规范Google_Cpp_Style_guide_CN.pdf, 少了无聊的争论点; 同时作为Web服务的杀手级语言, 其有着不输给Python的简洁和不差cpp的性能, 但有点简洁过头了都不要class, 泛型, 而且没想到Google的Golang开发者团队如此的固执>好在能快速成型, 跨平台, 静态安全, 类库丰富, 写起来也是很清爽, GoFmt, GoDoc等出厂插件也蛮好用的, 在VIM下编写也可以做到实时调用小工具> 自从写了Python, 按照PEP8经常把代码写的左歪右倒, 滚起页来, 单靠缩进完全不知道自己在哪, 虽说有Pycharm加持但是我的600行代码就改个函数名, 波浪线要飘5s;之所以单个Python模块如此大也是因为Python的包调用比较难用, 需要追加依赖路径到sys.path, 相比go指定好goroot就可以层级调用。

包概念

程序以package main的main()函数作为入口, 测试函数我目前在本包写个xxxMain函数, 丢到main包中调用, 这样看起来本包也有了main函数, 当然真正的测试走的是test.go的方法简答。

函数样例

convertToBin

把整数类型装换成二进制, 返回字符串

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func convertToBin(n int) string {
    result := ""
    if n == 0 {
        return "0"
    }
    for ; n > 0; n /= 2 {
        lsb := n % 2
        result = strconv.Itoa(lsb) + result
    }
    return result
}

printFile 简单读文件

func NewScanner(r io.Reader) *Scanner > NewScanner returns a new Scanner to read from r. The split function defaults to ScanLines.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func printFile(filename string) { // 实用的IO
    file, err := os.Open(filename)
    if err != nil {
        panic(err) // 中断程序并给出错误
    }
    scanner := bufio.NewScanner(file)

    for scanner.Scan() { // 相当于 ;scanner.Scan(); 也就是 while
        fmt.Println(scanner.Text())
    }
}

死循环

没有了while, 本身在cpp已经很少用到了for(;;)==while()

1
2
3
4
5
func forever() {
    for { // while True
        fmt.Println("forever")
    }
}

特殊的switch

switch后可以留空, 然后在case补全判断条件, 相当于if…elif…else 默认case有break, 除非显式fallthrough 较为严格的编译前检查, 未使用的变量必须删去, 或者丢给垃圾变量 _ error处理简单粗暴, 带回返回值处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func eval(a, b int, op string) (int, error) {
    switch op { // 可以留空,在case补条件
    case "+":
        return a + // 以+结尾编译时不会被加上分号
            b, nil
    case "-":
        return a - b, nil
    case "*":
        return a * b, nil
    case "/":
        q, _ := div(a, b) // unused变量
        return q, nil
    default:
        return 0, fmt.Errorf("unsupported operation: " + op) // fmt的格式化输出
    }
}

函数是一等公民

函数作为参数传入, 遗憾的是不支持函数重载, 依赖接口, 用户定义作为第二等公民, 在这点上对于Go的争论点很多很多, 争论归争论, 学到最后再发言

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func apply(foo func(int, int) int, a, b int) int {
    p := reflect.ValueOf(foo).Pointer() // 反射
    opName := runtime.FuncForPC(p).Name()
    fmt.Printf("Calling function %s with args "+"(%d %d)", opName, a, b)
    return foo(a, b)
}

func applyRetString(foo func(int, int, string) (int, error), a, b int, op string) (int, error) {
    p := reflect.ValueOf(foo).Pointer()
    opName := runtime.FuncForPC(p).Name()
    fmt.Printf("Calling function %s with args "+"(%d %s %d)\n", opName, a, op, b)
    return foo(a, b, op)
}

func div(a, b int) (q, r int) {
    return a / b, a % b
}

func main(){
    fmt.Println(apply(div, 3, 4))
    fmt.Println(apply( // 匿名函数 main.main.func1
        func(a, b int) int {
            return int(math.Pow(float64(a), float64(b)))
        }, 3, 4),
    )
    fmt.Println(applyRetString(eval, 3, 4, "++"))
}

执行外部命令

简单的调用终端执行cmd

1
2
3
4
5
6
cmd := exec.Command("python3", "-c", "from math import *;import math;ns = vars(math).copy();ns['__builtins__']=None;print(str(eval(str('cos(3.14/6)+sin(3.14/3)'), ns)))")
    if ans, err := cmd.CombinedOutput(); err == nil {
        fmt.Println(string(ans))
    } else {
        fmt.Println(err)
    }

map类型

统计读入文件重复的行数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
func countLines(f *os.File, counts map[string]int) {
    // map传入指向原内存块的指针, 相当于引用传递
    input := bufio.NewScanner(f)
    for input.Scan() {
        counts[input.Text()]++
    }
}

// 输出出现次数大于1次的行内容
func outputDupLines(counts map[string]int) {	
    for line, n := range counts {
        if n > 1 {
            fmt.Println(n, line)
        }
    }
}

// 从标准输入中统计出现次数大于一次的行内容
func dup1() {
    counts := make(map[string]int) // make创建空map 键string 值int
    input := bufio.NewScanner(os.Stdin) // 标准输入
    for input.Scan() {
        // 读入下一行并且移除行末的换行符, 无输入返回false
        counts[input.Text()]++
    }
    outputDupLines(counts)
}

func dup2() {
    // 练习1.4 “出现重复的行时打印文件名称”
    counts := make(map[string]int)
    fileNamesList := os.Args[1:] // 获取文件列表
    numOfFiles := len(fileNamesList)
    if numOfFiles == 0 { // 从标准输入读取文本
        countLines(os.Stdin, counts)
    } else {
        for _, arg := range fileNamesList {
            f, err := os.Open(arg)
            if err != nil {
                fmt.Fprintf(os.Stderr, "dup2: %v\n", err) // %v 自然格式
                continue
            }
            input := bufio.NewScanner(f) // 从文件流中读取文本
            for input.Scan() {
                curLine := input.Text()
                counts[curLine]++
            }
            f.Close()
        }
    }
    outputDupLines(counts)
}

// 前两个都是按文件流的形式读取文本
// dup3把文本全部读入内存后在操作
func dup3() {
    counts := make(map[string]int)
    for _, fileName := range os.Args[1:] { // 获取命令行参数
        data, err := ioutil.ReadFile(fileName)
        // 使用io工具包util中的ReadFile一次性读取文本
        // ReadFile函数返回一个字节切片
        if err != nil {
            fmt.Fprintf(os.Stderr, "dup3: %v\n", err)
            // 自然格式输出标准错误信息
            continue
        }
        for _, line := range strings.Split(string(data), "\n") {
            // 与strings.Join 相反按照提供的sep分割字符串
            counts[line]++
        }
    }
    outputDupLines(counts)
}

获取命令行参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func echo() {
    var s, sep string = "", ""
    for i := 1; i < len(os.Args); i++ {
        s += sep + os.Args[i] // os.Args获取命令行字符串
        sep = " "
    }
    fmt.Println(s)
}

func echo1() {
    s, sep := "", ""
    for _, arg := range os.Args[1:] { // 第二种索引方法 range产生索引和该处的值
        s += sep + arg // +=  产生新的字符创并把值赋给s, 当内容很大时这样的形式代价很高,通常用string包的Join
        sep = " "
    }
    fmt.Println(s)
}

func echo2() {
    fmt.Println(strings.Join(os.Args[1:], " "))
    fmt.Println(os.Args[1:]) // 方括号空格隔开
}

func echoArg0() {
    fmt.Println(os.Args[0])
    for indx, arg := range os.Args {
        fmt.Println(indx, arg)
    }
}

GET 请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func fetchURL() {
    for _, url := range os.Args[1:] {
        if strings.HasPrefix(url, "http://") == false { // 补充前缀http://
            url = "http://" + url
        }
        resp, err := http.Get(url) // GET请求
        if err != nil {
            fmt.Fprintf(os.Stderr, "fetch:reading %s: %v\n", url, err)
            os.Exit(1)
        }
        statue := resp.Status
        fmt.Printf("Access %s, statue is %s", url, statue)

        f, err := os.Create("resp.txt") // 创建记录响应的文件
        if err != nil {
            fmt.Fprintf(os.Stderr, "Create: %v\n", err)
            os.Exit(1)
        } else {
            _, err := io.Copy(f, resp.Body) // 将读取的响应体存到文件中,避免过大的缓冲区
            if err != nil {
                fmt.Fprintf(os.Stderr, "Copy: %v\n", err)
                os.Exit(1)
            }
        }
        // b, err := ioutil.ReadAll(resp.Body)
        resp.Body.Close()
    }
}

简单并发GET

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func fetchallMain() {
    start := time.Now()
    ch := make(chan string) // channel 通道用来在goroutine之间传递参数
    for _, url := range os.Args[1:] {
        if strings.HasPrefix(url, "http://") == false {
            url = "http://" + url
        }
        go fetch(url, ch) // go function 创建新的goroutine, 并在进程中执行函数
    }
    for range os.Args[1:] {
        fmt.Println(<-ch) // 每个线程的异步返回, main的goroutine负责接收ch, 防止因main函数提前退出
    }
    fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds()) // 并发总消耗时间
}

func fetch(url string, ch chan<- string) {
    start := time.Now()
    resp, err := http.Get(url)
    if err != nil {
        ch <- fmt.Sprint(err)
        return
    }
    // nbytes, err := io.Copy(ioutil.Discard, resp.Body) // 变量垃圾桶
    secs := time.Since(start).Seconds()
    // 比较两次访问响应的内容

    f, _ := os.Create("resp.txt")
    nbytes, err := io.Copy(f, resp.Body)
    resp.Body.Close()

    if err != nil {
        ch <- fmt.Sprintf("while reading %s: %v\n", url, err) // 当前ch向main异步写回字符串
        return
    }
    ch <- fmt.Sprintf("%.2fs %7d %s\n", secs, nbytes, url) // 当前ch向main异步写回字符串
}

生成gif

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 调色板
var palette = []color.Color{color.White, color.Black}
var colorfulPalette = []color.Color{color.White, color.Black,
color.RGBA{142, 53, 74, math.MaxUint8},
color.RGBA{131, 138, 45, math.MaxUint8}}

// 复合声明生成切片 palette = [{65535} {0}]

const ( // 包级别常量
    whiteIndex = 0 // 白色在调色板的索引
    balckIndex = 1
    suohIndex  = 2
    kokeIndex  = 3
)

func gifMain() {
    makeGif(os.Stdout, 5, 100, 64, 8) // go build;./gif >out.gif
}

func makeGif(out io.Writer, cycles, size, nframes, delay int) {
    const (
        // cycles  = 5 // 圈数
        // 练习从URL中获取参数
        res     = 0.001
        // size    = 100 // 大小
        // nframes = 64  // 帧数
        // delay   = 8   // 延迟值80ms
    )

    freq := rand.Float64() * 3.0
    anim := gif.GIF{LoopCount: nframes} // 复合声明生成struct结构体
    phase := 0.0
    for i := 0; i < nframes; i++ { // 生成动画帧
        rect := image.Rect(0, 0, 2*size+1, 2*size+1) // 图片大小 201*201
        img := image.NewPaletted(rect, palette)
        for t := 0.0; t < float64(cycles)*2.0*float64(math.Pi); t += res { // 设置偏振值x, y
            x := math.Sin(t)
            y := math.Sin(t*freq + phase)
            img.SetColorIndex(size+int(x*float64(size)+0.5), size+int(y*float64(size)+0.5), balckIndex)
        }
        phase += 0.1
        anim.Delay = append(anim.Delay, delay)
        anim.Image = append(anim.Image, img)
    }
    gif.EncodeAll(out, &anim)
}

小型WEB服务I

1
2
3
4
5
6
7
8
9
func serverMain() {
    http.HandleFunc("/", handler) // 处理发送到/后的服务
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Listen localhost:8000 url.Path=%q\n", r.URL.Path) 
    // 标准输出流, 写入到http的响应中
}

小型WEB服务II

在serve1的基础上添加计数, 和在地址栏为gif输入参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
var mu sync.Mutex // 共享变量
var count int

func server2Main() {
    http.HandleFunc("/", handler3)
    http.HandleFunc("/count", counter)
    http.HandleFunc("/gif", varhandle)
    http.HandleFunc("/code", showCode)
    // HandleFunc 第二参数可简化传入匿名函数
    log.Fatal(http.ListenAndServe("10.0.0.100:8000", nil))
}

func counter(w http.ResponseWriter, r *http.Request) {
    mu.Lock()
    fmt.Fprintf(w, "Count %d\n", count)
    mu.Unlock()
}

// 打印请求头更多的内容
func handler3(w http.ResponseWriter, r *http.Request) {
    mu.Lock() //共享变量加锁
    count++
    mu.Unlock()
    // fmt.Fprintf(w, "URL.Path=%q\n", r.URL.Path)
    // Request 结构体
    fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto)
    for k, v := range r.Header {
        fmt.Fprintf(w, "Header[%q]=%q\n", k, v)
    }
    fmt.Fprintf(w, "Host=%q\n", r.Host)
    fmt.Fprintf(w, "RemoteAdd=%q\n", r.RemoteAddr)
    if err := r.ParseForm(); err != nil { // 限制作用域
        log.Print(err)
    }
    for k, v := range r.Form {
        fmt.Fprintf(w, "Form[%q]=%q\n", k, v)
    }
}
func varhandle(w http.ResponseWriter, r *http.Request) {
    query := r.URL.RawQuery
    Q := strings.Split(query, "?")
    cycles, size, nframes, delay := 5, 100, 64, 8
    for _, q := range Q {
        switch {
        case strings.HasPrefix(q, "cycles="):
            cycles, _ = strconv.Atoi(q[7:])
        case strings.HasPrefix(q, "size="):
            size, _ = strconv.Atoi(q[5:])
        case strings.HasPrefix(q, "nframes="):
            nframes, _ = strconv.Atoi(q[8:])
        case strings.HasPrefix(q, "delay="):
            delay, _ = strconv.Atoi(q[6:])
        }
    }
    fmt.Printf("请求 cycles=%d size=%d nframes=%d delay=%d\n", cycles, size, nframes, delay)
    makeGif(w, cycles, size, nframes, delay)
}

func showCode(w http.ResponseWriter, f *http.Request) {
    fileF, _ := os.Open("code.txt")
    input := bufio.NewScanner(fileF)
    for input.Scan() {
        fmt.Fprintln(w, input.Text())
    }
}

统计二进制1的个数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
var pc [256]byte // 256 * 8

var a [10]int

func init() { // 初始化, 在编译时自动调用
    for x := range pc {
        pc[x] = pc[x/2] + byte(x&1)
    }
}

var pC [256]byte = func() (pC [256]byte) {
    for x := range pC {
        pC[x] = pC[x/2] + byte(x&1)
    }
    return
}()

// PopCount 返回64位无符号整数二进制1的位数
func PopCount(x uint64) int {
    return int(
        pc[byte(x>>(0*8))] +
            pc[byte(x>>(1*8))] +
            pc[byte(x>>(2*8))] +
            pc[byte(x>>(3*8))] +
            pc[byte(x>>(4*8))] +
            pc[byte(x>>(5*8))] +
            pc[byte(x>>(6*8))] +
            pc[byte(x>>(7*8))])
}

// PopCounttttLoooop 循环代替单一的表达式
func PopCounttttLoooop(x uint64) (cnt int) {
    for i := 0; i < 8; i++ {
        cnt += int(pC[byte(x>>(uint32(i)*8))])
    }
    return
}

// PopCounttttDrop 使用 bit运算 x&(x-1) 每次剔除x最后一位非0bit位
func PopCounttttDrop(x uint64) (cnt int) {
    for x != 0 {
        x &= (x - 1)
        cnt++
    }
    return
}

// LocalMain for 测试
func LocalMain() {
    for x := range a { // 默认一位是遍历值
        fmt.Println(x)
    }
    for i, x := range a { // 默认两位是遍历索引和值
        fmt.Println(i, x)
    }
}

自定义类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Package tempconv performs Celsius and Fahrenheit conversions.
package tempconv

import (
    "fmt"
)

// 导出量在声明上方需要有相应的注释, 为了在包的其他文件访问时, 使用godoc查询

// Celsius 摄氏度
type Celsius float64

// Fahrenheit 华氏度
type Fahrenheit float64

const (
    // AbsoluteZeroC 绝对零度
    AbsoluteZeroC Celsius = -273.15
    // FreezingC 结冰点温度
    FreezingC Celsius = 0
    // BoilingC 沸水温度
    BoilingC Celsius = 100
)

// 声明类型的方法集
func (c Celsius) String() string {
    return fmt.Sprintf("%g °C", c)
}

func (f Fahrenheit) String() string {
    return fmt.Sprintf("%g °F", f)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package tempconv

import (
    "fmt"
)

// C2F 摄氏度2华氏度
func C2F(c Celsius) Fahrenheit {
    return Fahrenheit(c*9/2 + 32) // 使用显式类型转换操作(非函数)
}

// F2C 华氏度转化为摄氏度
func F2C(f Fahrenheit) Celsius {
    return Celsius((f - 32) * 5 / 9)
}

func tempconvMain() {
    c := F2C(212.0)
    fmt.Println(c.String()) // 100 °C
    fmt.Printf("%v\n", c)   // 100 °C 优先调用类型的String方法
    fmt.Printf("%s\n", c)   // 100 °C
    fmt.Println(c)          // 100 °C
    fmt.Printf("%g\n", c)   // 100    紧凑输出数值
    fmt.Println(float64(c)) // 100

    zero := AbsoluteZeroC
    fmt.Println(zero)
}

命令行标志参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
    "flag"
    "fmt"
    "strings"
)

// flag标志参数, 返回指针
// 标志参数的名字, 默认值, 描述信息
var n = flag.Bool("n", false, "omit trailing newline")
var sep = flag.String("s", " ", "separator")
var upgrade = flag.Bool("upgrade", false, "upgrade")

func echo4() {
    flag.Parse() // 更新标志参数的值
    fmt.Print(strings.Join(flag.Args(), *sep))
    if *upgrade {
        fmt.Print("Upgrading Now!")
    }
    if !*n { // 是否输出行末
        fmt.Println()
    }
}

func echo4Main() {
    echo4()
    p := new(int) // 匿名变量
    fmt.Printf("%v %T %d\n", p, p, *p)
    *p = 2
    fmt.Println(*p)

    s := []string{"hello", "world", "!"}
    fmt.Println(s)
}

输出当前路径,短变量声明:=的潜在BUG

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
var pwd string

func pcwd() {
    pwd, err := os.Getwd() // 短变量声明覆盖包级变量, BUG
    if err != nil {
        log.Fatalf("os.Getwd failed: %v", err)
    }
    fmt.Printf("Working direction = %s\n", pwd) // 打印日志信息, 以os.Exit(1)结束
}

func pcwd2() {
    var err error
    pwd, err = os.Getwd()
    if err != nil {
        log.Fatalf("os.Getwd failed: %v", err)
    }
}

受约束的指针

在golang中指针无法参与运算

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func pointer() {
    var x int
    var null *int
    x = 2
    px := &x
    fmt.Printf("%v %d %T\n", px, *px, px)
    *px = 3
    fmt.Printf("%v %d %T\n", px, *px, px)
    // &p 取地址
    // *p 取值, 作为左值可被赋值, 作为右值可赋值
    fmt.Println(null == nil)
    // 空指针初始化为零值 nil
}

func f() *int {
    v := 1
    return &v
}

func incr(p *int) int {
    *p++
    return *p
}

func sum(ints ...int) (ret int) {
    for _, x := range ints {
        ret += x
    }
    return 
}
func point1Main() {
    p1, p2 := f(), f()
    fmt.Println(*p1, *p2)
    fmt.Printf("%d %d %d\n", *p1, *p2, incr(p1))
    fmt.Println(*p1, *p2, p1 == p2, incr(p1))
    fmt.Println(sum(1, 2, 3, 4))
}

字符串操作-提取路径的文件名,itoa

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
func basename(s string) string {
   for i := len(s) - 1; i >= 0; i-- {
      if s[i] == '/' {
         s = s[i+1:]
         break
      }
   }
   for i := len(s) - 1; i >= 0; i-- {
      if s[i] == '.' {
         s = s[:i]
         break
      }
   }
   return s
}

func basename2(s string) string {
   slash := strings.LastIndex(s, "/")
   s = s[slash+1:]

   if dotIdx := strings.LastIndex(s, "."); dotIdx >= 0 {
      s = s[:dotIdx]
   }
   return s
}

func int2String(values []int) string {
   var buf bytes.Buffer
   buf.WriteByte('[') //或者用buf.WriteRune()
   for i, v := range values {
      if i > 0 {
         buf.WriteByte(',')
      }
      fmt.Fprintf(&buf, "%d", v)
   }
   buf.WriteByte(']')
   return buf.String()
}

func basenameMain() {
   fmt.Println(basename("./a/b/c/d.py/e.go"))
   fmt.Println(basename2("./a/b/c/d.py/e.go"))
   fmt.Println(int2String(([]int{1, 2, 3})))
}

func stringMain() {
   s := "Hello, 世界"
   fmt.Printf("% x", s)
   fmt.Println(len(s)) // 返回字符串中的字节数目
   fmt.Println("\xe4\xb8\x96 \xe7\x95\x8c")
   fmt.Println("\u4e16 \u754c")
   //fmt.Println('\xe4\xb8\x96') 尽管是16bit仍不是合法rune
   
   for i := 0; i < len(s); {
      r, size := utf8.DecodeRuneInString(s[i:])
      // unicode解码器自动向后寻找至多三个字节
      // 高位是110为多字节码点开始, 10为多字节码点的某个字节开始
      fmt.Printf("%d\t%c\n", i, r)
      i += size
   }
   
   for i, r := range s { // 在range中隐式解码Unicode, 字节自动递增
      fmt.Printf("%d\t%q\t%#[2]x\n", i, r)
   }
   fmt.Println(utf8.RuneCountInString(s))
   s = "こんにちは"
   fmt.Printf("% x\n", s)
   r := []rune(s) // []rune接受string的转换
   fmt.Printf("% x\n", r)
   fmt.Println(string(r))
   for i := 0; i < len(r); i++ {
      fmt.Printf("%d\t%c\n", i, r[i])
   }
}