[关闭]
@octopus 2019-03-11T11:51:09.000000Z 字数 22776 阅读 13093

go 学习笔记

go


一) 基础语法

1. 变量定义

变量要先声明,再赋值
  1. // 声明:
  2. var a int // 声明 int 类型的变量
  3. var b [10] int // 声明 int 类型数组
  4. var c []int // 声明 int 类型的切片
  5. var d *int // 声明 int 类型的指针
  6. // 赋值:
  7. a = 10
  8. b[0] = 10
  9. // 同时声明与赋值
  10. var a = 10
  11. a := 10
  12. a,b,c,d := 1,2,true,"def"

:= 这个符号直接取代了var和type,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用var方式来定义全局变量。

2. 常量定义

  1. const filename = "ab"
  2. const a,b = 3,4 // 常量可作为各种类型调用,此处即可用作int类型,也可用作 float
  3. const(
  4. java = 1
  5. php = 2
  6. python = 3
  7. )
  8. const(
  9. java = iota // 自增值,初始为0
  10. php
  11. python
  12. )

3. 条件语句与循环

if

  1. if a > 100 {
  2. return 100
  3. }else if a >50 {
  4. return 50
  5. }else{
  6. return 0
  7. }
  8. if a,b := 1,2; a+b>3{
  9. fmt.Println(a,b)
  10. }
  11. fmt.Println(a,b) // 错误! a,b的是 if 条件里定义的,作用域仅限于 if 中使用

利用 if 语句判断读取文件时是否有异常

  1. import (
  2. "io/ioutil"
  3. "fmt"
  4. )
  5. func main() {
  6. const filename = "2.txt"
  7. content, err := ioutil.ReadFile(filename)
  8. if(err!=nil){
  9. fmt.Println(err)
  10. }else {
  11. fmt.Printf("%s", content)
  12. }
  13. }

switch

go中的 switch 不需要手动 break
  1. var grade string = "B"
  2. switch marks {
  3. case 90: grade = "A"
  4. case 80: grade = "B"
  5. case 50,60,70 : grade = "C"
  6. default: grade = "D"
  7. }
  8. switch {
  9. case grade == "A" :
  10. fmt.Printf("优秀!\n" )
  11. case grade == "B", grade == "C" :
  12. fmt.Printf("良好\n" )
  13. case grade == "D" :
  14. fmt.Printf("及格\n" )
  15. case grade == "F":
  16. fmt.Printf("不及格\n" )
  17. default:
  18. fmt.Printf("差\n" );
  19. }
  20. fmt.Printf("你的等级是 %s\n", grade );

4. for循环

  1. // 赋值语句;判断语句;递增语句
  2. for i:=100; i>0; i--{
  3. fmt.Println(i)
  4. }
  5. // 无赋值
  6. func test(n int){
  7. for ; n>0 ; n/=2 {
  8. fmt.Println(n);
  9. }
  10. }
  11. // 仅赋值
  12. scanner := bufio.NewScanner(file)
  13. for scanner.Scan(){
  14. fmt.Println(scanner.Text);
  15. }
  16. // 死循环
  17. for{
  18. fmt.Println(1);
  19. }

5. 函数

func eval(a,b int, s string) int{ ... }

  1. // 当有返回多个值时
  2. func test1(a,b int) (int, int){
  3. return a+b, a*b
  4. }
  5. // 为多个返回值起名字(仅用于简单函数)
  6. func test2(a,b int) (q, r int){
  7. q = a+b
  8. r = a*b
  9. return // 自动对号入座返回相应变量
  10. }
  11. q, r := test2(1,2)
  12. // 输出错误
  13. func test(a,b int)(int, error) {
  14. if a+b>100{
  15. return a+b, fmt.Errorf("%s","error!")
  16. }else{
  17. return a+b, nil
  18. }
  19. }

6. 指针

go语言的参数传递是值传递

  1. func main() {
  2. a,b:=1,2
  3. swap_test(&a,&b)
  4. }
  5. func swap_test(a,b *int) {
  6. fmt.Println(a, *b) // 0xc420014050 2
  7. }
  8. // 理解: a,b *int 存的是 int 类型值的地址,当对指针类型的变量 *a 时,就是取出地址对应的值

二)内建容器

1. 数组

定义数组

  1. var arr [3]int // 会初始化为 [0,0,0]
  2. arr := [3]{1,2,3}
  3. arr := [...]{1,2,3,4,5}
  4. arr := [2][4]int // 2行4列

遍历数组

  1. arr := [3]{1,2,3}
  2. for k,v := range(arr){
  3. fmt.Println(k, v) // 0,1 1,2 2,3
  4. }

函数传递(按值传递)

注意,[5]int 与 [10]int 是不同的类型
go 语言一般不直接使用数组,而是使用切片

  1. func printArr(arr [5]int) {
  2. for k,v:=range (arr){
  3. fmt.Println(k,v)
  4. }
  5. }
  6. func main() {
  7. arr :=[5] int {6,7,8,9,10}
  8. printArr(arr)
  9. }

2. 切片

概念

  1. // 前开后闭
  2. arr := [...]{0,1,2,3,4,5,6,7}
  3. arr1 := arr[1:2] // 1
  4. arr2 := arr[:5] // 0,1,2,3,4
  5. arr3 := arr[2:] // 2,3,4,5,6,7
  6. arr4 := arr[:] // 0,1,2,3,4,5,6,7

视图

切片是数组的“视图”,即引用

  1. func updateArr(arr []int) { // []中不写具体大小,表示是切片,引用传递
  2. arr[0] = 100
  3. }
  4. func main() {
  5. arr :=[5] int {0,1,2,3,4}
  6. arr1 := arr[1:3]
  7. fmt.Println(arr,arr1) // [0 1 2 3 4] [1 2]
  8. updateArr(arr1)
  9. fmt.Println(arr,arr1) // [0 100 2 3 4] [100 2]
  10. }

