《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 100
0b1100100, 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 100
0b1100100
0144
0x64

使用浮点数时,须注意小数位的有效精度,相关细节可参考 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+000
true true
1.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 main

func 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" test

TEXT 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 byte

const (
	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 = 10

	var x int = d  // 错误:annot use d (type data) as type int in assignment
	println(x)

	println(d == x) // 错误:invalid operation: d == x (mismatched types data and int)
}

未命名类型

与有明确标识符的 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 = [2]int{1, 2} // 基础类型相同,右值为 未命名类型

	fmt.Println(d)

	a := make(chan int, 2)
	var b chan<- int = a // 双向通道 转换为 单向通道,其中 b 为 未命名类型

	b <- 2
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值