searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Go语言学习笔记——函数

2023-10-07 05:52:47
3
0

函数

基本语法

func 函数名 (形参列表) (返回值列表) {
    执行语句...
    return 返回值列表
}

函数可以有返回值,也可以没有

包的引入

包的本质实际上就是创建不同的文件夹,来存放程序文件

go的每一个文件都是属于一个包的,也就是说go是以包的形式来管理文件和项目目录结构的

包的作用

  1. 区分相同名字的函数、变量等标识符

  2. 当程序文件很多时,可以很好的管理项目

  3. 控制函数、变量等访问范围,即作用域

包的基本语法

打包:package 包名

引入包:import "包的路径"

示例:

//main.go
package main
import(
	"fmt"
	"go_code/project2/utils"
)
func main(){
	var n1 float64 =1.2
	var n2 float64 =3.2
	var operator byte ='-'
	res :=utils.Cal(n1,n2,operator) 
	fmt.Printf("res=%T, res=%f",res,res)
}
//utils.go
package utils
import(
	"fmt"
)

func Cal(n1 float64, n2 float64, operator byte) (float64){  //Cal要大写
	var res float64
	switch operator{
	case '+': 
		res=n1+n2
	case '-': 
		res=n1-n2
	case '*': 
		res=n1*n2
	case '/':
		res=n1/n2
	default : 
		fmt.Println("操作符错误")
	}
	return res
}
  • 由于go是以包的形式来管理文件和项目目录结构的,则一个包就是一个文件夹,因此go的每个文件都要属于一个文件夹(包)

  • 包的路径:环境变量${GOPATH}/src开始,到包的文件夹

包的注意事项

  1. 在给一个文件打包时,该包对应一个文件夹。比如utils文件夹对应的包名就是utils,文件的包名通常和文件所在的文件夹名一致,一般为小写字母。

  2. 当一个文件要使用其他包函数或变量时,需要先引入对应的包

    1. package在第一行,然后是import

    2. 在import包时,路径从$GOPATH的src下开始,不用带src,编译器会自动从src下开始引入

  3. 为了让其他包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其他语言的public,这样才能跨包访问。

  4. 在访问其他包函数时,语法是包名.函数名

  5. 如果包名较长,Go支持给包取别名,取别名后,原来的包名就不能使用了

    import(
    	"fmt"
    	test_pac "go_code/project2/utils"
    )
    func main(){
    	var n1 float64 =1.2
    	var n2 float64 =3.2
    	var operator byte ='-'
    	res :=test_pac.Cal(n1,n2,operator) 
    	fmt.Printf("res=%T, res=%.2f",res,res)
    }
  6. 在同一个包下,不能有相同的函数名,否则报重复定义

  7. 如果要编译成一个可执行文件,就需要将这个包声明为main,即package main。这个就是一个语法规范

    编译时需要编译main包所在的文件夹

    编译后生成一个有默认名的可执行文件,在$GOPATH目录下,可以指定名字和目录,比如:放在bin目录下:go build -o bin/my.exe go_code/project/main。(路径默认带src)

  8. 编译完后会生成一个和src同级的文件夹pkg,里面包含一个.a的库文件

return语句

  1. 如果返回多个值时,在接收时,希望忽略某个返回值,则使用_符号表示占位忽略

  2. 如果返回值只有一个,(返回值类型列表)可以不写()

