Go03-函数+包+异常处理

发布时间 2023-03-26 13:17:33作者: 行稳致远方

Go03-函数+包+异常处理

1.函数的基本介绍

func main() {
	// 1 为完成某一功能的程序指令的集合,成为函数。
	// 2 函数分为自定义函数和系统函数。

	// 3 函数的定义,使用关键字func来定义一个函数。
	/*
	func 函数名 (形参列表) (返回值列表) {
		函数执行的语句
		return 返回值列表
	}
	 */
	a1 := test01(1)
	fmt.Println(a1) // 11
}

func test01(a int) int {
	return a + 10
}

2.包的基本介绍

  1. Go中每一个文件都属于一个包,Go是以包的形式管理文件和项目目录的。

  2. 包的作用:区分相同的函数、变量名;当程序文件很多时,可以更好的管理项目;控制函数、变量的访问范围,即作用域。

  3. 包的基本语法:打包语法,package 包名;引入包的基本语法,import 包的路径

  4. 包的基本使用

    1. $GOPATH/src/GoCode/GoDemo02/main/main.go。
    package main
    
    import (
        // 引入包,从GOPATH下引入包,src可以省略。
    	"GoCode/GoDemo02/utils"
    	"fmt"
    )
    
    func main() {
    	a := utils.Add(1, 2)
    	fmt.Println(a) // 3
    }
    
    1. $GOPATH/src/GoCode/GoDemo02/utils/add.go。
    package utils
    
    func Add(a int, b int) int {
    	return a + b
    }
    
  5. 包名通常和文件所在的文件夹名一致,如add.go在utils文件夹下,就package utils打为utils包;add.go在utils下,也可以打为其他包,如package abc,打为abc包,引入语句为import "GoCode/GoDemo02/abc",使用语句为abc.Add(1, 2)。

  6. 包名一般为小写字母,如果有多个小写字母使用_隔开。

  7. 引入包的两种方式:引入包的第一种方式,用于引入一个包,import "包名";引入包的第二种方式,用于引入多个包,import ( "包名" \n "包名")

  8. import导入包时,路径从$GOPATH的src开始,一般不需要不用写src,编译器会自动从src下开始寻找。

  9. 如果需要让其包访问到本包的函数或者变量,则包中的函数或者变量首字母需要大写,类似于其他编程语言中的public权限访问修饰符,这样才能进行跨包访问。

  10. 包名较长时,则可以给包取别名,去别名之后,就需要使用别名来访问包中的函数。

    1. 取别名import a "GoCode/GoDemo02/utils"
    2. 取别名之后访问包中的函数就需要使用别名来访问,a.Add(1, 2)
  11. 在同一个包下,不能有相同的函数名或者全局变量名,否则报重复定义的错误。理解:包中函数的调用语法是包名.函数名,如果有相同的函数名,则不知道调用的函数。即GoCode/GoDemo02/utils包下,可以有多个go文件,如add1.go、add2.go,但是utils包下所有的文件中只能有一个Add函数。

  12. 如果需要编译一个可执行文件,就需要将这个包声明为main包,这是一个语法规范;如果开发一个库文件,则包名可以自定义。

  13. D:\GOPATH>go build -o bin/my.exe GoCode/GoDemo02/main,go build的-o参数可以指定打包之后可执行文件的路径和名称。在GOPATH下打包,并将可执行文件放到GOPATH/bin下。

  14. go build打包时,会将引入的外部项目打包为.a文件,并且.a文件保存在pkd下。如将引入的redis打包为$GOPATH\pkg\windows_amd64\github.com\garyburd\redigo\redis.a

3.函数的参数和返回值

  1. 函数的return。
func main() {
	// 1 Go中的函数支持多个参数的返回值。
	// 在返回多个值时,如果希望忽略某个返回值,则使用_符号占位忽略。
	a1, a2 := test01()
	fmt.Println(a1, a2) // 2 3

	// 使用占位符忽略第一个返回值。
	_, a3 := test01()
	fmt.Println(a3) // 3

	// 2 当函数的返回值只有一个时,可以忽略返回值的()。
	a4 := test02()
	fmt.Println(a4) // 10
}

func test02() int {
	return 10
}

