关于接口的认识

接口类型,是一种抽象的类型。不会暴露出它所代表的对象内部值的结构和这个对象支持哪些基础操作。 概括来说就是,当我们看到一个接口的时候,我们不会知道它是什么,只能知道的就是可以通过它的方法来做什么

实现接口的条件

一个类型如果拥有了一个接口需要的所有方法,那么这个类型就实现了这个接口。

表达一个类型属于某个接口,只要这个类型实现了这个接口。所以我们可以将任意一个值赋值给空接口

interface{} 被称为空接口类型,空接口类型对实现它的类型没有要求

对于创建一个interface{} 值持有一个boolean, float, string, map, pointer或者任意其他类型,我们不能直接对他支持有的值做操作,这里再次强调,interface{}没有任何方法

接口值

接口值由两个部分组成:一个具体类型和那个类型的值。被称为接口的动态类型和动态值。 对于Go来说,类型是编译期的概念,因此一个类型不是一个值。

接口的零值:类型和值的部分都是nil

一个接口值基于它的动态类型被描述为空或非空,所以一个空的接口值,你可以使用w == nil 或者w!=nil 来判断接口值是否为空,调用一个空接口值上的任意方法都会昌盛panic

接口值偶可以用== 和 != 进行比较。两个接口值相等仅当他们都是nil值或者他们的动态类型相同并且动态值也根据这个动态类型的==操作相等。 因为接口值是可以比较的,所以他们可以用在map的键或者作为switch语句的操作数。

但是切记,如果两个接口的值的动态类型相同,但是这个动态类型不可以比较如切片,将他们进行比较就会失败并且panic

**接口类型是非常与众不同的。其它类型要么是安全的可比较类型(如基本类型和指针)要 么是完全不可比较的类型(如切片,映射类型,和函数),但是在比较接口值或者包含了接口值的聚合 类型时,我们必须要意识到潜在的panic。 **

一个包含nil指针的接口不是nil接口

一个不包含任何值的nil接口值和一个刚好包含nil指针的接口值是不同的。

如下面的情况:

var buf *bytes.Buffer

buf != nil

这里的情况就是buf变量赋值了一个*bytes.Buffer的空指针,所以out的动态值是nil, 然后他的动态类型是 *bytes.Buffer, buf在这里是一个包含空指针的非空指针。所以buf != nil的接口依然是true

sort.Interface接口

一个内置的排序算法需要三个东西:序列的长度,表示两个元素比较的结果,一种交换两个元素的方式;

package sort
type Interface interface {
    Len() int
    Less(i, j int) bool // i, j are indices of sequence elements
    Swap(i, j int)
    
}

为了对序列进行排序,我们就需要定义一个实现了这三个方法的类型。然后对这个类型的一个实例执行sort.Sort函数

假如我们对字符串切片进行排序:

type StringSlice []string
func(p StringSlice) Len() int { return len(p)}
func (p StringSlice) Less(i, j int) bool {return p[i]< p[j]}
func(p StringSlice) Swap(i,j int) { p[i], p[j] = p[j], p[i]}

sourt.Sort(StringSlice(names))

error 接口

调用errors.New函数是非常稀少的,因为有一个非常方便的封装函数fmt.Errorf,它还能处理字符串格式化

类型断言

类型断言是一个使用在接口值上的操作,语法看起来想x.(T)被称为断言类型,这里x表示一个接口的类型和T表示一个类型

一个类型断言检查它操作的对象的动态类型是否和断言的类型匹配

这里有两种可能: 第一种,如果断言的类型T是一个具体类型,然后类型断言检查x的动态类型是否和T相同,如果检查成功,类型断言的结果是x的动态值,它的类型就是T,具体类型的类型断言从它的操作对象中获得具体的值,如果检查失败,接下来的这个操作会抛出panic

第二种,如果断言的类型T是一个接口类型,然后类型断言检查是否x的动态类型满足T,如果检查成功,动态值没有获取到,这个结果仍然是一个有相同类型的值部分的接口,但是结果有类型T. 对于一个接口类型的类型断言改变了类型的表述方式,改变了可以获取的方法集合,但是它保护了接口值内部动态类型和值的部分

插入遗留知识

IntSet类型的String方法的接收者是一个指针类型,所以不能在一个不能寻址的IntSet值上调用这个方法

type IntSet struct {}

func (*Insert) String() string

var _ = InSet{}.String()  // compile error: String requires *IntSet receiver



var a IntSet

var _ = s.String() // OK: s is a variable and &s has a String method

关于给接口赋值

先说一个通用的规则:

如果我们使用一个变量给另外一个变量赋值,那么真正赋给后者的并不是前者持有的那个值,而是该值的一个副本

但是对于接口赋值来说这里其实还有一层原因: 我们都知道接口类型本身是无法被值化的,在我们赋予它实际值之前,它的值就是nil, 这也是它的零值 当我们给一个接口变量赋值的时候,该变量的动态类型和动态值一起被存储在一个专用的数据结构中。 所以这样一个变量的值其实是这个专用数据结构的一个实例,而不是我们赋给该变量的实际的值。

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"
}

func main() {
    dog := Dog{"aaa"}
    fmt.Printf("the dog name is %q\n", dog.Name())
    var pet Pet = dog
    dog.SetName("monster")
    fmt.Printf("The dog name is %q\n", dog.Name())
    fmt.Printf("This pet is a %s the name is %q\n",pet.Category(),pet.Name())
    fmt.Println()

    dog1 := Dog{"bbb"}
    fmt.Printf("The name of ifrst dog is %q.\n", dog1.Name())
    dog2 := dog1
    fmt.Printf("the name of second dog is %q.\n", dog2.Name())
    dog1.name = "monster"
    fmt.Printf("The name of ifrst dog is %q.\n", dog1.Name())
    fmt.Printf("the name of second dog is %q.\n", dog2.Name())
    fmt.Println()

    dog = Dog{"ccc"}
    fmt.Printf("the dog name is %q.\n", dog.Name())
    pet = &dog
    dog.SetName("monster")
    fmt.Printf("the dog name is %q.\n", dog.Name())
    fmt.Printf("this pet is a %s the name is %q.\n", pet.Category(), pet.Name())
}

在看一个代码例子:

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"
}

func main() {
    var dog1 *Dog
    fmt.Println("The first dog is nil.")
    dog2 := dog1
    fmt.Println("The second dog is nil")
    var pet Pet = dog2
    if pet == nil {
        fmt.Println("The pet is nil")
    } else {
        fmt.Println("The pet not is nil")
    }
    fmt.Printf("The type of pet is %T\n", pet)
    fmt.Printf("The type of pet is %s.\n", reflect.TypeOf(pet).String())
    fmt.Printf("the type of second dog is %T.\n", dog2)
    fmt.Println()

    wrap := func(dog *Dog) Pet {
        if dog == nil {
            return nil
        }
        return dog
    }
    pet = wrap(dog2)
    if pet == nil {
        fmt.Println("The pet is nil.")
    } else {
        fmt.Println("The pet is not nil")
    }

}

只有我们把一个有类型的nil赋给接口变量,那么这个变量的值就一定不会是那个真正的nil. 因此,当我们使用判断符号== 判断pet是否与字面量nil相等的时候,答案一定回事false

如果想要一个接口变量的值真正为nil,要么只声明它,但是不做初始化,要么直接把字面量nil赋值给它

关于接口之间的组合

只要组合接口之间有同名的方法就会产生冲突,从而无法通过编译,即使同名方法的签名彼此不同也是如此