切片的扩展(cap)

  1. // 切片的切片依然是对一个数组的引用
  2. func updateArr(arr []int) {
  3. arr[0] = 100
  4. }
  5. func main() {
  6. arr :=[5] int {0,1,2,3,4}
  7. arr1 := arr[1:3]
  8. arr2 := arr1[0:3]
  9. fmt.Println(arr,arr1,arr2) // [0 1 2 3 4] [1 2] [1 2 3]
  10. updateArr(arr1)
  11. fmt.Println(arr,arr1,arr2) // [0 100 2 3 4] [100 2] [100 2 3]
  12. }
  13. // 查看扩展
  14. arr :=[5] int {0,1,2,3,4}
  15. arr1 := arr[1:3]
  16. arr2 := arr1[0:3]
  17. fmt.Println(arr1,len(arr1),cap(arr1)) // [1 2] 2 4
  18. fmt.Println(arr2,len(arr2),cap(arr2)) // [1 2 3] 3 4

直接创建切片

  1. 1) s := []int{1,2,3}
  2. 2) var s []int // 会初始化为 nil
  3. 3) s := make([]int, 16, 32) // make(切片类型,切片长度,切片cap长度)

添加元素

  1. // 若添加元素个数不超过cap值,则对原数组进行修改
  2. arr :=[5] int {0,1,2,3,4}
  3. arr1 := arr[1:3]
  4. arr2 := append(arr1, 10, 11)
  5. fmt.Println(arr1,arr2,arr) // [1 2] [1 2 10 11] [0 1 2 10 11]
  6. // 若添加元素个数超过cap值,则开辟新数组,拷贝并添加
  7. arr :=[5] int {0,1,2,3,4}
  8. arr1 := arr[1:3]
  9. arr2 := append(arr1, 10, 11, 12)
  10. fmt.Println(arr1,arr2,arr) // [1 2] [1 2 10 11 12] [0 1 2 3 4]
  11. func main() {
  12. var s []int
  13. for i:=0; i<10; i++ {
  14. s = append(s,i)
  15. fmt.Println(s, cap(s))
  16. }
  17. }
  18. 结果:(当cap超出,就会重新分配cap值更大的新数组)
  19. [0] 1
  20. [0 1] 2
  21. [0 1 2] 4
  22. [0 1 2 3] 4
  23. [0 1 2 3 4] 8
  24. [0 1 2 3 4 5] 8
  25. [0 1 2 3 4 5 6] 8
  26. [0 1 2 3 4 5 6 7] 8
  27. [0 1 2 3 4 5 6 7 8] 16
  28. [0 1 2 3 4 5 6 7 8 9] 16

copy

  1. s1 := []int{0,1,2,3}
  2. s2 := make([]int, 6)
  3. copy(s2,s1)
  4. fmt.Println(s1,s2) // [0 1 2 3] [0 1 2 3 0 0]

3. Map

定义

  1. 1) m := map[string]int{} // nil
  2. 2) var m map[string]string // nil
  3. 3) m := make(map[string]string) // empty map
  4. m2 := map[string]string{
  5. "name":"zy",
  6. "age":"10",
  7. }
  8. fmt.Println(m2) // map[name:zy age:10]

遍历

map 是无序的hash map,所以遍历时每次输出顺序不一样

  1. m := map[string]string{
  2. "name":"zy",
  3. "age":"10",
  4. }
  5. for k,v := range m{
  6. fmt.Println(k,v)
  7. }

取值

  1. m := map[string]string{
  2. "name":"zy",
  3. "age":"10",
  4. }
  5. name := m["name"]
  6. fmt.Println(name) // “zy”
  7. // 获取一个不存在的值
  8. value := m["aaa"]
  9. fmt.Println(value) // “” 返回一个空值
  10. // 判断key是否存在
  11. value, ok := m["aaa"]
  12. fmt.Println(value, ok) // "" false
  13. // 标准用法:
  14. if v,ok:= m["name"]; ok{
  15. fmt.Println(v)
  16. }else{
  17. fmt.Println("key not exist!")
  18. }

del

  1. delete(m, "name")

三) 面向对象

go语言的面向对象仅支持封装,不支持继承和多态

1. 结构体

定义

  1. // 定义一个结构体
  2. type treeNode struct {
  3. value int
  4. left,right *treeNode
  5. }
  6. func main() {
  7. root := treeNode{1,nil,nil}
  8. node1 := treeNode{value:3}
  9. root.left = &node1
  10. root.left.right = new(treeNode) // 内建函数初始化node
  11. nodes := []treeNode{
  12. {1,nil,nil},
  13. {2,&root,&node1},
  14. }
  15. fmt.Println(nodes[1].left.left.value) // 3
  16. }

自定义工厂函数

由于没有构造函数,所以可以用工厂函数代替

  1. func createNode(value int) *treeNode {
  2. return &treeNode{value:value}
  3. }
  4. func main(){
  5. node := createNode(10)
  6. fmt.Println(node) // &{10 <nil> <nil>
  7. }

结构体方法

结构体方法并不是写在结构体中,而是像函数一样写在外面,它实际上久是定义了[接收对象]的函数

由于本质依然是函数,所以也是按值传递,若要改变对象,用指针

  1. type treeNode struct {
  2. value int
  3. left,right *treeNode
  4. }
  5. // func (接收对象) 方法名(参数) 返回值{}
  6. func (node treeNode) get() int{
  7. return node.value
  8. }
  9. func (node *treeNode) set(value int) {
  10. node.value = value
  11. }
  12. func main() {
  13. root := treeNode{2,nil,nil}
  14. res := root.get()
  15. fmt.Println(res) // 2
  16. }

2.封装

  1. 名字一般用 CamelCase
  2. 首字母大写是 public 方法
  3. 首字母小写是 private 方法(2和3 对于变量常量也依然适用)

