这是用户在 2024-7-23 14:47 为 https://go.dev/blog/laws-of-reflection 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

The Go Blog Go 博客

The Laws of Reflection
反思法则

Rob Pike  罗布·派克
6 September 2011
2011 年 9 月 6 日

Introduction 介绍

Reflection in computing is the ability of a program to examine its own structure, particularly through types; it’s a form of metaprogramming. It’s also a great source of confusion.
计算中的反射是程序检查其自身结构的能力,特别是通过类型;这是元编程的一种形式。这也是造成混乱的一个重要原因。

In this article we attempt to clarify things by explaining how reflection works in Go. Each language’s reflection model is different (and many languages don’t support it at all), but this article is about Go, so for the rest of this article the word “reflection” should be taken to mean “reflection in Go”.
在本文中,我们试图通过解释反射在 Go 中的工作原理来澄清问题。每种语言的反射模型都是不同的(许多语言根本不支持它),但本文是关于 Go 的,因此在本文的其余部分中,“反射”一词应理解为“Go 中的反射”。

Note added January 2022: This blog post was written in 2011 and predates parametric polymorphism (a.k.a. generics) in Go. Although nothing important in the article has become incorrect as a result of that development in the language, it has been tweaked in a few places to avoid confusing someone familiar with modern Go.
2022 年 1 月添加的注释:这篇博文写于 2011 年,早于 Go 中的参数多态性(又称泛型)。尽管本文中的任何重要内容都没有因为语言的发展而变得不正确,但它在一些地方进行了调整,以避免让熟悉现代 Go 的人感到困惑。

Types and interfaces 类型和接口

Because reflection builds on the type system, let’s start with a refresher about types in Go.
因为反射建立在类型系统之上,所以让我们首先回顾一下 Go 中的类型。

Go is statically typed. Every variable has a static type, that is, exactly one type known and fixed at compile time: int, float32, *MyType, []byte, and so on. If we declare
Go 是静态类型的。每个变量都有一个静态类型,即在编译时已知并固定的一种类型: intfloat32*MyType[]byte ,等等。如果我们声明

type MyInt int

var i int
var j MyInt

then i has type int and j has type MyInt. The variables i and j have distinct static types and, although they have the same underlying type, they cannot be assigned to one another without a conversion.
那么 i 的类型为 intj 的类型为 MyInt 。变量 ij 具有不同的静态类型,尽管它们具有相同的基础类型,但如果不进行转换,它们就无法相互分配。

One important category of type is interface types, which represent fixed sets of methods. (When discussing reflection, we can ignore the use of interface definitions as constraints within polymorphic code.) An interface variable can store any concrete (non-interface) value as long as that value implements the interface’s methods. A well-known pair of examples is io.Reader and io.Writer, the types Reader and Writer from the io package:
类型的一个重要类别是接口类型,它表示固定的方法集。 (在讨论反射时,我们可以忽略使用接口定义作为多态代码中的约束。)接口变量可以存储任何具体(非接口)值,只要该值实现接口的方法即可。一对著名的示例是 io.Readerio.Writer ,即来自 io 包的 ReaderWriter 类型:

// Reader is the interface that wraps the basic Read method.
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Writer is the interface that wraps the basic Write method.
type Writer interface {
    Write(p []byte) (n int, err error)
}

Any type that implements a Read (or Write) method with this signature is said to implement io.Reader (or io.Writer). For the purposes of this discussion, that means that a variable of type io.Reader can hold any value whose type has a Read method:
任何使用此签名实现 Read (或 Write )方法的类型都被称为实现 io.Reader (或 io.Writer )。出于本讨论的目的,这意味着 io.Reader 类型的变量可以保存其类型具有 Read 方法的任何值:

var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on

It’s important to be clear that whatever concrete value r may hold, r’s type is always io.Reader: Go is statically typed and the static type of r is io.Reader.
重要的是要清楚,无论 r 可能包含什么具体值, r 的类型始终是 io.Reader :Go 是静态类型,而 rio.Reader

