类型和值的属性

Properties of types and values 类型和值的属性

原文:https://go.dev/ref/spec#Properties_of_types_and_values

Underlying types 底层类型/基本类型

​ 每个类型T都有一个底层类型。如果T是预先声明的布尔型、数值型或字符串型之一,或者是一个类型字面量,那么对应的底层类型就是T本身。否则,T的底层类型是T在其声明中所指的类型的底层类型。对于类型参数,则是其类型约束的底层类型,它总是一个接口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
type (
	A1 = string
	A2 = A1
)

type (
	B1 string
	B2 B1
	B3 []B1
	B4 B3
)

func f[P any](x P) { … }

stringA1A2B1B2的底层类型是 string。[]B1B3B4的底层类型是[]B1P的底层类型是interface{}

Core types 核心类型

​ 每个非接口类型T都有一个核心类型,它与T底层类型相同。

​ 如果满足以下条件之一,那么接口T就有一个核心类型:

  1. 存在一个单一的类型U,它是T类型集中所有类型的底层类型;或者
  2. T的类型集只包含具有相同元素类型E通道类型,并且所有定向通道具有相同的方向。

其他接口都没有核心类型。

接口的核心类型取决于满足的条件:

  1. 类型U;或者
  2. 如果T只包含双向通道,则为类型chan E;或者为 chan<- E<-chan E类型,这取决于现存定向信道的方向。

根据定义,核心类型绝不是已定义的类型类型参数接口类型

具有核心类型的接口的示例:

1
2
3
4
5
6
7
8
type Celsius float32
type Kelvin  float32

interface{ int }                          // int
interface{ Celsius|Kelvin }               // float32
interface{ ~chan int }                    // chan int
interface{ ~chan int|~chan<- int }        // chan<- int
interface{ ~[]*data; String() string }    // []*data

没有核心类型的接口的示例:

interface{}                               // no single underlying type
interface{ Celsius|float64 }              // no single underlying type
interface{ chan int | chan<- string }     // channels have different element types
interface{ <-chan int | chan<- int }      // directional channels have different directions

​ 一些操作(切片表达式追加和复制)依赖于稍微宽松的核心类型形式,这些核心类型接受字节切片和字符串。具体来说,如果正好有两种类型,[]bytestring,它们是接口T的类型集中所有类型的底层类型,那么T的核心类型就被称为bytestring

具有bytestring核心类型的接口的例子:

interface{ int }                          // int (same as ordinary core type) => int (与普通核心类型相同)
interface{ []byte | string }              // bytestring
interface{ ~[]byte | myString }           // bytestring

注意bytestring不是一个真正的类型;它不能用来声明变量(是由其他类型组成的)。它的存在只是为了描述一些从字节序列中读取的操作的行为,这些字节序列可能是字节切片或字符串。

Type identity 类型一致性

两种类型要么相同,要么不同。

命名类型总是与任何其他类型不同。否则,如果两个类型的底层类型字面量在结构上是一致的,那么这两个类型就是相同的;也就是说,它们有相同的字面量结构,相应的组成部分拥有一致的类型。详细来说:

  • 如果两个数组类型有一致的元素类型和相同的数组长度,那么它们就是一致的。
  • 如果两个切片类型有一致的元素类型,那么它们就是一致的。
  • 如果两个结构体类型具有相同的字段序列,并且相应的字段具有一致的名称、一致的类型和一致的标签,那么它们就是一致的。来自不同包的不可导出的字段名总是不同的。
  • 如果两个指针类型有一致的基本类型,那么它们就是一致的。
  • 如果两个函数类型有相同数量的参数和结果值,并且相应的参数和结果类型是相同的,并且两个函数要么都是可变的,要么都不是。参数和结果名称不需要匹配。
  • 如果两个接口类型定义了相同的类型集,那么它们就是一致的。
  • 如果两个映射类型有一致的键和元素类型,它们就是一致的。
  • 如果两个通道类型有一致的元素类型和相同的方向,那么它们是一致的。
  • 如果两个实例化的类型的定义类型和所有类型参数都是一致的,那么它们就是一致的。

给出声明:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
type (
	A0 = []string
	A1 = A0
	A2 = struct{ a, b int }
	A3 = int
	A4 = func(A3, float64) *A0
	A5 = func(x int, _ float64) *[]string

	B0 A0
	B1 []string
	B2 struct{ a, b int }
	B3 struct{ a, c int }
	B4 func(int, float64) *B0
	B5 func(x int, y float64) *A1

	C0 = B0
	D0[P1, P2 any] struct{ x P1; y P2 }
	E0 = D0[int, string]
)