每个目录只有一个包 (package)
main包包含程序入口
为某结构体定义的方法必须放在同一个包内,但可以放不同文件

  1. /* 目录结构
  2. go
  3. |__tree1 // 为了讲解,目录名,文件名,包名不同
  4. |——treeNode.go
  5. |__main
  6. |_main.go
  7. */
  8. // treeNode.go
  9. package tree
  10. type TreeNode struct { // 外部可用的结构体
  11. value int // 私有属性
  12. left TreeNode
  13. right TreeNode
  14. }
  15. func (node TreeNode) Get() int{ // 公有方法
  16. return node.Value
  17. }
  18. func (node *treeNode) Set(value int) {
  19. node.value = value
  20. }
  21. // main.go
  22. package main
  23. import "../../tree1"
  24. func main() {
  25. node := tree.TreeNode{}
  26. node.Set(2)
  27. res := node.Get(); // 2
  28. }
  29. // 总结:import 包所属目录名,就可以使用 包名.结构体/方法 ,与目录下的文件名无关

没有继承

go语言没有继承,如何扩展系统类型或者自定义类型呢?

  1. 定义别名
  2. 使用组合

配置 $GOPATH 与生成可执行文件

  1. // vim .bash_profile
  2. export GOPATH=/Users/yuzhang/go
  3. PATH=$PATH:/usr/local/mysql/bin:$GOPATH/bin
  4. > source .bash_profile
  5. > go install ./... // 将本目录下所有的go文件编译成可执行文件,放在 $GOPATH/bin 下

获取第三方库

go get xxx 从官方下载第三方库,需要翻墙
gopm 可以获取国内镜像

  1. // 首先需要下载 go // -v 表示详细信息,可省略
  2. 2. gopm 可执行文件放在 path 环境变量可找到地方
  3. 3. gopm get -g -v golang.org/x/tools/cmd/goimports
  4. 4. go install golang.org/x/tools/cmd/goimports

3. 接口

定义

  1. type xxx interface{
  2. FuncName() string // 定义接口方法与返回类型
  3. }

实现

结构体不需要显示“实现”接口,只要定义好接口方法即可

  1. // interface/interface.go
  2. package file
  3. type File interface {
  4. Read() string
  5. Write(str string)
  6. }
  7. // interface/implament.go
  8. // File1 结构体实现了接口规定的方法
  9. package file
  10. type File1 struct {
  11. Content string
  12. }
  13. func (file File1) Read() string{
  14. return file.Content
  15. }
  16. func (file *File1) Write(str string) {
  17. file.Content = str
  18. }
  19. // interface/entry/main.go
  20. package main
  21. import (
  22. "../../interface"
  23. "fmt"
  24. )
  25. func get(f file.File) string { // 只有实现了 File 接口的结构体实例才能调用此方法
  26. res := f.Read()
  27. return res
  28. }
  29. func main() {
  30. f := file.File1{}
  31. f.Write("www")
  32. fmt.Println(get(f))
  33. }

类型

查看类型: i.(type)

  1. var i AnimalInterface // 定义变量 i 是动物接口类型
  2. i = Cat{"cat"} // 假设 Cat 结构体实现了 AnimalInterface 接口
  3. i.(type) // Cat
  4. i = Dog{"dog"} // 假设 Dog 结构体实现了 AnimalInterface 接口
  5. i.(type) // Dog

约束接口类型:i.(xxx)

  1. var i AnimalInterface // 定义变量 i 是动物接口类型
  2. i = Cat{"cat"} // 假设 Cat 结构体实现了 AnimalInterface 接口
  3. cat := i.(Cat) // 如果 i 是Cat类型的,则拷贝赋值给 cat变量,否则报错)
  4. if dog, ok := i.(Dog); ok{
  5. // ok
  6. }else{
  7. // i isn't dog
  8. }

泛型: interface{}

  1. type Queue []int // 定义了一个 int 类型的切片
  2. func (q *Queue) Push(v int){
  3. *q = append(*q, v)
  4. }
  5. func (q *Queue) Pop() int{
  6. head := (*q)[0]
  7. *q = (*q)[1:]
  8. return head
  9. }
  10. // 将上面的切片改成可以接受任意类型:
  11. type Queue []interface{} // 定义了一个 int 类型的切片
  12. func (q *Queue) Push(v interface{}){
  13. *q = append(*q, v)
  14. }
  15. func (q *Queue) Pop() interface{}{
  16. head := (*q)[0]
  17. *q = (*q)[1:]
  18. return head
  19. }
  20. // 强制类型转换:
  21. head.(int)

组合

  1. type Cat interface{
  2. cat()
  3. }
  4. type Dog interface{
  5. dog()
  6. }
  7. type Animal interface{ // 要实例既实现 Cat 又实现 Dog
  8. Cat
  9. Dog
  10. }
  11. func get(i Animal){
  12. i.cat()
  13. i.dog()
  14. }

常用系统接口

  1. // 1. 类似 toString() 的信息打印接口
  2. type Stringer interface{
  3. String() string
  4. }
  5. // 2. Reader
  6. type Reader interface{
  7. Read(p []byte) (n int, err error)
  8. }
  9. // 3. Writer
  10. type Writer interface{
  11. Write(p []byte) (n int, err error)
  12. }

四) 函数式编程

1. 闭包

  1. func add() func(int) int {
  2. sum := 0 // 此处的 sum 为自由变量
  3. return func(v int) int {
  4. sum += v // 指向外层sum
  5. return sum
  6. }
  7. }
  8. func main(){
  9. add := add()
  10. for i:=0; i<10; i++ {
  11. fmt.Printf(add(i)) // 从 0 到 10 的累加
  12. }
  13. }

生成器

  1. // 一个斐波那契数列的生成器
  2. func fib() func() int{
  3. a, b := 0, 1
  4. return func() int{
  5. a, b = b, a+b
  6. return a
  7. }
  8. }
  9. func main(){
  10. f = fib()
  11. f() // 1
  12. f() // 1
  13. f() // 2
  14. f() // 3
  15. }

2. 函数接口

