序言
🐒:
今天开始学习 Go 语言的 变量与常量,该文章会记录一些相关的知识点和案例。
变量
定义
用于存储程序运行时可改变的数据
声明方式
| 方式 | 语法 | 适用场景 | 示例 |
|---|---|---|---|
| 标准声明 | var 变量名 类型 = 表达式 | 显式指定类型 或 需要零值初始化 | var age int = 25 |
| 类型推断 | var 变量名 = 表达式 | 省略类型,由右侧值推导 | var age = 25 |
| 声明赋值 | var 变量名 类型 变量名 = 表达式 | 省略类型,由右侧值推导先声明,后赋值 | var age int age = 25 |
| 短变量声明 | 变量名 := 表达式 | 函数内部,最常用、最简洁 | age := 25 |
| 多变量声明 | var (变量1 类型1 = 值1 ...) var 变量1,变量2 = 值1,值2 ) | 批量声明,常用于包级别 | var (age int = 25; name string = "windy") var a, b = 1,2 |
示例:标准声明
go
package main
import "fmt"
func main() {
var age int = 25
fmt.Println(age)
}
bash
25
🐒:
这种声明方式虽然很清晰明了,但对我来说有点麻烦。代码敲多了的时候,是不会考虑这种,因为有点累脑费眼,所以优先于简洁的方式。
示例:类型推断
go
package main
import "fmt"
func main() {
var age = 25
fmt.Println(age)
}
bash
25
🐒:
这种声明方式相对于第一个来说简单了些,我一般用于全局变量(看情况进行初始化),例如存储验证码。
示例:声明赋值
go
package main
import "fmt"
func main() {
var age int
age = 25
fmt.Println(age)
}
bash
25
🐒:
这种声明方式乍一看比第一个还复杂,但我用的比较多,例如初始化结构体,然后根据需求挨个进行赋值。
示例:短变量声明
go
package main
import "fmt"
func main() {
age := 25
fmt.Println(age)
}
bash
25
🐒:
这种声明方式最简洁,我用的也多,例如调用函数的时接收结果(
res, _ := myFun())。
示例:多变量声明
go
import "fmt"
func main() {
var (
age int = 25
name string = "windy"
)
fmt.Println(age, name)
var a, b = 1, 2
fmt.Println(a, b)
}
// 也可以删除类型,结果是一样的
bash
25 windy
12
🐒:
这种声明方式我基本上就是了解了解,就没用过。
短变量声明的一些小细节
作用域限制: 只能用在函数内部,不能用于包级别
重复声明规则: 在同一代码块中,若左侧变量名至少有一个是新变量,则允许对旧变量使用:=(此时旧变量的行为是赋值而非重新声明)。
特殊的空白标识符 : 如果函数的返回值有些不需要,可以给变量取名为下划线_。
示例:重复声明规则
运行成功
go
package main
import "fmt"
func myFunOne() (string, error) {
return "Hello", nil
}
func myFunTwo() (string, error) {
return "world", nil
}
func main() {
one, err := myFunOne() // 声明 a 和 err
two, err := myFunTwo() // 声明 b,对 err 赋值(合法,因为 b 是新的)
fmt.Println(one, two, err)
}
bash
Hello world <nil>
没编译时就提示报错
go
package main
import "fmt"
func myFunOne() (string, error) {
return "Hello", nil
}
func myFunTwo() (string, error) {
return "world", nil
}
func main() {
one, err := myFunOne() // 声明 a 和 err
one, err := myFunTwo() // 报错,左侧无新变量
fmt.Println(one, err)
}
// 报错提示:No new variables on the left side of ':=', Replace with'='
// 左边没有新变量,直接用 = 即可(会覆盖之前的值)
特殊的空白标识符
在 Go 的短变量声明 := 中,下划线 _ 是一个特殊的空白标识符。它用于占位,表示主动忽略某个返回值或声明项。关于它的使用,有几个关键点需要特别注意:
-
_不算"新变量",不参与重复声明规则的判定- 在
:=左侧,只要出现至少一个非_的新变量名,声明就是合法的。 _可以被重复使用任意多次,它不会导致编译错误,也不会被视为新的有效变量。
- 在
-
_无法被读取,只用于占位丢弃值_本质上不绑定任何值。你不能试图读取或使用它。- 它最常见的场景是处理函数的多返回值,比如忽略
error。
-
_声明的变量 "未使用" 不会报错- Go 编译器强制要求声明的非
_变量必须被使用。但_是唯一的例外,它可以被多次 "声明" 且永不使用。
- Go 编译器强制要求声明的非
-
_不能用于常量或类型声明_仅在 变量声明 和 赋值 场景作为占位符,不能用于const或type定义。
-
在
:=中左侧全是_是语法错误- 即使你只是为了调用一个函数并忽略其所有返回值,也不能写
_ := myFun()。 - 正确做法是 直接调用 或 使用普通赋值(非短声明):
- 即使你只是为了调用一个函数并忽略其所有返回值,也不能写
-
记住:把
_当成黑洞,它只为满足语法结构而存在,不参与任何变量计数的逻辑。
常量
定义
编译时确定、运行时不改变的值
声明方式
| 方式 | 语法 | 示例 |
|---|---|---|
| 标准声明 | const 常量名 类型 = 表达式 | const Age int = 25 |
| 类型推断 | const 常量名 = 表达式 | const Age = 25 |
| 多常量声明 | const (常量1 类型1 = 值1 ...) | const (Age int = 25; Name string = "windy") |
| 隐式重复 | 多常量声明块中省略右值时自动沿用上一行表达式 | const ( A = 1; B; C ) |
示例:标准声明
go
package main
import "fmt"
func main() {
const age int = 25
fmt.Println(age)
}
bash
25
🐒:
这种声明方式虽然有点麻烦,但很清晰明了。一般来说,常量定义的数量比变量少很多,且大多数都定义在函数外,调用常量的时候能一下子知道是什么类型的会比较好。
示例:类型推断
go
package main
import "fmt"
func main() {
const age = 25
fmt.Println(age)
}
bash
25
🐒:
这种声明方式个人习惯上不常使用,推荐第一种。
示例:多常量声明
go
package main
import "fmt"
func main() {
const (
age int = 25
name string = "windy"
)
fmt.Println(age, name)
}
// 也可以删除类型,结果是一样的
bash
25 windy
🐒:
这种声明方式一般在定义差不多含义的常量的时候使用,比如返回码( 200,404,500 等 )。
示例:隐式重复
go
package main
import "fmt"
func main() {
const (
Apple = "fruit"
Banana // = "fruit"
Cherry // = "fruit"
)
// Banana 和 Cherry 的值都是 "fruit"
fmt.Println(Apple, Banana, Cherry)
}
bash
fruit fruit fruit
🐒:
基本上没用过这种声明方式,不知道以后会不会有用得到的场景
命名规则
标识符语法规则(强制)
- 由字母(Unicode 字母)、数字、下划线
_组成。 - 不能以数字开头。
- 区分大小写 ,例如
Name和name是不同的。 - 不能是 Go 语言的 关键字 ,例如
func、var、const、if、for等。 - 下划线
_是特殊的空白标识符,不能作为常规变量名使用(声明即丢弃)。
导出规则(可见性控制)
Go 没有 public/private 关键字,而是用 首字母的大小写 控制包外可见性:
| 首字母 | 可见性 | 术语 |
|---|---|---|
| 大写 | 包外可访问 | Exported (导出标识符) |
| 小写 或 下划线 | 仅包内可访问 | Unexported (未导出标识符) |
命名风格约定(非强制,但强烈推荐)
Go 社区遵循一套统一的命名风格,保证了代码的可读性和一致性:
| 对象类型 | 风格 | 示例 |
|---|---|---|
| 变量 | 驼峰式 (camelCase) | userCount, httpRequest |
| 常量 | 驼峰式 (PascalCase 或 camelCase) | MaxRetries (导出), defaultPort (未导出) |
| 函数 / 方法 | 驼峰式 | GetUser, parseData |
| 类型名 | 驼峰式,首字母大写(导出) | UserService, httpClient (未导出) |
| 包名 | 全小写,简短,无下划线 | time, httputil, strings |
| 接口名 | 通常以 er 结尾(单方法接口) |
Reader, Writer, Closer |
| 缩写词 | 全大写或全小写,保持一致 | HTTPServer (或 httpServer),不能 写成 HttpServer |
特殊命名约定 与 注意点
_(空白标识符) :用于占位忽略值。_test后缀 :文件名xxx_test.go是测试文件,包名通常是xxx_test或原包名。- 内部包
internal:路径中包含/internal/的包只能被其父目录下的包导入,是一种强制的可见性控制。 - 避免无意义的包名 :如
util、common、helper,尽量使用描述功能的具体名字。
使用范围(作用域 Scope)
作用域决定了标识符( 变量名、常量名 )在代码中的可见范围。Go 的作用域是词法作用域 (静态作用域 ),由代码块
{}界定。
作用域层级(从大到小)
| 级别 | 声明位置 | 可见范围 |
|---|---|---|
| 全局作用域 | 所有函数之外 | 整个包(package)内 |
| 包级私有 | 函数外,首字母小写 | 仅当前包 |
| 包级导出 | 函数外,首字母大写 | 当前包 + 其他导入此包的包 |
| 函数作用域 | 函数体内任何位置 | 该函数体内(从声明点到函数结束) |
| 块作用域 | if、for、switch 的 {} 内 |
仅该花括号块内 |
作用域遮蔽 (Shadowing)
内层作用域声明的变量若与外层同名,会 遮蔽 外层变量 。这是 Go 代码中非常常见的坑。
示例
go
package main
import "fmt"
var myName = "true" // 包级变量
func main() {
fmt.Println(myName) // 输出 true
// 在块内声明同名变量,创建了新变量而非修改外层
if true {
var myName = "false"
fmt.Println(myName)
// 这个 myName 是块内新变量,值为 false
}
fmt.Println(myName) // 仍然输出 true,包级变量未被修改
}
bash
true
false
true
🐒:
为了节省脑子,尽量不重名,如果实在不想思考其他切合的名字,就直接
name1,name2。
避免遮蔽的建议
- 使用
go vet的-shadow选项检测(go vet -shadow ./...)。 - 在内部作用域中,若意图修改外层变量,直接使用赋值
=而非:=。
包级变量 与 常量 的初始化顺序
- 在同一个包内,多个
.go文件中的包级变量按 文件名字母顺序 初始化。 - 单个文件内,按声明顺序 从上到下 初始化。
- 若初始化表达式引用其他包级变量,Go 会处理依赖关系,禁止循环引用。
- 常量在编译时求值,不依赖运行时初始化顺序。