Golang 变量
案例:
Golang 变量使用的三种方式
● 第一种:指定变量类型,声明后若不赋值,使用默认值
-
Golang 的变量如果没有赋初值,编译器会使用默认值
-
比如 int 默认值 0, string 默认值为空串, 小数默认为 0
● 第二种:根据值自行判定变量类型(类型推导) , 不使用默认值
● 第三种:省略 var
○ 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误
● 第四种: 多变量声明
○ 在编程中,有时我们需要一次性声明多个变量,Golang 也提供这样的语法
变量的声明,初始化和赋值
程序中 + 号的使用
package main
import "fmt"
//演示golang中+的使用
func main() {
var i = 1
var j = 2
var r = i + j //做加法运算
fmt.Println("r=", r)
var str1 = "hello "
var str2 = "world"
var res = str1 + str2 //做拼接操作
fmt.Println("res=", res)
}
● 当左右两边都是数值型时,则做加法运算
● 当左右两边都是字符串,则做字符串拼接
- 不可以一个是字符, 一个是其他类型
数据类型的基本介绍
整数类型
● 简单的说,就是用于存放整数值的,比如 0, -1, 2345 等等。
int 的无符号的类型:
int 的其它类型的说明:
整型的使用细节
● Golang 各整数类型分:有符号和无符号,int uint 的大小和系统有关。
● Golang 的整型默认声明为 int 型
● 如何在程序查看某个变量的字节大小和数据类型 (使用较多)
● Golang 程序中整型变量在使用时,遵守保小不保大的原则
○ 即:在保证程序正确运行下,尽量使用占用空间小的数据类型。
● bit: 计算机中的最小存储单位。
● byte:计算机中基本存储单元。
- [二进制再详细说] 1byte = 8 bit
小数类型/浮点型
● 小数类型就是用于存放小数的,比如 1.2 0.23 -1.911
小数类型分类
对上图的说明:
● 关于浮点数在机器中存放形式的简单说明,浮点数=符号位+指数位+尾数位
- 说明:浮点数都是有符号的
var price float32 = 89.12
fmt.Println("price=", price)
var num1 float32 = -0.00089
var num2 float64 = -7809656.09
fmt.Println("num1=", num1, "num2=", num2)
● 尾数部分可能丢失,造成精度损失。 -123.0000901
//尾数部分可能丢失,造成精度损失。 -123.0000901
var num3 float32 = -123.0000901
var num4 float64 = -123.0000901
fmt.Println("num3=", num3, "num4=", num4)
说明:float64 的精度比 float32 的要准确.
说明:如果我们要保存一个精度高的数,则应该选用 float64
● 浮点型的存储分为三部分:符号位+指数位+尾数位 在存储过程中,精度会有丢失
一般出现在尾数也就是小数的时候丢失精度
浮点型使用细节
● Golang 浮点类型有固定的范围和字段长度,不受具体 OS(操作系统)的影响。
● Golang 的浮点型默认声明为 float64 类型。
○ 也就是最大的范围
//Golang 的浮点型默认声明为float64 类型
var num5 = 1.1
fmt.Printf("num5的数据类型是 %T \n", num5)
● 浮点型常量有两种表示形式
-
十进制数形式:如:5.12 .512 (必须有小数点)
-
科学计数法形式:如:
-
5.1234e2 = 5.12 * 10 的 2 次方
-
5.12E-2 = 5.12/10 的 2 次方
//十进制数形式:如:5.12 .512 (必须有小数点)
num6 := 5.12
num7 := .123 //=> 0.123
fmt.Println("num6=", num6, "num7=", num7)
//科学计数法形式
num8 := 5.1234e2 // ? 5.1234 * 10的2次方
num9 := 5.1234E2 // ? 5.1234 * 10的2次方 shift+alt+向下的箭头
num10 := 5.1234E-2 // ? 5.1234 / 10的2次方 0.051234
fmt.Println("num8=", num8, "num9=", num9, "num10=", num10)
● 通常情况下,应该使用 float64 ,因为它比 float32 更精确。
● 开发中,推荐使用 float64
字符类型
● Golang 中没有专门的字符类型,如果要存储单个字符(字母),一般使用 byte 来保存。
● 字符串就是一串固定长度的字符连接起来的字符序列。
● Go 的字符串是由单个字节连接起来的。
● 也就是说对于传统的字符串是由字符组成的,而 Go 的字符串不同,它是由字节组成的。
- 也就是我们不能用正常的方式打印字符串, 正常的方式打印的字符串是码值
var c1 int = '北'
fmt.Println("c1=", c1, unsafe.Sizeof(c1))
var c1 = '北'
fmt.Printf("c1=%c", c1)
对上面代码说明
● 如果我们保存的字符在 ASCII 表的,比如[0-1, a-z,A-Z..]直接可以保存到 byte
● 如果我们保存的字符对应码值大于 255,这时我们可以考虑使用 int 类型保存
● 如果我们需要按照字符的方式输出,这时我们需要格式化输出
- 即 fmt.Printf(“%c”, c1)
字符类型使用细节
● 字符常量是用单引号('')括起来的单个字符。
-
例如:var c1 byte = 'a'
-
var c2 int = '中' var c3 byte = '9' 2)
-
Go 中允许使用转义字符 '\’来将其后的字符转变为特殊字符型常量。
-
例如:var c3 char = ‘\n’ // '\n'表示换行符
● Go 语 言 的 字 符 使 用 UTF-8 编码,如果想查询字符对应的utf8 码值
-
英文字母-1 个字节 汉字-3 个字节
● 在 Go 中,字符的本质是一个整数,直接输出时,是该字符对应的 UTF-8 编码的码值。
● 可以直接给某个变量赋一个数字,然后按格式化输出时%c,会输出该数字对应的 unicode 字符
var c1 = 22269
fmt.Printf("c1=%c", c1)
● 字符类型是可以进行运算的,相当于一个整数,因为它都对应有 Unicode 码
- 注意是单引号的
字符类型本质探讨
● 字符型 存储到 计算机中,需要将字符对应的码值(整数)找出来
-
存储:字符--->对应码值---->二进制-->存储
-
读取:二进制----> 码值 ----> 字符 --> 读取
● 字符和码值的对应关系是通过字符编码表决定的(是规定好)
● Go 语言的编码都统一成了utf-8。非常的方便很统一,再也没有编码乱码的困扰了
布尔类型
● 布尔类型也叫 bool 类型,bool 类型数据只允许取值 true 和 false
● bool 类型占 1 个字节。
● bool 类型适于逻辑运算,一般用于程序流程控制
//演示golang中bool类型使用
func main() {
var b = false
fmt.Println("b=", b)
//注意事项
//1. bool类型占用存储空间是1个字节
fmt.Println("b 的占用空间 =", unsafe.Sizeof(b) )
//2. bool类型只能取true或者false
}
string 类型
● 字符串就是一串固定长度的字符连接起来的字符序列。
● Go 的字符串是由单个字节连接起来的。
● Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本
//string的基本使用
var address string = "北京长城 110 hello world!"
fmt.Println(address)
string 使用注意事项和细节
● Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本
● 这样 Golang 统一使用 UTF-8 编码,中文乱码问题不会再困扰程序员。
● 字符串一旦赋值了,字符串就不能修改了:在 Go 中字符串是不可变的。
//字符串一旦赋值了,字符串就不能修改了:在Go中字符串是不可变的
var str = "hello"
str[0] = 'a' //这里就不能去修改str的内容,即go中的字符串是不可变的。
字符串的两种表示形式
● 双引号, 会识别转义字符
//输出源代码等效果 【案例演示】
str2 := "abc\nabc"
fmt.Println(str2)
● 反引号,以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果
//使用的反引号 ``
str3 := `
package main
import (
"fmt"
"unsafe"
)
//演示golang中bool类型使用
func main() {
var b = false
fmt.Println("b=", b)
//注意事项
//1. bool类型占用存储空间是1个字节
fmt.Println("b 的占用空间 =", unsafe.Sizeof(b) )
//2. bool类型只能取true或者false
}
`
fmt.Println(str3)
● 字符串拼接方式
//字符串拼接方式
var str = "hello " + "world"
str += " haha!"
fmt.Println(str)
● 当一行字符串太长时,需要使用到多行字符串,可以如下处理
//当一个拼接的操作很长时,可以分行写,但是注意,需要将+保留在上一行
str4 := "hello " + "world" + "hello " + "world" + "hello " +
"world" + "hello " + "world" + "hello " + "world" +
"hello " + "world"
fmt.Println(str4)
基本数据类型的默认值
在 go 中,数据类型都有一个默认值,当程序员没有赋值时,就会保留默认值,在 go 中,默认值 又叫零值。
var a int // 0
var b float32 // 0
var c float64 // 0
var isMarried bool // false
var name string // ""
//这里的%v 表示按照变量的值输出
fmt.Printf("a=%d,b=%v,c=%v,isMarried=%v name=%v", a, b, c, isMarried, name)
基本数据类型的相互转换
Golang 和 java / c 不同,Go 在不同类型的变量之间赋值时需要显式转换。也就是说 Golang 中数 据类型不能自动转换。
基本语法
● 表达式 T(v) 将值 v 转换为类型 T
○ T: 就是数据类型,比如 int32,int64,float32 等等
○ v: 就是需要转换的变量
var i int32 = 100
//希望将 i => float
var n1 float32 = float32(i)
var n2 int8 = int8(i)
var n3 int64 = int64(i) //低精度->高精度
fmt.Printf("i=%v n1=%v n2=%v n3=%v \n", i ,n1, n2, n3)
基本数据类型相互转换的注意事项
● Go 中,数据类型的转换可以是从 表示范围小-->表示范围大,也可以 范围大--->范围小
● 被转换的是变量存储的数据(即值),变量本身的数据类型并没有变化!
var i int32 = 100
//被转换的是变量存储的数据(即值),变量本身的数据类型并没有变化
fmt.Printf("i type is %T\n", i) // int32
● 在转换中,比如将 int64 转成 int8 【-128---127】 ,编译时不会报错,只是转换的结果是按 溢出处理,和我们希望的结果不一样。
● 因此在转换时,需要考虑范围
//在转换中,比如将 int64 转成 int8 【-128---127】 ,编译时不会报错,
//只是转换的结果是按溢出处理,和我们希望的结果不一样
var num1 int64 = 999999
var num2 int8 = int8(num1)
fmt.Println("num2=", num2)
数据类型和 string 的转换
● 方式 1:fmt.Sprintf("%参数", 表达式)
var num1 int = 99
var num2 float64 = 23.456
var b bool = true
var myChar byte = 'h'
var str string //空的str
//使用第一种方式来转换 fmt.Sprintf方法
str = fmt.Sprintf("%d", num1)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%f", num2)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%t", b)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%c", myChar)
fmt.Printf("str type %T str=%q\n", str, str)
● 方式 2:使用 strconv 包的函数
var num3 int = 99
var num4 float64 = 23.456
var b2 bool = true
str = strconv.FormatInt(int64(num3), 10)
fmt.Printf("str type %T str=%q\n", str, str)
// strconv.FormatFloat(num4, 'f', 10, 64)
// 说明: 'f' 格式 10:表示小数位保留10位 64 :表示这个小数是float64
str = strconv.FormatFloat(num4, 'f', 10, 64)
fmt.Printf("str type %T str=%q\n", str, str)
str = strconv.FormatBool(b2)
fmt.Printf("str type %T str=%q\n", str, str)
//strconv包中有一个函数Itoa
var num5 int64 = 4567
str = strconv.Itoa(int(num5))
fmt.Printf("str type %T str=%q\n", str, str)
string 类型转基本数据类型
● 使用时 strconv 包的函数
var str string = "true"
var b bool
b, _ = strconv.ParseBool(str)
fmt.Printf("b type %T b=%v\n", b, b)
var str2 string = "1234590"
var n1 int64
var n2 int
n1, _ = strconv.ParseInt(str2, 10, 64)
n2 = int(n1)
fmt.Printf("n1 type %T n1=%v\n", n1, n1)
fmt.Printf("n2 type %T n2=%v\n", n2, n2)
var str3 string = "123.456"
var f1 float64
f1, _ = strconv.ParseFloat(str3, 64)
fmt.Printf("f1 type %T f1=%v\n", f1, f1)
string 转基本数据类型的注意事项
● 在将 String 类型转成 基本数据类型时,要确保 String 类型能够转成有效的数据
-
比如 我们可以 把 "123" , 转成一个整数,但是不能把 "hello" 转成一个整数,
-
如果这样做,Golang 直接将其转成 0
-
其它类型也是一样的道理. float => 0 bool => false
//注意:
var str4 string = "hello"
var n3 int64 = 11
n3, _ = strconv.ParseInt(str4, 10, 64)
fmt.Printf("n3 type %T n3=%v\n", n3, n3)
指针
● 基本数据类型,变量存的就是值,也叫值类型
● 获取变量的地址,用&
- 比如: var num int, 获取 num 的地址:&num
● 指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值
- 比如:var ptr *int = &num
//基本数据类型在内存布局
var i int = 20
// i 的地址是什么,&i
fmt.Println("i的地址=", &i)
//下面的 var ptr *int = &i
//1. ptr 是一个指针变量
//2. ptr 的类型 *int
//3. ptr 本身的值&i
var ptr *int = &i
fmt.Printf("ptr=%v\n", ptr)
fmt.Printf("ptr 的地址=%v", &ptr)
fmt.Printf("ptr 指向的值=%v", *ptr)
● 获取指针类型所指向的值,使用:*,比如:var ptr int, 使用ptr 获取 ptr 指向的值
- fmt.Printf("ptr 指向的值=%v", *ptr)
● 写一个程序,获取一个 int 变量 num 的地址,并显示到终端
● 将 num 的地址赋给指针 ptr , 并通过 ptr 去修改 num 的值
指针的使用细节
● 值类型,都有对应的指针类型, 形式为 *数据类型
- 比如 int 的对应的指针就是 *int, float32 对应的指针类型就是 *float32
● 值类型包括:基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct
值类型和引用类型
● 值类型:基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct
● 引用类型:指针、slice 切片、map、管道 channel、interface 等都是引用类型
值类型和引用类型的使用特点
● 值类型:变量直接存储值,内存通常在栈中分配
● 引用类型:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值)
● 内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由 GC 来回收
● 内存的栈区和堆区示意图
数组
array 就是数组,它的定义方式如下:
var arr [n]type
在 [n]type 中,n 表示数组的长度,type 表示存储元素的类型。对数组的操作和其它语言类似,都是通过 [] 来进行读取或赋值:
var arr [10]int
arr[0] = 44
arr[1] = 45
fmt.Println("这是数组的第一个元素 ",arr[0])
fmt.Println("这是数组的第二个元素 ",arr[1])
fmt.Pintln(arr)
这是数组的第一个元素 44
这是数组的第二个元素 45
[44 45 0 0 0 0 0 0 0 0]
-
由于长度也是数组类型的一部分,因此 [3]int 与 [4]int 是不同的类型,数组也就不能改变长度。
-
数之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。
数组可以使用另一种 := 来声明
a := [3]int{1,2,3}
b := [2]string{"a","b"}
c := [...]int{1,2,3,4,5}
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
[1 2 3]
[a b]
[1 2 3 4 5]
也许你会说,我想数组里面的值还是数组,能实现吗?
当然咯,Go 支持嵌套数组,即多维数组。
比如下面的代码就声明了一个二维数组:
doubleArray := [2][4]int{[4]int{1,2,3,4}, [4]int{11,22,33,44}}
easyArray := [2][4]int{{1,2,3,4},{11,22,33,44}}
fmt.Println(doubleArray)
fmt.Println(easyArray
[[1 2 3 4] [11 22 33 44]]
[[1 2 3 4] [11 22 33 44]]
数组的分配如下所示:
动态数组
1. 在很多应用场景中,数组并不能满足我们的需求。在初始定义数组时,我们并不知道需要多大的数组,因此我们就需要 “动态数组”。
2. 在 Go 里面这种数据结构叫 slice
slice 并不是真正意义上的动态数组,而是一个引用类型。
slice 总是指向一个底层 array,slice 的声明也可以像 array 一样,只是不需要长度。
slice := []int{1, 2, 3, 4}
slice2 := []int{5, 6, 7}
fmt.Println(slice)
fmt.Println(slice2)
slice = slice2[0:3] //[0,3)
fmt.Println(slice)
[1 2 3 4]
[5 6 7]
[5 6 7]
注意 slice 和数组在声明时的区别:
1. 声明数组时,方括号内写明了数组的长度或使用 ... 自动计算长度
2. 而声明 slice 时,方括号内没有任何字符。
slice 有一些简便的操作
-
slice 的默认开始位置是 0,ar[:n] 等价于 ar[0:n]
-
slice 的第二个序列默认是数组的长度,ar[n:] 等价于 ar[n:len(ar)]
-
如果从一个数组里面直接获取 slice,可以这样 ar[:],因为默认第一个序列是 0,第二个是数组的长度,即等价于 ar[0:len(ar)]
下面这个例子展示了更多关于 slice 的操作:
// 声明一个数组
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// 声明两个 slice
var aSlice, bSlice []byte
// 演示一些简便操作
aSlice = array[:3] fmt.Printf("%c\n", aSlice)
aSlice = array[5:] fmt.Printf("%c\n", aSlice)
aSlice = array[:] fmt.Printf("%c\n", aSlice)
aSlice = array[3:7] fmt.Printf("%c\n", aSlice)
bSlice = aSlice[1:3]
fmt.Printf("%c\n", bSlice)
bSlice = aSlice[:3]
fmt.Printf("%c\n", bSlice)
bSlice = aSlice[0:5]
fmt.Printf("%c\n", bSlice)
bSlice = aSlice[:]
fmt.Printf("%c\n", bSlice)
fmt.Printf("%c\n", bSlice)
[a b c]
[f g h i j]
[a b c d e f g h i j]
[d e f g]
[e f]
[d e f]
[d e f g h]
[d e f g]
[d e f g]
- slice 是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值
- 例如上面的 aSlice 和 bSlice,如果修改了 aSlice 中元素的值,那么 bSlice 相对应的值也会改变。
从概念上面来说 slice 像一个结构体,这个结构体包含了三个元素:
- 一个指针,指向数组中 slice 指定的开始位置
- 长度,即 slice 的长度
- 最大长度,也就是 slice 开始位置到数组的最后位置的长度
- 上面的赋值, slice最大cap = 8
- 实际上是数组从2开始截断的总容量给了slice动态数组, 作为数组的总容量
- 但是只有cde这3个被显示, 实际len = 3, 实际赋值的只有3个
对于 slice 有几个有用的内置函数:
- len 获取 slice 的长度
- cap 获取 slice 的最大容量
- append 向 slice 里面追加一个或者多个元素,然后返回一个和 slice 一样类型的 slice
- copy 函数 copy 从源 slice 的 src 中复制元素到目标 dst,并且返回复制的元素的个数
注:
- append 函数会改变 slice 所引用的数组的内容,从而影响到引用同一数组的其它 slice。
- 但当 slice 中没有剩余空间(即 (cap-len) == 0 )时,此时将动态分配新的数组空间。
- 返回的 slice 数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的 slice 则不受影响。(等同于复制一个新的数组, 之前的数组不会变化)
从 Go 1.2 开始 slice 支持了三个参数的 slice,之前我们一直采用这种方式在 slice 或者 array 基础上来获取一个 slice
var array [10]int
slice := array[2:4]
这个例子里面 slice 的容量是 8,新版本里面可以指定这个容量, 数组从2开始截断的总长度给了slice, 但只有[2,4)的值进行了赋值
slice = array[2:4:7]
- 上面这个的容量就是 7-2,即 5。这样这个产生的新的 slice 就没办法访问最后的三个元素。
var array = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice := array[2:4:7]
fmt.Println(slice)
fmt.Println(cap(slice))
fmt.Println(len(slice))
[3 4]
5
2
- 像只有两个参数的赋值, 就是默认了取最大的容量(array[2:4])
- 如果 slice 是这样的形式 array[:i:j],即第一个参数为空,默认值就是 0。
map
就是 Python 中字典的概念,它的格式为
map[keyType]valueType
我们看下面的代码
map 的读取和设置也类似 slice 一样,通过 key 来操作,只是 slice 的 index 只能是 int 类型,而 map 多了很多类型,可以是 int,可以是 string 及所有完全定义了 == 与 != 操作的类型。
var num map[string]int = make(map[string]int)
num["key"] = 1
fmt.Println(num)
strMap := make(map[string]string)
strMap["key"] = "value"
strMap["1"] = "1"
fmt.Println(strMap)
map[key:1]
map[1:1 key:value]
使用 map 过程中需要注意的几点:
- map 是无序的,每次打印出来的 map 都会不一样,它不能通过 index 获取,而必须通过 key 获取
- map 的长度是不固定的,也就是和 slice 一样,也是一种引用类型
- 内置的 len 函数同样适用于 map,返回 map 拥有的 key 的数量
- map 的值可以很方便的修改,通过 numbers["one"]=11 可以很容易的把 key 为 one 的字典值改为 11
- 在 Go 中,没有值可以安全地进行并发读写,它不是 thread-safe,在多个 go-routine 存取时,必须使用 mutex lock 机制
map 的初始化可以通过 key:val 的方式初始化值,同时 map 内置有判断是否存在 key 的方式 通过 delete 删除 map 的元素:
var num map[string]int = map[string]int{"1": 1, "2": 2}
fmt.Println(num)
value, ok := num["3"]
if ok {
fmt.Println(value)
return
}
fmt.Println("错误")
delete(num, "2")
value2, ok2 := num["2"]
if ok2 {
fmt.Println(value2)
return
}
fmt.Println("错误")
map[1:1 2:2]
错误
错误
map 也是一种引用类型,如果两个 map 同时指向一个底层,那么一个改变,另一个也相应的改变:
var num map[string]int = map[string]int{"1": 1, "2": 2}
num2 := map[string]string{"3": "3", "4": "4"}
fmt.Println(num)
fmt.Println(num2)
atoi, _ := strconv.Atoi(num2["3"])
num["3"] = int(atoi)
fmt.Println(num)
map[1:1 2:2]
map[3:3 4:4]
map[1:1 2:2 3:3]
make、new 操作
make 用于内建类型(map、slice 和 channel)的内存分配。new 用于各种类型的内存分配。
- 内建函数 new 本质上说跟其它语言中的同名函数功能一样:
- new(T) 分配了零值填充的 T 类型的内存空间,并且返回其地址,即一个 *T 类型的值。
- 用 Go 的术语说,它返回了一个指针,指向新分配的类型 T 的零值(并不是同一个零值)。
有一点非常重要:new 返回指针。
num := new(map[int]int)
fmt.Println(num)
&map[]
- 内建函数 make(T, args) 与 new(T) 有着不同的功能
- make 只能创建 slice、map 和 channel,并且返回一个有初始值 (非零) 的 T 类型,而不是 *T指针类型。
本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。
例如
- 一个 slice,是一个包含指向数据(内部 array)的指针、长度和容量的三项描述符;
- 在这些项目被初始化之前,slice 为 nil。
- 对于 slice、map 和 channel 来说,make 初始化了内部的数据结构,填充适当的值。
make 返回初始化后的(非零)值。
strMap := make(map[string]string)
fmt.Println(strMap)
map[]
零值(默认值)
关于 “零值”,所指并非是空值,而是一种 “变量未填充前” 的默认值,通常为 0。
此处罗列 部分类型 的 “零值”
int 0
int8 0
int32 0
int64 0
uint 0x0
rune 0 // rune 的实际类型是 int32
byte 0x0 // byte 的实际类型是 uint8
float32 0 // 长度为 4 byte
float64 0 // 长度为 8 byte
bool false
string ""