第七课 go语言基础-函数、包和错误处理

第七课 go语言基础-函数、包和错误处理

tags:

  • golang
  • 2019尚硅谷

categories:

  • golang
  • 函数(闭包、init、new、defer)
  • 参数作用域
  • 时间字符串函数
  • 错误处理

第一节 go语言函数

1.1go函数的基本使用

为完成某一功能的程序指令(语句)的集合,称为函数。在Go中,函数分为:**自定义函数、系统函数(**查看G0编程手册)
基本语法

func 函数名(形参列表) (返回值列表) {
	执行语句....
	return 返回值列表
}
  1. 形参列表:表示函数的输入
  2. 函数中的语问:表示为了实现某一功能代码块
  3. 函数可以有返回值,也可以没有
    列子:
package main
import "fmt"

//封装成函数,方便调用
func cal(n1 float64, n2 float64, operater byte) float64{
	//输入两个数,再输入一个运算符(+,-.*,/),得到结果.。
	var res float64 

	switch operater {
		case '+':
			res = n1 + n2
		case '-':
			res = n1 - n2
		case '*':
			res = n1 * n2
		case '/':
			res = n1/n2
		default:
			fmt.Println("运算符不合法!!!")
	}
	return res
}


func main() {
	var n1 float64 = 4.5
	var n2 float64 =  1.5
	var operater byte = '+'
	res := cal(n1, n2, operater)
	fmt.Println("res:", res)
}

第二节 go语言 包

2.1 包的基本概念

包的本质实际上就是创建不同的文件夹,来存放程序文件。
说明: go的每一个文件都是属于一个包的,也就是说go是以包的形式来管理文件和项目目录结构
包的三大作用:

  1. 区分相同名字的函数、变量等标识符
  2. 当程序文件很多时,可以很好的管理项目
  3. 控制函数、变量等访问范围,即作用域

2. 2包使用说明和注意事项

  1. 在给一个文件打包时,该包对应一个文件夹,比如utils文件夹对应的包名就是utils,文件的包名通常和文件所在的文件夹名一致,一般为小写字母
  2. 当一个文件要使用其它包函数或变量时,需要先引入对应的包
    • 引入方式1: import “包名”
    • 引入方式2:
import (
	"包名1"
	"包名2"
)
  1. package指令在文件第一行,然后是import指令。
  2. 在import包时,路径从**$GOPATH的src下开始**,不用带src,编译器会自动从src下开始引入。(配置到环境变量中,若改变重启电脑可生效)
  3. 为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的public ,这样才能跨包访问。比如utils.go的 func Cal(n1 float64, n2 float64, operater byte) float64
  4. 在访问其它包函数,变量时,其语法是包名.函数名
  5. 如果包名较长,Go支持给包取别名,注意细节:取别名后,原来的包名就不能使用了。如果给包取了别名,则需要使用别名来访问该包的函数和变量。
/* 取别名
import (
	"fmt"
	util "go_code/go_study_chapter07/demo01_func/utils"
)
*/
package main
import (
	"fmt"
	"go_code/go_study_chapter07/demo01_func/utils"
)


func main() {
	var n1 float64 = 4.5
	var n2 float64 =  1.5
	var operater byte = '+'
	res := utils.Cal(n1, n2, operater)
	fmt.Println("res:", res)
}
  1. 同一包下,不能有相同的函数名(和全局变量名),否则报重复定义, 留意下导入你会发现如果函数名一样,它自己就判断不了是哪个文件夹下的文件。
  2. 如果你要编译成一个可执行程序文件,就需要将这个包声明为main,即package main.这个就是一个语法规范,如果你是写一个库,包名可以自定义。
    • 编译的指令,在项目目录下,编译路径不需要带src,编译器会自动带编译时需要编译main包所在的文件夹。
    • 项目的目录结构最好按照规范来组织。编译后生成一个有默认名的可执行文件,$GOPATH目录下,可以指定名字和目录,比如:放在bin目录(指定自己的名字)下: D:\goproject>go build -o bin/my.exe go _code/project/main

第三节 go语言函数

3.1 函数的执行过程

