变量、数据类型与常量:Go 的值从哪来、能装什么
变量、数据类型与常量:Go 的值从哪来、能装什么
这一章讲 Go 里「值」的三件事: 变量怎么声明、能装什么类型; 常量怎么声明、和变量有什么本质不同;打印时怎么控制输出格式。 读完你会理解两件以后会反复救你的事:Go 不给「没初始化的变量」留随机值, 以及不同类型之间不能偷偷互换,要转换就摆到明面上写。
1. 变量:先给「声明」一个准确定义
书里给的定义值得照抄口径:变量是给「存在内存某个位置、属于某个特定类型的值」起的名字。 声明一个变量,做的事有两件:在内存里占一块地方,告诉编译器这块地方的名字和类型1。
两条硬规矩从定义里直接长出来:
- Go 是静态类型:变量的类型定下来就不能换。今天装整数的变量,明天不能改塞文本。 能变的是「值」,不是「类型」1;
- 每个变量都必须显式声明,没有「先用后声明」这回事。
主线例子:一个变量的一生
这一章的主线例子跟着原书走——声明一个变量,不给值,看编译器塞了什么; 再赋值,看它怎么变2:
var variable1 int // 声明:只要了类型,没给值
fmt.Println(variable1) // → 0 ← 编译器自动放的「零值」
variable1 = 123 // 赋值:类型不变,值变了
fmt.Println(variable1) // → 123
var variable2 string
fmt.Println(variable2) // → "" ← 字符串的零值是空串
variable2 = "hello world"
fmt.Println(variable2) // → hello world
variable4 := false // 声明+赋值一起,编译器按值认出是布尔类型
fmt.Println(variable4) // → false图说:每个变量出生自带一个值——不是你给的,是编译器按类型给的默认礼物,叫零值。 数字是 0,字符串是空串,布尔是 false1。
零值(zero value)这个词记一下:Go 里声明时不给初值的变量,自动得到所属类型的默认值。 好处很实际:别的语言里未初始化的变量可能装着内存里残留的随机垃圾,Go 永远不会—— 这是「一个都没少声明」带来的确定性(结果唯一、可预料)。
六种声明写法,一张表收齐
原书一口气列了六种,前三种最常用3:
| 写法 | 例子 | 什么时候用 |
|---|---|---|
| 只声明类型 | var name string | 想先占坑、稍后赋值 |
| 声明+值,类型靠猜 | var name = "Ansh" | 类型一眼能从值看出 |
| 短声明 | name := "Ansh" | 函数内部最常用,最省字 |
| 一次声明多个(同类型) | var a, b int | 一批同类型的变量 |
| 一次声明多个(短声明) | a, b := 1, "two" | 函数内部一次生几个 |
| 成组声明 | var ( a int; b string ) | 一堆变量想摆在一起好看 |
注意:短声明 := 只能在函数内部用;函数外面给变量、常量起头,只能用 var/const。
这个限制原书没明说,但所有顶层例子用的都是 var(补充,不在书里,来自通用知识)。
2. 「声明了就必须用」和空白标识符的第一次实战
Go 编译器有一个出了名的洁癖:声明了却一次没用的变量,直接编译报错,程序不许生成。 书里给的例子4:
variable13, variable14 := 123, 456
fmt.Println(variable13) // 编译错:variable14 declared and not used这里 _ 就登场了(拆解 02 里它只有定义,这里是它存在的理由之一):
两个值只想要一个,就把不要的那个接住再扔掉4:
variable15, _ := 123, 456
fmt.Println(variable15) // → 123,456 被空白标识符收走,编译器满意,你也满意3. 数据类型总图:两大类
数据类型是数据的一个属性,决定这块数据能装什么、占多大地方。 类型还告诉编译器存储空间的大小5。
Go 的类型分两大类,先建立这张地图,再逐个进:
Go 数据类型
├── 基本类型(primitive):单个值,不可再拆
│ ├── 布尔 bool:true / false
│ ├── 数值:整数一族(int8…uint64)、浮点(float32/64)、复数(complex64/128)
│ │ byte(=uint8)、rune(=int32,存 Unicode 码点)
│ └── 字符串 string:只读的字节序列,UTF-8 编码
└── 复合类型(non-primitive):装一批值的容器
├── 指针 pointer(第 10 章专讲)
├── 数组 array、切片 slice(第 8 章专讲)
├── 结构体 struct(第 11 章)、map(第 14 章)、channel(第 15、16 章)
└── 接口 interface(第 13 章)
图说:左半边本章讲透,右半边这一章只给名片,后面各章领走。3.1 布尔与数值
布尔类型只有两个值:true 和 false,常用于条件判断(第 5 章的 if 就吃这个)6。
数值里要单独说的是整数为什么有一整个家族。Go 把整数按「带不带符号」和「占多少位」切成了十种:
| 类型 | 范围 | 一句话记忆 |
|---|---|---|
| int8 | -128 ~ 127 | 带符号 8 位 |
| int16 | -32768 ~ 32767 | 带符号 16 位 |
| int32 | 约 ±21 亿 | 带符号 32 位 |
| int64 | 约 ±922 亿亿 | 带符号 64 位 |
| uint8(byte) | 0 ~ 255 | 不带符号 8 位 |
| uint16 | 0 ~ 65535 | 不带符号 16 位 |
| uint32 | 0 ~ 约 42 亿 | 不带符号 32 位 |
| uint64 | 0 ~ 约 1844 亿亿 | 不带符号 64 位 |
(范围数字照原书7;「位」是计算机记数的最小单位,8 位就是一个字节。)
为什么分这么细? 因为占的内存不同、能装的数不同——uint8 只够装 0 到 255,
拿它存身份证号一秒钟就溢出。日常写程序,绝大多数情况写 int 就行
(它跟着机器走:32 位机器上是 32 位,64 位机器上是 64 位)7。
两个别名记住:byte 就是 uint8(处理原始字节时用),rune 就是 int32 的别名, 专门拿来存一个 Unicode 字符的编号(第 9 章字符串会重逢)7。
浮点只有两种:float32 和 float64(带小数点的数);复数 complex64/complex128 日常极少碰, 知道有就行8。error 类型在 Go 里也是个类型——装错误的,第 17 章主角8。
3.2 静态类型的第一个账单:不同整数之间也不许直接塞
这是新手在 Go 里撞的第一堵墙,书里特意给了完整例子9:
var int32val int32 = 100
var int16val int16 = int32val // 编译错!cannot use int32val as type int16
var int16val int16 = int16(int32val) // 对:写成显式转换,int16(...) 念作「转成 int16」
var int64val int64 = int64(int32val) // 对:100
int32val = -100
uint8val = uint8(int32val)
fmt.Println(uint8val) // → 156 !!!最后两行是全书第一个「坑」:把 -100 硬塞进 uint8(只能装 0~255 的类型), 结果是 156,不是报错。原因:转换按二进制补码(2's complement,计算机存负数的记法) 直接重抄一遍字节——-100 的字节摆进 uint8 里读出来正好是 1569。
判断(我们的,不是书里的): 「溢出不报错、悄悄给错值」是 Go 类型系统里最 容易咬人的一处,书里的例子只演示了现象,没给防范建议。实际写码时: 只有百分之百确定范围够,才做数值类型的显式转换;涉及用户输入、文件数据的数值, 先按大范围类型(int64)接住再收窄。 如果错,会错在: 如果编译器或运行时工具链(vet、静态检查)能拦住这类转换, 那「最容易咬人」就要降级——但它们拦不住合法范围内的转换,这个判断大体仍成立。
3.3 字符串:只读的字节串,不是字符数组
书里给了一个和直觉相斥的定义:Go 的字符串不是一串字符,而是一串「只读的字节」, 按 UTF-8 编码10。
这句话里有两个新概念,分开说:
UTF-8 是一种编码方案:给世界上每个字符(包括中文、emoji)编一个号, 再把编号翻译成 1 到 4 个字节存进内存。 英文字母占 1 个字节,常见汉字占 3 个字节。
「只读」= 不可变(immutable):字符串一旦造出来,里面的字节不许改。
没有 s[0] = 'H' 这种写法。想「改」,实际上是造一个新字符串10。
还有一个坑书里点了一句:手工用 \u、\x 这类转义往字符串里塞字符,
可能拼出不符合 UTF-8 规则的序列,导致后续处理出错或结果出乎意料10。
字符串这一章只给名片,第 9 章专讲。
3.4 复合类型,本章只给名片
指针、数组、切片、结构体、map、channel——本章的定义照原书接住,详细章节标注在上一张图里。 这里只点两个本章就露出、且影响理解的差别:
- 数组是「值」:把一个数组赋给另一个变量,整个数组被复制一份; 数组传给函数,函数拿到的也是复制品11;
- 切片和 map 是「引用」:赋值或传参传的是同一份数据的门牌号, 两边改的是同一样东西12。这个差别是第 8 章的重头戏。
4. 常量:值在编译期就必须定死
常量是程序全程不变的值,用 const 声明;它带的值叫字面量(literal)。13
常量最重要的规矩藏在书里这一句:Go 要求常量的值在编译期就确定。 想把函数调用的结果赋给常量?不行——函数要跑起来才有结果,编译器在编译期拿不到它13。
带类型与不带类型:能不能混,全看有没有写类型
这是本章最绕但最常考的一处。常量有两种写法14:
const A = 123 // 不带类型(untyped):A 只是个「整数 123」
const B int = 123 // 带类型(typed):B 是「int 类型的 123」区别不在值,在**「能不能自由地赋给别的类型」**:
const untypedConstString = "Golang is a great language" // 不带类型
const typedConstString string = "Golang is a great language" // 带类型
type myString string // 基于 string 造一个「新类型」(结构一样,身份不同)
const customName myString = untypedConstString // 对:不带类型的常量能适配新类型
const customName myString = typedConstString // 错:conversion not allowed图说:不带类型的常量像一块没贴标签的原材料,塞进什么容器随容器; 带类型的常量已经定型,身份不符就拒收14。布尔常量同理15。
判断(我们的,不是书里的): 「不带类型常量很灵活」这条是 Go 的隐藏福利, 新手最容易在「自定义类型 + 常量」组合上踩错。经验法则: 不确定要不要写类型时,不写。 写了类型,你就是在主动放弃灵活性。 如果错,会错在: 有些团队规范反而要求显式类型以防隐式转换蔓延——那是代码风格之争, 不改变机制本身。
数值常量和 rune 的第一次照面
数值常量可以是整数、浮点、复数,也可以是 rune。 书里演示了 rune 常量的一个反直觉事实16:
const untypedConstrune = 'G'
fmt.Println(untypedConstrune) // → 71,不是字母 G!单引号括起来的单个字符是 rune 常量,它装的是这个字符的 Unicode 编号
(大写字母 G 的编号恰好是 71)。想看到「G」本人,要用 %c 格式符打印(下一节)。
这也解释了为什么 rune 是 int32:Unicode 编号最大约 111 万,32 位够装16。
iota:常量块里的自动计数器
iota 是 Go 预置的一个计数器:每遇到一个 const 块,它归零;
块内每过一行(空行、注释行、_ 占位的行都算数),它加一。17
const (
C0 = iota // iota 此刻是 0;这一行结束后它加一
_ // 这一行 iota 是 1,被空白标识符收走(占位,跳过编号 1)
C1 = iota // iota 此刻是 2
)
fmt.Println(C0, C1) // → 0 2典型用途:给一组枚举常量自动编号(星期、状态码),加新成员时不用手改后面的数字。
注意两个细节:每个新的 const 块, iota 重新归零(注释行不影响计数,
但 _ 占位行消耗一个编号)17。
5. 格式符:打印的控制面板
原书后半章是一张按类型分的格式符表18。格式符都以 % 开头,用在 Printf 这类函数里。
把全表压缩成最常用的八张脸:
| 格式符 | 打印什么 | 例子(Printf 输出) |
|---|---|---|
| %v | 值的默认格式(不知道用啥就用它) | true、123 |
| %#v | 值的 Go 语法写法 | "abc" 带引号、[]int{1, 2} |
| %T | 值的类型 | int、string、bool |
| %t | 布尔 | true / false |
| %d | 十进制整数 | 123 |
| %b / %o / %x | 二进制 / 八进制 / 十六进制 | 1111011 / 173 / 7b |
| %c | 编号对应的字符 | 71 → G |
| %q | 带引号的字符串 / 字符 | "hello" |
| %s | 字符串原样 | hello |
| %f / %e / %g | 浮点(定点 / 科学 / 自动选) | 123.456000 / 1.234560e+02 |
| %% | 一个 % 本身 | % |
| %p | 指针的内存地址 | 0x1183c0c0 |
表里浮点默认精度(小数能精确到几位)、十六进制大小写等细节,原书表格更全18,用到时回查原文地图。
6. 作者的判断与证据
这一章几乎全是语言规则的陈述(规则本身可由编译器验证)。带判断色彩的是两处:
| 书里的说法 | 性质 |
|---|---|
| 「静态类型能在编译期抓住大量小 bug」 | 作者观点,业界共识,无定量证据 |
| 「手工转义可能破坏 UTF-8」 | 事实陈述,属实 |
书里有一处表述需要打补丁:「类型能转换无类型数据,但不能转换带类型数据的类型」14, 表述拗口;准确说法是:不带类型常量可隐式适配任何兼容类型,带类型值必须显式转换。
7. 边界与局限
- 格式符一节占全书很大篇幅但价值密度低——这是手册型内容,知道
%v %T %d %s %q五个就覆盖九成日常,其余回查; - 复合类型(指针、切片、map、channel)在本章只有一句话定义,别在这里死磕, 各自的专章会把「值 vs 引用」的差别讲透;
- 没有讲类型的零值全表(比如切片、map 的零值是 nil),本章只给了数字/字符串/布尔三种;
- iota 的高级用法(表达式、位运算枚举)本书没讲,到此为止够用。
8. 可带走的
- 声明变量 = 占内存 + 告诉编译器名字和类型;类型终身不变,值可以换;
- 零值:不给初值时数字是 0、字符串是
""、布尔是 false——永远没有随机垃圾; - 六种声明写法,函数体内最常用
:=,函数体外只能用var/const; - 声明了就必须用,没用的用
_显式丢弃; - 整数有十种(int8~uint64 + 别名 byte/rune),平时写 int;
- 不同数值类型之间不能隐式互换,
int16(x)是显式转换;溢出不报错,悄悄给错值(-100 → uint8 变 156); - 字符串是只读字节串,UTF-8 编码;单引号字符是编号(71),不是字符本人;
- 常量的值必须编译期确定;不带类型常量灵活、带类型常量定型;
- iota 在每个 const 块里从 0 开始逐行加一,配合
_可以跳过编号; - 打印五件套:
%v(默认)、%T(类型)、%d(整数)、%s(字符串)、%q(带引号)。
9. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 变量的定义与零值 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:46(搜「zero for numeric」) |
| 六种声明写法 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:49(搜「Go facilitates multiple ways」) |
| 空白标识符丢弃值 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:82(搜「blank identifier」) · text/11-fm-learning-go-programming.txt:169(搜「variable15」) |
| declared and not used 报错 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:166(搜「declared and not used」) |
| 数据类型定义与分类 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:178(搜「categorization」) |
| 布尔类型 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:191(搜「either true or false」) |
| 整数家族与范围 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:209(搜「uint8」) · text/11-fm-learning-go-programming.txt:242(搜「Unicode code point」) |
| 显式转换与 -100→156 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:263(搜「int16(int32val)」) · text/11-fm-learning-go-programming.txt:284(搜「2's complement」) |
| 浮点与复数 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:290(搜「float32」) |
| 字符串=只读字节串、UTF-8、不可变 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:305(搜「read-only byte slice」) · text/11-fm-learning-go-programming.txt:308(搜「escape sequences」) |
| 数组是值(复制) | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:368(搜「behaves like a value」) |
| 切片是引用、make 三要素 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:482(搜「reference type」) |
| map 与 channel 名片 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:551(搜「fast key lookup」) · text/11-fm-learning-go-programming.txt:593(搜「race condition」) |
| 常量、编译期确定 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:626(搜「remain fixed」) |
| 带类型/不带类型常量 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:686(搜「Mixing data types」) · text/11-fm-learning-go-programming.txt:716(搜「conversion not allowed」) |
| 'G' 打印出 71 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:683(搜「unicode value」) |
| iota 逐行加一、块间归零 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:737(搜「increments with const declaration」) |
| 格式符总表 | Variables, Data Types and Constants | text/11-fm-learning-go-programming.txt:782(搜「percentage (%) sign」) · text/11-fm-learning-go-programming.txt:804(搜「default format」) |
Footnotes
-
出处:「Variables, Data Types and Constants」第 46 段(
text/11-fm-learning-go-programming.txt:46,搜「zero for numeric」)。 ↩ ↩2 ↩3 -
出处:「Variables, Data Types and Constants」第 88–118 段(
text/11-fm-learning-go-programming.txt:91,搜「// 0」;text/11-fm-learning-go-programming.txt:106,搜「123 hello world」)。例子中的变量名与输出照原书。 ↩ -
出处:「Variables, Data Types and Constants」第 52–79 段(
text/11-fm-learning-go-programming.txt:49,搜「Go facilitates multiple ways」)。 ↩ -
出处:「Variables, Data Types and Constants」第 163–172 段(
text/11-fm-learning-go-programming.txt:166,搜「declared and not used」;text/11-fm-learning-go-programming.txt:169,搜「variable15」)。 ↩ ↩2 -
出处:「Variables, Data Types and Constants」第 178 段(
text/11-fm-learning-go-programming.txt:178,搜「amount of space」)。 ↩ -
出处:「Variables, Data Types and Constants」第 191 段(
text/11-fm-learning-go-programming.txt:191,搜「either true or false」)。 ↩ -
出处:「Variables, Data Types and Constants」第 209–242 段(
text/11-fm-learning-go-programming.txt:209,搜「uint8」;text/11-fm-learning-go-programming.txt:242,搜「Unicode code point」)。各类型范围照原书列表。 ↩ ↩2 ↩3 -
出处:「Variables, Data Types and Constants」第 287–302 段(
text/11-fm-learning-go-programming.txt:290,搜「float32」)。 ↩ ↩2 -
出处:「Variables, Data Types and Constants」第 260–284 段(
text/11-fm-learning-go-programming.txt:263,搜「int16(int32val)」;text/11-fm-learning-go-programming.txt:284,搜「prints 156」)。 ↩ ↩2 -
出处:「Variables, Data Types and Constants」第 305、308 段(
text/11-fm-learning-go-programming.txt:305,搜「read-only byte slice」;text/11-fm-learning-go-programming.txt:308,搜「escape sequences」)。 ↩ ↩2 ↩3 -
出处:「Variables, Data Types and Constants」第 368 段(
text/11-fm-learning-go-programming.txt:368,搜「behaves like a value」)。 ↩ -
出处:「Variables, Data Types and Constants」第 482 段(
text/11-fm-learning-go-programming.txt:482,搜「reference type」)。 ↩ -
出处:「Variables, Data Types and Constants」第 626 段(
text/11-fm-learning-go-programming.txt:626,搜「remain fixed」)。 ↩ ↩2 -
出处:「Variables, Data Types and Constants」第 686、716 段(
text/11-fm-learning-go-programming.txt:686,搜「Mixing data types」;text/11-fm-learning-go-programming.txt:716,搜「conversion not allowed」)。 ↩ ↩2 ↩3 -
出处:「Variables, Data Types and Constants」第 719–734 段(
text/11-fm-learning-go-programming.txt:725,搜「myBool bool」)。 ↩ -
出处:「Variables, Data Types and Constants」第 680–683 段(
text/11-fm-learning-go-programming.txt:683,搜「unicode value」)。 ↩ ↩2 -
出处:「Variables, Data Types and Constants」第 737–776 段(
text/11-fm-learning-go-programming.txt:737,搜「increments with const declaration」;text/11-fm-learning-go-programming.txt:761,搜「C0: 0」)。 ↩ ↩2 -
出处:「Variables, Data Types and Constants」第 782 段(
text/11-fm-learning-go-programming.txt:782,搜「percentage (%) sign」)及其后的各张表(第 799–1480 段)。 ↩ ↩2