补充:接口 Read 与 Write

  1. package main
  2. import (
  3. "io"
  4. "bufio"
  5. "fmt"
  6. "strings"
  7. )
  8. type funcType func() int
  9. func fib() funcType{
  10. a, b := 0, 1
  11. return func() int{
  12. a, b = b, a+b
  13. return a
  14. }
  15. }
  16. func (f funcType) Read(p []byte) (n int, err error) {
  17. next := f()
  18. if next > 1000 {
  19. return 0, io.EOF
  20. }
  21. s := fmt.Sprintf("%d ", next)
  22. return strings.NewReader(s).Read(p)
  23. }
  24. func scan(read io.Reader) {
  25. scanner := bufio.NewScanner(read)
  26. for scanner.Scan() {
  27. text := scanner.Text()
  28. fmt.Printf(text)
  29. }
  30. }
  31. func main() {
  32. f := fib()
  33. scan(f)
  34. }

五) 错误处理与资源管理

1. defer

有 defer 关键字修饰的语句,会在 return 前执行
若有多个 defer 则保持栈的性质,先进后出,最先定义的最后执行

  1. func writeFile(filename string) {
  2. file, err := os.Create(filename)
  3. if err!=nil {
  4. panic("创建文件失败!") // 打印错误信息
  5. }
  6. defer file.Close() // 函数执行完毕前。关闭文件句柄
  7. writer := bufio.NewWriter(file)
  8. defer writer.Flush() // 函数执行完毕前,将缓冲区中的内容刷新到文件中去
  9. for i:=0; i<100; i++ {
  10. fmt.Fprintln(writer,i) // 写入缓冲区
  11. }
  12. }
  13. func main() {
  14. writeFile("1.txt")
  15. }

2. panic 与 recover

panic:
1. 停止当前函数执行
2. 停止之前执行每层的 defer
3. 如果没有 recover 程序直接退出

recover:
1. 仅在 defer 中调用
2. 可以获取 panic 的值
3. 如果无法处理,可重新 panic
(个人理解就像 try cache 中的 cache 可以捕获异常)
  1. // 例一:捕获 panic
  2. func dopanic(){
  3. defer func() {
  4. err := recover()
  5. fmt.Println(err) // error!!
  6. }()
  7. panic("error!!")
  8. }
  9. func main() {
  10. dopanic()
  11. }
  12. // 例二:捕获其他异常
  13. func dopanic(){
  14. defer func() {
  15. err := recover()
  16. fmt.Println(err) // runtime error: integer divide by zero
  17. }()
  18. a := 0
  19. b := 1/a
  20. fmt.Println(b)
  21. }
  22. func main() {
  23. dopanic()
  24. }
  25. // 例三:无异常处理
  26. func dopanic(){
  27. defer func() {
  28. err := recover()
  29. if err, ok := err.(error); ok{
  30. fmt.Println(err)
  31. }else{
  32. fmt.Println("no error!")
  33. }
  34. }()
  35. a := 0
  36. fmt.Println(a)
  37. }
  38. func main() {
  39. dopanic()
  40. }

六) goroutine 并发

1. goroutine

go func(){}()
用 go 关键字开启协程,协程是非抢占式多任务处理,由协程主动交出控制权

  1. func main() {
  2. for i:=0; i<1000; i++{
  3. go func(i int) {
  4. for {
  5. fmt.Println(i); // IO 操作,会主动交出控制权
  6. }
  7. }(i)
  8. }
  9. time.Sleep(time.Microsecond);
  10. }
  11. // 如果没有 time.Sleep,在for执行完 i 的自增后就会立刻结束程序,此时协程还没来得及处理就结束了,没有任何打印
  1. func main() {
  2. var arr [10]int
  3. for i:=0; i<1000; i++{
  4. go func(i int) {
  5. for {
  6. arr[i]++ // 不会交出控制权,所以会在这里死循环
  7. }
  8. }(i)
  9. }
  10. time.Sleep(time.Microsecond)
  11. }

goroutine可能交出控制权的点:
- I/O操作,select
- channel
- 等待锁
- 函数调用时(有时,不一定)
- runtime.Gosched()

2. channel

channel其实就是传统语言的阻塞消息队列,可以用来做不同goroutine之间的消息传递

定义

- 声明一个channel:
    var c chan int
    c := make(chan int)

- 使用 channel:
    c <- 1      // 向管道中写入
    d := <-c    // 从管道读出
  1. func worker(c chan int) {
  2. for {
  3. r := <-c
  4. fmt.Println(r)
  5. }
  6. }
  7. func main() {
  8. c := make(chan int)
  9. go worker(c)
  10. c <- 1
  11. c <- 2
  12. time.Sleep(time.Microsecond)
  13. }

更进一步:channel工厂

  1. func workerFactory(i int) chan int{
  2. c := make(chan int)
  3. go func(c chan int) {
  4. fmt.Println( <-c )
  5. }(c)
  6. return c
  7. }
  8. func main() {
  9. var arr [10] chan int
  10. for i:=0; i<10 ; i++ {
  11. arr[i] = workerFactory(i) // 创建 channel 并监听
  12. }
  13. for i:=0; i<10 ; i++ {
  14. arr[i] <- i // 向各channel中输入
  15. }
  16. time.Sleep(time.Microsecond)
  17. }

channel 方向(只读与只写)

  1. func workerFactory() chan <- int{ // 返回的是只写channel
  2. c := make(chan int)
  3. go func(c chan int) {
  4. fmt.Println( <-c )
  5. }(c)
  6. return c
  7. }
  8. c := workerFactory(0)
  9. r := <- c // 报错!
  10. // 同理, `<- chan int` 是只读channel

缓冲区

向管道中写入就必须定义相应的输出,否则会报错
有缓冲区与无缓冲区的区别是 一个是同步的 一个是非同步的,即阻塞型队列和非阻塞队列 详解:https://blog.csdn.net/samete/article/details/52751227

  1. c := make(chan int, 3) // 缓冲区长度3
  2. c<-1
  3. c<-2
  4. c<-3 // 在这之前都在缓冲区中,不报错
  5. c<-4 // 报错

关闭管道