在这里插入图片描述

  1. 在调用一个函数时,会给该函数分配-一个新的空间,编译器会通过自身的处理让这个新的空间
    和其它的栈的空间区分开来
  2. 在每个函数对应的栈中,数据空间是独立的,不会混淆
  3. 当一个函数调用完毕(执行完毕)后,程序会销毁这个函数对应的栈空间。

3.2 函数的return语句

  1. Go函数支持返回多个值,用逗号隔开
func 函数名 (形参列表) (返回值类型列表) {
	语句...
	return返回值列表
}
  1. 如果返回多个值时,在接收时,希望忽略某个返回值,则使用 _ 符号表示占位 忽略
  2. 如果返回值只有一个, (返回值类型列表)可以不写()
package main
import (
	"fmt"
)


func getSumAndsub(n1 int, n2 int) (int, int) {
	sum:=n1+n2
	sub:=n1-n2
	return sum, sub
}

func main() {
	// 忽略返回的减法的结果
	sum, _ := getSumAndsub(10, 20)
	fmt.Println("main sum =", sum) //30
	//调用getSumAndsub
	res1, res2 := getSumAndsub(1, 2) //res1 = 3 res2 = -1 
	fmt.Printf("res1=%v res2=%v\n", res1, res2)
}

3.3 函数的递归调用

  1. 一个函数在函数体内又调用了本身,我们称为递归调用
  2. 函数递归需要遵守的重要原则:
    • 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
    • 函数的局部变量是独立的,不会相互影响
    • 递归必须向退出递归的条件逼近,否则就是无限递归,死机啦)
    • 当一个函数执行完毕,或者遇到return, 就会返回,遵守谁调用,就将结果返回给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁
  3. 递归案例:请使用递归的方式,求出斐波那契数1,1,2,3,5,8,13…给你一个整数n,求出它的斐波那契数是多少?
package main
import "fmt"
	
func fib(n int) int {
	if(n==1 || n==2){
		return 1
	}else{
		return fib(n-1) + fib(n-2)
	}
}

func main() {
	res := fib(3)
	//测试
	fmt.Println("res=", res)
	fmt.Println("res=", fib(4))
	fmt.Println("res=", fib(5))
	fmt.Println("res=", fib(6))
}

3.4 函数的注意事项和细节讨论

  1. 函数的形参列表可以是多个,返回值列表也可以是多个。
  2. 形参列表和返回值列表的数据类型可以是值类型和引用类型
  3. 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其
    包文件使用,类似public, 首字母小写,只能被本包文件使用,其它包文件不能使用,类似privat
  4. 函数中的变量是局部的,函数外不生效
  5. 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。
  6. 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传
    入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。
  7. Go函数不支持函数重载
  8. 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量
    了。通过该变量可以对函数调用
  9. 函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用
  10. 为了简化数据类型定义,Go支持自定义数据类型
    • 基本语法: type 自定义数据类型名数据类型// 理解: 相当于一个别名
    • 案例: type myInt int // 这时myInt 就等价int 来使用了.
  11. 支持对函数返回值命名
  12. 使用_标识符,忽略返回值
  13. Go支持可变参数
    • args 是slice切片,通过args[index]可以访问到各个值。
    • 如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后。
      在这里插入图片描述
package main
import (
	"fmt"
)


//在Go中, 函数也是一种数据类型,
//可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用
func getSum(n1 int, n2 int) int {
	return n1 + n2
}

//函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用
func myFun(funvar func(int, int) int, num1 int, num2 int ) int {
	return funvar (num1, num2)
}

//支持对函数返回值命名, 返回不需要规定位置啦
func getSumAndsub(n1 int, n2 int) (sum int, sub int){
	sub=n1-n2
	sum=n1+n2
	return
}

//案例演示:编写一个函数sum , 可以求出1到多个int的和
//可以参数的使用
func sum(n1 int,args... int) int {
	sum := n1
	//遍历args
	for i := 0; i < len(args); i++ {
	sum += args[i] //args[0] 表示取出args切片的第一个元素值,其它依次类推
	}
	return sum
}

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)
	
	res2 := myFun(getSum, 50, 60)
	fmt.Println("res2=", res2)
	
	//给int取了别名,在go中myInt 和int 虽然都是int类型, 但是go认为myInt和int两个类型
	type myInt int
	var num1 myInt 
	var num2 int
	num1 = 40
	num2 = int(num1) //各位, 注意这里依然需要显示转换,go认为myInt和int两个类型
	fmt.Println("num1=" , num1, " num2=" , num2 )

	a1, b1 := getSumAndsub(1, 2)
	fmt.Printf("a=%v b=%v\n", a1, b1)

	//测试一下可变参数的使用
	res4 := sum(10, 0, -1, 90, 10,100)
	fmt.Println("res4=", res4)
}

