Go语言核心36讲
郝林
《Go并发编程实战》作者,前轻松筹大数据负责人
立即订阅
24139 人已学习
课程目录
已完结 54 讲
0/4登录后,你可以任选4讲全文学习。
开篇词+学习路线 (3讲)
开篇词 | 跟着学,你也能成为Go语言高手
免费
预习篇 | 写给0基础入门的Go语言学习者
50 | 学习专栏的正确姿势
模块一:Go语言基础知识 (6讲)
01 | 工作区和GOPATH
02 | 命令源码文件
03 | 库源码文件
04 | 程序实体的那些事儿(上)
05 | 程序实体的那些事儿(中)
06 | 程序实体的那些事儿 (下)
模块二:Go语言进阶技术 (16讲)
07 | 数组和切片
08 | container包中的那些容器
09 | 字典的操作和约束
10 | 通道的基本操作
11 | 通道的高级玩法
12 | 使用函数的正确姿势
13 | 结构体及其方法的使用法门
14 | 接口类型的合理运用
15 | 关于指针的有限操作
16 | go语句及其执行规则(上)
17 | go语句及其执行规则(下)
18 | if语句、for语句和switch语句
19 | 错误处理(上)
20 | 错误处理 (下)
21 | panic函数、recover函数以及defer语句 (上)
22 | panic函数、recover函数以及defer语句(下)
模块三:Go语言实战与应用 (27讲)
23 | 测试的基本规则和流程 (上)
24 | 测试的基本规则和流程(下)
25 | 更多的测试手法
26 | sync.Mutex与sync.RWMutex
27 | 条件变量sync.Cond (上)
28 | 条件变量sync.Cond (下)
29 | 原子操作(上)
30 | 原子操作(下)
31 | sync.WaitGroup和sync.Once
32 | context.Context类型
33 | 临时对象池sync.Pool
34 | 并发安全字典sync.Map (上)
35 | 并发安全字典sync.Map (下)
36 | unicode与字符编码
37 | strings包与字符串操作
38 | bytes包与字节串操作(上)
39 | bytes包与字节串操作(下)
40 | io包中的接口和工具 (上)
41 | io包中的接口和工具 (下)
42 | bufio包中的数据类型 (上)
43 | bufio包中的数据类型(下)
44 | 使用os包中的API (上)
45 | 使用os包中的API (下)
46 | 访问网络服务
47 | 基于HTTP协议的网络服务
48 | 程序性能分析基础(上)
49 | 程序性能分析基础(下)
尾声与思考题答案 (2讲)
尾声 | 愿你披荆斩棘,所向无敌
新年彩蛋 | 完整版思考题答案
Go语言核心36讲
登录|注册

12 | 使用函数的正确姿势

郝林 2018-09-07
在前几期文章中,我们分了几次,把 Go 语言自身提供的,所有集合类的数据类型都讲了一遍,额外还讲了标准库的container包中的几个类型。
在几乎所有主流的编程语言中,集合类的数据类型都是最常用和最重要的。我希望通过这几次的讨论,能让你对它们的运用更上一层楼。
从今天开始,我会开始向你介绍使用 Go 语言进行模块化编程时,必须了解的知识,这包括几个重要的数据类型以及一些模块化编程的技巧。首先我们需要了解的是 Go 语言的函数以及函数类型。

前导内容:函数是一等的公民

