1 安装

https://golang.google.cn/dl/

2 Golang语言特性

2.1 优势

  • 极简单的部署方式

    • 可直接变成成机器码

    • 不依赖其他库

    • 直接运行即可部署

  • 静态类型语言

    • 编译过程就可以检查出来隐藏的大多数问题
  • 语言层面的并发

    • 天生的基因支持(不是美容的)
      • 很多语言是通过一层层的包装来达到这种优化的、优良的并发特性
    • 充分的利用多核
      • 不需要绑定CPU以及优化Go语言调度的时间片
  • 强大的标准库

    • runtime系统调度机制

      • 能够帮我们做一些垃圾回收
    • 高效的GC垃圾回收

      • 从Go语言1.8版本后,GC增加了三色标记和混合写屏障
    • 丰富的标准库

      • 加解密、数学、进程、线程、网络通信、文件系统
  • 简单易学

    • 25个关键字

    • C语言简洁基因,内嵌C语法支持

    • 面向对象特征(继承、多态、封装)

    • 跨平台的语言

  • 大厂领军

    • 谷歌、fb、腾讯、百度、京东、新浪、七牛

2.2 Golang适合做什么(强项)

  • 云计算基础设施领域
    • docker、七牛云存储、kubernetes
  • 基础后端软件
    • tidb、influxdb
  • 微服务
    • go-kit、micro
  • 互联网基础设施
    • hyperledger

2.3 成就

docker、kubernetes

2.4 缺点

  • 大部分第三方库都在github上
  • 无泛化类型
  • 所有的Exception都用Error来处理
  • 对C的降级处理,并非无缝,没有C降级到asm那么完美(序列化问题)

3 基础语法

3.1 引入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main // 程序的包名,与文件名无关
import "fmt"
/* 导入多个
import (
	"fmt"
	"time"
)
*/
func main(){
	fmt.Println('Hello Go!') // 分号可选
}
/* 执行 */
go build hello.go // 编译生成hello可执行文件,可以通过./hello执行
go run hello.go // 编译+运行

3.2 变量的声明

  • 方法一:var 变量名 变量类型
1
2
var a int
// 不赋值的话默认值为0
  • 方法二:var 变量名 类型 = 初始值
1
var a int = 100
  • 方法三:var 变量名 = 初始值
1
2
var a = 100
// 在初始化变量时赋值,可以省略类型,自动匹配类型
  • 方法四:变量名 := 初始值
1
2
3
a := 100
// var关键字省略,类型自动匹配
// 该方法不支持声明全局变量
  • 单行多变量声明:
1
var xx, yy int = 100, 'tom'
  • 多行多变量声明:
1
2
3
4
var (
	x int = 100
	y bool = true
)

3.3 常量与iota

  • 常量用const关键字声明

    • const a int = 100

    • 多常量声明:

      1
      2
      3
      4
      
      const (
      	a = 100
      	b = 200
      )
      
  • const关键字也可以声明枚举(配合iota使用)

    • iota:可以在const()添加iota关键字,每行的iota值都会累加1,第一行iota默认值为0

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      
      // 声明枚举
      const (
      	BEIJING = 0
      	SHANGHAI = 1
      	SHENZHEN = 2
      )
      // 配合iota关键字声明枚举
      const (
      	BEIJING = iota // 0
      	SHANGHAI // 1
      	SHENZHEN // 2
      )
      // 面试题
      const (
      	a, b = iota*2, iota*3 // iota = 0 => a = 0, b = 0
      	c, d // iota = 1 => c = 2, d = 3
      	e, f = iota*3, iota*4 // iota = 2 => e = 6, f = 9
      )
      

3.4 函数

1
2
3
4
func foo1(a string, b int) int {
	c := 100
	return c
}