函数注意事项和细节

  1. 函数的形参列表可以是多个,返回值列表也可以是多个

  2. 形参列表和返回值列表的数据类型可以是值类型和引用类型。

  3. 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其他包文件使用,类似public,首字母小写,只能被本包文件使用,其他包文件不能使用,类似private

  4. 函数中的变量是局部的,函数外不生效

  5. 基本数据类型和数组默认都是值传递,即进行值拷贝。在函数内修改,不会影响到原来的值

  6. 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用

  7. Go函数不支持重载!

  8. 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。

    func getSum(n1 int, n2 int) int {
    	return n1 + n2
    }
    func main(){
    	a := getSum
    	fmt.Printf("a的类型%T,getSum类型是%T\n",a,getSum)
    
    	res := a(10,40)//等价于 res:= getSum(10,40)
    	fmt.Println("res=",res)
    }
  9. 函数既然是一种数据类型,那么在Go中,函数可以作为形参,并且调用
    func getSum_Sub(n1 int, n2 int) (sum int, sub int) {
    	sum =n1 + n2
    	sub =n1 - n2
    	return 
    }
    func myFun(funvar func(int,int) (int,int), num1 int, num2 int) (int , int ){
    	return funvar(num1, num2)
    }
    func main(){
    	a := getSum_Sub
    	fmt.Printf("a的类型%T,getSum类型是%T\n",a,getSum_Sub)
    
    	res,_ := a(10,40)//等价于 res:= getSum(10,40)
    	fmt.Println("res=",res)
    
    	b:=myFun
    	fmt.Printf("b的类型%T,myFun类型是%T\n",b,myFun)
    
    	sum,sub:=b(getSum_Sub,50,20)
    	fmt.Println("sum=",sum,"sub=",sub)
    }
  10. 为了简化数据类型定义,Go支持自定义数据类型

    基本语法:type 自定义数据类型名 数据类型 (相当于一个别名,相当于typename)。但是Go语言会认为这两个是不同类型

    //自定义变量类型
    type myInt int
    var num1 myInt
    var num2 int
    num1 =40
    num2= int(num1)//需要显示转换成int,因为Go认为两个是不同类型
    //自定义函数类型
    func getSum_Sub(n1 int, n2 int) (sum int, sub int) {
    	sum =n1 + n2
    	sub =n1 - n2
    	return 
    }
    
    type mySum func(int,int)(int,int)
    
    func myFun(funvar mySum, num1 int, num2 int) (int , int ){
    	return funvar(num1, num2)
    }
  11. 支持对函数返回值命名
    func cal(n1 int,n2 int)(sum int, sub int){
    	sum = n1 + n2
        sub = n1 - n2
        return
    }
  12. 使用_标识符,忽略返回值

  13. Go中支持可变参数

    //支持0到多个参数
    func sum(args... int) (sum int){
        
    }
    //支持1到多个参数
    func sum(n1 int, args... int) (sum int){
        
    }
    1. args是slice切片,通过args[index] 可以访问到各个值。

    2. 如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后

      func sum_test(n1 int, args... int) (sum int) {
      	sum =n1
      	//遍历args
      	for i:=0;i<len(args);i++ {
      		sum += args[i] //args[0]表示取出args切片的第一个元素值,其他依此类推
      	}
      	return 
      }
      func main(){
          res2:= sum_test(10)
      	fmt.Println("res2=",res2)
      
      	res3 := sum_test(1,2,3,4,5)
      	fmt.Println("res3=",res3)
      }

init函数

每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用,也就是说init会在main函数前被调用。

通常可以在init函数中,完成初始化工作

注意事项和细节

  1. 如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程是变量定义->init函数->main函数

  2. init函数最主要的作用,就是完成一些初始化工作。可以在引入包中进行初始化,这样main执行时,先执行包的初始化

如果main.go和utils.go都含有变量定义,init函数时,执行流程如下:

名函数

Go支持匿名函数,如果某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用。

使用方式

  1. 在定义匿名函数时直接使用

    func main(){
    	res1 := func (n1 int,n2 int) int {
    		return n1+n2
        }(10,20)  //定义到}为止,后面(10,20)直接调用
    
    	fmt.Println("res1=",res1)
    }
  2. 将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
    	a := func (n1 int,n2 int) int {
    		return n1+n2
    	}
    	res1:=a(1,2)
    	fmt.Println("res1=",res1)
  3. 全局匿名函数

    如果将匿名函数赋值给一个全局变量,那么这个匿名函数,就称为一个全局匿名函数,可以在程序有效

闭包

基本介绍:闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)

//累加器
func AddUpper() func(int) int {
	var n int =10
	return func(x int) int{
		n+=x
		return n
	}
}

func main(){
	a:=AddUpper()
	var res int
	res=a(1)
	fmt.Println("res=",res)
	res=a(2)
	fmt.Println("res=",res)
}

上面代码说明:

  1. AddUpper是一个函数,返回的数据类型是func(int) int

  2. 闭包说明

    var n int =10
    return func(x int) int{
    	n+=x
    	return n
    }

    AddUpper返回的是一个匿名函数,但是这个匿名函数引用到函数外的n。因此这个匿名函数就和n形成一个整体,构成闭包。

  3. 可以理解为:闭包是类,函数是操作,n是字段。函数和它使用到的n构成闭包

  4. 当我们反复的调用f函数时,因为n初始化一次,因此每次调用n都会进行累加

  5. 要搞清楚闭包的关键,就是要分析出返回的函数使用到哪些变量。因为函数和它引用到的变量共同构成闭包

Go语言能通过escape analyze识别出变量的作用域,自动将变量在堆上分配。

//由于n1没有形成闭包,因此会在栈上创建,而n在堆上
func AddUpper() func(int) int {//类型为func() func(int) int
	var n int =10  //堆上
	var n1 int =20 //栈上
	return func(x int) int{
		n+=x
		fmt.Println("n=",n,"n1=",n1)
		return n
	}
}
a:=AddUpper()//这里不能省略这个(),类似于类的构造函数。称作返回一个闭包 此时a的类型是func(int) int
	res=a(1)
	fmt.Println("res=",res)//n= 11 n1= 20 res= 11
	res=a(1)
	fmt.Println("res=",res)//n= 12 n1= 20 res= 12

示例:

func makeSuffix(suffix string) func (string) string{
	return func (name string) string {
		//如果name没有指定后缀,则加上,否则就返回原来的名字
		if !strings.HasSuffix(name ,suffix){
			return name + suffix
		}
		return name

	}
}


func main(){
	f:=makeSuffix(".jpg")
	fmt.Println("output:",f("a.jpg"))
	fmt.Println("output:",f("b"))
}

传统函数虽然也能实现,但是每次都需要重新传入后缀名。用闭包可以像类一样确定好就不用再写了

