Golang--函数、包、defer、系统函数、内置函数

时间:2024-11-04 07:19:00

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 int
        t = num1
        num1 = num2
        num2 = t
}
func main(){	
        //调用函数:交换10和20
        var num1 int = 10
        var num2 int = 20
        fmt.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 := 0
	for _,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 += 10
		fmt.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)myInt

func add(a,b myInt)myInt{
	return a + b
}

func main(){
	var a myAdd
	a = add
	fmt.Println(a(1,2))
}

2.10 支持对函数返回值命名

传统写法要求:返回值和返回值的类型对应,顺序不能差
升级写法:对函数返回值命名,里面顺序就无所谓了,顺序不用对应

package main
import "fmt"

//传统函数返回值写法
func f()(int, string){
	nun := 1
	str := "hello golang"
	//返回值和返回值类型需要一致,顺序需要一致
	return nun,str
}

//函数返回值命名写法
func f1()(num int,str string){
	num = 1
	str = "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)  //0
    fmt.Println("Monday:", Monday)  //1
    fmt.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(
            //"包名"
            //"包名"
    )
  • 在函数调用的时候前面要定位到所在的包
  • 函数名,变量名首字母大写,函数,变量可以被其它包访问
  • 一个目录下不能有重复的函数(归属于同一个包)
  • 包名和文件夹的名字,可以不一样
  • 一个目录下的同级文件归属一个包同级别的源文件的包的声明必须一致
  • 可以给包取别名,取别名后,原来的包名就不能使用了


包是什么:

  1. 在程序层面,所有使用相同  package 包名  的源文件组成的代码模块
  2. 在源文件层面就是一个文件夹

4、init函数

init函数:初始化函数,可以用来进行一些初始化的操作
每一个源文件
都可以包含一个init函数该函数会在main函数执行前,被Go运行框架调用。


执行流程:
全局变量定义  >  init函数  >  main函数

 多个源文件都有init函数的时候,如何执行:

 

 5、匿名函数

  • Go支持匿名函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数
  • 匿名函数使用方式:
  1. 定义匿名函数时就直接调用,这种方式匿名函数只能调用一次(用的多)
  2.  将匿名函数赋给一个变量(该变量就是函数变量了),再通过该变量来调用匿名函数(用的少)
  3.  如何让一个匿名函数,可以在整个程序中有效呢?将匿名函数给一个全局变量就可以了
    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 := 0
	return func (num int) int{
		sum += num
		return sum
	}
}

func main(){
	f := getSum()
	fmt.Println(f(10)) //10
	fmt.Println(f(20)) //30
	fmt.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-range
	for _,ch := range str{
		fmt.Printf("%c",ch)
	}
	fmt.Println()
	//len
	for 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.Time
        fmt.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())//月:February
        fmt.Printf("月:%v \n",int(now.Month()))//月:2
        fmt.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中国