# 类型

# 基本类型

# 变量

关键字 var 用于定义变量。和 C 不同,类型被放在变量名后。

自动初始化为二进制零值(zero value)。
显式提供初始值,可省略类型,由编译器推断。
同一作用域内不能重复定义。
未使用局部变量当做错误。

var x int      // 0
var y = false  // bool

可一次定义多个变量,包括用不同初始值定义不同类型。

var (
	x, y int            // 相同类型
	a, s = 100, "abc"   // 不同类型
)

简短模式:(short variable declaration)

显式初始化。
不能提供类型。
仅限函数内部。

func main() {
	x := 100
	if x > 0 {
		x := "abc"       // 定义同名变量!
		println(&x, x)
	}
	println(&x, x)
}
// 0xc000032760 abc
// 0xc000032758 100

简短模式并不总是重新定义,也可能是部分退化的赋值操作。

退化赋值 前提条件:最少有一个新变量被定义,且必须是同一作用域。

func main() {
	x := 100
	println(&x)
	x, y := 200, "abc"  //x 退化赋值,y 是新变量。
	println(&x, x)
	println(y)
}
/*
0xc000032768
0xc000032768 200
abc
*/
func main() {
	x := 100
	println(&x)
	{                       // 不同作用域。
		x, y := 200, "abc"  // 全部新定义。不同地址
		println(&x, x)
		println(y)
	}
}
/*
0xc000032768
0xc000032760 200
abc
*/

多变量赋值,先计算出全部右值,然后再依次赋值。

func main() {
	x, y := 1, 2
	x, y = y+3, x+2
	println(x, y)  // 5, 3
}

# 常量

常量表示恒定不变的值,通常是一些字面量。

必须是编译期可确定的值。
可指定类型,或由编译器推断。
不支持字面量类型后缀。(C: 123LL)
可在函数内定义。
未使用常量不会引发编译错误。

const (
	ptrSize = unsafe.Sizeof(uintptr(0))
	strSize = len("hello, world!")
)
func main() {
	const x, f = 100, 1.23
	{
		const x = "abc"
		println(x)
	}
}

如指定类型,须确保值类型一致,可做显式转换。 右值不能超出常量取值范围,否则引发错误。

const (
	x, y int  = 99, -999
	b    byte = byte(x)
	// n = uint8(y)   
	//     ~~~~~ cannot convert y (constant -999 of type int) to type uint8
)

在常量组中如不指定类型和初始化值,则其(类型和初始化表达式)与上一常量相同。

func main() {
	const (
		x uint16 = 120
		y
		s = "abc"
		z
	)
	fmt.Printf("%T, %v\n", y, y)   // uint16, 120
	fmt.Printf("%T, %v\n", z, z)   // string, abc
}

# 枚举

没有明确意义上的枚举类型(enum),借助 iota 自增常量值来实现枚举效果。

const (
  x = iota   // 0
	y          // 1
	z          // 2
)
const (
	_  = iota               // 0
	KB = 1 << (10 * iota)   // 1 << (10 * 1)
	MB                      // 1 << (10 * 2)
	GB                      // 1 << (10 * 3)
)
func main() {
	println(KB, MB)  // 1024 1048576
}

自增作用范围为常量组。 可使用多个 iota,各自单独计数,只须列数相同即可。

const (
	_, _ = iota, iota * 10   // 0, 0 * 10
	a, b                     // 1, 1 * 10
	c, d                     // 2, 2 * 10
)

如中断自增,须显式恢复,且后续自增值按行序递增。

const (
	a = iota   // 0
	b          // 1
	c = 100    // 100
	d          // 100 (与上一行常量右值表达式相同)
	e = iota   // 4   (恢复 iota 自增,计数包括 c、d 两行)
	f          // 5
)

默认数据类型为 int,可显式指定。

const (
	a         = iota   // int, 0
	b float32 = iota   // float32, 1.0
	c         = iota   //int, 2(若没有 iota,则与 b 类型相同)
)

用自定义类型实现用途明确的枚举类型。 但不能将取值范围严格限定在预定义的枚举值内。

type color byte
const (
	black color = iota
	red
	blue
)
func test(c color) {
	println(c)
}
func main() {
	test(red)
	const m = 100
	test(m)      // 无类型常量和字面量不超出 color/byte 取值范围即可。
	// const n int = 100
	// test(n)
	//      ~ cannot use n (constant 100 of type int) as type color
	// x := 2
	// test(x)
	//      ~ cannot use x (variable of type int) as type color
}

