函数
为什么要用函数
- 提高代码的复用性,减少代码冗余,提高代码维护性
函数的定义
- 为完成某一功能的程序指令(语句)的集合,称为函数
- 对特定的功能进行提取,形成一个代码片段。
函数的说明
- 函数和函数是并列的关系,所以定义的函数不能写到main函数中
基本语法
func 函数名(形参列表) (返回值类型列表){
执行语句
return + 返回值列表
}
函数名
- 遵循标识符命名规范:见名知意,驼峰命名
- 首字母不能是数字
- 首字母大写该函数可以被本包文件和其他包文件使用(类型public)
- 首字母小写只能被本包文件使用,其他包文件不能使用(类似private)
形参列表
- 形参列表:个数:可以是一个参数,可以是n个参数,可以是0个参数
- 形式参列表:作用:接受外来的数据
- 实际参数:实际传入的数据
返回值类型列表
- 函数的返回值对应的类型应该返回在返回值列表中
- 返回0个:返回值不写
- 返回1个:返回值中的()可以省略不写
- 返回多个:返回值中的()不可省略
- 如果有返回值不想接收。可使用
_可忽略
注意事项
- 函数名相同,形参列表不同叫重载,但Golang中不支持重载
- Golang中支持可变参数(如果你希望函数带有可变数量的参数)
//定义一个函数,函数的参数为:可变参数 ... 参数的数量可变
// args...int 可以传入任意多个数量的int类型的数据,传入0个,1个。。。。n个
func test(args...int){
//函数内部处理可变参数的时候。将可变参数当做切片来出来
for i:=0;i <len(args);i++ {
fmt.Println(args[i])
}
}
- 基本数据类型和数组默认都是值传递的。即进行值拷贝,在函数内修改不会影响原来的值
- 以值传递方式的数据类型,如果希望在函数内的数量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量,以效果来看类似引用传递
//参数的类型为指针
func test(num *int){
//对地址对应的变量进行改变数值
*num = 30
}
func main(){
var num int = 10
fmt.Println(&num)
test(&num)
fmt.Println(num)
}
- 函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。
func test(num int) {
fmt.Println(num)
}
func main() {
// 函数也是一种数据类型,可以赋值给一个变量
a := test //变量就是一个函数类型的变量
fmt.Printf("a的类型是:%T,test函数的类型是:%T\n",a, test)
// 通过变量可以对函数调用
a(10) // 等价于 test(10)
}
- 函数既然是一种数据类型,因此在Go中也可以作为形参,并且调用(把函数本身当做一种数据类型)
func test02(num1 int,num2 float32,testFunc func(int)) {
fmt.Println("------test02")
}
func main() {
// 函数也是一种数据类型,可以赋值给一个变量
a := test //变量就是一个函数类型的变量
fmt.Printf("a的类型是:%T,test函数的类型是:%T\n",a, test)
// 通过变量可以对函数调用
a(10) // 等价于 test(10)
//调用test02函数
test02(10,3.19, test)
test02(10,3.19, a)
}
- 为了简化数据类型定义,GO支持自定义数据类型
基本语法:type 自定义数据类型 数据类型
可以理解为起了一个别名
type myFunc func(int)
func test03(num1 int,num2 float32,testFunc myFunc){
fmt.Println("------test02")
}
func main(){
var num1 myInt = 30
fmt.Println("num1为", num1)
var num2 int = 30
num2 = int(num1) // 虽然是别名,但在go中编译识别时候还是认为myInt和int不是同一个数据类型
fmt.Println("num1为", num2)
test03(10,9.8, a)
}
- GO语言里支持对返回值进行定义
- 传统写法要求:返回值和返回值的类型对应,顺序不能差
// 定义一个函数:求两个数的和、差
func test04(num1 int, num2 int)(int,int) {
result01 := num1 + num2
result02 := num1 - num2
return result01, result02
}
* 返回值定义的则无需关心顺序
// 定义一个函数:求两个数的和、差
func test05(num1 int, num2 int)(sum int,sub int) {
sum = num1 + num2
sub = num1 - num2
return
}
调用案例
func cal(num1 int, num2 int) (int,int) {
var sum int = 0
sum += num1
sum += num2
var result int = num1 - num2
return sum,result
}
func cal(num1 int, num2 int) int { //如果返回值类型就一个,那括号可省略不写。
var sum int = 0
sum += num1
sum += num2
return sum
}
func main() {
sum, result := cal(20,10)
fmt.Println(sum)
}
内存分析
栈、堆、代码区
栈:通常会放基本数据类型
- 为每个函数开辟栈帧(开辟空间)
- 在函数执行后,程序会销毁函数对应开辟的栈空间(栈帧)
堆:通常会放复杂数据类型
代码区:通常会放代码本身
包的引入
使用包的原因
- 我们不可能把所有函数放到同一个源文件中,可以分门别类的把函数放在不同的分类中
- 解决同名问题:两个人都想定义一个同名的函数,在同一个文件中是不可以定义相同名字的函数,但可以用包来区分。
package main //1.package进行包的声明,建议:包的声明和这个包所在的文件夹同名
// 2.main包是程序的入口包,一般main函数会放在这个包下
//import "fmt"
// 3. 包名是从$GOPATH/src后开始计算的。使用/进行路径分隔。
//import "testproject01/dbutils"
// 5.如果有多个包。可以一次性导入
import (
"fmt"
"testproject01/dbutils"
)
func main() {
fmt.Println("你好,这是main函数的这行")
dbutils.GetConn() //4.在函数调用的时候前面要定位到所在的包
}
包的细节
- package进行包的声明,建议:包的声明这个包和所在的文件夹同名
- main包是程序的入口包,一般main函数会放在这个包下。
- main函数已经要在main包下,否则不能编译执行
- 打包语法:
package 包名形式 - 引入包的语法:
import 包的路径- 包名是从
$GOPATH/src后开始计算的。使用/进行路径分隔。
- 包名是从
- 如果有多个包建议一次性导入,格式如下
import(
"fmt"
"testproject01/dbutils"
)
- 在函数调用的时候前面要定位到所在的包
- 函数名、变量名首字母大写,才可被其他包访问
- 一个目录下不能有重复的函数,即使是不同文件下,但他们在同一个目录下。如果存在相同的函数名。则会报错。
- 包名和文件夹的名字,可以不一样。import引入的是包所在的文件夹的路径,函数前的定位用的是包名
- 一个目录下的同级文件归属于一个包
同级别的源文件的包的声明必须一致 - 包到底是什么
- 在程序层面,所有使用相同
package 包名的源文件组成的代码模块 - 在源文件层面就是一个文件夹
- 在程序层面,所有使用相同
- 可以给包取别名,取别名后,原来的包名就不能使用了
init函数
- init函数:初始化函数,可以用来进行一些初始化的操作
- 每一个源文件都可以包含一个init函数,该函数会在main函数前,被GO运行框架调用
- 全局变量定义、init函数、main函数都存在时是怎样的执行过程
- 全局变量定义-->init函数-->main函数
- 多个源文件都有init函数时,执行过程
- 先执行main函数中import的包,包中如果有init函数先执行
- 以此类推,如果import的包中还有import的包则继续执行
- 最后才是当前main函数下的init函数
匿名函数
- Go支持匿名函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数
- 匿名函数使用方式:
- 在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次(用的多)
import "fmt" func main() { //定义匿名函数 result := func (num1 int,num2 int)(int){ return num1 + num2 }(10, 20) fmt.Println(result) }- 将匿名函数赋值给一个变量(该变量就是函数变量了),再通过该变量来调用匿名函数,可重复调(用的比较少)
func main() { //定义匿名函数 result := func (num1 int,num2 int)(int){ return num1 + num2 }(10, 20) fmt.Println(result) // 将匿名函数赋给变量,这个变量实际就是函数类型为int sub := func (num1 int,num2 int)(int){ return num1 - num2 } result01 :=sub(30, 70) fmt.Println(result01) }- 如何让一个匿名函数,可以将匿名函数给一个全局变量就可以了。
闭包
什么是闭包
- 闭包就是一个函数和其相关的引用环境组合的一个整体
- 案例
// 函数功能:求和
// 函数的名字: getSum 参数为空
// getSum函数返回值为一个函数,这个函数的参数是一个int类型的参数,返回值也是int类型
func getSum() func (int) int{
var sum int = 0
return func(num int) int {
sum += num
return sum
}
}
//闭包:返回的匿名函数+匿名函数以外的变量num
func main() {
f := getSum()
fmt.Println(f(1))//1
fmt.Println(f(2))//3
fmt.Println(f(3))//6
fmt.Println(f(4))//10
}
总结:匿名函数中引用的变量会一直保存在内存中,可以一直使用
闭包的本质
- 闭包的本质依旧是一个匿名函数,只是这个函数引入外界的变量/参数即:
匿名函数+引用的变量/参数=闭包
闭包的特点:
- 返回的是一个匿名函数,但是这个匿名函数引用到函数外的变量/参数,因此这个匿名函数就和变量/参数形成一个整体,构成闭包。
- 闭包中使用的变量/参数会一直保存在内存中,所以会一直使用--> 意味着闭包不可滥用(对内存消耗大)
- 不使用闭包时候:想保留的值,不可以反复使用
- 闭包应用场景:闭包可以保留上次引用的某个值,传一次即可反复使用
defer关键字
- 在函数中,程序员经常需要创建资源,为了在函数执行完毕后,及时释放资源。Go的设计者提供defer关键字
- 案例
func main() {
fmt.Println(add(30,60))
}
func add(num1 int,num2 int) int {
//在Golang中,程序遇到defer关键字,不会立即执行defer后的语句,而是将defer后的语句压入一个栈中,继续执行函数后面的语句。
defer fmt.Println("num1=", num1)
defer fmt.Println("num2=", num1)
// 栈的特点是:先进后出
// 在函数执行完毕后,从栈中取出语句开始执行,按照先进后出的规则执行语句
var sum int = num1 + num2
fmt.Println("sum=", sum)
return sum
}
- 发现defer关键字,会将后面的代码语句压入栈中,也会将相关的值同时拷贝到栈中,不会随着函数后面的变化而变化
- 案例
func main() {
fmt.Println(add(30,60))
}
func add(num1 int,num2 int) int {
//在Golang中,程序遇到defer关键字,不会立即执行defer后的语句,而是将defer后的语句压入一个栈中,继续执行函数后面的语句。
defer fmt.Println("num1=", num1)
defer fmt.Println("num2=", num2)
num1 += 90 //num1:120
num2 += 50 //num2:110
// 栈的特点是:先进后出
// 在函数执行完毕后,从栈中取出语句开始执行,按照先进后出的规则执行语句
var sum int = num1 + num2
fmt.Println("sum=", sum)
return sum
}
defer应用场景
如:关闭某个使用的资源,在使用的时候直接随手defer,因为defer有延迟执行机制(函数执行完毕再执行defer压入栈的语句,所以用完立即关闭,比较省心,省事
系统函数
字符串相关函数
- 内置函数不用导包,直接用即可。
- 案例
import (
"fmt"
"strconv"
"strings"
)
func main() {
//1.统计字符串长度
str := "你好Golang" //在golang中,汉字是utf-8字符集,一个汉字3个字节
fmt.Println(len(str))
//2.对字符串遍历
r:=[]rune(str) //转成切片
for i := 0; i < len(r); i++ {
fmt.Printf("%c\n",r[i])
}
//3.字符串转整数
num1, _ := strconv.Atoi("666")
fmt.Println(num1)
//4. 整数转字符串
str1 := strconv.Itoa(88)
fmt.Println(str1)
//5.查找子串是否在指定的字符串中
strings.Contains("javaandgolang", "go")
//6.统计一个字符串有几个指定的字符串
count := strings.Count("golangandjava", "ga")
fmt.Println(count)
//7.不区分大小写的字符串比较
flag := strings.EqualFold("hello", "HELLO")
fmt.Println(flag)
//区分大小写的字符串比较
fmt.Println("hello"=="Hello")
//8.返回子串在字符串中第一次出现的索引值,存在返回索引值,不存在返回-1
index := strings.Index("golangandjavaga","ga")
fmt.Println(index)
//9.字符串的替换
str2 := strings.Replace("goandjavagogo","go","golang",-1)
str3 := strings.Replace("goandjavagogo","go","golang",2)
fmt.Println(str2)
fmt.Println(str3)
//n 可以指定要替换的次数
//10.按照指定字符进行切割,返回一个字符串数组
arr := strings.Split("go-python-java","-")
fmt.Println(arr)
//11.将字符串字母进行大小写
fmt.Println(strings.ToLower("Go"))
fmt.Println(strings.ToUpper("Go"))
//12.字符串左右两边空格去除
fmt.Println(strings.TrimSpace(" go and java "))
//13.将字符串左右指定的字符去除
fmt.Println(strings.Trim("~golang~","~"))
//14.将字符串左边的字符去掉
fmt.Println(strings.TrimLeft("~golang~","~"))
//15.将字符串右边的字符去掉
fmt.Println(strings.TrimRight("~golang~","~"))
//16.判断字符串是否以指定的字符串开头
fmt.Println(strings.HasPrefix("http://localhost", "http"))
//16.判断字符串是否以指定的字符串结尾
fmt.Println(strings.HasSuffix("test.png", "jpg"))
}
日期和时间相关函数
- 时间相关函数可以使用
time包 - 案例
import (
"fmt"
"time"
)
func main() {
now := time.Now()
//Now()返回的值是一个结构体,类型是:time.Time
fmt.Printf("%v ~~~~类型为%T\n",now,now)
//调用结构体中的方法:
fmt.Printf("年:%v\n",now.Year())
fmt.Printf("月:%v\n",int(now.Month())) //月就变成数字,默认是
fmt.Printf("日:%v\n",now.Day())
fmt.Printf("时:%v\n",now.Hour())
fmt.Printf("分:%v\n",now.Minute())
fmt.Printf("秒:%v\n",now.Second())
fmt.Println("--------------------1-----------------------------")
//将字符串直接输出
fmt.Printf("当前年月日:%d-%d-%d 时分秒:%d-%d-%\n",now.Year(),now.Month(),now.Day(),now.Hour(),
now.Minute(),now.Second())
//Sprintf可以得到这个字符串以便后续使用。
dateStr := fmt.Sprintf("当前年月日:%d-%d-%d 时分秒:%d-%d-%d",now.Year(),now.Month(),now.Day(),now.Hour(),
now.Minute(),now.Second())
fmt.Println(dateStr)
fmt.Println("----------------------2---------------------------")
//这个参数字符串的各个数字必须是固定的,必须这样写
dateStr2 := now.Format("2006/01.02 15/04/05")
fmt.Println(dateStr2)
//选择任意的组合都是可以的,根据需求自己选择
dateStr3 := now.Format("2006 01.02")
fmt.Println(dateStr3)
}
内置函数
什么是内置函数
- Golang设计者为了编程方便,提供了一些函数,这些函数不用导包可以直接使用,我们称为Go的内置函数/内建函数
内置函数存放位置
- builtin包下,直接用即可,不用引包
常用函数
- len函数:统计字符串长度
- new函数:用于分配内存,主要用来分配值类型(int系列、float系列、bool、string、数组和结构体struct)
func main() {
//new分配内存,new函数的实参是一个类型而不是具体是数值,返回值是对应类型的指针num: *int
num := new(int)
fmt.Printf("num的类型是:%T,num的值是:%v,num地址是:%v,num指针指向的值是:%v", num,num,&num,*num)
}
- make函数:用于分配内存,主要用来分配引用类型(指针、slice切片、map、管道chan、interface等)