第一天GO训练营地
本文记录了训练营第一课的相关内容,并结合go入门指南中的详细介绍,对课程中未充分展开的部分进行了补充。
除了基础课程的内容,本训练营还特别强调了实践的重要性。在完成每一个小节后,通过动手编写代码和运行测试用例,可以更快巩固概念。例如,在变量部分的练习中,要求通过声明多个变量并进行简单运算,帮助我们理解变量作用域和类型推断的特点。这样的课程设置让理论和实践结合得更加紧密。
本篇主要讲述变量、控制结构、内置数据类型、数组和切片的相关内容。
函数和结构体将在后续单独成章。
介绍
Golang的特点
除了表中列出的主要特点,Golang 还注重简洁性和开发效率。例如,它内置了 goroutine 和 channel 机制,大大简化了并发程序的设计。这种高效的并发支持是 Golang 在分布式系统开发中备受欢迎的重要原因。此外,Golang 的语法风格清晰直接,减少了学习过程中的心智负担,非常适合初学者和需要快速开发的团队。
复制代码
| 特点 | 描述 |
| -------------- | -------------------------- |
| 高性能、高并发 | 性能接近C++,标准库原生支持高并发。 |
| 语法简单 | 学习曲线平缓,周期短至周计。 |
| 丰富的标准库 | 满足大部分开发需求。 |
| 工具链 | 包括编译、代码格式化、代码检查、测试功能。 |
| 静态编译 | 生成体积小、易部署的可执行文件。 |
| 快速编译 | 支持增量编译。 |
| 跨平台 | 轻松实现交叉编译。 |
| 原生GC支持 | 提供自动垃圾回收机制。 |
开发环境
本文使用 gvm 在 debian12 上进行开发,开发工具包括 vscode 和 neovim。
gvm 的github链接。
我的实验代码仓库。
在配置开发环境时,使用 gvm 是一个便捷的选择,它不仅支持 Go 的多版本管理,还提供了版本切换和环境隔离功能。需要注意的是,在安装 gvm 时,可能需要配置 zsh 或 bash 的启动脚本以正确加载环境变量。此外,开发中常用的插件如 Go Extension 也能大幅提高编辑器的智能提示能力和代码检查效率。
数据类型
表达式是某种特定类型的值,由其他值和运算符组合而成。每种类型定义了自身可组合的运算符集合,若使用不符合集合内的运算符,会在编译时报错。布尔型在控制流程中尤为常用,例如用作循环或条件语句的判断条件。此外,Go 语言中没有隐式类型转换,例如布尔值不能直接与整型值进行运算。这种设计减少了潜在的类型混淆,但也需要程序员显式进行类型转换。
Bool
示例:var b bool = true。
布尔型的值只能是 true 或 false。两个相同类型的值可通过 == 或 != 进行比较,返回布尔值 true 或 false,但只有类型相同时才能比较。
go
// 示例代码
var aVar = 10
aVar == 5 // false
aVar == 10 // true
数字类型
Go 语言支持整型、浮点型和复数,位运算采用补码。基于架构的类型如 int、uint 和 uintptr,其长度依赖于操作系统:
- 在 32 位操作系统上,
int和uint长度为 32 位(4 字节)。 - 在 64 位操作系统上,
int和uint长度为 64 位(8 字节)。 uintptr的长度足以存储指针。
当涉及到跨平台开发时,理解架构相关类型(如 int 和 uint)的长度差异尤为重要。例如,在处理需要高精度计算的场景时,应优先使用 int64 或 uint64,以确保跨平台结果一致。此外,Go 的整数运算遵循溢出截断原则,这在循环计数或位操作时需要特别注意。
Go 语言没有 float,只有 float32 和 float64,没有 double 类型。
整数类型
int8:-128 到 127int16:-32768 到 32767int32:-2147483648 到 2147483647int64:-9223372036854775808 到 9223372036854775807
无符号整数
uint8:0 到 255uint16:0 到 65535uint32:0 到 4294967295uint64:0 到 18446744073709551615
浮点型(IEEE-754 标准)
float32:±1e-45 到 ±3.4e38float64:±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 的独立类型。字符是整数的特殊用例。byte 是 uint8 的别名。字符用单引号括起,可将数字赋给 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 语言中的条件语句包括 if、if-else 和 switch。这些语句的语法简洁,同时内置了对逻辑条件的强制检查。
if 和 if-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 循环,并可通过不同写法实现 while 和 do-while 的功能。与其他语言不同,Go 中没有 while 和 do-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