Go语言变量与常量
本文档详细介绍Go语言中变量和常量的声明、初始化、作用域和使用方法。
📋 目录
变量基础
变量概念
变量是程序中用来存储数据的内存位置,通过变量名可以访问和修改存储的值。
变量的特点:
- 有名称(标识符)
- 有类型(决定存储的数据类型)
- 有值(存储的具体数据)
- 有地址(在内存中的位置)
go
var age int = 25 // 变量名: age, 类型: int, 值: 25
fmt.Printf("变量值: %d, 地址: %p\n", age, &age)零值
Go语言中,声明但未初始化的变量会自动设置为对应类型的零值:
| 类型 | 零值 |
|---|---|
| 数值类型 | 0 |
| 布尔类型 | false |
| 字符串类型 | "" (空字符串) |
| 指针、切片、映射、通道、函数、接口 | nil |
go
var a int // 0
var b float64 // 0.0
var c bool // false
var d string // ""
var e []int // nil
var f *int // nil
fmt.Printf("int零值: %d\n", a)
fmt.Printf("float64零值: %f\n", b)
fmt.Printf("bool零值: %t\n", c)
fmt.Printf("string零值: %q\n", d)
fmt.Printf("slice零值: %v\n", e)
fmt.Printf("pointer零值: %v\n", f)变量声明
1. 标准声明
使用 var 关键字声明变量:
go
// 基本语法
var 变量名 类型 = 初始值
// 示例
var name string = "Go语言"
var age int = 10
var height float64 = 1.75
var isStudent bool = true2. 类型推导
省略类型,让编译器自动推导:
go
var name = "Go语言" // 推导为 string
var age = 25 // 推导为 int
var height = 1.75 // 推导为 float64
var isActive = true // 推导为 bool3. 短变量声明
使用 := 操作符(仅在函数内部使用):
go
func main() {
name := "Go语言"
age := 25
height := 1.75
isActive := true
fmt.Printf("姓名: %s, 年龄: %d\n", name, age)
}4. 批量声明
同类型批量声明
go
// 方式1:一行声明多个
var a, b, c int = 1, 2, 3
// 方式2:分别声明
var x int = 10
var y int = 20
var z int = 30
// 方式3:短变量声明
a, b, c := 1, 2, 3不同类型批量声明
go
// 使用var块
var (
name string = "张三"
age int = 25
height float64 = 1.75
isActive bool = true
)
// 类型推导
var (
name = "张三"
age = 25
height = 1.75
isActive = true
)5. 变量重新声明
在短变量声明中,如果至少有一个新变量,可以重新声明已存在的变量:
go
func main() {
name := "张三"
fmt.Println("第一次:", name)
// 重新声明name,同时声明新变量age
name, age := "李四", 25
fmt.Printf("重新声明后: %s, %d\n", name, age)
// 错误:没有新变量
// name, age := "王五", 30 // 编译错误
}6. 匿名变量
使用下划线 _ 忽略不需要的值:
go
// 函数返回多个值,只需要其中一个
func getUserInfo() (string, int, bool) {
return "张三", 25, true
}
func main() {
name, _, isActive := getUserInfo() // 忽略年龄
fmt.Printf("姓名: %s, 状态: %t\n", name, isActive)
// 忽略所有返回值(仅执行函数)
_, _, _ = getUserInfo()
}常量
常量基础
常量是在编译时确定的值,程序运行期间不能修改。
go
// 基本语法
const 常量名 类型 = 值
// 示例
const pi float64 = 3.14159
const name string = "Go语言"
const maxSize int = 1024常量声明方式
1. 单个常量
go
const pi = 3.14159
const greeting = "Hello, World!"
const maxRetries = 32. 批量常量
go
const (
StatusOK = 200
StatusNotFound = 404
StatusError = 500
)
// 类型化常量
const (
Red int = 1
Green int = 2
Blue int = 3
)3. 常量组中的省略
在常量组中,除第一个外,其他常量可以省略类型和值:
go
const (
a = 1
b // b = 1 (继承上一个值)
c // c = 1 (继承上一个值)
)
const (
x = "hello"
y // y = "hello"
z // z = "hello"
)iota 常量生成器
iota 是Go语言的常量计数器,在每个 const 关键字出现时重置为0:
基本用法
go
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)高级用法
go
// 跳过某些值
const (
_ = iota // 0 (跳过)
KB = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20 = 1048576
GB // 1 << 30 = 1073741824
)
// 表达式中使用iota
const (
bit0 = 1 << iota // 1 << 0 = 1
bit1 // 1 << 1 = 2
bit2 // 1 << 2 = 4
bit3 // 1 << 3 = 8
)
// 多个常量使用同一个iota值
const (
a, b = iota + 1, iota + 2 // a=1, b=2 (iota=0)
c, d // c=2, d=3 (iota=1)
e, f // e=3, f=4 (iota=2)
)实际应用示例
go
// 文件权限
const (
ReadPermission = 1 << iota // 1 (001)
WritePermission // 2 (010)
ExecutePermission // 4 (100)
)
// 状态枚举
type Status int
const (
Pending Status = iota
Running
Completed
Failed
)
func (s Status) String() string {
switch s {
case Pending:
return "Pending"
case Running:
return "Running"
case Completed:
return "Completed"
case Failed:
return "Failed"
default:
return "Unknown"
}
}作用域
作用域类型
Go语言中有四种作用域:
- 全局作用域 - 包级别
- 函数作用域 - 函数内部
- 块作用域 - 代码块内部
- 文件作用域 - 导入的包名
1. 全局作用域
go
package main
import "fmt"
// 全局变量
var globalVar = "我是全局变量"
const globalConst = "我是全局常量"
func main() {
fmt.Println(globalVar)
fmt.Println(globalConst)
// 全局变量可以在任何函数中访问
otherFunction()
}
func otherFunction() {
fmt.Println("在其他函数中访问:", globalVar)
}2. 函数作用域
go
func example() {
// 函数参数具有函数作用域
var localVar = "我是局部变量"
fmt.Println(localVar) // 可以访问
}
func main() {
example()
// fmt.Println(localVar) // 错误:无法访问
}3. 块作用域
go
func main() {
x := 10
if x > 5 {
y := 20 // y只在if块内有效
fmt.Printf("x=%d, y=%d\n", x, y)
}
// fmt.Println(y) // 错误:y超出作用域
for i := 0; i < 3; i++ {
z := i * 2 // z只在for循环内有效
fmt.Printf("i=%d, z=%d\n", i, z)
}
// fmt.Println(i) // 错误:i超出作用域
// fmt.Println(z) // 错误:z超出作用域
}4. 变量遮蔽
内层作用域的变量可以遮蔽外层同名变量:
go
var x = "全局x"
func main() {
fmt.Println("1:", x) // 全局x
x := "函数x"
fmt.Println("2:", x) // 函数x (遮蔽全局x)
{
x := "块x"
fmt.Println("3:", x) // 块x (遮蔽函数x)
}
fmt.Println("4:", x) // 函数x
}类型转换
Go语言不支持隐式类型转换,必须显式转换:
基本类型转换
go
// 语法:T(v) - 将v转换为类型T
var a int = 42
var b float64 = float64(a) // int转float64
var c int32 = int32(a) // int转int32
var d string = string(rune(a)) // int转string(需要通过rune)
fmt.Printf("a=%d, b=%f, c=%d, d=%s\n", a, b, c, d)数值类型转换
go
func main() {
// 整数转换
var i8 int8 = 127
var i16 int16 = int16(i8)
var i32 int32 = int32(i16)
var i64 int64 = int64(i32)
fmt.Printf("int8: %d, int16: %d, int32: %d, int64: %d\n", i8, i16, i32, i64)
// 浮点数转换
var f32 float32 = 3.14
var f64 float64 = float64(f32)
fmt.Printf("float32: %f, float64: %f\n", f32, f64)
// 整数和浮点数互转
var num int = 42
var fnum float64 = float64(num)
var backToInt int = int(fnum)
fmt.Printf("int: %d, float64: %f, back to int: %d\n", num, fnum, backToInt)
}字符串转换
基本类型与字符串互转
go
import (
"fmt"
"strconv"
)
func main() {
// 基本类型转字符串
var num int = 42
var pi float64 = 3.14159
var flag bool = true
// 方法1: fmt.Sprintf
str1 := fmt.Sprintf("%d", num)
str2 := fmt.Sprintf("%.2f", pi)
str3 := fmt.Sprintf("%t", flag)
fmt.Printf("sprintf: %s, %s, %s\n", str1, str2, str3)
// 方法2: strconv包
str4 := strconv.Itoa(num) // int转string
str5 := strconv.FormatFloat(pi, 'f', 2, 64) // float64转string
str6 := strconv.FormatBool(flag) // bool转string
fmt.Printf("strconv: %s, %s, %s\n", str4, str5, str6)
}字符串转基本类型
go
func main() {
// 字符串转基本类型
numStr := "42"
piStr := "3.14159"
flagStr := "true"
// 转换并处理错误
num, err1 := strconv.Atoi(numStr)
if err1 != nil {
fmt.Printf("转换错误: %v\n", err1)
}
pi, err2 := strconv.ParseFloat(piStr, 64)
if err2 != nil {
fmt.Printf("转换错误: %v\n", err2)
}
flag, err3 := strconv.ParseBool(flagStr)
if err3 != nil {
fmt.Printf("转换错误: %v\n", err3)
}
fmt.Printf("转换结果: %d, %f, %t\n", num, pi, flag)
// 无效转换示例
invalidStr := "hello"
_, err := strconv.Atoi(invalidStr)
if err != nil {
fmt.Printf("无效转换: %v\n", err)
}
}类型转换注意事项
精度丢失
govar f float64 = 3.99 var i int = int(f) // i = 3 (小数部分丢失) fmt.Printf("原值: %f, 转换后: %d\n", f, i)溢出处理
govar big int64 = 999999 var small int8 = int8(big) // 溢出,结果不可预期 fmt.Printf("原值: %d, 转换后: %d\n", big, small)安全转换
gofunc safeIntToInt8(val int) (int8, error) { if val < -128 || val > 127 { return 0, fmt.Errorf("值 %d 超出int8范围", val) } return int8(val), nil }
指针
指针基础
指针是存储另一个变量内存地址的变量。
go
var num int = 42
var ptr *int = &num // ptr是指向num的指针
fmt.Printf("num的值: %d\n", num)
fmt.Printf("num的地址: %p\n", &num)
fmt.Printf("ptr的值(地址): %p\n", ptr)
fmt.Printf("ptr指向的值: %d\n", *ptr)指针操作
1. 声明和初始化
go
// 声明指针
var ptr1 *int // 零值为nil
var ptr2 *string // 零值为nil
// 初始化指针
var num int = 42
ptr1 = &num // 指向已存在的变量
// 使用new函数创建
ptr3 := new(int) // 创建int类型的零值,返回指针
*ptr3 = 100 // 给指针指向的值赋值
fmt.Printf("ptr1指向的值: %d\n", *ptr1)
fmt.Printf("ptr3指向的值: %d\n", *ptr3)2. 指针的零值
go
var ptr *int
fmt.Printf("指针的零值: %v\n", ptr) // <nil>
// 检查指针是否为nil
if ptr == nil {
fmt.Println("指针为nil")
}
// 使用nil指针会导致panic
// fmt.Println(*ptr) // 运行时panic3. 指针运算
Go语言不支持指针算术运算(如C语言中的ptr++):
go
var arr [3]int = [3]int{1, 2, 3}
var ptr *int = &arr[0]
fmt.Printf("第一个元素: %d\n", *ptr)
// 错误:Go不支持指针算术
// ptr++ // 编译错误
// ptr = ptr + 1 // 编译错误
// 正确方式:重新获取地址
ptr = &arr[1]
fmt.Printf("第二个元素: %d\n", *ptr)指针的实际应用
1. 函数参数传递
go
// 值传递 - 不会修改原变量
func changeValue(x int) {
x = 100
}
// 指针传递 - 会修改原变量
func changePointer(x *int) {
*x = 100
}
func main() {
num := 42
fmt.Printf("原始值: %d\n", num)
changeValue(num)
fmt.Printf("值传递后: %d\n", num) // 42 (未改变)
changePointer(&num)
fmt.Printf("指针传递后: %d\n", num) // 100 (已改变)
}2. 结构体指针
go
type Person struct {
Name string
Age int
}
func main() {
// 创建结构体
p1 := Person{Name: "张三", Age: 25}
// 结构体指针
p2 := &Person{Name: "李四", Age: 30}
// 使用new创建
p3 := new(Person)
p3.Name = "王五" // 自动解引用
p3.Age = 35
fmt.Printf("p1: %+v\n", p1)
fmt.Printf("p2: %+v\n", *p2)
fmt.Printf("p3: %+v\n", *p3)
}3. 指针数组和数组指针
go
func main() {
// 指针数组 - 数组的元素是指针
var ptrArray [3]*int
a, b, c := 1, 2, 3
ptrArray[0] = &a
ptrArray[1] = &b
ptrArray[2] = &c
for i, ptr := range ptrArray {
fmt.Printf("ptrArray[%d] = %d\n", i, *ptr)
}
// 数组指针 - 指向数组的指针
arr := [3]int{10, 20, 30}
var arrayPtr *[3]int = &arr
fmt.Printf("数组: %v\n", arr)
fmt.Printf("通过指针访问: %v\n", *arrayPtr)
fmt.Printf("通过指针访问元素: %d\n", (*arrayPtr)[1])
}指针最佳实践
检查nil指针
gofunc safeAccess(ptr *int) { if ptr != nil { fmt.Printf("值: %d\n", *ptr) } else { fmt.Println("指针为nil") } }避免野指针
gofunc createPointer() *int { // 错误:返回局部变量的地址 // var x int = 42 // return &x // 正确:使用new或在堆上分配 x := new(int) *x = 42 return x }合理使用指针
go// 大结构体使用指针传递,避免复制开销 type LargeStruct struct { data [1000]int } func processLargeStruct(ls *LargeStruct) { // 处理大结构体 }
通过掌握变量和常量的使用方法,您就可以在Go程序中有效地管理数据了。下一步可以学习Go的控制结构和函数。