【Golang第4章:函数】Golang包的引用,return语句、指针、匿名函数、闭包、go函数参数传递方式,golang获取当前时间
介绍
本人是个菜鸡,这个是在B站上看边看视频边做的笔记,这一章是编程语言的函数
Golang包的引用,return语句、指针、匿名函数、闭包、go函数参数传递方式,golang获取当前时间,具体请看【文章目录】
配套视频自己去B站里面搜【go语言】,最高的播放量就是
里面的注释我写的可能不太对,欢迎大佬们指出╰(°▽°)╯
文章目录
(四).函数
函数定义:
func 函数名 (参数列表) (返回值列表) { //返回值只有一个时可以不写()
//函数体
return 返回值列表
}
例子:
package main
import "fmt"
//声明一个函数为cal,声明3个值
func cal(n1 float64, n2 float64, n3 byte) float64 {
var res float64
switch n3 {
case '+':
res = n1 + n2
case '-':
res = n1 - n2
case '*':
res = n1 * n2
case '/':
res = n1 / n2
default:
fmt.Println("符合出入错误。。。")
}
return res
}
func main() {
var n1 float64 = 4.5
var n2 float64 = 2.5
var n3 byte = '+'
//声明一个n4并把函数的结果赋值,调用函数
n4 := cal(n1, n2, n3)
fmt.Println("结果是:", n4)
}
1.包的引入
package 指令在 文件第一行,然后是 import 指令。
在 import 包时,路径从 $GOPATH 的 src 下开始,不用带 src , 编译器会自动从 src 下开始引入
引入包并取别名,注意细节:取别名后,原来的包名就不能使用了
package utils
import "fmt"
var Num1 int = 300
package main
import (
u111 "demo13/utils" //取别名
"fmt"
)
func main() {
fmt.Println("数字是:", u111.Num1) //引入变量
}
2.项目打包成可执行文件
默认打包路径在$GOPATH
目录下
PS D:\code\Go> go build 08demo/main
PS D:\code\Go> dir
目录: D:\code\Go
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2022/8/9 14:26 pkg
d----- 2022/8/9 14:26 src
-a---- 2022/8/10 10:19 1890816 main.exe
指定打包到当前bin目录下
PS D:\code\Go> go build -o ./bin/my.exe 08demo/main
PS D:\code\Go> dir
目录: D:\code\Go
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2022/8/10 10:21 bin
d----- 2022/8/9 14:26 pkg
d----- 2022/8/9 14:26 src
-a---- 2022/8/10 10:19 1890816 main.exe
3.return语句
基本语法和说明:
Go函数支持返回多个值,这一点时其他编程语言没有的。
func 函数名 (形参列表) (返回值类型列表) {
语句...
return 返回值列表
}
- 如果返回多个值时,在接收时,希望忽略某个返回值,则使用下划线
_
符合表示占位忽略 - 如果返回值只有一个,(返回值类型列表) 可以不写()
返回一个值
package main
import "fmt"
func test(n1 int, n2 int) int { //返回参数时,需要声明返回的数值类型
sum := n1 + n2
fmt.Println("test sum = ", sum)
//当函数有return的语句时,就是将结果返回给调用者
//即谁调用我,我就返回给谁
return sum
}
func main() {
//调用test
sum := test(10, 20)
fmt.Println("main sum = ", sum)
}
返回多个值
func test(n1 int, n2 int) (int, int) { //返回多个参数
sum := n1 + n2
sub := n1 - n2
return sum, sub
}
func main() {
//调用test,调用2个返回参数
sum1, sub1 := test(10, 20)
fmt.Printf("sum=%v,sub=%v\n", sum1, sub1)
//如果只返回一个参数,只要sum2,可以使用占位符下划线 _ 替代 sub2
sum2, _ := test(10, 20)
fmt.Println("sum=", sum2)
}
4.递归调用
基本介绍
一个函数在函数体内又调用了本身,我们称为递归调用
func test(n int) {
if n > 2 {
n--
test(n)
}
fmt.Println("n=", n)
}
func main() {
test(4)
}
5.函数细节
使用指针修改函数外数据
func test(n1 *int) { //2.取内存地址原值
*n1 = *n1 + 10 //3.取内存地址原值并进行运算
fmt.Println("test n1=", *n1)
}
func main() {
num := 20
fmt.Println("main num=", num)
test(&num) //1.传入内存地址
fmt.Println("main num=", num) //4.由于内存地址里面的值被修改,输出30
}
结果:
main num= 20
test n1= 30
main num= 30
通过变量对函数进行调用
//在GO中,函数也是一种数据类型
//可以赋值给一个变量,则该变量就是一个函数类型的变量了,通过该变量可以对函数调用
func getSum(n1 int, n2 int) int {
return n1 + n2
}
func main() {
a := getSum
fmt.Printf("a的类型%T, getSum类型是%T\n", a, getSum)
res := a(10, 20) //等价于 res := getSum(10, 20)
fmt.Println("res=", res)
}
函数回调
函数既然是一种数据类型,因此在 Go 中,函数可以作为形参,并且调用
func getSum(n1 int, n2 int) int { //4.取到对应的参,运算后并进行返回
return n1 + n2
}
//2.接受参数,aaa表示为getSumF函数的变量,num1和num2接收后分别为20和30
func myFun(aaa func(int, int) int, num1 int, num2 int) int {
return aaa(num1, num2) //3.返回num1和num2到aaa变量 //5.返回运算后结果
}
func main() {
res := myFun(getSum, 20, 30) //1.把参数返回给函数myFun
fmt.Println("res=", res) //6.完成输出
}
使用type取别名
func main() {
type myInt int //给int取别名
var num1 myInt //虽然都是int类型,但是go认为myInt和int是2个类型
num1 = 40
num2 := int(num1) //这里go认为myInt和int是2个数据类型
fmt.Println("num1=", num1, "num2=", num2)
}
支持可变参数
//支持0到多个参数
func sum (args ...int) sum int{
}
//支持1到多个参数
func sum (n1 int, args ...int) sum int{
}
如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后。
6.init函数
每一个源文件中都有一个init函数,最大的作用是用来初始化源文件,该函数会在main函数执行前被调用
func main() { //3.在执行main
fmt.Println("main...", age)
}
func init() { //2.在执行init
fmt.Println("init...")
}
var age = test() //1.根据规则先执行变量
func test() int { //让age优先调用
fmt.Println("test...")
return 90
}
输出:
$ go run .\main.go
test...
init...
main... 90
如果一个文件同时包含全局变量定义,init 函数和 main 函数,则执行的流程是全局变量定义->init 函数->main 函数
如果main
函数引入的utils
文件里面有init
函数,优先执行被引入的init
函数
7.匿名函数
Go 支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考 虑使用匿名函数,匿名函数也可以实现多次调用。
//全局匿名函数Fun1
//如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效
var Fun1 = func(n1 int, n2 int) int {
return n1 * n2
}
func main() {
//在定义匿名函数是时就直接调用,这种方式匿名函数只能调用一次
res1 := func(n1 int, n2 int) int { //没有函数名
return n1 + n2
}(10, 20) //将参数返回给n1和n2
fmt.Println("res1=", res1)
//可以调用多次
a := func(n1 int, n2 int) int {
return n1 - n2
}
res2 := a(10, 30)
fmt.Println("res2=", res2)
res3 := a(20, 10)
fmt.Println("res3=", res3)
res4 := Fun1(3, 4) //调用全局匿名函数
fmt.Println("res4=", res4)
}
8.闭包
闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
func AddUpper() func(int) int { //闭包
var n int = 10 //累加器
return func(i int) int {
n = n + i //进行累加
return n //把值返回给累加器
}
}
func main() {
//使用前面的代码
f := AddUpper()
fmt.Println(f(1)) //10+1,结果为11,现在累加器变量n为11
fmt.Println(f(2)) //11+2,结果为13,现在累加器变量n为13
fmt.Println(f(1)) //13+1,结果为14
}
import (
"fmt"
"strings"
)
/*
1) 编写一个函数 makeSuffix(suffix string) 可以接收一个文件后缀名(比如.jpg),并返回一个闭包
2) 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回 文件名.jpg , 如果已经有.jpg 后缀,则返回原文件名。
3) 要求使用闭包的方式完成
4) strings.HasSuffix , 该函数可以判断某个字符串是否有指定的后缀。
*/
func makeSuffix(suffix string) func(string) string {
return func(Name string) string {
if !strings.HasSuffix(Name, suffix) {
return Name + suffix
}
return Name
}
}
func main() {
str1 := makeSuffix(".jpg")
fmt.Println("文件名处理后=", str1("winter")) //winter.jpg
fmt.Println("文件名处理后=", str1("win.jpg")) //win.jpg
fmt.Println("文件名处理后=", str1("win.png")) //win.png.jpg
}
上面代码的总结和说明:
- 返回的匿名函数和 makeSuffix (suffix string) 的 suffix 变量 组合成一个闭包,因为 返回的函数引用 到 suffix 这个变量
- 我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每 次都传入 后缀名,比如 .jpg ,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复 使用。大家可以仔细的体会一把!
9.defer
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等) ,为了在函数执行完 毕后,及时的释放资源,Go 的设计者提供 defer (延时机制)。
func sun(n1 int, n2 int) int { //2.传入参数
//当执行到defer是,会将defer后面的语句按顺序存放到独立的栈(defer栈)中
//当程序执行完毕后,再从defer栈,按照先入后出的方式出栈
//入栈(defer栈)时,会同时拷贝n1和n2的值,后面参数变量不会影响已入栈的参数
defer fmt.Println("ok1 n1=", n1) //3.因为有defer,第1个入栈,暂时不执行 //9.继续出栈,在执行ok1
defer fmt.Println("ok2 n2=", n2) //4.因为有defer,第2个入栈,暂时不执行 //8.进行出栈,先执行ok2
n1++
n2++
res := n1 + n2 //5.相加后赋值给res
fmt.Println("ok3 res=", res) //6.先输出ok3
return res //7.当sun函数执行完毕,返回参数后,在执行独立的栈(defer)的代码
}
func main() {
res := sun(10, 20) //1.赋值给res,调用sun
fmt.Println("res=", res) //10.输出结果
}
当执行到defer是,会将defer后面的语句按顺序存放到独立的栈(defer栈)中
当程序执行完毕后,再从defer栈,按照先入后出的方式出栈
入栈(defer栈)时,会同时拷贝n1和n2的值,后面参数变量不会影响已入栈的参数
defer 最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源
- 在 golang 编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是 锁资源), 可以执行 defer file.Close() defer connect.Close()
- 在 defer 后,可以继续使用创建资源.
- 当函数完毕后,系统会依次从 defer 栈中,取出语句,关闭资源.
- 这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦
10.函数参数传递方式
1)基本介绍
我们在讲解函数注意事项和使用细节时,已经讲过值类型和引用类型了,这里我们再系统总结一 下,因为这是重难点,值类型参数默认就是值传递,而引用类型参数默认就是引用传递。
2)两种传递方式
- 值传递
- 引用传递
其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的 拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的 数据大小,数据越大,效率越低。
3)值类型和引用类型
- **值类型:**基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct
- **引用类型:**指针、slice 切片、map、管道 chan、interface 等都是引用类型
4)值传递和引用传递使用特点
1.**值类型默认是值传递:**变量直接存储值,内存通常在栈中分配
2.**引用类型默认是引用传递:**变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。
3.如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操 作变量。从效果上看类似引用 。这个案例在前面详解函数使用注意事项的
5)变量作用域
1.函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
2.函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用 域在整个程序有效
3.如果变量是在一个代码块,比如 for / if 中,那么这个变量的的作用域就在该代码块
练习1
var name = "tom" //全局变量
func test01() {
fmt.Println(name)
}
func test02() {
name := "jack" // name := "jack" 是重新声明局部变量 等同与 var name = "jack" ,如果是name = "jack",相对于赋值jack给name
fmt.Println(name)
}
func main() {
fmt.Println(name) //tom
test01() //tom
test02() //jack
test01() //tom
}
name := "jack"
:重新声明局部变量 等同与 var name string name = "jack"
name = "jack"
:相当于赋值jack给name的全局变量,修改了全局变量,属于赋值语句
练习2
6)字符串常用的系统函数
说明:字符串在我们程序开发中,使用的是非常多的,常用的函数需要同学们掌握[带看手册或者 官方编程指南]:
1.统计字符串的长度,按字节 len(str)
func main() {
//golang的编码统一为utf-8(ascii的字符(字母和数字)占一个字节,汉字占用3个字节)
str := "hello你好"
fmt.Println("hello你好字节数量为:", len(str))
}
2.字符串遍历,同时处理有中文的问题 r := []rune(str)
str := "hello你好"
str1 := []rune(str)
for i := 0; i < len(str1); i++ {
fmt.Printf("字符=%c\n", str1[i])
}
3.字符串转整数: n, err := strconv.Atoi("12")
//字符串转整数,err值必须为数字
n, err := strconv.Atoi("1设置")
//判断n是否为数字,nil是一种空类型表示为0值
if err != nil {
fmt.Println("转换错误", err)
} else {
fmt.Println("转换成功", n)
}
4.整数转字符串 str := strconv.Itoa(12345)
str := strconv.Itoa(1234)
fmt.Printf("str=%v,str=%T", str, str)
5.字符串 转 []byte
: var bytes = []byte("hello go")
var bytes = []byte("hello go")
//输出对应字符的ascii编码
fmt.Printf("byte=%v\n", bytes) //byte=[104 101 108 108 111 32 103 111]
6.[]byte
转 字符串: str = string([]byte{97, 98, 99})
str := string([]byte{97, 98, 99})
//输出ascii编码对应的字符
fmt.Printf("str=%v\n", str) //str=abc
7.10进制转2,8,16进制:str = strconv.FormatInt(123, 2) // 2-> 8 , 16
//输入int64类型10进制参数,返回2进制
str := strconv.FormatInt(123, 2)
fmt.Printf("123对应的2进制是:%v\n", str)
//输入int64类型10进制参数,返回16进制
str1 := strconv.FormatInt(123, 16)
fmt.Printf("123对应的16进制是:%v\n", str1)
8.查找子串是否在指定的字符串中: strings.Contains("seafood", "foo") //true
//字符串查找
str := strings.Contains("seafood", "foo")
str1 := strings.Contains("seafood", "adb")
fmt.Printf("str=%v\n", str) //查找到为真true
fmt.Printf("str=%v\n", str1) //查不到为假false
9.统计一个字符串有几个指定的子串 : strings.Count("ceheese", "e") //4
//查看字符串里面有几个e,没有返回0
str := strings.Count("ceheese", "e")
fmt.Printf("str=%v\n", str) // str=4
10.不区分大小写的字符串比较(==是区分字母大小写的): fmt.Println(strings.EqualFold("abc", "Abc")) // true
str := strings.EqualFold("abc", "Abc")
//比较字符串,不区分大小写
fmt.Printf("结果= %v\n", str) //true
//比较字符串,区分大小写
fmt.Println("结果=", "abc" == "Abc") //false
11.返回子串在字符串第一次出现的 index(指数) 值,如果没有返回则-1
: strings.Index("NLT_abc", "abc") // 4
//abc在字符串NLT_abc第一次出现为第5个字符,返回4
str := strings.Index("NLT_abc", "abc")
fmt.Printf("str=%v\n", str)
//cabc在参数里面没有出现,返回-1
str1 := strings.Index("NLT_abc", "cabc")
fmt.Printf("str1=%v\n", str1)
12.返回子串在字符串最后一次出现的index,如没有返回则-1
: strings.LastIndex("go golang", "go")
//go在字符串go golang第最后一次出现为第4个字符,返回3
str := strings.LastIndex("go golang", "go")
fmt.Printf("str=%v\n", str)
13.将指定的子串替换成 另外一个子串: strings.Replace("go go hello", "go", "go 语言", n)
,n 可以指定你希望替换几个,如果 n=-1 表示全部替换
//对指定的字符串进行替换
//第1个为原字符串,第2个为你要替换的字符串,第3个为你替换后的参数,第4个为你要替换几个原字符
//替换1个go,输出"go go hello"为"你好 go hello"
//替换2个go,输出"go go hello"为"你好 你好 hello"
str := strings.Replace("go go hello", "go", "你好", 2)
fmt.Printf("str=%v\n", str)
14.按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组:strings.Split("hello,wrold,ok", ",")
//按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组
//以","逗号为分割符,输出"hello wrold ok"
str := strings.Split("hello,wrold,ok", ",")
fmt.Printf("str=%v\n", str)
//查看是不是真的成了数组
for i := 0; i < len(str); i++ {
fmt.Printf("str%v=%v\n", i, str[i])
}
15.将字符串的字母进行大小写的转换: strings.ToLower("Go")
和strings.ToUpper("Go")
str := "goLang Hello"
str1 := strings.ToLower(str) //改为全小写
str2 := strings.ToUpper(str) //改为全大写
fmt.Printf("str=%v\n", str1)
fmt.Printf("str=%v\n", str2)
16.将字符串左右两边的空格去掉:strings.TrimSpace(" tn a lone gopher ntrn ")
//将字符串左右两边的空格去掉:
//`strings.TrimSpace(" tn a lone gopher ntrn ")`
str1 := strings.TrimSpace(" tn a lone gopher ntrn ")
fmt.Printf("str=%q\n", str1)
17.将字符串左右两边指定的字符去掉 :strings.Trim("! hello! ", "! ")
将左右两边 "! " 叹号和空格去掉
//将字符串左右两边指定的字符去掉
//:strings.Trim("! hello! ", "! ")
//将左右两边 "! " 叹号空格去掉
str := strings.Trim("! hello! ", "! ")
str1 := strings.Trim("! hello! ", " ")
fmt.Printf("str=%q\n", str)
fmt.Printf("str=%q\n", str1)
18.将字符串左边指定的字符去掉 : strings.TrimLeft("! hello! ", " !")
// [“hello”] //将左边 ! 和 ""去掉
19.将字符串右边指定的字符去掉 :strings.TrimRight("! hello! ", " !")
// [“hello”] //将右边 ! 和 ""去掉
20.判断字符串是否以指定的字符串开头: strings.HasPrefix("ftp://192.168.10.1", "ftp")
// true
21.判断字符串是否以指定的字符串结束: strings.HasSuffix("NLT_abc.jpg", "abc")
//false
7)时间和日期相关函数
基本的介绍
说明:在编程中,程序员会经常使用到日期相关的函数,比如:统计某段代码执行花费的时间等。
①.获取当前系统时间和日期
func main() {
//获取当前时间
now := time.Now()
fmt.Printf("now=%v\nnow=%T\n", now, now)
fmt.Printf("年%v\n", now.Year())
fmt.Printf("月%v\n", now.Month())
fmt.Printf("月%v\n", int(now.Month())) //转换为int类型
fmt.Printf("日%v\n", now.Day())
fmt.Printf("时%v\n", now.Hour())
fmt.Printf("分%v\n", now.Minute())
fmt.Printf("秒%v\n", now.Second())
//格式化日期时间1
fmt.Printf("当前时间为:%02d-%02d-%02d %02d:%02d:%02d\n",
now.Year(), now.Month(), now.Day(),
now.Hour(), now.Minute(), now.Second())
datestr := fmt.Sprintf("当前时间为:%02d-%02d-%02d %02d:%02d:%02d\n",
now.Year(), now.Month(), now.Day(),
now.Hour(), now.Minute(), now.Second())
//输出赋值后的时间
fmt.Printf("%v\n", datestr)
//格式化日期时间2
fmt.Println(now.Format("2006-01-02 15:04:05"))
fmt.Println("年=", now.Format("2006"))
fmt.Println("日=", now.Format("02"))
}
对上面代码的说明:
“2006/01/02 15:04:05” 这个字符串的各个数字是固定的,必须是这样写。
“2006/01/02 15:04:05” 这个字符串各个数字可以自由的组合,这样可以按程序需求来返回时间
②.时间的常量
const (
Nanosecond Duration = 1 //纳秒
Microsecond = 1000 * Nanosecond //微秒
Millisecond = 1000 * Microsecond //毫秒
Second = 1000 * Millisecond //秒
Minute = 60 * Second //分钟
Hour = 60 * Minute //小时
)
//需求,每隔1秒打印一个数字,打印到100时就退出
//需求2,每隔0.1秒打印一个数字,打印到100时就退出
i := 0
for {
i++
fmt.Println(i)
//等待1秒
//time.Sleep(time.Second)
//等待0.1秒 1000毫秒 * 100 = 0.1秒
time.Sleep(time.Millisecond * 100)
if i == 100 {
break
}
}
③.使用时间戳获取随机数字
Unix时间戳和UnixNano时间戳
now := time.Now()
fmt.Println("Unix时间戳=", now.Unix())
fmt.Println("UnixNano时间戳=", now.UnixNano())
练习1
编写一段代码来统计 函数 test03 执行的时间
package main
import (
"fmt"
"strconv"
"time"
)
func test03() {
str := ""
for i := 0; i < 100000; i++ { //运行10万次
str += "hello" + strconv.Itoa(i) //拼接字符
}
}
func main() {
start := time.Now().Unix()
fmt.Println(start)
test03()
end := time.Now().Unix()
fmt.Println(end)
fmt.Printf("执行test03耗费时间为%v秒\n", end-start)
}
8)内置函数
说明: Golang 设计者为了编程方便,提供了一些函数,这些函数可以直接使用,我们称为 Go 的内置函 数。文档:https://studygolang.com/pkgdoc -> builti
1.len:用来求长度,比如 string、array、slice、map、channel
2.new:用来分配内存,主要用来分配值类型,比如 int、float32,struct…返回的是指针
func main() {
num1 := 100
fmt.Printf("类型=%v,值=%v,地址=%v\n", num1, num1, &num1)
num2 := new(int) //给num2这个指针指定一个空间,类型为int,默认值为0,返回的是指针
fmt.Printf("类型=%T , 值=%v , 地址=%v , num2这个指针指向的值=%v\n", num2, num2, &num2, *num2)
*num2 = 100 //给num2赋值
fmt.Printf("类型=%T , 值=%v , 地址=%v , num2这个指针指向的值=%v\n", num2, num2, &num2, *num2)
*num2 = 200 //给num2赋值
fmt.Printf("类型=%T , 值=%v , 地址=%v , num2这个指针指向的值=%v\n", num2, num2, &num2, *num2)
}
PS D:\Code\Golang\src\demo15\main> go run .\main.go
类型=100,值=100,地址=0xc00012a058
类型=*int , 值=0xc00012a078 , 地址=0xc00014e020 , num2这个指针指向的值=0
类型=*int , 值=0xc00012a078 , 地址=0xc00014e020 , num2这个指针指向的值=100
类型=*int , 值=0xc00012a078 , 地址=0xc00014e020 , num2这个指针指向的值=200
3.make:用来分配内存,主要用来分配引用类型,比如 channel、map、slice。这个我们后面讲解。
9)错误处理
func test() {
num1 := 10
num2 := 0
res := num1 / num2 //错误,0不能作为分母
fmt.Println(res)
}
func main() {
test()
}
对上面代码的总结
- 在默认情况下,当发生错误后(panic) ,程序就会退出(崩溃.)
- 如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。还可 以在捕获到错误后,给管理员一个提示(邮件,短信。。。)
这里引出我们要将的错误处理机制
基本说明
- Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种处理。
- Go 中引入的处理方式为:
defer
,panic
,recover
- 这几个异常的使用场景可以这么简单描述:Go 中可以抛出一个 panic 的异常,然后在 defer 中 通过 recover 捕获这个异常,然后正常处理
使用 defer+recover 来处理错误
package main
import (
"fmt"
"time"
)
func test() {
//使用defer + recover来捕获和处理异常
defer func() { //匿名函数
err := recover() // recover()是一个内置函数,可以捕获异常
if err != nil { //判断err是否为空,
fmt.Println("err=", err)
}
}() // () 调用匿名函数
num1 := 10
num2 := 0
res := num1 / num2 //错误,0不能作为分母
fmt.Println(res)
}
func main() {
test()
for {
fmt.Println("继续执行代码")
time.Sleep(time.Second) //休眠
}
}
错误处理的好处
进行错误处理后,程序不会轻易挂掉,如果加入预警代码,就可以让程序更加的灵活
自定义错误
Go 程序中,也支持自定义错误, 使用 errors.New 和 panic 内置函数
-
errors.New(“错误说明”) , 会返回一个 error 类型的值,表示一个错误
-
panic 内置函数 ,接收一个 interface{}类型的值(也就是任何值了)作为参数。可以接收 error 类型的变量,输出错误信息,并退出程序
//函数去读取一个配置文件名
//如果文件名传入不正确,返回一个自定义错误
func readConf(name string) (err error) {
if name == "config.ini" { //判断config.ini文件
return nil //当文件名正确时是返回一个空
} else {
return errors.New("未找到config.int文件") //当文件名错误时返回一个自定义错误
}
}
func test01() {
err := readConf("config.i i") //传入参数给readConf函数
if err != nil {
panic(err) // 如果读取文件发生错误,就输出这个错误,并终止程序
}
fmt.Println("程序继续执行")
}
func main() {
test01()
}
11.函数练习
练习1
循环打印输入的月份的天数。【使用continue实现】
— 要有判断输入的月份是否错误的语句
效果:
练习2
编写一个函数:
随机猜数游戏:
随机生成一一个1- - 100 的整数
有十次机会
如果第一次就猜中,提示“你真是个天才”
如果第2–3次猜中,提示“你很聪明,赶上我了”
如果第4–9次猜中,提示“一般般’
如果最后一次猜中,提示“可算猜对啦”
一次都没猜对, 提示“说你点啥好呢”
练习3
编写一个函数:输出100以内的所有素数(素数就只能被1和本身整除的数),每行显示5个;并求和
练习4
编写-一个函数,判断是打鱼还是晒网.
中国有句俗语叫“三天打鱼两天晒网”。如果从1990年1月1日起开始执行“三天打鱼两天晒网”。如何判断在以后的某-天中是“打鱼”还是“晒网”?
练习5
打印如下效果
----------小小计算器----------
1.加法
2.减法
3.乘法
4.除法
0.退出
请选择:
10+5=15
----------小小计算器----------
1.加法
2.减法
3.乘法
4.除法
o.退出
请选择: 0
程序退出!
练习6
输出小写的a-z以及大写的Z-A,
使用for和ASCII码
![](https://img-blog.csdnimg.cn/63a303d37981450995a1c8035418a207.png)
章节目录
【Golang第1~3章:基础】如何安装golang、第一个GO程序、golang的基础
【Golang第4章:函数】Golang包的引用,return语句、指针、匿名函数、闭包、go函数参数传递方式,golang获取当前时间
【Golang第5章:数组与切片】golang如何使用数组、数组的遍历和、使用细节和内存中的布局;golang如何使用切片,切片在内存中的布局
【Golang第6章:排序和查找】golang怎么排序,golang的顺序查找和二分查找,go语言中顺序查找二分查找介绍和案例
【Golang第7章:map】go语言中map的基本介绍,golang中map的使用案例,go语言中map的增删改查操作,go语言对map的值进行排序
【Golang第8章:面向对象编程】Go语言的结构体是什么,怎么声明;Golang方法的调用和声明;go语言面向对象实例,go语言工厂模式;golang面向对象的三大特性:继承、封装、多态
【Golang第9章:项目练习】go项目练习家庭收支记账软件项目、go项目练习客户管理系统项目
【Golang第10章:文件操作】GO语言的文件管理,go语言读文件和写文件、GO语言拷贝文件、GO语言判断文件是否存在、GO语言Json文件格式和解析
【Golang第12章:goroutine协程与channel管道】GO语言goroutine协程和channel管道的基本介绍、goroutine协