Go类型官方参考 中英文对照

官方参考

https://golang.org/ref/spec#Types

其中类型有:

Method sets 方法集合
Boolean types 布尔类型
Numeric types 数字类型
String types 字符串类型
Array types 数组类型
Slice types 切片类型
Struct types 结构体类型
Pointer types 指针类型
Function types 函数类型
Interface types 接口类型
Map types 字典类型
Channel types 通道类型

Method sets 方法集合

A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T). Further rules apply 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-blankmethod name.

The method set of a type determines the interfaces that the type implements and the methods that can be calledusing a receiver of that type.

接口类型Interface types的接口就是方法集合。若一组方法的接受参数都为参数T,把这组方法叫T类型的方法集合。T指针的对应方法集合,其接受参数为T或者*T。这意味着T指针的方法集合包含T类型的方法集合。对结构体来说,该规则同样适用。任何类型都有一个空的方法集合。在方法集合中,每个方法都有一个非空的方法名称。

类型的方法集合确定了该类型实现的接口,以及可以可以被该类型的接收器调用的方法。

Boolean types 布尔类型

A boolean type represents the set of Boolean truth values denoted by the predeclared constants true and false. The predeclared boolean type is bool; it is a defined type.

布尔类型定义了布尔变量,可被赋值为常量true和false。该类型是已定义类型。例如可以使用char类型来定义。

Numeric types 数字类型

A numeric type represents sets of integer or floating-point values. The predeclared architecture-independent numeric types are:

uint8       the set of all unsigned  8-bit integers (0 to 255)
uint16      the set of all unsigned 16-bit integers (0 to 65535)
uint32      the set of all unsigned 32-bit integers (0 to 4294967295)
uint64      the set of all unsigned 64-bit integers (0 to 18446744073709551615)

int8        the set of all signed  8-bit integers (-128 to 127)
int16       the set of all signed 16-bit integers (-32768 to 32767)
int32       the set of all signed 32-bit integers (-2147483648 to 2147483647)
int64       the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807)

float32     the set of all IEEE-754 32-bit floating-point numbers
float64     the set of all IEEE-754 64-bit floating-point numbers

complex64   the set of all complex numbers with float32 real and imaginary parts
complex128  the set of all complex numbers with float64 real and imaginary parts

byte        alias for uint8
rune        alias for int32

The value of an n-bit integer is n bits wide and represented using two's complement arithmetic.

uint     either 32 or 64 bits
int      same size as uint
uintptr  an unsigned integer large enough to store the uninterpreted bits of a pointer value

There is also a set of predeclared numeric types with implementation-specific sizes:

To avoid portability issues all numeric types are defined types and thus distinct except byte, which is an alias for uint8, and rune, which is an alias for int32. Conversions are required when different numeric types are mixed in an expression or assignment. For instance, int32 and int are not the same type even though they may have the same size on a particular architecture.

数字类型包括整型和浮点。内置声明包括uint8 uint16 uint32 uint64以及int8 int16 int32 int64 float32 float64 complex64 complex128 byte rune。

N位整数是N位宽度,使用二进制补码计算。

与平台无关的声明包块uint int uintptr。

除了byte和rune外,其他数字类型为已定义类型,而byte是uint8的别名,rune是int32的别名。在数字类型之间的转换需要进行显示转换。

String types 字符串类型

A string type represents the set of string values. A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string. The predeclared string type is string; it is a defined type.

The length of a string s (its size in bytes) can be discovered using the built-in function len. The length is a compile-time constant if the string is a constant. A string's bytes can be accessed by integer indices 0 through len(s)-1. It is illegal to take the address of such an element; if s[i] is the i'th byte of a string, &s[i] is invalid.

字符串类型可以表示所有字符串的值。一个字符串的值是一系列空或不空的字节序列。他的值不能被修改。字符串被创建后就不能修改内容。我们使用string来声明一个string类型。string类型是已定义类型。

一个字符串的长度(其包含多少个字节)可以使用内建函数len来计算。在编译器可以计算一个常量string的长度。string类型的变量可以访问其位置0到位置len-1的值。对这些位置的元素,获取其地址是非法的。使用s[i]表示s字符串第i个位置的元素,而&s[i]是非法的。

