golang学习笔记007--函数,包和错误处理

1.函数

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

2.包

● 包的三大作用
  ○ 区分相同名字的函数,变量等标识符
  ○ 当程序文件很多时,可以很好的管理项目
  ○ 控制函数,变量等访问范围,及作用域
● 注意事项
  ○ 文件的包名通常和文件所在文件夹名一致,一般为小写字母
  ○ 当一个文件要使用其它包函数或变量时,需要先引入对应的包
  ○ 在访问其它包函数,变量时,其语法是 包名.函数名
  ○ 如果包名较长,go语言支持给包起别名.取别名后,原来的包名就不能使用了
  ○ 在同一个包下,不能有相同的函数名或全局变量
  ○ 如果要编译成一个可执行程序文件,就需要将这个包声明为main,即package main,如果你写一个库,包可以自定义

在这里插入图片描述

3.函数调用机制

在这里插入图片描述

说明:
	● 在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其他的栈空间区分开来
	● 在每个函数对应的栈中,数据空间是独立的,不会混淆
	● 当一个函数调用文笔后,程序会销毁这个函数对应的栈空间

4.return语句

go函数支持返回多个值
● 如果返回多个值时,在接受时,希望忽略某个值,使用_符号表示占位忽略
● 如果返回一个值,(返回值类型列表)可以不写()

	package main
	import "fmt"
	
	func getSumAndSub (a int,b int) (int,int){
		sum := a + b
		sub := a - b
		return sum,sub
	}
	
	func main(){
		res1,res2 :=getSumAndSub(5,2)
		fmt.Printf("res1=%v res2=%v\n",res1,res2)//res1=7 res2=3
		res3,_ :=getSumAndSub(5,2)
		fmt.Printf("res3=%v",res3)//res3=7
	}

5.递归

在这里插入图片描述
在这里插入图片描述
结果为:
n=2
n=2
n=3
注意事项:
● 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
● 函数的局部变量是独立的,不会相互影响
● 递归必须向退出递归的条件逼近,否则就是无线递归
● 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当函数执行完毕后或者返回时,该函数本书也会被系统销毁

6.函数使用注意事项和细节

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

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

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

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

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

  6. 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量
    在这里插入图片描述

  7. go函数不支持函数重载
    在这里插入图片描述

  8. 在go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了,通过该变量可以对函数调用
    在这里插入图片描述

  9. 函数既然是一种数据类型,因此在go中,函数可以作为形参,并且调用
    在这里插入图片描述
    在这里插入图片描述

  10. 为了简化数据类型定义,go支持自定义函数类型
    ● 基本语法:type 自定义数据类型名 数据类型 //理解:相当于一个别名
    ● 案例:type myInt int //这时myInt就等价int来使用了
    ● 案例:type mySum func(int,int) int //这时mySum就等价于一个函数类型 func(int,int)int
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  11. 支持对函数返回值命名
    在这里插入图片描述
    在这里插入图片描述

  12. 使用_标识符,忽略返回值

  13. go支持可变参数
    在这里插入图片描述
    在这里插入图片描述

7.init函数

每一个源文件都可以包含一个init函数,该函数会在main函数执行之前,被go运行框架调用,也就是说init会在main函数前被调用
在这里插入图片描述
在这里插入图片描述

注意事项及细节:
● 如果一个文件同时包含全局变量定义,init函数和main函数,则执行顺序为 全局变量定义->init函数 ->main函数

● init函数最主要的作用,就是完成一些初始化的工作
● 如果引入的包中都有变量定义和init,则执行顺序为
在这里插入图片描述

8.匿名函数

go支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用

8.1 匿名函数使用方式1

在调用匿名函数时直接调用,这种方式匿名函数只能调用一次
在这里插入图片描述

8.2 匿名函数使用方式2

将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
在这里插入图片描述

8.3 全局匿名函数

如果将匿名函数赋给一个全局变量,那么这个匿名函数就成为一个全局匿名函数
在这里插入图片描述

9.闭包

9.1 闭包介绍

