07-Go语言基础之函数

  • Go函数是指:将一个语句序列打包成一个单元,然后可以在程序中其他地方多次调用。

  • Go分为自定义函数,系统函数。

  • 函数可以将一个大的工作拆解成小的任务。

  • 函数对用户隐藏了细节,直接调用函数名,内部代码块自动执行。

  • Golang函数特点:

支持不定长参数
支持多返回值
支持命名返回参数
支持匿名函数、闭包
函数也是类型,可以赋值给变量

一个package下不得有两个同名函数,不支持函数重载

函数参数可以没有,或者多个参数
注意类型在变量名后面
多个连续的函数命名参数是同一类型,除了最后一个类型,其余可以省略
函数可以返回任意数量的返回值
函数体中,形参作为局部变量
函数返回值可以用 _标识符进行忽略

Go函数基本语法

1)形参:函数的输入参数

2)执行代码:实现函数功能的代码块

3)函数的返回值可有可无

func 函数名(形参列表)(返回值列表){
    //执行代码
    return 返回值列表
}

func test(x, y int, z string) (int, string) {
    //类型相同的相邻参数x,y参数类型可以合并
    //多返回值得用括号括起来
    n := x + y
    return n, z
}

语法解读

1. 函数名:由字母,数字、下划线组成,不得以数字开头,同一个包下,函数名不得重复。

2. 参数:有2部分,参数变量名、参数变量类型,多个参数由逗号隔开;

3. 返回值:返回值也一样是变量名、变量类型两部分组成;也可以只写返回值的类型;多个返回值必须有括号包括()。

4. 函数体,函数内具体执行的代码;

Go函数综合语法实战

package main

import "fmt"

// 最普通的函数,无参数,无返回值
func sayHello() {
    fmt.Printf("hello world\n")
}

// 求和函数add
func add(a, b, c int) int {
    sum := a + b + c
    return sum
    //return a + b
}

// 接收2个参数a 和b都是int类型
// 返回2个参数,sum和sub作为返回值,也叫做对返回值命名,繁琐写法,简写(sum,sub int)
// 给返回值命名
func calc(a, b int) (sum, sub int) {
    sum = a + b
    sub = a - b
    return
}

// 接收不定长参数个数、可变参数是指函数的参数数量不固定,通过再参数后添加... 标识。
// 参数名是b,类型是不固定个数的int类型
// 变量b此时是一个slice切片,数据类型:[]int,可以通过索引取值
// 注意,如果有多个参数,可变参数,必须写在最后一个。
func calc_v1(a int, b ...int) int {
    fmt.Println("测试参数a:", a)
    sum := 0
    for i := 0; i < len(b); i++ {
        sum = sum + b[i]
    }
    return sum
}

func main() {
    //执行函数
    sayHello()

    //打印返回值求和结果,可以直接打印,可以用变量接收结果
    fmt.Println("直接打印返回值:", add(5, 5, 5))
    res := add(2, 3, 4)
    fmt.Println("变量接收函数返回值:", res)

    //多个返回值
    sum1, sub1 := calc(5, 10)
    fmt.Printf("calc计算和是%d\n", sum1)
    fmt.Printf("calc计算差是%d\n", sub1)

    //传入不固定长度的参数个数
    //注意参数的传递顺序
    sum := calc_v1(10, 20, 30, 40)
    fmt.Println("可变参数函数结果:", sum)

}

/*
hello world
直接打印返回值: 15
变量接收函数返回值: 9
calc计算和是15
calc计算差是-5
测试参数a: 10
可变参数函数结果: 90

*/

函数进阶

函数变量作用域

全局变量

全局变量就是指在go文件中,函数以外的变量,在整个程序运行的生命周期内都可访问,函数内可以访问全局的变量。

package main

import "fmt"

// 函数外全局变量
var myweb = "www.yuchaoit.cn"

func testGlobalVar() {
    fmt.Println("函数内可以打印全局变量", myweb)
}

func main() {
    testGlobalVar()

    //主函数也一样
    fmt.Println("main函数一样打印全局变量", myweb)

}