3.4.1 支持多返回值

  • 返回多个值(匿名)

    1
    2
    3
    
    func foo1(a string, b int)(int, int){
    	return 100, 200
    }
    
  • 返回多个值(有形参名称)

    1
    2
    3
    4
    5
    6
    7
    
    // func fool(a string, b int)(r1 int, r2 int){
    func fool(a string, b int)(r1,r2 int){
      // ri r2 属于foo3的局部变量,默认值为0
    	r1 = 100
    	r2 = 200
    	return
    }
    

3.4.2 init函数与import导包

  • init函数:包的主入口

    • init()执行优先级大于main()

    • 程序运行流程图:

      image-20211129145235373

    • 示例如下:

      • 文件结构
      1
      2
      3
      4
      5
      6
      
      ├── 5-init
      │   ├── lib1
      │   │   └── lib1.go
      │   ├── lib2
      │   │   └── lib2.go
      │   └── main.go
      
      • lib1.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      
      package lib1
      
      import "fmt"
      
      //当前lib1包提供的API
      // 首字母大写表示暴露出来的API(public)
      func Lib1Test() {
      	fmt.Println("lib1Test()...")
      }
      
      func init() {
      	fmt.Println("lib1. init() ...")
      }
      
      • main.go
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      
      package main
      
      import (
      	"GolangStudy/5-init/lib1"
      	"GolangStudy/5-init/lib2"
      )
      
      func main() {
      	lib1.lib1Test()
      	lib2.Lib2Test()
      }
      
  • import导包

    • import _ "fmt":匿名导入fmt包,无法使用该包的方法,但是会执行当前的包内部的init()方法;解决了如果导入的包不使用会报错的问题

    • import aa "fmt":给fmt包起一个别名aa,aa.Println()来直接调用

    • import . "fmt":将当前fmt包的全部方法导入,可以直接使用,不需要fmt API来调用

    • 示例:

      1
      2
      3
      4
      5
      
      import => mylib2 "GolangStudy/5-init/lib2"
      use => mylib2.Lib2Test()
      
      import => . "GolangStudy/5-init/lib2"
      use => Lib2Test()
      

3.5 指针

  • 背景:改变变量值的函数

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    package main
    import "fmt"
    func changeValue(p int){
    	p = 10
    }
    func main(){
    	var a int = 1
    	changeValue(a)
    	fmt.Println(a) // 是1还是10呢?
    }
    
    • 结果为1,因为changeValue方法只改变了p在内存中的值,内存分析如下

image-20211129155304551

  • 需要改变a在内存中的值:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    package main
    import "fmt"
    func changeValue(p *int){ // *int表示整型地址类型,p的初始值为0x00000000
    	*p = 10 // *p表示通过p中存放的地址,索引到对应内存空间(a),然后改变其值为10
    }
    func main(){
    	var a int = 1
    	changeValue(&a) // &a表示a在内存中的地址
    	fmt.Println(a) // 10
    }
    
    • 在内存图中体现为:

    image-20211129155711781

  • 交换两个值的练习:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    package main
    
    import "fmt"
    
    func swap(a *bool, b *bool)  {
    	fmt.Println(a,b) // 0x14000014092 0x14000014093
    	temp := *a // temp = true
    	*a = *b // *a = false
    	*b = temp // *b = true
    }
    
    func main(){
    	var a bool = true
    	var b bool = false
    	swap(&a, &b)
    }
    

3.6 defer关键字

  • 表示一个函数在执行结束之前的行为,类似finally关键字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    package main 
    import "fmt"
    func main(){
    	defer fmt.Println("defer")
    	fmt.Println("main")
    }
    // 执行结果
    // main
    // defer
    
  • defer的执行顺序

    • 当一个函数有多个defer语句时,会以栈的形式执行,即先入后出

      1
      2
      3
      4
      5
      6
      
      func main(){
      	defer fmt.Println("A")
      	defer fmt.Println("B")
      	defer fmt.Println("C")
      }
      // 执行结果:C B A
      
    • return的优先级要高于defer

