文章目录
数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。
Go 语言按类别有以下几种数据类型:
类型 | 描述 |
---|---|
布尔型 | 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。 |
数字类型 | 整型 int 和浮点型 float,Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码。 |
字符串类型 | 字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。Go语言的字符串的字节使用UTF-8编码标识Unicode文本。 |
派生类型 | 包括:(a) 指针类型(Pointer) (b) 数组类型 © 结构体类型(struct) (d) 联合体类型 (union) (e) 函数类型 (f) 切片类型 (g) 接口类型(interface) (h) Map 类型 (i) Channel 类型 |
一、go 语言布尔类型
一个布尔类型的值只有两种:true
或 false
。if
和 for
语句的条件部分都是布尔类型的值,并且==
和<
等比较操作也会产生布尔型的值。
一元操作符!
对应逻辑非操作,因此!true的值为 false,更复杂一些的写法是(!true==false) ==true
,实际开发中我们应尽量采用比较简洁的布尔表达式,就像用 x 来表示x==true
。
package main
import "fmt"
func main() {
var num = 10
if num == 5 {
fmt.Println("num == 5")
} else {
fmt.Println(num)
}
if num == 10 {
fmt.Println("num == 10")
} else {
fmt.Println(num)
}
if num != 5 {
fmt.Println("num != 5")
} else {
fmt.Println(num)
}
if num != 10 {
fmt.Println("num != 10")
} else {
fmt.Println(num)
}
}
// 结果如下
10
num == 10
num != 5
10
Go语言对于值之间的比较有非常严格的限制,只有两个相同类型的值才可以进行比较,如果值的类型是接口(interface),那么它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个值可以不是常量,但是类型必须和该常量类型相同。如果以上条件都不满足,则必须将其中一个值的类型转换为和另外一个值的类型相同之后才可以进行比较。
布尔值可以和 &&(AND)
和 ||(OR)
操作符结合,并且有短路行为,如果运算符左边的值已经可以确定整个布尔表达式的值,那么运算符右边的值将不再被求值,因此下面的表达式总是安全的:
短路行为:当有多个表达式(值)时,左边的表达式值可以确定结果时,就不再继续运算右边的表达式的值。
操作符 | 短路描述 |
---|---|
&& | 当左边为True,还需要运算右边,结果由右边决定;当左边为Flase,不需要右边进行运算,结果为Flase |
|| | 当左边为True,不需要运算右边,结果为True;当左边为Flase,需要计算右边,结果由右边决定 |
package main
import "fmt"
func main() {
var s1 string = "nihao"
// if s1 == "nihao" && s1[0] == 'n' { //s1 符合要求
// if s1 == "nihao" && s1[0] == 'x' { //s1 不符合要求
// if s1 == "nihao" && s1[0] == "n" { // invalid operation: s1[0] == "n" (mismatched types byte and untyped string)
// if s1 != "" && s1[0] == 'x' { // s1 不符合要求
// if s1 != "" && s1[0] == 'n' { // s1 符合要求
// if s1 == "nihao" || s1[0] == 'n' { //s1 符合要求
// if s1 == "nihao" || s1[0] == 'x' { // s1 符合要求
// if s1 != "" || s1[0] == 'x' { // s1 符合要求
if s1 != "" || s1[0] == 'n' { // s1 符合要求
fmt.Println("s1 符合要求")
} else {
fmt.Println("s1 不符合要求")
}
}
其中 s[0] 操作如果应用于空字符串将会导致 panic 异常(终端程序的异常)。
因为&&的优先级比||高(&& 对应逻辑乘法,|| 对应逻辑加法,乘法比加法优先级要高),所以下面的布尔表达式可以不加小括号:
if 'a' <= c && c <= 'z' ||
'A' <= c && c <= 'Z' ||
'0' <= c && c <= '9' {
// ...ASCII字母或数字...
}
布尔值并不会隐式转换为数字值 0 或 1,反之亦然,必须使用 if 语句显式的进行转换:
i := 0
if b {
i = 1
}
如果需要经常做类似的转换,可以将转换的代码封装成一个函数,如下所示:
// 如果b为真,btoi返回1;如果为假,btoi返回0
func btoi(b bool) int {
if b {
return 1
}
return 0
}
数字到布尔型的逆转换非常简单,不过为了保持对称,我们也可以封装一个函数:
// itob报告是否为非零。
func itob(i int) bool { return i != 0 }
Go语言中不允许将整型强制转换为布尔型,代码如下:
var n bool
fmt.Println(int(n) * 2)
编译错误,输出如下:
cannot convert n (type bool) to type int
布尔型无法参与数值运算,也无法与其他类型进行转换。
二、go 语言数字类型
Go语言的数值类型分为以下几种:整数、浮点数、复数,其中每一种都包含了不同大小的数值类型,例如有符号整数包含 int8、int16、int32、int64 等,每种数值类型都决定了对应的大小范围和是否支持正负符号。
2.1、Go语言整型(整数类型)
Go语言同时提供了有符号和无符号的整数类型,其中包括 int8、int16、int32 和 int64
四种大小截然不同的有符号整数类型,分别对应 8、16、32、64
bit(二进制位)大小的有符号整数,与此对应的是 uint8、uint16、uint32 和 uint64
四种无符号整数类型。
此外还有两种整数类型 int 和 uint,它们分别对应特定 CPU 平台的字长(机器字大小),其中 int 表示有符号整数,应用最为广泛,uint 表示无符号整数。实际开发中由于编译器和计算机硬件的不同,int 和 uint 所能表示的整数大小会在 32bit 或 64bit 之间变化。
大多数情况下,我们只需要 int 一种整型即可,它可以用于循环计数器(for 循环中控制循环次数的变量)、数组和切片的索引,以及任何通用目的的整型运算符,通常 int 类型的处理速度也是最快的。
用来表示 Unicode 字符的 rune
类型和 int32 类型是等价的,通常用于表示一个 Unicode 码点。这两个名称可以互换使用。同样,byte 和 uint8 也是等价类型,byte 类型一般用于强调数值是一个原始的数据而不是一个小的整数。
最后,还有一种无符号的整数类型 uintptr
,它没有指定具体的 bit 大小但是足以容纳指针。uintptr 类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。
尽管在某些特定的运行环境下 int、uint 和 uintptr 的大小可能相等,但是它们依然是不同的类型,比如 int 和 int32,虽然 int 类型的大小也可能是 32 bit,但是在需要把 int 类型当做 int32 类型使用的时候必须显示的对类型进行转换,反之亦然。
Go语言中有符号整数采用 2 的补码形式表示,也就是最高 bit 位用来表示符号位,一个 n-bit 的有符号数的取值范围是从 -2(n-1) 到 2(n-1)-1
。无符号整数的所有 bit 位都用于表示非负数,取值范围是 0 到 2n-1
。例如,int8 类型整数的取值范围是从 -128 到 127
,而 uint8 类型整数的取值范围是从 0 到 255
。
哪些情况下使用 int 和 uint
程序逻辑对整型范围没有特殊需求。例如,对象的长度使用内建 len()
函数返回,这个长度可以根据不同平台的字节长度进行变化。实际使用中,切片或 map 的元素数量等都可以用 int 来表示。
反之,在二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用 int 和 uint。
2.2、Go语言浮点类型(小数类型)
Go语言提供了两种精度的浮点数 float32
和 float64
,它们的算术规范由 IEEE754
浮点数国际标准定义,该浮点数规范被所有现代的 CPU 支持。
这些浮点数类型的取值范围可以从很微小到很巨大。浮点数取值范围的极限值可以在 math 包中找到:
- 常量
math.MaxFloat32
表示float32
能取到的最大数值,大约是3.4e38
; - 常量
math.MaxFloat64
表示float64
能取到的最大数值,大约是1.8e308
; float32
和float64
能表示的最小值分别为1.4e-45
和4.9e-324
。
一个 float32 类型的浮点数可以提供大约 6 个十进制数的精度,而 float64 则可以提供约 15 个十进制数的精度,通常应该优先使用 float64 类型,因为 float32 类型的累计计算误差很容易扩散,并且 float32 能精确表示的正整数并不是很大。如下:
package main
import "fmt"
func main() {
var f float32 = 16777216 // 1 << 24
var f1 float64 = 16777216
fmt.Println(f) // 1.6777216e+07
fmt.Println(f1) // 1.6777216e+07
fmt.Println(f +1) // 1.6777216e+07
fmt.Println(f == f+1) // true
fmt.Println(f1 + 1) // 1.6777217e+07
fmt.Println(f1 == f1 + 1) // false
}
浮点数在声明的时候可以只写整数部分或者小数部分,像下面这样:
const e = .71828
const f = 1.
fmt.Println(e) // 0.71828
fmt.Println(f) // 1
很小或很大的数最好用科学计数法书写,通过 e 或 E 来指定指数部分:
const Avogadro = 6.02214129e23 // 阿伏伽德罗常数
const Planck = 6.62606957e-34 // 普朗克常数
用 Printf 函数打印浮点数时可以使用%f
来控制保留几位小数
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("%f\n", math.Pi) // 3.141593
fmt.Printf("%.2f\n", math.Pi) // 3.14
}
2.3、Go语言复数
在计算机中,复数是由两个浮点数表示的,其中一个表示实部(real),一个表示虚部(imag)。
Go语言中复数的类型有两种,分别是 complex128(64 位实数和虚数)和 complex64(32 位实数和虚数),其中 complex128 为复数的默认类型。
复数的值由三部分组成 RE + IMi
,其中 RE 是实数部分,IM 是虚数部分,RE 和 IM 均为 float 类型,而最后的 i 是虚数单位。
声明复数的语法格式如下所示:
var name complex128 = complex(x, y)
其中 name 为复数的变量名,complex128 为复数的类型,"="后面的 complex 为Go语言的内置函数用于为复数赋值,x、y 分别表示构成该复数的两个 float64 类型的数值,x 为实部,y 为虚部。
上面的声明语句也可以简写为下面的形式:
name := complex(x, y)
对于一个复数z := complex(x, y)
,可以通过Go语言的内置函数real(z)
来获得该复数的实部,也就是 x;通过imag(z)
获得该复数的虚部,也就是 y。
使用内置的 complex 函数构建复数,并使用 real 和 imag 函数返回复数的实部和虚部:
var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y) // "(-5+10i)"
fmt.Println(real(x*y)) // "-5"
fmt.Println(imag(x*y)) // "10"
复数也可以用==和!=进行相等比较,只有两个复数的实部和虚部都相等的时候它们才是相等的。
Go语言内置的 math/cmplx
包中提供了很多操作复数的公共方法,实际操作中建议大家使用复数默认的 complex128 类型,因为这些内置的包中都使用 complex128 类型作为参数。
复数运算法则:https://baike.baidu.com/item/%E5%A4%8D%E6%95%B0%E8%BF%90%E7%AE%97%E6%B3%95%E5%88%99/2568041?fr=aladdin
三、Go语言字符串
一个字符串是一个不可改变的字节序列,字符串可以包含任意的数据,但是通常是用来包含可读的文本,字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码表上的字符时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)。
UTF-8 是一种被广泛使用的编码格式,是文本文件的标准编码,其中包括 XML 和 JSON 在内也都使用该编码。由于该编码对占用字节长度的不定性,在Go语言中字符串也可能根据需要占用 1 至 4 个字节,这与其它编程语言如 C++、Java 或者 Python 不同(Java 始终使用 2 个字节)。Go语言这样做不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码。
字符串是一种值类型,且值不可变,即创建某个文本后将无法再次修改这个文本的内容,更深入地讲,字符串是字节的定长数组。
3.1、定义字符串
可以使用双引号""
来定义字符串,字符串中可以使用转义字符来实现换行、缩进等效果,常用的转义字符包括:
- \n:换行符
- \r:回车符
- \t:tab 键
- \u 或 \U:Unicode 字符
- \:反斜杠自身
如下:
package main
import "fmt"
func main() {
var s string = "hello Jack!"
var s1 string = "hello \nJack!"
var s2 string = "hello \rJack!"
var s3 string = "hello \tJack!"
var s4 string = "hello \\Jack!"
fmt.Println(s)
fmt.Println(s1)
fmt.Println(s2)
fmt.Println(s3)
fmt.Println(s4)
}
// 结果如下:
hello Jack!
hello
Jack!
Jack!
hello Jack!
hello \Jack!
一般的比较运算符(==、!=、<、<=、>=、>
)是通过在内存中按字节比较来实现字符串比较的,因此比较的结果是字符串自然编码的顺序。字符串所占的字节长度可以通过函数 len() 来获取,例如 len(str)。
var s string = "hello Jack!"
var s1 string = "hello \nJack!"
fmt.Println(len(s)) // 11
fmt.Println(len(s1)) // 12
字符串的内容(纯字节)可以通过标准索引法来获取,在方括号[ ]
内写入索引,索引从 0 开始计数:
- 字符串 str 的第 1 个字节:str[0]
- 第 i 个字节:str[i - 1]
- 最后 1 个字节:str[len(str)-1]
需要注意的是,这种转换方案只对纯 ASCII 码的字符串有效。
注意:获取字符串中某个字节的地址属于非法行为,例如 &str[i]。
var s string = "hello Jack!"
fmt.Println(s[3]) //108
fmt.Println(s[4]) //111
3.2、字符串拼接符+
两个字符串 s1 和 s2 可以通过 s := s1 + s2
拼接在一起。将 s2 追加到 s1 尾部并生成一个新的字符串 s。
可以通过下面的方式来对代码中多行的字符串进行拼接:
str := "Beginning of the string " +
"second part of the string"
fmt.Println(str01)
// 因为编译器会在行尾自动补全分号,所以拼接字符串用的加号“+”必须放在第一行末尾
也可以使用+=
来对字符串进行拼接:
str02 := "hel" + "lo"
fmt.Println(str02)
str02 += " world"
fmt.Println(str02)
3.3、定义多行字符串
在Go语言中,使用双引号书写字符串的方式是字符串常见表达方式之一,被称为字符串字面量(string literal),这种双引号字面量不能跨行,如果想要在源码中嵌入一个多行字符串时,就必须使用`反引号,代码如下:
str_more := `first line
second line
`
fmt.Println(str_more)
first line
second line
在这种方式下,反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。
在` 间的所有代码均不会被编译器识别,而只是作为字符串的一部分。