golang-FAQ

new与make之间有什么不同?

new 分配内存,make 初始化切片、映射和信道类型

如何获得方法的动态分配?

拥有动态分配方法的唯一途径就是通过接口。结构或其它混合类型的方法总是静态地确定

如何编写单元测试?

在相同的目录中创建一个以 _test.go 结尾的新文件作为你的包源文件。 在该文件中,加入 import "testing" 并编写以下形式的函数:

func TestFoo(t *testing.T) {
    ...
}

在该目录中运行 go test。该脚本会查找 Test 函数, 构建一个测试二进制文件并运行它。

闭包作为Go程在运行时会发生什么?

当闭包与并发一起使用时,可能会产生一些混乱。考虑以下程序:

func main() {
    done := make(chan bool)
    
    value := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }
    
    // 在退出前等待所有Go程完成
    for _ = range values {
        <-done
    }
}

有人可能会错误地希望看到 a, b, c 作为输出。而你可能会看到 c, c, c。这是因为每一次循环迭代中都使用了变量 v 的相同实例,因此每一个闭包都共享了单一的变量。当该闭包运行时,它将在 fmt.Println 执行后打印出 v 的值,但 v 可能已经在Go程启动后被修改了。要在这类问题发生前发现它们,请运行 go vet

要将 v 的当前值在每一个闭包启动后绑定至它们,就必须在每一次迭代中, 通过修改内部循环来创建新的变量。其中一种方式就是将变量作为实参传至该闭包中:

for _, v := range values {
    go func(u string) {
        fmt.Println(u)
        done <- true
    } (v)
}

在这个例子中,v 的值作为一个实参传入了该匿名函数。然后这个值就可作为变量 u 在该函数中访问了。

甚至只需简单地创建新的变量,使用声明的风格看起来可能有点怪,但这在Go中能很好地工作:

for _, v := range values {
    v := v	//创建新的"v"
    go func() {
        fmt.Println(v)
        done <- true
    } ()
}
为什么T与*T拥有不同的方法集?

根据Go规范中的定义

其它任意已命名类型 T 的方法集由所有带接收者类型 T 的方法组成。 与指针类型 *T 相应的方法集为所有带接收者 *TT 的方法的集(就是说,它也包含 T 的方法集)。

如果一个接口值包含一个指针 *T,一个方法调用可通过解引用该指针来获得一个值, 但如果一个接口值包含一个值 T,就没有可用的方式让一个方法调用获得一个指针。

即便在编译器可以获得传入方法的值的地址的情况下,若该方法修改了该值,则更改会在调用者中丢失。 一个常见的例子是

var buf bytes.Buffer
io.Copy(buf, os.Stdin)

会将标准输入复制到 buf副本中,而不是复制到 buf 自身。这几乎是从不期望的行为。

应当为值或指针定义方法吗?
func (s *MyStruct) pointerMethod() {}	//为指针定义的方法
func (s MyStruct) valueMethod() {}		//为值定义的方法

当为一个类型定义了一个方法,则接收者(上面例子中的 s)的表现正好就是该方法的实参。 将接收者定义为值还是指针都是一样的问题,就像一个函数的实参应该是值还是指针一样。 有几点需要考虑的地方。

首先,也是最重要的一点,方法需要修改接收者吗?如果是,则接收者必须是一个指针。 (切片和映射的行为类似于引用,所以它们的状况有一点微妙,但比如说要改变方法中切片的长度, 则接受者仍然必须是指针。)在上面的例子中,如果 pointerMethod 修改了 s 的字段,那么调用者将观察到那些改变,但 valueMethod 是由调用者实参的副本调用的(这是传值的规定),因此对它的更改对于调用者来说是不可见的。

顺便一提,指针接收者在Java中的情况和Go是相同的,尽管在Java中指针隐藏在幕后; 而Go的值接受者则不相同。

其次是效率问题的考虑。若接受者很大,比如说一个大型的 struct, 使用指针接收器将更廉价。

接着是一致性问题。若某些类型的方法必须拥有指针接收者,则其余的也应该这样, 因此不管该类型被如何使用,方法集都始终如一。更多详情见方法集一节。

对于诸如基本类型、切片以及小型 struct 这样的类型,值接收者是非常廉价的, 因此除非该方法的语义需要一个指针,一个有效而清楚的值接收者。

函数形参在什么时候传值?

和所有C家族中的语言一样,Go中的所有东西都通过值来传递。也就是说, 函数总是会获得向它传递的东西的一份副本,就好像有一个赋值语句向它的形参赋值。 例如,将一个 int 值传入一个函数就会创建该 int 值的一份副本,而传入一个指针值则会创建该指针的一份副本,而不是它所指向的数据。 (关于它如何影响方法接收器的讨论见下一节。)

