第一天GO训练营地

98 阅读8分钟

第一天GO训练营地

本文记录了训练营第一课的相关内容,并结合go入门指南中的详细介绍,对课程中未充分展开的部分进行了补充。 除了基础课程的内容,本训练营还特别强调了实践的重要性。在完成每一个小节后,通过动手编写代码和运行测试用例,可以更快巩固概念。例如,在变量部分的练习中,要求通过声明多个变量并进行简单运算,帮助我们理解变量作用域和类型推断的特点。这样的课程设置让理论和实践结合得更加紧密。 本篇主要讲述变量、控制结构、内置数据类型、数组和切片的相关内容。
函数和结构体将在后续单独成章。

介绍

Golang的特点 除了表中列出的主要特点,Golang 还注重简洁性和开发效率。例如,它内置了 goroutinechannel 机制,大大简化了并发程序的设计。这种高效的并发支持是 Golang 在分布式系统开发中备受欢迎的重要原因。此外,Golang 的语法风格清晰直接,减少了学习过程中的心智负担,非常适合初学者和需要快速开发的团队。


复制代码
| 特点           | 描述                       |
| -------------- | -------------------------- |
| 高性能、高并发 | 性能接近C++,标准库原生支持高并发。 |
| 语法简单       | 学习曲线平缓,周期短至周计。     |
| 丰富的标准库   | 满足大部分开发需求。          |
| 工具链         | 包括编译、代码格式化、代码检查、测试功能。 |
| 静态编译       | 生成体积小、易部署的可执行文件。  |
| 快速编译       | 支持增量编译。               |
| 跨平台         | 轻松实现交叉编译。            |
| 原生GC支持     | 提供自动垃圾回收机制。        |

开发环境

本文使用 gvmdebian12 上进行开发,开发工具包括 vscodeneovim

gvmgithub链接

我的实验代码仓库。 在配置开发环境时,使用 gvm 是一个便捷的选择,它不仅支持 Go 的多版本管理,还提供了版本切换和环境隔离功能。需要注意的是,在安装 gvm 时,可能需要配置 zshbash 的启动脚本以正确加载环境变量。此外,开发中常用的插件如 Go Extension 也能大幅提高编辑器的智能提示能力和代码检查效率。


数据类型

表达式是某种特定类型的值,由其他值和运算符组合而成。每种类型定义了自身可组合的运算符集合,若使用不符合集合内的运算符,会在编译时报错。布尔型在控制流程中尤为常用,例如用作循环或条件语句的判断条件。此外,Go 语言中没有隐式类型转换,例如布尔值不能直接与整型值进行运算。这种设计减少了潜在的类型混淆,但也需要程序员显式进行类型转换。

Bool

示例:var b bool = true

布尔型的值只能是 truefalse。两个相同类型的值可通过 ==!= 进行比较,返回布尔值 truefalse,但只有类型相同时才能比较。

go

// 示例代码
var aVar = 10
aVar == 5 // false
aVar == 10 // true

数字类型

Go 语言支持整型、浮点型和复数,位运算采用补码。基于架构的类型如 intuintuintptr,其长度依赖于操作系统:

  • 在 32 位操作系统上,intuint 长度为 32 位(4 字节)。
  • 在 64 位操作系统上,intuint 长度为 64 位(8 字节)。
  • uintptr 的长度足以存储指针。

当涉及到跨平台开发时,理解架构相关类型(如 intuint)的长度差异尤为重要。例如,在处理需要高精度计算的场景时,应优先使用 int64uint64,以确保跨平台结果一致。此外,Go 的整数运算遵循溢出截断原则,这在循环计数或位操作时需要特别注意。

Go 语言没有 float,只有 float32float64,没有 double 类型。

整数类型

  • int8:-128 到 127
  • int16:-32768 到 32767
  • int32:-2147483648 到 2147483647
  • int64:-9223372036854775808 到 9223372036854775807

无符号整数

  • uint8:0 到 255
  • uint16:0 到 65535
  • uint32:0 到 4294967295
  • uint64:0 到 18446744073709551615

浮点型(IEEE-754 标准)

  • float32:±1e-45 到 ±3.4e38
  • float64:±5e-324 到 ±1.07e308

int 类型是计算最快的一种类型。Go 不允许不同类型混合使用,但常量之间可混合使用。示例:


package main

func main() {
    var a int
    var b int32
    a = 15
    // b = a + a // 编译错误
    b = b + 5    // 因为 5 是常量
}

字符类型

严格来说,这不是 Go 的独立类型。字符是整数的特殊用例。byteuint8 的别名。字符用单引号括起,可将数字赋给 byte。字符类型不仅可用于单字符表示,还可以通过结合 rune 实现对 Unicode 字符的处理。rune 是 Go 中的 Unicode 字符类型,它是 int32 的别名,用于处理多字节字符。示例:

类型别名

类型别名用于简化名称或解决名称冲突,例如:

go
package main
import "fmt"

type TZ int

func main() {
    var a, b TZ = 3, 4
    c := a + b
    fmt.Printf("c has the value: %d", c) // 输出:c has the value: 7
}

类型别名的新类型与原类型不同,新类型不会继承原类型附带的方法。


常量

常量的计算规则是编译时确定值,这使得它们适合用作配置参数和标志位。此外,Go 提供了 iota 关键字,用于生成连续值枚举:

const 定义的常量用于存储不可更改的数据,可为布尔型、数字型和字符串型。常量定义格式为:const identifier [type] = value,例如:

go
复制代码
const Pi = 3.14159

未指定类型的常量会在使用时根据上下文推断类型。常量值必须在编译时确定。

  • 正确示例:const c1 = 2/3
  • 错误示例:const c2 = getNumber() // 编译错误

控制结构

条件语句

Go 语言中的条件语句包括 ifif-elseswitch。这些语句的语法简洁,同时内置了对逻辑条件的强制检查。

ifif-else

Go 的 if 语句无需括号,条件语句直接放置在 if 后。可以在条件语句中声明变量:

go
复制代码
package main
import "fmt"

func main() {
    x := 10
    if x > 5 {
        fmt.Println("x is greater than 5")
    } else {
        fmt.Println("x is less than or equal to 5")
    }

    // 带变量声明
    if y := x * 2; y > 15 {
        fmt.Println("y is greater than 15")
    } else {
        fmt.Println("y is less than or equal to 15")
    }
}

switch

switch 语句是多分支控制结构,支持多种情况匹配。Go 中的 switch 自动在匹配成功后退出,无需显式使用 break。示例:

go
复制代码
package main
import "fmt"

func main() {
    num := 2
    switch num {
    case 1:
        fmt.Println("One")
    case 2:
        fmt.Println("Two")
    default:
        fmt.Println("Other")
    }
}

支持多个条件分支匹配:

go
复制代码
switch num {
case 1, 3, 5:
    fmt.Println("Odd number")
case 2, 4, 6:
    fmt.Println("Even number")
default:
    fmt.Println("Out of range")
}

循环语句

Go 语言仅支持 for 循环,并可通过不同写法实现 whiledo-while 的功能。与其他语言不同,Go 中没有 whiledo-while 关键字。

基本 for 循环

go
复制代码
package main
import "fmt"

func main() {
    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }
}

类似 while 的写法

go
复制代码
package main
import "fmt"

func main() {
    i := 0
    for i < 5 {
        fmt.Println(i)
        i++
    }
}

无限循环

使用 for 实现:

go
复制代码
for {
    fmt.Println("This will run forever")
}

可以通过 break 跳出循环,或使用 continue 跳过本次迭代。

遍历集合

使用 range 关键字遍历数组、切片、映射等:

go

package main
import "fmt"

func main() {
    arr := []int{1, 2, 3, 4, 5}
    for index, value := range arr {
        fmt.Printf("Index: %d, Value: %d\n", index, value)
    }
}

range 返回两个值,分别是索引和对应值。如果不需要索引,可以用下划线 _ 忽略:

go
复制代码
for _, value := range arr {
    fmt.Println(value)
}

数组和切片

数组

Go 的数组是固定长度的,类型和值均在编译时确定:

go
复制代码
package main
import "fmt"

func main() {
    var arr [5]int
    arr[0] = 1
    fmt.Println(arr)

    arr2 := [3]int{1, 2, 3}
    fmt.Println(arr2)
}

数组是值类型,赋值时会复制整个数组,改变副本不会影响原数组:

go
复制代码
package main
import "fmt"

func main() {
    arr := [3]int{1, 2, 3}
    arrCopy := arr
    arrCopy[0] = 10
    fmt.Println(arr)      // [1, 2, 3]
    fmt.Println(arrCopy)  // [10, 2, 3]
}

切片

切片是动态数组,可以自动调整大小。通过数组或直接声明创建切片:

go
复制代码
package main
import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    slice := arr[1:4]
    fmt.Println(slice) // [2, 3, 4]

    dynamicSlice := []int{1, 2, 3}
    fmt.Println(dynamicSlice) // [1, 2, 3]
}

切片是引用类型,修改切片会影响底层数组:

go
复制代码
slice[0] = 10
fmt.Println(arr) // [1, 10, 3, 4, 5]

切片扩容

使用 append 向切片中添加元素:


复制代码
slice := []int{1, 2, 3}
slice = append(slice, 4, 5)
fmt.Println(slice) // [1, 2, 3, 4, 5]

append 可能会创建新的底层数组,因此需要小心切片共享底层数组的情况。

使用 make 创建切片

可以使用 make 函数指定初始长度和容量:

go
复制代码
slice := make([]int, 5, 10)
fmt.Printf("Length: %d, Capacity: %d\n", len(slice), cap(slice)) // Length: 5, Capacity: 10