12 | 使用函数的正确姿势
package main
import "fmt"
//只要两个函数的参数列表和结果列表中的元素顺序及其类型是一致的,们就可以说它们是一样的函数,或者说是实现了同一个函数类型的函数。
type Printer func(content string) (n int, err error)
func printToStd(content string) (byteNum int, err error) {
return fmt.Println(content)
}
func main() {
var p Printer
p = printToStd
p("something")
//printToStd("s")
}
package main
import (
"errors"
"log"
"fmt"
)
//接受其他的函数作为参数传入
type operate func(x,y int) int
func calculate(x,y int,op operate)(int,error) {
if op == nil {
return 0,errors.New("invalid operation")
}
return op(x,y),nil
}
func main() {
//匿名函数
op := func(x,y int) int {
return x + y
}
//作为参数 传入到calculate
res,err := calculate(1,2,op)
if err != nil {
log.Fatal(err)
}
fmt.Println(res)
//入参的时候给出函数的实现
res,err = calculate(1,2, func(x, y int) int {
return x * y
})
if err != nil {
log.Fatal(err)
}
fmt.Println(res)
}
值拷贝 与 引用类型
1.分2种情况,若是修改数组中的切片的某个元素,会影响原数组。若是修改数组的某个元素即a[1]=[]string{"x"}就不会影响原数组。谨记Go中都是浅拷贝,值类型和引用类型的区别
2.当函数返回指针类型时不会发生拷贝。当函数返回非指针类型并把结果赋值给其它变量肯定会发生拷贝
13 | 结构体及其方法的使用法门
类似与java的toString
package main
import "fmt"
type A struct {
a1 string
a2 string
}
type B struct {
b1 string
b2 string
}
func (b B)String() string {
return fmt.Sprintf("%s %s",b.b1,b.b2)
}
func main() {
a := A{
"a1",
"a2",
}
b := B{
"b1",
"b2",
}
fmt.Println(a)
fmt.Println(b)
}
结果:(结构体似乎是实现了String接口)
{a1 a2}
b1 b2
函数可以当做值被传递,可以没有名字
但是方法不能被传递,需要有名字
package main
import "fmt"
type A1 struct {
a1 string
a2 string
B1
}
type B1 struct {
b1 string
b2 string
}
func (b B1)String() string {
return fmt.Sprintf("%s %s",b.b1,b.b2)
}
func main() {
b := B1{
"b1",
"b2",
}
a := A1{
"a1",
"a2",
b,
}
fmt.Println(a.B1.b1)
fmt.Println(a.b1)
fmt.Println(a.String())
fmt.Println(a.B1.String())
}
当嵌入字段上的方法与主体的方法同名(A与B1有相同的方法),通过主体调用方法时,会屏蔽嵌入字段的方法
问题 1:Go 语言是用嵌入字段实现了继承吗?
这里强调一下 Go 语言中根本没有继承的概念,它所做的是通过嵌入字段的方式实现了类型之间的组合
关于值方法和指针方法 这个解释很不错
方法的定义感觉本质上也是一种语法糖形式,其本质就是一个函数,声明中的方法接收者就是函数的第一个入参,在调用时go会把施调变量作为函数的第一个入参的实参传入,比如
func (t MyType) MyMethod(in int) (out int)
可以看作是
func MyMethod(t Mytype, in int) (out int)
比如 myType.MyMethod(123) 就可以理解成是调用MyMethod(myType, 123),如果myType是*MyType指针类型,则在调用是会自动进行指针解引用,实际就是这么调用的 MyMethod(*myType, 123),这么一理解,值方法和指针方法的区别也就显而易见了。
思考题:
1.我们可以在结构体类型中嵌入某个类型的指针类型吗?如果可以,有哪些注意事项?
2.字面量struct{}代表了什么?又有什么用?
思考题1, 我们可以在结构体中嵌入某个类型的指针类型, 它和普通指针类似,默认初始化为nil,因此在用之前需要人为初始化,否则可能引起错误
思考题2, 空结构体不占用内存空间,但是具有结构体的一切属性,如可以拥有方法,可以写入channel。所以当我们需要使用结构体而又不需要具体属性时可以使用它。
14 | 接口类型的合理运用
package main
type Pet interface {
SetName(name string)
Name() string
}
type Dog struct {
name string
}
func (d Dog)Name() string {
return d.name
}
func (d *Dog)SetName(name string) {
d.name = name
}
//dog不是接口的实现 dog指针才是 因为dog只实现了一个方法
这边有两个注意点
首先是pet的值 是dog而不是dog1
其次是pet1与pet2不同
package main
import "fmt"
type Pet interface {
//SetName(name string)
Name() string
}
type Dog struct {
name string
}
func (d Dog)Name() string {
return d.name
}
func (d *Dog)SetName(name string) {
d.name = name
}
func main() {
dog := Dog{"dog"}
var pet Pet = dog
dog.SetName("dog1")
fmt.Println(dog) //dog1
fmt.Println(pet) //dog
var pett Pet = &dog
dog.SetName("dog1")
fmt.Println(dog) //dog1
fmt.Println(pett) //dog1
var d Dog
var pet1 Pet
var pet2 = d
fmt.Println(pet1) //nil
fmt.Println(d) //{}
fmt.Println(pet2) //{}
}
当我们给一个接口变量赋值的时候,该变量的动态类型会与它的动态值一起被存储在一个专用的数据结构中(iface)。
接口变量的值并不等同于这个可被称为动态值的副本。它会包含两个指针,一个指针指向动态值,一个指针指向类型信息。
接口之间的组合
type A interface {
a()
}
type B interface {
A
b()
}
写代码的时候 可以把大借口拆分为几个小接口,这样程序会更灵活。
15 | 关于指针的有限操作