这些类型是一致的:

A0, A1, and []string
A2 and struct{ a, b int }
A3 and int
A4, func(int, float64) *[]string, and A5

B0 and C0
D0[int, string] and E0
[]int and []int
struct{ a, b *B5 } and struct{ a, b *B5 }
func(x int, y float64) *[]string, func(int, float64) (result *[]string), and A5

B0B1是不同的,因为它们是由不同的类型定义所创建的新类型;func(int, float64) *B0func(x int, y float64) *[]string是不同的,因为B0[]string是不同的;P1P2是不同,因为它们是不同的类型参数。D0[int, string]struct{ x int; y string }是不同的,因为前者是一个实例化的定义类型,而后者是一个类型字面量(但它们仍然是可分配的)。

Assignability 可分配性

​ 在以下这些情况中,V类型的值x是可以分配给T类型的变量("x可以分配给T"):

  • VT是一致的。
  • VT有一致的底层类型,但不是类型参数,并且VT中至少有一个不是命名类型
  • VT是具有一致元素类型的通道类型,V是一个双向通道,并且VT中至少有一个不是命名类型
  • T是接口类型,但不是一个类型参数,并且x实现T
  • x是预先声明的标识符nil,并且T是一个指针、函数、切片、映射、通道或接口类型,但不是一个类型参数。
  • x是可由T类型的值表示的非类型化的常量

​ 除此之外,如果x的类型VT是类型参数,并且满足以下条件之一,那么x也可以分配给类型T的变量:

  • x是预先声明的标识符nilT是类型参数,并且x可以分配给T的类型集中的每个类型。
  • V不是命名类型T是一个类型参数,并且x可以分配给T的类型集中的每个类型。
  • V是类型参数,T不是命名类型,而V的类型集中的每个类型的值都可以分配给T

Representability 可表示性

​ 如果满足以下条件之一,常量x就可以被T类型的值所表示,其中T不是类型参数

  • x在由T所确定的值的集合中。
  • T浮点类型,并且x可以被舍入到T的精度而不会溢出。四舍五入使用的是IEEE 754的四舍五入到偶数的规则,但IEEE的负0被进一步简化为无符号0。请注意,常量值绝不会出现IEEE负零、NaN或无穷大。
  • T是复数类型,x组成real(x)imag(x)可以用T的组成类型(float32float64)的值表示。

​ 如果T是类型参数,并且x可以由T的类型集中的每个类型的值来表示,那么x就可以由T类型的值来表示。

x                   T           x is representable by a value of T because

'a'                 byte        97 is in the set of byte values
97                  rune        rune is an alias for int32, and 97 is in the set of 32-bit integers
"foo"               string      "foo" is in the set of string values
1024                int16       1024 is in the set of 16-bit integers
42.0                byte        42 is in the set of unsigned 8-bit integers
1e10                uint64      10000000000 is in the set of unsigned 64-bit integers
2.718281828459045   float32     2.718281828459045 rounds to 2.7182817 which is in the set of float32 values
-1e-1000            float64     -1e-1000 rounds to IEEE -0.0 which is further simplified to 0.0
0i                  int         0 is an integer value
(42 + 0i)           float32     42.0 (with zero imaginary part) is in the set of float32 values


x                   T           x is not representable by a value of T because

0                   bool        0 is not in the set of boolean values
'a'                 string      'a' is a rune, it is not in the set of string values
1024                byte        1024 is not in the set of unsigned 8-bit integers
-1                  uint16      -1 is not in the set of unsigned 16-bit integers
1.1                 int         1.1 is not an integer value
42i                 float32     (0 + 42i) is not in the set of float32 values
1e1000              float64     1e1000 overflows to IEEE +Inf after rounding

Method sets 方法集

​ 类型的方法集确定了该类型的操作数可以调用的方法。每个类型都有一个与之相关的(可能是空的)方法集。

  • 定义类型T的方法集包括所有用接收器类型T声明的方法
  • 指向定义类型T的指针(T既不是指针也不是接口)的方法集是与接收器*TT一起声明的所有方法的集合。
  • 接口类型的方法集是该接口类型集中每个类型的方法集的交集(最终的方法集通常只是接口中声明的方法集)。

​ 进一步的规则,应用于包含嵌入字段的结构体(和结构体指针),会在关于结构体类型的章节中描述。任何其他类型都有一个空的方法集。

​ 在方法集中,每个方法必须有一个唯一的空白方法名

最后修改 June 5, 2023: 更新标准库 (33f199b)