首页 > 代码库 > 《Go学习笔记 . 雨痕》类型

《Go学习笔记 . 雨痕》类型

一、基本类型

清晰完备的预定义基础类型,使得开发跨平台应用时无须过多考虑符合和长度差异。

类型长度默认值说明
bool1false 
byte10uint8
int, uint4, 80默认整数类型,依据目标平台,32 或 64 位
int8, uint810-128 ~ 127,0 ~ 125
int16, uint1620-32,768 ~ 32,767,0 ~ 65,535
int32, uint3240-21亿 ~ 21亿,0 ~ 42亿
int64, uint6480 
float3240.0 
float6480.0默认浮点数类型
complex648  
complex12816  
rune40Unicode Code Point, int32
uintptr4, 80足以存储指针的 uint
string ""字符串,默认值为空字符串,而非 NULL
array  数组
struct  结构体
function nil函数
interface nil接口
map nil字典,引用类型
slice nil切片,引用类型
channel nil通道,引用类型

支持八进制、十进制以及科学计数法。标准库 math 定义了各数字类型的取值范围。

import (	"fmt"	"math")func main()  {	a, b, c := 100, 0144, 0x64	fmt.Println(a, b, c)	fmt.Printf("0b%b, %#o, %#x\n", a, a, a)	fmt.Println(math.MinInt8, math.MaxInt8)}

输出:

100 100 1000b1100100, 0144, 0x64-128 127

标准库 strconv 可在不同进制(字符串)间转换。

import (	"strconv")func main()  {	a, _ := strconv.ParseInt("1100100", 2, 32)	b, _ := strconv.ParseInt("0144", 8, 32)	c, _ := strconv.ParseInt("64", 16, 32)	println(a, b, c)	println("0b" + strconv.FormatInt(a, 2))	println("0" + strconv.FormatInt(a, 8))	println("0x" + strconv.FormatInt(a, 16))}

输出:

100 100 1000b110010001440x64

使用浮点数时,须注意小数位的有效精度,相关细节可参考 IEEE-754 标准。

