类型转换

https://gfw.go101.org/article/value-conversions-assignments-and-comparisons.html

问题1, 下面的代码 logPathPatterns(patterns) 怎么理解呢

1
2
3
4
5
6
7
8
9
10
11
12
13

// LogPathPatterns sets the patterns to find log paths in the Server.
func LogPathPatterns(patterns ...string) Option {
return logPathPatterns(patterns)
}

type logPathPatterns []string

func (opt logPathPatterns) apply(m *Server) error {
m.tOpts = append(m.tOpts, tailer.LogPatterns(opt))
return nil
}

开始我以为这是一个函数调用,全局去 mtail 代码里面搜索没有搜到,此时就很懵逼了,一脸疑惑, 这到底是啥玩意呢?

type logPathPatterns []string 这个 还好理解,是 定义个 新的类型,类型名 logPathPatterns, 这个类型 和 字符串切片 有相同的属性,相同的行为。

func (opt logPathPatterns) apply(m *Server) error 从这里 还可以看到 这个 类型 实现了 Option 的 apply 方法了,所以 他也是 option 子类型了。

类型转换规则大全

在Go中,如果一个值v可以被显式地转换为类型T,则此转换可以使用语法形式  (T)(v)   来表示。 
在大多数情况下,特别是T为一个类型名(即一个标识符)时,此形式可简化为   T(v) 

当我们说一个值x可以被隐式转换为一个类型T,这同时也意味着x可以被显式转换为类型T。

所以上面的 logPathPatterns(patterns) 其实就是个 类型转换, 把 patterns 这个值 转换成 logPathPatterns 类型了。

原始的 patterns 是个 字符串切片, 这里可以转换的。

1. 显然的类型转换规则

如果两个类型表示着同一个类型,则它们的值可以相互隐式转换为这两个类型中的任意一个。

比如,

类型byte和uint8的任何值可以转换为这两个类型中的任意一个。
类型rune和int32的任何值可以转换为这两个类型中的任意一个。
类型[]byte和[]uint8的任何值可以转换为这两个类型中的任意一个。

此条规则没什么可解释的,无论你是否认为此种情况中发生了转换。

2. 底层类型相关的类型转换规则

给定一个 非接口值 x 和一个 非接口类型T,并假设x的类型为Tx,

如果类型Tx和T的底层类型相同(忽略掉结构体字段标签),则x可以被显式转换为类型T。
如果类型Tx和T中至少有一个是无名类型并且它们的底层类型相同(考虑结构体字段标签),则x可以被隐式转换为类型T。
如果类型Tx和T的底层类型不同,但是两者都是无名的指针类型并且它们的基类型的底层类型相同(忽略掉结构体字段标签),则x可以(而且只能)被显式转换为类型T。

(注意:两处“忽略掉结构体字段标签”从Go 1.8开始生效。)

例子 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 类型[]int、IntSlice和MySlice共享底层类型:[]int。
type IntSlice []int
type MySlice []int

var s = []int{}
var is = IntSlice{}
var ms = MySlice{}

// 这两行隐式转换编译不通过。
/*
is = ms
ms = is
*/
// 必须使用显式转换。
is = IntSlice(ms)
ms = MySlice(is)


// 这些隐式转换是没问题的。
s = is
is = s
s = ms
ms = s

例子2

1
2
3
4
5
6
7
8
9
10
11
12
type Foo = struct{n int `foo`}
type Bar = struct{n int `bar`}


var x map[Bar]Foo
var y map[Foo]Bar

// 必须使用显式转换。
x = map[Bar]Foo(y) // 把y值转换为 x 类型的值 并赋值给 x 了,x是 “map[Bar]Foo” 这一大坨类型的。
y = map[Foo]Bar(x)


指针相关的转换例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
type MyInt      int        // 这里自定义了 3 种 类型
type IntPtr *int
type MyIntPtr *MyInt // *int

