学习目标:
熟悉Go基础数据类型及代码如何定义
学习内容
汇总
数据类型 | 分类 | 简介 |
---|---|---|
有符号整型 | int | 平台相关(32位系统上是32位,64位系统上是64位) |
int8 | 8位整型,范围 -128 到 127 | |
int16 | 16位整型,范围 -32768 到 32767 | |
int32 | 32位整型,范围 -2147483648 到 2147483647 | |
int64 | 范围 -9223372036854775808 到 9223372036854775807 | |
无符号整型 | uint | 平台相关(32位系统上是32位,64位系统上是64位) |
uint8 | 8位无符号整型,范围 0 到 255 | |
uint16 | 16位无符号整型,范围 0 到 65535 | |
uint32 | 32位无符号整型,范围 0 到 4294967295 | |
uint64 | 64位无符号整型,范围 0 到 18446744073709551615 | |
别名整型 | byte | 等同于uint8 |
rune | 等同于 int32,表示一个 Unicode 码点 | |
浮点型 | float32 | 32位浮点数 |
float64 | 64位浮点数 | |
复数型 | complex64 | 实部和虚部都是 float32 |
complex128 | 实部和虚部都是 float64 | |
布尔型 | bool | 布尔类型,值为 true 或 false |
字符串 | string | 字符串类型,表示 UTF-8 编码的文本 |
代码定义方式
go中针对任何类型的变量都有四种赋值方式,var声明类型、var声明类型并赋值、使用:=+类型来显示定义,以及直接使用:=来自动隐式赋值。如果只声明不赋值,则会根据其数据类型设置默认值,比如string是“”,int是0。
package main
func main() {
// 第一种方式
var a int8 = 127
// 第二种方式
var b int8
b = 127
// 第三种方式
c := int8(127)
// 不显示定义类型,则默认是int
d := 2143242
}
有符号整型
原码、反码、补码
为了更好的解释数据范围的来源,需要先了解一下操作系统中原码、反码、补码的概念。
因此,为了保证0表示的唯一,以及运算的方便,负数往往采用补码表示。
举例说明
+3:原码表示:0 0000011, 反码表示: 0 0000011, 补码表示: 0 0000011
-3: 原码表示:1 0000011, 反码表示: 1 1111100(正数原码取反得到), 补码表示: 1 1111101
因此 3+(-3) = 0000 0000(补码相加,各个位置相加即可,符号位进位需要舍弃)
数据范围
以int8举例,其在操作系统占8位,以二进制表示。 第一位为符号位,0代表正数,1表示负数。
因此其正数范围为 0 0000000 ~ 0 1111111 = 0~ 127。
因此其负数范围为 1 0000000 ~ 1 1111111 = -128 ~ - 1 (补码)
因此int8的数据范围为-128~127,同理可以推出其他几种数据范围
问题探讨
声明时如果超出数据范围
package main
func main() {
var a int8 = 127
var b int8 = 128 // 如果定义128,代码会报错,不给定义
}
赋值时超出数据范围
package main
import "fmt"
func main() {
a := 1234323432
fmt.Printf("%32b\n", a)
var b int8
b = int8(a)
fmt.Println(b)
}
推理:
a的二进制原码 = 1001001100100100100011111101000
后8位为11101000,此为补码,-1得到反码 11100111
然后再取反得到正数原码00011000 = 2*2*2 + 2*2*2*2 = 8+16 = 24
所以为b的值为
-24
不同类型的数值进行相加
package main
func main() {
var a = int8(32)
var b = int16(64)
c := a + b //不同类型不给运算,此处报错
}
无符号整型
与有符号整型概念基本一致,无法表示负数
别名整型
在Go语言中,byte 和 rune 是 uint8 和 int32 类型的别名,它们主要用于处理字符和字符串。这些别名的主要用途包括:
byte
byte 是 uint8 类型的别名,表示一个8位无符号整数。它主要用于处理原始字节数据。使用 byte 可以使代码更加明确,表明变量或数据是用来表示字节而不是一般的整数。
用途:
处理原始二进制数据:例如读取文件或网络数据时,通常会以字节为单位进行处理。
字符串和字节数组的转换:在Go中,字符串是不可变的字节序列,[]byte 类型可以用来表示和操作可变的字节数组。
package main
import (
"encoding/json"
"fmt"
)
func main() {
s := "abcdefg"
bytes, _ := json.Marshal(s)
fmt.Println(bytes)
}
------------------------运行结果-------------------
[34 97 98 99 100 101 102 103 34]
rune
rune 是 int32 类型的别名,表示一个32位有符号整数。它用来表示Unicode代码点。由于Unicode字符可能超出8位(即一个字节)的范围,所以需要用32位(即四个字节)来表示。
用途:
处理Unicode字符:当需要处理多语言或特殊字符时,使用 rune 可以确保每个字符都能被正确表示。
字符串的遍历:遍历字符串中的每个字符时,使用 rune 可以方便地处理各种Unicode字符,而不仅仅是ASCII字符。
package main
import (
"fmt"
)
func main() {
// 使用 rune
var r rune = '界'
fmt.Printf("rune: %c, %U, %d\n", r, r, r)
// 遍历字符串中的每个字符
str := "Hello, 世界"
for i, c := range str {
fmt.Printf("index: %d, char: %c, rune: %U\n", i, c, c)
}
}
---------------- 运行结果---------------------
byte: a, 97
rune: 界, U+754C, 30028
index: 0, char: H, rune: U+0048
index: 1, char: e, rune: U+0065
index: 2, char: l, rune: U+006C
index: 3, char: l, rune: U+006C
index: 4, char: o, rune: U+006F
index: 5, char: ,, rune: U+002C
index: 6, char: , rune: U+0020
index: 7, char: 世, rune: U+4E16
index: 10, char: 界, rune: U+754C
Unicode和UTF-8的区别
前面多次提到了这两个概念,那么他们之间有什么区别和联系呢?
Unicode是字符集而UTF-8是一种编解码规则。字符集为每一个字符分配一个独一无二的ID,而编码规则就是将字符集转换为字符。
总结
byte 用于表示和处理原始的字节数据,是 uint8 类型的别名。
rune 用于表示和处理Unicode字符,是 int32 类型的别名。
使用 byte 和 rune 可以使代码在处理字符和字节数据时更加清晰和安全。
字符串
结构体定义
Go语言中的字符串本质上是一个只读的字节数组(byte array),底层由以下结构体表示:
type StringHeader struct {
Data uintptr
Len int
}
这个结构体定义了字符串的指针和长度。
Data :字段是一个指向存储字符串实际内容的指针,类型为 uintptr,指向字节数组的起始位置。
Len :字段表示字符串的长度,即字符的个数(而不是字节的个数)。
不可变性
字符串在Go语言中是不可变的,一旦创建后就不能被修改。这意味着,对字符串进行操作(如连接、切片等)都会返回一个新的字符串,而不会修改原始字符串的内容。
字符串的底层实现原理
-
UTF-8 编码:
Go语言中的字符串使用UTF-8编码存储字符数据。UTF-8 是一种可变长度的编码方式,每个字符可以占用1到4个字节,最大支持Unicode字符集。 -
内存分配:
当创建一个新的字符串时,Go语言会在内存中分配足够的空间来存储字符串的内容,并且在需要时动态扩展或缩小内存空间。字符串的内存管理由Go的运行时系统来处理。 -
字符串切片:
字符串可以像切片一样进行切片操作,但切片操作返回的仍然是一个新的字符串,共享相同的底层字节数组。 -
字节切片转换:
可以通过类型转换将字符串转换为字节数组([]byte)进行直接的字节级操作。但是需要注意,如果修改了字节数组的内容,对原始字符串可能会造成意想不到的影响,因为字符串是不可变的。
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
str := "Hello, 世界"
// 获取字符串的底层数据结构
var sh reflect.StringHeader
sh = *(*reflect.StringHeader)(unsafe.Pointer(&str))
fmt.Printf("String data pointer: %v\n", sh.Data)
fmt.Printf("String length: %d\n", sh.Len)
// 尝试修改字符串(不可行,会导致编译错误)
// str[0] = 'h' // cannot assign to str[0]
// 字符串切片操作
substr := str[7:]
fmt.Println("Substring:", substr) // 输出:世界
// 修改原始字符串不会影响切片
// str = "Goodbye" // 这里会创建一个新的字符串,而不是修改原始字符串
// 字节切片转换
bytes := []byte(str)
fmt.Println("Bytes:", bytes)
}
---------------运行结果------------------
String data pointer: 4372623558
String length: 13
Substring: 世界
Bytes: [72 101 108 108 111 44 32 228 184 150 231 149 140]