Array types 数组类型

An array is a numbered sequence of elements of a single type, called the element type. The number of elements is called the length and is never negative.

ArrayType   = "[" ArrayLength "]" ElementType .
ArrayLength = Expression .
ElementType = Type .

The length is part of the array's type; it must evaluate to a non-negative constant representable by a value of type int. The length of array a can be discovered using the built-in function len. The elements can be addressed by integer indices 0 through len(a)-1. Array types are always one-dimensional but may be composed to form multi-dimensional types.

[32]byte
[2*N] struct { x, y int32 }
[1000]*float64
[3][5]int
[2][2][2]float64  // same as [2]([2]([2]float64))

数组类型是多个同类型的元素构成的可数序列。元素的数量是数组的长度。长度不能为负数。

数组的长度也是数组的一部分。他的值非负,可以用int表示。数组的长度使用len求出。数组的所有元素可以从位置0到len-1进行访问。数组类型永远是一维的,进行组合可以表示多位类型。

Slice types 切片

A slice is a descriptor for a contiguous segment of an underlying array and provides access to a numbered sequence of elements from that array. A slice type denotes the set of all slices of arrays of its element type. The value of an uninitialized slice is nil.

SliceType = "[" "]" ElementType .

Like arrays, slices are indexable and have a length. The length of a slice s can be discovered by the built-in function len; unlike with arrays it may change during execution. The elements can be addressed by integer indices 0 through len(s)-1. The slice index of a given element may be less than the index of the same element in the underlying array.

A slice, once initialized, is always associated with an underlying array that holds its elements. A slice therefore shares storage with its array and with other slices of the same array; by contrast, distinct arrays always represent distinct storage.

The array underlying a slice may extend past the end of the slice. The capacity is a measure of that extent: it is the sum of the length of the slice and the length of the array beyond the slice; a slice of length up to that capacity can be created by slicing a new one from the original slice. The capacity of a slice a can be discovered using the built-in function cap(a).

A new, initialized slice value for a given element type T is made using the built-in function make, which takes a slice type and parameters specifying the length and optionally the capacity. A slice created with make always allocates a new, hidden array to which the returned slice value refers. That is, executing

make([]T, length, capacity)

produces the same slice as allocating an array and slicing it, so these two expressions are equivalent:

make([]int, 50, 100)
new([100]int)[0:50]

Like arrays, slices are always one-dimensional but may be composed to construct higher-dimensional objects. With arrays of arrays, the inner arrays are, by construction, always the same length; however with slices of slices (or arrays of slices), the inner lengths may vary dynamically. Moreover, the inner slices must be initialized individually.

切片表示底层数组的连续段,提供对该段位置的元素访问。切片的类型表示其元素的类型。未初始化的切片的值为nil。

与数组相同,切片的长度可以使用len来求出。长度通常小于原来的数组。

切片自初始化之后与低层数组共享存储空间,多个切片可以共享同一个存储空间。数组通常代表了不同的存储空间。

切片的低层数组长度可以超过切片的末端,使用cap可以计算切片的容量,容量是数组超出切片的值加上切片本身的值。可以通过对切片重新切分得到新的超过原长度的切片。

使用内建函数make创建指定类型的切片。make接受切片类型和切片的长度,容量作为可选参数。使用make创建出来的切片总是被分配一个切片所引用的隐藏数组,这意味着通过make创建长度50容量100的切片make([]int, 50, 100)与通过new创建一个长度100的数组并对其切片50的长度new([100]int)[0:50]两者的结果相同。

与数组相同,切片的长度是一维的,但是可以表示更高的维度。对于数组来说,内部的数组由于构造的长度相同,是不能改变的。复合切片的内部长度会变成动态的,复合切片的内部也需要单独初始化。

Struct types 结构体

A struct is a sequence of named elements, called fields, each of which has a name and a type. Field names may be specified explicitly (IdentifierList) or implicitly (EmbeddedField). Within a struct, non-blank field names must be unique.

