Go语言中,数据类型用于声明函数与变量、常量的数据的类型。
Go语言的基本数据类型包括整型、浮点型、字符与字符串、布尔型等,另外,还要了解数据类型之间的转换方法、指针与类型别名的使用等。
var value1 int32 //全局声明
func main() {
value2 := 64 //函数内部声明
}
有符号整数类型 |
无符号整数类型 |
int8(8bit) |
unit8 |
int16(16bit) |
uint16 |
int32 (32bit) |
uint32 |
int64 (64bit) |
uint64 |
Go语言中,除了指定的整型之外,还有int(符号整数)和uint(无符号整数)两种整数类型。在实际开发中,由于编译器和计算机硬件的不同,所以,它们的字节长度也是有所差异的,int和uint所能表示的整数大小在32bit或64bit之间变化。
另外,还有一种无符号的整数类型uintptr,它没有指定具体的bit大小,但是足以容纳指针。uintptr类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。
使用int和uint的情况如下:
整型的位运算符:
Go语言提供了两种精度的浮点数,即float32和float64:
在使用浮点型时需要注意以下几点:
var a := 20.00
const e = .71828 //0.71828
const f = 1. //1
用Printf函数打印浮点数时可以使用“%f”来控制保留几位小数,例如:
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("%f\n", math.Pi) // 按照默认宽度和精度输出整型
fmt.Printf("%.2f\n", math.Pi) //按照默认 宽度 、2位精度输出(小数点后的位数)
}
字符串是不可改变的字节序列,字符串可以包含任意数据,但是通常包含可读的文本。字符串是UTF-8字符的一个序列(当字符为ASCII码表上的字符时则占用1B,其他字符根据需要占用2~4B)。
UTF-8是一种被广泛使用的编码格式,是文本文件的标准编码,其中包括XML和JSON在内也都使用该编码。由于该编码对占用字节长度的不定性,在Go语言中字符串也可能根据需要占用1~4B,这与其他编程语言如C++、Java或Python不同(Java始终使用2B)。Go语言这样做不仅减少了内存和硬盘空间占用,同时也不用像其他语言那样需要对使用UTF-8字符集的文本进行编码和解码。
字符串中的每一个元素称为“字符”,在遍历或者单个获取字符串元素时可以获得字符。
Go语言的字符有以下两种:
在Go语言中,字符串的常见表达方式之一就是使用双引号书写字符串的方式,这种方式被称为字符串字面量(string literal),使用该方式时需要注意双引号字面量不能跨行。如果想要在源码中嵌入一个多行字符串,就必须使用“`”反引号,代码如下:
func main() {
const str = `第一行
第二行
第三行
\r\n
`
fmt.Println(str)
}
在Go语言中可以使用“+”连接字符串
func main() {
s := "adcd 你"
fmt.Println(s[4:] + "好")
str := "哎一古," +
"金社长"
fmt.Println(str)
}
另外,字符串还可以使用“==”和“<”等符号进行比较,通过比较逐字节的编码获取结果
func compare() {
a := "你"
b := "好"
if a < b {
fmt.Println(a[0], b[0])
fmt.Println(a[1], b[1])
fmt.Println(a[2], b[2])
}
c := "a"
d := "b"
if c < d {
fmt.Println(c[0], "小于", d[0])
}
}
在Go语言中,字符串的内容不能修改,这就意味着不能使用a[i]这种方式修改字符串中的UTF-8编码,如果确实要修改,那么可以将字符串的内容复制到另一个可写的变量中,然后进行修改。一般使用[]byte或[]rune类型。
如果要对字符串中的字节进行修改,则转换为[]byte格式,如果要对字符串中的字符进行修改,则转换为[]rune格式,转换过程会自动复制数据。
修改字符串中的字节(使用[]byte):
func mod_byte() {
a := "Hello 世界!"
b := []byte(a) // 转换为[]byte,自动复制数据
b[5] = ',' // 修改[]byte
fmt.Printf("%s\n", a) // a 不能被修改,内容保持不变
fmt.Printf("%s\n", b) // 修改后的数据
}
修改字符串中的字符(使用[]rune):
func mod_character() {
a := "Hello 世界!"
b := []rune(a) // 转换为[]rune,自动复制数据
b[6] = '中' // 修改[]rune
b[7] = '国' // 修改[]rune
fmt.Println(a) // a不能被修改,内容保持不变
fmt.Println(string(b)) //转换为字符串,右一次复制数据
}
在Go语言中,单个字符可以使用单引号(')来创建,字符串支持切片操作。但是需要注意的是,如果字符串都是由ASCII字符组成的,可以随便使用切片进行操作;如果字符串中包含其他非ASCII字符,直接使用切片获取想要的单个字符时应十分小心,因为对字符串直接使用切片时是通过字节进行索引的,而非ASCII字符在内存中可能不是由1B组成的。如果想对字符串中的字符依次访问,可以使用range操作符。2
布尔型的常量和变量也可以通过逻辑运算符(非!、和&&、或||)结合来产生另外一个布尔值,这样的逻辑语句就其本身而言,并不是一个完整的Go语言语句。
逻辑值可以被用于条件结构中的条件语句,以便测试某个条件是否满足。另外,和(&&)、或(||)、相等(==)、不等(!=)属于二元运算符,而非(!)属于一元运算符。这里使用T代表条件符合的语句,用F代表条件不符合的语句。
非运算符用于取得和布尔值相反的结果。
!T -> false
!F -> true
只有当两边的值都为true时,和运算符的结果才是true。
T && T-> true
T && F-> false
F && T-> false
F && F->false
只有当两边的值都为false时,或运算符的结果才是false,其中任意一边的值为true就能够使得该表达式的结果为true。
T || T -> true
T || F -> true
F || T -> true
F || F -> false
在Go语言中,&&和||是具有快捷性质的运算符,当运算符左边表达式的值已经能够决定整个表达式的值时(&&左边的值为false,||左边的值为true),运算符在右边的表达式将不会被执行。利用这个性质,如果有多个条件判断,应当将计算过程较为复杂的表达式放在运算符的右侧,以减少不必要的运算。
另外,利用括号可以提升某个表达式的运算优先级。在格式化输出时,可以使用%t来表示要输出的值为布尔型。
在必要及可行的情况下,一个类型的值可以被转换成另一种类型的值。类型转换用于将一种数据类型的变量转换为另外一种类型的变量。
Go语言类型转换的基本格式如下:type_name(expression)
a := 5.0
b := int(a)
类型转换只能在定义正确的情况下转换成功:
func main() {
// 输出个数值范围
fmt.Println("int8 range:", math.MinInt8, math.MaxInt8)
fmt.Println("int16 range:", math.MinInt16, math.MaxInt16)
fmt.Println("int32 range:", math.MinInt32, math.MaxInt32)
fmt.Println("int64 range:", math.MinInt64, math.MaxInt64)
// 初始化一个32位整型值
var a int32 = 1047483647
// 输出变量的十六进制形式和十进制值
fmt.Printf("int32: 0x%x %d\n", a, a)
// 将a变量数值转换为16进制,发生数值阶段
b := int16(a)
// 输出变量的十六进制形式和十进制值
fmt.Printf("int16: 0x%x %d\n", b, b)
// 将常量保存为float32类型
var c float32 = math.Pi
// 转换为int类型,浮点发生精度丢失
fmt.Println(int(c))
}
根据运行结果,16位有符号整型的范围为-32 768~32 767,而变量a的值1 047 483 647不在这个范围内。1 047 483 647对应的十六进制为0x3e6f54ff,转为int16类型后,长度缩短一半,也就是在十六进制上砍掉一半,变成0x54ff,对应的十进制值为21759。
类型别名是Go 1.9版本中新增的功能,主要用于解决代码升级、迁移中存在的类型兼容性问题。
在Go 1.9版本之前定义内建类型的代码书写方式如下:
type byte uint8
type rune int32
在Go 1.9版本之后定义内建类型的代码书写方式修改为如下:
type byte = uint8
type rune = int32
定义类型别名的书写格式如下:type TypeAlias = Type
类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。
type name = string //类型别名
type name string //类型声明
type name string
将name定义为一个新的类型,该类型拥有和string一样的特性,但是两者是不同的类型,不可用“+”进行拼接等运算。type name = string
将name定义为string的一个别名,使用name和string相同。二者可以当作同一种类型运算。别名只在源码中存在,编译完成后,不会有别名类型。类型别名与类型定义表面上看只有一个等号的差异,那么它们之间实际的区别有哪些:
// 将NewInt定义为int类型
type NewInt int
// 给int取一个别名 IntAlias
type IntAlias = int
func main() {
// 将 a 声明为 NewInt 类型
var a NewInt
// 查看 a 的类型名
fmt.Printf("a type: %T\n", a)
// 将 a2 声明为 IntAlias 类型
var a2 IntAlias
// 查看 a2 的类型名
fmt.Printf("a2 type: %T\n", a2)
}
能够随意为各种类型起名字,是否意味着可以在自己的包中为这些类型任意添加方法呢?
// 定义time.Duration的别名为MyDuration
type MyDuration = time.Duration
// 为MyDuration添加一个函数
func (m MyDuration) EasySet(a string) {
}
从以上结果中可以看出,编译出错。编译器提示:不能在一个非本地的类型time.Duration中定义新的方法,非本地类型指的就是time.Duration不是在main包中定义的,而是在time包中定义的,与main包不在同一个包中,因此不能为不在一个包中的类型定义方法。
解决这个问题有以下两种方法:
type MyDuration time.Duration
,也就是将MyDuration从别名改为类型。当类型别名作为结构体嵌入的成员时会发生什么情况呢?
// 定义商标结构
type Brand struct {
}
// 为商标结构添加Show()方法
func (b Brand) Show() {
}
// 为Brand定义一个别名FakeBrand
type FakeBrand = Brand
// 定义车辆结构
type Vehicle struct {
// 嵌入两个结构
FakeBrand
Brand
}
func main() {
// 声明变量a为车辆类型
var a Vehicle
// 指定调用FakeBrand的Show
a.FakeBrand.Show()
// 取a的类型反射对象
ta := reflect.TypeOf(a)
// 遍历a的所有成员
for i := 0; i < ta.NumField(); i++ {
// a的成员信息
f := ta.Field(i)
// 打印成员的字段名和类型
fmt.Printf("FieldName: %v, FieldType: %v\n", f.Name, f.Type.Name())
}
}
如果尝试将第25行改为:a.Show()
编译器将发生错误