映射和切片值的行为就像指针一样:它们就是包含指向基本映射或切片数据的指针的描述符。 复制一个映射或切片会创建一个存储在接口值中的东西的一个副本。若该接口值保存了一个结构, 复制该接口值则会创建一个该结构的副本。若该接口值保存了一个指针, 复制该接口值则会创建一个该指针的副本,而且同样不是它所指向的数据。

为什么我的nil错误值不等于nil?

在底层,接口作为两个元素实现:一个类型和一个值。该值被称为接口的动态值, 它是一个任意的具体值,而该接口的类型则为该值的类型。对于 int 值3, 一个接口值示意性地包含(int, 3)。

只有在内部值和类型都未设置时(nil, nil),一个接口的值才为 nil。特别是,一个 nil 接口将总是拥有一个 nil 类型。若我们在一个接口值中存储一个 *int 类型的指针,则内部类型将为 *int,无论该指针的值是什么:(*int, nil)。 因此,这样的接口值会是非 nil 的,即使在该指针的内部为 nil

这种情况会让人迷惑,而且当 nil 值存储在接口值内部时这种情况总是发生, 例如错误返回:

func returnError() error {
    var p *MyError = nil
    if bad() {
        p = ErrBad
    }
    return p	//将总是返回一个非nil错误
}

如果一切顺利,该函数会返回一个 nilp, 因此该返回值为拥有(*MyError, nil)的 error 接口值。这也就意味着如果调用者将返回的错误与 nil 相比较, 它将总是看上去有错误,即便没有什么坏事发生。要向调用者返回一个适当的 nil error,该函数必须返回一个显式的 nil

func returnError() error {
    if bad() {
        return ErrBad
    }
    return nil
}

这对于总是在签名中使用 error 类型返回错误(正如我们上面做的)而非像 *MyError 这样具体类型的函数来说是个不错的主意,它可以帮助确保错误被正确地创建。 例如,即使 os.Open 返回一个 error, 若非 nil 的话,它总是具体的类型 *os.PathError

对于那些描述,无论接口是否被使用,相似的情形都会出现。只要记住,如果任何具体的值已被存储在接口中, 该接口就不为 nil

能否将[]T转换为[]interface{}?

不能直接转换,因为它们在内存中的表示并不相同。必须单独地将元素复制到目标切片。 下面的例子将 int 切片转换为 interface{} 切片:

t := []int{1, 2, 3, 4}
s := make([]interface{}, len(t))
for i, v := range t {
    s[i] = v
}
为什么类型T不满足Equal接口?

考虑以下简单的接口,它表示一个可以将自身与另一个值进行比较的对象:

type Equaler interface {
    Equal(Equaler) bool
}

以及此类型 T

type T int
func (t T) Equal(u T) bool { return t == u } // does not satisfy Equaler

不像在一些多态类型系统中类似的情况,T 并未实现 EqualerT.Equal 的实参类型为 T,而非字面上所需要的类型 Equalar

在Go中,类型系统并不提升 Equal 的实参,那是程序员的责任, 就以下类型 T2 所示,它实现了 Equaler

type T2 int
func (t T2) Equal(u Equaler) bool { return t == u.(T2) }	//满足Equaler

即使它不像其它的类型系统也好,因为在Go中任何满足 Equaler 的类型都能作为实参传至 T2.Equal,并在运行时我们必须检查该实参是否为 T2类型。一些语言将其安排在编译时以保证做到这一点。

一个相关的例子是另一种情形:

type Opener interface {
    Open() Reader
}

func (t T3) Open() *os.File

在Go中,T3 并不满足 Opener,尽管它在另一种语言中可能满足。

在相同情况下,Go的类型系统确实为程序员做的更少, 子类型化的缺乏使关于接口满足的规则非常容易制订: 函数的名字和签名完全就是那些接口吗?Go的规则也容易高效地实现。 我们感觉这些效益抵消了自动类型提升的缺失。Go在某天应当采取一些泛型的形式, 我们期望会有一些方式来表达这些例子的想法,且也拥有静态检查。

如何保证我的类型满足某个接口?

可以通过尝试赋值来要求编译器检查类型 T 是否实现了接口 I

type T struct{}
var _ I = T{}	//确认T是否实现了I。

如果你希望接口的使用者显式地声明它们实现了它,你可以将一个带描述性名称的方法添加到该接口的方法集中:

type Fooer interface {
    Foo()
    ImplementsFooer()
}

然后类型必须实现 ImplementsFooer 方法成为 Fooergodoc的输出中清晰地记录了事实和通告。

type Bar struct{}
func (b Bar) ImplemnetsFooer() {}
func (b Bar) Foo() {}

大部分代码无需使用这类约束,因为它们限制了实用程序的接口思想。 但有时候,它们也需要解决相似接口之间的歧义。

原文地址:https://www.cnblogs.com/xzpin/p/11935276.html