第七课 go语言基础-函数、包和错误处理
tags:
- golang
- 2019尚硅谷
categories:
- golang
- 包
- 函数(闭包、init、new、defer)
- 参数作用域
- 时间字符串函数
- 错误处理
文章目录
第一节 go语言函数
1.1go函数的基本使用
为完成某一功能的程序指令(语句)的集合,称为函数。在Go中,函数分为:**自定义函数、系统函数(**查看G0编程手册)
基本语法
func 函数名(形参列表) (返回值列表) {
执行语句....
return 返回值列表
}
- 形参列表:表示函数的输入
- 函数中的语问:表示为了实现某一功能代码块
- 函数可以有返回值,也可以没有
列子:
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是以包的形式来管理文件和项目目录结构。
包的三大作用:
- 区分相同名字的函数、变量等标识符
- 当程序文件很多时,可以很好的管理项目
- 控制函数、变量等访问范围,即作用域
2. 2包使用说明和注意事项
- 在给一个文件打包时,该包对应一个文件夹,比如utils文件夹对应的包名就是utils,文件的包名通常和文件所在的文件夹名一致,一般为小写字母。
- 当一个文件要使用其它包函数或变量时,需要先引入对应的包
- 引入方式1: import “包名”
- 引入方式2:
import (
"包名1"
"包名2"
)
- package指令在文件第一行,然后是import指令。
- 在import包时,路径从**$GOPATH的src下开始**,不用带src,编译器会自动从src下开始引入。(配置到环境变量中,若改变重启电脑可生效)
- 为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的public ,这样才能跨包访问。比如utils.go的 func Cal(n1 float64, n2 float64, operater byte) float64
- 在访问其它包函数,变量时,其语法是包名.函数名。
- 如果包名较长,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)
}
- 在同一包下,不能有相同的函数名(和全局变量名),否则报重复定义, 留意下导入你会发现如果函数名一样,它自己就判断不了是哪个文件夹下的文件。
- 如果你要编译成一个可执行程序文件,就需要将这个包声明为main,即package main.这个就是一个语法规范,如果你是写一个库,包名可以自定义。
- 编译的指令,在项目目录下,编译路径不需要带src,编译器会自动带编译时需要编译main包所在的文件夹。
- 项目的目录结构最好按照规范来组织。编译后生成一个有默认名的可执行文件,$GOPATH目录下,可以指定名字和目录,比如:放在bin目录(指定自己的名字)下: D:\goproject>go build -o bin/my.exe go _code/project/main
第三节 go语言函数
3.1 函数的执行过程
- 在调用一个函数时,会给该函数分配-一个新的空间,编译器会通过自身的处理让这个新的空间
和其它的栈的空间区分开来 - 在每个函数对应的栈中,数据空间是独立的,不会混淆
- 当一个函数调用完毕(执行完毕)后,程序会销毁这个函数对应的栈空间。
3.2 函数的return语句
- Go函数支持返回多个值,用逗号隔开
func 函数名 (形参列表) (返回值类型列表) {
语句...
return返回值列表
}
- 如果返回多个值时,在接收时,希望忽略某个返回值,则使用 _ 符号表示占位 忽略
- 如果返回值只有一个, (返回值类型列表)可以不写()
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 函数的递归调用
- 一个函数在函数体内又调用了本身,我们称为递归调用
- 函数递归需要遵守的重要原则:
- 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
- 函数的局部变量是独立的,不会相互影响
- 递归必须向退出递归的条件逼近,否则就是无限递归,死机啦)
- 当一个函数执行完毕,或者遇到return, 就会返回,遵守谁调用,就将结果返回给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁
- 递归案例:请使用递归的方式,求出斐波那契数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 函数的注意事项和细节讨论
- 函数的形参列表可以是多个,返回值列表也可以是多个。
- 形参列表和返回值列表的数据类型可以是值类型和引用类型。
- 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其
包文件使用,类似public, 首字母小写,只能被本包文件使用,其它包文件不能使用,类似privat - 函数中的变量是局部的,函数外不生效
- 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。
- 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传
入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。 - Go函数不支持函数重载
- 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量
了。通过该变量可以对函数调用 - 函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用
- 为了简化数据类型定义,Go支持自定义数据类型
- 基本语法: type 自定义数据类型名数据类型// 理解: 相当于一个别名
- 案例: type myInt int // 这时myInt 就等价int 来使用了.
- 支持对函数返回值命名
- 使用_标识符,忽略返回值
- 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函数
- 每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用,也就是说init会在main函数前被调用。
package main
import (
"fmt"
)
//init函数,通常可以在init函数中完成初始化工作
/*
init()...
main()...
*/
func init(){
fmt.Println("init()...")
}
func main(){
fmt.Println("main()...")
}
- 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 匿名函数
- Go支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用。
- 匿名函数使用方式1: 在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。
- 匿名函数使用方式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 闭包
- 基本介绍:闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
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
}
- 对上面闭包代码的说明和总结
- AddUpper是一个函数,返回的数据类型是fun (int) int
- 闭包的说明
- 返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成一个整体,构成闭包。
- 当我们反复的调用f函数时,因为n是初始化一次,因此每调用一次就进行累加。
- 5)我们要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包。
- 闭包的最佳实践
- 返回的匿名函数和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
- 为什么需要defer
- 在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)。
- 当执行到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
}
- defer的注意事项和细节
- 当go执行到一个defer时,不会立即执行defer后的语句,而是将defer 后的语句压入到一个defer栈中,然后继续执行函数下一个语句。
- 当函数执行完毕后,在从defer栈中,依次从栈顶取出语句执行(注:遵守栈先入后出的机制),
- 在defer将语句放入到栈时,也会将相关的值拷贝同时入栈。(看上面的n1++,面试常问到)
- defer 的最佳实践
- defer最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源
- 在golang编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源),可 以执行defer file.Close() defer connect.Close()
- 在defer后,可以继续使用创建资源。
- 当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源.
- 这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心。
第五节 参数传递和变量作用域
5.1 引用传递和值传递
- 值类型参数默认就是值传递,而引用类型参数默认就是引用传递。
- 两种传递方式
- 值传递
- 引用传递
- 其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低。
5.2 值类型和引用类型
- 值类型:基本数据类型int 系列, float 系列,bool, string 、数组和结构体struct
- 引用类型:指针、slice 切片、map、管道chan、interface等都是引用类型
- 值类型默认是值传递:变量直接存储值,内存通常在栈中分配
- 引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。
- 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操
作变量。从效果上看类似引用。
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 变量作用域
- 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
- 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
- 如果变量是在一个代码块,比如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 字符串函数
- 字符串在我们程序开发中,使用的是非常多的,到官方文档中查找常用的内建函数builtin(不用导包直接使用)
- 统计字符串的长度,按字节len(str)
- 字符串遍历,同时处理有中文的问题r := []rune(str)
- 字符串转整数: n, err := strconv.Atoi(“12”)
- 整数转字符串str = strconv. Itoa(12345)
- 字符串转[]byte切片 var bytes = []byte(“hello go”)
- []byte 转字符串: str = string([]byte{97, 98, 99})
- 10进制转2, 8,16进制: str = strconv.FormatInt(123, 2), 返回对应的字符串
- 查找子串是否在指定的字符串中: strings.Contains(“seafood”, “foo”)
- 统计一个字符串有几个指定的子串: strings.Count(“ceheese”, “e” )
- 返回子串在字符串第一次出现的index值,如果没有返回-1:strings.Index(“NLT_ abc”, “abc”)
- 返回子串 在字符串最后一次出现的index,如没有返回-1 : strings ,LastIndex(“go golang”, “go”)
- 将指定的子串替换成另外一个子串: strings. Replace(“go go hello”, “go”, “go语言”,n)n可以指定你希望替换几个,如果n=-1表示全部替换
- 按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组:strings.Split(“hello,wrold,ok”, ”,”)
- 将字符串的字母进行大小写的转换:strings.ToLower(“Go”) // go strings.ToUpper(“Go”)
- 将字符串左右两边的空格去掉: strings.Trimspace(" tn a lone gopher ntrn")
- 将字符串左右两边指定的字符去掉:strings.Trim("! hello!","!")
- 将字符串左边指定的字符去掉: strings.TrimLeft("! hello! ", “!”)
- 将字符串右边指定的字符去掉: strings.TrimRight("! hello! “, " !”)
- 判断字符串是否以指定的字符串开头: strings. HasPrefix(“ftp://192.168.10.1”, “ftp”) // true .
- 判断字符串是否以指定的字符串结束: 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 时间函数
- 在编程中,经常使用到日期相关的函数,比如:统计某段代码执行花费的时间等等。时间和日期相关函数,需要导入time包
- time.Time类型,用于表示时间
- 获取当前时间
- 通过now可以获取到年月日,时分秒
- 格式化日期时间
- 方式1:就是使用Printf或者SPrintf
- 方式二:使用time. Format()方法完成
- 时间的常量。常量的作用:在程序中可用于获取指定时间单位的时间,比如想得到100毫秒
100 * time. Millisecond
const(
Nanosecond Duration= 1 //纳秒
Microsecond = 1000 * Nanosecond //微秒
Millisecond = 1000 * Microsecond //毫秒
Second = 1000 * Millisecond //秒
Minute = 60*Second//分钟
Hour = 60 * Minute //小时
)
- 结合Sleep来使用一下时间常量
- 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 内置函数
- Golang设计者为了编程方便,提供了一些函数,这些函数可以直接使用,我们称为Go的内置函
数。文档: htps://studygolang .com/pkgdoc -> builtin - len:用来求长度,比如string、array、slice、 map、 channel
- new:用来分配内存,主要用来分配值类型,比如int、float32、strut…返回的是指针
- 分配一个空间,储存数值
- 又分配一空间,把值的地址放进入。让num2指向这个空间。
- 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 错误处理机制
- 在默认情况下,当发生错误后(panic),程序就会退出(崩溃.)
- 如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。还可
以在捕获到错误后,给管理员一个提示(邮件,短信。。。) - 这里引出错误处理机制
- 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)
}
}
- 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()下面的代码...")
}