/*
函数内可以打印全局变量 www.yuchaoit.cn
main函数一样打印全局变量 www.yuchaoit.cn

*/

局部变量

package main

import "fmt"

// 函数外全局变量
var myweb = "www.yuchaoit.cn"

func testGlobalVar() {
    fmt.Println("函数内可以打印全局变量", myweb)
}

func testLocalVar() {
    localVar := "小黑子"
    fmt.Printf("函数执行:该变量只在函数内可用:%s\n", localVar)
}

func main() {
    //testGlobalVar()

    //主函数也一样
    fmt.Println("main函数一样打印全局变量", myweb)

    //局部变量无法被调用
    //fmt.Println(localVar)
    testLocalVar()
}

/*
main函数一样打印全局变量 www.yuchaoit.cn
函数执行:该变量只在函数内可用:小黑子
*/

函数内变量作用域优先级

package main

import "fmt"

var name = "globalVar"

func testVar() {
    //局部变量也定义name,优先调用局部的,务必注意,这里是一个新变量
    name := "localVar"
    fmt.Printf("testVar--  name变量值:%s\n", name)

}
func main() {
    testVar()
    fmt.Printf("main--  name变量值:%s\n", name)

}

/*
testVar--  name变量值:localVar
main--  name变量值:globalVar

*/

if内变量作用域

package main

import "fmt"

var name = "globalVar"

func testVar() {
    //局部变量也定义name,优先调用局部的,务必注意,这里是一个新变量
    name := "localVar"
    fmt.Printf("testVar--  name变量值:%s\n", name)

}

func testVar2(x, y int) {
    fmt.Printf("函数局部变量:%d、%d\n", x, y)
    //测试if、for等局部变量
    if x > 0 {
        z := 996
        fmt.Println("if内z变量生效:", z)
    }
    //fmt.Println("外部依然无法调用z变量",z)

}

func main() {
    testVar2(5, 6)
    //fmt.Println("外部依然无法调用z变量",z)
    //fmt.Printf("main--  name变量值:%s\n", name)

}

/*
函数局部变量:5、6
if内z变量生效: 996

*/

/*
testVar--  name变量值:localVar
main--  name变量值:globalVar

*/

for内变量作用域

package main

import "fmt"

var name = "globalVar"

func testVar() {
    //局部变量也定义name,优先调用局部的,务必注意,这里是一个新变量
    name := "localVar"
    fmt.Printf("testVar--  name变量值:%s\n", name)

}

func testVar2(x, y int) {
    fmt.Printf("函数局部变量:%d、%d\n", x, y)
    //测试if、for等局部变量
    if x > 0 {
        z := 996
        fmt.Println("if内z变量生效:", z)
    }
    //fmt.Println("外部依然无法调用z变量",z)

    for k := 0; k < 10; k++ {
        fmt.Println("for循环内k变量:", k)
    }
    //fmt.Println("外部无法调用k变量",k)
}

func main() {
    testVar2(5, 6)
    //fmt.Println("外部依然无法调用z变量",z)
    //fmt.Printf("main--  name变量值:%s\n", name)

}

/*
函数局部变量:5、6
if内z变量生效: 996

*/

/*
testVar--  name变量值:localVar
main--  name变量值:globalVar

*/

type自定义函数类型变量

函数也可以作为一个变量的类型,通过type关键字定义。

type calc func(x,y int)int

# 上面语句是,自定义一个类型,名为cale, 是一个函数类型,且接受x,y两个参数,返回一个int类型值。

代码实践

package main

import "fmt"

type calc func(x, y int) int

//只要参数,返回值,符合cale类型,就可以赋值给calc类型

func add(x, y int) int {
    return x + y
}

func sub(x, y int) int {
    return x - y
}