func test01() (int, int) {
	return 2, 3
}
  1. 函数的参数。
func main() {
	// 1 基本数据类型和数组都是值传递,即进行值拷贝。在函数内进行修改不会影响原值。
	s1 := "alice"
	test01(s1)
	fmt.Println(s1) // alice

	// 2 如果希望函数可以修改值类型的参数,可以传递变量的地址。
	s2 := "alice"
	test02(&s2)
	fmt.Println(s2) // tom

	// 3 Go中的函数不支持重载,即不支持同名函数。
}

// 报错。Go不支持函数的重载。
//func test02(a int)  {
//
//}

func test02(s *string) {
	*s = "tom"
}

func test01(s string) {
	s = "tom"
}
  1. 函数返回值命名。
func main() {
	// 1 Go支持对函数的返回值命名。
	a1 := test01(10, 10)
	fmt.Println(a1) // 20
}

func test01(a int, b int) (sum int) {
	sum = a + b
	return
}
  1. 可变参数。
func main()  {
	// 1 可变参数本质是slice切片,可以通过args[index]访问。
	// 2 如果函数中的形参中有可变参数,则可变参数需要写在最后。
	a1 := test01(1, 2, 3)
	fmt.Println(a1) // 6
}

func test01(a ...int) int {
	sum := 0
	for i := 0; i < len(a); i++ {
		sum += a[i]
	}

	// 可变参数for...range遍历。
	for index, value := range a{
		fmt.Println(index, value)
	}
	return sum
}

5.Go中函数也是一种数据类型

func main() {
	// 1 Go中函数也是一种数据类型,可以赋值给一个变量,
	// 则该变量就是一个函数类型的变量,并且该变量也可以像函数一样被调到。
	var a1 func(int, int) int = test01
	// a1的类型func(int, int) int,test01的类型func(int, int) int
	fmt.Printf("a1的类型%T,test01的类型%T\n", a1, test01)

	// 变量像函数一样调用,等价于a2 := test01(10, 20)
	a2 := a1(10, 20)
	fmt.Println(a2) // 30

	// 2 Go中函数也是一种数据类型,所以可以作为形参传递。
	a3 := test02(test01)
	fmt.Println(a3) // 20
}

func test02(a func(int, int) int) int {
	return a(10, 10)
}

func test01(a int, b int) int {
	return a + b
}

6.自定义数据类型

// 1 Go中支持自定义数据类型,可以将自定义数据类型理解为别名。
// 自定义数据语法:type 自定义数据类型名 数据类型
type myInt int
var a1 myInt = 10
// a1类型main.myInt,a1=10
fmt.Printf("a1类型%T,a1=%d\n", a1, a1)

// 2 自定义函数类型。
// a2的类型是func(int, int) int,太长了,可以使用自定义类型。
var a2 func(int, int) int = test01
// a2的类型func(int, int) int
fmt.Printf("a2的类型%T\n", a2)

type MyTest func(int, int) int
var a3 MyTest = test01
// main.MyTest
fmt.Printf("%T", a3)

7.init函数

  1. init函数。
func main() {
	// 1 Go中的init函数会在main函数前执行。
	// a b
	fmt.Println("b")
}

func init() {
	fmt.Println("a")
}
  1. init函数的执行顺序。
func main() {
	// 1 如果.go文件中同时存储全局变量、init函数、main函数,
	// 则执行顺序是:全局变量、init函数、main函数。

	/*
	全局变量执行。。。
	init函数执行。。。
	main函数开始执行。。。
	*/
	fmt.Println("main函数开始执行。。。")

	// 2 如果main.go和utils.go中都有init函数,main.go使用utils.go中的函数。
	// 则执行顺序是:utils.go中的全局变量、utils.go中的init函数、main.go中的全局变量、
	// main.go中的init函数、main.go中的main函数。
}

var a int = test01()

func test01() int {
	fmt.Println("全局变量执行。。。")
	return 1
}

func init() {
	fmt.Println("init函数执行。。。")
}

8.匿名函数