StructType    = "struct" "{" { FieldDecl ";" } "}" .
FieldDecl     = (IdentifierList Type | EmbeddedField) [ Tag ] .
EmbeddedField = [ "*" ] TypeName .
Tag           = string_lit .
// An empty struct.
struct {}

// A struct with 6 fields.
struct {
    x, y int
    u float32
    _ float32  // padding
    A *[]int
    F func()
}

A field declared with a type but no explicit field name is called an embedded field. An embedded field must be specified as a type name T or as a pointer to a non-interface type name *T, and T itself may not be a pointer type. The unqualified type name acts as the field name.

// A struct with four embedded fields of types T1, *T2, P.T3 and *P.T4
struct {
    T1        // field name is T1
    *T2       // field name is T2
    P.T3      // field name is T3
    *P.T4     // field name is T4
    x, y int  // field names are x and y
}

The following declaration is illegal because field names must be unique in a struct type:

struct {
    T     // conflicts with embedded field *T and *P.T
    *T    // conflicts with embedded field T and *P.T
    *P.T  // conflicts with embedded field T and *T
}

A field or method f of an embedded field in a struct x is called promoted if x.f is a legal selector that denotes that field or method f.

Promoted fields act like ordinary fields of a struct except that they cannot be used as field names in composite literals of the struct.

Given a struct type S and a defined type T, promoted methods are included in the method set of the struct as follows:

  • If S contains an embedded field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.
  • If S contains an embedded field *T, the method sets of S and *S both include promoted methods with receiver T or*T.

A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag. The tags are made visible through a reflection interface and take part in type identity for structs but are otherwise ignored.

struct {
    x, y float64 ""  // an empty tag string is like an absent tag
    name string  "any string is permitted as a tag"
    _    [4]byte "ceci n'est pas un champ de structure"
}

// A struct corresponding to a TimeStamp protocol buffer.
// The tag strings define the protocol buffer field numbers;
// they follow the convention outlined by the reflect package.
struct {
    microsec  uint64 `protobuf:"1"`
    serverIP6 uint64 `protobuf:"2"`
}

结构体是一系列字段的集合,每个字段有自己的名称和类型。类型可以隐式指定。在结构中非空字段名称必须是唯一的。

结构体可以为空。也可以包含int float等类型。如果使用_表示padding占位。

如果结构体的字段只有类型而没有名字,我们表示这个字段是嵌入字段。嵌入字段的类型必须是类型T,或者是一个非接口类型的指针。

结构体x中的嵌入字段的字段或方法叫做结构体x的提升。对结构体x,调用其不存在的成员或者方法,会使用嵌入字段的字段或方法来代替。

被提升的字段表现的就像原始字段,除了他们不能用在特殊场合。

给定结构体类型和定义的类型T,提升方法包含在结构体的方法集中。

如果S包含嵌入字段T,使用T为接受着的话,提升方法被包含在S和S的方法集合。使用T为接受者的话,提升方法被包含在*S的方法集合。

结构体的字段后面,可以跟上一个字符串。这个字符串可以通过反射的方式获取。可以用于特殊的场合。例如json转换,protobuf转换。

Pointer types 指针类型

A pointer type denotes the set of all pointers to variables of a given type, called the base type of the pointer. The value of an uninitialized pointer is nil.

PointerType = "*" BaseType .
BaseType    = Type .

*Point
*[4]int

一个指针类型表示指向给定类型所有变量的指针的类型,是指针的基础类型。指针在未被初始化的时候使用nil作为初值。

Function types 函数类型

A function type denotes the set of all functions with the same parameter and result types. The value of an uninitialized variable of function type is nil.

FunctionType   = "func" Signature .
Signature      = Parameters [ Result ] .
Result         = Parameters | Type .
Parameters     = "(" [ ParameterList [ "," ] ] ")" .
ParameterList  = ParameterDecl { "," ParameterDecl } .
ParameterDecl  = [ IdentifierList ] [ "..." ] Type .