func main() {

    //使用自定义类型,函数类型的变量
    var c1 calc
    fmt.Printf("c1变量类型:%T、值:%#v\n", c1, c1)

    //变量赋值,就等于你可以使用c1这个函数名
    c1 = add
    fmt.Printf("赋值后c1变量类型:%T、值:%#v\n", c1, c1)

    //使用c1函数
    fmt.Println("c1函数执行结果:", c1(7, 8))

    var c2 calc

    c2 = sub
    //执行c2函数
    fmt.Println("c2函数执行结果:", c2(8, 3))

}

/*
c1变量类型:main.calc、值:(main.calc)(nil)
赋值后c1变量类型:main.calc、值:(main.calc)(0x108e9e0)
c1函数执行结果: 15
c2函数执行结果: 5

*/

函数可以作为参数

package main

import "fmt"

// 函数可以作为参数,加法函数
func add(x, y int) int {
    return x + y
}

func sub(x, y int) int {
    return x - y
}

// 计算函数,接收计算方法,接收函数作为参数
func calc(x, y int, operator func(x, y int) int) int {
    return operator(x, y) //返回计算方法、以及传入的参数
}

func main() {

    //函数调用,以及传入函数作为参数
    fmt.Printf("加法函数执行:%d\n", calc(7, 2, add))
    fmt.Printf("减法函数执行:%d\n", calc(9, 4, sub))

}

/*
加法函数执行:9
减法函数执行:5
*/

匿名函数

Go支持匿名函数,顾名思义就是没名字的函数。

匿名函数一般用在,函数只运行一次,也可以多次调用。

匿名函数可以像普通变量一样被调用。

匿名函数由不带函数名字的函数声明函数体组成。

package main

import "fmt"

/*
匿名函数定义

    func (参数)(返回值){
        //代码体
    }
*/

func main() {
    //定义匿名函数,交给变量add
    add := func(x, y int) int {
        return x + y
    }

    //通过变量名执行函数
    fmt.Println(add(3, 4))

    //定义匿名函数,接收2个参数n1,n2,返回值int
    // 自执行函数,匿名函数定义完后可以立即执行,
    res := func(n1, n2 int) int {
        return n1 * n2
    }(10, 20) //匿名函数在此处调用,传参
    fmt.Println("res=", res)
}

全局变量定义匿名函数

package main

import "fmt"

/*
匿名函数定义

    func (参数)(返回值){
        //代码体
    }
*/
//全局变量定义匿名函数
var f1 = func(x, y int) int {
    return x + y
}

// 其他函数可以调用匿名函数
func f2(a, b int) int {
    // 函数也是可以当做返回值的
    return f1(a, b)
}

func main() {
    //执行f2函数
    fmt.Println("f2执行结果:", f2(3, 4))
}

闭包函数

匿名函数主要用于 闭包函数的开发。

闭包 = 函数 + 引用环境

闭包是指一个函数与其相关的引用环境,组合的一个整体。

image-20221226212144377

package main

import (
    "fmt"
)

//闭包是由一个函数和其相关的引用环境组合的一个整体。(闭包=函数+引用环境)
//函数add返回值是个匿名函数 func(int)int

func adder() func(int) int {
    //定义一个局部变量
    x := 10
    //返回一个匿名函数,并且携带了一个函数外的变量x
    return func(y int) int {
        fmt.Printf("计算前的x--%d 、传入的y--%d\n", x, y)
        x += y
        return x
    }
}

/*
add函数返回了一个匿名函数,这个匿名函数又引用了函数外的变量n,因此匿名函数+n组成了一个整体,形成闭包
当调用f函数时,n仅仅被初始化一次,因此每次调用形成累计
*/

func main() {
    //调用adder函数,获取返回值
    f := adder()
    // 此时f就是匿名函数,并且引用了外部作用域内的x变量,此时f函数就构成了闭包。
    // 只要f不结束,变量x也一直有效
    fmt.Println(f(50)) //10+50=60
    fmt.Println(f(20)) //60+20=80
    fmt.Println(f(20)) //80+20=100 同一个f对象,保留了n的值

    //只要重新执行函数,f1就是一个重新的闭包函数,x变量的值也重置了
    f1 := adder()
    fmt.Println(f1(15)) // 10+15=25
}

