Skip to content

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 = true

2. 类型推导

省略类型,让编译器自动推导:

go
var name = "Go语言"      // 推导为 string
var age = 25            // 推导为 int
var height = 1.75       // 推导为 float64
var isActive = true     // 推导为 bool

3. 短变量声明

使用 := 操作符(仅在函数内部使用):

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 = 3

2. 批量常量

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. 全局作用域 - 包级别
  2. 函数作用域 - 函数内部
  3. 块作用域 - 代码块内部
  4. 文件作用域 - 导入的包名

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)
    }
}

类型转换注意事项

  1. 精度丢失

    go
    var f float64 = 3.99
    var i int = int(f)  // i = 3 (小数部分丢失)
    fmt.Printf("原值: %f, 转换后: %d\n", f, i)
  2. 溢出处理

    go
    var big int64 = 999999
    var small int8 = int8(big)  // 溢出,结果不可预期
    fmt.Printf("原值: %d, 转换后: %d\n", big, small)
  3. 安全转换

    go
    func 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)  // 运行时panic

3. 指针运算

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])
}

指针最佳实践

  1. 检查nil指针

    go
    func safeAccess(ptr *int) {
        if ptr != nil {
            fmt.Printf("值: %d\n", *ptr)
        } else {
            fmt.Println("指针为nil")
        }
    }
  2. 避免野指针

    go
    func createPointer() *int {
        // 错误:返回局部变量的地址
        // var x int = 42
        // return &x
        
        // 正确:使用new或在堆上分配
        x := new(int)
        *x = 42
        return x
    }
  3. 合理使用指针

    go
    // 大结构体使用指针传递,避免复制开销
    type LargeStruct struct {
        data [1000]int
    }
    
    func processLargeStruct(ls *LargeStruct) {
        // 处理大结构体
    }

通过掌握变量和常量的使用方法,您就可以在Go程序中有效地管理数据了。下一步可以学习Go的控制结构和函数。

基于 MIT 许可发布