闭包就是一个函数和与其他相关的引用环境组合的一个整体
在这里插入图片描述
对上面代码的说明和总结
● AddUpper是一个函数,返回的数据类型是fun(int) int
● 闭包的说明
在这里插入图片描述
○ 返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成一个整体,构成闭包
● 可以这样理解:闭包是类,函数是操作,n是字段.函数和它使用到n构成闭包
● 当我们反复的调用f函数时,因为n初始化一次,因此每调用一次就进行累计
● 对上面的代码进行修改,加深理解
在这里插入图片描述

9.2 闭包的最佳实践

● 编写一个函数 makeSuffix(suffix string)可以接收一个文件后缀名(比如.jpg),并返回一个闭包
● 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回文件名.jpg ,如果已经有.jpg后缀,则返回原文件名。
● 要求使用闭包的方式完成
● strings.HasSuffix,该函数可以判断某个字符串是否有指定的后缀
在这里插入图片描述
● 返回的匿名函数和 makeSuffix (suffix string)的 suffix变量组合成一个闭包,因为返回的函数引用到suffix这个变量
● 我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入后缀名,比如 jpg ,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。

10.函数的defer

10.1 为什么需要defer

在函数中,程序员经常要创建资源(比如:数据库连接,文件句柄,锁等),为了在函数执行完毕后,及时的释放资源.go的设计者提供了defer延迟机制
在这里插入图片描述

10.2 defer注意事项

● 当go执行到一个defer时,不会立即执行defer语句,而是将defer后的语句压入到一个栈中,然后继续执行函数的下一个语句
● 在函数执行完毕后,再从defer栈中,依次从栈顶取出语句执行(先进后出)
● 在defer将语句放入栈时,也会将相关的值拷贝同时入栈
在这里插入图片描述
在这里插入图片描述

10.3 defer的最佳实践

defer最主要的价值是:当函数执行完毕后,可以及时的释放函数创建的资源
在这里插入图片描述
说明:
● 在golang编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源),可以执行defer file.Close() defer connect.Close()
● 在defer 后,可以继续使用创建资源.
● 当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源.
● 这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心

11.函数参数传递方式

2种传递方式
● 值传递
● 引用传递
其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低。
● 值类型:基本数据类型int系列, float系列, bool, string 、数组和结构体 struct
● 引用类型:指针、slice切片、map、管道chan、interface 等都是引用类型

在这里插入图片描述
在这里插入图片描述

12.变量作用域

● 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
● 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
● 如果变量是在一个代码块,比如for/if中,那么这个变量的作用域就在该代码块
● 编译器采用就近原则
在这里插入图片描述
在这里插入图片描述

13.字符串操作

package main
import (
	"fmt"
	"strconv"
	"strings"
)