当确定不再向缓冲区中发送数据,可以关闭管道,若还有协程在不断接收 管道数据,则会源源不断的收到 0 即空串,直到程序结束。

  1. func workerFactory() chan int{
  2. c := make(chan int, 3)
  3. go func(c chan int) {
  4. for {
  5. fmt.Println( <-c ) // 一直读取管道
  6. }
  7. }(c)
  8. return c
  9. }
  10. func main() {
  11. var c chan int
  12. c = workerFactory()
  13. c <- 1
  14. c <- 2
  15. close(c) // 关闭管道
  16. time.Sleep(time.Second)
  17. }
  18. // 结果:
  19. // 1,2,0,0,0,0,0...
  20. // 改进:
  21. for {
  22. n, ok := <-c
  23. if !ok {
  24. break
  25. }
  26. fmt.Println( n )
  27. }
  28. // 或
  29. for n := range c{
  30. fmt.Println( n )
  31. }

用channel等待任务结束

上面的例子使用 time.Sleep(time.Microsecond)来等待任务结束,不精确且耗时

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type worker struct {
  6. in chan int // in 用来读写
  7. done chan bool // done 用来表示已读取完成
  8. }
  9. func createWorker() worker{
  10. worker := worker{
  11. in:make(chan int),
  12. done:make(chan bool),
  13. }
  14. doWorker(worker);
  15. return worker
  16. }
  17. func doWorker(w worker) {
  18. go func(w worker) {
  19. for {
  20. fmt.Println(<-w.in)
  21. go func(w worker) { // 注意此处也要 go,不然阻塞
  22. w.done<-true
  23. }(w)
  24. }
  25. }(w)
  26. }
  27. func main() {
  28. var arr [10] worker
  29. for i:=0; i<10; i++ {
  30. arr[i] = createWorker()
  31. }
  32. for i:=0; i<10; i++ {
  33. arr[i].in<-i
  34. }
  35. for i:=0; i<10; i++ {
  36. arr[i].in<-i
  37. }
  38. for i:=0; i<10; i++ {
  39. <-arr[i].done
  40. <-arr[i].done
  41. }
  42. }

用 sync.WaitGroup 等待任务结束

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. type worker struct {
  7. in chan int
  8. wg *sync.WaitGroup // *
  9. }
  10. func createWorker(wg *sync.WaitGroup) worker{
  11. worker := worker{
  12. in:make(chan int),
  13. wg:wg,
  14. }
  15. doWorker(worker);
  16. return worker
  17. }
  18. func doWorker(w worker) {
  19. go func(w worker) {
  20. for {
  21. fmt.Printf("%c \n", <-w.in)
  22. w.wg.Done() // 发送任务结束的信号
  23. }
  24. }(w)
  25. }
  26. func main() {
  27. var wg sync.WaitGroup // 定义WaitGroup
  28. var arr [10] worker
  29. for i:=0; i<10; i++ {
  30. arr[i] = createWorker(&wg) //按址传递,用一个引用来开始和结束
  31. }
  32. for i:=0; i<10; i++ {
  33. wg.Add(1) // 开始一个任务前,计时器加一(一定要在开始前加)
  34. arr[i].in <- 'a'+i
  35. }
  36. for i:=0; i<10; i++ {
  37. wg.Add(1)
  38. arr[i].in <- 'A'+i
  39. }
  40. wg.Wait() // 阻塞等待所有任务 done
  41. }

3. select

用法

  1. func main() {
  2. var c1,c2 chan int
  3. select {
  4. case n:=<-c1:
  5. fmt.Println(n)
  6. case n2:=<-c2:
  7. fmt.Println(n2)
  8. default:
  9. fmt.Println("no value") // ✔️
  10. }
  11. }
  1. func create(i int) chan int{
  2. c := make(chan int)
  3. go func(c chan int, i int) {
  4. for {
  5. c <- 'a'+i
  6. }
  7. }(c,i)
  8. return c
  9. }
  10. func main() {
  11. c1,c2 := create(1),create(2)
  12. for {
  13. select { // 由于没有 default,会一直阻塞读
  14. case n1 := <-c1:
  15. fmt.Printf("%c \n",n1)
  16. case n2 := <-c2:
  17. fmt.Printf("%c \n",n2)
  18. }
  19. }
  20. }

定时器

  1. func main() {
  2. c1,c2 := create(1),create(2)
  3. tm := time.After(2*time.Second) // 定时器,2秒后触发
  4. tm2 := time.Tick(1*time.Second) // 每1秒触发一次
  5. for {
  6. select {
  7. case n1 := <-c1:
  8. fmt.Printf("%c \n",n1)
  9. case n2 := <-c2:
  10. fmt.Printf("%c \n",n2)
  11. case t := <- tm2:
  12. fmt.Println("------- ",t," -----------")
  13. case <- tm:
  14. fmt.Println("bye")
  15. return
  16. }
  17. }
  18. }

七) 标准库

查看标准库文档的方式:
1. 自己启动一个文档服务器: > godoc -http :8888 然后访问 localhost:8888
2. 标准库中文版:https://studygolang.com/pkgdoc

1. http

简单的请求获取页面

  1. func main() {
  2. res, err := http.Get("https://www.baidu.com/"); // 返回响应对象指针
  3. if err != nil {
  4. panic(err)
  5. }
  6. defer res.Body.Close() // 最后关闭 响应 资源
  7. str, err := httputil.DumpResponse(res, true)
  8. if err != nil {
  9. panic(err)
  10. }
  11. fmt.Printf("%s", str)
  12. }
  13. resp, err := http.Get("http://example.com/") // GET
  14. resp, err := http.Post("http://example.com/") // POST
  15. resp, err := http.PostForm("http://example.com/", url.Values{"foo": "bar"}) // 提交表单

复杂请求,带响应头等信息

  1. func main() {
  2. request, err := http.NewRequest(http.MethodGet,"https://www.baidu.com/", nil); // 返回请求对象指针
  3. request.Header.Add("User-Agent","Mozilla/5.0 ...") // 添加头信息
  4. res, err := http.DefaultClient.Do(request) // 默认简易版客户端访问
  5. if err != nil {
  6. panic(err)
  7. }
  8. defer res.Body.Close()
  9. str, err := httputil.DumpResponse(res, true)
  10. if err != nil {
  11. panic(err)
  12. }
  13. fmt.Printf("%s", str)
  14. }

