为了充分利用内存以及对不同数据抽象出不同算法,Go 语言内置了 7 类基本数据类型。本节我们就详细解读一下Go 语言中的这7个基本数据类型紧密相关的基础知识,为后面的编程打下扎实的根基。
▲ bool 是布尔类型的标识符,这个类型只有两个值,分别是 true 和 false,通常代表 真/假、有/无、是/否等。
▲ 布尔类型的变量:
var isOk1 bool // 完整格式声明变量 isOk1 为布尔变量,默认值是假,可以理解为初始化为假
isOk1 = true // 给布尔变量 isOk1 赋值为真,必须是提前声明过的布尔变量
var isOk2 bool = true // 完整格式声明变量 isOk2 为布尔变量,及初始化值为真
var isOk3 = true // 简式格式声明变量 isOk3 及初始化值为真,编译器根据值的类型推导为布尔类型
isOk4 := true // 仅适用于局部变量,简式格式声明变量 isOk4 及初始化值为真,编译器根据值的类型推导为布尔类型
▲ 布尔类型的表达式(主要用于判断逻辑,如 if、switch、for 的条件):
a == b // 若 a 与 b 相等,则结果为真,否则结果为假
a != b // 若 a 与 b 不相等,则结果为真,否则结果为假
a > b // 若 a 大于 b,则结果为真,否则结果为假
a >= b // 若 a 大于 b 或等于 b,则结果为真,否则结果为假
a < b // 若 a 小于 b 则结果为真,否则结果为假
a <= b // 若 a 小于 b 或等于 b,则结果为真,否则结果为假
!true // 将布尔值反转,结果为 false
(a > b) && (b > c) // 若 a 大于 b 并且 b 大于 c 结果才为真,否则结果为假(多个条件全为真则为真,否则为假)
(a > b) || (b > c) // 若 a 大于 b 或者 b 大于 c 则结果为真,否则结果为假(多个条件有任何一个为真则为真,否则为假)
▲ 布尔类型的转换
布尔类型不能与字符串和数字类型进行直接转换,若需要转换,需要导入标准库或可以自己编写下面类似的转换函数:
boolToInt 自定义布尔类型转整数类型:
// boolToInt 将布尔类型值的 true 和 false 转换为整数类型的 1 或 0
// 参数:
// b:传入待转换的布尔类型值
// 返回值:
// i:转换后的整数结果,1 或 0
func boolToInt(b bool) (i int) {
if b {
i = 1
}
return
}
FormatBool 标准库 strconv 中布尔类型转字符串类型:
s := strconv.FormatBool(true) // 需导入 strconv 包,变量 s 为字符串类型,s 的值为 “true”
boolToString 自定义布尔类型转字符串类型:
// boolToString 将布尔类型值的 true 和 false 转换为字符串类型的 "true" 或 "false"
// 参数:
// b:传入待转换的布尔类型值
// 返回值:
// s:转换后的字符串结果,“true” 或 “false”
func boolToString(b bool) (s string) {
s = "false"
if b {
s = "true"
}
return
}
intToBool 自定义整数类型转布尔类型:
// intToBool 将整数类型值大于0的转换为布尔值 true,将整数类型的 0 值转换为布尔类型的 false
// 参数:
// i:传入待转换的整数类型值
// 返回值:
// b:转换后的布尔类型结果,true 或 false
func intToBool(i int) (b bool) {
if i > 0 {
b = true
}
return
}
ParseBool 标准库 strconv 中字符串类型转布尔类型:
// 只能接受 1、0、t、f、T、F、true、false、True、False、TRUE、FALSE,其它的值均返回错误,
a, err := strconv.ParseBool("false") // 需导入 strconv 包,变量 a 为布尔类型,值为 false
stringToBool 自定义整数类型转布尔类型:
// stringToBool 将字符串类型的空“”或“false”转换为布尔类型的 false,其他字符串转为布尔类型的 true
// 参数:
// s:传入待转换的字符串类型值
// 返回值:
// b:转换后的布尔类型结果,true 或 false
func stringToBool(s string) (b bool) {
if s != "" && s != "false" {
b = true
}
return
}
▲ int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64 以及 byte、rune、uintptr 是整数类型的 13 个标识符,这些整数类型标识符之间的主要差别就是有无符号、和长度大小。
byte 是 uint8 的别名,所以与 uint8 具备同样特征,只是通常用来表示字节值便于区分。rune 是 uint32 的别名,所以与 uint32 具备同样特征,只是通常用来表示Unicode码便于区分。uintptr 是无符号整数 ,一个能足够容纳指针位数大小的整数类型,长度最大受操作系统限制,类似于uint32 或 uint64。
以字母 u 开头的都是无符号的(就是没有负数,最小值从 0 起步)。其它(别名不讨论)不以字母 u 开头的整数类型都是有符号的(就是有负数,最小值小于 0)。
上述标识符中的 8、16、32、64 都是指二进制位数的长度,知道最大位数,就能知道有符号和无符号的最大值了。例如 8 位的二进制最大值是 11111111,那换算成无符号10进制就是 255,换算成有符号就是 -128 就变成了最小值。
int 与 uint 的长度与编译环境的操作系统位数相同,有两种长度 32位 或 64位。所以使用 int 存储数字可能超过 2147483647 或使用 uint 存储数字可能超过 4294967295 时,要特别注意操作系统的位数与你项目实际运行环境是否吻合,否则会出现莫名其妙的异常情况。
类型 | 长度 | 范围 |
---|---|---|
int8 / uint8(byte) | 8位(1个字节) | -128 ~ 127 / 0 ~ 255 |
int16 / uint16 | 16位(2个字节) | -32768 ~ 32767 / 0 ~ 65535 |
int32 / uint32(rune) | 32位(4个字节) | -2147483648 ~ 2147483647 / 0 ~ 4294967295 |
int64 / uint64 | 64位(8个字节) | -9223372036854775808 ~ 9223372036854775807 / 0 ~ 18446744073709551615 |
int / uint | 32/64位(4/8个字节) | int 和 uint 在32位系统中同 int32 和 uint32,64位系统中同 int64 和 uint64 |
uintptr | 32/64位(4/8个字节) | 0 ~ 4294967295 / 0 ~ 18446744073709551615 |
▲ 整数类型的变量:
var a int // 完整格式声明变量 a 为 int 类型变量,默认值是 0,可以理解为初始化为 0
a = 36 // 给 int 变量 a 赋值为 36,必须是提前声明过的整数型变量才可以直接赋值数字
var b int16 = 98 // 完整格式声明变量 b 为 int16 变量,及初始化值为 98
var c = 21 // 简式格式声明变量 c 及初始化值为 21,编译器根据值的类型推导为 int 类型
d := 306 // 仅适用于局部变量,简式格式声明变量 d 及初始化值为 306,编译器根据值的类型推导为 int 类型
var e byte = 'A' // byte 类型通常用于声明保存字符的 ascii 码值,这里实际保存的是数学值 65(A 的 ascii 码值)
e = 65 // 赋值 65 同样代表了字母 A
e = 0x41 // 这是赋值的16进制的 41 ,实际与10进制的 65 是同一个值,在内存中的二进制值一模一样
var f rune = '\u4e00' // 给变量 f 赋值了一个汉字 “一” 的 Unicode 编码值,实际内部存储的还是整数值 19968
注意:简式格式声明通过数字推导出来的整数类型是 int,不会根据初始化数值大小指定其他定位数的整数类型。给 byte 类型赋值字符时一定要用单引号。
既然 byte 和 rune 保存的也是整数值,那么怎么看除出来存的是什么字符呢?除了 Go 语言内部的一些转换逻辑,我们还可以在控制台终端上直接使用格式化打印输出,具体语句如下:
var e rune = '\u4E00'
fmt.Printf("%d", e) // 输出整数 19968
fmt.Printf("%c", e) // 输出字符 一
fmt.Printf("%X", e) // 输出UTF-8字节 4E00
fmt.Printf("%U", e) // 输出UTF-8码位 U+4E00
以上格式化输出语句对 byte 类型同样有效。
▲ 整数类型的运算表达式:
整数类型中实际是有 10 个基本子类型,但是它们之间不能直接进行算术运算。在 Go 语言中,只有类型完全一致的整数类型才可以进行算术运算。在加减乘除的算术运算表达式中,与数学课程学习的算术基本一致,复杂的算式使用小括号提权,使结构更清晰,避免记错优先级造成计算结果与预想的不一致。
var a int = 101
var b int32 = 203
var c int32 = 45
var d int8 = 19
var e = 116
f := 41
var g int
g = a + b // 错误:因为 a 与 b 类型不完全相同
g = b + e // 错误:因为 b 与 e 类型不完全相同
g = b + c // 错误:因为 b 与 c 虽然类型完全相同,但与 g 不相同
d = d + 256 // 错误:因为 256 超出了 d 的 int8 类型数值范围
d = d + 100 // 正确:都是 int8 类型,且字面量 100 没有超出 int8 范围
g = a + (e + f) * 2 // 正确,都是 int 类型
整数类型还支持位运算,结果还是整数类型。
var a int = 64 >> 2 // 结果为 16
var b int16 = 8 << 1 // 结果为 16
c := b & 7 // 变量 c 的值为 0
d := b | 7 // 变量 d 的值为 23
e := ^b // 变量 e 的值为 -17
f := b &^ 16 // 变量 f 的值为 0
▲ 整数类型的转换
整数类型的各子类型之间运算,需要统一数据类型。Go 语言提供了整数类型的强制转换,直接使用类型名同名的强制转换标识符即可,下面列举:
// 变量 a 是整数类型中的任何一个都可以使用下面的强制转换方法
int(a) // 将变量 a 的值强制转换为 int 类型
int8(a) // 将变量 a 的值强制转换为 int8 类型
int16(a) // 将变量 a 的值强制转换为 int16 类型
int32(a) // 将变量 a 的值强制转换为 int32 类型
int64(a) // 将变量 a 的值强制转换为 int64 类型
uint(a) // 将变量 a 的值强制转换为 uint 类型
uint8(a) // 将变量 a 的值强制转换为 uint8 类型
uint16(a) // 将变量 a 的值强制转换为 uint16 类型
uint32(a) // 将变量 a 的值强制转换为 uint32 类型
uint64(a) // 将变量 a 的值强制转换为 uint64 类型
byte(a) // 将变量 a 的值强制转换为 byte 类型
rune(a) // 将变量 a 的值强制转换为 rune 类型
在编程开发中,我们还需要在字符串类型和整数类型之间互转,Go 语言在标准库里给我们提供了方法:
FormatInt 标准库 strconv 中的 int 转 string:
s1 := strconv.Itoa(-11) // 支持负号和2~36进制数(必须 int 类型),s1 的值为字符串类型 “-11”,需导入 strconv 包
var n int64 = -11 // 声明 int64 类型的有符号整数变量
s2 := strconv.FormatInt(n, 10) // 第1个参数必须是int64类型,第2个参数为转换成 2~36 之间的某个进制数,需导入 strconv 包
FormatUint 标准库 strconv 中的 uint 转 string:
var n uint64 = 11 // 声明 uint64 类型的无符号整数变量
s := strconv.FormatUint(n, 10) // 第1个参数必须是int64类型,第2个参数为转换成 2~36 之间的某个进制数,需导入 strconv 包
ParseInt 标准库 strconv 中的 string 转 int:
n1, err := strconv.Atoi("-11") // 只接受数字和负号,n1 的值为 int 类型 -11,需导入 strconv 包
// “-11”:待转的字符串,可以有负号;10:为2~36的进制数;0:表示int,可以是 8、16、32、64 代表其他及格 int 类型
n2, err := strconv.ParseInt("-11", 10, 0) // 需导入 strconv 包,n2 的值为 -11,类型为 int
ParseUnit 标准库 strconv 中的 string 转 uint:
// 同上,只是第一个参数不接受负号
n, err := strconv.ParseInt("11", 10, 0) // 需导入 strconv 包,n 的值为 11,类型为 uint
▲ 浮点类型与整数类型同属于数字类型,主要区别就是浮点类型包含小数点。Go 语言内置两种浮点类型,分别是float32 和 float64。两个浮点数不能用 == 或 != 进行比较判断,因为计算机很难精确表示和存储浮点数。要求高精度计算的请使用标准库中的 math 库中的相应方法(函数)。
类型 | 长度 | 说明 |
---|---|---|
float32 | 32位(4个字节) | 单精度浮点数,最大范围是 3.4e38 |
float64 | 64位(8个字节) | 双精度浮点数,最大范围是 1.8e308,浮点数的默认类型 |
▲ 浮点类型的变量:
var a float32 // 完整格式声明变量 a 为单精度浮点变量,默认值是 0,可以理解为初始化为 0
a = 3.14 // 给单精度浮点变量 a 赋值为 3.14,必须是提前声明过的浮点变量
var b float64 = 3.14 // 完整格式声明变量 b 为双精度浮点变量,及初始化值为 3.14
var c = 3.14 // 简式格式声明变量 c 及初始化值为 3.14,编译器根据值的类型推导为双精度浮点类型
d := 3.14 // 仅适用于局部变量,简式格式声明变量 d 及初始化值为 3.14,编译器根据值的类型推导为双精度浮点类型
▲ 浮点类型的运算转换:
浮点数在运算过程中同样需要类型完全相同,否则无法计算。另外会有需要取整数部分、四舍五入、转为整数类型等,Go 语言提供了相关的方法函数:
z := float32(a) // 将变量 a 的值强制转换为 float32 类型
z := float64(a) // 将变量 a 的值强制转换为 float64 类型
v := int(3.14) // 取整,强制转换为整数,去掉小数部分,变量 v 的值是 3。转其它整数类型也是类似用法
// 以下需要导入 math 库,返回值均为 float64
v1:= math.Ceil(3.14) // 进位取整,只要小数部分不为零就将整数部分加 1,v1 的值为 4
v2:= math.Floor(3.14) // 舍位取整,舍弃小数部分,v2 的值为 3
v3:= math.Round(3.54) // 四舍五入取整,v3 的值为 4
v4 := math.Round(3.1415 * 100) / 100 // 四舍五入保留两位小数,保留三位小数就把 100 换成 1000,依次类推,v4 的值 3.14
v5 := math.Max(3.1415926, 3.1415927) // 高精度比较大小,返回最大的数,v5 的值 3.1415927
v5 := math.Min(3.1415926, 3.1415927) // 高精度比较大小,返回最小的数,v5 的值 3.1415926
// 以下需要导入 strconv 库
// 3.1415:待转的小数,'f':格式为-ddd.dddd,3:保留小数位数,64:待转小数的精度 32/64
v5 := strconv.FormatFloat(3.1415, 'f', 3, 64) // 将浮点型四舍五入后返回字符串类型值,v5 的值 “3.142”。
//
v6, err := strconv.ParseFloat("3.1415", 64) // 将字符串浮点数字转换为浮点 float64 类型值,v6 的值 3.1415
▲ Go语言内置了 complex64 和 complex128 两种复数类型。复数在计算机中使用两个浮点数来表示,一个表示实部,另一个表示虚部。复数的字面量默认类型是 complex128。
类型 | 长度 | 说明 |
---|---|---|
complex64 | 64位(8个字节) | 64 位复数,由两个float32构成,一个表示实部,一个表示虚部 |
complex128 | 128位(16个字节) | 128 位复数,由两个float64构成,一个表示实部,一个表示虚部 |
▲ 复数类型的变量:
如果一个浮点数面值或十进制整数字面量后面跟一个 i ,例如 3.14i 或 5i,这就构成一个复数的虚部,实部直接书写加在虚部前面。
var a complex64 = 2.1 + 6i // 完整格式声明复数变量 a,类型为 complex64
var b complex128 = 3.1 + 7i // 完整格式声明复数变量 b,类型为 complex128
c := complex(2.1, 3) // 仅适用局部变量,简式格式使用创建函数声明复数变量 c,类型为 complex128
d := complex64(2.1, 3) // 仅适用局部变量,简式格式使用创建函数声明复数变量 d,类型为 complex64
e := complex128(2.1, 3) // 仅适用局部变量,简式格式使用创建函数声明复数变量 e,类型为 complex128
f := 2.1 + 5i // 仅适用局部变量,简式格式声明复数变量 f,类型为 complex128
▲ 复数类型相关的函数:
Go 语言内置了获取复数的实部和虚部的函数:
f := 2.1 + 5i // 声明一个 complex128 类型的复数变量 f
v1 := real(f) // 获取复数变量 f 的实部,v1 的值为 2.1
v2 := imag(f) // 获取复数变量 f 的虚部,v1 的值为 5
▲ 字符串是 Go 语言的原生基本数据类型,其初始化可以使用字符串字面量。字符串是类似字节数组的字符集合,可以通过类似数组的方式获取其字节单元,但是不能使用这种方式修改。与C/C++不同的是尾部不包含 null 字符。
▲ 字符串类型的变量:
var s1 string // 完整格式声明一个字符串变量 s1,默认为空字符串
s1 = "hello" // 给字符串变量 s1 赋值一个字符串 “hello”,必须是提前声明过的字符串变量
var s2 string = "hello" // 完整格式声明一个字符串变量 s2,并初始化为 “hello”
var s3 = "hello" // 简式声明一个变量 s3 并初始化为 “hello”,编译器根据值的类型推导为字符串类型
s4 := "hello" // 仅适用局部变量,简式声明一个变量 s4 并初始化为 “hello”,编译器根据值的类型推导为字符串类型
▲ 字符串类型的运算:
s1 := "hello" // 声明一个字符串变量 s1,初始化为字符串 “hello”
s2 := "!" // 声明一个字符串变量 s2,初始化为字符串 “!”
v := len(s1) // 计算字符串长度(字符个数),v 的值是 5
// 下面是几种字符串拼接方式,性能从上至下越来越高
s3 := s1 + s2 // s3 的值是 “hello!”,增加内容项则增加加号
s4 := fmt.Sprintf("%s%s", s1, s2) // s3 的值是 “hello!”,增加内容项则增加参数和格式表达式内容
var ss = []string{s1, s2} // 需要导入 strings 包,定义一个字符串数组包含上述的字符串
s5 := strings.Join(ss, "") // 调用 Join 函数实现拼接,s5 的值是 “hello!”
var b bytes.Buffer // 需要导入 bytes 包,声明一个 bytes.Buffer 类型的空变量 b
b.WriteString(s1) // 向里面写入字符串 s1
b.WriteString(s2) // 向里面写入字符串 s2
s6 := b.String() // 将 bytes.Buffer 类型转换为字符串类型,s6 的值是 “hello!”
var ss strings.Builder // 需要导入 strings 包,声明一个 strings.Builder 类型的空变量 ss
ss.WriteString(s1) // 向里面写入字符串 s1
ss.WriteString(s2) // 向里面写入字符串 s2
s7 := ss.String() // 将 strings.Builder 类型转换为字符串类型,s7 的值是 “hello!”
字符串也适用于 ==、!=、<、<=、>、>= 等判断对比运算符。
▲ 字符串类型转换:
字符串类型与上面几个数字类型之间的转换方法,上面已经在相关类型中有说明,这里不在赘述了。另外关于字符串的数组属性及切片操作等在后面数组和切片章节再做介绍。
▲ 字符串类型的其他相关操作函数:
标准库 strings 包部分常用函数(需要导入 strings 包):
is1 := strings.HasPrefix("hello!", "h") // 判断 “hello!” 是否以 “h” 开头,返回布尔值,is1 的值为 true
is2 := strings.HasSuffix("hello!", "h") // 判断 “hello!” 是否以 “h” 结尾,返回布尔值,is2 的值为 false
is3 := strings.Contains("hello!", "e") // 判断 “hello!” 中是否包含 “e” 结尾,返回布尔值,is3 的值为 true
i1:= strings.Index("hello!", "l") // 查找 “l” 在 “hello!” 中首次出现的位置(int 类型),-1 为未找到,i1 值为 2
i2:= strings.LastIndex("hello!", "l") // 查找 “l” 在 “hello!” 中最后出现的位置(int 类型),-1 为未找到,i2 值为 3
i3:= strings.IndexRune("中国人", '人') // 查找非 ascii 字符“人”在“中国人”的定位,第2个参数为 rune 类型,必须单引号
i4:= strings.Count("hello!", "l") // 统计 “l” 在 “hello!” 中出现的次数(int类型),i4 值为 2
s1 := strings.Replace("hello!", "l", "?", 2) // 将 “hello!” 中的前2个(-1为所有) “l” 替换为 “?”,s1 的值 “he??o!”
s2 := strings.Repeat("he", 3) // 将 “he” 重复 3 次,生成一个新的字符串,s2 的值 “hehehe”
s3 := strings.TrimSpace(" he llo ! ") // 删除 “ he llo ! ” 头尾两端的空白字符,s3 的值 “he llo !”
s4 := strings.Trim(" he llo ! ", " ") // 删除 “ he llo ! ” 头尾两端的 “ ” 字符串,s4 的值 “he llo !”
s5 := strings.TrimLeft(" he llo ! ", " ") // 删除 “ he llo ! ” 左侧的 “ ” 字符串,s5 的值 “he llo ! ”
s6 := strings.TrimRight(" he llo ! ", " ") // 删除 “ he llo ! ” 右侧的 “ ” 字符串,s6 的值 “ he llo !”
s7 := strings.ToLower("Hello!") // 将 "Hello!" 全部变成小写,s7 的值 “hello!”
s8 := strings.ToUpper("Hello!") // 将 "Hello!" 全部变成大写,s8 的值 “HELLO!”
s9 := strings.Fields(" he llo ! ") // 按照空白字符将 “ he llo ! ” 分割成切片 slice,s9 的值 [he llo !]
s10 := strings.Split("hello!", "e") // 按照 “e” 将 “hello!” 分割成切片 slice,s10 的值 [h llo!]
Go 语言内置了 byte 和 rune 两种字符类型,因其实际就是 uint8 和 uint32 的别名,所以就不在单独讨论了,具体可参看上面整数型的相关内容。
错误类型是Go语言内置的一种专门用于输出错误信息的类型结构,涉及内容较多,放在后面再专门详解。
当我们需要知道一个值是什么数据类型时,该如何操作呢?先简单介绍一种在控制台终端直接打印输出的方法:
f := 3.1415926
fmt.Printf("%T", f) // 输出变量 f 的数据类型名称,不换行,结果:float64
fmt.Printf("%T\n", f) // 输出变量 f 的数据类型名称,换行,结果:float64
还有很多其他方法,在后面涉及时再继续实践。
.
.
上一页:Go/Golang语言学习实践[回顾]教程14–详解Go语言代码结构、包、作用域、变量、常量
下一页:Go/Golang语言学习实践[回顾]教程16–详解Go语言的各种引号及整数进制
.