第四节 go特殊函数

4.1 init函数

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


//init函数,通常可以在init函数中完成初始化工作
/*
init()...
main()...
*/
func init(){
	fmt.Println("init()...")
}

func main(){
	fmt.Println("main()...")
}
  1. inti函数的注意事项和细节
    • 如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程全局变量定义->init函数->main函数
    • init函数最主要的作用,就是完成一些初始化的工作
    • 细节说明:面试题:案例如果main.go和utils.go 都含有变量定义,init 函数时,执行的流程又是怎么样的呢?
      在这里插入图片描述
package utils 
import "fmt"
var Age int
var Name string


//Age 和Name 全局变量,我们需要在main.go 使用
//但是我们需要初始化Age和Name
//init函数完成初始化工作
func init(){
	fmt.Println("utils包的init()...")
	Age = 100
	Name = "tom~"
}
package main
import (
	"fmt"
	//引入包
	"go_code/go_study_chapter07/demo05_func_init/utils"
)

/*
utils包的init()...
test()
init()...
main()..
age= 90
Age= 100 Name= tom~
*/

var age = test()
//为了看到全局变量是先被初始化的,我们这里先写函数
func test() int{
	fmt.Println("test()") //1
	return 90
}
//init函数,通常可以在init函数中完成初始化工作
func init(){
	fmt.Println("init()...") //2
}

func main() {
	fmt.Println("main()..\nage=", age) //3
	fmt.Println("Age=", utils.Age, "Name=", utils.Name)
}

4.2 匿名函数

  1. Go支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用。
    • 匿名函数使用方式1: 在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。
    • 匿名函数使用方式2: 将匿名函数赋给-一个变量(函数变量),再通过该变量来调用匿名函数
  2. 全局匿名函数:如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效。
package main
import "fmt"

var (
	//fun1就是一个全局匿名函数
	Fun1 = func (n1 int, n2 int) int{
		return n1 * n2
	}
)

func main() {
	//在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次
	//案例演示,求两个数的和,使用匿名函数的方式完成
	res1 := func (n1 int, n2 int) int {
		return n1 + n2
	}(10, 20)
	fmt.Println("res1=", res1)

	//将匿名函数func (n1 int, n2 int) int赋给a变量
	//则a的数据类型就是函数类型,此时,我们可以通过a完成调用
	a := func (n1 int, n2 int) int {
		return n1-n2
	}
	res2 := a(10, 30)
	fmt.Println("res2=", res2)
	res3 := a(90, 30)
	fmt.Println("res3=", res3)
	//全局匿名函数的使用
	res4 := Fun1(4, 9)
	fmt.Println("res4=", res4)
}

4.3 闭包

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

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

func main() {

	//使用前面的代码
	f := AddUpper()
	fmt.Println(f(1))// 11
	// 这里需要特别注意 上面的值11 又赋值给了n 11+2=13
	fmt.Println(f(2))// 13
	fmt.Println(f(3))// 16
}
  1. 对上面闭包代码的说明和总结
    • AddUpper是一个函数,返回的数据类型是fun (int) int
    • 闭包的说明
      • 返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成一个整体,构成闭包。
      • 当我们反复的调用f函数时,因为n是初始化一次,因此每调用一次就进行累加。
      • 5)我们要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包。
  2. 闭包的最佳实践
    • 返回的匿名函数和makeSuffix (suffix string) 的suffix 变量组合成一个闭包,因为返回的函数引用到suffix 这个变量
    • 我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入后缀名,比如jpg ,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。大家可以仔细的体会一把!
package main
import (
	"fmt"
	"strings" 
)