An extremely important example of an interface type is the empty interface:
接口类型的一个极其重要的例子是空接口:

interface{}

or its equivalent alias,
或其等效别名,

any

It represents the empty set of methods and is satisfied by any value at all, since every value has zero or more methods.
它表示空的方法集,并且可以满足任何值,因为每个值都有零个或多个方法。

Some people say that Go’s interfaces are dynamically typed, but that is misleading. They are statically typed: a variable of interface type always has the same static type, and even though at run time the value stored in the interface variable may change type, that value will always satisfy the interface.
有人说 Go 的接口是动态类型的,但这是误导。它们是静态类型的:接口类型的变量始终具有相同的静态类型,即使在运行时存储在接口变量中的值可能会更改类型,该值也将始终满足接口的要求。

We need to be precise about all this because reflection and interfaces are closely related.
我们需要精确地对待这一切,因为反射和接口密切相关。

The representation of an interface
接口的表示

Russ Cox has written a detailed blog post about the representation of interface values in Go. It’s not necessary to repeat the full story here, but a simplified summary is in order.
Russ Cox 撰写了一篇关于 Go 中接口值表示的详细博客文章。这里没有必要重复整个故事,但可以做一个简单的总结。

A variable of interface type stores a pair: the concrete value assigned to the variable, and that value’s type descriptor. To be more precise, the value is the underlying concrete data item that implements the interface and the type describes the full type of that item. For instance, after
接口类型的变量存储一对:分配给该变量的具体值,以及该值的类型描述符。更准确地说,值是实现接口的底层具体数据项,类型描述了该项的完整类型。例如,之后

var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty

r contains, schematically, the (value, type) pair, (tty, *os.File). Notice that the type *os.File implements methods other than Read; even though the interface value provides access only to the Read method, the value inside carries all the type information about that value. That’s why we can do things like this:
r 示意性地包含(值,类型)对( tty*os.File )。请注意,类型 *os.File 实现了 Read 以外的方法;尽管接口值仅提供对 Read 方法的访问,但内部值携带有关该值的所有类型信息。这就是为什么我们可以这样做:

var w io.Writer
w = r.(io.Writer)

The expression in this assignment is a type assertion; what it asserts is that the item inside r also implements io.Writer, and so we can assign it to w. After the assignment, w will contain the pair (tty, *os.File). That’s the same pair as was held in r. The static type of the interface determines what methods may be invoked with an interface variable, even though the concrete value inside may have a larger set of methods.
此赋值中的表达式是类型断言;它断言 r 中的项目也实现了 io.Writer ,因此我们可以将其分配给 w 。分配后, w 将包含该对( tty*os.File )。这与 r 中保存的对相同。接口的静态类型决定了可以使用接口变量调用哪些方法,即使内部的具体值可能具有更大的方法集。

Continuing, we can do this:
继续,我们可以这样做:

var empty interface{}
empty = w

and our empty interface value empty will again contain that same pair, (tty, *os.File). That’s handy: an empty interface can hold any value and contains all the information we could ever need about that value.
我们的空接口值 empty 将再次包含同一对( tty*os.File )。这很方便:空接口可以保存任何值,并包含我们可能需要的有关该值的所有信息。

(We don’t need a type assertion here because it’s known statically that w satisfies the empty interface. In the example where we moved a value from a Reader to a Writer, we needed to be explicit and use a type assertion because Writer’s methods are not a subset of Reader’s.)
(这里我们不需要类型断言,因为静态地知道 w 满足空接口。在我们将值从 Reader 移动到 Writer 的方法不是 Reader 的子集。)

One important detail is that the pair inside an interface variable always has the form (value, concrete type) and cannot have the form (value, interface type). Interfaces do not hold interface values.
一个重要的细节是,接口变量内的对始终具有(值,具体类型)形式,并且不能具有(值,接口类型)形式。接口不保存接口值。

Now we’re ready to reflect.
现在我们准备好反思了。

The first law of reflection
第一反射定律

1. Reflection goes from interface value to reflection object.
1.反射从接口值到反射对象。