func main() {
	// 1 匿名函数使用方式一,定义匿名函数时就进行匿名函数的调用。
	// 这种匿名函数只能调用一次。
	a := func(a int, b int) int {
		return a + b
	}(10, 20)
	fmt.Println(a) // 30

	// 2 匿名函数使用方式二,将匿名函数赋值给某个变量,
	// 通过该变量来调用匿名函数,该方式定义的匿名函数可以被多次调用。
	b := func(a int, b int) int {
		return a + b
	}
	fmt.Println(b(10, 10)) // 20
	fmt.Println(b(20, 20)) // 40

	// 3 全局匿名函数,将一个匿名函数赋值给一个全局变量,则这个匿名函数,
	// 就成为了一个全局匿名函数。
	fmt.Println(c(5, 5)) // 25
}

var (
	c = func(a int, b int) int {
		return a * b
	}
)

9.闭包

  1. 闭包。
func main() {
	// 1 闭包就是一个函数和与其相关的应用环境组合的一个整体。
	// test01()的返回值是一个函数,这个函数和所拥有的test01()中的全部变量组成了闭包。
	// test01()返回的函数中应用的变量只会被初始化一次。
	a := test01()
	fmt.Println(a(1)) // 11
	fmt.Println(a(1)) // 12
}

func test01() func(int) int {
	a := 10
	return func(b int) int {
		a = a + b
		return a
	}
}
  1. 闭包练习。
func main() {
	a := makeSuffix(".jpg")
	fmt.Println(a("1.jpg"))
	fmt.Println(a("1.png"))
}

// 1 makeSuffix接受一个文件后缀名.jpg并返回一个闭包。
// 2 调用闭包,传入文件名,如果该文件名的后缀和传入的后缀.jpg相同,则返回原文件名;
// 如果该文件没有指定的后缀,则返回文件名+后缀名。
func makeSuffix(suffix string) func(fileName string) string	 {
	return func(fileName string) string {
		if strings.HasSuffix(fileName, suffix) {
			return fileName
		}

		return fileName + suffix
	}
}

10.函数的defer

func main() {
	// 1 defer在函数执行完成之后执行某些语句,如函数执行完成之后释放资源。
	// 2 当执行到defer时,不会立即执行defer后的语句,而是将defer压入栈中,
	// 然后继续执行后面的语句。当函数执行完成后,依次从栈顶取出语句执行。
	/*
	test01
	defer02
	defer01
	 */
	test01()

	// 3 将defer语句压入栈中时,会将相关的值拷贝到栈中。
	/*
	10 20
	11 21
	b= 20
	a= 10
	 */
	test02(10, 20)
}

func test02(a int, b int)  {
	defer fmt.Println("a=", a)
	defer fmt.Println("b=", b)

	fmt.Println(a, b)
	a++
	b++
	fmt.Println(a, b)
}

func test01() {
	defer fmt.Println("defer01")
	defer fmt.Println("defer02")

	fmt.Println("test01")
}

11.函数的参数传递

  1. 两种参数传递的方式:值传递和引用传递。
  2. 不管是值传递还是引用传递,传递的都是变量的副本。不同的是,值传递的是值的副本拷贝;引用传递的是地址的副本拷贝。一般来说,地址拷贝效率高,因为数据量小。
  3. 值类型的数据有:基本数据类型int系列、float系列、string、bool、数组和结构体;引用类型的数据有:指针、slice切片、map、管道chan、interface。
  4. 值类型默认是值传递,变量直接存储值,内存通常在栈中分配。
  5. 引用类型默认是引用传递,变量存储的是一个地址,变量对应的地址存储真正的数据,内存通常在堆上分配。当没有没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。
  6. 如果希望函数内的变量可以修改函数外值类型的数据,可以传入值类型变量的地址&,函数以指针的方式操作变量。

12.字符串相关的函数

// 1 len()统计字符串的长度,按字节。一个英文和数字占一个字节,一个汉字占三个字节。
a1 := "1中"
fmt.Println(len(a1)) // 4

// 2 []rune(),处理字符串遍历时中文乱码问题。
a2 := "1中"
// 1	ä	¸	­
for i := 0; i < len(a2);i++ {
    fmt.Printf("%c\t", a2[i])
}

fmt.Println()
a3 := []rune(a2)
// 1	ä	¸	­
for i := 0;i < len(a3);i++ {
    fmt.Printf("%c\t", a3[i])
}

