• 08-Go语言中函数和指针


    函数

    Go语言中支持函数、匿名函数和闭包,并且函数在G语言中属于“一等公民”

    函数的基本定义

    Go语言中定义函数使用func关键字,具体格式如下:

    func 函数名(参数)(返回值){
        函数体
    }
    
    • 1
    • 2
    • 3

    其中:

    • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。

    • 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。

    • 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。

    • 函数体:实现指定功能的代码块。

      我们先来定义一个求两个数之和的函数:

      func intSum(x int,y int)int{
        return x+y
      }
      
      • 1
      • 2
      • 3

    函数的参数和返回值都是可选的,例如我们可以实现一个既不需要参数也没有返回值的函数:

    func sayHello() {
    	fmt.Println("Hello 沙河")
    }
    
    • 1
    • 2
    • 3

    函数调用

    定义了函数之后,我们可以通过函数名()的方式调用函数。 例如我们调用上面定义的两个函数,代码如下:

    func main() {
    	sayHello() //调用函数
    	res := initSum(4, 5)
    	fmt.Println(res)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意,调用有返回值的函数时,可以不接受其返回值。

    函数的参数

    类型简写:

    函数参数中如果相邻变量类型相同,则可以省略类型,例如:

    func initSum(x,y int)int{
      return x+y
      
    }
    
    • 1
    • 2
    • 3
    • 4

    上面的代码中,intSum函数有两个参数,这两个参数的类型均为int,因此可以省略x的类型,因为y后面有类型说明,x参数也是该类型。

    不定长参数:

    可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后...来标识。

    注意:可变参数通常要作为函数最后的一个参数。

    //定义可变参数函数
    func initSum2(x ...int) int {
    	fmt.Println(x) //x是一个切片
    	sum := 0
    	for _, v := range x {
    		sum += v
    	}
    	return sum
    
    }
    
    -----------------------------------
    //调用上面的函数:
    
    	res1 := initSum2()
    	res2 := initSum2(10)
    	res3 := initSum2(10, 20)
    	res4 := initSum2(10, 20, 30)
    	fmt.Println(res1, res2, res3, res4)
    
    //运行结果:
    []
    [10]
    [10 20]
    [10 20 30]
    0 10 30 60
    
    • 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

    可变参数搭配固定函数使用时,可变函数在固定函数的后面,示例代码如下:

    //固定参数+可变参数
    func initSum3(x int, y ...int) int {
    	fmt.Println(x, y)
    	sum := x
    	for _, v := range y {
    		sum += v
    	}
    	return sum
    
    }
    
    //调用上面函数
    res5 := initSum3(100)
    	res6 := initSum3(100, 10)
    	res7 := initSum3(100, 10, 20)
    	fmt.Println(res5, res6, res7)
    
    //运行结果:
    100 []
    100 [10]
    100 [10 20]
    100 110 130
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    本质上,函数的可变参数是通过切片实现的。

    注意:没有默认参数!!!

    函数返回值

    Go语言中通过return关键字向外输出返回值。

    多个返回值:

    GO语言中函数支持多返回值,函数如果有多个返回值时,必须用()将所有返回值包裹起来。

    //多返回值
    func calc(x, y int) (int, int) { //定义多返回值
    	sum := x + y
    	sub := x - y
    	return sum, sub
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    命名返回值

    函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。

    //返回值命名
    func calc2(x, y int) (sum, sub int) {
    	sum = x + y //因为在返回值已经声明定义参数,函数体直接使用就行
    	sub = x - y
    	return sum, sub
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    返回值补充

    当我们的一个函数返回值类型为slice时,nil可以看做是一个有效的slice,没必要显示返回一个长度为0的切片。

    func someFunc(x string) []int {
    	if x == "" {
    		return nil // 没必要返回[]int{}
    	}
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    函数进阶

    变量的作用域
    1. 全局变量

      全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效。在函数中可以访问到全局变量。

      package main
      
      import "fmt"
      
      //定义全局变量num
      var num int64 = 10
      
      func testGlobalVar() {
      	fmt.Printf("num=%d\n", num) //函数中可以访问全局变量num
      }
      func main() {
      	testGlobalVar() //num=10
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    2. 局部变量

      局部变量又分为两种:函数内定义的变量和语句快定义的变量。

      函数内定义的变量:

      func testGlobalVar() {
      	//定义一个函数局部变量num,仅在该函数内生效
      	num := 10
      	fmt.Printf("num=%d\n", num)
      }
      
      func main() {
      	testGlobalVar()
      	fmt.Println(num) //此时无法使用变量num
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      如果局部变量和全局变量重名,优先访问局部变量。

      package main
      
      import "fmt"
      
      //定义全局变量num
      var num int64 = 10
      
      func testNum() {
      	num := 100
      	fmt.Printf("num=%d\n", num) // 函数中优先使用局部变量
      }
      func main() {
      	testNum() // num=100
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      语句快定义的变量:

      通常我们会在if条件判断,for循环,switch语句上使用这种定义变量的方式.

      //语句块定义的变量(if语句块)
      func testlocalVar2(x, y int) {
      	fmt.Println(x, y) //函数的参数也只能在本函数内生效
      	if x > 0 {
      		z := 10 //变量z只能在if语句块生效
      		fmt.Println(z)
      	}
      	// fmt.Println(z) //此处无法使用变量z
      }
      
      //调用
      testlocalVar2(10,20)
      //运行结果:
      10 20
      10
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      //语句块定义的变量(for语句块)
      func testlocalVar3() {
      	for i := 0; i < 10; i++ {
      		fmt.Println(i) //变量i只在当前for循环语句块中生效
      	}
      	//fmt.Println(i) 此处无法使用变量i
      }
      
      //调用函数
      testlocalVar3()
      
      //运行结果:
      0
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      函数类型与变量

      1、定义函数类型

      我们可以使用type关键字来定义一个函数类型,具体格式如下:

      type calculation func(int, int) int
      
      • 1

      上面语句定义了一个名为calculation的函数类型。这种函数类型里面的函数接收两个int类型的参数并且有一个int类型的返回值.

      简单来说,凡是满足这个条件的函数都是calculation类型的函数,例如下面的add和sub是calculation类型。

      func add(x,y int)int{
        return x+y
      }
      
      func sub(x,y int)int{
        return x+y
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      add和sub都能赋值给calculation类型的变量

      var c calculation
      c=add
      
      • 1
      • 2

      2、函数类型变量

      我们可以声明函数类型的变量并且为该变量赋值

      //type函数类型
      type calculation func(int, int) int //声明函数类型
      func testType() {
      	var c calculation             //声明一个calculation类型的变量c
      	c = initSum                   //把initSum赋值给c
      	fmt.Printf("type of %T\n", c) // type of c:main.calculation
      	fmt.Println(c(2, 4))          //调用initsum函数
      
      	f := initSum                  // 将函数add赋值给变量f
      	fmt.Printf("type of %T\n", f) //type of f: func(int, int) int
      	fmt.Println(c(6, 10))         // 调用initsum函数
      
      }
      
      //运行结果:
      type of main.calculation
      6
      type of func(int, int) int
      16
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

    只要以type关键字开头的都是定义类型。

    3、函数签名

    • 函数签名 --> 函数定义(声明)的格式,与参数名称和返回值名称无关

    • 函数签名一样 --> 函数的参数、返回值的类型和个数、顺序都要一样

    func fi(name string, age int) {}
    
    func fj(age int, name string) {}
    
    type MyFF func(string, int)
    
    func f16() {
    	var mf MyFF
    	mf = fi
    	// mf = fj  // 函数签名不一致
    	mf("ddd", 1)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    高阶函数

    高阶函数分为函数作为参数和函数作为返回值两部分。

    1、函数作为参数

    //高阶函数-函数作为参数
    func add(x, y int) int {
    	return x + y
    }
    func calc3(x, y int, op func(int, int) int) int { //传入一个名字为op的参数为2个int,返回值int的函数
    	return op(x, y)
    }
    
    //调用
    func main() {
    	ret2 := calc(10, 20, add)
    	fmt.Println(ret2) //30
    }
    
    //运行结果:
    30
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2、函数作为返回值

    //高阶函数-函数作为返回值
    /*
    	1、函数内部调用了一个变量res
    	2、返回值是res
    */
    
    func do(x, y int, s string) (res func(int, int) int) {
    	switch s {
    	case "+":
    		return add
    	case "-":
    		return sub
    	}
    	return res
    }
    
    func do1(x, y int, s string) func(int, int) int {
    	var res func(int, int) int
    	switch s {
    	case "+":
    		return add
    	case "-":
    		return sub
    	}
    	return res
    }
    
    • 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
    匿名函数和立即执行函数

    函数当然还可以作为返回值,但是在GO语言中函数内部不能在像之前定义函数了,只能定义匿名函数。匿名函数就是没有函数名的函数,匿名函数的定义格式为:

    func(参数)(返回值){
        函数体
    }
    
    • 1
    • 2
    • 3

    1、把匿名函数赋值给变量

    //匿名函数
    //1、把匿名函数赋值给变量
    
    func f33() func() {
    	f1 := func() {
    		fmt.Println("美好的周末就要结束啦")
    	}
    	return f1
    }
    
    //调用
    f33()()
    
    //运行结果:
    美好的周末就要结束啦
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2、自执行函数

    //2、自执行函数:匿名函数定义完加()直接执行
    func f34() {
    	func() {
    		fmt.Println("哈哈哈哈")
    	}() //立即执行
    }
    
    //调用函数
    f34()
    
    //运行结果:
    哈哈哈哈
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    闭包

    闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单说,闭包=函数+引用环境。 首先我们来看一个例子:

    //闭包
    func addr() func(int) int {
    	var x int
    
    	//函数内部使用了它外部的函数变量x
    	f := func(y int) int {
    		x += y
    		return x
    	}
    	//把匿名函数当成返回值返回
    	return f
    }
    
    //调用
    func main(){
      f1 := addr()        //调用addr函数
    	fmt.Println(f1(10)) //10
    	fmt.Println(f1(20)) //10+20=30
    
    }
    
    //运行结果:
    10
    30
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    闭包进阶示例1:

    //定义一个累加器
    //新的值被返回的函数一直用
    
    func addr2(x int) func(int) int {
    	f := func(y int) int {
    		x += y
    		return x
    	}
    	return f
    	// return func (y int) int {  //另外一种写的形式
    	// 	x +=y
    	// 	return x
    	// }
    }
    
    //调用函数
    func main(){
      f1 := addr2(10)
    	fmt.Println(f1(20)) // 30
    	fmt.Println(f1(30)) //60
    }
    
    //运行结果:
    30
    60
    
    • 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

    闭包进阶示例2:

    /*
    	判断某个名字是否以指定的后缀结尾。
    */
    func makeSuffixFunc(suffix string) func(string) string {
    	return func(name string) string {
    		if !strings.HasSuffix(name, suffix) {
    			return name + suffix
    		}
    		return name
    
    	}
    }
    
    //调用函数
    func main(){
      jpg := makeSuffixFunc(".jpg")
    	txt := makeSuffixFunc(".txt")
    
    	fmt.Println(jpg("test")) // test.jpg
    	fmt.Println(txt("test")) //test.txt
    }
    
    //运行结果:
    test.jpg
    test.txt
    
    • 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

    闭包进阶示例3:

    /*
    	求两数的sum和sub
    */
    
    func calcs(base int) (func(int) int, func(int) int) {
    	add := func(i int) int {
    		base += i
    		return base
    	}
    
    	sub := func(i int) int {
    		base -= i
    		return base
    	}
    	return add, sub
    }
    
    //调用
    func main(){
      f1, f2 := calcs(10)
    	fmt.Println(f1(1), f2(2)) //11 9
    	fmt.Println(f1(3), f2(4)) // 12 8
    }
    
    //运行结果:
    11 9
    12 8
    
    • 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
    defer
    • 什么场景会用到defer
      • 释放资源
      • 关闭文件
      • 释放连接
    • defer的执行顺序
      • 先注册的后执行
    • defer的执行时机
      • 返回值赋值之后,底层RET指令之前
    • defer语句不能接收返回值 defer x := sub(10, 2)
    //defer语句
    func deFer() {
    	fmt.Println("start")
    	defer fmt.Println("1")
    	defer fmt.Println("2")
    	defer fmt.Println("3")
    	fmt.Println("end")
    }
    
    //调用
    deFer()
    
    //运行结果:
    start
    end
    3
    2
    1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    内置函数

    内置函数介绍
    close主要用来关闭channel
    len用来求长度,比如string、array、slice、map、channel
    new用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
    make用来分配内存,主要用来分配引用类型,比如channel、map、slice
    append用来追加元素到数组、slice中
    panic和recover用来做错误处理
    panic和recover

    Go语言中目前没有异常机制,但是使用panic/recover模式处理错误。panic可以在任何地方引发,但recover只有在defer调用的函数中有效。

    func funcA() {
    	fmt.Println("func A")
    }
    
    func funcB() {
    	panic("panic in B")
    }
    
    func funcC() {
    	fmt.Println("func C")
    }
    func main() {
    	funcA()
    	funcB()
    	funcC()
    }
    
    //运行结果:
    func A
    panic: panic in B
    
    goroutine 1 [running]:
    main.funB(...)
            /Users/alblue/Documents/路飞go/day03/作业/fun.go:212
    main.main()
            /Users/alblue/Documents/路飞go/day03/作业/mian.go:52 +0x66
    
    • 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

    程序运行期间funcB中引发了panic导致程序崩溃,异常退出了。这个时候我们就可以通过recover将程序回复回来,继续往后执行。

    func funA() {
    	fmt.Println("func A")
    }
    
    func funB() {
    	defer func() {
    		err := recover()
    		//如果程序出现panic,通过recover恢复回来
    		if err != nil {
    			fmt.Println("recocer ....")
    		}
    	}()
    	panic("panic in B")
    }
    
    func funC() {
    	fmt.Println("func C")
    }
    func main() {
    	funcA()
    	funcB()
    	funcC()
    }
    //运行结果:
    func A
    recocer ....
    func C
    
    • 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

    注意:

    1. recover()必须搭配defer使用。
    2. defer一定要在可能引发panic的语句之前定义。

    07-指针

    任何程序数据载入内存中,在内存中都有他们的地址,这就是指针。而为了保存一个数据在内存的地址,我们就需要指针变量。

    比如,“永远不要高估自己”这句话是我的座右铭,我想把它写入程序中,程序一启动这句话要加载到内存(假设内存地址为0x123456),我在程序中把这段话赋值给变量A,把内存地址赋值给变量B。这时候变量B就是一个指针变量。通过变量A和变量B都能找到我的座右铭。

    GO语言中的指针不能进行偏移和运算。因此go语言中的指针操作非常简单,我们只需要记住两个符号:&(取地址)和*(根据地址取值)。

    指针地址和指针类型

    每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行取地址操作。GO语言中的值类型(int,float,bool,string,array,struct)都有对应的指针类型,如:*int、*string等。

    去变量指针的语法如下:

    ptr := &v    // v的类型为T
    
    • 1

    其中:

    • v:代表被取地址的变量,类型为T
    • ptr:用于接收地址的变量,ptr的类型就为*T,称做T的指针类型。*代表指针。
    func poInt(){
    	a:=10
    	b:=&a
    	fmt.Printf("a:%d ptr:%p\n",a,&a) //a:10 ptr:0xc000014080
    	fmt.Printf("b:%p ptr:%T\n",b,b)  //b:0xc000014080 ptr:*int
    	
    }
    
    //运行结果:
    a:10 ptr:0xc000014080
    b:0xc000014080 ptr:*int
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    我们看一下b := &a的图示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U80ibcgw-1656580370457)(day03.assets/image-20220116184209206.png)]

    就两个操作:

    1. 取变量x的内存地址: &x 得到的是指针
    2. 有了指针变量p,*p 根据内存地址去找值

    指针取值

    在对普通变量使用&操作符取地址后会获得这个变量的指针,然后可以对指针使用*操作,也就是指针取值,代码如下。

    //指针取值
    func p1() {
    	//指针取值
    	a := 10
    	b := &a //取变量a的地址,将指针保存在b中
    	fmt.Printf("type of b:%T\n", b)
    	c := *b //指针取值(根据指针去内存取值)
    	fmt.Printf("type of c:%T\n", c)
    	fmt.Printf("value of c:%v\n", c)
    }
    
    //运行结果:
    type of b:*int
    type of c:int
    value of c:10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    总结: 取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。

    变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

    • 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
    • 指针变量的值是指针地址。
    • 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。

    指针传值示例:

    func modify1(x int) {
    	x = 100
    }
    
    func modify2(x *int) {
    	*x = 100
    }
    
    func main() {
    	a := 10
    	modify1(a)
    	fmt.Println(a) // 10
    	modify2(&a)
    	fmt.Println(a) // 100
    }
    
    //运行结果:
    10
    100
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    new和make

    new和make都是用来申请内存,new用的较少

    区别:

    1. new返回指针类型
    2. make只能用于slice、map、channel的初始化,返回的还是这三个引用类型本身;
    3. 而new用于类型的内存分配,并且对用的值为类型零值,返回的是指向类型的指针。

    我们先来看一个例子:

    func main() {
    	var a *int
    	*a = 100
    	fmt.Println(*a)
    
    	var b map[string]int
    	b["沙河娜扎"] = 100
    	fmt.Println(b)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    执行上面的代码会引发panic,为什么呢? 在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储。而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间。要分配内存,就引出来今天的new和make。 Go语言中new和make是内建的两个函数,主要用来分配内存。

    1、new

    new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。举个例子:

    //new
    func newDemo() {
    	a := new(int)
    	b := new(bool)
    	fmt.Printf("%T\n", a) //*int
    	fmt.Printf("%T\n", b) //*bool
    	fmt.Println(*a)       //0
    	fmt.Println(*b)       //false
    }
    
    //运行结果:
    *int
    *bool
    0
    false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    本节开始的示例代码中var a *int只是声明了一个指针变量a但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。应该按照如下方式使用内置的new函数对a进行初始化之后就可以正常对其赋值了:

    func newDemo2() {
    	var a *int
    	a = new(int)
    	*a = 10
    	fmt.Println(*a)
    }
    
    //结果:
    10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2、make

    make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。make函数的函数签名如下:

    func make(t Type, size ...IntegerType) Type
    
    • 1

    make函数是无可替代的,我们在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作。这个我们在上一章中都有说明,关于channel我们会在后续的章节详细说明。

    本节开始的示例中var b map[string]int只是声明变量b是一个map类型的变量,需要像下面的示例代码一样使用make函数进行初始化操作之后,才能对其进行键值对赋值:

    func main() {
    	var b map[string]int
    	b = make(map[string]int, 10)
    	b["沙河娜扎"] = 100
    	fmt.Println(b)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    练习

    /*
    你有50枚金币,需要分配给以下几个人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。
    分配规则如下:
    a. 名字中每包含1个'e'或'E'分1枚金币
    b. 名字中每包含1个'i'或'I'分2枚金币
    c. 名字中每包含1个'o'或'O'分3枚金币
    d: 名字中每包含1个'u'或'U'分4枚金币
    写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
    程序结构如下,请实现 ‘dispatchCoin’ 函数
    */
    var (
    	coins = 50
    	users = []string{
    		"Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
    	}
    	distribution = make(map[string]int, len(users))
    )
    
    func dispatchCoin() ( int) {
    	//统计每个user有多少个金币
    	for _, v := range users {
    		for _, s := range v {
    			switch s {
    			case 'e', 'E':
    				distribution[v] += 1
    			case 'i', 'I':
    				distribution[v] += 2
    			case 'o', 'O':
    				distribution[v] += 3
    			case 'u', 'U':
    				distribution[v] += 4
    			}
    		}
    	}
    
    	//计算剩下的金币
    	var sum int
    	for _, v := range distribution {
    		sum += v
    	}
    	
    	return 50 - sum
    
    }
    
    
    func main() {
    	left := dispatchCoin()
    	fmt.Println("剩下:", left)
    }
    
    
    • 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
  • 相关阅读:
    JavaSpringBoot的@Value设置默认值,用冒号:
    java计算机毕业设计在线校园超市系统源代码+系统+数据库+lw文档
    如何利用Xshell远程连接Linux服务器
    AWS 中文入门开发教学 9- 网络IP范围设计
    java-php-python-ssm基于人事管理系统计算机毕业设计
    对象存储Minio-测试
    【Linux问题】This account is currently not available.
    AttributeError: ‘builtin_function_or_method‘ object has no attribute ‘shuffle‘
    算法刷题日志——回溯算法
    [附源码]java毕业设计社区医院管理系统
  • 原文地址:https://blog.csdn.net/weixin_38753143/article/details/125544599