func main(){

	//统计字符串的长度,按字节 len(str)
	golang的编码统一为utf-8 (ascii的字符(字母和数字) 占一个字节,汉字占用3个字节)
	str := "hello北" 
	fmt.Println("str len=", len(str)) // 8


	str2 := "hello北京"
	//字符串遍历,同时处理有中文的问题 r := []rune(str)
	r := []rune(str2)
	for i := 0; i < len(r); i++ {
		fmt.Printf("字符=%c\n", r[i])
	}
// 字符=h
// 字符=e
// 字符=l
// 字符=l
// 字符=o
// 字符=北
// 字符=京

	//字符串转整数:	 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)
	str = strconv.Itoa(12345)
	fmt.Printf("str=%v, str=%T\n", str, str)

	//5)字符串 转 []byte:  var bytes = []byte("hello go")
	var bytes = []byte("hello go")
	fmt.Printf("bytes=%v\n", bytes)//bytes=[104 101 108 108 111 32 103 111]

	//6)[]byte 转 字符串: str = string([]byte{97, 98, 99})
	str = string([]byte{97, 98, 99}) 
	fmt.Printf("str=%v\n", str)

	//10进制转 2, 8, 16进制:  str = strconv.FormatInt(123, 2),返回对应的字符串
	str = strconv.FormatInt(123, 2)
	fmt.Printf("123对应的二进制是=%v\n", str)//123对应的二进制是=1111011
	str = strconv.FormatInt(123, 16)
	fmt.Printf("123对应的16进制是=%v\n", str)//123对应的16进制是=7b

	//查找子串是否在指定的字符串中: strings.Contains("seafood", "foo") //true
	b := strings.Contains("seafood", "mary")
	fmt.Printf("b=%v\n", b) 

	//统计一个字符串有几个指定的子串 : strings.Count("ceheese", "e") //4
	num := strings.Count("ceheese", "e")
	fmt.Printf("num=%v\n", num)

	//10)不区分大小写的字符串比较(==是区分字母大小写的): fmt.Println(strings.EqualFold("abc", "Abc")) // true

	b = strings.EqualFold("abc", "Abc")
	fmt.Printf("b=%v\n", b) //true

	fmt.Println("结果","abc" == "Abc") // false //区分字母大小写

	//11)返回子串在字符串第一次出现的index值,如果没有返回-1 : 
	//strings.Index("NLT_abc", "abc") // 4

	index := strings.Index("NLT_abcabcabc", "abc") // 4
	fmt.Printf("index=%v\n",index)

	//12)返回子串在字符串最后一次出现的index,
	//如没有返回-1 : strings.LastIndex("go golang", "go")

	index = strings.LastIndex("go golang", "go") //3
	fmt.Printf("index=%v\n",index)

	//将指定的子串替换成 另外一个子串: strings.Replace("go go hello", "go", "go语言", n) 
	//n可以指定你希望替换几个,如果n=-1表示全部替换

	str2 = "go go hello"
	str = strings.Replace(str2, "go", "北京", -1)
	fmt.Printf("str=%v str2=%v\n", str, str2)

	//按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组: 
	//strings.Split("hello,wrold,ok", ",")
	strArr := strings.Split("hello,wrold,ok", ",")
	for i := 0; i < len(strArr); i++ {
		fmt.Printf("str[%v]=%v\n", i, strArr[i])
	} 
	fmt.Printf("strArr=%v\n", strArr)

	//15)将字符串的字母进行大小写的转换: 
	//strings.ToLower("Go") // go strings.ToUpper("Go") // GO

	str = "goLang Hello"
	str = strings.ToLower(str) 
	str = strings.ToUpper(str) 
	fmt.Printf("str=%v\n", str) 

	//将字符串左右两边的空格去掉: strings.TrimSpace(" tn a lone gopher ntrn   ")
	str = strings.TrimSpace(" tn a lone gopher ntrn   ")
	fmt.Printf("str=%q\n", str)

	//17)将字符串左右两边指定的字符去掉 : 
	//strings.Trim("! hello! ", " !")  // ["hello"] //将左右两边 ! 和 " "去掉
	str = strings.Trim("! he!llo! ", " !")
	fmt.Printf("str=%q\n", str)

	//20)判断字符串是否以指定的字符串开头: 
	//strings.HasPrefix("ftp://192.168.10.1", "ftp") // true

	b = strings.HasPrefix("ftp://192.168.10.1", "hsp") //true
	fmt.Printf("b=%v\n", b)
}

14.时间函数

package main
import (
	"fmt"
	"time"
)

func main() {
	//看看日期和时间相关函数和方法使用
	//1. 获取当前时间
	now := time.Now()
	fmt.Printf("now=%v now type=%T\n", now, now)

	//2.通过now可以获取到年月日,时分秒
	fmt.Printf("年=%v\n", now.Year())
	fmt.Printf("月=%v\n", now.Month())
	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.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 \n", now.Year(), 
	now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())

	fmt.Printf("dateStr=%v\n", dateStr)

	//格式化日期时间的第二种方式
	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()

	fmt.Printf(now.Format("2006"))
	fmt.Println()


	//需求,每隔1秒中打印一个数字,打印到100时就退出
	//需求2: 每隔0.1秒中打印一个数字,打印到100时就退出
	// i := 0
	// for {
	// 	i++
	// 	fmt.Println(i)
	// 	//休眠
	// 	//time.Sleep(time.Second)
	// 	time.Sleep(time.Millisecond * 100)
	// 	if i == 100 {
	// 		break
	// 	}
	// }

	//Unix和UnixNano的使用
	fmt.Printf("unix时间戳=%v unixnano时间戳=%v\n", now.Unix(), now.UnixNano())

}