3.7 数组

3.7.1 静态数组与动态数组

  • 静态数组的声明与遍历

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    package main
    import "fmt"
    func main(){
      // var 数组名 [长度]类型{ 初始值 }
    	var array1 [10]int // 数组中默认值为0
    	array3 := [3]int{1, 2} // [1, 2, 0]
    	printArray(array3)
    }
    func printArray(arr [3]int){
      // 普通for循环形式遍历
      for i := 0;i < len(arr);i++ {
        fmt.Println(arr[i])
      }
      fmt.Println("-------")
      // range关键字遍历
      for index, value := range arr {
        fmt.Println(index, value)
      }
      arr[0] = 100 // 静态数组作为参数传入时,仅相当于值拷贝,改变数组的值不会对原数组产生影响
    }
    
  • 动态数组的声明与遍历

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    package main
    import "fmt"
    func main(){
      // 声明时中括号内为空,则表示此数组为slice切片数组
    	array1 := []int{1,2,3}
    	printArray(array1)
    }
    func printArray(arr []int){
    	// 动态数组的传参相当于引用拷贝,改变数组的值相当于改变原数组
    	arr[0] =100
    	// range关键字遍历
    	for index, value := range arr {
    		fmt.Println(index, value)
    	}
    }
    // 输出
    // 0 100
    // 1 2
    // 2 3
    

3.7.2 slice数组的声明方式

  • 声明一个切片并初始化,len为3
    • slice1 := []int{1, 2, 3}
  • 声明一个切片,并用make关键字为其开辟3个空间,默认值为0
    • var slice []int
    • slice1 = make([]int, 3)
  • 声明一个切片,同时用make关键字为其开辟3个空间,初始化值为0
    • var slice1 []int = make([]int, 3)
  • 声明slice1,同时用make关键字为其开辟3个空间,初始化值为0,通过:=推导出slice是一个切片
    • slice1 := make([]int, 3)

3.7.3 slice的使用

  1. 切片容量的追加

    • 声明一个切片,利用fmt.printf()方法查看其长度容量等信息
    1
    2
    3
    4
    5
    6
    7
    8
    
    func main() {
    // 开辟一个整型切片,长度为3,容量为5
    numbers := make([]int,3,5)
    // %d表示整型数字,%v表示默认打印,&T表示打印类型
    fmt.Printf("len = %d, cap = %d, slice = %v, type = %T",len(numbers),cap(numbers),numbers,numbers)
    }
    // 输出
    // len = 3, cap = 5, slice = [0 0 0], type = []int
    

    ``

  • append方法在数组末尾插入一个元素

numbers = append(numbers, 1) // 此时len = 4, cap = 5, slice = [0 0 0 1], type = []int ```

  • 继续追加元素,当超过cap时,会动态开辟另外一个cap大小的内存

numbers = append(numbers, 1) // 此时len = 6, cap = 10, slice = [0 0 0 1 1 1], type = []int ```

  • len和cap的关系:⻓度表示左指针⾄右指针之间的距离,容量表示左指针⾄底层数组末尾的距离

image-20211207092542842

  1. 切片的截取
  • 按范围截取

    1
    2
    3
    4
    5
    
    func (){
    	s := []string{'a','b','c'}// len=3,cap=3,s=['a','b','c']
    	// 左闭右开结构[0, 2)
    	s1 := s[0, 2] // ['a','b']
    }
    

    注:按范围截取的切片s1,其指针与原切片一致。也就是说,修改s1就相当于修改原切片s。

    1
    2
    
    s1[0] = 'g'
    // s = ['g', 'b', 'c']
    
  • copy关键字可以将底层数组的slice一起进行拷贝(深拷贝)

    1
    2
    3
    4
    
    s2 := make([]string, 3) // s2 = [ ]
    // 将s中的值拷贝到s2
    copy(s2, s)
    fmt.println(s2) //