GO语言学习之函数,异常
1.init函数
1》基本介绍
每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被go运行框架调用,也就是说Init会在main函数执行前被调用
2》.代码示例
package main
func init() {
println("我爱这世界,因为我爱你。。。")
}
func main() {
println("hello world")
}
3》init函数的注意事项和细节
<1如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程全局变量定义–>init函数–>main函数
package main
var a=test()
func test() int{
println("走向远方,走向那充满想象的未知。。。")
return 100
}
func init() {
println("我爱这世界,因为我爱你。。。")
}
func main() {
println("温暖,安详,笼罩着大地。。。")
}
/*
走向远方,走向那充满想象的未知。。。
我爱这世界,因为我爱你。。。
温暖,安详,笼罩着大地。。。
*/
<2.init最主要的作用,就是完成一些初始化工作,
package util
import (
"fmt"
)
var Name string
var pwd string
func init() {
fmt.Println("init()")
Name ="durex"
pwd="123"
}
package main
import (
"byteDemo02/util"
"fmt"
)
var name=test()
func test() string{
fmt.Println("test哈哈。。。")
return "隔壁老王"
}
func init() {
fmt.Println("爱是世界上最美的东西。。。")
}
func main() {
fmt.Println("name",name)
fmt.Println("name",util.Name)
/*运行结果:
init()
test哈哈。。。
爱是世界上最美的东西。。。
name 隔壁老王
name durex
*/
}
执行流程总结:先执行被导入包的初始化,再执行导入包的初始化,再按照代码顺序执行调用
2.匿名函数
GO支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用
1》匿名函数调用方式1:
package main
import "fmt"
func main() {
//没有名字,直接调用,这种匿名函数只能调用一次
res:=func(a int,b int)int{
return a+b
}(10,30)
fmt.Println("res=",res)
}
2》匿名函数调用方式2:
package main
import "fmt"
func main() {
res:=func(a int,b int)int{
return a-b
}
a:=res(100,76)
fmt.Println("a=",a)
a=res(200,100)
fmt.Println("a=",a)
}
3》全局匿名函数
如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序中有效
package main
import "fmt"
var(
Fun1= func(a int,b int) int {
return a*b
}
)
func main() {
res:=Fun1(8,9)
fmt.Println("res=",res)
}
3.闭包
1》闭包就是一个函数和与他相关的引用环境组合的一个整体(实体)
代码演示:
package main
import "fmt"
func addPackage() func(int)int {
n:=10
return func(i int) int {
n=n+i
return n
}
}
func main() {
res:=addPackage()
fmt.Println(res(1))
fmt.Println(res(2))
fmt.Println(res(3))
}
》addPackage 是一个函数,其返回值类型是 func(int)int
》闭包的说明:
n:=10
return func(i int) int {
n=n+i
return n
}
该段代码返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成以整体,构成闭包
》大家可以这样理解:闭包是类,函数时操作,n是字段,函数和它使用到n构成闭包
》当我们反复的调用f函数是,因为n是只初始化一次,因此每调用一次就进行累计
》我们要搞清楚闭包的关键,就是要分析出返回的函数,它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包
package main
import "fmt"
func addPackage() func(int)int {
n:=10
fmt.Println("哈哈哈哈哈哈")
str:="hello"
return func(i int) int {
str+=string(36)
fmt.Println(str)
n=n+i
return n
}
}
func main() {
res:=addPackage()
fmt.Println(res(1))
fmt.Println(res(2))
fmt.Println(res(3))
}
//结果:
哈哈哈哈哈哈
hello$
11
hello$$
13
hello$$$
16
可以知道 n:=10只执行一次
2》闭包的最佳实践
编写一个程序,具体要求如下:
<编写一个函数 makeSuffix(suffix string),可以接收一个文件后缀名(比如.jpg),并返回一个闭包
<调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如:jpg,如果已经有.jpg后缀,则返回原文件名
<要求使用闭包的方式完成
<string.HasSuffix,该函数可以判断某个字符串是否有指定的后缀
package main
import (
"fmt"
"strings"
)
func makeSuffix(suffix string)func(string)string{
return func(name string) string {
//如果该文件名没有指定的后缀(
if !strings.HasSuffix(name,suffix){
return name+suffix
}
return name
}
}
func main(){
res:=makeSuffix(".jpg")
fmt.Println("文件名处理后:",res("hello"))
fmt.Println("文件名处理后:",res("哈哈哈哈"))
}
代码解读:
返回的匿名函数和makeSuffix(suffix String)的suffix变量,组合成一个闭包,因为返回的函数引用到suffix这个变量
我们能通过这个案例体会到闭包的好处,如果用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入后缀名,比如:.jpg,而闭包因为可以保留上次引用的某个值,所以我么传入一次就可以反复使用。
4函数·的defer
1.为什么需要defer
在函数中,我们经常需要创建资源(比如:数据库连接,文件句柄,锁)
为了在函数执行完毕后,及时释放资源,GO设计者提供defer(延时机制)
2.代码演示:
package main
import "fmt"
func sum(n1 int,n2 int)int {
//当执行到defer语句时,暂时不执行,会将defer后面的语句压入到独立的栈内存中
//当函数执行完毕以后,再从defer栈,按照先入后出的方式,进行执行
defer fmt.Println("i love you <1>",n1)
defer fmt.Println("i love you <2>",n2)
n3:=n1+n2
fmt.Println("i love you <3>",n3)
return n3
}
func main() {
res:=sum(88,99)
fmt.Println("res=",res)
}
结果:
i love you <3> 187
i love you <2> 99
i love you <1> 88
res= 187
3.defer的最佳实践
defer的价值是:当函数执行完毕后,可以及时的释放函数创建的资源,看下模拟代码
func test01() {
//关闭文件资源
file=openfile("文件名")
defer file.close()
}
func test02() {
//释放数据库连接
connect=openDatabase("文件名")
defer connect.close()
}
1》在开发过程中,创建资源后,比如:(打开文件,建立数据库连接,或者锁资源后)可以执行defer file.Close()
defer connect.Close()
2》在defer后,可以继续使用创建资源
3》当函数完毕后,系统会依次从defer栈中,取出数据,关闭资源
4》这种方式简洁方便,不用再为什么时间结束资源而操心
5.函数参数传递方式
1.两种方式
值类型参数默认就是值传递
值类型:(基本数据类型,int系列,float系列,Bool,string,struct,数组)
引用类型参数默认就是引用传递
引用类型:(指针,slice切片,map,管道chain,interface等都是引用类型)
其实:不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的·是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低
2.两种传递方式的使用特点;
1》值类型默认是值传递,变量直接存储值,内存通常在栈中分配
2》引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正能存储数据的值,引用类型的内存通常在堆上分配,当没有任何变量使用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收
3》如果希望函数内的变量能修改函数外的变量,可以传入变量的地址值,&变量,函数内以指针的方式操作变量。从效果是看类似引用。
6.变量的作用域
1》函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
package main
import "fmt"
func test(){
a :=100
n :="hello liuyifei"
fmt.Println("a:",a," n:",n)
}
func main() {
//a 和n 只能在 test()中使用,这叫局域变量
}
2》函数外部声明/定义的变量叫全局变量,作用域在整个包中都有效,如果首字母为大写,则作用域在整个程序中有效
package main
import "fmt"
//全局变量,整个文件中都能使用
var a=111
var n="佳人"
func main() {
fmt.Println("a:",a," n:",n)
}
3》如果变量是在一个代码块,比如;for/if中,那么这个变量的作用域就在该代码块
package main
import "fmt"
func main() {
for i:=0;i<5;i++{
fmt.Println("i love the world")
}
var i =0
fmt.Println(i)
for i=8;i>5;i--{
fmt.Println("Because i love you")
}
}
4》小练习
package main
import "fmt"
var name="神仙姐姐"
func func01(){
fmt.Println("name:"+name)
}
func func02(){
name="嫦娥"
fmt.Println("name:"+name)
}
func main() {
func01()
func02()
func01()
}
//猜一下输出结果
7.函数小练习
1》将之前打印金字塔的案例,封装成函数
2》编写一个函数,从终端输入(1-9),打印出相应乘法表
8.字符串常用的系统函数
字符串在我们开发过程使用非常多,常用函数需要我们掌握
1》len(str)
统计字符串长度,按字节
str :="hello"
println("str的长度为:",len(str))
2》[]rune(str)
字符串遍历,同时处理有中文的问题 r:=[]rune(str)
str2:="hello 神仙姐姐"
s:=[]rune(str2)
for i:=0;i<len(s);i++{
fmt.Printf("字符为:%c \n",s[i])
}
3》n,err=strconv.Atoi(“88”)
字符串转整数:
x,err:=strconv.Atoi("12")
fmt.Println(x,err)// 12<nil> err=nil代表转换成功
y,err2:=strconv.Atoi("hello")
if err2!=nil{
fmt.Println("转换错误",err2)//转换错误 strconv.Atoi: parsing "hello": invalid syntax
}else {
fmt.Println("转换结果:",y)
}
4》str=strconv.Itoa(123)
整数转成字符串
st01:=strconv.Itoa(123)
fmt.Printf("st01的类型为%T,值为 %v\n",st01,st01)
//st01的类型为string,值为 123
5》[]byte(“str”)
字符串转[]byte
var bytes=[]byte(“hello world”)
var bytes=[]byte("hello world")
fmt.Println("bytes=",bytes)
6》[]byte 转字符串:
str=string([]byte(98,99,100))
str=string([]byte{98,99,100})
fmt.Printf("str=%v\n",str )
7》10进制转 2,8,16进制
str=strconv.FormatInt(123,2)
str=strconv.FormatInt(123,2)
fmt.Printf("str=%v\n",str )
8》查找子串是否在指定的字符串中:
string.Contains(“hello,liuyifei”,“liu”)//true
bo:=strings.Contains("hello,liuyifei","liu")
fmt.Println("bo=",bo)
9》统计字符串中有几个指定的子串:
strings.Count(“chinaese is janpanese‘s dad”,“ese”)
a:=strings.Count("chinaese is janpanese‘s dad","ese")
fmt.Println("a=",a)
10》不区分大小写比较字符串
(==是区分大小写的相等)
fmt.Println(strings.EqualFold("ABC","Abc"))//true
fmt.Println("ABC"=="Abc")//false
11》返回子串在字符串最后一次出现的index,
如果没有返回-1;
strings.LastIndex(“longlong ago,go is not in the world”,“go”)
inde:=strings.LastIndex("longlong ago,go is not in the world","go")
fmt.Println("inde=",inde)
12》返回子串在字符串第一次出现的index,
inde:=strings.Index("longlong ago,go is not in the world","go")
fmt.Println("inde=",inde)
13》将指定的字串替换成另外一个字串,
strings.Replace(“go hello”,“hello go”,“gogogo”,n)
n表示你准备替换几个,如果n=-1则代表全部替换
// non-overlapping: "123" repeat 6 times in s
sx := "123lafaldsjglad123lkfasdf123djfal123lkdjga123lksjfla123l"
old := "123"
new := "888"
fmt.Println("non-overlapping: ")
// n < 0 ,用 new 替换所有匹配上的 old;n=-1: 888lafaldsjglad888lkfasdf888djfal888lkdjga888lksjfla888l
fmt.Println("n=-1: ", strings.Replace(sx, old, new, -1 ))
// 不替换任何匹配的 old;n=0: 123lafaldsjglad123lkfasdf123djfal123lkdjga123lksjfla123l
fmt.Println("n=0: ", strings.Replace(sx, old, new, 0 ))
// 用 new 替换第一个匹配的 old;n=1: 888lafaldsjglad123lkfasdf123djfal123lkdjga123lksjfla123l
fmt.Println("n=1: ", strings.Replace(sx, old, new, 1 ))
// 用 new 替换前 7 个匹配上的 old(实际没那么多);n=7: 888lafaldsjglad888lkfasdf888djfal888lkdjga888lksjfla888l
fmt.Println("n=7: ", strings.Replace(sx, old, new, 7 ))
14》按照指定的某个字符,为分隔标识,将一个字符串拆分成字符串数组
strx4:="hello,liuyifei,do you know ,i love you"
strArr:=strings.Split(strx4,",")
fmt.Println("strArr",strArr)
15》将字符串的字母进行大小写转换:
strings.ToLower(“LIUYIFEI”)
strings.ToUpper(“liuyifei”)
x01:=strings.ToLower("LIUYIFEI")
fmt.Println("x01=",x01)
x02:=strings.ToUpper("liuyifei")
fmt.Println("x02=",x02)
16》将字符串左右两边空格去掉
strings.TrimSpace(“long long ago”)
x03:=strings.TrimSpace(" long long ago ")
fmt.Println("x03=",x03)
17》去掉字符串左右两边的字符
string.TrimLeft("!hello!!","!")
string.TrimRight("!hello!!","!")
x01=strings.TrimLeft("!hello!!","!")
fmt.Println("x01=",x01)
x02=strings.TrimRight("!hello!!","!")
fmt.Println("x02=",x02)
18》判断字符串是否以指定的字符串开头:
strings.HasPrefix(“http://www.baidu.com”,“http”)
boo:=strings.HasPrefix("http://www.baidu.com","http")
fmt.Println(boo)
19》判断字符串是否以指定的字符串结束
boo=strings.HasSuffix("http://www.baidu.com","http")
fmt.Println(boo)
9.时间日期相关函数
1》时间和日期函数,需要导入time包
time包里提供了时间的显示和测量用的函数。
2》time.Time类型,用于表示时间
package main
import (
"fmt"
"time"
)
func main() {
now:=time.Now()
fmt.Printf("now类型:%T,值:%v",now,now)
}
3》如何获取到其他的日期信息
now:=time.Now()
fmt.Printf("now类型:%T,值:%v",now,now)
fmt.Printf("year=%v",now.Year())
fmt.Printf("month=%v",now.Month())
fmt.Printf("day=%v",now.Day())
fmt.Printf("Hour=%v",now.Hour())
fmt.Printf("Minutes=%v",now.Minute())
fmt.Printf(" Second=%v",now.Second())
4》格式化日期时间
方式1:使用Printf或者SPrintf
fmt.Printf("%d-%d%d %d:%d:%d\n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second())
方式2:使用time.Format()
5》时间的常量
const{
//每种秒之间的倍率是 1000
Nanosecond //纳秒
Microsecond //微秒
Millisecond //毫秒
second //秒
Minute //分
Hour // 时
}
常量的作用在程序中可用于获取指定时间单位的时间,比如想得到100毫秒
100*time.Millisecond
6》结合Sleep来使用一下时间常量
package main
import (
"fmt"
"time"
)
func main() {
i:=0
for{
i++
fmt.Println(i)
//休眠
time.Sleep(time.Millisecond*100)
if i>100{
break
}
}
}
7》time的Unix和UnixNano方法
Unix将t表示为Unix时间,即从时间点January 1, 1970 UTC到时间点t所经过的时间(单位秒)。
-
UnixNano将t表示为Unix时间,即从时间点January 1, 1970 UTC到时间点t所经过的时间(单位纳秒)。如果纳秒为单位的unix时间超出了int64能表示的范围,结果是未定义的。注意这就意味着Time零值调用UnixNano方法的话,结果是未定义的。
now:=time.Now()
fmt.Printf("unix时间戳:%v\n,UNIXNANO时间戳为%v\n:",now.Unix(),now.UnixNano())
8:编写代码测试一个应用执行的时间
package main
import (
"fmt"
"time"
)
func funx(){
i:=0
for{
i++
fmt.Println(i)
//休眠
time.Sleep(time.Millisecond*100)
if i>100{
break
}
}
}
func main() {
now:=time.Now()
start:=now.Unix()
funx()
end:=now.Unix()
fmt.Printf("funx()函数执行时间为:%v 秒",end-start)
}
10.内置函数
Go设计者为了编程方便,提供了一些函数,这些函数可以直接使用
我们称为GO内置函数,
1>len:用来求长度:string,array,slice,map,channel
2>new:用来分配内存,主要用来分配值类型,比如:int,float32,struct…返回的是指针
举例说明new的使用
num1:=100
fmt.Printf("num1类型 %T,地址值为:%v,值为%v\n",num1,&num1,num1)
num2:=new(int)
*num2=100
fmt.Printf("num2类型 %T,地址值为:%v,值为%v\n",num2,&num2,num2)
3>make:用来分配内存,主要用来分配引用类型,比如:channel,slice,map,这个后面讲解
11.错误处理
1.代码:
package main
import "fmt"
func test() {
n1:=3
n2:=0
res:=n1/n2
fmt.Println(res)
}
func main() {
test()
fmt.Println("发生了啥。。。")
}
1》在默认情况下,当发生错误(panic)后,程序就会退出(崩溃)
2》如果我们希望,当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行,还可以捕获到错误后,给管理员一个提示(邮件,短信。。)
3》这里我们引出错误处理机制
2.基本说明
1》go语言追求简洁优雅,所以go不支持传统的try …catch…finally这种处理
2》Go中引入的处理方式;defer,panic,recover
3》这几个异常的使用场景可以这么简单描述:
Go中可以抛出一个Panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理
3》使用defer+recover处理错误
package main
import "fmt"
func test() {
defer func() {
err:=recover()
if err!=nil{
fmt.Println("err:",err)
}
}()
n1:=3
n2:=0
res:=n1/n2
fmt.Println(res)
}
func main() {
test()
fmt.Println("发生了啥。。。")
}
/*结果
err: runtime error: integer divide by zero
发生了啥。。。
*/
4.错误处理的好处
进行错误处理后,程序不会轻易挂掉,如果加入预警代码,就可以让程序更加健壮
func test() {
defer func() {
err:=recover()
if err!=nil{
fmt.Println("err:",err)
}
}()
n1:=3
n2:=0
res:=n1/n2
fmt.Println(res)
//这里可以接入发邮件的功能
fmt.Println("发送邮件给管理员")
}
5.自定义错误
GO中支持自定义错误,使用errors.New和panic内置函数
1》errors.New(“错误说明”),会返回一个error的类型的值,表示一个错误
2》panic内置函数,接收一个interface{}类型的值(也就是任何值了)作为参数,可以接收error类型的变量,输出错误信息,并退出程序
package main
import (
"errors"
"fmt"
)
func readConf(name string)(err error){
if name=="config.ini"{
//
return nil
}else {
//返回一个自定义错误
return errors.New("读取文件错误。。。")
}
}
func funcx(){
err:=readConf("configx.ini")
if err!=nil{
//如果读取文件错误,就输出这个错误,并终止程序
panic(err)
}
fmt.Println("funcx...hello world")
}
func main() {
funcx()
fmt.Println("这世界,我来了,,,,,")
}