var pi = new(int) // pi的类型为*int
var ip IntPtr = pi // 没问题,因为底层类型相同, 并且pi的类型为无名类型。



var _ *MyInt = pi // 不能隐式转换
var _ = (*MyInt)(pi) // 显式转换是没问题的

// 类型 *int 的值不能被直接转换为类型 MyIntPtr,
// 但是可以间接地转换过去。

/*
var _ MyIntPtr = pi // 不能隐式转换
var _ = MyIntPtr(pi) // 也不能显式转换
*/

var _ MyIntPtr = (*MyInt)(pi) // 间接隐式转换没问题
var _ = MyIntPtr((*MyInt)(pi)) // 间接显式转换没问题

// 类型IntPtr的值不能被直接转换为类型MyIntPtr,
// 但是可以间接地转换过去。
/*
var _ MyIntPtr = ip // 不能隐式转换
var _ = MyIntPtr(ip) // 也不能显式转换
*/
// 间接隐式或者显式转换都是没问题的。
var _ MyIntPtr = (*MyInt)((*int)(ip)) // ok
var _ = MyIntPtr((*MyInt)((*int)(ip))) // ok 真特么的 复杂!!!!!!!!!!!!

3. 通道相关的类型转换规则

给定一个通道值x,假设它的类型Tx是一个双向通道类型,T也是一个通道类型(无论是双向的还是单向的)。如果Tx和T的元素类型相同并且它们中至少有一个为无名类型,则x可以被隐式转换为类型T。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

func main() {
type C chan string
type C1 chan<- string
type C2 <-chan string

var ca C
var cb chan string

cb = ca // ok,因为底层类型相同
ca = cb // ok,因为底层类型相同

// 这4行都满足此第3条转换规则的条件。
var _, _ chan<- string = ca, cb // ok
var _, _ <-chan string = ca, cb // ok
var _ C1 = cb // ok
var _ C2 = cb // ok

// 类型C的值不能直接转换为类型C1或C2。
/*
var _ = C1(ca) // compile error
var _ = C2(ca) // compile error
*/

// 但是类型C的值可以间接转换为类型C1或C2。
var _ = C1((chan<- string)(ca)) // ok
var _ = C2((<-chan string)(ca)) // ok
var _ C1 = (chan<- string)(ca) // ok
var _ C2 = (<-chan string)(ca) // ok
}

4. 和接口实现相关的类型转换规则

给定一个值x和一个接口类型I,如果x的类型(或者默认类型)为Tx并且类型Tx实现了接口类型I,则x可以被隐式转换为类型I。 

此转换的结果为一个类型为I的接口值。此接口值包裹了

x的一个副本(如果Tx是一个非接口值);
x的动态值的一个副本(如果Tx是一个接口值)。

5. 类型不确定值相关的类型转换规则

如果一个类型不确定值可以表示为类型T的值,则它可以被隐式转换为类型T。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18


package main

func main() {
var _ []int = nil
var _ map[string]int = nil
var _ chan string = nil
var _ func()() = nil
var _ *bool = nil
var _ interface{} = nil

var _ int = 123.0
var _ float64 = 123
var _ int32 = 1.23e2
var _ int8 = 1 + 0i
}

6. 常量的类型转换结果一般仍然是一个常量。

(除了下面第8条规则中将介绍的字符串转换为字节切片或者码点切片的情况。)

给定一个常量值x和一个类型T,如果x可以表示成类型T的一个值,则x可以被显式地转换为类型T;
特别地,如果x是一个类型不确定值,则它可以被隐式转换为类型T。

一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

func main() {
const I = 123
const I1, I2 int8 = 0x7F, -0x80
const I3, I4 int8 = I, 0.0

const F = 0.123456789
const F32 float32 = F
const F32b float32 = I
const F64 float64 = F
const F64b = float64(I3) // 这里必须显式转换

const C1, C2 complex64 = F, I
const I5 = int(C2) // 这里必须显式转换
}