自定义客户端访问,可查看重定向,cookie等信息

  1. func main() {
  2. request, err := http.NewRequest(http.MethodGet,"https://www.baidu.com/", nil);
  3. request.Header.Add("User-Agent","Mozilla/5.0...")
  4. client := http.Client{ // 自定义客户端
  5. CheckRedirect: func(req *http.Request, via []*http.Request) error {
  6. fmt.Println(req)
  7. return nil
  8. },
  9. // ...
  10. }
  11. res, err := client.Do(request)
  12. if err != nil {
  13. panic(err)
  14. }
  15. defer res.Body.Close()
  16. str, err := httputil.DumpResponse(res, true)
  17. if err != nil {
  18. panic(err)
  19. }
  20. fmt.Printf("%s", str)
  21. }

也可以创建HTTP服务器

  1. package main
  2. import (
  3. "net/http"
  4. "io/ioutil"
  5. "log"
  6. "io"
  7. )
  8. func EchoServer(w http.ResponseWriter, req *http.Request) {
  9. body, _ := ioutil.ReadAll(req.Body)
  10. io.WriteString(w, string(body))
  11. }
  12. func main() {
  13. http.HandleFunc("/echo/", EchoServer) // 路由处理函数
  14. log.Fatal(http.ListenAndServe(":8080", nil))
  15. }

2. time

获取当前时间 (标准时间)

  1. func main() {
  2. fmt.Println(time.Now())
  3. }
  4. // 2018-12-28 09:28:41.5305037 +0800 CST m=+0.027019001
解析:
time.Now() 返回了 time 结构体本身,该结构体实现了很多方法,如可以获得年月日信息等。

cmd-markdown-logo

获取当前时间的时间戳

  1. func main() {
  2. fmt.Println(time.Now().Unix())
  3. }
  4. // 1545961249

时间戳转换为标准时间

  1. func main() {
  2. now := time.Now().Unix()
  3. fmt.Println( time.Unix(now, 0) )
  4. }
  5. // 2018-12-28 10:33:43 +0800 CST
time.Unix(unixtime, 0) 可以将时间戳转化为标准时间,第二个参数只有在 [0,1e9] 范围外有效,推测是时间的偏移量,一般情况下传0即可

标准时间格式化

  1. func main() {
  2. now := time.Now()
  3. res := now.Format("2006-01-02 15:04:05")
  4. fmt.Println(res)
  5. }
  6. // 2018-12-28 11:54:54
其中,“2006-01-02 15:04:05” 是固定日期参照格式,不可以写成其他数字。

字符串转标准时间

  1. func main() {
  2. now := "2018/12/28 11:54:54"
  3. res,_ := time.Parse("2006/01/02 15:04:05", now)
  4. fmt.Println(res)
  5. }
  6. // 2018-12-28 11:54:54 +0000 UTC
注意time.Parse的第一个参数是时间模板,要与第二个参数,也就是待转化的字符串格式一模一样。返回 Time 类型

设置时区

  1. func main() {
  2. loc, _ := time.LoadLocation("Asia/Shanghai")
  3. fmt.Println(loc)
  4. }
  5. // Asia/Shanghai
返回一个 *Location 结构体

根据时区进行字符串到标准时间的转换

  1. func main() {
  2. now := "2018/12/28 11:54:54"
  3. loc, _ := time.LoadLocation("Asia/Shanghai")
  4. res,_ := time.ParseInLocation("2006/01/02 15:04:05",now,loc)
  5. fmt.Println(res)
  6. }
time.ParseInLocation 有三个参数:时间格式模板,需要转化的时间字符串,时区对象,返回标准时间。

3. io.ioutil

ReadDir

ioutil.ReadDir(dirname string) 列出文件夹与文件名

  1. func main() {
  2. files, err := ioutil.ReadDir(".")
  3. if err != nil {
  4. log.Fatal(err)
  5. }
  6. for _, file := range files {
  7. fmt.Println(file.Name(), file.IsDir())
  8. }
  9. }
  10. /*
  11. main.go false
  12. pipline true
  13. process true
  14. tmp true
  15. */

ReadDir() 返回一个 []os.FileInfo ,是一个 os.FileInfo 类型的切片,FileInfo 结构体如下

  1. type FileInfo interface {
  2. Name() string // base name of the file
  3. Size() int64 // length in bytes for regular files;
  4. Mode() FileMode // file mode bits
  5. ModTime() time.Time // modification time
  6. IsDir() bool // abbreviation for Mode().IsDir()
  7. Sys() interface{} // underlying data source (can return nil)
  8. }

ReadFile

ioutil.ReadFile(filename string) 读取文件内容

  1. func main() {
  2. data, err := ioutil.ReadFile("tmp/access.log")
  3. if err!=nil{
  4. os.Exit(1)
  5. }
  6. fmt.Println(string(data))
  7. }
  8. /*
  9. 第一行数据
  10. 第二行数据
  11. 第三行数据
  12. */

ioutil.ReadFile 的本质是:

  1. 1. os.Open(filename) os打开文件
  2. 2. ioutil.readAll(r io.Reader, capacity int64) 读取所有

ReadAll()

func ReadAll(r io.Reader) ([]byte, error) 读取读取器内的内容

  1. func main() {
  2. reader := strings.NewReader("hello word widuu") //返回*strings.Reader
  3. data, _ := ioutil.ReadAll(reader)
  4. fmt.Println(string(data))
  5. }

4. os

os 包提供了不依赖平台的操作系统函数接口。