# 命名

选用有实际含义,易于阅读和理解的字母或单词组合。

以字母或下划线开始。
由字母、数字和下划线组合而成。
区分大小写。

使用驼峰(camel case)拼写格式。
局部变量优先使用短名。
不使用保留关键字。
不与预定义常量、类型、内置函数同名。

空标识符是对编译器的一种建议,提示进行语法检查,但不一定生成对应机器指令。

名为 “_” 的 空标识符(blank identifier)是预设成员,不应重新定义。 可用作表达式左值,表示忽略,但无法作右值返回内容。

func main() {
	x := 100
	_ = x         // 规避变量未使用错误。
}

# 基础类型

==== type ==== | = len = | = default = | ====== comment =============
 bool               1         false
 byte               1           0         uint8
 int,   uint        8           0         x86:4, x64:8
 int8,  uint8       1           0         -128 ~ 127,     0 ~ 255
 int16, uint16      2           0         -32768 ~ 32767, 0 ~ 65535
 int32, uint32      4           0
 int64, uint64      8           0
 float32            4          0.0
 float64            8          0.0
 complex64          8
 complex128        16
 rune               4           0         unicode code point, int32
 uintptr            8           0         uint
 string                        ""         len()
 array                                    len() == cap()
 struct
 function                      nil
 interface                     nil
 map                           nil        make(), len()
 slice                         nil        make(), len(), cap()
 channel                       nil        make(), len(), cap()

支持二进制、八进制、十六进制以及科学记数法。

math:定义了各数字类型的取值范围。
strconv:在不同进制(字符串)间转换。

func main() {
	a, b, c := 0b1010, 0o144, 0x64
	fmt.Printf("0b%b, %#o, %#x\n", a, b, c)
	fmt.Println(math.MinInt8, math.MaxInt8)
}
// 0b1010, 0144, 0x64
// -128 127

可用下划线作为数字分隔符,以便于阅读。

func main() {
	a := 111_22_3_44
	println(a) // 11122344
}

注意,nil 是预定义标识符,不是关键字。

其含义是默认零值(zero value),而非 “空”。
没有类型,不能作为初始值供编译器推断。
不同类型的 nil 值不能比较。

func main() {
	x := nil   // use of untyped nil in assignment
}
func main() {
	var m map[string]int = nil
	var c []int = nil
	// fmt.Println(m == c)   // invalid: mismatched types map[string]int and []int
}

标准库 sync/atomic 提供了某些基本类型的原子操作版本。

package main
import (
	"sync/atomic"
)
func main() {
	// var x int
	var x atomic.Int64
	go func() {
		x.Add(1)
	}()
	x.Store(x.Load() + 1)
	select{}
}

# 别名

只是语法糖,编译器会将其还原为原始类型。
让通用类型具备更清晰的上下文含义。

byte        // alias for uint8
rune        // alias for int32
type X = int   // 别名
func main() {
	var a int = 100
	var b X = a
	fmt.Printf("%T, %v\n", b, b)    // int, 100
  fmt.Println(reflect.TypeOf(b))  // int
}

# 引用类型

特指 slice、map、channel 这三种预定义类型。

相比数字、数组等简单类型,引用类型有更复杂的内部结构。除分配内存外,还需初始化一系列属性,诸如指针、长度,甚至哈希分布、数据队列等。

从语言规范看,没有传统意义上的值类型和引用类型。只所以如此表述,除历史原因(早期文档)外,还因为它们与 make 函数的特殊关系,且被编译器和运行时特别对待(优化)。

      slice            array
  +-----------+       +-----//-----+
  |   array  -|-----> |  ...  ...  |
  +-----------+       +-----//-----+
  |   len     |
  +-----------+
  |   cap     |
  +-----------+
  
  |<-- new -->|                     
  |<------------- make ----------->|
  

以上图 slice 结构为例,由头和底层数组两部分组成。

new:按类型大小分配零值内存,返回指针。
make:转为类型构造函数(或指令),完成内部初始化。

函数 new 只关心类型长度(sizeof),不涉及内部结构和逻辑。

