类型系统
内置类型¶
内置类型分为了两大类:
- 值类型: 包括
bool,int系列,uint系列,float系列,string,complex(复数) 以及array(固定长度数组). - 引用类型 (指针类型): 包括
slice(切片),map(映射) 和chan(管道). 这些是Go中非常重要且常用的数据结构.
内置函数¶
这些函数无需导入任何包即可直接使用, 例如:
append: 向数组或切片追加元素.len和cap: 分别用于获取长度和容量.make和new: 都用于内存分配.make专用于slice,map,chan的初始化, 返回的是类型本身;new用于分配值类型的内存, 返回的是指向该类型的指针.panic和recover: 用于Go语言的异常处理机制.delete: 用于从map中删除键值对.close: 用于关闭chan.copy: 用于复制切片.
内置 error 接口¶
error 是一个内置的接口类型, 它只有一个方法: Error() string. 任何实现了这个方法的类型, 都可以被当作一个错误类型来处理. 这是Go语言中处理错误的惯用方式.
_¶
_主要有两种用法:
-
在
import中使用:- 当
import一个包时, 如果前面加上下划线, 如import _ "package_name", 那么 Go 只会执行该包的init()函数, 而不会将该包的其他函数或变量引入当前作用域. - 这种用法通常用于某些只需要其副作用(side effect)的包, 例如注册数据库驱动.
- 当
-
在代码中作为占位符:
- 当一个函数返回多个值, 但你只关心其中一部分时, 可以用下划线来忽略不关心的返回值.
- 例如,
os.Open()函数返回一个文件对象和一个错误. 如果你不关心错误处理(虽然通常不推荐这样做), 可以写成file, _ := os.Open("filename.txt"). - 这样做可以避免编译器因为定义了未使用变量而报错, 同时也能让代码更清晰.
变量和常量¶
变量¶
- 变量的意义: 程序运行时的数据都保存在内存中. 为了方便地操作这些数据, 我们使用"变量名"来关联一块内存地址, 而不是直接使用复杂的内存地址.
- 变量类型: Go 语言是静态类型语言, 每个变量都有明确的类型, 如整型 (
int), 字符串 (string), 布尔型 (bool) 等. - 变量声明:
- 标准声明: 使用
var关键字, 格式为var 变量名 变量类型. - 批量声明: 当有多个变量需要声明时, 可以使用
var加上括号()的形式. - 变量初始化: 声明变量时可以同时赋值.
- 标准格式:
var 变量名 类型 = 表达式(例如:var name string = "Go语言") - 类型推导: 如果提供了初始值, Go 编译器可以自动推断变量类型, 所以可以省略类型. (例如:
var name = "Go语言") - 短变量声明: 在函数内部, 可以使用
:=这种更简洁的方式来声明并初始化变量. (例如:name := "Go语言"). 注意: 这种方式只能用在函数内部.
- 标准格式:
- 标准声明: 使用
- 匿名变量:
- 使用下划线
_作为变量名. - 它用于接收一个你不需要使用的值, 比如函数返回多个值, 但你只关心其中一部分.
- 匿名变量不占用内存, 也不会引起"变量已声明但未使用"的编译错误.
- 使用下划线
常量¶
- 常量的意义: 常量是指程序运行期间值不能被修改的量.
-
声明方式: 使用
const关键字. 常量在定义时就必须赋值.- 在
const块中, 如果某一行没有赋值, 它会默认使用上一行的值.
- 在
-
iota常量计数器:iota是 Go 语言的一个特殊常量, 可以看作是一个在const块中自动递增的行索引.- 每当
const关键字出现时,iota的值会被重置为0. - 在
const块中, 每新增一行常量声明,iota的值就会加1. iota非常适合用来定义枚举值或者一组有规律递增的常量, 例如定义文件大小单位 (KB, MB, GB...).
作用域¶
Go 语言中的作用域规则非常清晰, 主要分为以下几种:
局部作用域¶
这是最常见的作用域.
- 定义: 在函数内部或者代码块(Block)内部声明的变量, 就属于局部作用域. 代码块通常指由花括号
{}包围的区域, 例如if,for,switch语句等. - 生命周期: 它的生命周期从声明的那一刻开始, 到它所在的函数或代码块执行结束时为止.
- 特点:
- 只能在它被声明的函数或代码块内部访问.
:=短变量声明只能用在局部作用域中.
示例代码:
package main
import "fmt"
func main() { // main 函数的开始
// a 是 main 函数的局部变量
a := 10
fmt.Println("在 main 函数中 a =", a) // 输出: 10
if true { // if 代码块的开始
// b 是 if 代码块的局部变量
b := 20
fmt.Println("在 if 块中 a =", a) // 可以访问外部的 a
fmt.Println("在 if 块中 b =", b) // 输出: 20
} // if 代码块的结束, b 在这里被销毁
// fmt.Println(b) // 这行会编译报错! 因为 b 的作用域只在 if 块内
} // main 函数的结束, a 在这里被销毁
包级作用域¶
有时我们希望一个变量能在整个包的所有文件中共享, 这时就需要包级作用域.
- 定义: 在任何函数之外声明的变量, 就属于包级作用域.
- 生命周期: 从程序开始执行时创建, 直到程序结束时销毁.
- 特点:
- 同一个包 (
package) 下的所有.go文件都可以直接访问它. - 声明时必须使用
var关键字, 不能用:=.
- 同一个包 (
根据变量名的首字母大小写, 包级变量又分为两种:
包内私有变量¶
- 命名: 变量名以小写字母开头.
- 访问权限: 只能在当前包内部使用, 其他包无法访问.
示例 (mymath/math.go):
全局公共变量¶
- 命名: 变量名以大写字母开头.
- 访问权限: 不仅可以在当前包内部使用, 还可以被其他包导入后使用. 这相当于其他语言中的
public变量.
示例 (mymath/math.go):
在另一个包中如何使用 (main.go):
package main
import (
"fmt"
"your_module_name/mymath" // 导入 mymath 包
)
func main() {
// 通过 "包名.变量名" 的方式访问
fmt.Println(mymath.Pi) // 正确, 可以访问
// fmt.Println(mymath.pi) // 编译报错! 无法访问小写字母开头的包级变量
}
变量屏蔽¶
这是一个非常重要且有时会引起 bug 的概念.
当你在一个内部作用域中声明了一个和外部作用域同名的变量时, 内部的这个变量会"屏蔽"或"隐藏"外部的同名变量. 在该内部作用域中, 你访问的将是新声明的这个变量.
示例代码:
package main
import "fmt"
// 包级变量
var level = "Package"
func main() {
fmt.Println("Main 开始:", level) // 输出: Package
// 在 main 函数中声明一个同名变量 level
level := "Function" // 这里创建了一个新的局部变量 level, 屏蔽了包级的 level
fmt.Println("Main 内部:", level) // 输出: Function
if true {
// 在 if 块中再次声明一个同名变量
level := "Block" // 又创建了一个新的, 作用域更小的局部变量
fmt.Println("If 块内部:", level) // 输出: Block
}
// 回到 main 函数作用域, if 块内的 level 已销毁
fmt.Println("Main 结束:", level) // 输出: Function
}
总结¶
| 作用域类型 | 声明位置 | 声明方式 | 可见范围 |
|---|---|---|---|
| 局部作用域 | 函数或代码块 {} 内部 |
var 或 := |
仅在当前函数或代码块内 |
| 包级作用域 (私有) | 任何函数外部 | var (小写开头) |
仅在当前包 (package) 内 |
| 包级作用域 (公共) | 任何函数外部 | var (大写开头) |
在所有包中(需导入) |