type Pet interface { Name() string Category() string } type Dog struct { name string // 名字。 } func (dog *Dog) SetName(name string) { //注意这里定义的是指针方法 dog.name = name } func (dog Dog) Name() string { return dog.name } func (dog Dog) Category() string { return "dog" }
dog := Dog{"little pig"} fmt.Printf("The dog's name is %q. ", dog.Name()) var pet Pet = dog //这里赋值只是复制了一个副本而已 除非赋值指针 dog.SetName("monster") fmt.Printf("The dog's name is %q. ", dog.Name()) fmt.Printf("This pet is a %s, the name is %q. ", pet.Category(), pet.Name()) fmt.Println()
我们看下结果
The dog's name is "little pig".
The dog's name is "monster".
This pet is a dog, the name is "little pig". //这里没有什么变化
为什么dog的name字段值变了,而pet的却没有呢?这里有一条通用的规则需要你知晓:如果我们使用一个变量给另外一个变量赋值,那么真正赋给后者的,并不是前者持有的那个值,而是该值的一个副本
再看下一个示列
dog1 := Dog{"little pig"} fmt.Printf("The name of first dog is %q. ", dog1.Name()) //little pig dog2 := dog1 fmt.Printf("The name of second dog is %q. ", dog2.Name()) //little pig dog1.name = "monster" fmt.Printf("The name of first dog is %q. ", dog1.Name()) //monster fmt.Printf("The name of second dog is %q. ", dog2.Name()) //little pig fmt.Println()
返回结果: 发现dog1赋值给dog2还是复制一个副本 结构不是引用类型
The name of first dog is "little pig".
The name of second dog is "little pig".
The name of first dog is "monster".
The name of second dog is "little pig".
// 示例3。 dog = Dog{"little pig"} fmt.Printf("The dog's name is %q. ", dog.Name()) pet = &dog //这里注意 赋值了指针地址 dog.SetName("monster") fmt.Printf("The dog's name is %q. ", dog.Name()) fmt.Printf("This pet is a %s, the name is %q. ", pet.Category(), pet.Name()) //monster
结果自己演示下就知道了
我们再来看下指针
一般指针的空值就是nil 我们来看个示列
// 示例1。 var dog1 *Dog fmt.Println("The first dog is nil.") dog2 := dog1 if dog2 == nil{ fmt.Println("dog2 is nil") //这里会被打印 }else { fmt.Println("The second dog is nil.") }
我们再来看个示列 我们把dog值赋值给Pet接口(因为dog实现了Pet方法)
var pet Pet = dog2 if pet == nil { fmt.Println("The pet is nil.") } else { fmt.Println("The pet is not nil.") } fmt.Printf("The type of pet is %T. ", pet) fmt.Printf("The value of pet is %v. ", pet) fmt.Printf("The type of pet is %s. ", reflect.TypeOf(pet).String()) fmt.Printf("The type of second dog is %T. ", dog2) fmt.Printf("The value of second dog is %v. ", dog2) fmt.Println()
这里很误导人 大多人会觉得 pet变量是nil其实不是
接口只有在申明或被直接赋值nil的时候才是nil 其它不是nil 当我们给一个接口变量赋值的时候,该变量的动态类型会与它的动态值一起被存储在一个专用的数据结构中
pet虽然被包装的动态值是nil,但是pet的值却不会是nil,因为这个动态值只是pet值的一部分而已。
当我们把dog2的值赋给变量pet的时候,dog2的值会先被复制,不过由于在这里它的值是nil,所以就没必要复制了。然后,Go 语言会用我上面提到的那个专用数据结构iface的实例包装这个dog2的值的副本,这里是nil。虽然被包装的动态值是nil,但是pet的值却不会是nil,因为这个动态值只是pet值的一部分而已。顺便说一句,这时的pet的动态类型就存在了,是*Dog。我们可以通过fmt.Printf函数和占位符%T来验证这一点,另外reflect包的TypeOf函数也可以起到类似的作用。换个角度来看。我们把nil赋给了pet,但是pet的值却不是nil。这很奇怪对吗?其实不然。在 Go 语言中,我们把由字面量nil表示的值叫做无类型的nil。这是真正的nil,因为它的类型也是nil的。虽然dog2的值是真正的nil,但是当我们把这个变量赋给pet的时候,Go 语言会把它的类型和值放在一起考虑
也就是说,这时 Go 语言会识别出赋予pet的值是一个*Dog类型的nil。然后,Go 语言就会用一个iface的实例包装它,包装后的产物肯定就不是nil了。只要我们把一个有类型的nil赋给接口变量,那么这个变量的值就一定不会是那个真正的nil。因此,当我们使用判等符号==判断pet是否与字面量nil相等的时候,答案一定会是false。那么,怎样才能让一个接口变量的值真正为nil呢?要么只声明它但不做初始化,要么直接把字面量nil赋给它。
上面打印出来的结果是:
The pet is not nil.
The type of pet is *main.Dog.
The value of pet is <nil>.
The type of pet is *main.Dog.
The type of second dog is *main.Dog.
The value of second dog is <nil>.
我们再来看个问题
如果我们把一个值为nil的某个实现类型的变量赋给了接口变量,那么在这个接口变量上仍然可以调用该接口的方法吗?如果可以,有哪些注意事项?如果不可以,原因是什么?
答案:
可以的,不过方法内不能使用实现类型内的变量,并且方法接收者必须是指针类型
代码示列:
type Named interface { // Name 用于获取名字。 Name() string } type PetTag struct { //named接口实现 name string owner string } func (pt *PetTag) Name() string { return "not null" //这里切记 不能调用指针里的字段 } func main(){ var pet *PetTag = nil var named Named named = pet named.Name() //这样不会发生恐慌 }