/*
1.编写一个函数makeSuffix(suffix string) 可以接收-一个文件后缀名(比如.jpg), 并返回一个闭包
2.调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回文件名.jpg , 
3.要求使用闭包的方式完成
4.strings.HasSuffix, 该函数可以判断某个字符串是否有指定的后缀。
*/
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(){
	//测试makeSuffix 的使用
	//返回一个闭包
	f2 := makeSuffix(".jpg" )
	fmt.Println("文件名处理后=", f2("winter"))  //winter.jgp
	fmt.Println("文件名处理后=", f2("bird.jpg")) // bird.jpg
}

4.4 函数的defer

  1. 为什么需要defer
    • 在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)。
  2. 执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈).当函数执行完毕后,再从defer栈, 按照先入后出的方式出栈,执行
package main
import "fmt"

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

func main() {
	res := sum(10, 20)
	fmt.Println("res=", res) // 4. res= 30
}
  1. defer的注意事项和细节
    • 当go执行到一个defer时,不会立即执行defer后的语句,而是将defer 后的语句压入到一个defer栈中,然后继续执行函数下一个语句。
    • 当函数执行完毕后,在从defer栈中,依次从栈顶取出语句执行(注:遵守栈先入后出的机制),
    • 在defer将语句放入到栈时,也会将相关的值拷贝同时入栈。(看上面的n1++,面试常问到)
  2. defer 的最佳实践
    • defer最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源
    • 在golang编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源),可 以执行defer file.Close() defer connect.Close()
    • 在defer后,可以继续使用创建资源。
    • 当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源.
    • 这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心。

第五节 参数传递和变量作用域

5.1 引用传递和值传递

  1. 值类型参数默认就是值传递,而引用类型参数默认就是引用传递。
  2. 两种传递方式
    • 值传递
    • 引用传递
    • 其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低。

5.2 值类型和引用类型

  1. 值类型:基本数据类型int 系列, float 系列,bool, string 、数组和结构体struct
  2. 引用类型:指针、slice 切片、map、管道chan、interface等都是引用类型
  3. 值类型默认是值传递:变量直接存储值,内存通常在栈中分配
  4. 引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。
  5. 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操
    作变量。从效果上看类似引用。
package main
import "fmt"

/*
	n1就是*int 类型
	test() nl=  30
	main() num= 30
*/
func test(n1 *int) {
	*n1 = *n1 + 10
	fmt.Println("test() nl= ", *n1) // 30
}

func main() {
	num := 20
	test(&num)
	fmt.Println("main() num=", num) // 30
}

5.3 变量作用域

  1. 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
  2. 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
  3. 如果变量是在一个代码块,比如for/if中,那么这个变量的的作用域就在该代码块
package main
import "fmt"

//函数外部声明/定义的变量叫全局变量
//作用域在整个包都有效,如果其首字母为大写,则作用域在整个程
var age int = 50
var Name string = "jack~"

//函数
func test() {
	//age和Name的作用域就只在test函数内部
	age := 10
	Name := "tom~"
	fmt.Println("age=", age) // 10
	fmt.Println("Name=", Name) // tom~
}

func main(){
	fmt.Println("age=", age) // 50 
	fmt.Println("Name=", Name) // jack~
	test()
	
	//如果变量是在一个代码块,比如for/if中,那么这个变量的的作用域就在该代码块
	for i:=0; i<=10; i++{
		fmt.Println("i=", i)
	}

	// i只有定义在外面 i才可以出for循环
	var i int //局部变量
	for i=0; i<=10; i++{
		fmt.Println("i=", i)
	}
	fmt.Println("i=", i)
}

第六节 常用的系统函数

