方法
在函数声明时,在其名字之前放上一个变量,即是一个方法,这个附加的参数会蒋该函数附加到这种类型上,即相当于为这种类型定义了一个独占的方法.如果函数声明时,名字前面没有接收变量,即是一个函数.
在Go语言里,我们为一些简单的数值,字符串,slice,map来定义一些附加行为很方便,方法可以被声明带任意类型,只要不是一个指针或者一个interface
在声明方法时,如果一个类型名本身是一个指针的话,是不允许其出现在接收器中的,比如:
type P *int
func (p P) f(){}//invalid receiver type P (P is a pointer type)
示例:
type People struct {
n int
}
func (p People) sayE() {
fmt.Println("syaE...")
}
func (p *People) sayC() {
fmt.Println("sayC...")
}
func main() {
p := People{}
p.sayC() //编译器自动将其转为(&p)sayC()
p.sayE()
p2 := &People{}
p2.sayC()
p2.sayE() //编译器自动将其转为(*p)sayE()
}
编译器会隐式的帮我们用&p去调用方法sayC,这种简写方法只适用于“变量”,包括struct里的字段,比如p.X,以及array和slice内的元素,比如slice[0],
我们不能通过一个无法取到地址的接收器来调用指针方法,比如临时变量的内存地址就是无法获取到的:People{}.sayC(),报错: compile error: can't take address of People literal
p2.sayE(),可以通过地址来找到这个变量,只要用解引用符号*来取到该变量即可,编译器在这里也会给我们隐式的插入*这个操作符,如(*p)sayE()
不管你的method的receiver是指针类型还是非指针类型,都是可以通过指针/非指针类型进行调用的,编译器会帮你做类型转换
接口
接口类型是对其他类型行为的抽象和概括,接口类型不会和特定的实现细节绑定在一起,使得函数更加的灵活和更加的具有适应能力.Go中的接口无需显示的实现,而是实现了接口的所有方法就隐式的实现了该接口.即一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口
一个类型可以自由的使用另一个满足相同接口的类型来进行替换被称作可替换性(LSP里氏替换)
每一个具体类型的组基于它们相同的行为可以表示成一个接口类型。不像基于类的语言,他们一个类实现的接口集合需要进行显示的定义,在Go语言中我们可以在需要的时候定义一个新的抽象或者特定的组,而不需要修改具体类型的定义。当具体的类型来自不同的作者时这种方式会特别有用。
接口值可以使用==和!=来进行比较,如果两个接口值都是nil或者二者的动态类型完全一致且二者动态值相等(使用动态类型的==操作符来做比较),那么两个接口值相等。因为接口值是可以比较的,所以它们可以用在map的键或者作为switch语句的操作数,然而,如果两个接口值的动态类型相同,但是这个动态类型是不可比较的(比如切片),蒋它们进行比较就会失败并且panic:
var x interface{} = []int{1,2,3}
fmt.Println(x == x) //panic:comparing uncomparable type []int
考虑到这点,接口类型的非常与众不同的,其他类型要么是安全的可比较类型(如基本类型和指针),要么是完全不可比较的类型(如切片,映射类型和函数),但是在比较接口值或者包含了接口值的聚合类型时,我们必须要意识到潜在的panic。同样的风险也存在于使用接口作为map的键或者switch的操作数,只能比较你非常确定它们的动态值是可比较类型的接口值。
一个包含nil指针的接口不是nil接口:一个不包含任何值的nil接口值和一个刚好包含nil指针的接口值是不同的。
const debug = false
func main() {
var buf *bytes.Buffer
if debug {
buf = new(bytes.Buffer)
}
f(buf)
}
func f(out io.Writer) { //蒋io.Writer改为 *bytes.Buffer就不会报nil pointer dereference
if out != nil{
out.Write([]byte("done!!!\n")) //panic: runtime error: invalid memory address or nil pointer dereference
}
}
当调用函数f时,它给f函数的out参数赋了一个*bytes.Buffer的空指针,所以out的动态值是nil。然而,它的动态类型是*bytes.Buffer,意思就是out变量是一个包含空指针值的非空接口,所以out != nil的结果依然是true
尽管一个nil的*bytes.Buffer指针有实现这个接口的方法,它也不满足这个接口具体的行为上的要求,特别是这个调用违反了(*bytes.Buffer).Write方法的接收者非空的隐含先决条件,所以蒋nil指针赋值给这个接口是错误的,解决方案就是蒋main函数中的变量buf的类型改为io.Writer,因此可以避免一开始就蒋一个不完全的值赋给这个接口
const debug = false
func main() {
var buf io.Writer//*bytes.Buffer
if debug {
buf = new(bytes.Buffer)
}
f(buf)
}
func f(out io.Writer) {
if out != nil{
out.Write([]byte("done!!!\n"))
}
}
示例:
type Person interface {
eat()
}
type People struct {
n int
}
/**
1. &People{}
*/
func (p *People) eat() {
fmt.Println("eat...")
}
/**
1. People{}
2. &People{} 编译器自动将其转为People对象
*/
/*func (p People) eat() {
}*/
func A(p Person) {
p.eat()
}
func main() {
//*People实现Person接口时
A(&People{})
//People实现Person接口时
A(People{})
A(&People{})
}
参考:https://www.jianshu.com/p/32225b60fa75 ,Go程序设计语言