2.1 变量、常量与基本类型


2.1 变量、常量与基本类型

本节摘要:Go 用 var 显式声明、短声明符冒号等号、const 常量三种方式引入名字;所有类型都有确定的零值,未初始化不会得到随机垃圾;整型按位宽家族化命名,字符串是不可变字节序列。本节讲清每种声明方式的适用边界、iota 枚举的玩法,以及 string 与 rune 的字节陷阱。

阅读收获

阅读完本节,你应当能够:

  1. 为局部变量、包级变量、常量选择正确的声明方式;
  2. 说出任意类型的零值;
  3. 解释 int 与 int64 的取舍、浮点与整型的转换规则;
  4. 用 iota 写出优雅的枚举与位标志;
  5. 处理字符串的按字节与按字符两种遍历差异。

一、三种引入名字的姿势

先看一个把三种声明一网打尽的例子:

package main import "fmt" const Pi = 3.14 var globalCount = 100 // 包级变量,用var func main() { var a int = 10 // 显式类型加显式初值 var b = 20 // 类型由右侧推导,b是int c := 30 // 短声明,只能在函数内用 fmt.Println(a, b, c, Pi, globalCount) }

三种方式各有领地。var 是正式声明,包级只能用它;类型可写可不写,不写就推导。短声明(冒号等号)是局部变量的日常写法,冒号左侧必须至少有一个新名字——a, err := f() 里 err 已存在时是赋值,a 是新声明,这个特性在"循环里反复接收错误"时极其顺手。const 在编译期定死,且必须是编译器能算出来的表达式;它没有固定内存地址(除非取地址强制触发分配),本质更接近文本替换加类型检查。

⚠️ 常见坑:短声明写在外层 if 之外却在代码块里又想新建同名变量时,内层的短声明会遮蔽外层——变量遮蔽是 Go 代码评审里最高频的缺陷来源,值得每次怀疑。

二、零值:没有未定义行为

Go 不允许"未初始化的脏内存"。任何类型声明后不赋初值,就得到一个确定的零值:

类型族 零值 记忆点
数值(int、float 等) 0 / 0.0 全零
布尔 false 全零的自然延伸
字符串 空串,长度为0 不是 nil
指针、切片、map、channel、函数、接口 nil 引用类统一 nil

这个设计直接消灭了一整类"C 里读未初始化变量"的事故。工程上的推论是:很多结构体只需要声明,不需要构造函数——零值即可用。标准库里 bytes.Buffer、sync.Mutex 都是"零值有用"的典范,后面章节会反复遇到。

三、基本类型家族

整型按位宽成套:int8/16/32/64 加平台相关的 int 与 uint(64 位平台上 int 是 64 位),另有 byte(uint8 的别名)与 rune(int32 的别名,专表 Unicode 码点)。常规业务代码无脑用 int 即可;与协议、文件格式打交道时才精确到位宽——这时溢出行为必须心里有数:int8 最大 127,再加 1 会回绕成 -128。

浮点只有 float32 与 float64,遵循 IEEE 754。钱的计算别用浮点,用整数分或者专门的定点方案,这是所有语言的通病不是 Go 的锅。

布尔就是 bool,且 Go 强制 if 的条件必须是布尔表达式——if 1 {} 直接编译错,杜绝了"把赋值当判断"的经典笔误。

字符串是不可变的字节序列。注意两点:下标访问拿到的是字节(byte)不是字符;len 返回字节数不是字符数。一个汉字 UTF-8 编码占 3 字节,所以对中文串直接下标切分,切出来的是乱码字节。按字符遍历要用 for range,它按 UTF-8 解码产出 rune:

s := "Go语言" fmt.Println(len(s)) // 8:两个字母加两个汉字各3字节 for i, r := range s { fmt.Printf("%d:%c ", i, r) // 下标是字节偏移,值是解码后的rune }

四、iota:常量生成器

const 块里每出现一行,iota 从 0 递增一次。最朴素的枚举:

type Weekday int const ( Sunday Weekday = iota // 0 Monday // 1,重复上一行表达式 Tuesday // 2 )

进阶玩法是位标志,一份表达式贯穿整个块:

const ( Read = 1 << iota // 1 Write // 2 Execute // 4 ) perm := Read | Write // 3:按位或组合权限

跳过某个值用下划线占位(比如让枚举从 1 开始时先放一个 _)。iota 的价值在于"单一事实源":改一处,全序列自动重排,不会出现手写枚举忘了同步的老 bug。

五、类型转换:永远显式

Go 不做任何隐式数值转换,int 与 int64 混算必须手动转:int64(a) + b。显式转换啰嗦,但每一处宽度变化都看得见——排障时这是恩人。转换语法就是"目标类型加括号":float64(x)string(65)(得到字母 A,注意这是码点转串,不是数字转字符串;数字转字符串要用 strconv 包的格式化函数)。

💡 关键直觉:把 Go 的类型系统当成海关——任何东西跨境(跨类型)都要填表申报,麻烦,但走私(隐式截断、符号扩展错误)绝迹。

六、类型转换全景与常见问答

再答几个新手高频疑问。:= 和 var 到底用哪个? 包级只能 var;局部默认 :=,需要零值语义(暂时不想给初值)时用 var 声明。为什么编译器报"声明了但未使用"? Go 把未使用变量当编译错误而不是警告——死变量是 bug 的温床,直接拒编译,下划线是唯一的"我知道我没用它"。string 能修改吗? 不能,任何"修改"都产生新串;需要可变字节缓冲就用字节切片,或标准库的字符串构造器。iota 从几开始? 零。要跳过值就插下划线占位。不同整型能直接比较吗? 类型不同就不能比较,先转换;比较本身没问题,溢出回绕才是陷阱来源。

核心回顾

  • 三种声明:var 用于包级与正式声明,短声明用于局部,const 编译期定值。
  • 短声明重声明:冒号左侧至少一个新名字,其余转为赋值。
  • 零值语义:数值零、字符串空、引用类 nil,零值可用是好 API 的设计目标。
  • 整型家族:业务用 int,协议用定位宽;byte 与 rune 分别是 uint8、int32 的别名。
  • 字符串是字节序列:len 是字节数,按字符遍历用 for range。
  • iota 枚举:行递增加表达式重复,配合位运算可做权限标志。
  • 显式转换:无隐式数值转换,string 与数值互转要走 strconv。

下一节把变量放进流程里:for、if 与 switch。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U