Go——类型简介

1、命名类型和未命名类型

命名类型(Named Type)

类型可以通过识符来表示,这种类型称为命名类型。Go语言的基本类型中有20个预声明简单类型都是命名类型,Go语言还有一种命名类型——用户自定义类型。

未命名类型(Unamed Type)

一个类型由预声明类型、关键字和操作符组合而成,这个类型称为未命名类型。未命名类型又称为字面量(Type Literal)(未命名类型和类型字面量二者等价)。

Go语言的基本类型中的复合类型:数组(array)、切片(slice)、字典(map)、通道(channel)、指针(pointer)、函数字面量(function)、结构(struct)和接口(interface)都属于类型字面量,也都是未命名类型。

所以*int、[]int、[2]int、map[k]v都是未命名类型。
注意:前面所说的结果和接口是未命名类型,这里的结构和接口没有使用type格式定义,具体如下示例:

package main

import "fmt"

func main() {
	//使用struct字面量声明的是未命名类型
	a := struct {
		name string
		age  int
	}{"andes", 18}
	fmt.Printf("%T\n", a) //struct { name string; age int }
	fmt.Printf("%v\n", a) //{andes 18}

	b := Person{"tom", 21}
	fmt.Printf("%T\n", b) //main.Person
	fmt.Printf("%v\n", b)//{tom 21}
}

//使用type声明的是命名类型
type Person struct {
	name string
	age  int
}

Go语言的命名类型和未命名类型如下图:
在这里插入图片描述

  1. 未命名类型和类型字面量是等价的,通常所说的Go语言基本类型中的复合类型就是类型字面量,所以未命名类型、类型字面量和Go语言基本类型中的复合类型三者等价。
  2. 通常所说的Go语言基本类型中的简单类型就是这20个预声明类型,它们都属于命名类型。
  3. 预声明类型是命名类型的一种,另一类命名类型是自定义类型。

2、底层类型

所有【类型】都有一个underlying type(底层类型)。底层类型的规则如下:

  1. 预声明类型(Pre-declared types)和类型字面量(type literals)的底层类型是它们自身。
  2. 自定义类型type newtype oldtype中newtype的底层类型是逐层递归向下查找的,直到查到的oldtype是预声明类型(Pre-declared types)或类型字面量(type literals)为止。例如:
type T1 string
type T2 T1
type T3 []string
type T4 T3
type T5 []T1
type T6 T5

T1和T2的底层类型都是string,T3和T4的底层类型都是string,T6和T5的底层类型都是[]T1。特别注意这里的T6、T5与T3、T4的底层类型是不一样的,一个是[]T1,另一个是[]string。
底层类型在类型赋值和类型强制转换时会使用.

3、类型相同和类型赋值

类型相同

Go是强类型的语言,编译器在编译时会进行严格的类型校验。两个命名类型是否相同,参考如下:

  1. 两个命名类型相同的条件是两个类型声明的语句完全相同。
  2. 命名类型和未命名类型永远不相同。
  3. 两个未命名类型相同的条件是它们的类型声明字面量的结构相同,并且内部元素的类型相同。
  4. 通过类型别名语句声明的两个类型相同。

Go1.9引入类型别名语法type T1 = T2,T1的类型完全和T2一样。引入别名主要有如下原因:

  1. 为了解决新旧包的 迁移兼容问题,比如context包先前并不在标准库里面,后面迁移到了标准库。
  2. Go的按包进行隔离的机制不太精细,有时我们需要将大包划分为几个小包进行开发,但需要在大包里面暴露全部的类型给使用者。
  3. 解决新旧类型的迁移问题,新类型先是旧类型的别名,后续的软件都基于新类型编程,在合适的时间将新类型升级为和旧类型不兼容,常用于软件的柔性升级。
类型可直接赋值

不同类型的变量之间一般是不能直接相互赋值的,除非满足一定的条件。下面探讨类型可赋值的条件。

类型为T1的变量a可以赋值给类型为T2的变量b,称为类型T1可以赋值给类型T2,伪代码表述如下:

//a是类型为T1的变量,或者a本身就是i一个字面常量或nil
//如果如下语句可以执行,则称之为类型T1可以赋值给类型T2
var b T2 = a

a可以赋值给变量b必须要满足如下条件中的一个:

  1. T1和T2的类型相同。
  2. T1和T2具有相同的底层类型,并且T1和T2里面至少有一个是未命名类型。
  3. T2是接口类型,T1是具体类型,T1的方法集是T2方法集的超集。
  4. T1和T2都是通道类型,它们拥有相同的元素类型,并且T1和T2中至少有一个是未命名类型。
  5. a是预声明标识符nil,T2是pointer、funcition、slice、map、channel、interface类型中的一个。
  6. a是一个字面常量值,可以用来表示类型T的值

示例如下:

package main

import "fmt"

func main() {
	mp := make(map[string]string, 10)
	mp["hi"] = "tata"

	//mp与ma有相同的底层类型map[string]string,并且是未命名类型,所以mp可以直接赋值给ma
	var ma Map = mp

	//im与ma虽然具有相同的底层类型map[string]string,但它们中没有一个是未命名类型
	//所以不能赋值,如下语句不能通过编译
	//var im iMap = ma

	ma.Print()

	//Map实现了Print(),所以其可以赋值给接口类型变量
	var i interface {
		Print()
	} = ma
	i.Print()

	s1 := []int{1, 2, 3}
	var s2 slice
	s2 = s1
	s2.Print()
}

type Map map[string]string

func (m Map) Print() {
	for _, key := range m {
		fmt.Println(key)
	}
}

type iMap Map

//只要底层类型是slice、map等支持range的类型字面量,新类型任然可以使用range迭代

func (m iMap) Print() {
	for _, key := range m {
		fmt.Println(key)
	}
}

type slice []int

func (s slice) Print() {
	for _, v := range s {
		fmt.Println(v)
	}
}

4、类型强制转换

由于Go是强类型的语言,如果不满足自动转换的条件,则必须进行强制类型转换。任意两个不相干的类型如果进行强制转换,则必须符合一定的规则。强制类型的语法格式:var a T=(T)(b),使用括号将类型和要转换的变量或表达式的值括起来。

非常量类型的变量ⅹ可以强制转化并传递给类型T,需要满足如下任一条件:

  1. x可以直接赋值给T类型变量。
  2. x的类型和T具有相同的底层类型。
package main

import "fmt"

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()
	im.Print()
}

type Map map[string]string

func (m Map) Print() {
	for _, key := range m {
		fmt.Println(key)
	}
}

type iMap Map

//只要底层类型是slice、map等支持range的类型字面量,新类型任然可以使用range迭代

func (m iMap) Print() {
	for _, key := range m {
		fmt.Println(key)
	}
}

type slice []int

func (s slice) Print() {
	for _, v := range s {
		fmt.Println(v)
	}
}
  1. x的类型和T都是未命名的指针类型,并且指针指向的类型具有相同的底层类型。
  2. x的类型和T都是整型,或者都是浮点型。
  3. x的类型和T都是复数类型。
  4. x是整数值或[]byte类型的值,T是string类型。
  5. x是一个字符串,T是[]byte或[]rune。

字符串和字节切片之间的转换最常见,示例如下:

s := "hello,世界!"
var a []byte
a = []byte(s)
var b string
b = string(a)
var c []rune
c = []rune(s)

fmt.Printf("%T\n", a)//[]uint8 byte是int8的别名
fmt.Printf("%T\n", b)//string
fmt.Printf("%T\n", c)//[]int32 rune是int32的别名

注意:

  1. 数值类型和string类型之间的相互转换可能造成值部分丢失;其他的转换仅是类型的转换,不会造成值的改变。string和数字之间的转换可使用标准库strconv。
  2. Go语言没有语言机制支持指针和interger之间的直接转换,可以使用标准库中的unsafe包进行处理。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值