At the basic level, reflection is just a mechanism to examine the type and value pair stored inside an interface variable. To get started, there are two types we need to know about in package reflect: Type and Value. Those two types give access to the contents of an interface variable, and two simple functions, called reflect.TypeOf and reflect.ValueOf, retrieve reflect.Type and reflect.Value pieces out of an interface value. (Also, from a reflect.Value it’s easy to get to the corresponding reflect.Type, but let’s keep the Value and Type concepts separate for now.)
从根本上来说,反射只是一种检查存储在接口变量内的类型和值对的机制。首先,我们需要了解反射包中的两种类型:类型和值。这两种类型可以访问接口变量的内容,以及两个简单的函数,称为 reflect.TypeOfreflect.ValueOf ,检索 reflect.Typereflect.Value 接口值的一部分。 (此外,从 reflect.Value 很容易到达相应的 reflect.Type ,但现在让我们将 ValueType 概念分开.)

Let’s start with TypeOf:
让我们从 TypeOf 开始:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.4
    fmt.Println("type:", reflect.TypeOf(x))
}

This program prints 该程序打印

type: float64

You might be wondering where the interface is here, since the program looks like it’s passing the float64 variable x, not an interface value, to reflect.TypeOf. But it’s there; as godoc reports, the signature of reflect.TypeOf includes an empty interface:
您可能想知道这里的接口在哪里,因为程序看起来像是将 float64 变量 x (而不是接口值)传递给 reflect.TypeOf 。但它就在那里;正如 godoc 报告的那样, reflect.TypeOf 的签名包含一个空接口:

// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type

When we call reflect.TypeOf(x), x is first stored in an empty interface, which is then passed as the argument; reflect.TypeOf unpacks that empty interface to recover the type information.
当我们调用 reflect.TypeOf(x) 时, x 首先存储在一个空接口中,然后作为参数传递; reflect.TypeOf 解压该空接口以恢复类型信息。

The reflect.ValueOf function, of course, recovers the value (from here on we’ll elide the boilerplate and focus just on the executable code):
当然, reflect.ValueOf 函数会恢复值(从这里开始,我们将省略样板文件,只关注可执行代码):

var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x).String())

prints 印刷

value: <float64 Value>

(We call the String method explicitly because by default the fmt package digs into a reflect.Value to show the concrete value inside. The String method does not.)
(我们显式调用 String 方法,因为默认情况下 fmt 包会深入到 reflect.Value 中以显示内部的具体值。 String 方法没有。)

Both reflect.Type and reflect.Value have lots of methods to let us examine and manipulate them. One important example is that Value has a Type method that returns the Type of a reflect.Value. Another is that both Type and Value have a Kind method that returns a constant indicating what sort of item is stored: Uint, Float64, Slice, and so on. Also methods on Value with names like Int and Float let us grab values (as int64 and float64) stored inside:
reflect.Typereflect.Value 都有很多方法让我们检查和操作它们。一个重要的例子是 Value 有一个 Type 方法,它返回 reflect.ValueType 。另一个是 TypeValue 都有一个 Kind 方法,该方法返回一个常量,指示存储的项目类型: Uint 、 < b10> 、 Slice 等等。 Value 上具有 IntFloat 等名称的方法也让我们获取值(如 int64float64 )存储在里面:

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())

prints 印刷

type: float64
kind is float64: true
value: 3.4

There are also methods like SetInt and SetFloat but to use them we need to understand settability, the subject of the third law of reflection, discussed below.
还有像 SetIntSetFloat 这样的方法,但要使用它们,我们需要了解可设置性,即下面讨论的第三反射定律的主题。

The reflection library has a couple of properties worth singling out. First, to keep the API simple, the “getter” and “setter” methods of Value operate on the largest type that can hold the value: int64 for all the signed integers, for instance. That is, the Int method of Value returns an int64 and the SetInt value takes an int64; it may be necessary to convert to the actual type involved:
反射库有几个值得特别指出的属性。首先,为了保持 API 简单, Value 的“getter”和“setter”方法对可以容纳值的最大类型进行操作: int64 对于所有有符号整数,对于实例。也就是说, ValueInt 方法返回 int64 ,而 SetInt 值采用 int64 ;可能需要转换为涉及的实际类型:

var x uint8 = 'x'
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())                            // uint8.
fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.
x = uint8(v.Uint())                                       // v.Uint returns a uint64.

The second property is that the Kind of a reflection object describes the underlying type, not the static type. If a reflection object contains a value of a user-defined integer type, as in
第二个属性是反射对象的 Kind 描述的是底层类型,而不是静态类型。如果反射对象包含用户定义的整数类型的值,如

type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)

the Kind of v is still reflect.Int, even though the static type of x is MyInt, not int. In other words, the Kind cannot discriminate an int from a MyInt even though the Type can.
vKind 仍然是 reflect.Int ,即使 x 的静态类型是 MyInt ,而不是 < b5> 。换句话说, Kind 无法区分 intMyInt ,尽管 Type 可以。

The second law of reflection
反射第二定律

2. Reflection goes from reflection object to interface value.
2.反射从反射对象到接口值。

Like physical reflection, reflection in Go generates its own inverse.
就像物理反射一样,Go 中的反射也会产生它自己的逆。

Given a reflect.Value we can recover an interface value using the Interface method; in effect the method packs the type and value information back into an interface representation and returns the result:
给定一个 reflect.Value ,我们可以使用 Interface 方法恢复接口值;实际上,该方法将类型和值信息打包回接口表示形式并返回结果:

// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}

As a consequence we can say
因此我们可以说

y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)

to print the float64 value represented by the reflection object v.
打印反射对象 v 表示的 float64 值。

We can do even better, though. The arguments to fmt.Println, fmt.Printf and so on are all passed as empty interface values, which are then unpacked by the fmt package internally just as we have been doing in the previous examples. Therefore all it takes to print the contents of a reflect.Value correctly is to pass the result of the Interface method to the formatted print routine:
不过,我们可以做得更好。 fmt.Printlnfmt.Printf 等的参数都作为空接口值传递,然后由 fmt 包在内部解包,就像我们一直在做的那样在前面的例子中。因此,正确打印 reflect.Value 的内容所需要做的就是将 Interface 方法的结果传递给格式化的打印例程:

fmt.Println(v.Interface())

(Since this article was first written, a change was made to the fmt package so that it automatically unpacks a reflect.Value like this, so we could just say
(自从本文首次撰写以来,对 fmt 包进行了更改,以便它自动解压像这样的 reflect.Value ,所以我们可以说

fmt.Println(v)

for the same result, but for clarity we’ll keep the .Interface() calls here.)
获得相同的结果,但为了清楚起见,我们将在此处保留 .Interface() 调用。)

Since our value is a float64, we can even use a floating-point format if we want:
由于我们的值是 float64 ,如果需要,我们甚至可以使用浮点格式:

fmt.Printf("value is %7.1e\n", v.Interface())

and get in this case
在这种情况下

3.4e+00

Again, there’s no need to type-assert the result of v.Interface() to float64; the empty interface value has the concrete value’s type information inside and Printf will recover it.
同样,不需要将 v.Interface() 的结果类型断言到 float64 ;空接口值内部有具体值的类型信息, Printf 将恢复它。

In short, the Interface method is the inverse of the ValueOf function, except that its result is always of static type interface{}.
简而言之, Interface 方法是 ValueOf 函数的逆函数,只不过它的结果始终是静态类型 interface{}

Reiterating: Reflection goes from interface values to reflection objects and back again.
重申一下:反射从接口值到反射对象,然后再返回。

The third law of reflection
反思第三定律

3. To modify a reflection object, the value must be settable.
3. 要修改反射对象,该值必须是可设置的。

The third law is the most subtle and confusing, but it’s easy enough to understand if we start from first principles.
第三定律是最微妙和最令人困惑的,但如果我们从第一原理开始,它就很容易理解。

Here is some code that does not work, but is worth studying.
这是一些不起作用的代码,但值得研究。

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.

