Go语言的数据类型分为四类:
- 基本数据类型(包括数字、布尔以及字符串)
- 符合数据类型(包括数组和结构体,通过组合简单类型,来表达更加复杂的数据结构)
- 引用类型(包括指针、切片、管道、字典、函数,,虽然种类很多,但它们的共同之处在于,它们都是直接引用程序的变量或状态,因此,作用于一个引用的操作的效果会被该引用的所有副本观察到)
- 接口类型
整型
Go提供了有符号和无符号两种整型:
- 有符号位整型(int8,int16,int32,int64)
- 无符号整型(unit8,unit16,unit32,unit64)
- 两种特定与平台CPU的整型(int、unit,一般情况下,64位操作系统的int与unit对应的就是int64和unit64,32位操作系统类似,但是并不绝对,也与编译器相关)
- rune类型,他是一个Unicode码点,与int32类型等价
- byte类型,他与int8等价
- 无符号整数类型unitptr
算数运算、逻辑运算、比较运算的二元操作符的优先级:
-
- / % << >> & &^
-
-
- | ^
-
- == != < <= > >=
- &&
- ||
同他优先级的自左到右执行
括号可以提高优先级
PS:Go语言的%操作符与其他编程语言不太想死,它的结果的符号位,完全由被取模数的符号决定:
-5 % -2 = -1
-5 % 2 = -1
5 % -2 = 1
Go提供了位操作符:
- & 位运算 AND
- | 位运算 OR
- &^ 位清空 (AND NOT)
- << 左移
- >> 右移
浮点型
复数
布尔型
布尔型只有true和false
布尔值可以和&&或者||结合使用,并且可以有短路的行为
布尔值并不会隐式转换为0和1,反之亦然,我们需要使用条件判断来做转换:
var b bool = true
flag := 0
if b {
flag = 1;
}
字符串
一个字符串是一个不可变的字节序列,字符串可能包含任意数据,包括值为0的byte,但通常包含的是人类可读的文本。
文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列
s1 := "123"
s2 := "abc"
s3 := "中国"
len1 := len(s1)
len2 := len(s2)
len3 := len(s3)
//'中' 的二进制格式是 11100100 10111000 10101101
//1110 0100 = 228
//1011 1000 = 184
//1010 1101 = 173
fmt.Println(s3[0]," ",s3[1]," ",s3[2]," ",s3[3]," ",s3[4]," ",s3[5]) //228 184 173 229 155 189
fmt.Println("123的长度为 ; ",len1) //3
fmt.Println("abc的长度为 ; ",len2) //3
fmt.Println("中国的长度为 ; ",len3) //6
//字符串因为是序列,所以可以做切片
zhong := s3[0:3]
guo := s3[3:len3]
fmt.Println("我爱你 ",zhong,guo)
Go内置的len(T)方法返回的是字节数目,而非字符数目
字符串可以使用像==和<这样的比较操作符来进行比较,而比较则是逐个字节进行比较
字符串是不可变的,因此对于字符串的修改删除操作都是不允许的:
imu1 := "abcd"
//imu1[0] = 'L' //编译不过去
imu2 := imu1
fmt.Println(imu1[0])
imu1 += " efg"
fmt.Println("imu2 = ",imu2)
fmt.Println("imu1 = ",imu1)
字符串不可变的特性,使得两个字符串共享相同的底层数据也是安全的,复制任意长度的字符串的成本也很廉价。同样,字符串可以其切片共享相同的数据,这是安全的。
因为Go源文件总是以UTF8编码的,而且Go的文本字符串也被解释为UTF8,因此我们可以在字符串的字面量中包含Unicode码点.
字符串字面量中不仅可以使用转移字符,而且通过使用十六进制或者八进制,还可以包含任意的byte(字节),使用方法如下:
byte_str_16 := "\x11"
byte_str_16 := "\x48" // \xhh 表示16进制的字节,h是一个16进制数字,大小写随意
fmt.Println(byte_str_16) //H
byte_str_8 := "\110" //八进制比较简单,直接一斜杠起始,后面跟着三个8进制数字,但是需要注意的是,这三个数字不能大于\377,因为\377的八进制为011 111 111,而这就是一个字节的最大值了,即十进制的255
fmt.Println(byte_str_8)
原生字符串字面量以`来替换掉”,需要注意的是,原生字符串中的转移字符是无效的,内容全部作为字面量,包括反斜杠和换行,所以原生字符串可能会占用多行。唯一需要处理是删除回车,这样字符串的值在所有平台上都是相同的,包括像windows这样传统地将回车放在文本文件中的平台。
原生字符串字面量的作用一般用于正则表达式或者HTML模板、JSON字面量、命令展示行以及其他需要扩展到多行的场景。
Unicode
Unicode收集了世界上所有的字符,包括音符以及特别神秘的字符,并为每一个字符分配一个唯一的数字,我们称这个数字为Unicode码点,在Go语言中,也可以成为rune。
我们可以讲一个rune序列一int32序列的形式展示出来,但是带来的问题就是,每一个字符都占用4个字节(因为rune、int32格式占用的内存空间就是4个字节)
UTF-8
UTF-8将Unicode代码点编码为可变长度的字节。
UTF-8以1~4个字节来编码所有的Unicode码点。ASCII部分以一个字节表示,绝大多数的字符一2~3个字节表示。对码点(也可以说是rune)所编码首个字节的高位bit位标识了该编码占用几个字节。方法如下;
- 如果首位为0,那么表示该编码占用1个字节,首位后面的7个bit位用于编码ASCII
- 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
每个使用UTF-8存储的字符,除了第一个字节外,其余字节的头两个比特都是以”10”开始,使文字处理器能够较快地找出每个字符的开始位置。
在UTF-8文件的开首,很多时都放置一个U+FEFF字符(UTF-8以EF,BB,BF代表),以显示这个文本文件是以UTF-8编码(了解即可)
UTF-8的这些特质,保证了一个字符的字节序列不会包含在另一个字符的字节序列中。这确保了以字节为基础的部分字符串比对(sub-string match)方法可以适用于在文字中搜索字或词。有些比较旧的可变长度8位编码(如Shift JIS)没有这个特质,故字符串比对的算法变得相当复杂。虽然这增加了UTF-8编码的字符串的信息冗余,但是利多于弊。另外,数据压缩并非Unicode的目的,所以不可混为一谈。即使在发送过程中有部分字节因错误或干扰而完全丢失,还是有可能在下一个字符的起点重新同步,令受损范围受到限制.
虽然变长的字节编码使得我们无法通过索引来实现访问第n个字符这样的需求,但是他却带来很多其他的优点:
- Backward compatibility(向后兼容):完全的兼容ASCII
- Self-synchronization(自同步):
- Prefix code(前缀编码):字符编码的第一个字节表示了该字符所占的字节个数,从流中读取数据可以立即对每个接收到的序列进行解码,而无需首先等待下一个序列的第一个字节或流结束指示。多字节序列的长度很容易由人类决定,因为它只是前导字节中高阶bit位为1的数目。如果流在序列中间结束,不正确的字符将不会被解码。
- Fallback and auto-detection(回退和自动检测):
- Sorting order:通过对相应的字节序列进行排序,可以按照Unicode码点顺序对UTF-8字符串列表进行排序。
这里没有内嵌的NUL(ZERO)字节,这对于使用NUL作为终止字符串的编程语言来说很方便。
rune字面量可以使用Unicode转义:
//首先,中字的Unicode16进制码点是4E2D
//当我们需要转义的码点是2字节的话,那么我们使用\u + 2字节码点
//当我们需要转义的码点是3个字节的话,那么我们需要使用\U + 4字节码点
//PS:码点需找工具查,不要使用UTF8编码直接作为码点,因为UTF8中有前缀编码,
// 所以不要直接使用,当然如果理解了UTF8编码规则的话,那么就可以从其中找到其Unicode码点
rune1 := '中'
var rune2 = '\u4E2D'
对于无法解析的码点,UTF8会以’\uFFFD’来替换,一般显示出来的话就是一个令牌,令牌内有个?
对UTF-8编码的字符串应用[]rune()转换,会返回该字符串所对应编码的码点序列:
code_point_str := "123贾宝玉"
//应用转换
code_point_array := []rune(code_point_str)
fmt.Printf("%x\n", code_point_array)
code_point_str = string(code_point_array)
fmt.Println(code_point_str)
输出:
[31 32 33 8d3e 5b9d 7389]
123贾宝玉
通过输出确实可以看到,[]rune是以码点来分解的字符串
字符串与切片
字符串是不可变的字节序列,而字节切片却是可以修改的:
str := "贾宝玉"
slices := []byte(str)
fmt.Println(slices)
newer_str := string(slices)
fmt.Println(newer_str)
输出
[228 184 173 229 155 189]
中国
//从输出可以看出,[]byte是讲字符串的UTF8编码拆分为字节
//[]byte(s)转换分配了一个新的字节数字来持有字符串s的字节的拷贝,然后生成一个切片来引用层的字节数组。但是在某些情况下,编译器优化会在某些情况下能够避免分配和复制,但这就需要保证对于slices的修改,不会影响到底层的字符串str
//我们可以通过string(byte[])来讲切片转换为不可变的字符串
为了不免转换中不要的内存分配,bytes包和strings包提供可很多使用方法:
//strings包
func Contains(s, substr string) bool
func Count(s, sep string) int
func Fields(s string) []string
func HasPrefix(s, prefix string) bool
func Index(s, sep string) int
func Join(a []string, sep string) string
//bytes包
func Contains(b, subslice []byte) bool
func Count(s, sep []byte) int
func Fields(s []byte) [][]byte
func HasPrefix(s, prefix []byte) bool
func Index(s, sep []byte) int
func Join(s [][]byte, sep []byte) []byte
bytes包中有一个Buffer类型,用于字节切片的缓存。
初始的Buffer是空的,随着像其中添加字符串、字节、字节数组等,数据会慢慢变大
Buffer是不需要初始化的,对于它,零值也是有意义的:
var buffer bytes.Buffer
buffer.WriteByte('A')
buffer.WriteRune('中')
buf := buffer.String()
fmt.Println(buf)//A中
可以通过WriteRune和wirteByte来向其中写入字符串、字节
字符串与数值之间的转换
数值 -> 字符串
方法一:
num := 10
itoa := strconv.Itoa(num)
方法二
num := 10
ss := fmt.Sprintf("%d", num)
数值二进制字符串转换:
方法一
num := 10
fmt.Println(strconv.FormatInt(int64(num), 2)) //1010 对应的10进制值正是10
方法二:
bb := fmt.Sprintf("%b",int64(10))
//这里看得出来,Springf的强大,通过%b,可以转为2进制字节形式,还有更多的%o和%x需要我们来探讨
字符串 -> 数值
方法一:
demo := "123"
nums,err := strconv.Atoi(demo)
if err != nil {
fmt.Println(err)
}
fmt.Println(nums)
方法二:
//参数:
//第一个参数填写的是字符串
// base = 0,则字符串中自己加进制,如Ox123,那么转换后就是291,它是16进制,08转换后为8,它是八进制
// base = 16,则字符串按照16进制来转换,
// base = 8, 则字符串按照8进制来转换
// base = 10, 则字符串按照10进制来转换
//第三个参数表示的是转换出的int的类型,值值可以写0,8,16,32,64,对应int,int8,int16,int32,int64
parseInt, err := strconv.ParseInt(demo, 10, 64)
fmt.Scanf()可以用于解析字符串与数字都在一行的情况
字符串 -> []byte
//将UTF-8的每一位转为字节
bs := []byte("哈哈")
常量
格式:
const 常量标识符 = 常量值
多常量声明:
const(
常量标识符 = 常量值
常量标识符 = 常量值
)
所有的常量都在编译期间完成.
对于多常量的声明,如果声明的多个常量的值是一致的,那么除了第一个外其它的常量右边的初始化表达式都可以省略,具体如下:
const (
num1 = 10
num2
num3 = 20
num4
)
fmt.Println(num2) //10
fmt.Println(num4) //20
iota 常量生成器
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
iota的初始值是0,然后逐步自增1,那么到Saturday时候,它的值就是6了
无类型常量
Go支持无明确类型的为常量,并且编译器会为这些无明确类型的常量提供更高的精度的算数运算。
这里有六种无明确数据类型常量类型:
- 无类型的布尔
- 无类型的整数
- 无类型的字符(rune)
- 无类型的浮点数
- 无类型的复数
- 无类型的字符串
无类型的常量不仅可以提高运算精度,而且可以直接用于更多的表达式而不需要类型转换。
const Pi64 float64 = math.Pi
fmt.Println(Pi64)
var x float32 = float32(Pi64)
fmt.Println(x)
var y float64 = Pi64
fmt.Println(y)
var z complex128 = complex128(Pi64)
fmt.Println(z)
3.141592653589793
3.1415927
3.141592653589793
(3.141592653589793+0i)
常量字面量的不同书写,对应着不同的类型。0, 0.0, 0i,和 ‘\u0000’字面量都对应着相同的值,但是却是不同的类型,分别是无类型整数,无类型浮点数,无类型复数,无类型字符(rune),相似的,true/false对应这无类型布尔,而字符串则对应着无类型字符串
只有常量才是无类型的,当将常量赋值给有类型的变量,无类型的值通常被隐式转为变量对应的类型:
var f float64 = 3 + 0i // untyped complex -> float64
f = 2 // untyped integer -> float64
f = 1e123 // untyped floating-point -> float64
f = 'a' // untyped rune -> float64
等同于
var f float64 = float64(3 + 0i)
f = float64(2)
f = float64(1e123)
f = float64('a')
对于没有显式类型的变量声明语法(包括短变量声明),无类型的常量会被隐式转换为默认的数据类型:
- 无类型的整数 -> int
- 无类型的布尔 -> bool
- 无类型的浮点 -> float64
- 无类型的复数 -> complex128
i := 0 // untyped integer; implicit int(0)
r := '\000' // untyped rune; implicit rune('\000')
f := 0.0 // untyped floating-point; implicit float64(0.0)
c := 0i // untyped complex; implicit complex128(0i)
fmt.Printf("%T\n", 0) // "int"
fmt.Printf("%T\n", 0.0) // "float64"
fmt.Printf("%T\n", 0i) // "complex128"
fmt.Printf("%T\n", '\000') // "int32" (rune)
如果将常量赋值给不同于默认类型的数据类型,方法如下:
方法一:
const no = 10
var num8 = int8(no)
方法二:
const no = 10
var num16 int16 = no