defer

在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时释放资源,Go的设计者提供defer(延时机制)

func sum (n1 int,n2 int) int{
	//当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈)
	//当函数执行完毕后,再从defer栈,按照先入后出的方式出栈,执行
	defer fmt.Println("ok1 n1=",n1)
	defer fmt.Println("ok2 n2=",n2)
    n1++
	n2++
	res := n1 +n2
	fmt.Println("ok3 res=",res)
	return res
}

func main(){
	res := sum(10,20)
	fmt.Println("res=",res)
}

结果:

ok3 res= 32
ok2 n2= 20
ok1 n1= 10
res= 32

defer的细节说明

  1. 当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈,然后继续执行函数下一个语句。

  2. 当函数执行完毕后,再从defer栈中,依次从栈顶取出语句执行(遵从先入后出)

  3. 在defer将语句放入到栈时,也会将相关的值拷贝同时入栈

主要价值

defer最主要的价值在于,当函数执行完毕后,可以及时的释放函数创建的资源。

func test(){
	file = openfile("xxx")
	defer file.close()
}

参数传递方式

  • 值传递:复制变量

  • 引用传递:相当于C++的地址传递,复制地址

变量作用域

和c++差不多

注意:

var Age int =20//ok
Name := "tom" // 报错:相当于 var Name string  Name = "tom"。赋值语句不能在函数体外执行
func main(){
    fmt.Println("name",Name)
}

可以在函数体外进行全局变量定义及初始化,但是不能赋值

var Age int =20//ok
var Name string //ok

//不能赋值
Name = "tom"//不行
Test:= "tom"//同样不行

字符串常用的系统函数

  1. 统计字符串的长度,按字节len(str)。中文字符3字节,比如hello北,输出为8

  2. 字符串遍历,同时处理有中文的问题 r:=[]rune(str)

  3. 字符串转整数:n,err := strconv.Atoi("12")

    n,err := strconv.Atoi("123")
    if err!= nil {
        fmt.Println("转换错误",err)
    }else {
        fmt.Println("转成的结果是",n)
    }
  4. 整数转字符串:str = strconv.Itoa(12345)

  5. 字符串转[]byte
    var bytes = []byte("hello go")
    fmt.Printf("bytes=%v\n",bytes)
  6. []byte转字符串
    str = string([]byte{97,98,99})//对应结果为abc(对应ascii码)
  7. 10进制转2,8,16进制(字符串):
    str := strconv.FormatInt(123,2)
    fmt.Printf("123对应的二进制是=%v\n",str)
    str = strconv.FormatInt(123,16)
    fmt.Printf("123对应的16进制是=%v\n",str)
  8. 查找子串是否在指定的字符串中,返回bool
    b:=strings.Contains("seafood","foo")
    fmt.Printf("是否含有=%v\n",b)
  9. 统计一个字符串有几个指定的子串:
    num := strings.Count("ceheEs","e")
    fmt.Printf("num=%v",num)//结果:2
  10. 不区分大小写的字符串比较(==是区分字母大小写的)
    b = strings.EqualFold("abc","Abc")
    fmt.Printf("b=%v\n",b)//true
  11. 返回子串在字符串第一次出现的index值,如果没有,返回-1
    index := strings.Index("NLT_abc","abc")
    fmt.Printf("index=%v\n",index)//4
  12. 返回子串在字符串最后一次出现的index,如没有,返回-1
    index := strings.LastIndex("go golang","go")
    fmt.Printf("lasindex=%v\n",index)//3
  13. 将指定的子串替换成另外一个子串:func Replace(s, old, new string, n int) stringn可以指定你希望替换几个,如果n=-1表示全部替换)
    str := strings.Replace("go golang","go","北京",-1)//返回一个新串
    fmt.Printf("str=%v\n",str)
  14. 按照指定的某个字符(如,)为分割标识,将一个字符串拆分成字符串数组
    strArr := strings.Split("hello,world,ok",",")
    fmt.Printf("strArr=%v\n",strArr)
  15. 将字符串的字母进行大小写转换:
    str="Hello goLang"
    str=strings.ToLower(str)//转小写,ToUpper转大写
    fmt.Printf("str=%v\n",str)//hello golang
  16. 将字符串左右两边的空格去掉:
    str=strings.TrimSpace("    Hello goLang         ")
    fmt.Printf("str=%q\n",str)//%q 会加上双引号:"Hello goLang"
  17. 将字符串左右两边指定的字符去掉:
    str=strings.Trim("!   ! Hello goLang!  !!"," !")
    fmt.Printf("str=%q\n",str)
  18. 去掉左边(或右边):strings.TrimLeftstrings.TrimRight

  19. 判断字符串是否以指定的字符串开头:
    b=strings.HasPrefix("ftp://192.168.10.1","ftp")
    fmt.Printf("b=%v\n",b)
  20. 判断字符串是否以指定的字符串结束:strings.HasSuffix