/*
计算前的x--10 、传入的y--50
60
计算前的x--60 、传入的y--20
80
计算前的x--80 、传入的y--20
100
计算前的x--10 、传入的y--15
25


*/

闭包实战

开发一个函数,去检测、修改文件名后缀。

package main

import (
    "fmt"
    "strings"
)

/*
1.makeSuffixFunc函数接收一个文件名后缀,如.png,且返回闭包
2.调用闭包,传入文件名前缀,如果没有后缀就添加后缀,返回 文件名.png
3.strings.HasSuffix可以判断指定字符串后缀
*/

func makeSuffixFunc(suffix string) func(string) string {
    //返回值闭包函数
    return func(filename string) string {
        //如果没有xx后缀,执行代码
        if !strings.HasSuffix(filename, suffix) {
            //则字符串拼接
            return filename + suffix
        }
        //如果已经是 .png文件,直接返回
        return filename
    }
}

func main() {
    //f1返回的是闭包函数,对此闭包函数进行功能性使用
    f1 := makeSuffixFunc(".png")
    fmt.Println(f1("孙悟空"))     //没有.png
    fmt.Println(f1("猪八戒.jpg")) //没有.png
    fmt.Println(f1("白龙马.png")) //有.png
    //给一组数据,批量添加文件名
    for i := 0; i < 10; i++ {
        res := f1(fmt.Sprintf("英雄%d", i))
        fmt.Println(res)
    }
}

/*
孙悟空.png
猪八戒.jpg.png
白龙马.png
英雄0.png
英雄1.png
英雄2.png
英雄3.png
英雄4.png
英雄5.png
英雄6.png
英雄7.png
英雄8.png
英雄9.png

*/

defer关键字

  • 程序开发中经常要创建资源(数据库初始化连接,文件句柄,锁等),在程序执行完毕都必须得释放资源

  • Go提供了defer(延时机制)更方便、更及时的释放资源。

  • 当defer所处的函数即将return时,将延迟处理的语句,按defer定义的逆序执行
1.内置关键字defer 用于延迟调用

2.defer在return前执行,常用于资源释放

3.多个defer按    先进后出    的机制执行

4.defer语句的变量,在defer声明时定义

image-20221227103212570

defer综合案例

掌握内容
- 使用defer场景
    - 释放资源,如mysql,redis连接
    - 关闭文件,如fd
- defer执行顺序
    - 先入后出

- defer语句不能接收返回值

image-20221227103604497


image-20221227104447342

package main

import (
    "fmt"
)

func testDefer1() {
    //defer机制 先入后出,如同羽毛球筒
    defer fmt.Println("hello v1") //顺序5
    defer fmt.Println("hello v2") //顺序4
    defer fmt.Println("hello v3") //顺序3

    // 先执行函数体,defer在函数退出前执行
    fmt.Println("aaaaa") // 顺序1
    fmt.Println("bbbb")  //顺序2
}

/*
aaaaa
bbbb
hello v3
hello v2
hello v1

*/

func testDefer2() {
    // 0 1 2 3 4
    for i := 0; i < 5; i++ {
        //每次循环,defer将后入的语句压到defer栈
        //依旧先入后出
        defer fmt.Printf("i=%d\n", i)
    }

    fmt.Printf("running...\n") //顺序1
    fmt.Printf("return...\n")  //顺序2
}

/*
running...
return...
i=4
i=3
i=2
i=1
i=0

*/

func testDefer3() {
    //阅读如下代码,回答打印什么
    var i int = 99
    //defer是声明时定义好的,之后再修改无效,因此i=0
    defer fmt.Printf("defer v1  i=%d\n", i)
    defer fmt.Printf("defer v2  i=%d\n", i)
    i = 1000
    fmt.Printf("i=%d\n", i)
    defer fmt.Printf("defer v3  i=%d\n", i)
}

/*
i=1000
defer v3  i=1000
defer v2  i=99
defer v1  i=99

*/

func main() {
    //testDefer1()
    //testDefer2()
    testDefer3()
}

内置函数