常用系统函数

  1. // Hostname返回内核提供的主机名
  2. func Hostname() (name string, err error)
  3. // Environ返回表示环境变量的格式为”key=value”的字符串的切片拷贝
  4. func Environ() []string
  5. // Getenv检索并返回名为key的环境变量的值
  6. func Getenv(key string) string
  7. // Getpid返回调用者所在进程的进程ID
  8. func Getpid() int
  9. // Exit让当前程序以给出的状态码code退出。一般来说,状态码0表示成功,非0表示出错。程序会立刻终止,defer的函数不会被执行
  10. func Exit(code int)
  11. // 获取文件信息
  12. func Stat(name string) (fi FileInfo, err error)
  13. // Getwd返回一个对应当前工作目录的根路径
  14. func Getwd() (dir string, err error)
  15. // 使用指定的权限和名称创建一个目录
  16. func Mkdir(name string, perm FileMode) error
  17. // 使用指定的权限和名称创建一个目录,包括任何必要的上级目录,并返回nil,否则返回错误
  18. func MkdirAll(path string, perm FileMode) error
  19. // 删除name指定的文件或目录
  20. func Remove(name string) error
  21. // 返回一个用于保管临时文件的默认目录
  22. func TempDir() string

File 结构体

  1. // Create采用模式0666(任何人都可读写,不可执行)创建一个名为name的文件,如果文件已存在会截断它(为空文件)
  2. func Create(name string) (file *File, err error)
  3. // Open打开一个文件用于读取。如果操作成功,返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式
  4. func Open(name string) (file *File, err error)
  5. // OpenFile用指定模式打开一个文件,参数:文件路径、打开模式、文件权限
  6. func OpenFile(name string, flag int, perm FileMode) (*File, error)
  7. // Stat返回描述文件f的FileInfo类型值
  8. func (f *File) Stat() (fi FileInfo, err error)
  9. // Readdir读取目录f的内容,返回一个有n个成员的[]FileInfo,这些FileInfo是被Lstat返回的,采用目录顺序
  10. func (f *File) Readdir(n int) (fi []FileInfo, err error)
  11. // Read方法从f中读取最多len(b)字节数据并写入b
  12. func (f *File) Read(b []byte) (n int, err error)
  13. // 向文件中写入字符串
  14. func (f *File) WriteString(s string) (ret int, err error)
  15. // Sync递交文件的当前内容进行稳定的存储。一般来说,这表示将文件系统的最近写入的数据在内存中的拷贝刷新到硬盘中稳定保存
  16. func (f *File) Sync() (err error)
  17. // Close关闭文件f,使文件不能用于读写
  18. func (f *File) Close() error

5. strconv

  1. // 将布尔值转换为字符串 true 或 false
  2. func FormatBool(b bool) string
  3. // 将字符串转换为布尔值
  4. // 它接受真值:1, t, T, TRUE, true, True
  5. // 它接受假值:0, f, F, FALSE, false, False
  6. // 其它任何值都返回一个错误。
  7. func ParseBool(str string) (bool, error)
  8. // 将整数转换为字符串形式。base 表示转换进制,取值在 2 到 36 之间。
  9. func FormatInt(i int64, base int) string
  10. // 将字符串解析为整数,ParseInt 支持正负号,ParseUint 不支持正负号。
  11. // base 表示进位制(2 到 36),如果 base 为 0,则根据字符串前缀判断,
  12. // 前缀 0x 表示 16 进制,前缀 0 表示 8 进制,否则是 10 进制。
  13. // bitSize 表示结果的位宽(包括符号位),0 表示最大位宽。
  14. func ParseInt(s string, base int, bitSize int) (i int64, err error)
  15. func ParseUint(s string, base int, bitSize int) (uint64, error)
  16. // 将整数转换为十进制字符串形式(即:FormatInt(i, 10) 的简写)
  17. func Itoa(i int) string
  18. // 将字符串转换为十进制整数,即:ParseInt(s, 10, 0) 的简写)
  19. func Atoi(s string) (int, error)

6. strings

HasPrefix 判断前缀

  1. func main() {
  2. res := strings.HasPrefix("test.com", "te")
  3. fmt.Println(res)
  4. }
  5. // true

HasSuffix 判断后缀

  1. func main() {
  2. res := strings.HasSuffix("test.com", "com")
  3. fmt.Println(res)
  4. }
  5. // true

Contains 是否包含子字符串

  1. func main() {
  2. res := strings.Contains("test.com", ".c")
  3. fmt.Println(res)
  4. }
  5. // true

Index 判断子字符串或字符在父字符串中出现的位置(索引)

  1. func main() {
  2. res := strings.Index("test.com", ".c")
  3. fmt.Println(res)
  4. }
  5. // 4

LastIndex 最后出现位置的索引

  1. func main() {
  2. res := strings.LastIndex("test.com", "t")
  3. fmt.Println(res)
  4. }
  5. // 3

Replace 替换字符

  1. func main() {
  2. s := "test.com.es"
  3. res := strings.Replace(s, "es","oo",-1)
  4. fmt.Println(res)
  5. }
  6. // toot.com.oo
strings.Replace(s, old, new, time) 参数分别是:字符串,要被替换的子串,新子串,若有多个满足条件的子串替换次数(若为-1则全部替换)

Count 子串出现的次数

  1. func main() {
  2. s := "test.com.es"
  3. res := strings.Count(s, "c")
  4. fmt.Println(res)
  5. }
  6. // 1

ToLower 全部小写

  1. func main() {
  2. s := "Test.Com.Es"
  3. res := strings.ToLower(s)
  4. fmt.Println(res)
  5. }
  6. // test.com.es

ToUpper 全部大写

  1. func main() {
  2. s := "Test.Com.Es"
  3. res := strings.ToUpper(s)
  4. fmt.Println(res)
  5. }
  6. // TEST.COM.ES

TrimSpace 去掉开头与结尾的空格和换行符

  1. func main() {
  2. s := " Test.Com.Es \n"
  3. res := strings.TrimSpace(s)
  4. fmt.Println(res)
  5. }
  6. // Test.Com.Es (前后无空格和换行符)

Trim 去掉开头与结尾的指定字符

  1. func main() {
  2. s := "A.Test.Com.Es.A"
  3. res := strings.Trim(s,"A")
  4. fmt.Println(res)
  5. }
  6. // .Test.Com.Es.