时间和日期相关函数

  1. 需要导入time

  2. time.Time类型,用来表示时间

    now := time.Now()//获取当前时间
    fmt.Printf("Now: %v,类型%T",now,now)
    //Now: 2022-08-03 15:47:48.2543597 +0800 CST m=+0.003611801,类型time.Time
  3. 如何获取到其他日期信息
    //通过now可以获取到年月日,时分秒
    fmt.Printf("年: %v\n",now.Year())		//年: 2022
    fmt.Printf("月: %v\n",now.Month())		//月: August	
    fmt.Printf("月: %v\n",int(now.Month()))	//月: 8
    fmt.Printf("日: %v\n",now.Day())			//日: 3
    fmt.Printf("时: %v\n",now.Hour())		//时: 15
    fmt.Printf("分: %v\n",now.Minute())		//分: 53
    fmt.Printf("秒: %v\n",now.Second())		//秒: 0
  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())
    
    dateStr:= fmt.Sprintf("当前年月日 %d-%d-%d  %d:%d:%d",now.Year(),now.Month(),now.Day(),
    now.Hour(),now.Minute(),now.Second())
    fmt.Println(dateStr)
    //当前年月日 2022-8-3  15:58:19
    //当前年月日 2022-8-3  15:58:19

    方式2:fmt.Printf(now.Format("2006/01/02 15:04:05"))

    这个时间是固定的(间隔符可以改变),必须这么写。但是可以自由组合,只显示日期,或者时间

    fmt.Printf(now.Format("2006/01/02 15:04:05"))
    fmt.Println()
    fmt.Printf(now.Format("2006-01-02"))
    fmt.Println()
    fmt.Printf(now.Format("15 04 05"))
    fmt.Println()
    /*
    2022/08/03 16:02:19
    2022-08-03
    16 02 19
    */
  5. 时间的常量

    定义在time包中

    const (
        Nanosecond  Duration = 1//纳秒
        Microsecond          = 1000 * Nanosecond//微秒
        Millisecond          = 1000 * Microsecond//毫秒
        Second               = 1000 * Millisecond//秒
        Minute               = 60 * Second//分钟
        Hour                 = 60 * Minute//小时
    )
    //使用
    100*time.Millisecond
  6. 结合使用sleep()使用常量
    i:=0
    for {
    	i++
    	fmt.Println(i)
    	//休眠
    	time.Sleep(time.Millisecond*20)//只能乘以大于1的整数,不能小于或者除
        if i==10{
            break
        }
    }
  7. Unix时间戳和Unixnano时间戳(作用是可以获取随机数字)

    下面的形式叫方法,与函数有一些区别

    fmt.Printf("Unix:%v,UnixNano:%v\n",now.Unix(),now.UnixNano())
    //Unix:1659514601,UnixNano:1659514601927962800
  8. 获取间隔秒数
    start := time.Now().Unix()
    test()
    end:= time.Now().Unix()
    fmt.Printf("执行test()耗费的时间为%v秒",end-start)

内置函数

  1. len

  2. new:用来分配内存,主要用来分配值类型,如int,float64,返回指针

    num1 :=100
    fmt.Printf("num1的类型%T, 值=%v, 地址 %v\n",num1,num1,&num1)
    
    num2 := new(int)
    *num2 =200
    fmt.Printf("num2的类型%T, 值=%v, 地址 %v ,存的值=%v\n",num2,num2,&num2,*num2)
    /*
    num1的类型int, 值=100, 地址 0xc000014088
    num2的类型*int, 值=0xc0000140c0, 地址 0xc000006030  存的值=200    //因为是指针,所以值是地址
    */

  3. make:用来分配内存,主要用来分配引用类型,比如channelmapslice

错误处理机制

  1. 在默认情况下,当发生错误后(panic),程序就会退出

  2. 如果希望发生错误时,可以捕获到错误,并进行处理,保证程序可以继续执行。还可以在捕获到错误后,给管理员一个提示

基本说明

  1. 不支持try...catch...finally

  2. Go处理方式:defer,panic,recover

  3. Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理

    package main
    import(
    	"fmt"
    	"time"
    )
    
    func test(){
    	//使用defer + recover 来捕获和处理异常
    	defer func(x int){
    		err := recover() //recover()内置函数,可以捕获到异常
    		if  err != nil { //说明捕获到错误
    			fmt.Println("err=",err,x)
    			//这里就可以将错误信息发送给管理员...
    			fmt.Println("发送给管理员")
    		}
    	}(1)//匿名函数调用加个()就行,因为此函数不用参数,如果有参数就填入参数
    	num1 :=10
    	num2 :=0
    	res := num1/num2
    	fmt.Println("res=",res)
    }
    func main(){
    	for{
    		test()
    		fmt.Println("main()下面的代码...")
    		time.Sleep(time.Second)
    	}
    }

自定义错误

Go程序中,也支持自定义错误,使用erroes.New和panic内置函数

  1. errors.New("错误说明")。会返回一个error类型的值,表示一个错误

  2. 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.Println("test02继续执行....")
    }
    func main(){
    	test02()
    	fmt.Println("main()下面的代码...")
    }