官网标准库https://pkg.go.dev/std

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

在后续学习中,使用该系列内置函数。

内置time包

image-20221227172039959

package main

import (
    "fmt"
    "time"
)

func main() {
    //获取当前时间
    now := time.Now()
    fmt.Printf("time.Now()-- 获取当前时间:%v、类型:%T\n", now, now)

    //通过now获取细化的、年月日 时分秒
    fmt.Printf("now细化当前时间-- 年=%v 月=%v 日=%v 时=%v 分=%v 秒=%v\n", now.Year(), int(now.Month()), now.Day(), now.Hour(), now.Minute(), now.Second())

    //时间格式化,这个时间固定2006-01-02 15:04:05 必须这么写
    fmt.Printf("now.Format获取-- 当前时间:%v\n", now.Format("2006-01-02 15:04:05\n"))

    //Unix时间戳和UnixNano用法
    fmt.Printf("now获取时间戳-- unix时间戳=%v unixnano时间戳=%v\n", now.Unix(), now.UnixNano())
}

统计代码执行时间

package main

import (
    "fmt"
    "strconv"
    "time"
)

// 计算程序运行时间
func test() {
    str := ""
    for i := 0; i < 100000; i++ {
        //将int转为string
        str += "yuchao" + strconv.Itoa(i)
    }
}

func main() {
    //程序开始前的时间戳
    start := time.Now().Unix()
    test()
    //程序结束时的时间戳
    end := time.Now().Unix()
    fmt.Printf("执行test()函数,花费时间%v秒\n", end-start)
}

time.Since统计函数执行时间

package main

import (
    "fmt"
    "time"
)

func test() {
    start := time.Now() // 获取当前时间
    sum := 0
    for i := 0; i < 100000000; i++ {
        sum++
    }
    elapsed := time.Since(start)
    fmt.Println("该函数执行完成耗时:", elapsed)
}
func main() {
    test()
}

Go异常处理

Go语言处理异常不同于其他语言处理异常的方式。

传统语言处理异常,如python
try 
catch
finally

go语言
引入了defer、panic、recover
1.Go程序抛出一个panic异常,在defer中通过recover捕获异常,然后后续处理。
2.panic()可以在任何地方主动触发、但是recover只能在defer调用的函数中生效。

defer与recover捕获异常

  • recover()必须结合defer一起用
  • defer必须在函数开头定义(在可能引发panic之前定义)
package main

import "fmt"

func f1() {
    fmt.Println("f1已执行..")
}

func f2() {
    //既然f2程序可能会导致异常崩溃,提前做好defer+recover捕获异常
    defer func() {
        err := recover() //recover会自动捕捉函数出现的panic
        if err != nil {
            fmt.Println("recover in f2 已恢复...")
        }
    }()
    panic("panic in f2()....")
}

func f3() {
    fmt.Println("f3已执行...")
}

func main() {
    f1()
    f2() //f2里的panic使得主进程直接退出
    f3()

}

/*
f1已执行..
recover in f2 已恢复...
f3已执行...

*/

goroutine中recover捕获异常

后面学习go并发时,开启goroutine执行任务,也可能会panic,也需要recover确保程序健康。

package main

import (
    "fmt"
    "time"
)

func test() {
    for i := 0; i < 10; i++ {
        time.Sleep(time.Second) //睡眠一秒
        fmt.Println("你好,大妹子,我是你表哥")
    }
}

func test2() {
    //测试一个异常goroutine
    //使用defer+recover捕获异常
    defer func() {
        //匿名函数来捕获异常
        if err := recover(); err != nil {
            fmt.Println("test2函数出错,recover已恢复:", err)
        }
    }()

    //主动模拟异常,对一个未初始化的map赋值,引出panic异常
    var myMap map[int]string
    myMap[0] = "你妹呀"
}

func main() {
    go test()  //正常
    go test2() //defer + recover
    // 主进程夯住,确保其他协程执行
    time.Sleep(time.Second * 10)
}

Go指针

变量是一个方便的占位符,用于引用计算机内存地址