7. 非常量数值转换规则

非常量浮点数和整数值可以被显式转换为任何浮点数和整数类型。
非常量复数值可以被显式转换为任何复数类型。

注意,

非常量复数值不能被转换为浮点数或整数类型。
非常量浮点数和整数值不能被转换为复数类型。
在非常量数值的转换过程中,溢出和舍入是允许的。当一个浮点数被转换为整数时,小数部分将被舍弃(向零靠拢)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

package main

import "fmt"

func main() {
var a, b = 1.6, -1.6 // 类型均为float64
fmt.Println(int(a), int(b)) // 1 -1

var i, j int16 = 0x7FFF, -0x8000
fmt.Println(int8(i), uint16(j)) // -1 32768

var c1 complex64 = 1 + 2i
var _ = complex128(c1)
}


8. 字符串相关的转换规则

如果一个值的类型(或者默认类型)为一个整数类型,则此值可以被当作一个码点值(rune值)显式转换为任何字符串类型。
一个字符串可以被显式转换为一个字节切片类型,反之亦然。 字节切片类型是指底层类型为[]byte的类型。
一个字符串可以被显式转换为一个码点切片类型,反之亦然。 码点切片类型是指底层类型为[]rune的类型。
请阅读字符串一文获取更多详情和示例。

9. 切片相关的类型转换规则

从Go 1.17开始,一个切片可以被转化为一个相同元素类型的数组的指针类型。 但是如果数组的长度大于被转化切片的长度,则将导致恐慌产生。
这里有一个例子。

10. 非类型安全指针相关的类型转换规则

非类型安全指针类型是指底层类型为unsafe.Pointer的类型。

任何类型安全指针类型的值可以被显式转化为一个非类型安全指针类型,反之亦然。
任何uintptr值可以被显式转化为一个非类型安全指针类型,反之亦然。
请阅读非类型安全指针一文获取详情和示例。

赋值规则

赋值可以看作是隐式类型转换。 各种隐式转换规则在上一节中已经列出。

除了这些规则,赋值语句中的目标值必须为一个可寻址的值、一个映射元素表达式或者一个空标识符。

在一个赋值中,源值被复制给了目标值。精确地说,源值的直接部分被复制给了目标值。

注意:函数传参和结果返回其实都是赋值。

值比较规则

Go白皮书 https://golang.google.cn/ref/spec#Comparison_operators 提到:

在任何比较中,第一个比较值必须能被赋值给第二个比较值的类型,或者反之。

所以,值比较规则和赋值规则非常相似。 换句话说,两个值是否可以比较取决于其中一个值是否可以隐式转换为另一个值的类型。 很简单?此规则描述基本正确,但是存在另外一条优先级更高的规则:

如果一个比较表达式中的两个比较值均为类型确定值,则它们的类型必须都属于可比较类型。
https://gfw.go101.org/article/type-system-overview.html#types-not-support-comparison

注意,尽管切片/映射/函数类型为不可比较类型,但是它们的值可以和类型不确定的预声明nil标识符比较。

上述规则并未覆盖所有的情况。如果两个值均为类型不确定值,它们可以比较吗?这种情况的规则比较简单:

两个类型不确定的布尔值可以相互比较。
两个类型不确定的数字值可以相互比较。
两个类型不确定的字符串值可以相互比较。
两个类型不确定的数字值的比较结果服从直觉。

注意,两个类型不确定的nil值不能相互比较。

任何比较的结果均为一个类型不确定的布尔值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

// 一些类型为不可比较类型的变量。
var s []int
var m map[int]int
var f func()()
var t struct {x []int}
var a [5]map[int]int

func main() {
// 这些比较编译不通过。
/*
_ = s == s
_ = m == m
_ = f == f
_ = t == t
_ = a == a
_ = nil == nil
_ = s == interface{}(nil)
_ = m == interface{}(nil)
_ = f == interface{}(nil)
*/

// 这些比较编译都没问题。
_ = s == nil
_ = m == nil
_ = f == nil
_ = 123 == interface{}(nil)
_ = true == interface{}(nil)
_ = "abc" == interface{}(nil)
}