fmt.Println()
// 3 字符串转整数。
a4, _ := strconv.Atoi("12")
fmt.Println(a4) // 12

// 4 整数转字符串。
a5 := strconv.Itoa(123)
fmt.Println(a5) // 123

// 5 字符串转[]byte数组
a6 := []byte("hello 张三")
// [104 101 108 108 111 32 229 188 160 228 184 137]
fmt.Println(a6)

// 6 byte转字符串。
a7 := []byte{97, 98, 99}
a8 := string(a7)
fmt.Println(a8) // abc

// 7 十进制转2、8、16进制。
fmt.Println(strconv.FormatInt(10, 2)) // 1010
fmt.Println(strconv.FormatInt(10, 8)) // 12
fmt.Println(strconv.FormatInt(10, 16)) // a

// 8 查找子串是否在指定的字符串中。
fmt.Println(strings.Contains("hello", "ll")) // true

// 9 统计字符串包含几个指定的子串。
fmt.Println(strings.Count("hello hello hello", "ll")) // 3

// 10 字符串比较。
b1 := "hello"
b2 := "HeLLo"
b3 := "hello"
// ==在比较字符串时,区分大小写
fmt.Println(b1 == b2) // false
fmt.Println(b1 == b3) // true

// strings.EqualFold()不区分大小写比较字符串。
fmt.Println(strings.EqualFold(b1, b2)) // true

// 11 子串查找,如果包含子串,则返回子串第一次出现的索引;如果不包含子串,则返回-1。
fmt.Println(strings.Index("hello", "ll")) // 2
fmt.Println(strings.Index("hello", "k")) // -1

// 12 返回子串最后一次出现的位置,没有找到字符返回-1。
fmt.Println(strings.LastIndex("hello", "e")) // 1
fmt.Println(strings.LastIndex("hello", "m")) // -1

// 13 字符串替换。
// strings.Replace(),最后一个参数n表示,要替换字符串的个数,-1表示替换所有。
// hemmo hello
fmt.Println(strings.Replace("hello hello", "ll", "mm", 1))
// hemmo hemmo
fmt.Println(strings.Replace("hello hello", "ll", "mm", -1))

// 14 字符串切割,将字符串按照指定字符切割为数组。
// [hello hello]
fmt.Println(strings.Split("hello,hello", ","))

// 15 字符串转大写和小写。
// 转小写 hello
fmt.Println(strings.ToLower("HeLLO"))
// 转大写 HELLO
fmt.Println(strings.ToUpper("hello"))

// 16 去掉字符串两边的空格。
fmt.Println(strings.TrimSpace(" hello ")) // hello

// 17 Trim的其中用法。
// 去掉字符串两边的指定字符。hello
fmt.Println(strings.Trim(",hello,", ","))
// 去掉字符串左边指定字符。,hello
fmt.Println(strings.TrimLeft("1,hello", "1"))
// 去掉字符串右边指定字符。hello,
fmt.Println(strings.TrimRight("hello,1", "1"))

// 18 判断字符串是否以指定字符开头。
fmt.Println(strings.HasPrefix("hello", "h")) // true

// 19 判断字符串是否以指定字符串结尾。
fmt.Println(strings.HasSuffix("hello", "o")) // true

13.时间和日期相关的函数

// 1 获取当前时间,time.Time用于表示时间类型。
a1 := time.Now();
// 2023-01-07 15:40:20.9207375 +0800 CST m=+0.002348001,type=time.Time
fmt.Printf("%v,type=%T\n", a1, a1)

// 2 获取日期的年月日时分秒信息。
fmt.Printf("年=%v\n", a1.Year()) // 年=2023
fmt.Printf("月=%v\n", a1.Month()) // 月=January
// Month(),返回的是Month类型,可以转换为int。
fmt.Printf("月=%v\n", int(a1.Month())) // 月=1
fmt.Printf("日=%v\n", a1.Day()) // 日=7
fmt.Printf("时=%v\n", a1.Hour()) // 时=15
fmt.Printf("分=%v\n", a1.Minute()) // 分=51
fmt.Printf("秒=%v\n", a1.Second()) // 秒=0

