Golang--函数、包、defer、系统函数、内置函数
1、何为函数
函数作用:提高代码的复用型,减少代码的冗余,提高代码的维护性
函数定义:为完成某一功能的程序指令(语句)的集合,称为函数。
语法:
func 函数名(形参列表)(返回值类型列表){
//执行语句
//……
return 返回值列表
}
例子:
package main
import "fmt"func dp(a int)(int){if a == 1{return 1}return a + dp(a-1)
}func main(){fmt.Println(dp(10))
}
2、详讲函数
- 对特定的功能进行提取,形成一个代码片段,这个代码片段就是我们所说的函数
- 函数的作用:提高代码的复用性
- 函数和函数是并列的关系,所以我们定义的函数不能写到main函数中
- 基本语法
func 函数名(形参列表)(返回值类型列表){
//执行语句
return 返回值列表
}
2.1 函数名
- 遵循标识符命名规范:见名知意 addNum,驼峰命名addNum
- 首字母不能是数字
- 首字母大写该函数可以被本包文件和其它包文件使用(类似public)
- 首学母小写只能被本包文件使用,其它包文件不能使用(类似private)
2.2 形参列表
- 形参列表:个数->0~n个参数
- 形式参数列表:作用->接收外来的数据
- 实际参数:实际传入的数据
2.3 返回值类型列表
- 函数的返回值对应的类型应该写在这个列表中,如果没有返回值,返回值类型什么都不写就可以了
- 返回值个数:0~n个
- 返回值有多个时,如果有返回值不想接收,那么可以利用_进行忽略
2.4 函数栈帧
package main
import "fmt"
//自定义函数:功能:交换两个数
func exchangeNum (num1 int,num2 int){ var t intt = num1num1 = num2num2 = t
}
func main(){ //调用函数:交换10和20var num1 int = 10var num2 int = 20fmt.Printf("交换前的两个数: num1 = %v,num2 = %v \n",num1,num2)exchangeNum(num1,num2)fmt.Printf("交换后的两个数: num1 = %v,num2 = %v \n",num1,num2)
}
查看实参和形参的地址:
2.5 Golang不支持函数重载
2.6 Golang中支持可变参数
- 定义一个函数,函数的参数为:可变参数 ... 参数的数量可变
- args...int 可以传入任意多个数量的int类型的数据 传入0~n个
- 函数内部处理可变参数的时候,将可变参数当做切片来处理
package main import "fmt"//可变参数函数 func sum(nums...int){total := 0for _,num := range nums{total += num}fmt.Println(total) }Go 不允许将多个不同类型的可变参数组合在函数参数列表中,//也就是说,在一个函数内部,你不能将可变参数列表分为不同的类型。 // func print(nums...int,str...string){ // fmt.Println(nums) // fmt.Println(str) // }func main(){sum(1,2)sum(1,2,3)nums := []int{1,2,3,4}sum(nums...)//print(1,2,3,"hello","golang") }
2.7 值传递和引用传递
值传递:基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。
package main import "fmt"func printArr(arr []int){for _, val := range arr{val += 10fmt.Println(val)} }func main(){//定义一个数组arr := [10]int{1,2,3,4,5,6,7,8,9,0}//将数组传递给函数printArr(arr[:]) //[11 12 13 14 15 16 17 18 19 10]//查看数组fmt.Println(arr) //[1 2 3 4 5 6 7 8 9 0] }
引用传递: 如果希望在函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果来看类似引用传递。
package main import "fmt"func printArr(arr *[]int){//遍历数组for i := 0; i < len(*arr); i++ {(*arr)[i] += 10}fmt.Println(*arr) }func main(){//定义一个数组arr := []int{1,2,3,4,5,6,7,8,9,0}//将数组的地址传递给函数printArr(&arr) //[11 12 13 14 15 16 17 18 19 10]//查看数组fmt.Println(arr) //[11 12 13 14 15 16 17 18 19 10] }
2.8 函数类型
- 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。
- 函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用(把函数本身当做一种数据类型)(回调函数)
package main
import "fmt"func printArr(arr *[]int){//遍历数组for i := 0; i < len(*arr); i++ {(*arr)[i] += 10}fmt.Println(*arr)
}func main(){//函数也是一种数据类型,可以赋值给一个变量a := printArr //函数类型的变量//查看函数类型fmt.Printf("a的类型:%T\n,printArr函数的类型:%T\n",a, printArr) //a的类型:func(*[]int)//定义一个数组arr := []int{1,2,3,4,5,6,7,8,9,0}//通过变量调用函数a(&arr) //[11 12 13 14 15 16 17 18 19 10]
}
package main
import "fmt"func printArr(arr *[]int){//遍历数组for i := 0; i < len(*arr); i++ {(*arr)[i] += 10}fmt.Println(*arr)
}func printArr_String(printArrFunc func(*[]int),arr *[]int, str *string)(bool){printArrFunc(arr)fmt.Println(*str)return true
}func main(){//函数也是一种数据类型,可以赋值给一个变量a := printArr //函数类型的变量//查看函数类型fmt.Printf("a的类型:%T\nprintArr函数的类型:%T\n",a, printArr) //a的类型:func(*[]int)fmt.Printf("printArr_String函数的类型:%T\n",printArr_String) //printArr_String函数的类型:func(func(*[]int), *[]int, *string) bool//定义一个数组arr := []int{1,2,3,4,5,6,7,8,9,0}//通过变量调用函数a(&arr) //[11 12 13 14 15 16 17 18 19 10]//定义一个字符串str := "hello golang"//通过变量调用函数printArr_String(a,&arr,&str) //[11 12 13 14 15 16 17 18 19 10] hello golang
}
2.9 type 自定义数据类型名
基本语法: type 自定义数据类型名 数据类型
可以理解为 : 相当于起了一个别名
package main
import "fmt"type myInt int
type myAdd func(a,b myInt)myIntfunc add(a,b myInt)myInt{return a + b
}func main(){var a myAdda = addfmt.Println(a(1,2))
}
2.10 支持对函数返回值命名
传统写法要求:返回值和返回值的类型对应,顺序不能差
升级写法:对函数返回值命名,里面顺序就无所谓了,顺序不用对应
package main
import "fmt"//传统函数返回值写法
func f()(int, string){nun := 1str := "hello golang"//返回值和返回值类型需要一致,顺序需要一致return nun,str
}//函数返回值命名写法
func f1()(num int,str string){num = 1str = "hello golang"//返回值和返回值类型需要一致,顺序可以不一致return
}func main(){//传统函数返回值写法nun,str := f()fmt.Println(nun,str) //1 hello golang//函数返回值命名写法num,str := f1()fmt.Println(num,str) //1 hello golang
}
2.11 Golang没有宏
- 在 Go 语言中,并没有像 C 或 C++ 那样的
#define
预处理器指令来定义宏。取而代之的是,Go 使用常量和变量来实现类似的功能。 - 可以使用const关键字来定义变量来实现类似的宏变量,但这并不等同于宏。常量在编译时就确定了值,而不能像宏那样具有更复杂的文本替换。
- 没有宏函数,只能通过定义普通函数来调用
- Go 语言设计的初衷是提倡清晰和简洁,使用函数来代替宏提供了更高的类型安全性和可读性。
package main import "fmt" const Pi = 3.14 func main() { fmt.Println("Pi:", Pi)
}
package main import "fmt" const ( ServerAddress = "localhost:8080" MaxRetries = 5
) func main() { fmt.Println("Server Address:", ServerAddress) fmt.Println("Max Retries:", MaxRetries)
}
Go 还允许使用 iota
定义一组相关的常量,自动递增。
package main import "fmt" const ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Saturday
) func main() { fmt.Println("Sunday:", Sunday) //0fmt.Println("Monday:", Monday) //1fmt.Println("Tuesday:", Tuesday) //2
}
3、包
3.1 包的引用
使用包的原因:
- 我们不可能把所有的函数放在同一个源文件中,可以分门别类的把函数放在不同的源文件中
- 解决同名问题:两个人都想定义一个同名的函数,在同一个文件中是不可以定义相同名字的函数的。此时可以用包来区分。
示例:
注意:
- 如果变量名、函数名、常量名首字母大写,则可以被其他的包访问;
- 如果首字母小写,则只能在本包中使用 (利用首字母大写小写完成权限控制)
- import导入语句通常放在文件开头包声明语句的下面。
- 导入的包名需要使用双引号包裹起来。
- 根据Go语言的规范,包的导入路径应该是从$GOPATH/src开始的相对路径或者是绝对路径
- 在函数调用时前面要定位到所在的包
- 建议包的声明这个包和所在的文件夹同名
- main包时程序的入口包,一般main函数会放在这个包下
需要配置一个环境变量:GOPATH
注意:最新版本需要禁止GO111MODULE,否则无法使用GOPATH实现下述操作。
禁止/启用GO111MODULE:
禁止(Windows):set GO111MODULE=off,请注意,这个设置只在当前cmd会话中有效。启用(Windows):set GO111MODULE=on
禁止(PowerShell):$env:GO111MODULE = "off",请注意,这个设置只在当前PowerShell会话中有效。
启用(PowerShell):$env:GO111MODULE = "on’"
3.2 包的使用
- package 包名,进行包的声明(建议:包的声明和目录同名)
- main包是程序的入口包,一般main函数会放在这个包下,main函数一定要放在main包下,否则不能编译执行
- 引入包的语法:import "包的路径",包名是从$GOPATH/src/后开始计算的(使用GOPATH,需要配置环境变量),使用/进行路径分隔
- 如果有多个包,建议一次性导入,如:
import(
//"包名"
//"包名"
) - 在函数调用的时候前面要定位到所在的包
- 函数名,变量名首字母大写,函数,变量可以被其它包访问
- 一个目录下不能有重复的函数(归属于同一个包)
- 包名和文件夹的名字,可以不一样
- 一个目录下的同级文件归属一个包,同级别的源文件的包的声明必须一致
- 可以给包取别名,取别名后,原来的包名就不能使用了
包是什么:
- 在程序层面,所有使用相同 package 包名 的源文件组成的代码模块
- 在源文件层面就是一个文件夹
4、init函数
init函数:初始化函数,可以用来进行一些初始化的操作
每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用。
执行流程:
全局变量定义 > init函数 > main函数
多个源文件都有init函数的时候,如何执行:
5、匿名函数
- Go支持匿名函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数
- 匿名函数使用方式:
- 在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次(用的多)
- 将匿名函数赋给一个变量(该变量就是函数变量了),再通过该变量来调用匿名函数(用的少)
- 如何让一个匿名函数,可以在整个程序中有效呢?将匿名函数给一个全局变量就可以了
package main import "fmt" var Func01 = func (num1 int,num2 int) int{return num1 * num2 } func main(){//定义匿名函数:定义的同时调用result := func (num1 int,num2 int) int{return num1 + num2}(10,20)fmt.Println(result)//将匿名函数赋给一个变量,这个变量实际就是函数类型的变量//sub等价于匿名函数sub := func (num1 int,num2 int) int{return num1 - num2}//直接调用sub就是调用这个匿名函数了result01 := sub(30,70)fmt.Println(result01)result02 := sub(30,70)fmt.Println(result02)result03 := Func01(3,4)fmt.Println(result03) }
6、闭包
- 闭包: 闭包就是一个函数和与其相关的引用环境组合的一个整体
- 闭包形式:返回的匿名函数+匿名函数以外的变量(匿名函数+引用的变量/参数 = 闭包)
- 匿名函数中引用的那个变量会一直保存在内存中,可以一直使用
package main
import "fmt"func getSum() func (int) int{sum := 0return func (num int) int{sum += numreturn sum}
}func main(){f := getSum()fmt.Println(f(10)) //10fmt.Println(f(20)) //30fmt.Println(f(30)) //60
}
闭包的本质:闭包本质依旧是一个匿名函数,只是这个函数引入外界的变量/参数
闭包的特点:
- 返回的是一个匿名函数,但是这个匿名函数引用到函数外的变量/参数 ,因此这个匿名函数就和变量/参数形成一个整体,构成闭包。
- 闭包中使用的变量/参数会一直保存在内存中,所以会一直使用,意味着闭包不可滥用(对内存消耗大)
使用场景:
- 不使用闭包的时候:想保留的值,不可以反复使用
- 闭包应用场景:闭包可以保留上次引用的某个值,我们传入一次就可以反复使用了
7、defer关键字
在函数中,程序员经常需要创建资源,为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer关键字。
- 在Go中,程序遇到defer关键字,并不会立即执行defer后的语句,而是将defer后的语句压入一个栈中(栈:先进后出),然后继续执行函数后面的语句
- 在函数执行完毕后,从栈中取出语句开始执行,按照先进后出的规则执行语句
- 遇到defer关键字,会将后面的代码语句压入栈中,也会将相关的值同时拷贝入栈中,不会随着函数后面的变化而变化
defer应用场景:
比如你想关闭某个使用的资源,在使用的时候直接随手defer,因为defer有延迟执行机制(函数执行完毕再执行defer压入栈的语句),在函数执行完后自动开始调用
8、系统函数
8.1 字符串相关函数
统计字符串的长度,按字节进行统计:
函数:len(str),内置函数不需要导包,直接使用
package main import "fmt"func main(){str := "hello golang"fmt.Println(len(str)) //12 }
字符串遍历:
1、使用for-range遍历2、使用配合len进行下标线性访问
package main import "fmt"func main(){str := "hello golang"//遍历字符串//for-rangefor _,ch := range str{fmt.Printf("%c",ch)}fmt.Println()//lenfor i := 0; i < len(str); i++ {fmt.Printf("%c",str[i])} }
字符串转整数:
函数:n, err := strconv.Atoi("str")
package main import("fmt""strconv" )func main(){str1 := "1232"str2 := "02"num1,_ := strconv.Atoi(str1)num2,_ := strconv.Atoi(str2)fmt.Println(num1 + num2) //1234 }
整数转字符串:
函数:str = strconv.Itoa(num)
查找子串是否在指定的字符串中:
函数:bool = strings.Contains(原串, 子串)
package main import("fmt""strings" )func main(){str1 := "hello golang"str2 := "godd"fmt.Println(strings.Contains(str1,str2)) //false }
统计一个字符串有几个指定的子串:
函数:count = strings.Count(原串,子串)package main import("fmt""strings" )func main(){str1 := "gogogogog"str2 := "go"fmt.Println(strings.Count(str1,str2)) //4 }
不区分大小写的字符串比较:
函数:bool = strings.EqualFold(str1 , str2)
区分大小写的字符串比较:
使用 == 比较运算符
返回子串在字符串第一次出现的索引值,如果没有返回-1 :
函数:index = strings.lndex("javaandgolang" , "a")
字符串的替换:
函数:str = strings.Replace(原串, 替换目标串, 替换串, n)
n可以指定你希望替换几个, 如果n=-1表示全部替换,替换两个n就是2
按照指定的某个字符,为分割标识,将一个学符串拆分成字符串数组:
函数:arrStr = strings.Split("go-python-java", "-")
将字符串的字母进行大小写的转换:
函数:
str = strings.ToLower("Go") // go
str = strings.ToUpper"go") //Go
将字符串左右两边的空格去掉:
函数:str = strings.TrimSpace(" go and java ")
将字符串左右两边指定的字符去掉:
函数:str = strings.Trim("~golang~ ", " ~")
将字符串左边指定的字符去掉:
函数:str = strings.TrimLeft("~golang~", "~")
将字符串右边指定的字符去掉:
函数:str = strings.TrimRight("~golang~", "~")
判断字符串是否以指定的字符串开头:
函数:bool = strings.HasPrefix("hello golang", "hello")
判断字符串是否以指定的字符串结束:
函数:bool = strings.HasSuffix("main.go", ".go")
8.2 日期和时间相关函数
时间和日期的函数,需要到入time包,获取当前时间,就要调用Now函数:
package main import ("fmt""time" ) func main(){//时间和日期的函数,需要到入time包,所以你获取当前时间,就要调用函数Now函数:now := time.Now()//Now()返回值是一个结构体,类型是:time.Timefmt.Printf("%v ~~~ 对应的类型为:%T\n",now,now)//2021-02-08 17:47:21.7600788 +0800 CST m=+0.005983901 ~~~ 对应的类型为:time.Time//调用结构体中的方法:fmt.Printf("年:%v \n",now.Year())fmt.Printf("月:%v \n",now.Month())//月:Februaryfmt.Printf("月:%v \n",int(now.Month()))//月:2fmt.Printf("日:%v \n",now.Day())fmt.Printf("时:%v \n",now.Hour())fmt.Printf("分:%v \n",now.Minute())fmt.Printf("秒:%v \n",now.Second()) }
按照指定格式:
9、内置函数
内置函数/内建函数:Golang设计者为了编程方便,提供了一些函数,这些函数不用导包可以直接使用,我们称为Go的内置函数/内建函数
内置函数存放位置:在builtin包下,使用内置函数直接用就行
常见内置函数:
len函数:
统计字符串的长度,按字节进行统计
new函数:
分配内存,主要用来分配值类型(int系列、float系列、bool、string、数组和结构体struct)
make函数:
分配内存,主要用来分配引用类型(指针、slice切片、map、管道chan、interface 等)
其他内置函数可以看:Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国