6.1 字符串函数

  1. 字符串在我们程序开发中,使用的是非常多的,到官方文档中查找常用的内建函数builtin(不用导包直接使用)
  2. 统计字符串的长度,按字节len(str)
  3. 字符串遍历,同时处理有中文的问题r := []rune(str)
  4. 字符串转整数: n, err := strconv.Atoi(“12”)
  5. 整数转字符串str = strconv. Itoa(12345)
  6. 字符串转[]byte切片 var bytes = []byte(“hello go”)
  7. []byte 转字符串: str = string([]byte{97, 98, 99})
  8. 10进制转2, 8,16进制: str = strconv.FormatInt(123, 2), 返回对应的字符串
  9. 查找子串是否在指定的字符串中: strings.Contains(“seafood”, “foo”)
  10. 统计一个字符串有几个指定的子串: strings.Count(“ceheese”, “e” )
  11. 返回子串在字符串第一次出现的index值,如果没有返回-1:strings.Index(“NLT_ abc”, “abc”)
  12. 返回子串 在字符串最后一次出现的index,如没有返回-1 : strings ,LastIndex(“go golang”, “go”)
  13. 将指定的子串替换成另外一个子串: strings. Replace(“go go hello”, “go”, “go语言”,n)n可以指定你希望替换几个,如果n=-1表示全部替换
  14. 按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组:strings.Split(“hello,wrold,ok”, ”,”)
  15. 将字符串的字母进行大小写的转换:strings.ToLower(“Go”) // go strings.ToUpper(“Go”)
  16. 将字符串左右两边的空格去掉: strings.Trimspace(" tn a lone gopher ntrn")
  17. 将字符串左右两边指定的字符去掉:strings.Trim("! hello!","!")
  18. 将字符串左边指定的字符去掉: strings.TrimLeft("! hello! ", “!”)
  19. 将字符串右边指定的字符去掉: strings.TrimRight("! hello! “, " !”)
  20. 判断字符串是否以指定的字符串开头: strings. HasPrefix(“ftp://192.168.10.1”, “ftp”) // true .
  21. 判断字符串是否以指定的字符串结束: strings. HasSuffix(“NLT_ _abc. ipg”, “abc”) //false
package main
import (
	"fmt"
	"strconv"
	"strings"
)


func main(){
	var str string = "hello"
	fmt.Println("str的长度:", len(str)) //5
	
	// 如果字符串中有中文 golang的编码统一为utf-8(字符数字ASCII中占1个字节, 汉字占三个字节)
	var str1 string = "hello北"
	fmt.Println("str1的长度:", len(str1)) //8
	for i := 0; i < len(str1); i++{
		fmt.Printf("字符=%c\n", str1[i]) // 中文部分会乱码
	}

	// 通过r := []rune(str)解决中文的问题
	r := []rune(str1)
	for i := 0; i < len(r); i++{
		fmt.Printf("字符=%c\n", r[i]) // 中文部分会乱码
	}

	// 字符串转整数 n, err := strconv.Atoi("12") 可以进行数据的校验
	n, err := strconv.Atoi("12")
	//n, err := strconv.Atoi("hello")
	if err != nil {
		fmt.Println("转换错误", err)
	}else {
		fmt.Println("转成的结果是", n)
	}

	// 整数转字符串
	str = strconv.Itoa(12345)
	fmt.Printf("str转成的结果是%v, \nstr类型是%T", str, str)

	// 字符串转[]byte切片 var bytes = []byte("hello go")
	var bytes = []byte("he1lo go") 
	fmt.Println("bytes=%v", bytes)

	// []byte 转字符串: str = string([]byte{97, 98, 99})
	str = string([]byte{97, 98, 99})
	fmt.Println(str)

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

	//查找子串是否在指定的字符串中: 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)

	//返回子串在字符串第一次出现的index值,如果没有返回-1:strings.Index("NLT_ abc", "abc") // 4
	index := strings.Index("NLT_abcabcabc", "abc") // 4
	fmt.Printf("index=%v\n", index)
	
	//返回子串 在字符串最后一次出现的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)
	}

	//将字符串的字母进行大小写的转换:strings.ToLower("Go") // go strings.ToUpper("Go")
	str = "goLang Hello"
	str = strings.ToLower(str)
	str = strings.ToUpper(str)
	fmt.Printf("str=%v\n", str) //golang hello

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

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

	//判断字符串是否以指定的字符串开头:
	//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)
}

6.2 时间函数

  1. 在编程中,经常使用到日期相关的函数,比如:统计某段代码执行花费的时间等等。时间和日期相关函数,需要导入time包
  2. time.Time类型,用于表示时间
  3. 获取当前时间
  4. 通过now可以获取到年月日,时分秒
  5. 格式化日期时间
    • 方式1:就是使用Printf或者SPrintf
    • 方式二:使用time. Format()方法完成
  6. 时间的常量。常量的作用:在程序中可用于获取指定时间单位的时间,比如想得到100毫秒
    100 * time. Millisecond
