文章目录
为完成某一功能的程序指令(语句)的集合,称为函数。 在
Go
中,函数分为: 自定义函数、系统函数
语法:
func函数名(形参列表) (返回值列表) {
执行语句...
return 返回值列表.
}
1. 包使用的注意事项和细节
- 为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的
public
,这样才能跨包访问。 - 在访问其它包函数,变量时,其语法是
包名.函数名
。 - 如果你要编译成一个可执行程序文件,就需要将这个包声明为
main
, 即package main
,这个就是一个语法规范,如果你是写一个库 ,包名可以自定义。
2. 函数调用过程
- 在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其它的栈的空间区分开来
- 在每个函数对应的栈中,数据空间是独立的,不会混淆
- 当一个函数调用完毕(执行完毕)后,程序会销毁这个函数对应的栈空间
3. 函数使用的注意事项和细节
- 函数的形参列表可以是多个,返回值列表也可以是多个
- 形参列表和返回值列表的数据类型可以是值类型和引用类型
- 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其它包文件使用,类似
public
, 首字母小写,只能被本包文件使用,其它包文件不能使用,类似private
- 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值
- 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址
&
,函数内以指针的方式操作变量
func increment(num *int) {
*num += 1
}
func main() {
num :=1
increment(&num)
fmt.Println(num)
}
Go
函数不支持函数重载- 使用
_
标识符,忽略返回值 - 如果返回值只有一个,(返回值类型列表) 可以不写
()
Go
支持可变参数,如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后
func sum(num int, args ...int) int {
sum := num
for i := 0; i < len(args); i++ {
sum += args[i]
}
return sum
}
func main() {
count := sum(1, 2, 3, 4)
fmt.Println(count)
}
4. init函数
每一个源文件都可以包含一个init
函数,该函数会在main
函数执行前,被Go
运行框架调用,也就是说init
在main
函数前被调用
- 如果一个文件同时包含全局变量定义,
init
函数和main
函数,则执行的流程全局变量定义->init
函数->main
函数 init
函数最主要的作用,就是完成一些初始化的工作- 导入包的
init
函数要比自身的init
函数要先执行
5. 匿名函数
Go
支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用
①使用方式一
在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次
func main() {
res := func(n1 int,n2 int) int{
return n1+n2
}(10,20)
fmt.Println(res)
}
②使用方式二
将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数,可多次调用
func main() {
func1 := func(n1 int, n2 int) int {
return n1 + n2
}
sum1 := func1(1, 2)
fmt.Println(sum1)
}
③使用方式三
如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效
var (
func1 = func(n1 int, n2 int) int {
return n1 + n2
}
)
func main() {
sum1 := func1(1, 2)
fmt.Println(sum1)
}
6. 闭包
闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
案例:
func addUpper() func(int) int {
n := 10
return func(i int) int {
n = n + i
return n
}
}
func main() {
f := addUpper();
fmt.Println(f(1))//11
fmt.Println(f(2))//13
fmt.Println(f(3))//16
}
说明:
addUpper
返回的是一个匿名函数,但是这个匿名函数引用到函数外的n
,因此这个匿名函数就和n
形成一个整体,构成闭包。- 闭包是类, 函数是操作,
n
是字段。函数和它使用到n
构成闭包 - 当我们反复的调用
f
函数时,因为n
是初始化一次,因此每调用一次就进行累计 - 我们要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包
- 闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。
7. defer
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等) ,为了在函数执行完毕后,及时的释放资源,Go
的设计者提供defer
(延时机制)
案例:
func sum(n1 int, n2 int) int {
//当执行到defer时, 暂时不执行,会将defer后面的语句压入到独立的栈( defer栈)
//当函数执行完毕后,再从defer栈,按照先入后出的方式出栈,执行
defer fmt.Println("def n1=", n1)
defer fmt.Println("def n2=", n2)
res := n1 + n2
fmt.Println("first res=", res)
return res
}
func main() {
res := sum(10, 20)
fmt.Println("last res=", res)
}
defer 的注意事项和细节:
- 当
go
执行到一个defer
时,不会立即执行defer
后的语句,而是将defer
后的语句压入到一个栈中 - 当函数执行完毕后,在从
defer
栈中,依次从栈顶取出语句执行 - 在
defer
将语句放入到栈时,也会将相关的值拷贝同时入栈
defer的最佳实践
当函数执行完毕后,可以及时的释放函数创建的资源
8. 函数参数传递方式
- 值传递
- 引用传递
不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的 数据大小,数据越大,效率越低
值类型:基本数据类型int
系列、float
系列、 bool
、 string
、数组和结构体struct
引用类型:指针、slice
切片、map
、管道chan
、interface
等都是引用类型
1)值类型默认是值传递:变量直接存储值,内存通常在栈中分配
2)引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC
来回收。
3)如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&
,函数内以指针的方式操作变量。
9. 系统函数
9.1 字符串函数
①统计字符串的长度,按字节len(str)
str := "hello world"
fmt.Println("str的长度为", len(str))
②字符串遍历,同时处理有中文的问题r := []rune(str)
r := []rune(str)
for i := 0; i < len(r); i++ {
fmt.Printf("%c", r[i])
}
③字符串转整数: n, err := strconv.Atoi(str)
i, e := strconv.Atoi("12")
if e != nil {
fmt.Println("转换错误", e)
} else {
fmt.Println("转换结果为:", i)
}
④整数转字符串str = strconv.Itoa(num)
str := strconv.Itoa(123)
fmt.Printf("%v,%T", str, str)
⑤字符串 转 []byte: var bytes = []byte(str)
str := "hello go"
bytes := []byte(str)
fmt.Printf("bytes=%v\n", bytes)
⑥[]byte 转字符串: str = string(bytes[])
str := string([]byte{'a', 'b', 'c'})
fmt.Printf("%v", str)
⑦10 进制转 2, 8, 16 进制: str = strconv.FormatInt(int, int)
str := strconv.FormatInt(123, 2)
fmt.Println(str)
⑧查找子串是否在指定的字符串中: strings.Contains(str, sub)
contains := strings.Contains("last", "as")
fmt.Println(contains)
⑨统计一个字符串有几个指定的子串 : strings.Count(str,sub)
count:= strings.Contains("laaast", "a")
fmt.Println(count)
⑩不区分大小写的字符串比较(==是区分字母大小写的): fmt.Println(strings.EqualFold(str1, str2))
equals := strings.EqualFold("abc", "ABC")
fmt.Println(equals)//true
fmt.Println("abc" == "ABC")//false
⑪返回子串在字符串第一次出现的index值,如果没有返回-1 : strings.Index(str,sub)
index:= strings.Index("abc", "a")
⑫返回子串在字符串最后一次出现的index值,如果没有返回-1 : strings.LastIndex(str,sub)
index:= strings.LastIndex("abc", "a")
⑬将指定的子串替换成另外一个子串: strings.Replace(str,old,new,n)
, n 可以指定你希望替换几个,如果 n=-1 表示全部替换
str := "hello go"
index:= strings.Replace(str, "go","golang",-1)
⑭按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组:strings.Split(str,splitStr)
str := "hello,go"
strArr:= strings.Split(str,",")
⑮将字符串的字母进行大小写的转换: strings.ToLower(str)
,strings.ToUpper(str)
str := "hello,go"
lower:= strings.ToLower(str)
upper:= strings.ToUpper(str)
⑯将字符串左右两边的空格去掉: strings.TrimSpace(str)
str := " hello,go "
trimSpace:= strings.TrimSpace(str)
⑰将字符串左右两边指定的字符去掉 : strings.Trim(str, sub)
str := "!hello,go!"
trim:= strings.Trim(str,"!")
⑱将字符串左边、右边指定的字符去掉:strings.TrimLeft(str, sub)
、strings.TrimLeft(str, sub)
str := "!hello,go!"
trimLeft:= strings.TrimLeft(str,"!")
trimRight:= strings.TrimRight(str,"!")
⑲判断字符串是否以指定的字符串开头: strings.HasPrefix(str, sub)
str := "!hello,go!"
trimLeft:= strings.HasPrefix(str,"!")
⑳判断字符串是否以指定的字符串结束: strings.HasSuffix(str,sub)
str := "!hello,go!"
trimLeft:= strings.HasSuffix(str,"!")
9.2 时间和日期相关函数
使用日期相关的函数需要导入time
包
①获取当前时间
now := time.Now()
fmt.Println(now)//2020-05-22 08:55:19.4277503 +0800 CST m=+0.001994801
②获取具体信息
now := time.Now()
fmt.Println(now)
fmt.Printf("年:%v\n", now.Year())//2020
fmt.Printf("月:%v\n", now.Month())//May
fmt.Printf("月:%v\n", int(now.Month()))//5
fmt.Printf("日:%v\n", now.Day())//22
fmt.Printf("时:%v\n", now.Hour())//9
fmt.Printf("分:%v\n", now.Minute())//1
fmt.Printf("秒:%v\n", now.Second())//5
③格式化日期时间:time.Format()
这个format
格式化字符串必须是下边固定的!
now := time.Now()
fmt.Printf("%v\n", now.Format("2006-01-02 15:04:05"))//2020-05-22 09:09:15
fmt.Printf("%v\n", now.Format("2006-01-02"))//2020-05-22
fmt.Printf("%v\n", now.Format("15:04:05"))//09:09:15
④时间常量
配合休眠函数sleep
/**
Hour=60Minute=60*60Second=60*60*1000Millisecond(毫秒)
Millisecond=1000Microsecond(微秒)1000*1000Nanosecond(//纳秒)
*/
time.Sleep(5 * time.Second)
⑤获取时间戳
Unix
将t
表示为Unix
时间,即从时间点January 1, 1970 UTC
到时间点t所经过的时间(单位秒)
UnixNano
将t
表示为Unix
时间,即从时间点January 0.1970 UTC
到时间点所经过的时间(单位纳秒)
now := time.Now()
//unix时间戳=1590110508 unixnano时间戳=1590110508535021100
fmt.Printf("unix时间戳=%v unixnano时间戳=%v\n", now.Unix(), now.UnixNano())
9.3 内置函数
- len:用来求长度,比如
string
、array
、slice
、map
、channel
- new: 用来分配内存,主要用来分配值类型,比如
int
、float32
、struct
…返回的是指针
num := new(int)
fmt.Printf("%T\n", num) //*int
*num = 100
fmt.Println(*num)//100
- make:用来分配内存,主要用来分配引用类型,比如
channel
、map
、slice
10.错误处理
Go
语言追求简洁优雅,Go
语言不支持传统的try-catch-finally
这种处理,Go
中引入的处理方式为:defer
、panic
、 recover
案例:
func test() {
defer func() {
err := recover() //recover()会捕获到异常
if err != nil {
fmt.Println("出错了")
//fmt.Println(err)
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println(res)
}
func main() {
test()//出错了
}
10.1 自定义错误
①errors.New("错误说明")
,会返回一个error
类型的值,表示一个错误
②panic
内置函数 ,接收一个interface{}
类型的值(也就是任何值了)作为参数。可以接收error
类
型的变量,输出错误信息,并退出程序
案例:
func readConf(name string) (err error) {
if name == "config.ini"{
//读取...
return nil
} else {
//返回一个目定义错误
return errors.New("读取文件错误..")
}
}
func test02() {
err := readConf("config2.ini")
if err!=nil {
//如果读取文件发送错误,就输出这个错误,并终止程序
panic(err)
}
fmt.Print1n("test02( )继续执行....")
}