编程语言研究书库

变量、数据类型与常量: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 位
uint160 ~ 65535不带符号 16 位
uint320 ~ 约 42 亿不带符号 32 位
uint640 ~ 约 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. 可带走的

  1. 声明变量 = 占内存 + 告诉编译器名字和类型;类型终身不变,值可以换;
  2. 零值:不给初值时数字是 0、字符串是 ""、布尔是 false——永远没有随机垃圾;
  3. 六种声明写法,函数体内最常用 :=,函数体外只能用 var/const;
  4. 声明了就必须用,没用的用 _ 显式丢弃;
  5. 整数有十种(int8~uint64 + 别名 byte/rune),平时写 int;
  6. 不同数值类型之间不能隐式互换,int16(x) 是显式转换;溢出不报错,悄悄给错值(-100 → uint8 变 156);
  7. 字符串是只读字节串,UTF-8 编码;单引号字符是编号(71),不是字符本人;
  8. 常量的值必须编译期确定;不带类型常量灵活、带类型常量定型;
  9. iota 在每个 const 块里从 0 开始逐行加一,配合 _ 可以跳过编号;
  10. 打印五件套:%v(默认)、%T(类型)、%d(整数)、%s(字符串)、%q(带引号)。

9. 原文地图

主题原书章原文位置
变量的定义与零值Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:46(搜「zero for numeric」)
六种声明写法Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:49(搜「Go facilitates multiple ways」)
空白标识符丢弃值Variables, Data Types and Constantstext/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 Constantstext/11-fm-learning-go-programming.txt:166(搜「declared and not used」)
数据类型定义与分类Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:178(搜「categorization」)
布尔类型Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:191(搜「either true or false」)
整数家族与范围Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:209(搜「uint8」) · text/11-fm-learning-go-programming.txt:242(搜「Unicode code point」)
显式转换与 -100→156Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:263(搜「int16(int32val)」) · text/11-fm-learning-go-programming.txt:284(搜「2's complement」)
浮点与复数Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:290(搜「float32」)
字符串=只读字节串、UTF-8、不可变Variables, Data Types and Constantstext/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 Constantstext/11-fm-learning-go-programming.txt:368(搜「behaves like a value」)
切片是引用、make 三要素Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:482(搜「reference type」)
map 与 channel 名片Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:551(搜「fast key lookup」) · text/11-fm-learning-go-programming.txt:593(搜「race condition」)
常量、编译期确定Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:626(搜「remain fixed」)
带类型/不带类型常量Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:686(搜「Mixing data types」) · text/11-fm-learning-go-programming.txt:716(搜「conversion not allowed」)
'G' 打印出 71Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:683(搜「unicode value」)
iota 逐行加一、块间归零Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:737(搜「increments with const declaration」)
格式符总表Variables, Data Types and Constantstext/11-fm-learning-go-programming.txt:782(搜「percentage (%) sign」) · text/11-fm-learning-go-programming.txt:804(搜「default format」)

Footnotes

  1. 出处:「Variables, Data Types and Constants」第 46 段(text/11-fm-learning-go-programming.txt:46,搜「zero for numeric」)。 ↩ ↩2 ↩3

  2. 出处:「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」)。例子中的变量名与输出照原书。 ↩

  3. 出处:「Variables, Data Types and Constants」第 52–79 段(text/11-fm-learning-go-programming.txt:49,搜「Go facilitates multiple ways」)。 ↩

  4. 出处:「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

  5. 出处:「Variables, Data Types and Constants」第 178 段(text/11-fm-learning-go-programming.txt:178,搜「amount of space」)。 ↩

  6. 出处:「Variables, Data Types and Constants」第 191 段(text/11-fm-learning-go-programming.txt:191,搜「either true or false」)。 ↩

  7. 出处:「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

  8. 出处:「Variables, Data Types and Constants」第 287–302 段(text/11-fm-learning-go-programming.txt:290,搜「float32」)。 ↩ ↩2

  9. 出处:「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

  10. 出处:「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

  11. 出处:「Variables, Data Types and Constants」第 368 段(text/11-fm-learning-go-programming.txt:368,搜「behaves like a value」)。 ↩

  12. 出处:「Variables, Data Types and Constants」第 482 段(text/11-fm-learning-go-programming.txt:482,搜「reference type」)。 ↩

  13. 出处:「Variables, Data Types and Constants」第 626 段(text/11-fm-learning-go-programming.txt:626,搜「remain fixed」)。 ↩ ↩2

  14. 出处:「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

  15. 出处:「Variables, Data Types and Constants」第 719–734 段(text/11-fm-learning-go-programming.txt:725,搜「myBool bool」)。 ↩

  16. 出处:「Variables, Data Types and Constants」第 680–683 段(text/11-fm-learning-go-programming.txt:683,搜「unicode value」)。 ↩ ↩2

  17. 出处:「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

  18. 出处:「Variables, Data Types and Constants」第 782 段(text/11-fm-learning-go-programming.txt:782,搜「percentage (%) sign」)及其后的各张表(第 799–1480 段)。 ↩ ↩2

On this page