Golang 基础语法记录 | 青训营笔记

152 阅读10分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第5篇笔记。在青训营之前,从没接触过golang,接触到此语言后,感受此语言十分强大,后续会继续高阶学习。本贴记录本人当时学习golang时的笔记。

GO语言学习之旅


一、环境安装

  • Windows 10 MSI安装包 点击下载

  • 测试环境,创建测试文件test.go, 输入以下内容

    package main  /*声明此文件属于哪个package*/import "fmt" /* 需要使用fmt包*/func main(){
        fmt.Println("Hello World!")
    }
    

    进行cmd命令行,执行以下命令:

    C:\YourDir> go run test.go
    Hello World!
    

二、语法基础

  • 一行表示一个语句

  • 字符串连接用 "+"

  • 声明变量:

    /*如果没有初始值,默认为0*/
    var identifier type
    /*可以一次声明多个变量*/
    var identifier1, identifier2 type
    ​
    intVal := 1
    /*相当于*/
    var intVal int
    intVal = 1
    ​
    f:='Suyame'
    /*相当于*/
    var f string
    f = 'Suyame'
    
  • 如果声明了局部变量(全局变量可以),但是没有使用它,会报错

    package main
    ​
    import "fmt"func main() {
       var a string = "abc"
       fmt.Println("hello, world")
    }
    /*报错: a declared and not used*/
    
  • 循环结构

    1. for结构

      for init; condition; post{
          // 语句
      }
      // 例如
      for i := 1; i < len(os.Args); i++ {
              s += sep + os.Args[i]
              sep = " "
          }
      
    2. "while" 循环

      for condition{
          // ...
      }
      // 无限循环
      // while True {...}
      for {
          //...
      }
      
    3. for each 类型

      for index, element := range List {
          //...
      }
      
  • 输入输出

    • bufio.Scanner 类型 input的变量

      input := bufio.NewScanner(os.Stdin)
      //读取一行
      input.Scan() //读到了新行会返回true, 否则返回false
      text := input.Text()
      //循环按行读取
      for input.Scan(){
          text := input.Text()
      }
      
    • fmt.Printf 输出格式

      image.png

  • 文件操作

    1. 打开文件

      f, err := os.Open(filename)
      // f 类型是 *os.File 表示打开的文件对象
      // err 错误信息, 默认为nil,表示成功打开
      
    2. 读取文件内容

      input := bufio.NewScanner(f)
      for input.Scan() {
          counts[input.Text()]++
      }
      
  • 自定义类型

    type Point struct {
        X, Y int
    }
    var p Point
    
  • 帮助手册

    go doc package_name
    
  • 变量的生命周期

    局部变量有一个动态的生命周期:每次执行声明语句时创建一个新的实体,变量一直生存到它变得不可访问,这时它占用的存储空间被回收。

  • 使用type声明新的类型

    type name underlying-type
    eg.
    type Name string
    

    注意这样声明的两个不同类型即使底层类型是一致的也不能进行比较和合并以及运算的,

  • 对新type声明方法

    // 声明String方法
    func (n Name) String() string {
        return fmt.Sprintf("%s", n)
    }
    
  • Go 位运算

    image.png

  • 一组转义符

    image.png

  • 注意byte类型是单引号,string类型是双引号

  • strconv包内的Atoi函数或ParseInt函数用于解释表示整数的字符串,而ParseUint用于无符号整数:

    x, err := strconv.Atoi("123")
    y, err := strconv.ParseInt("123", 10, 64) // 十进制,最长为64位
    
  • 四种复合数据类型

    1. 数组: 长度固定,元素类型保持一致

      var a [3]int
      // var a [3]int{1, 2, 3}
      // a := [...]int{1, 2, 3}
      fmt.Println(a[0])
      fmt.Println(a[len(a)-1])
      
      for i, v:= range a {
          fmt.Printf("%d %d\n", i, v)
      }
      
      for _, v := range a {
          fmt.Printf("%d\n", v)
      }
      
      // 定义了一个拥有100个元素的数组r,除了最后一个元素是-1外,该数组中的其它元素值都是0
      r := [...]int{99: -1}
      

      如果数值的元素是可以比较的,那么数值也是可以用 == 来比较其是否完全一样

    2. slice,可变长度同类型元素的序列

      1. 指针

      2. 长度

      3. 容量

      4. 无法比较,不能通过“==”

      5. 可用bytes.Equal 来比较两个字节slice([]byte)

      6. append方法

        var x []int
        x = append(x, 1)
        x = append(x, 2, 3)
        x = append(x, 4, 5, 6)
        x = append(x, x...) // 追加x中的所有元素
        //  x = [1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]
        
    3. map, K-V键值对

      //创建一个从string到int的map
      ages := make(map[string]int)
      //新建map并初始化map
      ages := map[string]int{
          "alice": 31,
          "suyame": 22,
      }
      //访问
      ages["alice"] = 32
      fmt.Println(ages["alice"]) // "32"
      // 删除alice元素
      delete(ages, "alice") 
      // 即使alice不在ages中,也是安全的,对应的元素不存在时,回返回零值
      
      1. map元素不是一个变量,不可以获取它的地址。因为map的增长可能会导致已有元素被重新散列到新的存储位置,这样就可能使得获取的地址无效
      // 遍历map中的键值对
      for k, v := range ages {
          fmt.Printf("%s\t%d\n", name, age)
      }
      // 将map按照键的排序
      import "sort"
      var names []string
      for name := range ages {
          names = append(names, name)
      }
      sort.Strings(names)
      for _, name := range names {
          fmt.Printf("%s\t%d\n", name, ages[name])
      }
      // 判断一个键是否在字典里
      age, ok := ages["bob"]
      if !ok {
          "bob" 不是字典中的键,age = 0
      }
      // 合并
      if age, ok := ages["bob"]; !ok {
          ...
      }
      
    4. 结构体

      type Employee struct {
          ID int
          Name string
          Address string
          DoB time.time
          Postion string
          Salary int
          ManagerID int
      }
      var dilbert Employee
      // 访问结构体, 用点号
      dilbert.Salary -= 5000
      

      结构体成员变量名称时首字母大写的,那么这个变量时可导出

      匿名成员

      type Point struct {
          X, Y int
      }
      type Circle struct {
          Point
          Radius int
      }
      type Wheel struct {
          Circle
          Spokes int
      }
      
      var w Wheel
      w.X = 8 // 等价于 w.Circle.Point.X = 8
      
  • 裸返回:

    将每一个命名返回结果按照顺序返回的快捷方式

    实际上是将返回变量的名称放在函数头上:

    func Function(s string)(target string){
        target := 'hhhh'
        return
        // 等同于return target
    }
    
  • 函数也可以看成变量: 类比Python

  • 匿名函数:

    func 后面不带名称

    strings.Map(func(r rune)rune{return r +1}, "HAL-9000")
    
  • 变长函数:

    可传入可变的参数个数

    在参数列表最后的类型名称之前使用省略号"... " 表示声明一个变长函数

    func sum(vals ...int) int {
        total := 0
        for _, val := range vals {
            total += val
        }
        return total
    }
    sum() // 0
    sum(2,3) // 5
    sum(1, 2, 3, 4) // 10
    values := []int{1, 2, 3, 4}
    sum(values...) // 10
    
    // interface{} 表示可以接受任何值
    func Fun(args ...interface{}) {
        ...
    }
    
  • string 转 int

    v, err := strconv.Atoi(input.Text())

    int 转string

    string := strconv.Itoa(int)

  • 方法:

    1. 声明与普通函数的声明类似,只是在函数名字前面多了一个参数。

      type Point struct{
          X, Y float64
      }
      
      // 普通函数
      func Distance(p, q Point) float64 {
          return math.Hypoy(q.X-p.X, q.Y-p.Y
      }
      // Point类型的方法
      func (p  POint) Distance(q Point) float64 {
      	return math.Hypot(q.X-p.X, q.Y-p.Y)
      }
      

      GO 中没有this, self 关键词, 使用我们自己传入的参数名称,如上述的p, 一般取类型首字母

  • 方法变量:

    p := Point{1, 2}
    distanceFromP := p.Distance
    # 调用方法变量
    distanceFromP(q)
    
  • 方法表达式:

    p := Point{1, 2}
    q := Point{2, 3}
    distance := Point.Distance
    # 调用方法表达式
    distance(p, q)
    
  • 封装

    必须需要结构体, 结构体的变量不以大写开头命名即可,在定义的包内可见而不是类型

字符串使用


  • 多行字符串用 `

  • 字符串分隔

    strings.Split(s1, sep)

    ex: strings.Split("I love u", ' ') // ["I" "love" "u"]

  • 包含:

    strings.Contains(s, subs)

  • 前缀

    strings.HasPrefix(s, pre)

  • 后缀

    strings.HasSuffix(s, suf)

  • 获取字串位置

    idx = strings.Index(s, target)

    last_idx = strings.LastIndex(s, target)

  • 拼接

    strings.Join([], sep)

    ex:

    strings.Join(["I", "love", "u"], ' ') // "I love u"

  • 修改字符串:转类型

    s2 := "白萝卜"
    s3 := []rune(s2) // 把字符串强制转换成了一个rune切片
    s3[0] = '红'
    fmt.Println(string(s3)) //将rune转成string
    

交叉编译


从零开始搭建Go语言开发环境 | 李文周的博客 (liwenzhou.com)

  • iota 是常量计数器,只能再常量的表达式中使用

    出现时值为0,每添加一行使iota计数一次。

    const(
    	a1 = iota //0
        a2 		  //1
        a3		  //2
    )
    
    const (
    	b1 = iota //0
        b2		  //1
        _
        b3 =      //3
    )
    // 插队
    const (
    	c1 = iota // 0
        c2 = 100  // 100
        c3        // 100
        c4 = iota // 3
    )
    // 多个常量声明在一行
    const (
        d1, d2 = iota + 1, iota + 2 // d1: 1, d2: 2
        d3, d4 = iota + 1, iota + 2 // d3: 2, d4: 3
    )
    // 定义数量级
    const (
    	_ = iota
        KB = 1 << (10*iota)
        MB = 1 << (10*iota)
        GB = 1 << (10*iota)
        TB = 1 << (10*iota)
        PB = 1 << (10*iota)
    )
    
  • goto + label 实现跳出多层for循环

    for i:=0; i<10; i++ {
        for j:=0; j<9; j++ {
            goto Labelname
        }
    }
    Labelname:
    	fmt.Println("hhh")
    
  • 数组是值类型

    b1 := [3]int{1, 2, 4}
    b2 := b1
    b2[0] = 100              // b1: [1 2 4], b2: [100 2 4]
    
  • make 函数创建切片

    s1 := make([]int, length, cap)
    
  • copy函数复制切片

    copy(des, src)
    
  • 删除切片中的元素

    a := []int{1, 2, 3, 4, 5, 6, 7}
    // 删除索引为2的元素
    a = append(a[:2], a[3:]...)
    fmt.Println(a) // [1 2 4 5 6 7]
    
  • sort 对slice排序

    func Sort() {
    	var a = [...]int{3, 7, 8, 9, 1}
    	// fmt.Println(a[:]) //将数组切片
    	//sort包内部实现了内部数据类型的排序
    	//升序排列
    	sort.Ints(a[:])
    	fmt.Println(a)
    	//降序排列
    	sort.Sort(sort.Reverse(sort.IntSlice(a[:])))
    	fmt.Println(a)
    }
    
  • 数组转成slice切片

    b = a[:]
    
  • 判断map中键是否存在

    value, ok := map[key]
    if ok{
        ...
    }
    
  • 从map中删除一对k-v,使用delete函数

    delete(map, key)
    
  • 闭包=函数+引用环境

    Go语言基础之函数 | 李文周的博客 (liwenzhou.com)

  • defer 语句

    语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。

    func main() {
    	fmt.Println("start")
    	defer fmt.Println(1)
    	defer fmt.Println(2)
    	defer fmt.Println(3)
    	fmt.Println("end")
    }
    // 输出
    //start
    //end
    //3
    //2
    //1
    

image.png

  • 经典defer案例

    func calc(index string, a, b int) int {
    	ret := a + b
    	fmt.Println(index, a, b, ret)
    	return ret
    }
    
    func main() {
    	x := 1
    	y := 2
    	defer calc("AA", x, calc("A", x, y))
    	x = 10
    	defer calc("BB", x, calc("B", x, y))
    	y = 20
    }
    
    //
    // A 1 2 3
    // B 10 2 12
    // BB 10 12 22
    // AA 1 3 4
    // defer注册要延迟执行的函数时该函数所有的参数都需要确定其值, 先计算参数的具体值!
    
  • 异常与恢复: panic 和recover

    recover 一定是在defer调用的函数中有效

    defer一定要在可能引发panic的语句之前定义。

    func funcA() {
    	fmt.Println("func A")
    }
    
    func funcB() {
    	defer func() {
    		err := recover()
    		//如果程序出出现了panic错误,可以通过recover恢复过来
    		if err != nil {
    			fmt.Println("recover in B")
    		}
    	}()
    	panic("panic in B")
    }
    
    func funcC() {
    	fmt.Println("func C")
    }
    func main() {
    	funcA()
    	funcB()
    	funcC()
    }
    
  • 实现继承

    //Animal 动物
    type Animal struct {
    	name string
    }
    
    func (a *Animal) move() {
    	fmt.Printf("%s会动!\n", a.name)
    }
    
    //Dog 狗
    type Dog struct {
    	Feet    int8
    	*Animal //通过嵌套匿名结构体实现继承
    }
    
    func (d *Dog) wang() {
    	fmt.Printf("%s会汪汪汪~\n", d.name)
    }
    
    func main() {
    	d1 := &Dog{
    		Feet: 4,
    		Animal: &Animal{ //注意嵌套的是结构体指针
    			name: "乐乐",
    		},
    	}
    	d1.wang() //乐乐会汪汪汪~
    	d1.move() //乐乐会动!
    }
    
  • 结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

  • 空接口定义,空接口类型的变量可以存储任意类型的值

    package main
    
    import "fmt"
    
    // 空接口
    
    // Any 不包含任何方法的空接口类型
    type Any interface{}
    
    // Dog 狗结构体
    type Dog struct{}
    
    func main() {
    	var x Any
    
    	x = "你好" // 字符串型
    	fmt.Printf("type:%T value:%v\n", x, x)
    	x = 100 // int型
    	fmt.Printf("type:%T value:%v\n", x, x)
    	x = true // 布尔型
    	fmt.Printf("type:%T value:%v\n", x, x)
    	x = Dog{} // 结构体类型
    	fmt.Printf("type:%T value:%v\n", x, x)
    }
    
    // 空接口作为函数参数, 这样函数就可以接受任意类型的值啦
    func show(a interface{}) {
    	fmt.Printf("type:%T value:%v\n", a, a)
    }
    
    // 空接口作为map值
    var studentInfo = make(map[string]interface{})
    studentInfo["name"] = "沙河娜扎"
    studentInfo["age"] = 18
    studentInfo["married"] = false
    fmt.Println(studentInfo)
    
  • 接口值包含: 存入的类型和值, 称为动态类型和动态值

  • 验证某一结构体是否满足特定的接口类型

    // 摘自gin框架routergroup.go
    type IRouter interface{ ... }
    
    type RouterGroup struct { ... }
    
    var _ IRouter = &RouterGroup{}  // 确保RouterGroup实现了接口IRouter
    
  • 并发Go语言基础之并发 | 李文周的博客 (liwenzhou.com)

    1. sync.WaitGroup

    2. channel类型

      ch := make(chan int)
      // 发送
      ch <- 10 // 把 10发送到ch中
      // 接受
      x := <- ch  // 从ch中接受值并赋值给变量x
      <-ch        // 从ch中接收值,忽略结果
      // 关闭
      close(ch)  // 通道通常由发送方执行关闭操作,并且只有在接收方明确等待通道关闭的信号时才需要执行关闭操作
      
      1. 接受操作时:

        value, ok := <- ch
        
        • 其中value是从通道中取出的值,如果关闭返回对应类型的空值
        • ok 通道ch关闭时返回false。 否则返回true