0条评论
0 / 1000
y****n
4文章数
0粉丝数
y****n
4 文章 | 0 粉丝
原创

Go语言学习笔记——函数

2023-10-07 05:52:47
3
0

函数

基本语法

func 函数名 (形参列表) (返回值列表) {
    执行语句...
    return 返回值列表
}

函数可以有返回值,也可以没有

包的引入

包的本质实际上就是创建不同的文件夹,来存放程序文件

go的每一个文件都是属于一个包的,也就是说go是以包的形式来管理文件和项目目录结构的

包的作用

  1. 区分相同名字的函数、变量等标识符

  2. 当程序文件很多时,可以很好的管理项目

  3. 控制函数、变量等访问范围,即作用域

包的基本语法

打包:package 包名

引入包:import "包的路径"

示例:

//main.go
package main
import(
	"fmt"
	"go_code/project2/utils"
)
func main(){
	var n1 float64 =1.2
	var n2 float64 =3.2
	var operator byte ='-'
	res :=utils.Cal(n1,n2,operator) 
	fmt.Printf("res=%T, res=%f",res,res)
}
//utils.go
package utils
import(
	"fmt"
)

func Cal(n1 float64, n2 float64, operator byte) (float64){  //Cal要大写
	var res float64
	switch operator{
	case '+': 
		res=n1+n2
	case '-': 
		res=n1-n2
	case '*': 
		res=n1*n2
	case '/':
		res=n1/n2
	default : 
		fmt.Println("操作符错误")
	}
	return res
}
  • 由于go是以包的形式来管理文件和项目目录结构的,则一个包就是一个文件夹,因此go的每个文件都要属于一个文件夹(包)

  • 包的路径:环境变量${GOPATH}/src开始,到包的文件夹

包的注意事项

  1. 在给一个文件打包时,该包对应一个文件夹。比如utils文件夹对应的包名就是utils,文件的包名通常和文件所在的文件夹名一致,一般为小写字母。

  2. 当一个文件要使用其他包函数或变量时,需要先引入对应的包

    1. package在第一行,然后是import

    2. 在import包时,路径从$GOPATH的src下开始,不用带src,编译器会自动从src下开始引入

  3. 为了让其他包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其他语言的public,这样才能跨包访问。

  4. 在访问其他包函数时,语法是包名.函数名

  5. 如果包名较长,Go支持给包取别名,取别名后,原来的包名就不能使用了

    import(
    	"fmt"
    	test_pac "go_code/project2/utils"
    )
    func main(){
    	var n1 float64 =1.2
    	var n2 float64 =3.2
    	var operator byte ='-'
    	res :=test_pac.Cal(n1,n2,operator) 
    	fmt.Printf("res=%T, res=%.2f",res,res)
    }
  6. 在同一个包下,不能有相同的函数名,否则报重复定义

  7. 如果要编译成一个可执行文件,就需要将这个包声明为main,即package main。这个就是一个语法规范

    编译时需要编译main包所在的文件夹

    编译后生成一个有默认名的可执行文件,在$GOPATH目录下,可以指定名字和目录,比如:放在bin目录下:go build -o bin/my.exe go_code/project/main。(路径默认带src)

  8. 编译完后会生成一个和src同级的文件夹pkg,里面包含一个.a的库文件

return语句

  1. 如果返回多个值时,在接收时,希望忽略某个返回值,则使用_符号表示占位忽略

  2. 如果返回值只有一个,(返回值类型列表)可以不写()

