「笔记001」Go语法基础1
一、简介
Golang(简称 Go)是由 Google 开发的一种静态强类型、编译型编程语言,因其简洁的语法设计、高效的运行时性能以及强大的并发支持,成为现代编程语言中的佼佼者。Go 语言注重简洁性和开发效率,主要特点包括:快速编译、高效并发模型、垃圾回收以及丰富的标准库支持,尤其适合后端服务开发、高并发程序以及微服务架构。
本文将简略介绍作者学到的 Go 语言的基础语法。
二、变量与常量
1. 变量声明
Go 的变量声明非常灵活,支持以下几种方式:
- 显式声明:使用
var
关键字指定类型。 - 自动推断类型:通过赋值自动推断。
- 简短声明:函数内部使用
:=
。
示例代码:
go
var name string = "Go" // 显式声明
age := 10 // 简短声明,自动推断为 int 类型
2. 常量
常量通过 const
声明,表示不可修改的固定值。
go
const Pi = 3.14159
知识点讲解:
- 类型推断:简短声明
:=
只适用于函数内部,是 Go 提升代码简洁性的关键。但在类型不明确的情况下,建议显式声明。 - 作用域:变量作用域依赖于声明位置。在函数内部声明的变量作用域仅限于函数内,而包级变量作用域在整个包中。
注意事项:
- 避免变量重复声明,特别是在复杂的代码块中可能造成混淆。
- 使用常量声明替代魔法数字(如 3.14),提升代码可读性。
三、控制流
1. for 循环
Go 的 for
循环是唯一的循环结构,但提供了多种形式:
- 标准计数器循环:
go for i := 0; i < 5; i++ { fmt.Println(i) }
- 条件循环:
go i := 0 for i < 3 { fmt.Println(i) i++ }
- 无限循环:
go for { fmt.Println("Infinite Loop") }
2. if-else 和 switch
if-else 语句:
go if num := 10; num > 5 { fmt.Println("Greater than 5") } else { fmt.Println("Less or equal to 5") }
- 注意:
num
的作用域仅限于if
块。
- 注意:
switch 语句:
go switch time.Now().Weekday() { case time.Monday: fmt.Println("Start of the week") case time.Sunday: fmt.Println("Weekend") default: fmt.Println("Midweek") }
Go 的switch
无需显式break
,默认跳出当前分支。
知识点讲解:
- Switch 的多样性:支持多个条件表达式,还可以省略条件,用作多分支的
if-else
替代。 - 短变量声明:在
if
和switch
中,常结合短变量声明提高代码紧凑性。
注意事项:
- 循环条件需谨慎,避免无限循环导致程序卡死。
- 如果需要多个条件表达式匹配同一分支,可用逗号分隔条件。
四、数组与切片
1. 数组
数组是固定大小的,同一数组内元素类型必须一致。
go
var arr [5]int = [5]int{1, 2, 3, 4, 5}
2. 切片
切片是动态大小的数组,使用更灵活:
go
s := []int{1, 2, 3}
s = append(s, 4, 5) // 添加元素
fmt.Println(s) // [1, 2, 3, 4, 5]
共享底层数组:
切片操作共享底层数据,因此修改切片内容会影响原始数组:
go
arr := [3]int{1, 2, 3}
s := arr[:]
s[0] = 99
fmt.Println(arr) // [99, 2, 3]
知识点讲解:
- 容量与长度:切片的长度(
len
)是当前元素数,容量(cap
)是从起始位置到底层数组末尾的空间。 - 切片副本:使用
copy
函数创建独立副本,避免原数据被修改。
注意事项:
- 使用切片操作时要小心数据共享导致的意外副作用。
- 如果需要扩展容量,建议提前预估容量大小以减少动态分配次数。
五、Map
Map 是键值对的集合,支持动态大小,但存储是无序的。
go
m := map[string]int{"one": 1, "two": 2}
m["three"] = 3
检测键是否存在:
go
val, ok := m["one"]
if ok {
fmt.Println("Key exists with value", val)
} else {
fmt.Println("Key does not exist")
}
知识点讲解:
- 零值行为:访问不存在的键不会报错,而是返回该值类型的零值。例如:整数返回
0
,字符串返回""
。 - 删除键值对:使用
delete(map, key)
删除指定键。
注意事项:
- Map 是无序的,遍历时不能假设顺序。
- 并发操作 Map 时需使用
sync.Map
或加锁,避免数据竞争。
六、函数与指针
1. 函数
Go 支持多返回值和具名返回值:
go
func divide(a, b int) (result int, err error) {
if b == 0 {
err = errors.New("division by zero")
return
}
result = a / b
return
}
2. 指针
Go 中的指针简化了内存操作:
go
func increment(n *int) {
*n++
}
num := 5
increment(&num)
fmt.Println(num) // 6
知识点讲解:
- 值传递与引用传递:普通变量在函数中是值传递,无法修改原值,而指针传递可以直接修改变量内容。
- 指针零值:指针未初始化时为
nil
,需避免对nil
指针解引用。
注意事项:
- 使用指针时要注意内存安全,避免出现悬挂指针或非法访问。
- 指针是提升性能的利器,但过多使用会降低代码可读性。
七、错误处理
Go 的错误处理是显式的,通常与多返回值结合使用:
go
func readFile(filename string) (string, error) {
data, err := os.ReadFile(filename)
if err != nil {
return "", err
}
return string(data), nil
}
通过 errors
包自定义错误:
go
if err != nil {
return "", errors.New("custom error message")
}
知识点讲解:
- 链式错误处理:多个函数调用返回错误时,需逐层检查并处理。
- 错误包装:可以使用
fmt.Errorf
添加上下文信息。
注意事项:
- 错误处理代码通常需要集中化,避免错误被忽略。
- 对关键操作务必检查返回的错误值,确保程序行为正确。
注意总结
- 变量作用域:根据需要设置变量的最小作用域范围,减少意外覆盖风险。
- 循环与条件:谨慎设置循环条件,避免死循环或漏处理分支。
- 数组与切片共享:操作切片时需要注意底层数据共享,适当使用
copy
创建副本。 - Map 并发访问:Map 默认不是线程安全的,使用时需加锁或采用并发友好数据结构。
- 指针使用:在提升性能的同时需注意指针安全,避免
nil
指针访问。 - 错误处理:显式错误返回是 Golang 的特色,应结合上下文进行适当封装,避免错误被忽视。