Go语言--数据类型

tech2025-08-10  5

目录

一 数据类型

布尔型(bool)

数字类型

字符串类型 (string)

派生类型

二 变量声明后的默认值

三 转换不同的数据类型

四 类型别名(Type Alias)

4.1 区分类型别名与类型定义

4.2 非本地类型不能定义方法

4.3 在结构体成员嵌入时使用别名

五 自定义类型

六 Go语言类型汇总

参考


一 数据类型

Go语言中有丰富的数据类型。Go语言按类别有以下几种数据类型:

布尔型(bool)

布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。

<注意>

布尔类型变量的默认值为false。Go 语言中不允许将整型强制转换为布尔型。布尔型无法参与数值运算,也无法与其他类型进行转换。

数字类型

数字类型又分为整型、浮点型和其他数字类型。

1、整型

整型分为有符号整型和无符号整型。

 2、浮点型

 float32的浮点数的最大范围约为3.4e38,可以使用常量定义:math.MaxFloat32。float64的浮点数的最大范围约为1.8e3.8,可以使用常量定义:math.MaxFloat64。complex64 和 complex128 是复数类型,complex64的实部和虚部为32位,complex128的实部和虚部为64位。

 3、其他数字类型

 《说明》Go语言的字符类型有两种:

一种是uint8类型,或者叫byte类型,代表了ASCII码的一个字符。另一种是rune类型,代表了一个UTF-8字符。当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际上是一个int32类型。

示例如下:

var ch1 byte = 'a' fmt.Printf("%d %T\n", ch1, ch1) var ch2 rune = '你' fmt.Printf("%d %T\n", ch2, ch2)

 输出结果为:

97 uint8

20320 int32

说说 int 和 uint

这两种类型是可以用来自动匹配特定平台整型长度的类型。因为不同平台的字节长度是有差异的。现如今64位平台已经很普及了,但是8位、16位、32位的操作系统依旧存在。在16位平台上虽然可以使用64位的变量,但是运行性能和内存性能会很差。同理,在64位平台上大量使用8位IE、16位、32位与平台位数不等长的变量时,编译器也是尽量将内存对齐以获得最好的性能。

不能正确匹配平台字节长度的程序,就类似于用轿车运一台牛和用一辆大卡车运送一台牛的情形一样。

在使用 int 和 uint 类型时,不能假定它是32位或者64位整型,而是考虑到 int 和 uint 可能在不同平台上的差异。

Q:哪些情况下使用int和uint?

答:逻辑对整型范围没有特殊要求。例如,对象的长度使用内建len()函数返回,这个长度可以根据不同平台的字节长度进行变化。实际使用中,切片和map的元素数量等都可以用int来表示。反之,在二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用 int 和 uint。

字符串类型 (string)

字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。

字符串的值为双引号中的内容。可以在Go语言中直接添加非ASCII码字符。

Go语言中,字符串的实现是基于UTF-8编码。通过rune类型可以方便地对每个UTF-8字符进行访问。同时,Go语言也支持按传统的ASCII码方式进行逐字符访问。

派生类型

指针类型(Pointer)数组类型(array)结构体类型(struct)通道类型(chan)函数类型(func)切片类型(slice)接口类型(interface)字典类型(map)

二 变量声明后的默认值

在Go语言中,任何类型的变量在声明后没有赋值的情况下,都会有一个默认值。

整形如int8、byte、int16、uint、uintprt等,默认值为0。浮点类型如float32、float64,默认值为0。布尔类型bool的默认值为false。复数类型如complex64、complex128,默认值为0+0i。字符串string的默认值为空,即 ""。错误类型error的默认值为 nil。对于一些复合类型,如指针、切片、字典、通道、接口,默认值为nil。而数组的默认值要根据其数据类型来确定。例如:var a [4]int,其默认值为[0 0 0 0]。

三 转换不同的数据类型

Go语言中,使用类型前置+括号的方式进行类型转换,一般格式如下:

T(表达式)

其中,T表示要转换的类型。表达式包括:变量、复杂算式和函数返回值等。

类型转换时,需要考虑两种类型的关系和取值范围,是否会发生数值截断。

浮点数在转换为整型时,会将小数点部分去掉,只保留整数部分;整数截断在类型转换时发生地较为隐性,这个要特别注意。

示例代码如下:

/* 程序描述:数据类型强制转换。 程序运行: go run typecast.go int8 range: -128 127 int16 range: -32768 32767 int32 range: -2147483648 2147483647 int64 range: -9223372036854775808 9223372036854775807 int32: 0x3e6f54ff 1047483647 int16: 0x54ff 21759 3 */ package main import ( "fmt" "math" ) 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 //输出变量的16进制形式和10进制值 fmt.Printf("int32: 0x%x %d\n", a, a) //将变量a赋值给int16类型的变量b,发生数值截断 b := int16(a) //输出变量b的16进制形式和10进制值 fmt.Printf("int16: 0x%x %d\n", b, b) //将常量Pi保存为float32类型 var c float32 = math.Pi //将PI转换成int类型,浮点发生精度丢失 fmt.Println(int(c)) }

四 类型别名(Type Alias)

类型别名是Go1.9版本添加的功能。主要用于代码升级、迁移中类型的兼容性问题。

在Go1.9版本之前的内建类型定义的代码是这样写的:

type byte uint8 type rune int32

而在Go1.9版本之后变为:

type byte = uint8 type rune = uint32

 这个修改就是配合类型别名而进行的修改。类型别名的声明格式如下:

type T1 = T2

这个声明表示T1类型是T2类型的别名,也就是说T1和T2是相同的类型。就好像一个人有时候有小名、乳名,上学后使用学名,长大后还有英文名,但是这些名字都指的是同一个人。

4.1 区分类型别名与类型定义

类型别名与类型定义表明上看只有一个等号的差异,那么二者之间实际的差别有哪些呢?下面通过一段代码来理解:

/** 程序描述:区分类型定义和类型别名。 **运行结果: a type: main.NewInt b type: int **结果分析: 结果显示变量a的类型为main.NewInt,表示是在main包下定义的NewInt类型。变量b的类型为int。IntAlias 类型只会在代码中存在,编译完成后,不会有IntAlias类型。 */ package main import "fmt" //将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) //将b声明为IntAlias类型 var b IntAlias fmt.Printf("b type: %T\n", b) }

可以看到,类型别名只存在于代码中,方便开发者进行类型区分。当编译完成后,就不存在所谓的类型别名了,而是变成了它原本的类型。就如同一个人它有多个名字,但是只有身份证上的名字才是他原本的名字。

4.2 非本地类型不能定义方法

 我们能够随意为各种类型起别名,是否意味着可以在自己本地包内为这些类型的别名任意添加方法?常见下面的代码示例:

package main import ( "time" ) //定义time.Duration的别名为MyDuration type MyDuration = time.Duration //为MyDuration添加一个方法 func (m MyDuration) EasySet(a string){ } func main(){ }

上面的代码中,我们为time.Duration类型起了一个别名MyDuration,然后为这个别名添加了一个方法。编译的时候会报错:

cannot define new methods on non-local type time.Duration

编译器提示:不能在一个非本地的类型time.Duration上定义新方法。非本地方法指的是使用time.Duration的代码所在的包,也就是main包。因为 time.Duration 是在time包中定义的,在main包中使用。因为在编译的时候,time.Duration的类型别名MyDuration被替换为了time.Duration类型,而time.Duration类型又是在time包中定义的,因此编译报错!即不能在main包中定义time.Duration类型的方法。

解决这个问题有下面两种方法:

将 type MyDuration = time.Duration 语句修改为 type MyDuration time.Duration,也就是将MyDuration 从别名改为定义新类型。将MyDuration 的别名定义在time包中。

4.3 在结构体成员嵌入时使用别名

当类型别名作为作为结构体嵌入的成员时会发生什么情况?示例代码如下:

/* 程序描述:类型别名作为结构体嵌入成员。 程序运行: go run typeAlias.go FieldName: FakeBrand, FieldType: Brand FieldName: Brand, FieldType: Brand **分析: Vehicle结构体类型有2个成员FakeBrand和Brand,从运行结果可以看出,它们的类型都是Brand。 */ package main import ( "fmt" "reflect" ) //定义商标结构体类型 type Brand struct{ } //为Brand结构体类型添加方法 func (t Brand) Show(){ } //为Brand定义一个别名FakeBrand type FakeBrand = Brand //定义车辆结构体类型 type Vehicle struct{ //嵌入两个结构 FakeBrand Brand } func main(){ var a Vehicle //声明变量a为车辆类型 //指定调用FakeBrand的Show方法 a.FakeBrand.Show() //a.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()) } }

 在该例中,FakeBrand是Brand结构体类型的一个类型别名,它们都是在同一个包中定义的,即main包。在Vehicle中嵌入FakeBrand和Brand并不意味着嵌入两个Brand。FakeBrand的类型会以名字的方式保留在Vehicle的成员中。

如果尝试将 a.FakeBrand.Show() 语句改为 a.Show(),编译器将会报错:ambiguous selector a.show

 在调用show()方法时,因为两个类型都有Show()方法,会发生歧义,证明FakeBrand本质上确实是Brand类型。

五 自定义类型

 Go语言中使用关键字 type 定义用户自定义类型,包括基于现有基础类型创建,或者是结构体、函数类型等。

package main import "fmt" type flags byte const ( read flags = 1 << iota //read初始化值为1,每次将上一次的值左移1位作为后续值 write exec ) func main(){ f := read | exec fmt.Printf("%b\n", read) fmt.Printf("%b\n", write) fmt.Printf("%b\n", exec) fmt.Printf("%b\n", f) }

 运行结果:go run demo.go 1 10 100 101

和var、const关键字一样,多个type定义可以合并成组,可在函数或代码块内定义局部类型。

func main(){ type ( //组 user struct { //结构体 name string age uint8 } event func(string) bool //函数类型 ) u := user{"Tom", 20} fmt.Println(u) var f event = func(s string) bool { println(s) return s != "" } f("abc") }

即便指定了基础类型,也只表明它们有相同底层数据结构,两者间不存在任何关系,属于完全不同的两种类型。除操作符外,自定义类型不会继承基础类型的其他信息(包括方法)。不能视为别名,不能隐式转换,不能直接用于比较表达式。

func main(){ type data int var d data = 10 var x int = d //错误:cannot use d(type data) as type int in assignment println(d == x) //错误:invalid operation: d == x (mismatched types data and int) }

 <注意> 要注意区分自定义类型和类型别名之间的区别。

六 Go语言类型汇总

Go语言类型汇总

 

参考

Go 语言数据类型

go语言变量声明后的默认值

《Go语言从入门到进阶实战(徐波/著)》

 

最新回复(0)