基本数据类型中,如name:="www.yuchaoit.cn",变量name存储的是值类型,字符串

  • 基本数据类型,变量存的是值、读作值类型
  • 通过&取址符,可以获取变量的内存地址,如&name
  • 指针类型的变量,直接就是存储内存地址,这个内存地址指向的内存空间,存储的是值。
  • 获取指针变量,指向的值,使用*符号,例如*ptr,可以获取内存地址指向的值。

什么是指针

源代码文件,如hello.go本身是一对文件,在通过go编译器运行之后,才在内存中有了该程序的一块地址,如何找到这块内存地址,就是通过指针。

并且如果你想保存某数据在内存中的地址,就需要使用指针变量。

图解Go指针玩法

image-20221227143830558

Go指针练习题

package main

import "fmt"

func main() {
    //值变量
    s := "迷茫的时候就努力学习吧"
    fmt.Printf("s变量值:%v、类型:%T、内存地址:%p\n", s, s, &s)

    //指针变量
    p := &s
    fmt.Printf("指针p变量值:%v、类型:%T、内存地址:%p\n", p, p, p)

    // 通过指针变量,获取字符串的值
    fmt.Printf("指针p变量对应的值:%v、类型:%T、内存地址:%p\n", *p, *p, p)
}

/*
s变量值:迷茫的时候就努力学习吧、类型:string、内存地址:0xc000096220
指针p变量值:0xc000096220、类型:*string、内存地址:0xc000096220
指针p变量对应的值:迷茫的时候就努力学习吧、类型:string、内存地址:0xc000096220

*/

小结

  • 取内存地址操作符、&
  • 取地址对应的值操作符、*
  • 这俩是互补操作

图解梳理指针、变量

image-20221227145502237

指针和函数传参

  • 函数接收值类型参数、发生变量拷贝,内存地址变化
  • 函数接收指针类型参数,可以修改源变量
package main

import "fmt"

// 函数传参,值类型会发生值拷贝
func modifyVar(x int) {
    x++
    fmt.Printf("函数内x修改后的值:%d、内存地址:%p\n", x, &x)
}

// 函数接收指针变量,可以修改源变量的值
// *int表示接受指针类型的变量,只能传入内存地址
func modifyPtr(x *int) {
    *x++ //注意,对值修改,需要给指针加上取值符
    fmt.Printf("函数内x修改后的值:%d、内存地址:%p\n", *x, x)

}

func main() {
    nums := 10
    modifyVar(nums)
    fmt.Printf("1 -- main函数内的nums值:%d、内存地址:%p\n", nums, &nums)

    modifyPtr(&nums)
    fmt.Printf("2 -- main函数内的nums值:%d、内存地址:%p\n", nums, &nums)

}

/*
函数内x修改后的值:11、内存地址:0xc0000ac010
1 -- main函数内的nums值:10、内存地址:0xc0000ac008
函数内x修改后的值:11、内存地址:0xc0000ac008
2 -- main函数内的nums值:11、内存地址:0xc0000ac008

*/

空指针与内存申请

当一个指针变量定义后,但是没有分配任何变量,指针默认值是nil,也被读作空指针。

package main

import "fmt"

func main() {
    var ptr *int
    // ptr指针变量类型:*int、值:<nil>、内存地址:0x0
    if ptr == nil {
        fmt.Printf("ptr指针变量是空指针,类型:%T、值:%v、内存地址:%p\n", ptr, ptr, ptr)
    }

    // int变量值类型,有默认值,0
    var num int
    fmt.Printf("变量num有默认值:%d、内存地址:%p\n", num, &num)
    p2 := &num
    if p2 != nil {
        fmt.Printf("p2指针变量不是空指针,类型:%T、值:%v、内存地址:%p\n", p2, p2, p2)
    }

    // map和slice为何需要开辟内存,因为map、slice是引用类型,没默认值
    var stuMap map[string]string
    if stuMap == nil {
        fmt.Printf("stuMap是空指针、类型:%T、值:%#v\n", stuMap, stuMap)
        fmt.Println("空指针nil不得直接操作,否则会panic")
        //stuMap["张三"] = "男孩"   panic: assignment to entry in nil map
    }

    //因此给给map申请内存空间,才可以增删改查
    stuMap = make(map[string]string)
    if stuMap != nil {
        stuMap["班长"] = "张三"
        fmt.Printf("stuMap初始化后-- 类型%T、值:%#v\n", stuMap, stuMap)
    }
}

