一、整型
1. 整型介绍
Go 语言中,整型可以分为以下几类:
类型 | 描述 |
---|---|
uint8 | 无符号 8位整型 (0 到 255) |
uint16 | 无符号 16位整型 (0 到 65535) |
uint32 | 无符号 32位整型 (0 到 4294967295) |
uint64 | 无符号 64位整型 (0 到 18446744073709551615) |
int8 | 有符号 8位整型 (-128 到 127) |
int16 | 有符号 16位整型 (-32768 到 32767) |
int32 | 有符号 32位整型 (-2147483648 到 2147483647) |
int64 | 有符号 64位整型 (-9223372036854775808 到 9223372036854775807) |
此外,还有一些特殊整型:
类型 | 描述 |
---|---|
uint | 32位操作系统上就是uint32 ,64位操作系统上就是uint64 |
int | 32位操作系统上就是int32 ,64位操作系统上就是int64 |
uintptr | 无符号整型,用于存放一个指针 |
int 型是计算最快的一种类型。实际使用中,切片或 map 的元素数量等都可以用int
来表示。在涉及到二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用int
和 uint
。
package main
import "fmt"
func main() {
num1 := 123456
fmt.Printf("%T\n", num1) // int
var num int8
//num = 111111111 // .\main.go:7:6: constant 111111111 overflows int8 赋值长度超过了8位
fmt.Println(num)
}
查看各类整型类型的表数范围:
package main
import (
"fmt"
"unsafe"
"math"
)
func main() {
var i8 int8
var i16 int16
var i32 int32
var i64 int64
var ui8 uint8
var ui16 uint16
var ui32 uint32
var ui64 uint64
fmt.Printf("%T %dB %v~%v\n", i8, unsafe.Sizeof(i8), math.MinInt8, math.MaxInt8) // int8 1B -128~127
fmt.Printf("%T %dB %v~%v\n", i16, unsafe.Sizeof(i16), math.MinInt16, math.MaxInt16) // int16 2B -32768~32767
fmt.Printf("%T %dB %v~%v\n", i32, unsafe.Sizeof(i32), math.MinInt32, math.MaxInt32) // int32 4B -2147483648~2147483647
fmt.Printf("%T %dB %v~%v\n", i64, unsafe.Sizeof(i64), math.MinInt64, math.MaxInt64) // int64 8B -9223372036854775808~9223372036854775807
fmt.Printf("%T %dB %v~%v\n", ui8, unsafe.Sizeof(ui8), 0, math.MaxUint8) // uint8 1B 0~255
fmt.Printf("%T %dB %v~%v\n", ui16, unsafe.Sizeof(ui16), 0, math.MaxUint16) // uint16 2B 0~65535
fmt.Printf("%T %dB %v~%v\n", ui32, unsafe.Sizeof(ui32), 0, math.MaxUint32) // uint32 4B 0~4294967295
fmt.Printf("%T %dB %v~%v\n", ui64, unsafe.Sizeof(ui64), 0, uint64(math.MaxUint64)) // uint64 8B 0~18446744073709551615
var ui uint
ui = uint(math.MaxUint64) //再+1会导致overflows错误
fmt.Printf("%T %dB %v~%v\n", ui, unsafe.Sizeof(ui), 0, ui) // uint 8B 0~18446744073709551615
var iMax, iMin int
iMax = int(math.MaxInt64) //再+1会导致overflows错误
iMin = int(math.MinInt64) //再-1会导致overflows错误
fmt.Printf("%T %dB %v~%v\n", iMax, unsafe.Sizeof(iMax), iMin, iMax) // int 8B -9223372036854775808~9223372036854775807
}
2. 数字字面量语法
Go1.13 版本之后引入了数字字面量语法,这样便于开发者以二进制、八进制或十六进制浮点数的格式定义数字,还允许我们用 _
来分隔数字:
package main
import "fmt"
func main() {
num1 := 123_456
fmt.Println(num1) // 123456
// 定义一个二进制数
num2 := 0b00101101
fmt.Println(num2) // 45 --> 0b00101101用十进制表示是45
// 定义一个八进制数
num3 := 0377
fmt.Println(num3) // 255 --> 0377用十进制表示是255
// 定义一个十六进制数
num4 := 0xff
fmt.Println(num4) // 255 --> 0xff用十进制表示是255
}
我们也可以使用 fmt
函数来将一个整数以不同进制形式展示:
package main
import "fmt"
func main() {
a := 10
fmt.Printf("%b \n", a) // 1010 占位符%b表示二进制
fmt.Printf("%o \n", a) // 12 占位符%o表示八进制
fmt.Printf("%d \n", a) // 10 占位符%o表示八进制
fmt.Printf("%x \n", a) // a 占位符%x表示十六进制小写
fmt.Printf("%X \n", a) // A 占位符%X表示十六进制大写
}
二、浮点数类型
Go语言支持两种浮点型数:float32
和float64
。这两种浮点型数据格式遵循IEEE 754
标准。
类型 | 描述 |
---|---|
float32 | -3.4028234663852886e+38 ~ 3.4028234663852886e+38 |
float64 | -1.7976931348623157e+308 ~ 1.7976931348623157e+308 |
package main
import (
"fmt"
"unsafe"
"math"
)
func main() {
var f32 float32
var f64 float64
fmt.Printf("%T %dB %v~%v\n", f32, unsafe.Sizeof(f32), -math.MaxFloat32, math.MaxFloat32) // float32 4B -3.4028234663852886e+38~3.4028234663852886e+38
fmt.Printf("%T %dB %v~%v\n", f64, unsafe.Sizeof(f64), -math.MaxFloat64, math.MaxFloat64) // float64 8B -1.7976931348623157e+308~1.7976931348623157e+308
}
打印浮点数时,可以使用fmt
包配合动词 %f
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("%f\n", math.Pi) // 3.141593
fmt.Printf("%.2f\n", math.Pi) // 3.14
}
三、复数类型
复数类型有 complex64
和 complex128
两种类型,complex64
的实部和虚部为32位,complex128
的实部和虚部为64位。
package main
import "fmt"
func main() {
var c1 complex64
c1 = 1 + 2i
var c2 complex128
c2 = 2 + 3i
fmt.Println(c1) // (1+2i)
fmt.Println(c2) // (2+3i)
}
四、布尔类型
Go语言中以bool
类型进行声明布尔型数据,布尔型数据只有true(真)
和false(假)
两个值。布尔类型经常用在条件判断语句,循环语句和逻辑表达式中,使用布尔类型时需要注意:
-
布尔类型变量的默认值为
false
。 -
Go 语言中不允许将整型强制转换为布尔型,即不能使用0和非0表示真假。
-
布尔型无法参与数值运算,也无法与其他类型进行转换。
package main
import "fmt"
func main() {
// 初始化布尔类似变量
var b1 bool = true
var b2 bool = false
var b3 = true
var b4 = false
b5 := true
b6 := false
fmt.Printf("b1: %v\n", b1) // b1: true
fmt.Printf("b2: %v\n", b2) // b2: false
fmt.Printf("b3: %v\n", b3) // b3: true
fmt.Printf("b4: %v\n", b4) // b4: false
fmt.Printf("b5: %v\n", b5) // b5: true
fmt.Printf("b6: %v\n", b6) // b6: false
// 布尔值用在条件判断中
age := 18
ok := age >= 18
if ok {
fmt.Println("你已经成年")
} else {
fmt.Println("你还未成年")
}
// 布尔值用在循环语句中
count := 10
for i := 0; i < count; i++ {
fmt.Printf("i: %v\n", i)
}
// 布尔值用在逻辑表达式中
age1 := 18
gender := "男"
if age1 >= 18 && gender == "男" {
fmt.Println("你是成年男子")
}
}
五、字符串类型
Go 语言中的字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int
、bool
、float32
、float64
等)一样。 Go 语言里的字符串的内部实现使用UTF-8
编码。
1. 定义字符串
package main
import "fmt"
func main() {
// 初始化当行字符串
str1 := "hello \nworld!"
// 初始化多行字符串
str2 := `hello\n
world`
fmt.Println(str1)
fmt.Println(str2)
}
定义单行字符串使用英文下的双引号包裹,字符串中如果使用到转义字符会生效;定义一个多行字符串使用反引号包裹,但是所有的转义字符均无效,文本将会原样输出。
2. 字符串常用方法
2.1 字符串长度
str1 := "abcdefg"
fmt.Println(len(str1)) // 7
2.2 拼接字符串
- 方式一,使用加号
name := "cdc"
age := "18"
msg1 := name + " " + age
fmt.Printf("msg1: %v\n", msg1) // cdc 18
msg2 := ""
msg2 += name
msg2 += " "
msg2 += age
fmt.Printf("msg2: %v\n", msg2) // cdc 18
golang 中字符串类型都是不可变的,每次运算都会产生一个新的字符串,所以会产生很多临时的无用的字符串,不仅没有用,还会给 gc 带来额外的负担,所以性能比较差,不推荐该方式。
- 方式二,使用
fmt.Sprintf()
方法
name := "cdc"
age := "18"
msg := fmt.Sprintf("%s %s", name, age)
fmt.Printf("msg: %v\n", msg)
内部使用 []byte
实现,不像直接运算符这种会产生很多临时的字符串,但是内部的逻辑比较复杂,有很多额外的判断,还用到了 interface
,所以性能也不是很好。
- 方式三,使用
strings.Join()
方法
msg := strings.Join([]string{name, age}, " ")
fmt.Printf("msg: %v\n", msg)
Join
会先根据字符串数组的内容,计算出一个拼接之后的长度,然后申请对应大小的内存,一个一个字符串填入,在已有一个数组的情况下,这种效率会很高,但是本来没有,去构造这个数据的代价也不小。
- 方式四,使用
buffer.WriteString()
方法
package main
import (
"bytes"
"fmt"
)
func main() {
var buffer bytes.Buffer
buffer.WriteString("cdc")
buffer.WriteString(" ")
buffer.WriteString("18")
fmt.Printf("msg: %v\n", buffer.String())
}
推荐该方式,可以当成可变字符使用,对内存的增长也有优化。
2.3 字符串分割
str2 := "how do you do"
splitRet := strings.Split(str2, "do")
fmt.Printf("%v -- %T -- %d\n", splitRet, splitRet, len(splitRet)) // [how you ] -- []string -- 3
2.4 判断是否包含
str2 := "how do you do"
fmt.Println(strings.Contains(str2, "do")) // true
fmt.Println(strings.Contains(str2, "hehe")) // false
2.5 判断前/后缀
str2 := "how do you do"
fmt.Println(strings.HasPrefix(str2, "how")) // true 判断前缀是否为how
fmt.Println(strings.HasPrefix(str2, "do")) // false
fmt.Println(strings.HasSuffix(str2, "how")) // false 判断后缀是否为how
fmt.Println(strings.HasSuffix(str2, "do")) // true
2.6 字串出现位置
str2 := "how do you do"
fmt.Println(strings.Index(str2, "do")) // 4
fmt.Println(strings.LastIndex(str2, "do")) // 11
2.7 字符串切片
// 通过索引切片,范围左闭右开
fmt.Println(str2[:5]) // how d --> 从开头切到指定索引
fmt.Println(str2[3:6]) // do --> 从指定开始索引位置切到指定结束索引位置
fmt.Println(str2[2:]) // w do you do --> 从指定索引位置切到字符串最后
fmt.Println(str2[:]) // how do you do --> 从开始切到最后
2.8 格式化输出
Go 语言中输出字符串时可以使用各种占位符来输出不同类型的值:
// 普通占位符
%v 相应值的默认格式。在打印结构体时,“加号”标记(%+v)会添加字段名
%#v 相应值的Go语法表示
%T 相应值的类型的Go语法表示
%% 字面上的百分号,并非值的占位符
// 布尔占位符
%t 单词 true 或 false
// 整数占位符
%b 二进制表示
%c 相应Unicode码点所表示的字符
%d 十进制表示
%o 八进制表示
%q 单引号围绕的字符字面值,由Go语法安全地转义
%x 十六进制表示,字母形式为小写 a-f
%X 十六进制表示,字母形式为大写 A-F
%U Unicode格式:U+1234,等同于 "U+%04X"
// 浮点数和复数的组成部分
%b 无小数部分的,指数为二的幂的科学计数法,与 strconv.FormatFloat的 'b' 转换格式一致。例如 -123456p-78
%e 科学计数法,例如 -1234.456e+78
%E 科学计数法,例如 -1234.456E+78
%f 有小数点而无指数,例如 123.456
%g 根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出
%G 根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出
// 字符串与字节切片
%s 输出字符串表示(string类型或[]byte)
%q 双引号围绕的字符串,由Go语法安全地转义
%x 十六进制,小写字母,每字节两个字符
%X 十六进制,大写字母,每字节两个字符
// 指针
%p 十六进制表示,前缀 0x
package main
import "fmt"
func main() {
// 普通占位符
type website struct {
url string
}
web := website{url: "www.chendacheng.com"}
name := "cdc"
fmt.Printf("%v\n", name) // cdc
fmt.Printf("%+v\n", web) // {url:www.chendacheng.com}
fmt.Printf("%#v\n", web) // main.website{url:"www.chendacheng.com"}
fmt.Printf("%T\n", name) // string
// 布尔占位符
fmt.Printf("%t\n", true) //输出值的 true 或 false
// 整数占位符
fmt.Printf("%b\n", 100) //二进制表示 1100100
fmt.Printf("%c\n", 1111) //数值对应的 Unicode 编码字符 ї
fmt.Printf("%d\n", 10) //十进制表示 10
fmt.Printf("%o\n", 8) //八进制表示 10
fmt.Printf("%q\n", 22) //转化为十六进制并附上单引号 '\x16'
fmt.Printf("%x\n", 1223) //十六进制表示,用a-f表示 4c7
fmt.Printf("%X\n", 1223) //十六进制表示,用A-F表示 4C7
fmt.Printf("%U\n", 1024) //Unicode表示 U+0400
fmt.Printf("%s\n", "wqdew") //直接输出字符串或者[]byte wqdew
fmt.Printf("%q\n", "dedede") //双引号括起来的字符串 "dedede"
fmt.Printf("%x\n", "abczxc") //每个字节用两字节十六进制表示,a-f表示 6162637a7863
fmt.Printf("%X\n", "asdzxc") //每个字节用两字节十六进制表示,A-F表示 6173647A7863
// 浮点数和复数的组成部分
fmt.Printf("%b\n", 12.34) //无小数部分,两位指数的科学计数法6946802425218990p-49
fmt.Printf("%e\n", 12.345) //科学计数法,e表示 1.234500e+01
fmt.Printf("%E\n", 12.34455) //科学计数法,E表示 1.234455E+01
fmt.Printf("%f\n", 12.3456) //有小数部分,无指数部分 12.345600
fmt.Printf("%g\n", 12.3456) //根据实际情况采用%e或%f输出 12.3456
fmt.Printf("%G\n", 12.3456) //根据实际情况采用%E或%f输出 12.3456
// 指针占位符
fmt.Printf("%T\n", &name) //*string 字符串指针
fmt.Printf("%p\n", &name) //0xc000042270 指针地址
}
六、byte和rune类型
1. 字节类型简介
组成每个字符串的元素叫做字符,可以通过遍历或者单个获取字符串元素获得字符。 字符用英文单引号包裹。
package main
import "fmt"
func main() {
var a = '华'
var b = 'a'
fmt.Printf("a: %v,%c\n", a, a) // a: 21326,华
fmt.Printf("b: %v,%c\n", b, b) // b: 97,a
}
Go 语言的字符有以下两种:
uint8
类型,或者叫 byte 型,代表一个ASCII码
字符。rune
类型,代表一个UTF-8字符
。
当需要处理中文、日文或者其他复合字符时,则需要用到rune
类型。rune
类型实际是一个int32
。Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode 的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾。
package main
import "fmt"
func main() {
s := "hello南京"
for i := 0; i < len(s); i++ { //byte
fmt.Printf("%v(%c) ", s[i], s[i])
}
fmt.Println()
for _, r := range s { //rune
fmt.Printf("%v(%c) ", r, r)
}
}
两种遍历字符串的输出结果如下:
104(h) 101(e) 108(l) 108(l) 111(o) 229(å) 141() 151(—) 228(ä) 186(º) 172(¬)
104(h) 101(e) 108(l) 108(l) 111(o) 21335(南) 20140(京)
因为UTF8编码下一个中文汉字由3~4个字节组成,所以我们不能简单的按照字节去遍历一个包含中文的字符串,否则就会出现上面输出中第一行的结果。建议使用for + range
方式遍历字符串。
2. 修改字符串
字符串底层是一个byte
数组,且字符串的长度是byte字节的长度,所以字符串类型可以和 []byte
类型相互转换。rune
类型用来表示utf8字符,一个 rune
字符由一个或多个 byte
组成。虽然字符串是不可变的,但是我们可以先将其转换成[]rune
或[]byte
,完成后再转换为string
。无论哪种转换,都会重新分配内存,并复制字节数组,即不会修改原始字符串,而是生成一个新的修改过后的字符串。
package main
import "fmt"
func main() {
s1 := "big"
// 强制类型转换
byteS1 := []byte(s1)
byteS1[0] = 'p'
fmt.Println(string(byteS1)) // pig
s2 := "白萝卜"
runeS2 := []rune(s2)
runeS2[0] = '红'
fmt.Println(string(runeS2)) // 红萝卜
}
七、强制类型转换
package main
import "fmt"
func main() {
num := 12
numFloat := float32(num)
fmt.Printf("%T -- %v\n", numFloat, numFloat) // float32 -- 12
a := 'a'
aStr := string(a)
fmt.Printf("%T -- %v\n", aStr, aStr) // string -- a
}