布尔类型#
Go语言中的布尔类型与其他语言基本一致,关键字也为 bool 。布尔型数据只有 true / false 两个值。
var a1 = false
var a2 = true
fmt.Println(a1, a2)注意:
- 布尔类型变量的默认值为
false - Go 语言中不允许将整型强制转换为布尔型
- 布尔型无法参与数值运算,也无法与其他类型进行转换
复数#
复数有实部和虚部,complex64 的实部和虚部为32位,complex128 的实部和虚部为64位,用的比较少不做详细记录。
var c1 complex64
c1 = 1 + 2i
var c2 complex128
c2 = 2 + 3i
fmt.Println(c1)
fmt.Println(c2)整型#
整型分为以下两个大类:有符号整形按长度分为:int8、int16、int32、int64,对应的无符号整型:uint8、uint16、uint32、uint64
| 类型 | 范围 | 占用空间(字节) |
|---|---|---|
int8 | (-128 到 127) -2^7 到 2^7-1 | 1 Byte |
int16 | (-32768 到 32767) -2^15 到 2^15-1 | 2 Byte |
int32 | (-2147483648 到 2147483647) -2^31 到 2^31-1 | 4 Byte |
int64 | (-9223372036854775808 到 9223372036854775807) -2^63 到 2^63-1 | 8 Byte |
uint8 | (0 到 255) 0 到 2^8-1 | 1 Byte |
uint16 | (0 到 65535) 0 到 2^16-1 | 2 Byte |
uint32 | (0 到 4294967295) 0 到 2^32-1 | 4 Byte |
uint64 | (0 到 18446744073709551615) 0 到 2^64-1 | 8 Byte |
特殊整型#
| 类型 | 描述 |
|---|---|
uint | 32 位操作系统上就是 uint32,64 位操作系统上就是 uint64 |
int | 32 位操作系统上就是 int32,64 位操作系统上就是 int64 |
uintptr | 无符号整型,用于存放一个指针 |
注意: 在使用 int 和 uint 类型时,不能假定它是 32 位或 64 位的整型,而是考虑 int 和 uint 可能在不同平台上的差异
字节(Byte)#
字节也叫 Byte,是计算机数据的基本存储单位。1Byte(字节)=8bit(位) 1024Byte(字节)=1KB。中文字符在 unicode 下占2个字节,在 utf-8 编码下占3个字节,go 默认编码是 utf-8。
unsafe.Sizeof(n1) 是 unsafe包的一个函数,可以返回变量占用的字节数。
package main
import (
"fmt"
"unsafe"
)
func main() {
var a int8 = 120
fmt.Printf("%T\n", a)
fmt.Println(unsafe.Sizeof(a))
}输出:
PS D:\GoProject> go run .\test.go
int8
1数字字面量语法#
Go1.13 版本之后引入了数字字面量语法,便于以二进制、八进制或十六进制浮点数的格式定义数字,例如:
func numberLiteralsSyntax() {
// 代表二进制的 101101,相当于十进制的 45
v1 := 0b00101101
fmt.Printf("value:%v type:%T \n", v1, v1)
// 代表八进制的377,相当于十进制的 255
v2 := 0o377
fmt.Printf("value:%v type:%T \n", v2, v2)
// 代表十六进制的 1 除以 2²,也就是 0.25
v3 := 0x1p-2
fmt.Printf("value:%v type:%T \n", v3, v3)
// 使用“_”分隔数字
v4 := 123_456
fmt.Printf("value:%v type:%T \n", v4, v4)
}借助 fmt 函数以不同进制形式显示整数
func convertOutput() {
// 十进制
var a int = 10
fmt.Printf("十进制:%d 二进制:%b \n", a, a) // %d表示十进制,%b表示二进制
// 八进制以0开头
var b int = 077
fmt.Printf("八进制:%o 二进制:%d\n", b, b) // %o表示八进制,%d表示十进制
// 十六进制 以 0x 开头
var c int = 0xff
fmt.Printf("十六进制:%x 十六进制大写:%X 十进制:%d 变量类型:%T \n", c, c, c, c) //%x 表示十六进制,%X 表示大写的十六进制,%d 表示十进制,%T 查看变量类型
// int不同长度转换
var num1 int8 = 127
num2 := int32(num1)
fmt.Printf("value:%v type:%T \n", num2, num2)
}不同长度转换#
package main
import (
"fmt"
)
func main() {
var num1 int8
num1 = 127
num2 := int32(num1)
fmt.Printf("值:%v 类型%T", num2, num2)
}浮点型#
Go 语言支持两种浮点型数:float32 和 float64。
这两种浮点型数据格式遵循 IEEE 754 标准
float32最大范围约为3.4e38,可以使用常量定义:math.MaxFloat32float64最大范围约为1.8e308,可以使用常量定义:math.MaxFloat64
打印浮点数时可以使用 fmt 包配合动词 %f,比如:
var f float64 = 3.1415926
fmt.Printf("%f\n", f) // 默认保留6位小数
fmt.Printf("%.2f\n", f) // 保留2位小数
fmt.Printf("值:%v,类型:%T \n", f, f) // 浮点数默认类型是float64
fmt.Println(math.MaxFloat32) // 输出float32最大值
fmt.Println(math.MaxFloat64) // 输出float64最大值
// 科学计数法表示浮点类型
n1 := 5.1234e2
n2 := 5.1234e2
n3 := 5.1234e-2
fmt.Println("n1=", n1, "n2=", n2, "n3=", n3) 精度丢失问题#
精度丢失是几乎所有的编程语言都有的问题,是典型的二进制浮点数精度损失问题。在定长条件下,二进制小数和十进制小数互转可能有精度丢失,Go也不例外,观察以下代码:
func floatPrecisionLossTest() {
d := 1129.6
fmt.Println(d * 100) // 此处结果应该是:112960,实际打印的却是:112959.99999999999
m1 := 8.2
fmt.Printf("值:%v--类型:%T \n", m1, m1)
m2 := 3.8
fmt.Printf("值:%v--类型:%T \n", m2, m2)
fmt.Println(m1 - m2) // 此处结果应该是:4.4,实际打印:4.3999999999999995
}解决精度丢失问题#
使用第三方包需要使用 go mod ,关于 go mod 参考之前的章节。引入第三方包解决经度丢失问题:
PS D:\GoProject\src> go get github.com/shopspring/decimal
go: downloading github.com/shopspring/decimal v1.3.1
go get: added github.com/shopspring/decimal v1.3.1import (
"github.com/shopspring/decimal"
)/*
使用第三方包解决float精度丢失问题:go get github.com/shopspring/decimal
文档:https://pkg.go.dev/github.com/shopspring/decimal#section-readme
*/
func floatPrecisionLossSolveTest() {
a := decimal.NewFromFloat(1129.6)
b := decimal.NewFromInt(100)
fmt.Println(a.Mul(b)) // output:112960
c := decimal.NewFromFloat(8.2)
d := decimal.NewFromFloat(3.8)
fmt.Println(c.Sub(d)) // output:4.4
// 初始化一个变量
d0 := decimal.NewFromFloat(0)
// 设置精度 为三位 四舍五入的精度
decimal.DivisionPrecision = 3
fmt.Println(d0)
// 加法 Add
var f1 float64 = 2.1
var i1 int = 3
fmt.Println(decimal.NewFromFloat(f1).Add(decimal.NewFromFloat(float64(i1)))) // 2.1 + 3: float和int相加=>output: "5.1"
var f2 float64 = 2.1
var f3 float64 = 3.1
fmt.Println(decimal.NewFromFloat(f2).Add(decimal.NewFromFloat(f3))) // 2.1 + 3.1 (float和float相加)=>output: "5.2"
var f4 float64 = 2
var f5 float64 = 3
fmt.Println(decimal.NewFromFloat(f4).Add(decimal.NewFromFloat(f5))) // 2 + 3 (int和int相加 可以直接相加 d1 = num1+num2)=> output: "5"
// 减法 Sub
var f7 float64 = 3.1
var f8 int = 2
d1 := decimal.NewFromFloat(f7).Sub(decimal.NewFromFloat(float64(f8))) // 3.1 - 2 float和int相减 => output: "1.1"
fmt.Println(d1)
var n1 float64 = 2.1
var n2 float64 = 3.1
fmt.Println(decimal.NewFromFloat(n1).Sub(decimal.NewFromFloat(n2))) // 2.1 - 3.1 (float和float相减)=>output: "-1"
var n3 int = 2
var n4 int = 3
fmt.Println(decimal.NewFromFloat(float64(n3)).Sub(decimal.NewFromFloat(float64(n4)))) // 2 - 3 (int和int相减 d1 = num1 - num2) => output: "-1"
// 乘法 Mul
var n5 float64 = 3.1
var n6 int = 2
fmt.Println(decimal.NewFromFloat(n5).Mul(decimal.NewFromFloat(float64(n6)))) // 3.1 * 2 float和int相乘 => output: "6.2"
var n7 float64 = 2.1
var n8 float64 = 3.1
fmt.Println(decimal.NewFromFloat(n7).Mul(decimal.NewFromFloat(n8))) // 2.1 * 3.1 (float和float相乘) => output: "6.51"
var n9 int = 2
var n10 int = 3
fmt.Println(decimal.NewFromFloat(float64(n9)).Mul(decimal.NewFromFloat(float64(n10)))) // 2 * 3 int和int相乘(d1 = num1 * num2) => output: "6"
// 除法 Div
var n11 int = 2
var n12 int = 3
fmt.Println(decimal.NewFromFloat(float64(n11)).Div(decimal.NewFromFloat(float64(n12)))) // 2 / 3 = 0.6666666666666667 => output: "0.6666666666666667"
// float64 和 int 相除
var num13 float64 = 2.1
var num14 int = 3
fmt.Println(decimal.NewFromFloat(num13).Div(decimal.NewFromFloat(float64(num14)))) // output: "0.7"
// float64 和 float64 相除
var num15 float64 = 2.1
var num16 float64 = 0.3
fmt.Println(decimal.NewFromFloat(num15).Div(decimal.NewFromFloat(num16))) // output: "7"
// int 和 int 相除 并保持3位精度
var num17 int = 2
var num18 int = 3
decimal.DivisionPrecision = 3
result := decimal.NewFromFloat(float64(num17)).Div(decimal.NewFromFloat(float64(num18))) // 2/3 = 0.667 => output: "0.667"
fmt.Println(result)
}科学计数法表示#
n1 := 5.1234e2
n2 := 5.1234e2
n3 := 5.1234e-2
fmt.Println("n1=", n1, "n2=", n2, "n3=", n3)字符串#
Go 语言中,一个字符串是一个不可改变的字节序列。字符串可以包含任意的数据,包括byte值0,但是通常用来保存人类可读的文本。
Go的字符串是由 Utf-8 编码之后的字节序列,所以不能修改。
- 1个汉字占用3个字节
- 1个英文字母占用1个字节
Go使用 rune(1个int32类型的数字) 表示中文字符,使用 byte(1个uint8类型的数字)表示英文。Go语言中组成字符串的最小单位是字符,存储的最小单位是字节,字符串本身不支持修改。字节是数据存储的最小单元,每个字节的数据都可以用整数表示,例如一个字节储存的字符 a,实际存储的是 97 而非字符的字形,将这个实际存储的内容用数字表示的类型,称之为 byte。
len#
内置的 len 函数用于返回一个字符串中的字节数目(不是 rune 字符数目),索引操作 s[i] 返回第 i 个字节的字节值,i 必须满足 0 ≤ i< len(s) 条件约束。
s := "hello, world"
fmt.Println(len(s)) // "12"
fmt.Println(s[0], s[7]) // "104 119" ('h' and 'w')如果试图访问超出字符串索引范围的字节将会导致 panic 异常:
c := s[len(s)] // panic: index out of range第 i 个字节并不一定是字符串的第 i 个字符,因为对于非 ASCII 字符的 UTF8 编码会要两个或多个字节。
比如:
s := "abc北京"
fmt.Printf("字节长度:%d \n", len(s)) //output:9
// 返回字符串每个字节的值
for i := 0; i < len(s); i++ {
fmt.Println(s[i])
}输出:
字节长度:9
97 // a
98 // b
99 // c
229 // 北
140 // 北
151 // 北
228 // 京
186 // 京
172 // 京英文字母占用一个字节:97:a/98:b/99:c,汉字占用三个字节:229/140/151组成北,228/186/172组成京
如果要获取字符串中字符的个数,需要先把字符串转换成 []rune 类型,然后用 len() 函数获取字符个数:
r := []rune(s)
fmt.Print(len(r)) // output:5字符串不可变性#
字符串的值是不可变的:一个字符串包含的字节序列永远不会被改变,当然也可以给一个字符串变量分配一个新字符串值。可以像下面这样将一个字符串追加到另一个字符串:
s := "left foot"
t := s
s += ", right foot"这并不会导致原始的字符串值被改变,但是变量 s 将因为 += 语句持有一个新的字符串值,但是 t 依然是包含原先的字符串值。
fmt.Println(s) // "left foot, right foot"
fmt.Println(t) // "left foot"因为字符串是不可修改的,因此尝试修改字符串内部数据的操作也是被禁止的:
s[0] = 'L' // compile error: cannot assign to s[0]或者使用 Sprintf :不会直接打印而是生成一个新的字符串
var result = fmt.Sprintf("%s%s", q1, q2)
fmt.Println(result)字符串字面量#
Golang 支持两种类型的字符串字面量:
- 非解释型表示法,需用用反引号"`“把字符序列包起来
- 解释型表示法,需要用双引号"“包裹字符序列
区别在于:前者表示所见即所得的(除了回车符)。后者所表示的值中转义符会起作用
// 字符串字面量语法:可以定义一个多行字符串
var s1 string = "hello"
var s2 string = `hello
world`
`字符串转义符#
所谓的解释型字符串就是用双引号括起来的字符串(”"),其中的转义字符会被替换掉,这些转义字符包括:
\a // 响铃
\b // 退格
\f // 换页
\n // 换行
\r // 回车
\t // 制表符
\u // Unicode 字符
\v // 垂直制表符
\" // 双引号
\\ // 反斜杠Go语言字符编码#
Go 语言采用的字符编码方案从属于 Unicode 编码规范。Go 语言的代码正是由 Unicode 字符组成的,且源码文件必须使用 UTF-8 编码格式进行存储。如果源码文件中出现了非 UTF-8 编码的字符,那么在构建、安装以及运行的时候,go 命令会报错 illegal UTF-8 encoding 。当一个 string 类型的值被转换为 []rune 类型值的时候,其中的字符串会被拆分成一个个的 Unicode 字符。
截取字符串#
可以通过下面的语法截取字符串中的内容:
str := "abcdefg"
t1 := str[1:4] //startIndex=1,endIndex=4此时 t1 的内容为 bcd,该语法通过下标索引的方式截取字符串中的内容,特点是 “左含右不含”。也就是说新的子串包含源串索引为 1 的字符,而不包含源串索引为 4 的字符。
如果要从源串的开始处截取可以省略第一个索引:
t2 := str[:4] //省略第一个索引从0开始,startIndex=0,endIndex=4t2 的内容为 “abcd”。
如果要从源串的某个位置开始一直截取到末尾,可以省略第二个索引:
t3 := str[1:] //省略第二个索引,从1开始截取到末尾t3 的内容为 “cdef”
访问越界问题:如果试图访问超出字符串索引范围的字节将会在运行时导致 panic 异常:
t4 := str[:10]runtime error: slice bounds out of range [:10] with length 7 数组越界
遍历字符串#
可以通过下标索引字符串中的字节,所以可以用这种方式遍历字符串:
p := "abc你好"
for i := 0; i < len(p); i++ {
fmt.Printf("%c", p[i])
}输出:
abcä% å¥%可见在字符串中含有非单字节的字符时这种方法是不正确的。range 函数解决这个问题:
for _, v := range s {
fmt.Printf("%c", v)
}输出:
abc你好带有 range 子句的 for 语句会先把被遍历的字符串值拆成一个字节序列,然后再试图找出这个字节序列中包含的每一个 UTF-8 编码值,或者说每一个 Unicode 字符。因此在 range for 语句中,赋给第二个变量的值是UTF-8 编码值代表的那个 Unicode 字符,类型是 rune
字符串分割#
url := "www.baidu.com"
strArray := strings.Split(url, ".")
fmt.Println(strArray)字符串 Join#
fmt.Println(strings.Join(strArray, "."))前缀判断#
fmt.Println(strings.HasPrefix(url, "www"))后缀判断#
fmt.Println(strings.HasSuffix(url, "com"))修改字符串#
Golang 中不能修改字符串的内容,就是说不能通过 s[i] 这种方式修改字符串中的字符。要修改字符串的内容,可以先将字符串的内容复制到一个可写的变量中,一般是 []byte 或 []rune 类型的变量,然后再进行修改。
注意:如果要对字符串中的字节进行修改,就转换为 []byte 类型,如果要对字符串中的字符修改,就转换为 []rune 类型,转换类型的过程中会自动复制数据
修改字符串中的字节([]byte)
对于单字节字符来说可以用这种方式进行修改:
ss := "hello world"
value := []byte(ss) // 转换为[]byte
value[5] = ',' // 将空格替换为“,”
fmt.Printf("%s\n", ss)
fmt.Printf("%s\n", value)输出:
hello world
hello,world修改字符串中的字符([]rune)
sss := "一梦三两年"
value2 := []rune(sss) // 转换为[]rune
value2[2] = '四'
value2[3] = '五'
fmt.Println(sss)
fmt.Println(string(value2))输出:
一梦三两年
一梦四五年注意:string 类型的 0 值是长度为 0 的字符串,即空字符串 "”
strconv 包#
除了字符串、字符、字节之间的转换,字符串和数值之间的转换也比较常见。由 strconv 包提供这类转换功能。
将一个整数转为字符串,一种方法是用 fmt.Sprintf 返回一个格式化的字符串;另一个方法是用strconv.Itoa() :
x := 123
y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv.Itoa(x)) // "123 123" “整数到ASCII”FormatInt 和 FormatUint 函数可以用不同的进制来格式化数字:
fmt.Println(strconv.FormatInt(int64(x), 2)) // "1111011"fmt.Printf 函数的 %b 、%d、%o 和%x 等参数提供功能往往比 strconv 包的 Format 函数方便很多,特别是在需要包含有附加额外信息的时候:
s := fmt.Sprintf("x=%b", x) // "x=1111011"如果要将一个字符串解析为整数,可以使用 strconv 包的Atoi或ParseInt函数,还有用于解析无符号整数的ParseUint 函数:
x, err := strconv.Atoi("123") // x is an int
y, err := strconv.ParseInt("123", 10, 64) // base 10, up to 64 bitsParseInt 函数的第三个参数是用于指定整型数的大小;例如16表示int16,0则表示int。在任何情况下,返回的结果y总是int64类型,可以通过强制类型转换将它转为更小的整数类型。
有时候也会使用 fmt.Scanf 来解析输入的字符串和数字,特别是当字符串和数字混合在一行的时候,它可以灵活处理不完整或不规则的输入。
字符类型#
在Go语言中支持两个字符类型,一个是byte(实际上是uint8的别名),代表UTF-8字符串的单个字节的值;另一个是rune,代表单个Unicode字符。
一个 string 类型的值既可以被拆分为一个包含多个字符的序列,也可以被拆分为一个包含多个字节的序列。
- 前者由以
rune为元素类型的切片表示 - 后者由以
byte为元素类型的切片代表
rune是Go语言特有的一个基本数据类型,它的一个值就代表一个字符,即:一个Unicode 字符(就是一个中文字符,占3个字节)
看以下代码:
s := "helloworld 世界你好"
fmt.Printf("string:%q\n", s) // 原文格式输出
fmt.Printf("rune(char):%q\n", []rune(s)) // 输出[]rune切片
fmt.Printf("rune(hex):%x\n", []rune(s)) // 采用16进制数表示
fmt.Printf("bytes(hex):% x\n", []byte(s)) // 输出[]byte切片输出:
string:"helloworld 世界你好"
rune(char):['h' 'e' 'l' 'l' 'o' 'w' 'o' 'r' 'l' 'd' ' ' '世' '界' '你' '好']
rune(hex):[68 65 6c 6c 6f 77 6f 72 6c 64 20 4e16 754c 4f60 597d]
bytes(hex):68 65 6c 6c 6f 77 6f 72 6c 64 20 e4 b8 96 e7 95 8c e4 bd a0 e5 a5 bd结果分析:
- 第二行输出说明:字符串在被转换为
[]rune类型的值时每个字符都会成为一个独立的rune类型的元素值。 - 由于每个
rune底层的值都是采用UTF-8编码值来表达的,所以第三行采用16进制数来表示上述字符串,每一个16进制的字符分别表示一个字符,可以看到,当遇到中文字符时,由于底层存储需要更大的空间,所以使用的16进制数字也比较大,比如4e16和754c分别代表世和界。 - 如果将整个字符的
UTF-8编码值都拆成字节序列时,就变成了第四行的输出,可以看到一个中文字符底层占用了三个byte,比如e4 b8 96/e7 95 8c分别对应UFT-8编码值的4e16和754c,也就是中文字符中的世和界。
总结: string 类型的值会由若干个 Unicode 字符组成,每个 Unicode 字符都可以由一个 rune 类型的值来承载。这些字符在底层都会被转换为 UTF-8 编码值,而这些 UTF-8 编码值又会以字节序列的形式表达和存储。所以:一个 string 类型的值在底层就是一个能够表达若干个 UTF-8 编码值的字节序列。