桃之夭夭,灼灼其华

go 基础语法

Word count: 1.7kReading time: 8 min
2021/03/23 Share

变量和类型

  • 变量

    1
    2
    3
    4
    var i int //声明不赋值,默认为0
    var i int = 1 //声明时赋值
    var i = 1 //声明时赋值
    i := 1 //声明时赋值
  • 基本类型

    • 空值:nil

    • 整型:int(与操作系统有关,32位还是64位),int8,int16,int32,int64,uint8,uint16… ,默认为0

    • 浮点型:float32,float64

    • 布尔型:bool(ture, false),默认为false

    • 字节:byte

    • 字符串:string,默认为””

      1
      2
      3
      4
      5
      var a int8 = 8
      var b float32 = 3.2
      var c byte = 'b'
      var d string = "string"
      var e bool = true
      1
      2
      3
      4
      5
      6
      7
      z := "中文"
      fmt.Println(z[1], string(z[1]), len(z)) //184 ¸ 6
      //go中字符串默认使用utf-8来存储。字符串是以 byte 数组形式保存的,类型是 uint8,占1个 byte,打印时需要用 string 进行类型转换,否则打印的是编码值。也正因为存储是以byte,uint8存储所以 z[1] 的值不是”文“,可以使用rune来处理。
      fmt.Println(reflect.TypeOf(z[1]).Kind()) // reflect.TypeOf展示数据类型,uint8
      runeArr := []rune(z)
      fmt.Println(reflect.TypeOf(runeArr[1]).Kind()) // int32
      fmt.Println(runeArr[1], string(runeArr[1])) //25991 文
  • 数组和切片

    • 数组(array):长度不可变

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      //声明
      var arr1 [3]int //一维
      var arr2 [3][3]int //二维
      //初始化
      arr := [3]int{1,2,3}
      //var arr = [3]int{1,2,3}
      //赋值操作
      arr[0] = 4
      //遍历
      for i := 0; i < len(arr); i++ {
      fmt.Println(arr[i])
      }
    • 切片(slice):数组的抽象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      //声明
      slice2 := make([]float32, 3, 5) // [0 0 0] 长度为3容量为5的切片
      fmt.Println(len(slice2), cap(slice2)) // 3 5
      //赋值操作
      // 添加元素,切片容量可以根据需要自动扩展
      slice2 = append(slice2, 1, 2, 3, 4) // [0, 0, 0, 1, 2, 3, 4] 容量不够时,会自动扩容
      fmt.Println(len(slice2), cap(slice2)) // 7 12
      // 子切片 [start, end)
      sub1 := slice2[3:] // [1 2 3 4]
      sub2 := slice2[:3] // [0 0 0]
      sub3 := slice2[1:4] // [0 0 1]
      // 合并切片
      combined := append(sub1, sub2...) // [1, 2, 3, 4, 0, 0, 0] sub2...为切片解构写法,将切片解构为N个独立的单元
  • 字典(map)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     //声明
    m1 := make(map[string]string)
    //初始化
    m2 := map[string]string{
    "key1": "value1",
    "key2": "value2",
    }
    //操作
    fmt.Println(m2["key1"])
    m2["key2"] = "value"
    fmt.Println(m2["key2"])
    //遍历
    for key,value := range m2{
    fmt.Println(key, value)
    }
  • 指针:指保存了变量的内存地址,使用符号 *,对一个已经存在的变量,使用 & 获取该变量的地址。go的参数都是值传递,当需要引用传递时要使用指针。

    1
    2
    3
    4
    str := "go"
    var p *string = &str // p 是指向 str 的指针
    *p = "hello"
    fmt.Println(str) // hello 修改了 p,str 的值也发生了改变

流程控制(if,for,switch)

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
func iterator(start, end int) int {
sum := 0
//普通for循环
for i := start; i < end; i++ {
sum += i
}
//可以省略刁前后的条件
for sum < 10 {
sum += sum
}
nums := []int{10, 20, 30, 40}
for i, num := range nums {
fmt.Println(i, num)
}
/*死循环
for {
}
*/
return sum
}

func condition(x, n float64) float64 {

a := math.Pow(x, n)
//条件判断
if a < 20 {
return a
}
//带条件条件判断
if b := a + 10; b > 20 {
return b
} else {
fmt.Printf("this is %f", b)
}
return 0
}
func switchTest(){
//除非以 fallthrough 语句结束,否则分支会自动终止。
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.", os)
}
}

