vault backup: 2026-06-07 12:14:39
This commit is contained in:
+100
-118
@@ -1,147 +1,129 @@
|
||||
---
|
||||
tags:
|
||||
- Go
|
||||
- golang
|
||||
- go基础语法
|
||||
- 常量
|
||||
tags: [go, golang, go基础语法, 常量]
|
||||
create time: 2026-06-07 15:00
|
||||
---
|
||||
|
||||
# Go语言常量
|
||||
# Go 语言常量
|
||||
|
||||
## 常量的定义
|
||||
## 概述
|
||||
|
||||
Go语言常量的定义跟其他语言类似,是一个简单值的标识符,在程序运行时,不可以被修改。常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。常量的定义方式:
|
||||
常量是编译期确定的不可变值。本文介绍常量的声明方式、`iota` 枚举技巧,以及常量与变量的关键区别。
|
||||
|
||||
## 正文
|
||||
|
||||
### 常量 vs 变量:本质区别是什么?
|
||||
|
||||
**常量在编译期就确定了值,之后不可修改。** 这意味着编译器可以做更多优化,比如内联替换、死代码消除等。
|
||||
|
||||
```go
|
||||
const identifier [type] = value
|
||||
const MaxSize = 1024 // 编译期确定,不可更改
|
||||
// MaxSize = 2048 // ❌ 编译错误:cannot assign to MaxSize
|
||||
|
||||
var BufferSize = 1024 // 运行时可变
|
||||
BufferSize = 2048 // ✅ 合法
|
||||
```
|
||||
其中类型说明符[type]可以省略,因为编译器可以根据变量的值来推断其类型。
|
||||
|
||||
> [!note] 📝 常量类型限制
|
||||
> 只能是布尔型、数字型(整数/浮点/复数)、字符串。不能是 slice、map、struct 等复杂类型。
|
||||
|
||||
### 声明方式
|
||||
|
||||
```go
|
||||
const b string = "abc" // 显式常量定义
|
||||
const b = "abc" // 隐式常量定义
|
||||
```
|
||||
多个相同类型的声明可以简写为:`const c_name1, c_name2 = value1, value2`
|
||||
```go
|
||||
const a, b = "abc", "def"
|
||||
const name type = value // 显式类型
|
||||
const name = value // 类型推断(更常用)
|
||||
|
||||
// 多常量一行声明
|
||||
const a, b = "hello", "world"
|
||||
```
|
||||
|
||||
## 常量用作枚举
|
||||
### `iota`:Go 的枚举神器
|
||||
|
||||
Go语言不像C++Java一样有专门的枚举类型,Go语言的枚举一般用常量表示。
|
||||
```go
|
||||
const (
|
||||
Unknown = 0
|
||||
Success = 1
|
||||
Fail = 2
|
||||
)
|
||||
```
|
||||
数字 0、1 和 2 分别代表未知成功和失败
|
||||
常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,必须是编译期可以确定的值,否则编译不过
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
a = "abc"
|
||||
b = len(a)
|
||||
c = unsafe.Sizeof(a)
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("a = %s, b = %d, c = %d\n", a, b, c)
|
||||
}
|
||||
```
|
||||
以上实例运行结果为:
|
||||
```
|
||||
abc 3 16
|
||||
```
|
||||
**思考题:为什么字符串"abc"的 `unsafe.Sizeof()` 是16呢?**
|
||||
实际上字符串类型的`unsafe.Sizeof()`一直是16,这又是为什么呢?因为字符串类型对应一个结构体,该结构体有两个域,第一个域是指向该字符串的指针,第二个域是字符串的长度,每个域占8个字节,但是并不包含指针指向的字符串的内容,这也就是为什么`sizeof`始终返回的是16。
|
||||
字符串可以理解成此结构体
|
||||
```c
|
||||
typedef struct {
|
||||
char* buffer; // 8字节
|
||||
size_tlen; // 8字节
|
||||
} string;
|
||||
```
|
||||
|
||||
## iota
|
||||
`iota`是一个特殊常量,可以认为是一个可以被编译器修改的常量。`iota`在`const`关键字出现时将被重置为`0`(const 内部的第一行之前),`const`中每新增一行常量声明将使`iota`计数一次(`iota`可理解为`const`语句块中的行索引)
|
||||
看例子:
|
||||
`iota` 是 Go 最优雅的枚举机制之一——它是 `const` 块内的**行索引**,从 0 开始,每行自动 +1。
|
||||
|
||||
```go
|
||||
const (
|
||||
a = iota
|
||||
b = iota
|
||||
c = iota
|
||||
Sunday = iota // 0
|
||||
Monday // 1
|
||||
Tuesday // 2
|
||||
Wednesday // 3
|
||||
Thursday // 4
|
||||
Friday // 5
|
||||
Saturday // 6
|
||||
)
|
||||
```
|
||||
第一个`iota`等于`0`,每当`iota`在新的一行被使用时,它的值都会自动加1;所以`a=0, b=1, c=2` 可以简写为如下形式:
|
||||
|
||||
> [!tip] 💡 省略写法
|
||||
> 同一 `const` 块内,如果后续行的值和上一行一样(都是 `iota`),可以省略右侧表达式:
|
||||
|
||||
```go
|
||||
const (
|
||||
a = iota
|
||||
b
|
||||
c
|
||||
Sunday = iota // 0
|
||||
Monday // 1 ← 自动继承 iota
|
||||
Tuesday // 2
|
||||
)
|
||||
```
|
||||
具体使用:
|
||||
|
||||
#### `iota` 的高级用法
|
||||
|
||||
当某行出现了**非 iota 的独立值**后,`iota` 继续计数,但其他常量需要重新指定表达式:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
const (
|
||||
a = iota // 0
|
||||
b // 1
|
||||
c // 2
|
||||
d = "ha" // 独立值,iota = 3
|
||||
e // "ha",iota = 4
|
||||
f = 100 // iota = 5
|
||||
g // 100,iota = 6
|
||||
h = iota // 7,恢复计数
|
||||
i // 8
|
||||
)
|
||||
fmt.Println(a, b, c, d, e, f, g, h, i)
|
||||
}
|
||||
```
|
||||
以上实例运行结果为:
|
||||
```
|
||||
0 1 2 ha ha 100 100 7 8
|
||||
```
|
||||
关于iota的一个有趣的例子:
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
const (
|
||||
i=1<<iota // iota = 0, i = 1 << 0 = 1
|
||||
j=3<<iota // iota = 1, j = 3 << 1 = 6
|
||||
k // iota = 2, k = 3 << 2 = 12
|
||||
l // iota = 3, l = 3 << 3 = 24
|
||||
a = iota // 0
|
||||
b // 1
|
||||
c // 2
|
||||
d = "ha" // "ha" — 独立值,iota 变成 3
|
||||
e // "ha" — 继承上一行的值,iota 变成 4
|
||||
f = 100 // 100 — 独立值,iota 变成 5
|
||||
g // 100 — 继承,iota 变成 6
|
||||
h = iota // 6 — 恢复计数
|
||||
i // 7
|
||||
)
|
||||
```
|
||||
|
||||
func main() {
|
||||
fmt.Println("i=", i)
|
||||
fmt.Println("j=", j)
|
||||
fmt.Println("k=", k)
|
||||
fmt.Println("l=", l)
|
||||
}
|
||||
#### 位运算枚举
|
||||
|
||||
结合位移操作,`iota` 可以优雅地生成幂次值:
|
||||
|
||||
```go
|
||||
const (
|
||||
_ = iota // 0,用 _ 丢弃
|
||||
KB = 1 << (10 * iota) // 1 << 0 = 1(通常不用)
|
||||
MB // 1 << 10 = 1024
|
||||
GB // 1 << 20 = 1048576
|
||||
TB // 1 << 30 = 1073741824
|
||||
)
|
||||
```
|
||||
以上实例运行结果为:
|
||||
|
||||
> [!question] ❓ 思考
|
||||
> 上面这段代码中,`KB`、`MB`、`GB` 的值分别是多少?为什么 `_` 那行很重要?
|
||||
|
||||
### 常量可以用在编译期函数中
|
||||
|
||||
`len()`、`cap()`、`unsafe.Sizeof()` 等在常量上下文中是**编译期求值**的:
|
||||
|
||||
```go
|
||||
const s = "abc"
|
||||
const n = len(s) // n == 3,编译期确定
|
||||
const sz = unsafe.Sizeof(s) // sz == 16(string struct 大小)
|
||||
```
|
||||
i= 1
|
||||
j= 6
|
||||
k= 12
|
||||
l= 24
|
||||
|
||||
> [!info] ℹ️ 为什么 `sizeof("abc")` 是 16?
|
||||
> Go 的 `string` 底层是一个结构体:`{ptr *byte, len int}`,两个字段各占 8 字节(64 位平台),所以 `unsafe.Sizeof(string)` 恒为 16。它不包含字符串的实际内容,只包含指向内容的指针和长度。
|
||||
|
||||
### 常量分组示例(枚举最佳实践)
|
||||
|
||||
```go
|
||||
type StatusCode int
|
||||
|
||||
const (
|
||||
OK StatusCode = 200
|
||||
NotFound StatusCode = 404
|
||||
ServerError StatusCode = 500
|
||||
)
|
||||
```
|
||||
<<表示左移,学过计算机我们都知道,左移以为其实就是乘以2,左移n位其实就是乘以2的n次方。所以:
|
||||
- i=1:左移 0 位,不变仍为 1,即1*2^0=1*1=1
|
||||
- j=3:左移 1 位,变为二进制 110,即 3*2^1=3*2=6
|
||||
- k=3:左移 2 位,变为二进制 1100,即3*2^ 2=3*4=12
|
||||
- l=3:左移 3 位,变为二进制 11000,即 3*2^ 3=3*8=24
|
||||
|
||||
给枚举值绑定自定义类型(如 `StatusCode`)可以提供类型安全——不同枚举类型之间不能直接比较。
|
||||
|
||||
> [!warning] ⚠️ 常见陷阱
|
||||
> 不带类型的裸 `const` 会隐式转换为 `int`。如果需要类型安全,务必显式声明类型或先定义枚举类型。
|
||||
|
||||
Reference in New Issue
Block a user