Within a list of parameters or results, the names (IdentifierList) must either all be present or all be absent. If present, each name stands for one item (parameter or result) of the specified type and all non-blank names in the signature must be unique. If absent, each type stands for one item of that type. Parameter and result lists are always parenthesized except that if there is exactly one unnamed result it may be written as an unparenthesized type.

The final incoming parameter in a function signature may have a type prefixed with .... A function with such a parameter is called variadic and may be invoked with zero or more arguments for that parameter.

func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefix string, values ...int)
func(a, b int, z float64, opt ...interface{}) (success bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)

一个函数类型表示所有拥有相同参数和返回值的函数的类型。未被初始化的函数类型变量的值是nil。

在参数列表或者返回列表中,名称必须都被显示的声明出来,或者都没有声明,只写类型。如果写了名称,每个名称都有类型和非空的名字,而名字必须唯一。如果没有声明名字,那就只写类型。参数和返回列表需要使用括号包裹。返回列表在只有一个类型且没有名字时可以不加括号。

最后传入的参数的前面可以使用可变参数,前缀可以使用三个点。调用时,需要传入0个或以上的参数。

Interface types 接口类型

An interface type specifies a method set called its interface. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.

InterfaceType      = "interface" "{" { MethodSpec ";" } "}" .
MethodSpec         = MethodName Signature | InterfaceTypeName .
MethodName         = identifier .
InterfaceTypeName  = TypeName .

As with all method sets, in an interface type, each method must have a unique non-blank name.

// A simple File interface
interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
    Close()
}

More than one type may implement an interface. For instance, if two types S1 and S2 have the method set

func (p T) Read(b Buffer) bool { return … }
func (p T) Write(b Buffer) bool { return … }
func (p T) Close() { … }

(where T stands for either S1 or S2) then the File interface is implemented by both S1 and S2, regardless of what other methods S1 and S2 may have or share.

A type implements any interface comprising any subset of its methods and may therefore implement several distinct interfaces. For instance, all types implement the empty interface:

interface{}
Similarly, consider this interface specification, which appears within a type declaration to define an interface called Locker:

type Locker interface {
    Lock()
    Unlock()
}

If S1 and S2 also implement

func (p T) Lock() { … }
func (p T) Unlock() { … }

they implement the Locker interface as well as the File interface.

An interface T may use a (possibly qualified) interface type name E in place of a method specification. This is called embedding interface E in T; it adds all (exported and non-exported) methods of E to the interface T.

type ReadWriter interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
}

type File interface {
    ReadWriter  // same as adding the methods of ReadWriter
    Locker      // same as adding the methods of Locker
    Close()
}

type LockedFile interface {
    Locker
    File        // illegal: Lock, Unlock not unique
    Lock()      // illegal: Lock not unique
}

An interface type T may not embed itself or any interface type that embeds T, recursively.

// illegal: Bad cannot embed itself
type Bad interface {
    Bad
}

// illegal: Bad1 cannot embed itself using Bad2
type Bad1 interface {
    Bad2
}
type Bad2 interface {
    Bad1
}

接口类型表示一个方法集合,该方法集合叫做该类型的接口。接口类型可以存储一个变量,以及该变量的方法集合的任何超集。传入的类型与对应的接口叫做该类型的实现。未被初始化的接口类型变量的值是nil。

和方法集合一样,接口类型的内部,每个方法必须油唯一非空名字。

如果S1、S2两种类型的方法集和都包含了同样的接口实现,不管方法集合其他的区别是什么,S1和S2都被认为是接口的实现。

如果一个类型实现了一个接口,这个接口的子集又是其他接口的实现,那么这个类型也会实现那些接口。举个例子,所有的类型都实现了空接口。

接口中也可以内嵌其他接口,但是其内嵌的接口的方法的名字不能冲突。

Map types 字典类型

A map is an unordered group of elements of one type, called the element type, indexed by a set of unique keys of another type, called the key type. The value of an uninitialized map is nil.

MapType = "map" "[" KeyType "]" ElementType .
KeyType = Type .
The comparison operators == and != must be fully defined for operands of the key type; thus the key type must not be a function, map, or slice. If the key type is an interface type, these comparison operators must be defined for the dynamic key values; failure will cause a run-time panic.