// 3 日期时间格式化方式一。
a2 := time.Now()
// 当前时间 2023-1-7 16:19:58
fmt.Printf("当前时间 %d-%d-%d %d:%d:%d\n", a2.Year(),
           a2.Month(), a2.Day(), a2.Hour(), a2.Minute(), a2.Second())

a3 := fmt.Sprintf("当前时间 %d-%d-%d %d:%d:%d\n", a2.Year(),
                  a2.Month(), a2.Day(), a2.Hour(), a2.Minute(), a2.Second())
fmt.Printf(a3)

// 3 日期时间格式格式化方式二。
a4 := time.Now()
// Format()的参数2006-01-02 15:04:05是固定的,必须这样写。
// 2023-01-07 16:27:21
fmt.Printf(a4.Format("2006-01-02 15:04:05"))
fmt.Println()
// 2023-01-07
fmt.Printf(a4.Format("2006-01-02"))
fmt.Println()
// 16:27:21
fmt.Printf(a4.Format("15:04:05"))
fmt.Println()

// 4 time中的时间常量。
minute := time.Minute
// 10分钟
fmt.Println(10 * minute) // 10m0s


// 5 Sleep的使用,每个一秒打印一个数字,打印到3退出。
a5 := 0
// 0 1 3
for  {
    fmt.Println(a5)
    time.Sleep(1 * time.Second)
    a5++
    if a5 == 3 {
        break
    }
}

// 6 Unix()和UnixNano()
a6 := time.Now()
// Unix()返回从1970年1月1日到时间点所经过的时间,单位秒。
fmt.Println(a6.Unix()) // 1673081313
// UnixNano()返回从1970年1月1日到时间点所经过的时间,单位纳秒;
// UnixNano()返回值类型是int64,如果返回值范围超过int64,结果时未定义的。
fmt.Println(a6.UnixNano()) // 1673081313761040800

14.new和make分配内存

// 1 new用来给值类型分配内存,比如,int系列、float系列、string、bool、数组和struct。
// new()返回一个指针,这个指针指向new分配的内存。
a1 := new(int)
*a1 = 100

// a1的地址 0xc000006028
fmt.Println("a1的地址", &a1)
// a1的值 0xc00000a0a8
fmt.Println("a1的值", a1)
// a1地址对应的值 100
fmt.Println("a1地址对应的值", *a1)

// 2 make可以给引用类型分配内存,比如给channel、map、slice分配内存。

15.异常处理

func main() {
	// 1 当程序报错后,会由于异常而退出,如果想不退出就需要进行异常处理。
	// 报错:panic: runtime error: integer divide by zero
	// test01()
	fmt.Println("程序继续执行")

	// 2 Go中可以通过panic抛出异常,然后再defer中通过recover来捕获异常。
	test02()
	fmt.Println("程序继续执行。。。")

	// 3 自定义错误。使用errors.New()和panic内置函数完成自定义错误。
	// errors.New("xxx错误")会返回一个error类型值,这个值表示一个错误。
	// panic内置函数可以接受一个interface{}类型的值(interface{}类型的值可以理解为任何值)作为参数。
	// panic接受error类型的变量后会输出错误信息,并退出程序。

	// test03继续执行
	test03("init.config")
	// 报错打印panic: 文件名错误,并且退出程序的执行,不会输出test03继续执行。
	test03("init1.config")
}

// 读取init.config配置文件的信息,如果文件名不是init.config则报错。
func readFile(fileName string) error {
	if fileName == "init.config" {
		return nil
	} else {
		return errors.New("文件名错误")
	}
}

func test03(fileName string) {

	err := readFile(fileName)
	if err != nil {
		panic(err)
	}
	fmt.Println("test03继续执行")
}

func test02() {
	// 使用defer + recover来捕获异常。
	defer func() {
		// recover是系统内置函数,可以捕获到运行时抛出的异常。
		error := recover()
		fmt.Println(error)
		if error != nil {
			fmt.Println("捕获到异常", error)
		}
	}()

	a1 := 10
	a2 := 0
	fmt.Println(a1 / a2)
}

func test01() {
	a1 := 10
	a2 := 0
	fmt.Println(a1 / a2)
}