chaoz的杂货铺

生命有息、学无止境、折腾不止

0%

2022-Golang-函数与方法

函数

“函数是一等的公民”是函数式编程(functional programming)的重要特征。

函数签名

函数的签名其实就是函数的参数列表和结果列表的统称,它定义了可用来鉴别不同函数的那些特征,同时也定义了我们与函数交互的方式。

各个参数和结果的名称不能算作函数签名的一部分。严格来说,函数的名称也不能算作函数签名的一部分,它只是我们在调用函数时,需要给定的标识符而已。

函数声明

注意这里的写法,在类型声明的名称右边的是func关键字,我们由此就可知道这是一个函数类型的声明。
例如:
type Printer func(contents string) (n int, err error)

高阶函数

什么是高阶函数?
简单地说,高阶函数可以满足下面的两个条件:

  1. 接受其他的函数作为参数传入;
  2. 把其他的函数作为结果返回。

闭包

有点像python的闭包,装饰器。
20220124183758
对于一个函数作为参数传递给函数/让函数来返回一个函数。

闭包函数的类型、闭包函数的自由变量、高阶函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"errors"
"fmt"
)

type operate func(x, y int) int

type calculateFunc func(x int, y int) (int, error)

func genCalculator(op operate) calculateFunc {
return func(x int, y int) (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
}
x, y := 56, 78
add := genCalculator(op)
result, err := add(x, y)
fmt.Printf("The result: %d (error: %v)\n",
result, err)
}

20220302100132

自由变量

在一个函数中存在对外来标识符的引用。所谓的外来标识符,既不代表当前函数的任何参数或结果,也不是函数内部声明的,它是直接从外边拿过来的。

虚函数

C++ 中有的,能够实现多态特性。
多态性是指一个名字,多种语义;或界面相同多种实现。
虚函数允许函数调用与函数题的联系在运行时才进行,称为动态联编。

搞不懂
go实现虚函数
https://blog.csdn.net/u010872203/article/details/107961060

可变函数

一般来说,函数只能接收固定数量的参数。而一个可变函数,它可以接受任意数量的参数。
如果函数的最后一个参数使用 … 前缀进行修饰,那这个函数就可以接收任意长度的参数 — 这个函数也被称为可变函数。
对于一个函数,只有最后一个参数才能是可变的。我们将在下面讨论,为什么是这样的。

append函数 就是一个可变函数

之前你可能会疑问: 为什么 append函数 可以为切片追加任意数量的元素呢?这其实是因为,append函数 就是一个可变函数。

func append(slice []Type, elems …Type) []Type

上面就是 append函数 的定义了。
在定义中,elems 就是一个可变参数。因此 append函数 可以接受任意数量的元素。

init()

20220222121801

实现场景

方法

经典demo

意会一下吧 。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package main

import "fmt"