/*
ptr指针变量是空指针,类型:*int、值:<nil>、内存地址:0x0
变量num有默认值:0、内存地址:0xc0000ac008
p2指针变量不是空指针,类型:*int、值:0xc0000ac008、内存地址:0xc0000ac008
stuMap是空指针、类型:map[string]string、值:map[string]string(nil)
空指针nil不得直接操作,否则会panic
stuMap初始化后-- 类型map[string]string、值:map[string]string{"班长":"张三"}

*/

new和make申请内存

1. new、make都用来申请内存、new用的较少
2. 主要使用make申请slice、map、channel的空间
3. new返回指针

为什么要申请内存

package main

import "fmt"

func main() {
    // go语言中引用类型的变量,默认是nil、没有内存空间,无法存值
    // 值类型变量,声明时默认就开辟了内存空间,有了默认值
    // 引用类型变量可以主动开辟内存,使用内置函数new()、make()

    //直接定义指针变量,引用类型默认没有内存空间
    var ptr *int
    //*ptr = 666  这里无法直接赋值, panic: runtime error: invalid memory address or nil pointer dereference
    if ptr == nil {
        fmt.Printf("ptr值:%#v\n", ptr) // ptr值:(*int)(nil)
    }

    //map、slice也一样是引用类型

}

new函数

new是一个内置函数,语法

package main

import "fmt"

func main() {
    // go语言中引用类型的变量,默认是nil、没有内存空间,无法存值
    // 值类型变量,声明时默认就开辟了内存空间,有了默认值
    // 引用类型变量可以主动开辟内存,使用内置函数new()、make()
    // func new(Type) *Type 接收一个数据类型,返回该类型的指针,默认指向该值的默认值内存地址。
    i := new(int)
    fmt.Printf("类型:%T、默认值地址:%#v、指针指向的默认值:%d\n", i, i, *i)

    b := new(bool)
    fmt.Printf("类型:%T、默认值地址:%#v、指针指向的默认值:%t\n", b, b, *b)

    s := new(string)
    fmt.Printf("类型:%T、默认值地址:%#v、指针指向的默认值:%s\n", s, s, *s)

    //在new函数初始化后,即可赋值操作
    *i = 9999
    *b = true
    *s = "www.yuchaoit.cn"
    fmt.Printf("赋值后:%v、%v、%v\n", *i, *b, *s)

}

/*
类型:*int、默认值地址:(*int)(0xc00010c008)、指针指向的默认值:0
类型:*bool、默认值地址:(*bool)(0xc00010c020)、指针指向的默认值:false
类型:*string、默认值地址:(*string)(0xc000108050)、指针指向的默认值:
赋值后:9999、true、www.yuchaoit.cn

*/

make函数

make也用于初始化引用类型,开辟内存空间,但只用于map、slice、channel三个类型,并且返回该类型本身,而不是指针变量,因为这三个类型本身就是引用类型。

package main

import "fmt"

func main() {
    //make函数源码
    // func make(t Type, size ...IntegerType) Type
    // 在go语言里,初始化slice、map、channel必须make分配内存就对了,然后才可以赋值操作。
    // 这里就是map基础知识,两种初始化方法
    // 语法1:底层自动make()初始化了map
    var stuScore = map[string]int{
        "张飞": 60,
        "李逵": 99,
    }
    fmt.Println(stuScore)

    //语法2:主动make
    var heroScore map[string]int
    heroScore = make(map[string]int, 10)
    for i := 0; i < 10; i++ {
        heroScore[fmt.Sprintf("hero%d", i)] = i
    }
    fmt.Println(heroScore)
}

