Go 语言规范学习(3)
文章目录
- Properties of types and values
- Representation of values
- Underlying types【底层类型】
- Core types【核心类型】
- Type identity
- Assignability
- Representability
- Method sets
- Blocks
- Declarations and scope
- Label scopes
- Blank identifier
- Predeclared identifiers
- Exported identifiers
- Uniqueness of identifiers
- Constant declarations
- Iota
- Type declarations
- Alias declarations
- Type definitions
- Type parameter declarations
- Type constraints
- Satisfying a type constraint
- Variable declarations
- Short variable declarations
- Function declarations
- Method declarations
Properties of types and values
Representation of values
值的表示。重要!
Values of predeclared types (see below for the interfaces any and error), arrays, and structs are self-contained: Each such value contains a complete copy of all its data, and variables of such types store the entire value. For instance, an array variable provides the storage (the variables) for all elements of the array. The respective zero values are specific to the value’s types; they are never nil.
Non-nil pointer, function, slice, map, and channel values contain references to underlying data which may be shared by multiple values:
- A pointer value is a reference to the variable holding the pointer base type value.
- A function value contains references to the (possibly anonymous) function and enclosed variables.
- A slice value contains the slice length, capacity, and a reference to its underlying array.
- A map or channel value is a reference to the implementation-specific data structure of the map or channel.
An interface value may be self-contained or contain references to underlying data depending on the interface’s dynamic type. The predeclared identifier nil is the zero value for types whose values can contain references.
When multiple values share underlying data, changing one value may change another. For instance, changing an element of a slice will change that element in the underlying array for all slices that share the array.
Underlying types【底层类型】
Each type T has an underlying type: If T is one of the predeclared boolean, numeric, or string types, or a type literal【类型字面值】, the corresponding underlying type is T itself. Otherwise, T’s underlying type is the underlying type of the type to which T refers in its declaration.
注意:因为接口类型在类型字面值中,所有接口类型的底层类型是接口自身!
For a type parameter that is the underlying type of its type constraint, which is always an interface.
类型参数的底层类型=类型约束的底层类型。由于类型约束是一个接口类型,所以类型参数的底层类型也是接口类型。
type (A1 = stringA2 = A1
)type (B1 stringB2 B1B3 []B1B4 B3
)func f[P any](x P) { … }
The underlying type of string, A1, A2, B1, and B2 is string. The underlying type of []B1, B3, and B4 is []B1. The underlying type of P is interface{}.
注意:B3 的底层类型是 []B1 而不是 []string,因为 []B1 是类型字面值!
类型参数 P 的底层类型是 any 的底层类型,any 的底层类型是 interface{}。
Core types【核心类型】
Each non-interface type T has a core type, which is the same as the underlying type of T.
每个非接口类型都有一个核心类型,接口类型不一定有一个核心类型。
An interface T has a core type if one of the following conditions is satisfied:
- There is a single type
Uwhich is the underlying type of all types in the type set ofT; or - the type set of
Tcontains only channel types with identical element typeE, and all directional channels have the same direction.
No other interfaces have a core type.
The core type of an interface is, depending on the condition that is satisfied, either:
- the type
U; or - the type
chan EifTcontains only bidirectional channels, or the typechan<- Eor<-chan Edepending on the direction of the directional channels present.
By definition, a core type is never a defined type, type parameter, or interface type.
By construction, an interface’s type set never contains an interface type.
对于非接口类型,它的核心类型是底层类型,而底层类型不可能是定义的类型,类型参数和接口类型。
对于接口类型,因为接口类型的类型集里不会包含一个接口类型,所以接口类型的核心类型不可能是接口类型。接口类型集里所有类型的底层类型也不可能是定义的类型和类型参数。
Examples of interfaces with core types:
type Celsius float32
type Kelvin float32interface{ int } // int
interface{ Celsius|Kelvin } // float32
interface{ ~chan int } // chan int
interface{ ~chan int|~chan<- int } // chan<- int
interface{ ~[]*data; String() string } // []*data
Examples of interfaces without core types:
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
Some operations (slice expressions, append and copy) rely on a slightly more loose form of core types which accept byte slices and strings. Specifically, if there are exactly two types, []byte and string, which are the underlying types of all types in the type set of interface T, the core type of T is called bytestring.
Examples of interfaces with bytestring core types:
interface{ int } // int (same as ordinary core type)
interface{ []byte | string } // bytestring
interface{ ~[]byte | myString } // bytestring
Note that bytestring is not a real type; it cannot be used to declare variables or compose other types. It exists solely to describe the behavior of some operations that read from a sequence of bytes, which may be a byte slice or a string.
Type identity
Two types are either identical or different.
A named type is always different from any other type.
Predeclared types, defined types, and type parameters are called named types【命名类型】. An alias denotes a named type if the type given in the alias declaration is a named type.【注意:类型字面值不是命名类型,比如[]int、struct{} 等是未命名类型】
byte类型和uint8类型是相同的类型,rune类型和int32类型是相同的类型。
Otherwise, two types are identical if their underlying type literals are structurally equivalent; that is, they have the same literal structure and corresponding components have identical types. In detail:
- Two array types are identical if they have identical element types and the same array length.
- Two slice types are identical if they have identical element types.
- Two struct types are identical if they have the same sequence of fields, and if corresponding pairs of fields have the same names, identical types, and identical tags, and are either both embedded or both not embedded. Non-exported field names from different packages are always different.
// 包 pkg1
type S1 struct {name stringAge int
}
// 包 pkg2
type S2 struct {name stringAge int
}
S1 和 S2 不是相同类型,因为它们的非导出字段 name 来自不同包,被视为不同字段。
- Two pointer types are identical if they have identical base types.
- Two function types are identical if they have the same number of parameters and result values, corresponding parameter and result types are identical, and either both functions are variadic or neither is. Parameter and result names are not required to match.
- Two interface types are identical if they define the same type set.
- Two map types are identical if they have identical key and element types.
- Two channel types are identical if they have identical element types and the same direction.
- Two instantiated types are identical if their defined types and all type arguments are identical.
Given the declarations
type (A0 = []stringA1 = A0A2 = struct{ a, b int }A3 = intA4 = func(A3, float64) *A0A5 = func(x int, _ float64) *[]stringB0 A0B1 []stringB2 struct{ a, b int }B3 struct{ a, c int }B4 func(int, float64) *B0B5 func(x int, y float64) *A1C0 = B0D0[P1, P2 any] struct{ x P1; y P2 }E0 = D0[int, string]
)
these types are identical:
A0, A1, and []string
A2 and struct{ a, b int }
A3 and int
A4, func(int, float64) *[]string, and A5B0 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
B0 and B1 are different because they are new types created by distinct type definitions; 【定义的类型是命名的类型,都和其他类型不同】
func(int, float64) *B0 and func(x int, y float64) *[]string are different because B0 is different from []string; 【B0是一个定义的类型,和其他类型都不同】
and P1 and P2 are different because they are different type parameters. 【类型参数是命名的类型,和其他类型都不同】
D0[int, string] and struct{ x int; y string } are different because the former is an instantiated defined type while the latter is a type literal (but they are still assignable). 【Instantiating a type results in a new non-generic named type。实例化后的类型是一个新的命名的类型,都和其他类型不同】
Assignability
A value x of type V is assignable to a variable of type T (“x is assignable to T”) if one of the following conditions applies【值x的类型是V,变量的类型是T,V和T可能是类型参数】:
VandTare identical.VandThave identical underlying types but are not type parameters and at least one ofVorTis not a named type. 【 V 和 T 的底层类型(underlying types)相同,且 V 和 T 都不是类型参数(type parameters),并且至少有一个不是命名类型(named type)。】
在赋值规则中,类型参数不能直接参与底层类型的比较,因为:
- 类型参数的具体类型在编译时才能确定,无法在赋值时静态判断其底层类型是否匹配。
- 泛型的类型约束(
Constraint)已经确保了类型参数的有效性,因此不需要在赋值阶段额外检查。
VandTare channel types with identical element types,Vis a bidirectional channel, and at least one ofVorTis not a named type.Tis an interface type, but not a type parameter, andximplementsT. 【变量的类型是接口类型,值x实现了这个接口】xis the predeclared identifiernilandTis a pointer, function, slice, map, channel, or interface type, but not a type parameter.xis an untyped constant representable by a value of typeT.
Additionally, if x’s type V or T are type parameters, x is assignable to a variable of type T if one of the following conditions applies:
xis the predeclared identifiernil,Tis a type parameter, andxis assignable to each type inT’s type set.Vis not a named type,Tis a type parameter, andxis assignable to each type inT’s type set. 【??x 怎么可以赋值给多个类型??】Vis a type parameter andTis not a named type, and values of each type inV’s type set are assignable toT.
这里不给举个例子,看不明白
Representability
A constant x is representable by a value of type T, where T is not a type parameter, if one of the following conditions applies:
xis in the set of values determined byT.Tis a floating-point type andxcan be rounded toT’s precision without overflow. Rounding uses IEEE 754 round-to-even rules but with an IEEE negative zero further simplified to an unsigned zero. Note that constant values never result in an IEEE negative zero, NaN, or infinity.Tis a complex type, andx’s componentsreal(x)andimag(x)are representable by values ofT’s component type (float32orfloat64).
If T is a type parameter, x is representable by a value of type T if x is representable by a value of each type in T’s type set. 【??这里也不给举个例子,无语。。。】
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 because0 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
The method set of a type determines the methods that can be called on an operand of that type. Every type has a (possibly empty) method set associated with it:
- The method set of a defined type
Tconsists of all methods declared with receiver typeT. - The method set of a pointer to a defined type
T(whereTis neither a pointer nor an interface) is the set of all methods declared with receiver*TorT. - The method set of an interface type is the intersection of the method sets of each type in the interface’s type set (the resulting method set is usually just the set of declared methods in the interface).
Further rules apply to structs (and pointer to structs) containing embedded fields, as described in the section on struct types. Any other type has an empty method set.
In a method set, each method must have a unique non-blank method name.
Blocks
A block is a possibly empty sequence of declarations and statements within matching brace brackets.
Block = "{" StatementList "}" .
StatementList = { Statement ";" } .
In addition to explicit blocks in the source code, there are implicit blocks:
- The universe block encompasses all Go source text.
- Each package has a package block containing all Go source text for that package.
- Each file has a file block containing all Go source text in that file.
- Each “if”, “for”, and “switch” statement is considered to be in its own implicit block.
- Each clause in a “switch” or “select” statement acts as an implicit block.
Blocks nest and influence scoping.
Declarations and scope
重要!
A declaration binds a non-blank identifier to a constant, type, type parameter, variable, function, label, or package. Every identifier in a program must be declared. No identifier may be declared twice in the same block, and no identifier may be declared in both the file and package block.
The blank identifier may be used like any other identifier in a declaration, but it does not introduce a binding and thus is not declared.
In the package block, the identifier init may only be used for init function declarations, and like the blank identifier it does not introduce a new binding.
Declaration = ConstDecl | TypeDecl | VarDecl .
TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
The scope of a declared identifier is the extent of source text in which the identifier denotes the specified constant, type, variable, function, label, or package.
Go is lexically scoped using blocks:
- The scope of a predeclared identifier is the universe block.
- The scope of an identifier denoting a constant, type, variable, or function (but not method) declared at top level (outside any function) is the package block.
- The scope of the package name of an imported package is the file block of the file containing the import declaration.
- The scope of an identifier denoting a method receiver, function parameter, or result variable is the function body.
- The scope of an identifier denoting a type parameter of a function or declared by a method receiver begins after the name of the function and ends at the end of the function body.
- The scope of an identifier denoting a type parameter of a type begins after the name of the type and ends at the end of the TypeSpec.
- The scope of a constant or variable identifier declared inside a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl for short variable declarations) and ends at the end of the innermost containing block.
- The scope of a type identifier declared inside a function begins at the identifier in the TypeSpec and ends at the end of the innermost containing block.
An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the entity declared by the inner declaration.
The package clause is not a declaration; the package name does not appear in any scope. Its purpose is to identify the files belonging to the same package and to specify the default package name for import declarations.
Label scopes
Labels are declared by labeled statements and are used in the “break”, “continue”, and “goto” statements. It is illegal to define a label that is never used. In contrast to other identifiers, labels are not block scoped and do not conflict with identifiers that are not labels. The scope of a label is the body of the function in which it is declared and excludes the body of any nested function.
Blank identifier
The blank identifier is represented by the underscore character _. It serves as an anonymous placeholder instead of a regular (non-blank) identifier and has special meaning in declarations, as an operand, and in assignment statements.
Predeclared identifiers
The following identifiers are implicitly declared in the universe block [Go 1.18] [Go 1.21]:
Types:any bool byte comparablecomplex64 complex128 error float32 float64int int8 int16 int32 int64 rune stringuint uint8 uint16 uint32 uint64 uintptrConstants:true false iotaZero value:nilFunctions:append cap clear close complex copy delete imag lenmake max min new panic print println real recover
Exported identifiers
An identifier may be exported to permit access to it from another package. An identifier is exported if both:
- the first character of the identifier’s name is a Unicode uppercase letter (Unicode character category Lu); and
- the identifier is declared in the package block or it is a field name or method name.
All other identifiers are not exported.
Uniqueness of identifiers
Given a set of identifiers, an identifier is called unique if it is different from every other in the set. Two identifiers are different if they are spelled differently, or if they appear in different packages and are not exported. Otherwise, they are the same.
不同包里定义的标识符即时名字一样也是不同的。
Constant declarations
A constant declaration binds a list of identifiers (the names of the constants) to the values of a list of constant expressions. The number of identifiers must be equal to the number of expressions, and the nth identifier on the left is bound to the value of the nth expression on the right.
ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] .IdentifierList = identifier { "," identifier } .
ExpressionList = Expression { "," Expression } .
If the type is present, all constants take the type specified, and the expressions must be assignable to that type, which must not be a type parameter. If the type is omitted, the constants take the individual types of the corresponding expressions. If the expression values are untyped constants, the declared constants remain untyped and the constant identifiers denote the constant values. For instance, if the expression is a floating-point literal, the constant identifier denotes a floating-point constant, even if the literal’s fractional part is zero.
const Pi float64 = 3.14159265358979323846
const zero = 0.0 // untyped floating-point constant
const (size int64 = 1024eof = -1 // untyped integer constant
)
const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", untyped integer and string constants
const u, v float32 = 0, 3 // u = 0.0, v = 3.0
Within a parenthesized const declaration list the expression list may be omitted from any but the first ConstSpec. Such an empty list is equivalent to the textual substitution of the first preceding non-empty expression list and its type if any. Omitting the list of expressions is therefore equivalent to repeating the previous list. The number of identifiers must be equal to the number of expressions in the previous list. Together with the iota constant generator this mechanism permits light-weight declaration of sequential values:
const (Sunday = iotaMondayTuesdayWednesdayThursdayFridayPartydaynumberOfDays // this constant is not exported
)
Iota
Within a constant declaration, the predeclared identifier iota represents successive【连续的】 untyped integer constants. Its value is the index of the respective ConstSpec in that constant declaration, starting at zero. It can be used to construct a set of related constants:
const (c0 = iota // c0 == 0c1 = iota // c1 == 1c2 = iota // c2 == 2
)const (a = 1 << iota // a == 1 (iota == 0)b = 1 << iota // b == 2 (iota == 1)c = 3 // c == 3 (iota == 2, unused)d = 1 << iota // d == 8 (iota == 3)
)const (u = iota * 42 // u == 0 (untyped integer constant)v float64 = iota * 42 // v == 42.0 (float64 constant)w = iota * 42 // w == 84 (untyped integer constant)
)const x = iota // x == 0
const y = iota // y == 0
By definition, multiple uses of iota in the same ConstSpec all have the same value:
const (bit0, mask0 = 1 << iota, 1<<iota - 1 // bit0 == 1, mask0 == 0 (iota == 0)bit1, mask1 // bit1 == 2, mask1 == 1 (iota == 1)_, _ // (iota == 2, unused)bit3, mask3 // bit3 == 8, mask3 == 7 (iota == 3)
)
This last example exploits the implicit repetition【隐式的重复】 of the last non-empty expression list.
Type declarations
A type declaration binds an identifier, the type name, to a type. Type declarations come in two forms: alias declarations and type definitions.
TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = AliasDecl | TypeDef .
Alias declarations
An alias declaration binds an identifier to the given type [Go 1.9].
AliasDecl = identifier [ TypeParameters ] "=" Type .
Within the scope of the identifier, it serves as an alias for the given type.
type (nodeList = []*Node // nodeList and []*Node are identical typesPolar = polar // Polar and polar denote identical types
)
If the alias declaration specifies type parameters [Go 1.24], the type name denotes a generic alias【泛型别名】. Generic aliases must be instantiated when they are used.
type set[P comparable] = map[P]bool
In an alias declaration the given type cannot be a type parameter.
type A[P any] = P // illegal: P is a type parameter
Type definitions
A type definition creates a new, distinct type with the same underlying type and operations as the given type and binds an identifier, the type name, to it.
TypeDef = identifier [ TypeParameters ] Type .
The new type is called a defined type. It is different from any other type, including the type it is created from.
type (Point struct{ x, y float64 } // Point and struct{ x, y float64 } are different typespolar Point // polar and Point denote different types
)type TreeNode struct {left, right *TreeNodevalue any
}type Block interface {BlockSize() intEncrypt(src, dst []byte)Decrypt(src, dst []byte)
}
A defined type may have methods associated with it. It does not inherit any methods bound to the given type, but the method set of an interface type or of elements of a composite type remains unchanged:
重要!
// A Mutex is a data type with two methods, Lock and Unlock.
type Mutex struct { /* Mutex fields */ }
func (m *Mutex) Lock() { /* Lock implementation */ }
func (m *Mutex) Unlock() { /* Unlock implementation */ }// NewMutex has the same composition as Mutex but its method set is empty.
type NewMutex Mutex// The method set of PtrMutex's underlying type *Mutex remains unchanged,
// but the method set of PtrMutex is empty.
type PtrMutex *Mutex// The method set of *PrintableMutex contains the methods
// Lock and Unlock bound to its embedded field Mutex.
type PrintableMutex struct {Mutex
}// MyBlock is an interface type that has the same method set as Block.
type MyBlock Block
Type definitions may be used to define different boolean, numeric, or string types and associate methods with them:
type TimeZone intconst (EST TimeZone = -(5 + iota)CSTMSTPST
)func (tz TimeZone) String() string {return fmt.Sprintf("GMT%+dh", tz)
}
If the type definition specifies type parameters, the type name denotes a generic type. Generic types must be instantiated when they are used.
// 泛型类型
type List[T any] struct {next *List[T]value T
}
In a type definition the given type cannot be a type parameter.
type T[P any] P // illegal: P is a type parameterfunc f[T any]() {type L T // illegal: T is a type parameter declared by the enclosing function
}
A generic type may also have methods associated with it. In this case, the method receivers must declare the same number of type parameters as present in the generic type definition.
// The method Len returns the number of elements in the linked list l.
func (l *List[T]) Len() int { … }
Type parameter declarations
A type parameter list declares the type parameters of a generic function or type declaration. The type parameter list looks like an ordinary function parameter list except that the type parameter names must all be present and the list is enclosed in square brackets rather than parentheses [Go 1.18].
TypeParameters = "[" TypeParamList [ "," ] "]" .
TypeParamList = TypeParamDecl { "," TypeParamDecl } .
TypeParamDecl = IdentifierList TypeConstraint .
All non-blank names in the list must be unique. Each name declares a type parameter, which is a new and different named type that acts as a placeholder for an (as of yet) unknown type in the declaration. The type parameter is replaced with a type argument upon instantiation of the generic function or type.
[P any]
[S interface{ ~[]byte|string }]
[S ~[]E, E any]
[P Constraint[int]]
[_ any]
Just as each ordinary function parameter has a parameter type, each type parameter has a corresponding (meta-)type which is called its type constraint.
A parsing ambiguity arises when the type parameter list for a generic type declares a single type parameter P with a constraint C such that the text P C forms a valid expression:
type T[P *C] …
type T[P (C)] …
type T[P *C|Q] …
…
上面的类型声明会被解析为一个数组类型的声明,有歧义。
In these rare cases, the type parameter list is indistinguishable from an expression and the type declaration is parsed as an array type declaration. To resolve the ambiguity, embed the constraint in an interface or use a trailing comma:
type T[P interface{*C}] …
type T[P *C,] …
Type parameters may also be declared by the receiver specification of a method declaration associated with a generic type.
Within a type parameter list of a generic type T, a type constraint may not (directly, or indirectly through the type parameter list of another generic type) refer to T.
type T1[P T1[P]] … // illegal: T1 refers to itself
type T2[P interface{ T2[int] }] … // illegal: T2 refers to itself
type T3[P interface{ m(T3[int])}] … // illegal: T3 refers to itself
type T4[P T5[P]] … // illegal: T4 refers to T5 and
type T5[P T4[P]] … // T5 refers to T4type T6[P int] struct{ f *T6[P] } // ok: reference to T6 is not in type parameter list
Type constraints
A type constraint is an interface that defines the set of permissible type arguments for the respective type parameter and controls the operations supported by values of that type parameter [Go 1.18].
TypeConstraint = TypeElem .
If the constraint is an interface literal of the form interface{E} where E is an embedded type element (not a method), in a type parameter list the enclosing interface{ … } may be omitted for convenience:
[T []P] // = [T interface{[]P}]
[T ~int] // = [T interface{~int}]
[T int|string] // = [T interface{int|string}]
type Constraint ~int // illegal: ~int is not in a type parameter list
The predeclared interface type comparable denotes the set of all non-interface types that are strictly comparable [Go 1.18].
Even though interfaces that are not type parameters are comparable, they are not strictly comparable and therefore they do not implement comparable. However, they satisfy comparable.
【接口类型不是严格可比较的,所以接口类型没有实现comparable】
Interface types that are not type parameters are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.
A type is strictly comparable if it is comparable and not an interface type nor composed of interface types. Specifically:
- Boolean, numeric, string, pointer, and channel types are strictly comparable.
- Struct types are strictly comparable if all their field types are strictly comparable.
- Array types are strictly comparable if their array element types are strictly comparable.
- Type parameters are strictly comparable if all types in their type set are strictly comparable.
A type T implements an interface I if
-
Tis not an interface and is an element of the type set ofI; or -
Tis an interface and the type set ofTis a subset of the type set ofI.
int // implements comparable (int is strictly comparable)
[]byte // does not implement comparable (slices cannot be compared)
interface{} // does not implement comparable (see above)
interface{ ~int | ~string } // type parameter only: implements comparable (int, string types are strictly comparable)
interface{ comparable } // type parameter only: implements comparable (comparable implements itself)
interface{ ~int | ~[]byte } // type parameter only: does not implement comparable (slices are not comparable)
interface{ ~struct{ any } } // type parameter only: does not implement comparable (field any is not strictly comparable)
The comparable interface and interfaces that (directly or indirectly) embed comparable may only be used as type constraints. They cannot be the types of values or variables, or components of other, non-interface types.
Satisfying a type constraint
A type argument T satisfies a type constraint C if T is an element of the type set defined by C; in other words, if T implements C. As an exception, a strictly comparable type constraint may also be satisfied by a comparable (not necessarily strictly comparable) type argument [Go 1.20]. More precisely:
A type T satisfies a constraint C if
TimplementsC; orCcan be written in the forminterface{ comparable; E }, whereEis a basic interface andTis comparable and implementsE.
type argument type constraint // constraint satisfactionint interface{ ~int } // satisfied: int implements interface{ ~int }
string comparable // satisfied: string implements comparable (string is strictly comparable)
[]byte comparable // not satisfied: slices are not comparable
any interface{ comparable; int } // not satisfied: any does not implement interface{ int }
any comparable // satisfied: any is comparable and implements the basic interface any
struct{f any} comparable // satisfied: struct{f any} is comparable and implements the basic interface any
any interface{ comparable; m() } // not satisfied: any does not implement the basic interface interface{ m() }
interface{ m() } interface{ comparable; m() } // satisfied: interface{ m() } is comparable and implements the basic interface interface{ m() }
为什么 any 满足 comparable?
根据规则的第二条,C 可以写成 interface{ comparable; E },其中 E 是一个基本接口。对于 comparable,它可以看作 interface{ comparable; any },因为 any 是一个基本接口。any 是 comparable 的,并且实现了 any,因此 any 满足 comparable 约束。
为什么 comparable 可以写成 interface{ comparable; any }?
按照接口类型的类型集定义: interface{ comparable; any } 的类型集是 comparable 和 any 类型集的交集,由于any类型集是所有的非接口类型,所以它们的交集就是 comparable 的类型集。由于 interface{ comparable; any } 和 comparable 的类型集相同,它们是相同的类型【按照接口类型是否相同的定义】!
Because of the exception in the constraint satisfaction rule, comparing operands of type parameter type may panic at run-time (even though comparable type parameters are always strictly comparable).
Variable declarations
A variable declaration creates one or more variables, binds corresponding identifiers to them, and gives each a type and an initial value.
VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
var i int
var U, V, W float64
var k = 0
var x, y float32 = -1, -2
var (i intu, v, s = 2.0, 3.0, "bar"
)
var re, im = complexSqrt(-1)
var _, found = entries[name] // map lookup; only interested in "found"
If a list of expressions is given, the variables are initialized with the expressions following the rules for assignment statements. Otherwise, each variable is initialized to its zero value.
If a type is present, each variable is given that type. Otherwise, each variable is given the type of the corresponding initialization value in the assignment. If that value is an untyped constant, it is first implicitly converted to its default type; if it is an untyped boolean value, it is first implicitly converted to type bool. The predeclared identifier nil cannot be used to initialize a variable with no explicit type.
var d = math.Sin(0.5) // d is float64
var i = 42 // i is int
var t, ok = x.(T) // t is T, ok is bool
var n = nil // illegal
Implementation restriction: A compiler may make it illegal to declare a variable inside a function body if the variable is never used.
Short variable declarations
A short variable declaration uses the syntax:
ShortVarDecl = IdentifierList ":=" ExpressionList .
It is shorthand for a regular variable declaration with initializer expressions but no types:
"var" IdentifierList "=" ExpressionList .
i, j := 0, 10
f := func() int { return 7 }
ch := make(chan int)
r, w, _ := os.Pipe() // os.Pipe() returns a connected pair of Files and an error, if any
_, y, _ := coord(p) // coord() returns three values; only interested in y coordinate
Unlike regular variable declarations, a short variable declaration may redeclare variables provided they were originally declared earlier in the same block (or the parameter lists if the block is the function body) with the same type, and at least one of the non-blank variables is new. As a consequence, redeclaration can only appear in a multi-variable short declaration. Redeclaration does not introduce a new variable; it just assigns a new value to the original. The non-blank variable names on the left side of := must be unique.
field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset) // redeclares offset
x, y, x := 1, 2, 3 // illegal: x repeated on left side of :=
Short variable declarations may appear only inside functions. In some contexts such as the initializers for “if”, “for”, or “switch” statements, they can be used to declare local temporary variables.
Function declarations
A function declaration binds an identifier, the function name, to a function.
FunctionDecl = "func" FunctionName [ TypeParameters ] Signature [ FunctionBody ] .
FunctionName = identifier .
FunctionBody = Block .
If the function’s signature declares result parameters, the function body’s statement list must end in a terminating statement.
func IndexRune(s string, r rune) int {for i, c := range s {if c == r {return i}}// invalid: missing return statement
}
If the function declaration specifies type parameters, the function name denotes a generic function. A generic function must be instantiated before it can be called or used as a value.
func min[T ~int|~float64](x, y T) T {if x < y {return x}return y
}
A function declaration without type parameters may omit the body. Such a declaration provides the signature for a function implemented outside Go, such as an assembly routine.
func flushICache(begin, end uintptr) // implemented externally
Method declarations
A method is a function with a receiver. A method declaration binds an identifier, the method name, to a method, and associates the method with the receiver’s base type.
MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] .
Receiver = Parameters .
The receiver is specified via an extra parameter section preceding the method name. That parameter section must declare a single non-variadic parameter, the receiver. Its type must be a defined type T or a pointer to a defined type T, possibly followed by a list of type parameter names [P1, P2, …] enclosed in square brackets. T is called the receiver base type. A receiver base type cannot be a pointer or interface type and it must be defined in the same package as the method. The method is said to be bound to its receiver base type and the method name is visible only within selectors for type T or *T.
A non-blank receiver identifier must be unique in the method signature. If the receiver’s value is not referenced inside the body of the method, its identifier may be omitted in the declaration. The same applies in general to parameters of functions and methods.
For a base type, the non-blank names of methods bound to it must be unique. If the base type is a struct type, the non-blank method and field names must be distinct.
Given defined type Point the declarations
func (p *Point) Length() float64 {return math.Sqrt(p.x * p.x + p.y * p.y)
}func (p *Point) Scale(factor float64) {p.x *= factorp.y *= factor
}
bind the methods Length and Scale, with receiver type *Point, to the base type Point.
If the receiver base type is a generic type, the receiver specification must declare corresponding type parameters for the method to use. This makes the receiver type parameters available to the method. Syntactically, this type parameter declaration looks like an instantiation of the receiver base type: the type arguments must be identifiers denoting the type parameters being declared, one for each type parameter of the receiver base type. The type parameter names do not need to match their corresponding parameter names in the receiver base type definition, and all non-blank parameter names must be unique in the receiver parameter section and the method signature. The receiver type parameter constraints are implied by the receiver base type definition: corresponding type parameters have corresponding constraints.
type Pair[A, B any] struct {a Ab B
}func (p Pair[A, B]) Swap() Pair[B, A] { … } // receiver declares A, B
func (p Pair[First, _]) First() First { … } // receiver declares First, corresponds to A in Pair
If the receiver type is denoted by (a pointer to) an alias, the alias must not be generic and it must not denote an instantiated generic type, neither directly nor indirectly via another alias, and irrespective of pointer indirections.
type GPoint[P any] = Point
type HPoint = *GPoint[int]
type IPair = Pair[int, int]func (*GPoint[P]) Draw(P) { … } // illegal: alias must not be generic
func (HPoint) Draw(P) { … } // illegal: alias must not denote instantiated type GPoint[int]
func (*IPair) Second() int { … } // illegal: alias must not denote instantiated type Pair[int, int]
相关文章:
Go 语言规范学习(3)
文章目录 Properties of types and valuesRepresentation of valuesUnderlying types【底层类型】Core types【核心类型】Type identityAssignabilityRepresentabilityMethod sets BlocksDeclarations and scopeLabel scopesBlank identifierPredeclared identifiersExported i…...
小林coding-17道Java基础面试题
1.说一下Java的特点?Java 的优势和劣势是什么?Java为什么是跨平台的?JVM、JDK、JRE三者关系?为什么Java解释和编译都有? jvm是什么?编译型语言和解释型语言的区别? Python和Java区别是什么? 2.八种基本的…...
ETCD --- 租约(Lease)详解
一、租约的核心概念 1. 租约(Lease) 一个租约是一个有时间限制的“授权”,绑定到键值对上。每个租约有一个唯一的ID(64位整数),通过etcdctl或客户端API创建。创建租约时需指定TTL(Time-To-Live),即租约的有效期(单位:秒)。客户端需定期向etcd发送续约(KeepAl…...
运筹说 第134期 | 矩阵对策的解法
上一期我们了解了矩阵对策的基本理论,包含矩阵对策的纯策略、矩阵对策的混合策略和矩阵对策的基本定理。 接下来小编将为大家介绍矩阵对策的解法,包括图解法、方程组法和线性规划法三种经典方法。 01 图解法 本节首先介绍矩阵对策的图解法,…...
3. 轴指令(omron 机器自动化控制器)——>MC_CamOut
机器自动化控制器——第三章 轴指令 15 MC_CamOut变量▶输入变量▶输出变量▶输入输出变量 功能说明▶时序图▶指令的中止▶重启运动指令▶多重启动运动指令▶异常 MC_CamOut 结束通过输入参数指定的轴的凸轮动作 指令名称FB/FUN图形表现ST表现MC_CamOut解除凸轮动作FBMC_Cam…...
TF32 与 FP32 的区别
TF32(Tensor Float 32)与FP32(单精度浮点数)是两种用于深度学习和高性能计算的浮点格式,其核心区别体现在精度、性能优化和应用场景上。以下是两者的详细对比分析: 一、位宽与结构差异 FP32的位宽结构 FP32…...
【大模型】视觉语言模型:Qwen2.5-VL的使用
官方github地址:https://github.com/QwenLM/Qwen2.5-VL 目录 Qwen家族的最新成员:Qwen2.5-VL 主要增强功能 模型架构更新 快速开始 使用Transformers聊天 Docker Qwen家族的最新成员:Qwen2.5-VL 主要增强功能 强大的文档解析功能&am…...
Web前端之UniApp、Taro、ReactNative和Flutter的区别
MENU 前言介绍及公司技术差异使用方法使用场景差异注意事项打包与部署差异框架应用实例结语 前言 在移动应用开发领域,跨平台框架已成为开发者的得力工具。UniApp、Taro、ReactNative和Flutter它们在Android(安卓)或iOS(苹果&…...
测试用例与需求脱节的修复方案
测试用例与需求脱节的问题可通过明确需求定义、加强需求追踪、建立有效沟通机制进行修复。其中,加强需求追踪尤为关键,能确保测试用例与实际需求的精确匹配,避免资源浪费和测试效果不佳。据行业研究,约70%的软件缺陷源于需求管理不…...
【Unity】 鼠标拖动物体移动速度跟不上鼠标,会掉落
错误示范: 一开始把移动的代码写到update里去了,发现物体老是掉(总之移动非常不流畅,体验感很差) void Update(){Ray ray Camera.main.ScreenPointToRay(Input.mousePosition);if (Physics.Raycast(ray, out RaycastHit hit, M…...
Ollama及HuggingFace路径环境变量设置
日常经常用到这俩的一些环境变量,特记录下来,如有错误,还请指正。 1. Ollama路径环境变量设置 Ollama 模型路径变量名为OLLAMA_MODELS,设置示例: 变量名示例OLLAMA_MODELS C:\Users\Administrator\.ollama\models D…...
VLAN 高级特性
VLAN Access 类型端口:只能属于 1 个 VLAN,发出数据时只能根据 PVID 剥离一个 VLAN Tag 入方向:针对没有 tag 的数据包打上 PVID 的 tag出方向:将 tag 为本接口 PVID 的数据包去掉 tag,发出数据。(只有在与…...
学习中学习的小tips(主要是学习苍穹外卖的一些学习)
目录 架构的细分 使用实体类来接收配置文件中的值 webMvcConfig类: jwt令牌 管理端的拦截器: JwtProperties: JwtTokenAdminInterceptor : 对密码加密操作 Redis: 分页查询 整体思想 为什么动态 SQL 推荐传实体…...
【极速版 -- 大模型入门到进阶】LORA:大模型轻量级微调
文章目录 🌊 有没有低成本的方法微调大模型?🌊 LoRA 的核心思想🌊 LoRA 的初始化和 r r r 的值设定🌊 LoRA 实战:LoraConfig参数详解 论文指路:LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE M…...
3d pose 指标和数据集
目录 3D姿态估计、3维重建指标: 数据集 EHF数据集 SMPL-X 3D姿态估计、3维重建指标: MVE、PMVE 和 p-MPJPE 都是用于评估3D姿态估计、三维重建等任务中预测结果与真实数据之间误差的指标。 MVE (Mean Vertex Error):是指模型重建过程中每个顶点的预测位置与真实位置之间…...
gogs私服详细配置
一.永久挂载方法 通过 /etc/fstab 实现绑定挂载(推荐) 绑定挂载(Bind Mount)允许将一个目录挂载到另一个目录,类似于软链接但更底层。 例如:将 /mnt/data 绑定到 /var/www/html,使两者内容同…...
1688商品详情接口:深度解析与应用实践
在电商领域,1688作为中国领先的B2B平台,拥有海量的商品信息。对于开发者、商家和数据分析师来说,获取1688商品的详细信息是实现数据分析、竞品研究、自动化管理和精准营销的重要手段。本文将详细介绍1688商品详情接口的使用方法、技术细节以及…...
线程同步——读写锁
Linux——线程同步 读写锁 目录 一、基本概念 1.1 读写锁的基本概念 1.2 读写锁的优点 1.3 读写锁的实现 1.4 代码实现 一、基本概念 线程同步中的读写锁(Read-Write Lock),也常被称为共享-独占锁(Shared-Exclusive Lock&a…...
邪性!Anaconda安装避坑细节Windows11
#工作记录 最近不断重置系统和重装Anaconda,配置的要累死,经几十次意料之外的配置状况打击之后,最后发现是要在在Anaconda安装时,一定要选“仅为我安装”这个选项,而不要选“为所有用户安装”这个选项。 选“仅为我安…...
【大模型】激活函数之SwiGLU详解
文章目录 1. Swish基本定义主要特点代码实现 2. GLU (Gated Linear Unit)基本定义主要特点代码实现 3. SwiGLU基本定义主要特点代码实现 参考资料 SWiGLU是大模型常用的激活函数,是2020年谷歌提出的激活函数,它结合了Swish和GLU两者的特点。SwiGLU激活函…...
AOA与TOA混合定位,MATLAB例程,三维空间下的运动轨迹,滤波使用EKF,附下载链接
本文介绍一个MATLAB代码,实现基于 到达角(AOA) 和 到达时间(TOA) 的混合定位算法,结合 扩展卡尔曼滤波(EKF) 对三维运动目标的轨迹进行滤波优化。代码通过模拟动态目标与基站网络&am…...
【动态编译】Roslyn中 SyntaxKind 枚举类型
在 Roslyn(.NET 的编译器平台)中,SyntaxKind 是一个枚举类型,定义了 C# 语言中所有可能的语法节点类型。它是 Roslyn 抽象语法树(AST)的基础,用于标识每个 SyntaxNode 的具体种类。SyntaxKind 的…...
getID3获取本地或远程视频时长
音频文件也可使用,使用ffmeg安装太复杂了 附ffmpeg方式:centos下安装ffmpeg_yum安装ffmpeg-CSDN博客 使用composer先安装 composer require james-heinrich/getid3 获取本地视频 //获取本地视频$video_path $_SERVER[DOCUMENT_ROOT].$params[video];…...
【211】线上教学系统
--基于SSM线上教学平添 主要实现的功能有: 管理员 : 首页、个人中心、学员管理、资料类型管理、学习资料管理、交流论坛、我的收藏管理、试卷管理、留言板管理、试题管理、系统管理、考试管理。 学员 : 首页、个人中心、我的收藏管理、留言板管理、考试管理。 前台…...
从混乱思绪到清晰表达:记录想法如何改变你的学习人生
关键要点 • 记录想法似乎是发现自己想法并将其组织成可传播形式的最佳理由,研究表明写作和教学能增强学习和理解。 • 证据倾向于支持写作有助于澄清思想,而教学通过“教授效应”深化知识。 • 教学和分享被认为是最有效的学习方法,这与记录…...
uvm sequence
UVM Sequence 是验证环境中生成和控制事务(Transaction)流的核心机制,它通过动态生成、随机化和调度事务,实现灵活多样的测试场景。以下是Sequence的详细解析: Sequence 的核心作用 事务流生成:通过 uvm_s…...
CMake ERROR: arm-none-eabi-gcc is not able to compile a simple test program.
用 cmake 构建 STM32 工程问题【已解决】 环境信息 os: ubuntu22.04gcc: arm-none-eabi-gcc (Arm GNU Toolchain 13.2.rel1 (Build arm-13.7)) 13.2.1 20231009cmake: cmake version 3.22.1ninja: 1.10.1 问题 log [main] 正在配置项目: Olidy [driver] 删除 /home/pomegr…...
地图项目入手学习
如果你目前对自己的地图项目实现原理不太了解,周末可以通过以下方法进行高效学习: ⸻ 第一步:梳理项目相关代码(3 小时) 目标:先大致了解你的地图项目代码,找到核心实现逻辑。 具体做法&…...
电机控制常见面试问题(二十)
文章目录 一.整流电路绕组接法二.电机为什么需要转速器三.电机转矩产生原理四.电机控制中载波频率大小的确定五.开关周期 Tpwm 一.整流电路绕组接法 为了引出直流的输出,一定要在整流变压器的二次侧引出零线,所以二次侧绕组必须接成星形 一次绕组必须要…...
小爱控制via电视浏览器搜索图片-Homeassistant重制上一个自动化
制作自动化详情 为了完成图片搜,暂定指令找找{描述} 在执行脚本的adb地方输入以下指令,百度 因安全不让在图片地址直接搜转用bing >- >am start -n mark.via.gp/mark.via.Shell -a android.intent.action.VIEW -d https://cn.bing.com/images/…...