在这里插入图片描述

最佳实践:获取时间戳

package main
import (
	"fmt"
	"time"
	"strconv"
)

func test03() {

	str := ""
	for i := 0; i < 100000; i++ {
		str += "hello" + strconv.Itoa(i)
	}
}

func main() {
	//在执行test03前,先获取到当前的unix时间戳
	start := time.Now().Unix()
	test03()
	end := time.Now().Unix()
	fmt.Printf("执行test03()耗费时间为%v秒\n", end-start)
}

在这里插入图片描述

15.内置函数builtin

● len:用来求长度,比如string,array,slice,map,channel
● new:用来分配内存,主要用来分配值类型,比如int,floate32,struct…返回的是指针

package main
import (
	"fmt"
)

func main() {

	num1 := 100
	fmt.Printf("num1的类型%T , num1的值=%v , num1的地址%v\n", num1, num1, &num1)

	num2 := new(int) // *int
	//num2的类型%T => *int
	//num2的值 = 地址 0xc04204c098 (这个地址是系统分配)
	//num2的地址%v = 地址 0xc04206a020  (这个地址是系统分配)
	//num2指向的值 = 100
	*num2  = 100
	fmt.Printf("num2的类型%T , num2的值=%v , num2的地址%v\n num2这个指针,指向的值=%v", 
		num2, num2, &num2, *num2)
}

在这里插入图片描述
在这里插入图片描述

● make:用来分配内存,主要用来分配引用类型,比如channel,map,slice,后面看

16.错误处理

16.1示例

	package main
	
	import (
		"fmt"
	)
	
	func test(){
		num1:=1
		num2:=0
		res:=num1/num2
		fmt.Println("res=",res)
	}
	
	func main(){
		fmt.Println("mian()代码继续执行11111")
		test()
		fmt.Println("mian()代码继续执行22222")
	}

在这里插入图片描述
项目报错,错误前代码执行,错误后代码不执行

16.2处理错误

● go语言追求简洁优雅,所以不支持传统的try…catch…finally
● go语言中引入的处理方式:defer,panic,recover
● go中可以抛出一个public的异常,然后在defer中通过recover捕获这个异常,然后正常处理

	package main
	
	import (
		"fmt"
	)
	
	func test(){
		//使用defer+recover来捕获和处理异常
		defer func(){
			err := recover()//recover()内置函数,可以捕获异常
			if err != nil{
				fmt.Println("err=",err)
				fmt.Println("继续执行...")
			}
		}()
		num1:=1
		num2:=0
		res:=num1/num2
		fmt.Println("res=",res)
	}
	
	func main(){
		fmt.Println("mian()代码继续执行11111")
		test()
		fmt.Println("mian()代码继续执行22222")
	}

在这里插入图片描述
错误处理,错误后面代码继续执行

16.3自定义错误

● go程序中,支持自定义错误,使用errors.New和panic内置函数
● errors.New(“错误说明”),会返回一个error类型的值,表示一个错误
● panic内置函数,接受一个interface{}类型的函数(也就是任何值)作为参数.可以接收error类型的变量,输出错误信息,并退出程序

	package main
	
	import (
		"fmt"
		"errors"
	)
	
	//如果文件名传入不正确,返回一个自定义错误
	func readConf(name string)(err error){
		if name =="Config.ini" {
			//正确,读取....
			return nil
		}else{
			//返回一个自定义错误
			return errors.New("读取文件错误...")
		}
	}
	
	
	
	func main(){
		fmt.Println("代码继续执行1111")
		err:=readConf("Config111.ini")
		if err !=nil{
			//如果读取文件错误,就输出这个错误,并终止程序
			panic(err)
		}
		fmt.Println("代码继续执行22222")
	}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值