/*
map[张飞:60 李逵:99]
map[hero0:0 hero1:1 hero2:2 hero3:3 hero4:4 hero5:5 hero6:6 hero7:7 hero8:8 hero9:9]

*/

小结

  • new、make都可以对引用类型,分配内存
  • new返回传入类型的指针,以及存储该类型的默认值。
  • make只可以对map、slice、channel的初始号化内存,返回该3个类型本身

函数知识点梳理

函数基础

1.函数定义、调用
func foo(x,y int)int{
    // xxx
    return
}

foo()


2.函数参数、可以0个、可以多个、多个同类型的参数可以简写,以逗号分隔

func sum(x int,y int)int{
    return 
}


func sum(x ,y int)int{
    return 
}

3. 函数可变参数语法,可变参数只能写在最后一个,传入的是切片 []int
func sum(x ,y int, z ...int )int{
    return 
}

4.函数返回值
普通返回值
命名返回值,等于在函数内声明了一个返回值变量
func foo(x,y int)(z int){
  z=x+y
  return 
}

多个返回值
package main

import "fmt"

func calc(x, y int) (sum int, sub int) {
    sum = x + y
    sub = x - y
    return sum, sub
}

func main() {
    sum, sub := calc(5, 6)
    fmt.Println(sum)
    fmt.Println(sub)
}

函数进阶

1.函数作用域
- 函数内部变量、函数外全局变量
- 出现同名变量,优先查找函数内部,找不到去函数外找
- if、for、switch都有自己作用域

package main

import "fmt"

func demo1() {
    m1 := make(map[string]int, 8)
    m1["yuchao"] = 666
    // if作用域
    if res, ok := m1["yuchao"]; !ok {
        fmt.Printf("res:%v不存在"+"\n", res)
    } else {
        fmt.Printf(`m1["yuchao"]:%v`+"\n", res)
    }

    //for作用域
    for key, value := range m1 {
        fmt.Println(key, "---", value)
    }
    //fmt.Println("外部用不到key",key)

}
func main() {
    demo1()
}


2.函数类型、函数签名(为后面知识点埋下伏笔)
自定义函数类型,只需要描述函数特征,不关心名字


type myFunc func(int, int) int

func foo(x, y int) int {
    return x + y
}

func main() {
    //使用自定义类型
    var f1 myFunc
    f1 = foo
    fmt.Println(f1(5, 7))

}


3.匿名变量、匿名函数、自执行函数

//匿名变量
for _,v :=range s1{

}
//自执行匿名函数
// 在函数内部,无法再通过func定义有名函数,因此可以使用匿名函数
func (x,y int)int{

}()

4.函数闭包
把匿名函数当做返回值,并且这个匿名函数还使用了外层作用域的变量。

5.高阶函数
把函数当做参数、以及返回值

内置函数

1. defer 延迟执行函数,所在函数即将推出时再执行,遵循先进后出规则。
用于如关闭文件句柄,数据库连接等。

2.panic、recover 是Go语言的异常处理机制
程序异常出错,会直接panic崩溃程序,可以通过recover捕获修复。
并且recover必须和defer一起用

3.make、new分配内存、以及区别

4. append 列表追加元素、扩容

5. len 统计map元素个数、切片元素个数

6. close 关闭channel通道

指针

1. 指针变量、指向内存地址
指针类型,如*string、*int

2.指针操作
- 取变量地址 &
- 通过指针取值  *ptr

3.函数传参、值类型、指针类型
Go函数传参值类型,会出现值拷贝、传递引用类型、指针可以修改源数据

package main

import "fmt"

// slice,map是引用类型,因此会修改源数据
func foo(m map[string]int) {
    m["张三"] = 200
    m["二狗"] = 666
}

func main() {
    stu := map[string]int{
        "老王": 777,
    }
    foo(stu)
    fmt.Println(stu)

}
Copyright © www.yuchaoit.cn 2025 all right reserved,powered by Gitbook作者:于超 2022-12-27 20:07:42

results matching ""

    No results matching ""