1、命名类型和未命名类型
命名类型:类型可以通过标识符来表示,这种类型称为命名类型。Go语言的基本类型中有20个预声明简单类型都是命名类型,Go语言还有一种命名类型——用户自定义类型。
未命名类型:一个类型由预声明类型、关键字和操作符组合而成,这个类型称为未命名类型。未命名类型又称为类型字面量(Type Literal),本书中的未命名类型和类型字面量二者等价。
Go语言的基本类型中的复合类型:数组(array)、切片(slice)、字典(map)、通道(channel)、指针(pointer)、函数字面量(function)、结构(struct)和接口(interface)都属于类型字面量,也都是未命名类型。所以,*int、[]int、 [2]int、map[k]v都是未命名类型。
注意:前面所说的结构和接口是未命名类型,这里的结构和接口没有使用type格式定义。
// 使用type声明的是命名类型
type Person struct {
name string
age int
}
func main() {
// 使用struct字面量声明的是未命名类型
a := struct {
name string
age int
}{"Tom", 18}
fmt.Printf("%T\n", a) // struct { name string; age int }
fmt.Printf("%v\n", a) // {Tom 18}
b := Person{"Jerry", 20}
fmt.Printf("%T\n", b) // main.Person
fmt.Printf("%v\n", b) // {Jerry 20}
}
命名类型和未命名类型说明如下:
- 未命名类型和类型字面量是等价的,通常所说的Go语言基本类型中的复合类型就是类型字面量,所以,未命名类型、类型字面量和Go语言基本类型中的复合类型三者等价。
- 通常所说的Go语言基本类型中的简单类型就是预声明类型,它们都属于命名类型。
- 预声明类型是命名类型的一种,另一类命名类型是自定义类型。
2、自定义类型
Go语言允许用户定义类型。当用户声明一个新类型时,这个声明就给编译器提供了一个框架,告知必要的内存大小和表示信息。声明后的类型与内置类型的运作方式类似。Go语言中声明用户定义的类型有两种方法,最常用的方法是使用关键字struct,它可以让用户创建一个结构类型。
结构类型通过组合一系列固定且唯一的字段来声明,如下面代码所示。结构中每个字段都会用一个已知类型声明,这个已知类型可以是内置类型,也可以是其他用户定义的类型。
//user在程序里定义一个用户类型
type user struct {
name string
emai1 string
ext int
privileged bool
}
//声明user类型的变量,并初始化所有字段
func main() {
tom := user{
name: "Tom",
email: "Tom@163.com",
ext: 123,
privileged: false,
}
fmt.Println(tom)
}
// 第二种形式没有字段名,只声明对应的值,结尾不需要逗号。
// 在这种形式下,值的顺序很重要,必须和结构声明中字段的顺序一致。
lisa := user{"Lisa", "Lisa@example.com ", 123, true}
- 结构类型的声明,这个声明以关键字type开始,之后是新类型的名字,最后是关键字struct。
- 这个结构类型有4个字段,每个字段都基于一个内置类型。一旦声明了类型就可以使用这个类型创建值。
type user struct {
name string
email string
ext int
privileged bool
}
type admin struct {
person user
level string
}
func main() {
fred := admin{
person: user{
name: "Tom",
email: "Tom@163.com",
ext: 123,
privileged: false,
},
level: "super",
}
fmt.Println(fred) // {{Tom Tom@163.com 123 false} super}
}
另一种声明用户定义类型的方法是,基于一个已有的类型,将其作为新类型的类型说明。标准库使用这种声明类型的方法,从内置类型创建出很多更加明确的类型,并赋予更高级的功能:
type Duration int64
上述代码是标准库的time包中的一个类型声明。Duration是一种描述时间隔的类型,单位是纳秒(ns)。这个类型使用内置的int64类型作为其表示,在Duration类型的声明中,将int64类型称为Duration的基础类型。不过,虽然int64是基础类型,但Go语言并不认为Duration和int64是同一种类型,这两个类型是完全不同的、有区别的类型。
类型int64的值不能作为类型Duration的值来用。虽然int64类型是基础类型,Duration类型依然是一个独立的类型。两种不同类型的值即便互相兼容,也不能互相赋值,编译器不会对不同类型的值进行隐式转换。
3、类型的强制转换
描述:由于Go语言是强类型的语言,如果不满足自动转换的条件,则必须进行强制类型转换。
语法:var a T=(T) (b)
非常量类型的变量x可以强制转化并传递给类型T,满足以下任一条件即可:
- x可以直接赋值给T类型变量。
- x的类型和T具有相同的底层类型。
- x的类型和T都是未命名的指针关型,并且指针指向的类型具有相同的成员类型。
- x的类型和T都是整型,或者都是浮点型。
- x的类型和T都是复数类型。
- x是整数值或[]byte类型的值,T是string类型。
- x是一个字符串,T是[]byte或[]rune。
type Map map[string]string
type iMap Map
func (m Map) print() {
for _, key := range m {
fmt.Println(key)
}
}
// 只要底层类型是slice、map等支持range的类型字面量,新类型仍然可以使用range迭代
func (m iMap) print() {
for _, key := range m {
fmt.Println(key)
}
}
func main() {
mp := make(map[string]string, 10)
mp["hi"] = "tata"
// mp 与 ma 有相同的底层类型map [string]string, 并且mp是未命名类型
var ma Map = mp
// im 与 ma 虽然有相同的底层类型,但是二者中没有一个是字面量类型,不能直接赋值,可以强制进行类型转换
// var im iMap = ma
var im iMap = (iMap)(ma)
ma.print() // tata
im.print() // tata
}
字符串和字节切片之间的转换最常见:
func main() {
s := "hello,world!"
var a []byte
a = []byte(s)
var b string
b = string(a)
var c []rune
c = []rune(s)
fmt.Printf("%T\n", a) // []uint8 type是int8的别名
fmt.Printf("%T\n", b) // string
fmt.Printf("%T\n", c) // []int32 rune是int32的别名
}
在使用类型的强制转换时,需要注意以下两点:
- 数值类型和string类型之间的相互转换可能造成值部分丢失;其他的转换仅是类型的转换,不会造成值的改变。string和数字之间的转换可使用标准库strconv。
- Go语言没有语言机制支持指针和interger之间的直接转换,可以使用标准库中的unsafe包进行处理。