两个值是如何进行比较的?

假设两个值可以相互比较,并且它们的类型同为T。 (如果它们的类型不同,则其中一个可以转换为另一个的类型。这里我们不考虑两者均为类型不确定值的情形。)

如果T是一个布尔类型,则这两个值只有在它们同为true或者false的时候比较结果才为true。
如果T是一个整数类型,则这两个值只有在它们在内存中的表示完全一致的情况下比较结果才为true。
如果T是一个浮点数类型, 则这两个值只要满足下面任何一种情况,它们的比较结果就为true:
    它们都为+Inf;
    它们都为-Inf;
    它们都为-0.0或者都为+0.0。
    它们都不是NaN并且它们在内存中的表示完全一致。
如果T是一个复数类型,则这两个值只有在它们的实部和虚部均做为浮点数进行进行比较的结果都为true的情况下比较结果才为true。
如果T是一个指针类型(类型安全或者非类型安全),则这两个值只有在它们所表示的地址值相等或者它们都为nil的情况下比较结果才为true。
如果T是一个通道类型,则这两个值只有在它们引用着相同的底层内部通道或者它们都为nil时比较结果才为true。
如果T是一个结构体类型,则它们的相应字段将逐对进行比较。只要有一对字段不相等,这两个结构体值就不相等。
如果T是一个数组类型,则它们的相应元素将逐对进行比较。只要有一对元素不相等,这两个结构体值就不相等。
如果T是一个接口类型,请参阅两个接口值是如何进行比较的。
如果T是一个字符串类型,请参阅两个字符串值是如何进行比较的。

请注意,动态类型均为同一个不可比较类型的两个接口值的比较将产生一个恐慌。比如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

package main

func main() {
type T struct {
a interface{}
b int
}
var x interface{} = []int{}
var y = T{a: x}
var z = [3]T{{a: y}}

// 这三个比较均会产生一个恐慌。
_ = x == x
_ = y == y
_ = z == z
}










https://gfw.go101.org/

概念:具名类型和无名类型(named type and unnamed type)

在Go 1.9之前,具名类型这个术语在Go白皮书中是精确定义的。 
在那时,一个具名类型被定义为一个可以用标识符表示的类型。 
随着在Go 1.9中引入了自定义类型别名(见下一节),
具名类型这个术语被从白皮书中删除了;取而代之的是定义类型。 
随着Go 1.18中引入了自定义泛型,具名类型这个术语又被重新加回到白皮书。

一个具名类型可能为

一个预声明类型;
一个定义(非自定义泛型)类型;
一个(泛型类型的)实例化类型;
一个类型参数类型(使用在自定义泛型中)。

其它类型称为无名类型。一个无名类型肯定是一个组合类型(反之则未必)。

语法:类型别名声明(type alias declaration)

从Go 1.9开始,我们可以使用下面的语法来声明自定义类型别名。此语法和类型定义类似,但是请注意每个类型描述中多了一个等号=。

1
2
3
4
5
6
7
8
9

type (
Name = string
Age = int
)

type table = map[string]int
type Table = map[Name]Age

类型别名也必须为标识符。同样地,类型别名可以被声明在函数体内。

在上面的类型别名声明的例子中,Name是内置类型string的一个别名,它们表示同一个类型。 同样的关系对下面的几对类型表示也成立:

别名Age和内置类型int。
别名table和映射类型map[string]int。
别名Table和映射类型map[Name]Age。

事实上,文字表示形式map[string]int和map[Name]Age也表示同一类型。 所以,table和Table一样表示同一个类型。

注意:尽管一个类型别名有一个名字,但是它可能表示一个无名类型。 比如,table和Table这两个别名都表示同一个无名类型map[string]int。