函数注意事项和细节

  1. 函数的形参列表可以是多个,返回值列表也可以是多个

  2. 形参列表和返回值列表的数据类型可以是值类型和引用类型。

  3. 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其他包文件使用,类似public,首字母小写,只能被本包文件使用,其他包文件不能使用,类似private

  4. 函数中的变量是局部的,函数外不生效

  5. 基本数据类型和数组默认都是值传递,即进行值拷贝。在函数内修改,不会影响到原来的值

  6. 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用

  7. Go函数不支持重载!

  8. 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。

    func getSum(n1 int, n2 int) int {
    	return n1 + n2
    }
    func main(){
    	a := getSum
    	fmt.Printf("a的类型%T,getSum类型是%T\n",a,getSum)
    
    	res := a(10,40)//等价于 res:= getSum(10,40)
    	fmt.Println("res=",res)
    }
  9. 函数既然是一种数据类型,那么在Go中,函数可以作为形参,并且调用
    func getSum_Sub(n1 int, n2 int) (sum int, sub int) {
    	sum =n1 + n2
    	sub =n1 - n2
    	return 
    }
    func myFun(funvar func(int,int) (int,int), num1 int, num2 int) (int , int ){
    	return funvar(num1, num2)
    }
    func main(){
    	a := getSum_Sub
    	fmt.Printf("a的类型%T,getSum类型是%T\n",a,getSum_Sub)
    
    	res,_ := a(10,40)//等价于 res:= getSum(10,40)
    	fmt.Println("res=",res)
    
    	b:=myFun
    	fmt.Printf("b的类型%T,myFun类型是%T\n",b,myFun)
    
    	sum,sub:=b(getSum_Sub,50,20)
    	fmt.Println("sum=",sum,"sub=",sub)
    }
  10. 为了简化数据类型定义,Go支持自定义数据类型

    基本语法:type 自定义数据类型名 数据类型 (相当于一个别名,相当于typename)。但是Go语言会认为这两个是不同类型

    //自定义变量类型
    type myInt int
    var num1 myInt
    var num2 int
    num1 =40
    num2= int(num1)//需要显示转换成int,因为Go认为两个是不同类型
    //自定义函数类型
    func getSum_Sub(n1 int, n2 int) (sum int, sub int) {
    	sum =n1 + n2
    	sub =n1 - n2
    	return 
    }
    
    type mySum func(int,int)(int,int)
    
    func myFun(funvar mySum, num1 int, num2 int) (int , int ){
    	return funvar(num1, num2)
    }
  11. 支持对函数返回值命名
    func cal(n1 int,n2 int)(sum int, sub int){
    	sum = n1 + n2
        sub = n1 - n2
        return
    }
  12. 使用_标识符,忽略返回值

  13. Go中支持可变参数

    //支持0到多个参数
    func sum(args... int) (sum int){
        
    }
    //支持1到多个参数
    func sum(n1 int, args... int) (sum int){
        
    }
    1. args是slice切片,通过args[index] 可以访问到各个值。

    2. 如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后

      func sum_test(n1 int, args... int) (sum int) {
      	sum =n1
      	//遍历args
      	for i:=0;i<len(args);i++ {
      		sum += args[i] //args[0]表示取出args切片的第一个元素值,其他依此类推
      	}
      	return 
      }
      func main(){
          res2:= sum_test(10)
      	fmt.Println("res2=",res2)
      
      	res3 := sum_test(1,2,3,4,5)
      	fmt.Println("res3=",res3)
      }

init函数

每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用,也就是说init会在main函数前被调用。

通常可以在init函数中,完成初始化工作

注意事项和细节

  1. 如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程是变量定义->init函数->main函数

  2. init函数最主要的作用,就是完成一些初始化工作。可以在引入包中进行初始化,这样main执行时,先执行包的初始化

如果main.go和utils.go都含有变量定义,init函数时,执行流程如下:

名函数

Go支持匿名函数,如果某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用。

使用方式

  1. 在定义匿名函数时直接使用

    func main(){
    	res1 := func (n1 int,n2 int) int {
    		return n1+n2
        }(10,20)  //定义到}为止,后面(10,20)直接调用
    
    	fmt.Println("res1=",res1)
    }
  2. 将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
    	a := func (n1 int,n2 int) int {
    		return n1+n2
    	}
    	res1:=a(1,2)
    	fmt.Println("res1=",res1)
  3. 全局匿名函数

    如果将匿名函数赋值给一个全局变量,那么这个匿名函数,就称为一个全局匿名函数,可以在程序有效

闭包

基本介绍:闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)

//累加器
func AddUpper() func(int) int {
	var n int =10
	return func(x int) int{
		n+=x
		return n
	}
}

func main(){
	a:=AddUpper()
	var res int
	res=a(1)
	fmt.Println("res=",res)
	res=a(2)
	fmt.Println("res=",res)
}

上面代码说明:

  1. AddUpper是一个函数,返回的数据类型是func(int) int

  2. 闭包说明

    var n int =10
    return func(x int) int{
    	n+=x
    	return n
    }

    AddUpper返回的是一个匿名函数,但是这个匿名函数引用到函数外的n。因此这个匿名函数就和n形成一个整体,构成闭包。

  3. 可以理解为:闭包是类,函数是操作,n是字段。函数和它使用到的n构成闭包

  4. 当我们反复的调用f函数时,因为n初始化一次,因此每次调用n都会进行累加

  5. 要搞清楚闭包的关键,就是要分析出返回的函数使用到哪些变量。因为函数和它引用到的变量共同构成闭包

Go语言能通过escape analyze识别出变量的作用域,自动将变量在堆上分配。

//由于n1没有形成闭包,因此会在栈上创建,而n在堆上
func AddUpper() func(int) int {//类型为func() func(int) int
	var n int =10  //堆上
	var n1 int =20 //栈上
	return func(x int) int{
		n+=x
		fmt.Println("n=",n,"n1=",n1)
		return n
	}
}
a:=AddUpper()//这里不能省略这个(),类似于类的构造函数。称作返回一个闭包 此时a的类型是func(int) int
	res=a(1)
	fmt.Println("res=",res)//n= 11 n1= 20 res= 11
	res=a(1)
	fmt.Println("res=",res)//n= 12 n1= 20 res= 12

示例:

func makeSuffix(suffix string) func (string) string{
	return func (name string) string {
		//如果name没有指定后缀,则加上,否则就返回原来的名字
		if !strings.HasSuffix(name ,suffix){
			return name + suffix
		}
		return name

	}
}


func main(){
	f:=makeSuffix(".jpg")
	fmt.Println("output:",f("a.jpg"))
	fmt.Println("output:",f("b"))
}

