Go语言新手村:轻松理解变量、常量和枚举用法
1. 介绍
1.1 发展
2007年,Google开始着手开发Go语言。
2009年11月,Google发布了Go语言的第一个稳定版本,即Go 1.0。这标志着Go语言正式面世,并开始逐渐受到广泛关注和应用。
2011年5月,Go 1.0.2发布,这是Go语言的第一个正式稳定版本,其中包括了一些重要的bug修复和改进。
2018年8月,Go 1.11发布,引入了模块支持、WebAssembly支持、延迟函数调用等特性,这些特性使得Go语言在项目管理、跨平台应用和异步编程等方面有了更加强大的能力。
2021年2月,Go 1.16发布,加入了对模块和错误处理的改进、垃圾回收器的性能提升等特性,这些改进使得Go语言在性能、可靠性和开发体验等方面有了更大的提升。
1.2 特点
- 高效性:Go语言具有高效的编译速度、并发处理能力和内存管理机制,适合处理高并发、高吞吐量的场景。
- 并发性:Go语言提供了原生的并发编程模型,通过协程(goroutine)和通道(channel)实现了轻量级的线程管理和通信,使得编写并发程序变得更加容易。
- 安全性:Go语言内置了安全性特性,例如强类型、空指针检查、内存自动管理等,减少了安全漏洞和错误的可能性。
- 跨平台:Go语言支持多种操作系统和CPU架构,可以轻松地在不同平台之间进行开发和部署。
2. 基础概念
2.1 内建变量类型
Go 语言中内建变量类型分为三种,分别是布尔类型、数值类型和字符串类型
- 布尔类型:布尔类型的关键字是
bool
,取值范围是true 或 false
,其中默认值为false
- 数值类型:Go 语言中数值类型很多,包括整数、浮点数、复数。这三种数值类型还有不同的长度。其中整型不仅有不同长度,还分为有符号整数(int)和无符号整数(uint)
类型 | 描述 | 大小 | 范围 |
---|---|---|---|
int |
平台相关有符号整数 | 32位或64位 | 根据平台变化 |
int8 |
8位有符号整数 | 8位 | -128 到 127 |
int16 |
16位有符号整数 | 16位 | -32768 到 32767 |
int32 |
32位有符号整数 | 32位 | -2147483648 到 2147483647 |
int64 |
64位有符号整数 | 64位 | -9223372036854775808 到 9223372036854775807 |
uint |
平台相关无符号整数 | 32位或64位 | 根据平台变化 |
uint8 (byte) |
8位无符号整数 | 8位 | 0 到 255 |
uint16 |
16位无符号整数 | 16位 | 0 到 65535 |
uint32 |
32位无符号整数 | 32位 | 0 到 4294967295 |
uint64 |
64位无符号整数 | 64位 | 0 到 18446744073709551615 |
uintptr |
存储指针的整数 | 平台相关 | 足够容纳指针值 |
rune |
Unicode码点 | 32位 | 等同于 int32 |
float32 |
单精度浮点数 | 32位 | 指数范围:-126到+127 |
float64 |
双精度浮点数 | 64位 | 指数范围:-1022到+1023 |
complex64 |
实部虚部分别32位的复数 | 64位 | 范围同float32 |
complex128 |
实部虚部分别64位的复数 | 128位 | 范围同float64 |
- 字符串类型:关键字
string
,默认值为空字符串
在 Go 语言中还有两个特殊的类型,rune
和 byte
,它们本质上其实是类型别名。rune 是 int32 的别名,常用于处理文本字符,尤其是非ASCII字符;byte 是 uint8 的别名,常用于处理字节、二进制文本。
2.2 变量
Go 语言中使用 var 关键字定义变量,实例代码如下
func main() {
var a int
var b string
var c bool
// 输出结果是该类型的零值(默认值)
fmt.Printf("a = %d, b = %q,c = %t \n", a, b, c)
a = 100
b = "hello"
c = true
// 延迟赋值
fmt.Printf("a = %d, b = %q,c = %t \n", a, b, c)
// a = 100, b = "hello",c = true
var name string = "张三"
var age int = 18
// 立刻赋值
fmt.Printf("name = %s, age = %d \n", name, age)
// name = 张三, age = 18
var country = "中国"
var price = 100
// 类型推导
fmt.Printf("country = %s, price = %d \n", country, price)
// country = 中国, price = 100
// 短变量声明
year := 2025
province, city := "江苏省", "苏州市"
fmt.Printf("year = %d, province = %s, city = %s \n", year, province, city)
// year = 2025, province = 江苏省, city = 苏州市
x, _ := anonymous()
fmt.Println(x)
// 李四
_, y := anonymous()
fmt.Println(y)
// 20
}
func anonymous() (string, int) {
return "李四", 20
}
在这一段代码中,展示了Go语言中的变量声明核心概念,下面来详细介绍一下。
- 零值初始化:Go为所有基础类型提供零值保障,避免未初始化风险
var a int // 未显式赋值 → 默认零值:0
var b string // 未显式赋值 → 默认零值:空字符串 ""
var c bool // 未显式赋值 → 默认零值:false
fmt.Printf("a = %d, b = %q, c = %t \n", a, b, c)
// 输出:a = 0, b = "", c = false
- 运行时动态修改值:变量在声明后随时修改变量值,但是变量类型固定后不可变更。
a = 100 // 整型赋值
b = "hello" // 字符串赋值
c = true // 布尔赋值
fmt.Printf("a = %d, b = %q, c = %t \n", a, b, c)
// 输出:a = 100, b = "hello", c = true
- 声明时赋值:在声明变量时立刻给变量赋值
var name string = "张三" // 显式声明string类型
var age int = 18 // 显式声明int类型
fmt.Printf("name = %s, age = %d \n", name, age)
// 输出:name = 张三, age = 18
- 类型推导:声明时省略变量类型且立刻赋值,编译器根据赋值自动推断类型
var country = "中国" // 自动推导为string类型
var price = 100 // 自动推导为int类型
fmt.Printf("country = %s, price = %d \n", country, price)
// 输出:country = 中国, price = 100
- 短变量声明:在函数内部快速声明并初始化变量的方式,并能自动推导类型
year := 2025 // 等价于 var year int = 2025
province, city := "江苏省", "苏州市" // 多变量同时声明
fmt.Printf("year = %d, province = %s, city = %s \n", year, province, city)
// year = 2025, province = 江苏省, city = 苏州市
- 匿名变量:在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量。 匿名变量用一个下划线
_
进行占位。匿名变量不占用命名空间,不会分配内存
func anonymous() (string, int) {
return "李四", 20
}
func main {
x, _ := anonymous()
fmt.Println(x)
// 李四
_, y := anonymous()
fmt.Println(y)
// 20
}
2.3 常量
常量使用 const
关键字进行声明。其基本语法与 var
声明类似。常量是在编译时就确定其值,并且在程序运行期间不可更改的标识符,所以常量在声明时必须赋值,且不可更改。
枚举
在 Go 语言中,虽然没有像其他编程语言(如 Java 或 C#)那样直接提供 enum
关键字来定义枚举类型,但可以通过使用 const
关键字结合 iota
标识符实现类似的功能。所以枚举值也可以看作一种特殊的常量类型。
iota
是 Go 语言中的一个特殊标识符,仅在 const
声明块中有效。它代表了从 0 开始的连续整数值。每当 const
声明块中出现新的一行(以换行符分隔),iota
的值就自动递增 1。这种机制非常适合用来定义枚举值。
// 定义星期几的枚举值
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
func main() {
fmt.Println("Sunday =", Sunday)
fmt.Println("Monday =", Monday)
fmt.Println("Tuesday =", Tuesday)
fmt.Println("Wednesday =", Wednesday)
fmt.Println("Thursday =", Thursday)
fmt.Println("Friday =", Friday)
fmt.Println("Saturday =", Saturday)
}
除了简单的递增序列,iota
还可以与其它表达式结合使用,生成更加复杂的枚举值。
- 指定起始值:
const (
a = iota + 1 // 1
b // 2
c // 3
)
- 跳过某些值:
const (
_ = iota // 忽略第0个值
a // 1
_ // 忽略第2个值
b // 3
)
- 位操作
const (
b = 1 << (10 * iota) // 1
kb // 1024
mb // 1048576
gb // 1073741824
tb // 1099511627776
pb // 1125899906842624
)
2.4 作用域
Go 也是一种静态语言
,静态语言在使用变量之前需要先声明。Go 语言和常见的静态语言一样,存在全局和局部两种作用域,其中全局作用域在Go语言中也被称为包级作用域
。无论是变量 (var
) 还是常量 (const
),它们的作用域都遵循完全相同的规则,主要取决于它们声明的位置。
包级作用域 (Package-Level Scope)
- 位置:在任何函数、方法或代码块之外的顶层声明。
- 生命周期:从程序启动时创建,到程序结束时销毁。
局部作用域 (Local Scope)
- 位置:在函数内部、方法内部或代码块内部(如
if
,for
,switch
语句的{}
内部)声明。 - 生命周期:从声明/初始化点开始,到包含它的函数、方法或代码块执行结束时终止。
接下来通过示例介绍一下包级变量的声明以及赋值,常量同理。
package main
import "fmt"
// 单独声明(使用零值)
var a int // 零值为 0
// 声明并显式赋值
var b string = "hello"
// 声明并使用类型推导
var c = true // 推导为 bool
// 使用 var() 块集中声明
var (
name string // 仅声明,零值 ""
age int // 仅声明,零值 0
country string = "中国" // 声明并赋值
province, city = "江苏", "苏州" // 多变量声明并赋值,类型推导
)
func main() {
// 包级变量在整个包内(包括main函数)都可访问
fmt.Printf("a = %d, b = %s, c = %t \n", a, b, c)
// 输出:a = 0, b = hello, c = true
// 可以在函数内部修改包级变量的值
name = "张三"
age = 18
fmt.Printf("name = %s, age = %d, country = %s, province = %s, city = %s \n", name, age, country, province, city)
// 输出:name = 张三, age = 18, country = 中国, province = 江苏, city = 苏州
}
这里主要介绍了四种包级变量的声明方式
在前一节中有一个短变量声明,短变量声明只能声明局部变量
方式 | 示例 | 适用场景 |
---|---|---|
零值声明 | var a int |
需要默认值时 |
显式类型初始化 | var b string = "hello" |
明确指定类型避免歧义 |
类型推导 | var c = true |
右侧值类型明确时简化代码 |
分组声明 | var (...) |
组织相关变量,增强可读性 |
包级变量的导出
在 Go 语言中,包级变量是指那些在包的顶层声明的变量,即不在任何函数内部声明的变量。这些变量可以在整个包内访问,并且如果它们的名字以大写字母开头,则还可以被其他包通过导入的方式访问。这种机制是 Go 实现封装和模块化的重要部分。
假设我们有一个名为 other
的包,其中包含了一些可导出的变量。
package other
// 这是一个可导出的包级变量,因为它以大写字母开头
var ExportedVar = "This is an exported variable"
// 这个变量不可导出,因为它以小写字母开头
var unexportedVar = "This is a unexported variable"
然后,在另一个文件或项目中,你可以这样导入并使用 other
包中的可导出变量:
package main
import (
"fmt"
"go-learning/other"
)
func main() {
fmt.Printf("可导出变量:%s \n", other.ExportedVar)
// 可导出变量:This is an exported variable
// 尝试访问不可导出的包级变量(会导致编译错误)
// fmt.Println(other.unexportedVar)
// Error:Cannot use the unexported variable 'unexportedVar' in the current package
other.ExportedVar = "这是修改后的可导出变量"
fmt.Printf("修改后的可导出变量:%s \n", other.ExportedVar)
// 修改后的可导出变量:这是修改后的可导出变量
}
包级变量的修改
直接修改其他包中的变量虽然可行,但不推荐,因为这会导致代码的耦合性增加,难以维护。通常,我们通过函数来封装对变量的访问和修改,这样可以加入额外的逻辑(如验证、加锁等)。
package other
import "sync"
// 这是一个可导出的包级变量,因为它以大写字母开头
var ExportedVar = "This is an exported variable"
// 这个变量不可导出,因为它以小写字母开头
var unexportedVar = "This is a unexported variable"
var (
mu sync.Mutex // 内部锁(不可导出)
)
// UpdateVal 安全修改配置的函数
func UpdateVal(newString string) {
mu.Lock()
defer mu.Unlock()
ExportedVar = newString
}
package main
import (
"fmt"
"go-learning/other"
)
func main() {
fmt.Printf("可导出变量:%s \n", other.ExportedVar)
other.UpdateVal("这是安全修改后的可导出变量")
fmt.Printf("安全修改后的不可导出变量:%s \n", other.ExportedVar)
// 安全修改后的不可导出变量:这是安全修改后的可导出变量
}
3. 小结
本文介绍了Go语言的基础类型、变量与常量的定义和作用域。讲解了包级与局部作用域的区别,以及可导出变量/常量的规则。