type Pet interface {
Name() string
Category() string
SetName(name 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"
}

type Dog1 struct {
name string // 名字。
}

func (dog *Dog1) SetName(name string) {
dog.name = name
}

func (dog Dog1) Name() string {
return dog.name
}

func (dog Dog1) Category() string {
return "dog"
}

var mHandler = []Pet{
new(Dog),
new(Dog1),
}

func main() {
//示例1。
dog := Dog{"little pig"}
fmt.Printf("The dog's name is %q.\n", dog.Name())
var pet Pet = &dog
dog.SetName("monster")
fmt.Printf("The dog's name is %q.\n", dog.Name())
fmt.Printf("This pet is a %s, the name is %q.\n",
pet.Category(), pet.Name())
fmt.Println()

// 示例2。
dog1 := Dog{"little pig"}
fmt.Printf("The name of first 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 first dog is %q.\n", dog1.Name())
fmt.Printf("The name of second dog is %q.\n", dog2.Name())
fmt.Println()

// 示例3。
dog = Dog{"little pig"}
fmt.Printf("The dog's name is %q.\n", dog.Name())
pet = &dog
dog.SetName("monster")
fmt.Printf("The dog's name is %q.\n", dog.Name())
fmt.Printf("This pet is a %s, the name is %q.\n",
pet.Category(), pet.Name())

for _, handler := range mHandler {
handler.SetName("ceshi")
fmt.Printf("The name of second dog is %q.\n", handler.Category())

fmt.Printf("The name of second dog is %q.\n", handler.Name())
}
}

20220302175039
20220302175115

接口

Go语言的接口实现是隐式的,无须让实现接口的类型写出实现了哪些接口。这个设计被称为非侵入式设计。

接口的实现需要遵循两条规则才能让接口可用:

  1. 接口的方法与实现接口的类型方法格式一致

    在类型中添加与接口签名一致的方法就可以实现该方法。

    签名包括方法中的名称、参数列表、返回参数列表。

    也就是说,只要实现接口类型中的方法的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致,那么接口的这个方法就不会被实现。

当类型无法实现接口时,编译器会报错:

  1. 函数名不一致导致的报错
  2. 实现接口的方法签名不一致导致的报错
  1. 接口中所有方法均被实现

    当一个接口中有多个方法时,只有这些方法都被实现了,接口才能被正确编译并使用。

类型与接口的关系

在Go语言中类型和接口之间有一对多和多对一的关系

一个类型可以实现多个接口

一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。
多个类型实现同一接口
并且一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。

接口嵌套

接口与接口间可以通过嵌套创造出新的接口

结构体

把一个结构体类型嵌入到另一个结构体类型中的意义

Go语言没有继承概念,通过嵌套字段的方式去实现类型的组合。

go的结构体能不能进行比较呢

go结构体和结构体指针的区别

值方法和指针方法都是什么意思,有什么区别?

结构体创建优化

结构体传递场景

空结构体的用处

空结构体不占用内存空间

接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package main

import (
"fmt"
"reflect"
// "reflect"
)

type BB struct {
}

func main() {
var a interface{}
fmt.Println(a)

var b *BB
fmt.Println(b)

a = b
fmt.Println(a)

if a != nil {
fmt.Println("空接口赋值之后其值为nil, 但是其本身不是nil哟.")
fmt.Println("所以打印的时候是nil, 判等的时候并不是nil.")
fmt.Println(a)
}

if b == nil {
fmt.Println("空接口赋值之后其值为nil, 但是其本身不是nil哟.")
fmt.Println("所以打印的时候是nil, 判等的时候并不是nil.")
fmt.Println(a)
}

var a1 []int
b1 := make([]int, 0)
var c1 = []int{}
//var d1 =[]int{5}
if a1 == nil {
fmt.Println("a1 is nil")
} else {
fmt.Println("a1 is not nil")
}
if b1 == nil {
fmt.Println("b1 is nil")
} else {
fmt.Println("b1 is not nil")
}
if c1 == nil {
fmt.Println("c1 is nil")
} else {
fmt.Println("c1 is not nil")
}
fmt.Println("reflect.DeepEqual(b1,c1) = ", reflect.DeepEqual(b1, c1))

var x1 *int = nil
func(x interface{}) {
if x == nil {
fmt.Println("empty interface")
return
}
fmt.Println("non-empty interface")
}(x1)

}

20220301002933

思考题

Go方法与函数的区别?

在Go语言中,函数和方法不太一样,有明确的概念区分。
其他语言中,比如Java,一般来说函数就是方法,方法就是函数;
但是在Go语言中,函数是指不属于任何结构体、类型的方法,也就是说函数是没有接收者的;而方法是有接收者的。

方法

1
2
3
func (t *T) add(a, b int) int {
return a + b
}

其中T是自定义类型或者结构体,不能是基础数据类型int等
函数

1
2
3
func add(a, b int) int {
return a + b
}

怎么定义接口返回类型的?

什么场景使用接口

相比于java interface有什么区别吗?

两个 interface 可以比较吗?

判断类型是否一样
reflect.TypeOf(a).Kind() == reflect.TypeOf(b).Kind()

判断两个interface{}是否相等
reflect.DeepEqual(a, b interface{})

将一个interface{}赋值给另一个interface{}
reflect.ValueOf(a).Elem().Set(reflect.ValueOf(b))

空 struct{} 占用空间么?

空结构体 struct{} 实例不占据任何的内存空间。
可以使用 unsafe.Sizeof 计算出一个数据类型实例需要占用的字节数:

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"unsafe"
)

func main() {
fmt.Println(unsafe.Sizeof(struct{}{})) //0
}

空 struct{} 的用途?

因为空结构体不占据内存空间,因此被广泛作为各种场景下的占位符使用。

  1. 将 map 作为集合(Set)使用时,可以将值类型定义为空结构体,仅作为占位符使用即可。
    空的结构体还是个占位符,map去重不错

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    type Set map[string]struct{}

    func (s Set) Has(key string) bool {
    _, ok := s[key]
    return ok
    }

    func (s Set) Add(key string) {
    s[key] = struct{}{}
    }

    func (s Set) Delete(key string) {
    delete(s, key)
    }

    func main() {
    s := make(Set)
    s.Add("Tom")
    s.Add("Sam")
    fmt.Println(s.Has("Tom"))
    fmt.Println(s.Has("Jack"))
    }
  2. 不发送数据的信道(channel)
    使用 channel 不需要发送任何的数据,只用来通知子协程(goroutine)执行任务,或只用来控制协程并发度。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    func worker(ch chan struct{}) {
    <-ch
    fmt.Println("do something")
    close(ch)
    }

    func main() {
    ch := make(chan struct{})
    go worker(ch)
    ch <- struct{}{}
    }
  3. 结构体只包含方法,不包含任何的字段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    type Door struct{}

    func (d Door) Open() {
    fmt.Println("Open the door")
    }

    func (d Door) Close() {
    fmt.Println("Close the door")
    }
喜欢这篇文章?打赏一下作者吧!

欢迎关注我的其它发布渠道