map[string]int
map[*T]struct{ x, y float64 }
map[string]interface{}
The number of map elements is called its length. For a map m, it can be discovered using the built-in function len and may change during execution. Elements may be added during execution using assignments and retrieved with index expressions; they may be removed with the delete built-in function.

A new, empty map value is made using the built-in function make, which takes the map type and an optional capacity hint as arguments:

make(map[string]int)
make(map[string]int, 100)
The initial capacity does not bound its size: maps grow to accommodate the number of items stored in them, with the exception of nil maps. A nil map is equivalent to an empty map except that no elements may be added.

字典类型的内部是无序元素排序的类型,内部的类型叫元素的类型,使用唯一键值进行索引,键值也有另外一种类型,叫键类型。未被初始化的字典类型的值是nil。

键值类型必须完全定义了==运算和!=运算。因此函数、字典、切片都是不允许作为键值的。如果键值是一个接口,比较操作必须被定义为可以接受一个动态的键值。对键值的相关操作失败时会引发程序运行时惊慌。

字典元素的数量称为字典的长度。可以通过len函数检查,其值在运行期间可能被更改。元素可能通过指派操作添加,可以通过下标取出,可以通过delete函数移除。

一个空的map的值可以通过内建函数make来创建,make的第一个参数是map的类型,第二个参数是容量。

字典的容量不限制大小,字典的容量会增长以容纳其中项目的数量。但nil字典不是这样的,nil字典基本上和空字典是一样的,除了不能添加任何元素。

Channel types 通道类型

A channel provides a mechanism for concurrently executing functions to communicate by sending and receiving values of a specified element type. The value of an uninitialized channel is nil.

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
The optional <- operator specifies the channel direction, send or receive. If no direction is given, the channel is bidirectional. A channel may be constrained only to send or only to receive by conversion or assignment.

chan T // can be used to send and receive values of type T
chan<- float64 // can only be used to send float64s
<-chan int // can only be used to receive ints
The <- operator associates with the leftmost chan possible:

chan<- chan int // same as chan<- (chan int)
chan<- <-chan int // same as chan<- (<-chan int)
<-chan <-chan int // same as <-chan (<-chan int)
chan (<-chan int)
A new, initialized channel value can be made using the built-in function make, which takes the channel type and an optional capacity as arguments:

make(chan int, 100)
The capacity, in number of elements, sets the size of the buffer in the channel. If the capacity is zero or absent, the channel is unbuffered and communication succeeds only when both a sender and receiver are ready. Otherwise, the channel is buffered and communication succeeds without blocking if the buffer is not full (sends) or not empty (receives). A nil channel is never ready for communication.

A channel may be closed with the built-in function close. The multi-valued assignment form of the receive operator reports whether a received value was sent before the channel was closed.

A single channel may be used in send statements, receive operations, and calls to the built-in functions cap and len by any number of goroutines without further synchronization. Channels act as first-in-first-out queues. For example, if one goroutine sends values on a channel and a second goroutine receives them, the values are received in the order sent.

通道提供了并行执行程序之间的通信方式,该方式使用发送和接受特定类型的值来实现。未被初始化的通道的值是nil。

标记<-用来指示通道的方向,例如是接受还是发送。如果不指定方向,则通道从是双向的。

通道的<-标记,转换或指派通道的方向,成为发送或接收。

通道所传递的内容也可以是通道。

使用make也可以创建初始化的通道,make的第二个参数可以设置通道的容量。

通道的容量是通道中元素的缓存数量。如果容量是0或者不设置,通道是没有缓存的,只有接收者和发送者都准备好时才进行通信。否则,通道在缓存没有满时,是不阻塞的。如果通道的值是0,则永远无法通信。

通道使用系统调用close来关闭。使用多值接受的第二个参数来判断通道是否关闭。

单向通道用于发送表达式,接收操作。cap与len也可以调用,并且无须担心多个同步的协程同时运行。通道是先入先出的,如果第一个协成放入了一个值,第二个协成取出的一定是先放入的值。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容