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))
}
闭包函数
匿名函数主要用于 闭包函数的开发。
闭包 = 函数 + 引用环境
闭包是指一个函数与其相关的引用环境,组合的一个整体。

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声明时定义

defer综合案例
掌握内容
- 使用defer场景
- 释放资源,如mysql,redis连接
- 关闭文件,如fd
- defer执行顺序
- 先入后出
- defer语句不能接收返回值


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()
}
内置函数
| 内置函数 | 介绍 |
|---|---|
| close | 主用来关闭channel |
| len | 用来求长度,比如string、array、slice、map、channel |
| new | 用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针 |
| make | 用来分配内存,主要用来分配引用类型,比如chan、map、slice |
| append | 用来追加元素到数组、slice中 |
| panic和recover | 用来做错误处理 |
在后续学习中,使用该系列内置函数。
内置time包

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指针玩法

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
*/
小结
- 取内存地址操作符、
& - 取地址对应的值操作符、
* - 这俩是互补操作
图解梳理指针、变量

指针和函数传参
- 函数接收值类型参数、发生变量拷贝,内存地址变化
- 函数接收指针类型参数,可以修改源变量
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)
}