const(
		Nanosecond Duration= 1 //纳秒
		Microsecond = 1000 * Nanosecond //微秒
		Millisecond = 1000 * Microsecond //毫秒
		Second = 1000 * Millisecond //秒
		Minute = 60*Second//分钟
		Hour = 60 * Minute //小时
	)
  1. 结合Sleep来使用一下时间常量
  2. time的Unix和UnixNano的方法
    • Unix将表示为Unix时间,即从时间点January 1, 1970 UTC到时间点所经过的时间(单位秒) .
    • UnixNano将t表示为Unix时间,即从时间点January 0, 1970 UTC到时间点t所经过的时间(单位纳秒)。如果纳秒为单位的unix时间超出了int64能表示的范围,结果是未定义的。注意这就意味着Time零值调用UnixNano方法的话,结果是未定义的。
package main

import (
	"fmt"
	"time"
)

func main(){
	//看看日期和时间相关函数和方法使用
	//1.获取当前时间
	now := time.Now()
	fmt.Printf("now=%v \nnow 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-0215:04:05"))
	fmt.Println()
	fmt.Printf( now.Format("2006-01-02"))
	fmt.Println()
	fmt.Printf(now.Format("15:04:05"))
	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())
}

6.3 内置函数

  1. Golang设计者为了编程方便,提供了一些函数,这些函数可以直接使用,我们称为Go的内置函
    数。文档: htps://studygolang .com/pkgdoc -> builtin
  2. len:用来求长度,比如string、array、slice、 map、 channel
  3. new:用来分配内存,主要用来分配值类型,比如int、float32、strut…返回的是指针
    • 分配一个空间,储存数值
    • 又分配一空间,把值的地址放进入。让num2指向这个空间。
  4. make:用来分配内存,主要用来分配引用类型,比如channel、map、slice。 这个我们后面讲解。
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 )
}

在这里插入图片描述

6.4 错误处理机制

  1. 在默认情况下,当发生错误后(panic),程序就会退出(崩溃.)
  2. 如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。还可
    以在捕获到错误后,给管理员一个提示(邮件,短信。。。)
  3. 这里引出错误处理机制
    • Go语言追求简洁优雅,所以,Go语言不支持传统的try…catch…finally 这种处理。
    • Go中引入的处理方式为: defer, panic, recover
    • 这几个异常的使用场景可以这么简单描述: Go中可以抛出一个panic的异常,然后在defer通过recover捕获这个异常,然后正常处理
package main
import (
	"fmt"
	"time"
)

func test() {
	//使用defer + recover 来捕获和处理异常
	defer func() {
		err := recover() // recover()内置函数,可以捕获到异常
		if err!=nil{//说明捕获到错误
			fmt.Println("err=", err)
			//这里就可以将错误信息发送给管理员.... 
			fmt.Println("发送邮件给[email protected]~" )
		}
	}()
	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println("res=", res)
}

func main() {
	//测试
	test() 
	for {
		fmt.Println("main()下面的代码...")
		time.Sleep(time.Second)
	}
}
  1. Go程序中,也支持自定义错误,使用 errors.New和panic内置函数。
    • errors.New(“错误说明”),会返回一个error 类型的值,表示一个错误
    • panic 内置函数,接收一个interface{}类型的值(也就是任何值了)作为参数。可以接收error 类型的变量,输出错误信息,并退出程序.
package main
import (
	"fmt"
	"errors"
)

//函数去读取以配置文件init. conf的信息
//如果文件名传入不正确,我们就返回一个自定义的错误
/*
panic: 读取文件错误..

goroutine 1 [running]:
main.test02()
        E:/GolangBase/src/go_code/go_study_chapter07/demo13_func_error_catch/userdefined/main/main.go:23 +0xb2
main.main()
        E:/GolangBase/src/go_code/go_study_chapter07/demo13_func_error_catch/userdefined/main/main.go:30 +0x29
exit status 2
*/
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()下面的代码...")
}
发布了61 篇原创文章 · 获赞 8 · 访问量 2796

猜你喜欢

转载自blog.csdn.net/aa18855953229/article/details/105348005