传统函数虽然也能实现,但是每次都需要重新传入后缀名。用闭包可以像类一样确定好就不用再写了

defer

在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时释放资源,Go的设计者提供defer(延时机制)

func sum (n1 int,n2 int) int{
	//当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈)
	//当函数执行完毕后,再从defer栈,按照先入后出的方式出栈,执行
	defer fmt.Println("ok1 n1=",n1)
	defer fmt.Println("ok2 n2=",n2)
    n1++
	n2++
	res := n1 +n2
	fmt.Println("ok3 res=",res)
	return res
}

func main(){
	res := sum(10,20)
	fmt.Println("res=",res)
}

结果:

ok3 res= 32
ok2 n2= 20
ok1 n1= 10
res= 32

defer的细节说明

  1. 当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈,然后继续执行函数下一个语句。

  2. 当函数执行完毕后,再从defer栈中,依次从栈顶取出语句执行(遵从先入后出)

  3. 在defer将语句放入到栈时,也会将相关的值拷贝同时入栈

主要价值

defer最主要的价值在于,当函数执行完毕后,可以及时的释放函数创建的资源。

func test(){
	file = openfile("xxx")
	defer file.close()
}

参数传递方式

  • 值传递:复制变量

  • 引用传递:相当于C++的地址传递,复制地址

变量作用域

和c++差不多

注意:

var Age int =20//ok
Name := "tom" // 报错:相当于 var Name string  Name = "tom"。赋值语句不能在函数体外执行
func main(){
    fmt.Println("name",Name)
}

可以在函数体外进行全局变量定义及初始化,但是不能赋值

var Age int =20//ok
var Name string //ok

//不能赋值
Name = "tom"//不行
Test:= "tom"//同样不行

字符串常用的系统函数

  1. 统计字符串的长度,按字节len(str)。中文字符3字节,比如hello北,输出为8

  2. 字符串遍历,同时处理有中文的问题 r:=[]rune(str)

  3. 字符串转整数:n,err := strconv.Atoi("12")

    n,err := strconv.Atoi("123")
    if err!= nil {
        fmt.Println("转换错误",err)
    }else {
        fmt.Println("转成的结果是",n)
    }
  4. 整数转字符串:str = strconv.Itoa(12345)

  5. 字符串转[]byte
    var bytes = []byte("hello go")
    fmt.Printf("bytes=%v\n",bytes)
  6. []byte转字符串
    str = string([]byte{97,98,99})//对应结果为abc(对应ascii码)
  7. 10进制转2,8,16进制(字符串):
    str := strconv.FormatInt(123,2)
    fmt.Printf("123对应的二进制是=%v\n",str)
    str = strconv.FormatInt(123,16)
    fmt.Printf("123对应的16进制是=%v\n",str)
  8. 查找子串是否在指定的字符串中,返回bool
    b:=strings.Contains("seafood","foo")
    fmt.Printf("是否含有=%v\n",b)
  9. 统计一个字符串有几个指定的子串:
    num := strings.Count("ceheEs","e")
    fmt.Printf("num=%v",num)//结果:2
  10. 不区分大小写的字符串比较(==是区分字母大小写的)
    b = strings.EqualFold("abc","Abc")
    fmt.Printf("b=%v\n",b)//true
  11. 返回子串在字符串第一次出现的index值,如果没有,返回-1
    index := strings.Index("NLT_abc","abc")
    fmt.Printf("index=%v\n",index)//4
  12. 返回子串在字符串最后一次出现的index,如没有,返回-1
    index := strings.LastIndex("go golang","go")
    fmt.Printf("lasindex=%v\n",index)//3
  13. 将指定的子串替换成另外一个子串:func Replace(s, old, new string, n int) stringn可以指定你希望替换几个,如果n=-1表示全部替换)
    str := strings.Replace("go golang","go","北京",-1)//返回一个新串
    fmt.Printf("str=%v\n",str)
  14. 按照指定的某个字符(如,)为分割标识,将一个字符串拆分成字符串数组
    strArr := strings.Split("hello,world,ok",",")
    fmt.Printf("strArr=%v\n",strArr)
  15. 将字符串的字母进行大小写转换:
    str="Hello goLang"
    str=strings.ToLower(str)//转小写,ToUpper转大写
    fmt.Printf("str=%v\n",str)//hello golang
  16. 将字符串左右两边的空格去掉:
    str=strings.TrimSpace("    Hello goLang         ")
    fmt.Printf("str=%q\n",str)//%q 会加上双引号:"Hello goLang"
  17. 将字符串左右两边指定的字符去掉:
    str=strings.Trim("!   ! Hello goLang!  !!"," !")
    fmt.Printf("str=%q\n",str)
  18. 去掉左边(或右边):strings.TrimLeftstrings.TrimRight

  19. 判断字符串是否以指定的字符串开头:
    b=strings.HasPrefix("ftp://192.168.10.1","ftp")
    fmt.Printf("b=%v\n",b)
  20. 判断字符串是否以指定的字符串结束:strings.HasSuffix