在 Go 语言中,函数可是一等的(first-class)公民,函数类型也是一等的数据类型。这是什么意思呢?
简单来说,这意味着函数不但可以用于封装代码、分割功能、解耦逻辑,还可以化身为普通的值,在其他函数间传递、赋予变量、做类型判断和转换等等,就像切片和字典的值那样。
而更深层次的含义就是:函数值可以由此成为能够被随意传播的独立逻辑组件(或者说功能模块)。
对于函数类型来说,它是一种对一组输入、输出进行模板化的重要工具,它比接口类型更加轻巧、灵活,它的值也借此变成了可被热替换的逻辑组件。比如,我在 demo26.go 文件中是这样写的:
package main
import "fmt"
type Printer func(contents string) (n int, err error)
func printToStd(contents string) (bytesNum int, err error) {
return fmt.Println(contents)
}
func main() {
var p Printer
p = printToStd
p("something")
}
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Go语言核心36讲》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(34)

  • 深白色
    1.分2种情况,若是修改数组中的切片的某个元素,会影响原数组。若是修改数组的某个元素即a[1]=[]string{"x"}就不会影响原数组。谨记Go中都是浅拷贝,值类型和引用类型的区别
    2.当函数返回指针类型时不会发生拷贝。当函数返回非指针类型并把结果赋值给其它变量肯定会发生拷贝
    2018-09-07
    1
    45
  • melon
    感觉go里通常写的函数的定义本质上就是一种语法糖形式,比如
    func test(int)int {
    ...
    }
    其实质就相当于定义了一个名为test,类型为func(int)int的变量,并给这个变量赋了值为{...}的初值,老师这样理解对吧。
    2018-09-07
    2
    11
  • Geek_牛逼的人
    1.如果是进行一层修改,即数组的某个完整元素进行修改(指针变化),那么原有数组不变;如果进行二层修改,即数组中某个元素切片内的某个元素再进行修改(指针未改变),那么原有数据也会跟着改变,传参可以理解是浅copy,参数本身的指针是不同,但是元素指针相同,对元素指针所指向目的的操作会影响传参过程中的原始数据;
    2.一般来说应该是复制的,传参和返回应该是一个对称的过程,本身对这一片内存数据的操作只发生在函数内部,脱离函数就应该脱离这块内存区域

    作者回复: 对的。

    2019-09-09
    6
  • yandongxiao
    go语法的一致性很完美。

    []int{}, map[int]int{} struct{}{} 它们都是由type + literal的形式构成。
    所以,func (x, y int) int {} 也是function type + function literal的形式。
    上面的表达式返回已声明并初始化的变量。所以foo := func (x, y int) int {} 就构成了所谓的匿名变量。
    func Foo(x, y int) int{} 更像是给定义的函数常量,因为Foo不能再被赋予其它值了。

    既然是一等公民,可以声明为变量,那么变量之间就可以比较。
    2018-09-25
    5
  • mumu
    闭包这里为啥不能这样实现呢
    func genCalculator(op operate) calculateFunc{
        return op
    }
    2018-09-09
    1
    4
  • 欢喜哥
    我在下面声明的函数printToStd的签名与Printer的是一致的,因此前者是后者的一个实现,即使它们的名称以及有的结果名称是不同的。

    这个地方是不是 后者是前者的实现? 也就是printToStd 是 Printer的实现?

    作者回复: 对,你可以把这看做是函数级别的接口和实现。

    2018-09-07
    4
  • TimLiu
    针对第一个问题,做了下实验,发现如果改变数组中的元素时是不会改变源数组的,当改变数组中的切片中的元素时是会改变源数组的,我猜是因为切片改变元素时底层数组会改变,所以源数组也跟着改变

    package main

    import "fmt"

    func main() {
    complexArray1 := [3][]string{
    []string{"d", "e", "f"},
    []string{"g", "h", "i"},
    []string{"j", "k", "l"},
    }

    fmt.Printf("The array: %v\n", complexArray1)
    array2 := modifyArray(complexArray1)
    fmt.Printf("The modified array: %v\n", array2)
    fmt.Printf("The original array: %v\n", complexArray1)
    array3 := modifyArray1(complexArray1)
    fmt.Printf("The modified array: %v\n", array3)
    fmt.Printf("The original array: %v\n", complexArray1)
    }

    func modifyArray(a [3][]string) [3][]string {
    a[1] = []string{"d", "e", "p"}
    return a
    }

    func modifyArray1(a [3][]string) [3][]string {
    a[1][1] = "v"
    return a
    }
    2018-09-27
    3
  • RyuGou
    第一道题,显然是不会造成原来内容的修改呀
    package main

    import "fmt"

    func main(){
    complexArray := [3][]string{
    []string{"d", "s", "f"},
    []string{"a", "b", "c"},
    []string{"e", "g", "h"},
    }
    tryTest(complexArray)
    fmt.Println(complexArray)
    }


    func tryTest(array [3][]string){
    array[1] = []string{"1", "2", "3"}
    return
    }
    输出:
    [[d s f] [a b c] [e g h]]

    作者回复: 因为数组的每个元素也会被复制,你这样赋值只会改变数组副本中的一个元素值而已。你直接改其中的切片的元素值就会影响到外边了。

    2018-09-07
    2
  • 兔子高
    哈喽老师你好,我学go的反射是学的最乱的,反射可以拿到您好好讲一讲吗?然后最近看到了go新出的法案里说go 2.0会出泛型有考虑讲一下go的泛型吗?

    作者回复: Go的泛型几年后才会推出,现在没必要讲啊。

    2018-09-07
    2
  • 🐶
    深白色说的很对!作为切片的话,将会影响原数组,毕竟我们知道切片的数据是通过指向地址取值,而函数进行对原数组修改,只是先拷贝一份,然后再修改,根本修改不到原数组。
    函数返回指针类型,的确不会发生拷贝,但是也是将指针值拷贝了,再返回,所以其实深究,还是有拷贝在里面的,若是返回非指针类型的结果,一定会发生拷贝。
    go没有引用传递,只有值传递,所以基本上都是值拷贝。
    2019-08-11
    1
  • benben
    第一个问题应该会受影响,因为数组元素是切片
    第二问题我想应该是复制的
    2018-09-07
    1
  • benben
    第一个问题应该会受影响,因为切片是引用类型的
    第二个问题我想是复制的
    2018-09-07
    1
  • 疯琴
    闭包讲得太牛B了,毕生看过最透彻的讲解。
    2019-11-26
  • panda199393
    看到genCalculator那块的时候刚我想到了python里面的decorator,也是将某个函数作为参数,套到定义好的模板函数里

    作者回复: 差不多,都属于闭包。

    2019-09-07
  • 窝窝头
    1.如果修改complexArray1里面的元素不影响原值,如果对元素里面的元素进行修改会影响原值
    2.函数返回给调用方的结果值如果是指针不会拷贝,如果非指针应该会拷贝
    2019-05-21
  • Aliliin
    改变数组中切片的某个元素的话, 原来的数组值会影响原值 .代码如下

    func TestComplexAreray(t *testing.T) {

    complexArray := [3][]string{
    []string{"d", "e", "f"},
    []string{"g", "h", "i"},
    []string{"j", "k", "l"},
    }
    ComplexAreray(complexArray)
    t.Log(complexArray)
    }

    func ComplexAreray(array [3][]string) {
    for _, v := range array {
    v[0] = "aaa"
    }
    return
    }

    输出为 : [[aaa e f] [aaa h i] [aaa k l]]
    2019-03-13
  • Aliliin
    genCalculator 的实现如下

    type operate func(x, y int) int
    type calculator func(x, y int) (int, error)

    func genCalculator(op operate) calculator {
        return func(x, y int) (int, error) {
            if op == nil {
                return 0, errors.New("Invalid operation")
            }
            return op(x, y), nil
        }

    }
    func TestFunc(t *testing.T) {
        op := func(x, y int) int {
            return x + y
        }
        add := genCalculator(op)
        x, y := 56, 78
        result, err := add(x, y)
        fmt.Printf("The result: %d (error: %v)\n", result, err)
    }
    2019-03-13
  • 轻装渐行
    老师好,请问下,Go一般用什么框架做单元测试呢?

    作者回复: 你可以看一下标准库中的testing包。后面会讲到这个。

    2019-03-12
  • Yayu
    请教老师一个关于控制算法相关的内容。本文中提及的卫述语句,经常会在诸如参数检查的时候使用,如果我业务逻辑代码模块中有很多函数,每个函数的参数是个名为 xxrRequest 的结构体,那么我就要在每个函数里写大段大段的卫戍语句。有什么可行的办法可以优化这种写法吗?

    作者回复: 把检查代码封装到结构体的方法中,或者统一到某一个检查专用的程序实体中。

    2019-02-20
  • yann [扬] :曹同学
    以前看好像没图呢,惊喜
    2019-01-14
收起评论
34
返回
顶部