If you run this code, it will panic with the cryptic message
如果您运行此代码,它将因神秘消息而出现恐慌

panic: reflect.Value.SetFloat using unaddressable value

The problem is not that the value 7.1 is not addressable; it’s that v is not settable. Settability is a property of a reflection Value, and not all reflection Values have it.
问题不在于值 7.1 不可寻址;而是值 7.1 不可寻址。就是 v 不可设置。可设置性是反射 Value 的一个属性,并非所有反射 Values 都具有它。

The CanSet method of Value reports the settability of a Value; in our case,
ValueCanSet 方法报告 Value 的可设置性;在我们的例子中,

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())

prints 印刷

settability of v: false

It is an error to call a Set method on a non-settable Value. But what is settability?
在不可设置的 Value 上调用 Set 方法是错误的。但什么是可调整性呢?

Settability is a bit like addressability, but stricter. It’s the property that a reflection object can modify the actual storage that was used to create the reflection object. Settability is determined by whether the reflection object holds the original item. When we say
可设置性有点像可寻址性,但更严格。这是反射对象可以修改用于创建反射对象的实际存储的属性。可设置性取决于反射对象是否持有原始项目。当我们说

var x float64 = 3.4
v := reflect.ValueOf(x)

we pass a copy of x to reflect.ValueOf, so the interface value created as the argument to reflect.ValueOf is a copy of x, not x itself. Thus, if the statement
我们将 x 的副本传递给 reflect.ValueOf ,因此作为 reflect.ValueOf 参数创建的接口值是 x 的副本,而不是 x 本身。因此,如果声明

v.SetFloat(7.1)

were allowed to succeed, it would not update x, even though v looks like it was created from x. Instead, it would update the copy of x stored inside the reflection value and x itself would be unaffected. That would be confusing and useless, so it is illegal, and settability is the property used to avoid this issue.
如果允许成功,它不会更新 x ,即使 v 看起来像是从 x 创建的。相反,它会更新存储在反射值内的 x 副本,而 x 本身不受影响。这会令人困惑且无用,因此它是非法的,而可设置性是用于避免此问题的属性。

If this seems bizarre, it’s not. It’s actually a familiar situation in unusual garb. Think of passing x to a function:
如果这看起来很奇怪,其实不然。这实际上是一种穿着不寻常服装的熟悉情况。考虑将 x 传递给函数:

f(x)

We would not expect f to be able to modify x because we passed a copy of x’s value, not x itself. If we want f to modify x directly we must pass our function the address of x (that is, a pointer to x):
我们不希望 f 能够修改 x ,因为我们传递了 x 值的副本,而不是 x 本身。如果我们希望 f 直接修改 x ,我们必须向函数传递 x 的地址(即指向 x 的指针) :

f(&x)

This is straightforward and familiar, and reflection works the same way. If we want to modify x by reflection, we must give the reflection library a pointer to the value we want to modify.
这是简单且熟悉的,反射的工作方式也是如此。如果我们想通过反射修改 x ,我们必须给反射库一个指向我们想要修改的值的指针。

Let’s do that. First we initialize x as usual and then create a reflection value that points to it, called p.
让我们这样做吧。首先,我们像往常一样初始化 x ,然后创建一个指向它的反射值,称为 p

var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())

The output so far is
到目前为止的输出是

type of p: *float64
settability of p: false

The reflection object p isn’t settable, but it’s not p we want to set, it’s (in effect) *p. To get to what p points to, we call the Elem method of Value, which indirects through the pointer, and save the result in a reflection Value called v:
反射对象 p 不可设置,但它不是我们想要设置的 p ,它(实际上)是 *p 。为了获取 p 指向的内容,我们调用 ValueElem 方法,该方法通过指针间接进行,并将结果保存在反射 Value 称为 v

v := p.Elem()
fmt.Println("settability of v:", v.CanSet())

Now v is a settable reflection object, as the output demonstrates,
现在 v 是一个可设置的反射对象,如输出所示,

settability of v: true

and since it represents x, we are finally able to use v.SetFloat to modify the value of x:
由于它代表 x ,我们终于可以使用 v.SetFloat 来修改 x 的值:

v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)

The output, as expected, is
正如预期的那样,输出是

7.1
7.1

Reflection can be hard to understand but it’s doing exactly what the language does, albeit through reflection Types and Values that can disguise what’s going on. Just keep in mind that reflection Values need the address of something in order to modify what they represent.
反射可能很难理解,但它所做的正是语言所做的事情,尽管通过反射 TypesValues 可以掩盖正在发生的事情。请记住,反射值需要某些内容的地址才能修改它们所代表的内容。

Structs 结构体

In our previous example v wasn’t a pointer itself, it was just derived from one. A common way for this situation to arise is when using reflection to modify the fields of a structure. As long as we have the address of the structure, we can modify its fields.
在我们前面的例子中 v 本身并不是一个指针,它只是从一个指针派生出来的。出现这种情况的常见方式是使用反射来修改结构体的字段。只要我们有结构体的地址,我们就可以修改它的字段。

Here’s a simple example that analyzes a struct value, t. We create the reflection object with the address of the struct because we’ll want to modify it later. Then we set typeOfT to its type and iterate over the fields using straightforward method calls (see package reflect for details). Note that we extract the names of the fields from the struct type, but the fields themselves are regular reflect.Value objects.
这是一个分析结构体值 t 的简单示例。我们使用结构体的地址创建反射对象,因为我们稍后需要修改它。然后我们将 typeOfT 设置为它的类型,并使用简单的方法调用迭代字段(有关详细信息,请参阅包reflect)。请注意,我们从结构类型中提取字段的名称,但字段本身是常规的 reflect.Value 对象。

type T struct {
    A int
    B string
}
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
    f := s.Field(i)
    fmt.Printf("%d: %s %s = %v\n", i,
        typeOfT.Field(i).Name, f.Type(), f.Interface())
}

The output of this program is
该程序的输出是

0: A int = 23
1: B string = skidoo

There’s one more point about settability introduced in passing here: the field names of T are upper case (exported) because only exported fields of a struct are settable.
这里还引入了关于可设置性的一点: T 的字段名称是大写的(导出的),因为只有结构体的导出字段是可设置的。

Because s contains a settable reflection object, we can modify the fields of the structure.
因为 s 包含一个可设置的反射对象,所以我们可以修改该结构体的字段。

s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)

And here’s the result:
结果如下:

t is now {77 Sunset Strip}

If we modified the program so that s was created from t, not &t, the calls to SetInt and SetString would fail as the fields of t would not be settable.
如果我们修改程序,使 s 是从 t 创建的,而不是 &t ,则对 SetIntSetString 的字段不可设置。

Conclusion 结论

Here again are the laws of reflection:
这又是反射定律:

  • Reflection goes from interface value to reflection object.
    反射从接口值到反射对象。

  • Reflection goes from reflection object to interface value.
    反射从反射对象到接口值。

  • To modify a reflection object, the value must be settable.
    要修改反射对象,该值必须是可设置的。

Once you understand these laws reflection in Go becomes much easier to use, although it remains subtle. It’s a powerful tool that should be used with care and avoided unless strictly necessary.
一旦你理解了这些法则,Go 中的反射就会变得更容易使用,尽管它仍然很微妙。这是一个强大的工具,应谨慎使用,除非绝对必要,否则应避免使用。

There’s plenty more to reflection that we haven’t covered — sending and receiving on channels, allocating memory, using slices and maps, calling methods and functions — but this post is long enough. We’ll cover some of those topics in a later article.
还有很多关于反射的内容我们没有涉及——在通道上发送和接收、分配内存、使用切片和映射、调用方法和函数——但是这篇文章已经足够长了。我们将在后面的文章中介绍其中一些主题。

Next article: The Go image package
下一篇:Go镜像包

Previous article: Two Go Talks: "Lexical Scanning in Go" and "Cuddle: an App Engine Demo"
上一篇文章: 两场 Go 讲座:“Go 中的词法扫描”和“Cuddle:App Engine 演示”

Blog Index  博客索引