func main() {
	s := *new([]int)             // slice: { ptr, len, cap }
	m := *new(map[string]int)    // map: ptr
	c := *new(chan int)          // chan: ptr
	println(unsafe.Sizeof(s))    // 24
	println(unsafe.Sizeof(m))    // 8
	println(unsafe.Sizeof(c))    // 8
}
/*
(gdb) x/3xg &s  ; 没有底层数组。
0xc000036740:	0x0000000000000000	0x0000000000000000
0xc000036750:	0x0000000000000000
(gdb) x/xg &m   ; 仅指针。
0xc000036708:	0x0000000000000000
(gdb) x/xg &c
0xc000036710:	0x0000000000000000
*/
/*
$ go build -gcflags "-N"
$ go tool objdump -S -s "main\.main" ./test
func main () {
    ; 不涉及内部分配和逻辑处理。
	...
	m := *new (map [string] int)
	MOVQ $0x0, 0x20 (SP)	
	MOVQ 0x20 (SP), AX	
	MOVQ AX, 0x8 (SP)	
	c := *new (chan int)
	MOVQ $0x0, 0x18 (SP)	
	MOVQ 0x18 (SP), AX	
	MOVQ AX, 0x10 (SP)
}
*/

虽然 new 的行为很像 malloc,但优先在栈上分配。

func main() {
	p := new(int)
	*p = 100
}
/*
$ go build -gcflags "-N"
$ go tool objdump -S -s "main\.main" ./test
func main() {
	p := new(int)
	MOVQ $0x0, 0(SP)	
	LEAQ 0(SP), AX		
	MOVQ AX, 0x8(SP)	
	*p = 100
	MOVQ $0x64, 0(SP)	
}
*/

编译器将 make 翻译成 makeslice、makemap 之类的构造函数调用(或等价指令)。

func main() {
	s := make([]int, 10, 100)
	println(unsafe.Sizeof(s))  // 24
}
/*
(gdb) x/3xg &s  ; 分配底层数组,并初始化 len 和 cap 字段。
0xc000032758:	0x000000c000032438	0x000000000000000a
0xc000032768:	0x0000000000000064
*/
func main() {
	s := make([]int, 10000)
	s[0] = 1
	m := make(map[string]int, 10)
	m["a"] = 1
}
/*
$ go build -gcflags "-N"
$ go tool objdump -S -s "main\.main" ./test
func main() {
	s := make([]int, 10000)
		CALL runtime.makeslice(SB)	
	m := make(map[string]int, 10)
		CALL runtime.makemap(SB)
*/

# 类型转换

除常量、别名以及未命名类型外,强制要求显式类型转换。

a := 10
b := byte(a)
c := a + int(b)    // 确保类型一致。
func main() {
	x := 100
	// var b bool = x  // ~ cannot use x (int) as type bool
	// if x {} 				 // ~ non-boolean condition in if statement
}

# 词法歧义

如目标类型是指针、单向通道或没有返回值的函数,那么可能会造成语法解析错误。

func main() {
	x := 100
	// p := *int(&x) // ~ cannot convert &x (*int) to 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 基于现有类型(underlying type)定义用户自定义类型。

type A int         // 定义新类型
type B = int       // 别名

即使底层类型相同,也非同一类型(区别于别名)。
除运算符外,不继承任何信息(方法等)。
不能隐式转换,不能直接比较。

type X int
func main() {
	var a int = 100
	// var b X = a  // ~ cannot use a (int) as type X
	b := X(a)
	println(b)
	// println(a == b) // ~ invalid operation: mismatched types int and X
}

# 未命名类

与 bool、int 这类有明确标识的类型不同,array、slice、map、channel 等与其元素类型或长度属性相关,被称作未命名类型(unnamed type)。可用 type 提供具体名称,变为命名类型。

[]int             // unnamed type
type A []int      // named type
[2]int, [3]int   // 未命名类型:长度不同,不是同一类型。
[]int,  []byte   // 未命名类型:元素类型不同,不是同一类型。

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

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

func main() {
	var a, b interface {
		test()
	}
	println(a == b)
}

转换规则:

所属类型相同。
基础类型相同,且其中一个是未命名类型。
数据类型相同,将双向通道赋值给单向通道,且其中一个为未命名类型。
将默认值 nil 赋值给切片、字典、通道、指针、函数或接口。
对象实现了目标接口。

func main() {
  // 同一类型(相同声明的未命名类型)。
	var a [2]int
	var b [2]int = a
  // 基础类型相同,其中一个是未命名类型。
	type array [2]int
	var c array = a
	_, _, _ = a, b, c
}
更新于
-->