函数、结构体、方法和接口

  • 函数(func):使用关键字func,参数可以有多个,返回值也支持有多个

    1
    2
    3
    4
    //在 Go 中,首字母大写的名称是被导出的(类似public)。Swap 和 SWAP 都是被导出的名称。名称 swap 是不会被导出的(类似private)
    func swap(x, y string) (string, string) {
    return y, x
    }
  • 结构体(struct)

    1
    2
    3
    4
    5
    //定义
    type Student struct {
    name string
    age int
    }
  • 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //在结构体Student 添加方法(s *Student) 
    func (s *Student) getName() string {
    return s.name
    }
    //操作
    func main() {
    //age未被赋值,默认值 0
    stu := &Student{
    name: "zhangsan",
    }
    fmt.Println(stu.getName) // zhangsan
    }
  • 接口(interface):不需要显式地声明实现了哪一个接口,只需要直接实现该接口对应的方法即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    type Person interface {
    getName() string
    }

    func main() {
    //实例化 Student后,强制类型转换为接口类型 Person
    var p Person = &Student{
    name: "lisi",
    age: 18,
    }
    fmt.Println(p.getName()) // lisi
    s Student = p.(Student) //显示转换,将Person转换为Student
    //空接口interface{},可以存储任何类型的数据
    m := make(map[string]interface{})
    m["name"] = "Tom"
    m["age"] = 18
    m["scores"] = [3]int{98, 99, 85}
    fmt.Println(m) // map[age:18 name:Tom scores:[98 99 85]]
    }

并发

关键字go,在需要执行的方法函数前添加go

同一个Goroutine线程内部,程序是顺序一致性的。但是不同的Goroutine之间,并不满足顺序一致性内存模型。

  • sync

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import (
    "fmt"
    "sync"
    "time"
    )
    //sync.WaitGroup
    var wg sync.WaitGroup
    //执行下载操作
    func download(url string) {
    fmt.Println("start to download", url)
    time.Sleep(time.Second) // 模拟耗时操作
    wg.Done() //减去一个计数
    }

    func main() {
    for i := 0; i < 3; i++ {
    wg.Add(1) //为 wg 添加一个计数
    go download("a.com/" + string(i+'0')) //开启新线程去执行函数
    }
    wg.Wait() //等待所有的协程执行结束
    fmt.Println("Done!")
    }
    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
    import (
    "sync"
    )
    //sync.Mutex 加锁
    var total struct {
    sync.Mutex
    value int
    }

    func worker(wg *sync.WaitGroup) {
    defer wg.Done()

    for i := 0; i <= 100; i++ {
    //加锁
    total.Lock()
    total.value += i
    total.Unlock()
    }
    }

    func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go worker(&wg)
    go worker(&wg)
    wg.Wait()

    fmt.Println(total.value)
    }
    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
    import (
    "sync"
    "sync/atomic"
    )
    //sync/atomic
    var total uint64

    func worker(wg *sync.WaitGroup) {
    defer wg.Done()

    var i uint64
    for i = 0; i <= 100; i++ {
    //原子操作
    atomic.AddUint64(&total, i)
    }
    }

    func main() {
    var wg sync.WaitGroup
    wg.Add(2)

    go worker(&wg)
    go worker(&wg)
    wg.Wait()
    }
  • channel

    对于从无缓冲Channel,进行的接收发生在对该Channel进行的发送完成之前

    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
    import (
    "fmt"
    "math/rand"
    "time"
    )

    func boring(msg string, c chan string) {
    for i := 0; ; i++ {
    // 发送结果到channel中
    // 会等待接收端就绪
    c <- fmt.Sprintf("%s %d", msg, i)
    time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
    }
    }

    func main() {

    // 定义channel
    c := make(chan string)
    // 调用boring函数
    go boring("boring!", c)

    for i := 0; i < 5; i++ {
    // <-c 从boring函数读取值
    // <-c 等待获取值
    fmt.Printf("You say: %q\n", <-c)
    }
    //当接收到5条消息就打印
    fmt.Println("You're boring. I'm leaving")
    }

    对于带缓冲的Channel,对于Channel的第K个接收完成操作发生在第K+C个发送操作完成之前,其中C是Channel的缓存大小

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //根据控制Channel的缓存大小来控制并发执行的Goroutine的最大数目
    var limit = make(chan int, 3)

    func main() {
    for _, w := range work {
    go func() {
    limit <- 1
    w()
    <-limit
    }()
    }
    //select{}一个空的管道选择语句,该语句会导致main线程阻塞,从而避免程序过早退出。
    //还有for{}、<-make(chan int)等诸多方法可以达到类似的效果。因为main线程被阻塞了,如果需要程序正常退出的话可以通过调用os.Exit(0)实现。
    select{}
    }

程序初始化顺序

img

CATALOG
  1. 1. 变量和类型
  2. 2. 流程控制(if,for,switch)
  3. 3. 函数、结构体、方法和接口
  4. 4. 并发
  5. 5. 程序初始化顺序