时间和日期相关函数

  1. 需要导入time

  2. time.Time类型,用来表示时间

    now := time.Now()//获取当前时间
    fmt.Printf("Now: %v,类型%T",now,now)
    //Now: 2022-08-03 15:47:48.2543597 +0800 CST m=+0.003611801,类型time.Time
  3. 如何获取到其他日期信息
    //通过now可以获取到年月日,时分秒
    fmt.Printf("年: %v\n",now.Year())		//年: 2022
    fmt.Printf("月: %v\n",now.Month())		//月: August	
    fmt.Printf("月: %v\n",int(now.Month()))	//月: 8
    fmt.Printf("日: %v\n",now.Day())			//日: 3
    fmt.Printf("时: %v\n",now.Hour())		//时: 15
    fmt.Printf("分: %v\n",now.Minute())		//分: 53
    fmt.Printf("秒: %v\n",now.Second())		//秒: 0
  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())
    
    dateStr:= fmt.Sprintf("当前年月日 %d-%d-%d  %d:%d:%d",now.Year(),now.Month(),now.Day(),
    now.Hour(),now.Minute(),now.Second())
    fmt.Println(dateStr)
    //当前年月日 2022-8-3  15:58:19
    //当前年月日 2022-8-3  15:58:19

    方式2:fmt.Printf(now.Format("2006/01/02 15:04:05"))

    这个时间是固定的(间隔符可以改变),必须这么写。但是可以自由组合,只显示日期,或者时间

    fmt.Printf(now.Format("2006/01/02 15:04:05"))
    fmt.Println()
    fmt.Printf(now.Format("2006-01-02"))
    fmt.Println()
    fmt.Printf(now.Format("15 04 05"))
    fmt.Println()
    /*
    2022/08/03 16:02:19
    2022-08-03
    16 02 19
    */
  5. 时间的常量

    定义在time包中

    const (
        Nanosecond  Duration = 1//纳秒
        Microsecond          = 1000 * Nanosecond//微秒
        Millisecond          = 1000 * Microsecond//毫秒
        Second               = 1000 * Millisecond//秒
        Minute               = 60 * Second//分钟
        Hour                 = 60 * Minute//小时
    )
    //使用
    100*time.Millisecond
  6. 结合使用sleep()使用常量
    i:=0
    for {
    	i++
    	fmt.Println(i)
    	//休眠
    	time.Sleep(time.Millisecond*20)//只能乘以大于1的整数,不能小于或者除
        if i==10{
            break
        }
    }
  7. Unix时间戳和Unixnano时间戳(作用是可以获取随机数字)

    下面的形式叫方法,与函数有一些区别

    fmt.Printf("Unix:%v,UnixNano:%v\n",now.Unix(),now.UnixNano())
    //Unix:1659514601,UnixNano:1659514601927962800
  8. 获取间隔秒数
    start := time.Now().Unix()
    test()
    end:= time.Now().Unix()
    fmt.Printf("执行test()耗费的时间为%v秒",end-start)

内置函数

  1. len

  2. new:用来分配内存,主要用来分配值类型,如int,float64,返回指针

    num1 :=100
    fmt.Printf("num1的类型%T, 值=%v, 地址 %v\n",num1,num1,&num1)
    
    num2 := new(int)
    *num2 =200
    fmt.Printf("num2的类型%T, 值=%v, 地址 %v ,存的值=%v\n",num2,num2,&num2,*num2)
    /*
    num1的类型int, 值=100, 地址 0xc000014088
    num2的类型*int, 值=0xc0000140c0, 地址 0xc000006030  存的值=200    //因为是指针,所以值是地址
    */

  3. make:用来分配内存,主要用来分配引用类型,比如channelmapslice

错误处理机制

  1. 在默认情况下,当发生错误后(panic),程序就会退出

  2. 如果希望发生错误时,可以捕获到错误,并进行处理,保证程序可以继续执行。还可以在捕获到错误后,给管理员一个提示

基本说明

  1. 不支持try...catch...finally

  2. Go处理方式:defer,panic,recover

  3. Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理

    package main
    import(
    	"fmt"
    	"time"
    )
    
    func test(){
    	//使用defer + recover 来捕获和处理异常
    	defer func(x int){
    		err := recover() //recover()内置函数,可以捕获到异常
    		if  err != nil { //说明捕获到错误
    			fmt.Println("err=",err,x)
    			//这里就可以将错误信息发送给管理员...
    			fmt.Println("发送给管理员")
    		}
    	}(1)//匿名函数调用加个()就行,因为此函数不用参数,如果有参数就填入参数
    	num1 :=10
    	num2 :=0
    	res := num1/num2
    	fmt.Println("res=",res)
    }
    func main(){
    	for{
    		test()
    		fmt.Println("main()下面的代码...")
    		time.Sleep(time.Second)
    	}
    }

自定义错误

Go程序中,也支持自定义错误,使用erroes.New和panic内置函数

  1. errors.New("错误说明")。会返回一个error类型的值,表示一个错误

  2. 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.Println("test02继续执行....")
    }
    func main(){
    	test02()
    	fmt.Println("main()下面的代码...")
    }
文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0