func main()  {	var a float32 = 1.1234567899	// 注意:默认浮点数类型是 float64	var b float32 = 1.12345678	var c float32 = 1.123456781	println(a, b, c)	println(a == b, a == c)	fmt.Printf("%v %v, %v\n", a, b, c)}

输出:

+1.123457e+000 +1.123457e+000 +1.123457e+000true true1.1234568 1.1234568, 1.1234568

别名

在官方的语言规范中,专门提到 两个 别名。

byte        alias for unit8
rune        alias for unit32

别名类型无须转换,可直接赋值。

func test(x byte) {	println(x)}func main()  {	var a byte = 0x11	var b uint8 = a	var c uint8 = a + b	test(c)}

但这并不表示,拥有相同底层结构的就属于别名。就算在 64位 平台上 int 和 int64 结构完全一致,也分属不同类型,须显式转换。

func add(x, y int) int {	return x + y}func main() {	var x int = 100	var y int64 = x		// 错误:cannot use x (type int) as type int64 in assignment	add(x, y)			// 错误:cannot use y (type int64) as type int in argument to add}

二、引用类型

所谓引用类型(reference type),特指 slice 、map 、channel 这三种预定义类型。相比 数字 、数组 等类型,引用类型 拥有更复杂的存储结构。除分配内存外,他们还须初始化一系列属性,诸如 指针 、长度 ,甚至包括哈希分布、数据队列等。

内置函数 new() 按指定类型长度分配零值内存,返回指针,并不关心类型内部结构和初始化方式。而 引用类型 则必须使用 make() 函数创建,编译器会将 make() 转换为目标类型专用的创建函数(或指令),以确保完成全部内存分配和相关属性初始化。

// test.go 文件

package mainfunc mkslice() []int  {	s := make([]int, 0, 10)	s = append(s, 100)	return s}func mkmap() map[string]int {	m := make(map[string]int)	m["a"] = 1	return m}func main() {	m := mkmap()	println(m["a"])	s := mkslice()	println(s[0])}

输出:

$ go build -gcflags "-l"  // 禁用函数内联$ go tool objdump -s "main\.mk" testTEXT main.mkslie(SB) test.go    CALL runtime.makeslice(SB)TEXT main.mkmap(SB) test.go    CALL runtime.makemap(SB)    

除 new() / make() 函数外,也可用 初始化表达式,编译器生成的指令基本相同。

当然,new() 函数也可为引用类型分配内存,但这是不完整创建。以字典(map)为例,它仅分配了字典类型本身(实际就是个指针包装)所需内存,并没有分配键值存储内存,也没有初始化散列桶等内部属性,因此它无法正常工作。

import "fmt"func main() {	p := new(map[string]int)  // 函数 new 返回指针	m := *p	m["a"] = 1  // 报错:panic: assignment to entry in nil map [运行期错误]	fmt.Println(m)}

三、类型转换

隐式转换造成的问题远大于它带来的好处。

常量别名类型 以及 未命名类型 外,Go 强制要求使用显示类型转换。加上不支持操作符重载,所以我们总是能确定语句及表达式的明确含义。

func main() {	a := 10	b := byte(a)	c := a + int(b) // 混合类型表达式必须确保类型一致	fmt.Println(c)}

同样不能讲 非bool 类型结果当作  true/false 使用。

func main() {	x := 100	var b bool = x // 报错:cannot use x (type int) as type bool in assignment	if x { // 报错:non-bool x (type int) used as if condition	}}

语法歧义

如果转换的目标 指针单向通道没有返回值的函数 类型,那么必须使用 括号(),以避免造成语法分解错误。

func main() {	x := 100	p := *int(&x)	// 报错:cannot convert &x (type *int) to type int					// invalid indirect of int(&x) (type int)	println(p)}

正确的做法是用括号,让编译器将 *int 解析为指针类型。

(*int)(p)            --> 如果没有括号 -->      *(int(p))
(<-chan int)(c)                               <-(chan  int(c))
(func())(x)                                   func() x

func() int (x)       --> 有返回值的函数类型可省略括号,但依然建议使用。
(func() int) (x)     使用括号后,更易阅读

四、自定义类型

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

type flags byteconst (	read flags = 1 << iota	write	exec)func main() {	f := read | exec	fmt.Printf("%b\n", f)  // 输出二进制标志位}

输出:

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")}

 输出:

{Tom 20}abc

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

func main() {	type data int	var d data = http://www.mamicode.com/10>

未命名类型

与有明确标识符的 bool 、int 、string 等类型相比,数组 、切片 、字典 、通道 等类型与具体元素类型或长度等属性有关,故称作 未命名类型(unnamed type)。当然,可用 type 为其提供 具体名称,将其改变为 命名类型(named type)

具有相同声明的未命名类型视作同一类型。

  • 具有相同基类型的指针;
  • 具有相同元素类型 和 长度的数组(array);
  • 具有相同元素类型的切片(slice);
  • 具有相同键值类型的字典(map);
  • 具有相同数据类型及操作方向的通道(channel);
  • 具有相同字段序列(字段名、字段类型、标签,以及字段顺序)的结构体(struct);
  • 具有相同签名(参数和返回值列表,不包括参数名)的函数(func);
  • 具有相同方法集(方法名、方法签名,不包括顺序)的接口(interface);

容易被忽视的是 struct tag,它也属于类型组成部分,而不仅仅是元数据描述。

func main() {	var a struct { // 匿名结构类型		x int `X`		s string `S`	}	var b struct {		x int		s string	}	b = a // 错误:cannot use a (type struct { x int "X"; s string "S" }) as type struct { x int; s string } in assignment	fmt.Println(b)}

同样,函数的参数顺序也属签名组成部分。

func main() {	var a func(int, string)	var b func(string, int)	b = a // 错误:cannot use a (type func(int, string)) as type func(string, int) in assignment	b("s", 1)}

未命名类型转换规则:

  • 所属类型相同;
  • 基础类型相同,且其中一个是未命名类型
  • 数据类型相同,将双向通道赋值给单向通道,且其中一个为未命名类型;
  • 将默认值 nil 赋值给 切片、字典、通道、指针、函数 或 接口;
  • 对象实现了目标接口;
func main() {	type data [2]int	var d data = http://www.mamicode.com/[2]int{1, 2} // 基础类型相同,右值为 未命名类型>

《Go学习笔记 . 雨痕》类型