trim 家族还有其他方法,如 TrimRight,TrimLeft 等,带有第二个参数,可以自定义去除的子串。

Split 字符串分割为数组

  1. func main() {
  2. s := "A.Test.Com.Es.A"
  3. res := strings.Split(s,".")
  4. fmt.Println(res)
  5. }
  6. // [A Test Com Es A]

Join 拼接字符串数组为字符串

  1. func main() {
  2. s := []string{"hello","world"}
  3. res := strings.Join(s,",")
  4. fmt.Println(res)
  5. }
  6. // hello,world

7. url

Parse

url.Parse 返回一个 *URL 结构体,结构体的成员变量如下:

  1. type URL struct {
  2. Scheme string
  3. Opaque string
  4. User *Userinfo
  5. Host string
  6. Path string
  7. RawPath string
  8. ForceQuery bool
  9. RawQuery string
  10. Fragment string
  11. }
  1. func main() {
  2. str := "https://root:123456@www.baidu.com:8080/login/xxx?&name=xiaoqing&age=24#fff"
  3. u, err := url.Parse(str)
  4. if err == nil {
  5. scheme := u.Scheme
  6. fmt.Println(scheme) // "https"
  7. opaque := u.Opaque
  8. fmt.Println(opaque) // ""
  9. user := u.User
  10. fmt.Println(user) // "root:123456"
  11. host := u.Host
  12. fmt.Println(host) // "www.baidu.com:8080"
  13. path := u.Path
  14. fmt.Println(path) // "/login/xxx"
  15. rawPath := u.RawPath
  16. fmt.Println(rawPath) // ""
  17. forceQuery := u.ForceQuery
  18. fmt.Println(forceQuery) // "false"
  19. rawQuery := u.RawQuery
  20. fmt.Println(rawQuery) // "&name=xiaoqing&age=24"
  21. fragment := u.Fragment
  22. fmt.Println(fragment) // "fff"
  23. }
  24. }

八) 进阶

1. 测试文件编写

testing

  1. 测试文件的命名规则: xxx_test.go,其中xxx最好与被测试文件同名,如 main_test.go
  2. 必须 import "testing"
  3. 每个test case 的格式:func TestPrint(t *testing.T){}
  4. 切到目录中,执行 go test 即可
  1. # test/main.go 【被测试文件】
  2. package main
  3. func add(a,b int) int{
  4. return a+b
  5. }
  6. # test/main_test.go 【测试文件】
  7. package main
  8. import (
  9. "testing"
  10. "fmt"
  11. )
  12. func TestAdd(t *testing.T) {
  13. res := add(1,2)
  14. if res != 3{
  15. t.Errorf("error!")
  16. }
  17. }

顺序执行测试函数

  1. package main
  2. import (
  3. "testing"
  4. "fmt"
  5. )
  6. func testAdd1(t *testing.T) { // 开头小写,会被跳过
  7. res := add(1,2)
  8. if res != 3{
  9. t.Errorf("error!")
  10. }
  11. }
  12. func testAdd2(t *testing.T) {
  13. res := add(2,3)
  14. if res != 5{
  15. t.Errorf("error!")
  16. }
  17. }
  18. func TestAll(t *testing.T){
  19. t.Run("testAdd1", testAdd1) // 在此处顺序执行
  20. t.Run("testAdd2", testAdd2)
  21. }
  22. func TestMain(m *testing.M) { // 当定义了 TestMain,只执行此函数,其他均不执行
  23. fmt.Println("Tests begin...")
  24. m.Run() // 跑其他各测试函数
  25. }
  26. # go test -v // 查看测试详情
  27. Tests begin...
  28. === RUN TestAll
  29. === RUN TestAll/testAdd1
  30. === RUN TestAll/testAdd2
  31. --- PASS: TestAll (0.00s)
  32. --- PASS: TestAll/testAdd1 (0.00s)
  33. --- PASS: TestAll/testAdd2 (0.00s)
  34. PASS
  35. ok _/D_/study/go/test 1.620s

九)自己封装的函数

1. 支持中文的字符串截取

  1. /**
  2. 将字符串 s 从 sub 子串开始截取 len 个字节,支持中文
  3. 1. 获取子串位置
  4. 2. 用 []byte 去掉所有子串前面的的字符
  5. 3. 将剩余部分转化成 rune ,截取 len 个字节
  6. 4. 最后转化成 string
  7. */
  8. func SubCn(s, sub string, len int) (string, bool){
  9. subIndex := strings.Index(s, sub)
  10. if subIndex>0 {
  11. sByte := []byte(s)[subIndex:]
  12. sRune := []rune(string(sByte))[0:len]
  13. return string(sRune), true
  14. }
  15. return "", false
  16. }

十)疑问整理

1. rune 是什么类型,与 byte 和 string 的区别

byte 等同于int8,常用来处理ascii字符
rune 等同于int32,常用来处理unicode或utf-8字符,类似c语言中的char
  1. s:="Go编程"
  2. fmt.Println(len(s)) // 8
解析:string底层是用byte数组存的,并且是不可以改变的。中文占3个字节
  1. s:="Go编程"
  2. s2 := []rune(s)
  3. fmt.Println(len(s2)) // 4

2. 单引号与双引号的区别

  1. // Go语言的字符串是一个用UTF-8编码的变宽字符序列,它的每一个字符都用一个或多个字节表示
  2. // 双引号用来创建可解析的字符串字面量(支持转义,但不能用来引用多行)
  3. str := "hello,world"
  4. //单引号则用于表示Golang的一个特殊类型:rune
  5. r := 'c'
  6. // 另外,反引号用来创建原生的字符串字面量,这些字符串可能由多行组成(不支持任何转义序列),原生的字符串字面量多用于书写多行消息、HTML以及正则表达式
  7. func main() {
  8. s :=`hi, am xxx, \n
  9. ccccccccccccccccc
  10. cccccccc`
  11. fmt.Println(s)
  12. }
  13. /*
  14. 输出:
  15. hi, am xxx, \n
  16. ccccccccccccccccc
  17. cccccccc
  18. */
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注