函数
文章目录
- 函数
- 1.函数入门
- (1)为什么需要函数?
- (2)什么是函数:
- 2.包
- 3.函数的调用机制
- 通俗理解
- 调用过程:
- return语句
- 递归调用
- 4.函数注意事项和细节讨论
- 5.init函数
- 6.匿名函数
- 7.闭包
- 8.defer
- 9.函数参数的传递方式
- 10.字符串中常用的函数
- 11.时间和日期相关的函数
- 12.内置函数
- 13.go的错误处理机制
1.函数入门
(1)为什么需要函数?
完成一个需求:输入两个数,再输入一个运算符(±*/),得到结果
使用传统方法:
func main(){/*请大家完成这样一个需求:输入两个数,在输入一个运算符(+-* / ,得到结果)*/var n1 float64 =1.2var n2 float64 =2.3var operator byte ='/'var res float64switch operator {case '+':res = n1 + n2case '-':res = n1 - n2case '*':res = n1 * n2case '/':res = n1 / n2 default:fmt.Println("操作符错误") }fmt.Println("res=",res)
}
分析代码上的问题:
- 代码冗余太长
- 不好改动,不利于代码进行维护
- 函数可以解决这个问题
(2)什么是函数:
为完成某一个功能的程序指令(语句)的集合,称为函数
在Go中,函数分为:自定义函数、系统函数(查看GO编程手册)
函数基本语法:
func 函数名 (形象列表) (返回值列表){执行语句...return 返回值列表
}
1)形参列表:表示函数的输入
2)函数中的语句:表示为了实现某一个功能的代码块
3)函数可以有返回值,也可以没有
案例入门:
//将计算的功能放到一个函数中,在需要的时候进行调用
func cal (n1 float64,n2 float64,operator byte) float64{var res float64switch operator {case '+':res = n1 + n2case '-':res = n1 - n2case '*':res = n1 * n2case '/':res = n1 / n2 case '%':res = n1 % n2 default:fmt.Println("操作符错误") }return res
}
//调用函数:
func main(){
result := cal(3.2,2.1,'-') //输入参数就可以对这个函数调用
fmt.Println("result=",result) //1.1
}
2.包
为什么要用包
1)在实际的开发中,我们往往需要在不同的文件中,去调用其他文件的定义的函数,比如main.go中,去使用utils.go文件中的函数,如何实现?
2)现在有两个程序员共同开发一个go项目,两个程序员都想定义定义个交cal名字的函数怎么办
包的原理图
包的本质实际上就是创建不同的文件夹,来存储程序文件
此图用来说明一下包的原理图:
包的示意图
包的介绍
包的基本概念:
说明:go的每一个文件都是属于一个包的,也就是说go是以包的形式来管理文件和项目目录结构的
包的三大作用
- 区分相同名字的函数、变量等标识符
- 当程序文件很多时,可以很好的管理项目
- 控制函数、变量等访问访问范围,即作用域
打包的基本语法
pacakage 包名
引入包的基本语法
import "包的路径"
包使用的快速入门案例
utils包:
package utils
import (
“fmt”
)
//将计算的功能放入一个函数中,然后在需要的时候进行使用
//为了让马哥其他的包文件使用Cal函数,需要将c大写,类似java中public
func Cal (n1 float64,n2 float64,operator byte) float64{
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
}
引入utils包
import (
“fmt”
“go_code/functions/utils”
)
调用
func main(){
result := utils.Cal(3.2,2.1,‘-’)
fmt.Println(“result=…”,result)
}
//这样就完事了
包的注意事项以及细节1)在给一个文件打包时,该包对应一个文件夹,比如在这里的utils文件夹对应的包名就是utils,文件的包名通常和文件夹名一致,一般为小写字母2).当一个文件要使用其他的包函数或变量是需要先引入对应的包引入方式1: import “包名”引入方式2:
import(
“name”
“name”
)
3)pacakage指令在文件第一行,然后是、import指令4)在import包是路径从$GOPATH的src下开始,不用带src,编译器会自动从src下开始引入5)为了让其他包的文件,可以访问到本包的函数,则该函数名的首字母需要大写6)在访问其他包的函数名时,其语法是包.函数,
utils.Cal(1,2)
7)如果包名较长,Go支持给包取别名,注意细节:取别名后,原来的包名就不能使用了```go
import ("fmt"//前面的是别名ut "go_code/functions/utils"
)
//别名调用,原来的包名就会失效,就必须使用别名调用
result := ut.Cal(3.2,2.1,'-')fmt.Println("result=..",result)
8)在同一个包下,不能有相同的函数名(也不能有相同的全局变量名),否则报重复定义的错误
9)如果你要编译成一个可执行文件,就需要将这个包声明为main,即pacakage main这就是一个语法规范,如果你写一个库,包名可以自定义
注意:-o表示输出 bin\my.exe表示在当前目录中的bin下面
3.函数的调用机制
通俗理解
调用过程:
对上图的说明:
(1)在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其他栈的空间区分开来
(2)在每个函数对应的栈中,数据空间都是独立的,不会混淆
(3)当一个函数调用完毕后,程序会销毁这个函数的栈空间
案例理解:
1)传入一个数进行加1的操作
package mainimport ("fmt"
)
//编写一个函数 test
func test(n1 int){n1 = n1 + 1fmt.Println("n1=",n1)
}
func main(){n1 :=10//调用testtest(n1)}
2)计算两个数,并返回 getSum
func getSum(n1 int,n2 int) int {
//当函数有、return语句时,就是将结果返回给调用者
//即谁调用我,我就返回给谁return n1 + n2
}
//调用
func main(){n1 :=10//调用testtest(n1)sum := getSum(1,3)fmt.Println("getSum得到的值是:",sum)//4}
return语句
基本语法
Go函数支持返回多个值,这一点是其他编程语言没有的
func 函数名 (形参列表) (返回值类型列表){语句...return 返回值列表
}
注意:
(1)如果返回多个值时,在接收时,希望忽略某个返回值则使用_符号表示占位忽略
(2)如果返回值只有一个,(返回值类型列表) 可以不写()
案例演示
请编写函数,可以计算两个数的和和差,并返回结果
func getSumAndSub(n1 int,n2 int) (int,int) {sum := n1 +n2sub := n1 -n2return sum,sub
}
调用
func main(){//调用getSumAndSub()函数res1,res2 := getSumAndSub(1,2)fmt.Printf("res1=%v,res2=%v",res1,res2)//res1=3 res2=1
}
一个细节说明:希望忽略某个返回值则使用_符号表示占位忽略
//希望忽略某个返回值则使用_符号表示占位忽略
_, res3 := getSumAndSub(1,2)
fmt.Println("res3=",res3) //1-2=-1
递归调用
基本介绍:
一个函数在函数体内又调用了本身,我们称为递归调用
案例入门:
func test(n int){if n > 2 {n--test(n)}fmt.Println("n=",n) //n=2 n=2 n=3//这里是if里面执行完了就会执行下面的输出
}
func test2 (n int) { //这里走了if就不会走else,if n > 2{n-- //递归必须向退出递归逼近test2(n)}else{fmt.Println("n=",n) //n=2 }
}
以上代码传入test(4)分别输出什么![在这里插入图片描述](https://img-blog.csdnimg.cn/79c35b1280e64b798cf1a9cf06abcf67.jpeg#pic_center)
函数递归需要遵守的重要原则
1)执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
2)函数额局部变量是独立的,不会相互影响
3)递归必须向退出递归的条件逼近,否则就是无限递归了,死龟了
4)当一个函数执行完毕,或遇到return就会返回遵守递归调用,就将结果返回给谁,当函数执行完毕或者返回时,该函数也会被销毁
117集
练习题:
题1:斐波那契数列
请使用递归的方式,求出斐波那契数1,1,2,3,5,8,13…给你一个整数n求出他的值是多少
//斐波那契数func feibo(n int) int{if n==1 || n==2{return 1}else{return feibo(n-1)+feibo(n-2)}}
题2:求函数值
已知f(1)=3; f(n)= 2*f(n-1)+1
请使用递归的思想编程,求出f(n)的值
//求函数的值 当f(1)=3,f(n)=2*f(n-1)+1
func han(n int) int {if n==1{return 3}else{return 2*han(n-1)+1}
}
题3猴子吃桃问题
有一堆桃子,猴子第一天吃了其中的一半,并且多吃了一个:以后每天猴子都吃了其中的一半,然后再多吃一个,想再吃时(还没吃),发现只有1个桃子。问题:最初共有多少个桃子
思路分析:
- 第10天只有1个桃子
- 第九天有几个桃子=(第10天桃子数 + 1)*2
- 规律第n天桃子数peach(n)=(peach(n+1)+1)*2
//猴子吃桃问题
func MonkeyPeach(n int) int {if n>10 || n<0{fmt.Println("输入的天数不对")return 0}if n==10 {return 1}else{return (MonkeyPeach(n+1)+1)*2}
}//反过来
func maonkey2(n int) int {if n==1 {return 1}else{return (maonkey2(n-1)+1)*2}
}
4.函数注意事项和细节讨论
1)函数的形参列表可以是多个。返回值列表也可以是多个
2)形参列表和返回值列表的数据类型可以是值类型和应用类型
3)函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其他包文件使用,类似于public,首字母小写,只能被本包文件使用,其他包文件不能使用,类似于private
4)函数中的变量是局部的,函数外不生效
package main
import ("fmt"
)
//函数中的变量是局部的,函数外不生效
func test(){//n1是test函数的局部变量,只能在test函数中使用var n1 int = 10}
func main(){//这里不能使用n1,因为n1是test函数的局部变量fmt.Println("n1=",n1)
}
5)基本数据类型和数组默认都是值传递,即进行值拷贝。在函数内修改,不会影响到原来的值。
func test2(n1 int) {n1 = n1 + 10fmt.Println("test2(n1)=",n1)
}
func main(){//这里不能使用n1,因为n1是test函数的局部变量// fmt.Println("n1=",n1)n1 := 20test2(n1)//30 不会影响到外面的值fmt.Println("n1=",n1) //20}
6)如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。从效果上来看类似引用
//n1就是*int类型
func test3(n1 *int) {*n1 = *n1 + 10fmt.Println("test03(n1)=",*n1)//30}
func main(){num := 20
test3(&num)//传的是地址,函数内部通过指针修改变量
fmt.Println("main() num=",num)//30}
7)Go函数不支持重载
func test2(n1 int) {n1 = n1 + 10fmt.Println("test2(n1)=",n1)
}
func test2(n1 int,n2 int) {//错误不支持重载会报重复定义的错}
8)在Go中,**函数也是一种数据类型,**可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数进行调用。
package main
import ("fmt"
)
//在go中,函数也是一种数据类型
//可以赋给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数进行调用
func getSum(n1 int,n2 int) int{return n1+n2
}
func main(){a :=getSumfmt.Printf("a的类型%T,getSum类型是%T\n",a,getSum)res := a(10,40) //等价 res =:= getSum(10,40)fmt.Println("res=",res)
}
9)函数既然是一种数据类型,因此在go中,函数可以作为形参,并且调用
package main
import ("fmt"
)
//在go中,函数也是一种数据类型
//可以赋给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数进行调用
func getSum(n1 int,n2 int) int{return n1+n2
}//函数既然可以作为一种数据类型,因此在Go中,函数可以作为形参,并且调用
func myFunc(funvar func(int,int)int,num1 int,num2 int) int{return funvar(num1,num2)
}
func main(){a :=getSumfmt.Printf("a的类型%T,getSum类型是%T\n",a,getSum)res := a(10,40) //等价 res =:= getSum(10,40)fmt.Println("res=",res)//看案例res2 :=myFunc(getSum,50,60)fmt.Println("res2=",res2) //110}
10)为了简化数据类型定义,Go支持定义数据类型
基本语法:type 自定义数据类型名 数据类型 //理解:相当于一个别名
案例:type myInt int //这时myInt就等价int来使用了
-----type myInt int //给int取了别名 在go中myInt和int虽然都是//int类型,但是在go中还是认为myInt 和int是不同的数据类型var num1 myIntvar num2 intnum1 = 40// num2= num1 //报错不是同一个类型num2 = int(num1) //这样进行转换就可以fmt.Println("num1=",num1) //40
--------------------------------------- 案例:type mySum func(int,int)int //这时mySum就等价一个函数类型 func(int,int)int----- //在举一个案例type myFunt func(int,int)int //这时myFun就是 func(int,int)int类型func myFunc2(funvar myFunt,num1 int,num2 int) int{return funvar(num1,num2)
}
//main函数进行调用
//案例结果res3 :=myFunc2(getSum,500,600)fmt.Println("res2=",res3) //1100
}
-------------------------------------
11)支持对函数返回值命名
func cal(n1 int,n2 int) (sum int,sub int){
sum = n1 +n2
sub = n1 - n2
return //这样就不用这样return sum sub之类额
}//main中调用a,b := cal(1,2)fmt.Printf("a=%v,b=%v",a,b) //3,-1,a是sum b是sub
}
12)使用_标识符,忽略返回值
func cal(n1 int,n2 int) (sum int,sub int){
sum = n1 +n2
sub = n1 - n2
return
}
fun main(){
res,_:=cal(10,20)
fmt.Printf("res=%d",res1) //30
}
13)Go支持可变参数
//支持0到多个参数
func sum(arg.. int)sum int{
}
//支持1到多个参数
func sum(n1 int,args.. int)sum int{
}
说明
(1)args是slice,通过args[index]可以访问到各个值
(2)案例演示:编写一个函数sum,可以求出1到多个int的和
(3)如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后
package mainimport ("fmt"
)//编写一个sum函数,可以求出 1到多个int的和
func sum(n1 int,args... int) int{sum := n1//遍历argesfor i :=0;i <len(args);i++{sum += args[i] //args[0]表示取出args切片的第一个元素的值,其他依次类推}return sum
}
func main(){
//测试可变参数的使用
res :=sum(10,0,-1,90,10)
fmt.Println("res=",res)
}
函数练习题
func sum2(n1,n2 float32)float32{fmt.Printf("n1 type=%T\n",n1) //n1 type=float32return n1 +n2
}
func main(){
fmt.Println("sum2=",sum2(1,2)) //3}
最后一句会报错因为myfunc就接收两个int的参数但是b是三个int的参数,类型不匹配。
练习题三
请编写一个函数swap(n1 int,n2 int)可以交换n1和n2的值
func swap(n1 *int,n2 *int){//定义一个临时变量t :=*n1*n1=*n2*n2=t
}
func main(){
a :=10
b :=20
fmt.Printf("a=%v,b=%v",a,b)//10,20
swap(&a,&b)//传入的是地址
fmt.Printf("a=%v,b=%v",a,b) //20,10
}
5.init函数
基本介绍
每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用,也就是说init会在main函数前被调用
案例说明
package main
import ("fmt"
)//init函数,通常可以在init函数中完成初始调用
func init(){//先执行fmt.Println("init()....")
}func main(){fmt.Println("main()....")
}
//执行结果
D:\myfile\GO\project\src\go_code\functions\funint>go run main.go
init()....
main()....
init函数的注意事项和注意细节
1)如果一个文件同时包含变量定义,init函数和main函数,则执行的流程是先init函数后main函数
package main
import ("fmt"
)var age = test()//为了看到全局变量是先被初始化的,我们这里先写函数
func test()int{ //1fmt.Println("test")return 90
}//init函数,通常可以在init函数中完成初始调用
func init(){//先执行 2fmt.Println("init()....")
}func main(){ //3fmt.Println("main()...age=",age)
}
2)init函数最主要的作用,就是完成一些初始化的工作
utils.go
package utils
import ("fmt"
)
var Age int
var Name string
//Aage 和name全局变量我们需要在main.go中使用
//但是我们需要初始化Age和Name//init函数完成初始化
func init(){fmt.Println("init函数执行")Age = 100Name= "初始化"
}
//调用
package main
import ("fmt"//引入包"go_code/functions/funint/utils"
)var age = test()//为了看到全局变量是先被初始化的,我们这里先写函数
func test()int{ //1fmt.Println("test")return 90
}//init函数,通常可以在init函数中完成初始调用
func init(){//先执行 2fmt.Println("init()....")
}func main(){ //3fmt.Println("main()...age=",age)fmt.Println("Age=",utils.Age,"Name",utils.Name)
}
3)面试题:案例如果main.go和utils.go都含有变量定义,init函数时,执行的流程又是怎么样
6.匿名函数
介绍:
Go支持匿名函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次使用
匿名函数使用方式1
在定义匿名函数是就直接调用,这种方式匿名函数只能调用一次
func main(){//在定义匿名函数是就直接调用,这种方式匿名函数只能调用一次//案例演示,求两个数额和,使用匿名函数实现res1 :=func (n1 int,n2 int)int {return n1+n2}(10,20)fmt.Println("res1=",res1) //30
}
匿名函数使用方式2
将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
//第二种方法,将匿名函数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) //-20
全局匿名函数
如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效
package main
import ("fmt"
)
var (Fun1 =func (n1 int,n2 int) int {return n1*n2}
)func main(){//全局匿名函数的使用res4 := Fun1(4,9)fmt.Println("res4=",res4)//36
}
7.闭包
介绍:
闭包就是一个函数与其相关的引用环境组合成一个整体(实体)
案例演示:
package main
import ("fmt"
)
//累加器
func Addupper() func (int) int {var n int = 10return func (x int)int{n = n+ xreturn n}
}
func main(){//使用前面的代码f := Addupper()fmt.Println(f(1)) //11fmt.Println(f(2)) //13fmt.Println(f(3)) //16
}
对上面代码的说明和总结
1)AddUpper是一个函数,返回额度数据类型是fun(int)int
2)闭包的说明:
var n int = 10return func (x int)int{n = n+ xreturn n}
返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成了一个整体,构成闭包
3)可以这样理解:闭包是类,函数是操作,n是字段
4)当我们反复调用f函数时,因为n是初始化1次,因此每调用一次就进行累加
5)我们要搞清楚闭包的关键,就是要分析出返回的函数他使用(引用)到哪些变量,因为函数和它引用的变量是一个整体
//累加器
func Addupper() func (int) int {var n int = 10var str string ="hello"return func (x int)int{n = n+ xstr += "x"fmt.Println("str=",str)return n}
}
func main(){//使用前面的代码f := Addupper()fmt.Println(f(1)) //11fmt.Println(f(2)) //13fmt.Println(f(3)) //16
}
//运行结果;
str= hellox
11
str= helloxx
13
str= helloxxx
16
闭包的最佳实践
请编写一个程序,具体要求如下
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使用
//返回一个闭包
f :=makeSuffix(".jpg")
fmt.Println("文件名处理后=",f("winter")) //输出winter.jpg
fmt.Println("文件名处理后=",f("bird.jpg")) //输出bird.jpg
}
代码说明:
1)返回的函数和makeSuffix(suffix string)的suffiix变量和返回的函数组合成一个闭包,因为返回的函数引用到suffix这个变量
2)我们体会一下闭包的好处,如果使用传统的方法,也可以实现这个轻松的功能,但是传统方法需要每次都传入后缀名,比如.jpg,而闭包可以保留上次引用的某个值,所以我们传入一次就可以反复使用,仔细体会把
//传统方法
func makeSuffix2(suffix string,name string) string{//如果name没有指定的后缀,则加上。否则就返回原来的名字if !strings.HasSuffix(name,suffix){return name +suffix}return name}func main(){
//传统方法就要传入两个参数
fmt.Println("文件名处理后=",makeSuffix2(".jpg","winter")) //输出winter.jpg}
8.defer
为什么需要defer
在函数中,程序员经常需要创建资源(比如,数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)
快速入门案例
package main
import ("fmt"
)
func sum(n1 int,n2 int)int{//当执行到defer时,暂时不会执行会将defer后面的语句压入到独立的栈中(defer栈)//当函数执行完毕后,再从defer栈,按陷入后出的方式中出栈,执行defer fmt.Println("ok1 n1=",n1)defer fmt.Println("ok2 n2=",n2)res := n1 + n2fmt.Println("ok3 res=",res)return res}
func main(){sum(10,20)//输出:
// ok3 res= 30
// ok2 n2= 20
// ok1 n1= 10
// }
defer的注意事项和最佳实践
1)当go执行到了一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入到一个栈中,然后继续执行函数的下一个语句
2)当函数执行完毕后,从defer栈中,依次从栈顶取出语句执行(遵从栈 先入后出的机制)
3)在defer将语句放入栈中也会将相关的值拷贝同时入栈
func sum(n1 int,n2 int)int{defer fmt.Println("ok1 n1=",n1)defer fmt.Println("ok2 n2=",n2)n1++ //n1=11n2++ //n2=21res := n1 + n2fmt.Println("ok3 res=",res)return res}
func main(){sum(10,20)}
//输出结果仍然不变
ok3 res= 32
ok2 n2= 20
ok1 n1= 10
defer主要的价值是在:当函数执行完毕后,可以及时的释放函数创建的资源
func test(){
//关闭文件资源
file = openfile(文件名)
defer file.close()
}func test2(){
//释放数据库资源
connect = openDatabase()
defer connect.close()
//其他代码
}
1)在golang编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的连接,或者是锁资源,可以理解为defer file.Close() defer connect.Close())
2)在defer后,可以继续创建资源
3)当函数完毕后,系统依次从defer栈中,取出语句,关闭资源
4)这种机制非常简洁,程序员们不用再为什么时机关闭资源而烦心。
9.函数参数的传递方式
基本介绍
我们在讲解函数注意事项和使用细节时,已经讲过值类型和引用类型了,这里我们在系统的总结一下,因为这是重难点,值类型参数默认就是值传递了,而引用类型参数默认就是引用传递
两种传递方式
1)值传递
2)引用传递
其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝的效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低
值类型和引用类型
1)值类型:基本数据类型int系列,float系列,bool,string、数组和结构体struct
2)引用类型:指针、slice切片、map、管道cha、interface等都是引用类型
值传递和引用传递的使用特点
1)值类型默认值传递:变量直接存储值,内存通常在栈中分配
2)引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆中分配,当任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC回收
变量作用域
说明
1)函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
package main
import ("fmt"
)
var name string="tom" //全局变量
func test01(){fmt.Println(name)
}func test02(){//编译器采用就近原则name ="jack"fmt.Println(name) //jack
}func main(){
fmt.Println(name) //tom
test01()//tom
test02()//jack
test01()//jack
2)函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果首字母为大写,则作用域在整个程序有效
package main
import ("fmt"
)var age int= 50
var Name string= "jhon"
//函数
func test(){//age和Name作用域就只在test函数内部age := 10Name := "tom"fmt.Println("age=",age)fmt.Println("Name=",Name)
}
func main(){test() //10 tom
// fmt.Println("age=",age) //会报错fmt.Println("age=",age)//50 fmt.Println("Name=",Name)//jhon
}
3)如果变量是在一个代码块,比如for/if中,那么这个变量的作用域就在该代码块
for i :=0 ;i<=10;i++{fmt.Println("i=",i)}
// fmt.Println("i=",i)会报错
下列代码是否报错
var Age int = 20
Name :="tom" //相当于 var Name string Name="tom"在函数外不能够这样写
func main(){
fmt.Println("name",name1)
}
函数的课堂练习
1)函数可以没有返回值,编写一个函数,从终端输入一个整数,打印对应的金字塔
//打印金字塔的案例
func jinzi(){var toleve intfmt.Println("请输入toleve值:")fmt.Scanln(&toleve)for i := 1;i<=toleve;i++{ //行//在打印星号前打印空格for k :=1;k<=toleve-i;k++{fmt.Print(" ")}for j :=1;j<=2*i-1;j++{fmt.Printf("*")}fmt.Println(" ")}}
2)编写一个函数从终端输入一个整数(1-9),打印出对应的九九乘法表
//打印九九乘法表的函数
func chengfa(){var n int fmt.Println("请输入n的值:")fmt.Scanln(&n)for i :=1; i<=n;i++{for j :=1;j<=i;j++{fmt.Printf("%v*%v=%v ",i,j,i*j)}fmt.Println(" ")}
10.字符串中常用的函数
说明:字符串在我们程序开发中,使用的是非常多的,常用的函数需要参考官方编程手册
1)统计字符串的长度,按字节len(str)
func main(){//统计字符串的长度,按字节len(str)str := "hello"str2 := "hello我"//go的编码统一utf8(asciii的字符(字母和数字)占一个字节,一个汉字占三个字节)fmt.Println("str len =",len(str)) //str len =5fmt.Println("str2 len =",len(str2)) //str len =8(5个字母一个汉字)
}
2)字符串遍历,同时处理有中文的问题r :=[]rune(str)
str3 := "hello北京"//字符串遍历,同时处理有中文的问题r :=[]rune(str)r := []rune(str3)for i :=0;i< len(r); i++{fmt.Printf("字符=%c\n",r[i])}
3)字符串转整数:n,err :=strconv.Atoi(“12”)
//字符串转整数:n,err :=strconv.Atoi("12")n,err :=strconv.Atoi("123")if err != nil{fmt.Println("转换错误",err)}else{fmt.Println("转成的结果是",n) //转成的结果是123}
//如果传入hello
/字符串转整数:n,err :=strconv.Atoi("12")n,err :=strconv.Atoi("hello")if err != nil{fmt.Println("转换错误",err)//转换错误 strconv.Atoi: parsing "hello": invalid syntax}else{fmt.Println("转成的结果是",n) }
4)整数转字符串:str := strconv.ltoa(12345)
/整数转字符串:str = strconv.Itoa(12345)str = strconv.Itoa(12345)fmt.Printf("str=%v,str=%T",str,str)//str=12345,str=string
5)字符串转[]byte: var bytes =[]byte(“hello go”)
//字符串转[]byte: var bytes =[]byte("hello go")var bytes = []byte("hello go")fmt.Printf("bytes=%v",bytes)//输出结果为:bytes=[104 101 108 108 111 32 103 111]
6)[]byte转字符串:str=string([]byte{97,98,99})
//[]byte转字符串:str=string([]byte{97,98,99})str = string([]byte{97,98,99})fmt.Printf("str=%v",str)//str=abc
7)10进制转2,8,16进制:str=strconv.FormatInt(123,2) //2>8,16
//10进制转2,8,16进制:str=strconv.FormatInt(123,2) //2>8,16str=strconv.FormatInt(123,2)fmt.Printf("123对应的二进制是%v\n",str)//1111011str=strconv.FormatInt(123,16)fmt.Printf("123对应的十六进制是%v\n",str)//7b
8)查找子串是否在指定的字符串中:string.Contains(“sefood”,“food”) //true
//查找子串是否在指定的字符串中:string.Contains("sefood","food") //truesb := strings.Contains("seafood","foo")fmt.Printf("b=%v",b)//true
9)统计一个字符串有几个指定的子串: strings.Count(“ceheese”,“e”)//4
//统计一个字符串有几个指定的子串: //strings.Count("ceheese","e")//4c :=strings.Count("ceheese","e")fmt.Printf("c=%v\n",c) //4
10)不区分大小写的字符串比较(==是区分字母大小写的):fmt.Println(strings.EqualFold(“abc”,“Abc”)) //true
//不想区分大小写的字符串比较(==是区分字母大小写的):
//fmt.Println(strings.EqualFold("abc","Abc")) //true
fmt.Println(strings.EqualFold("abc","Abc"))//true
fmt.Println("abc"=="Abc")//false ==是区分大小写的
11)返回子串在字符串第一次出现的index值,如果没有返回-1:strings.Index(“NLT_abc”,“abc”) //4
//返回子串在字符串第一次出现的index值
// ,如果没有返回-1:strings.Index("NLT_abc","abc") //4
e := strings.Index("NLT_abc","abc")
fmt.Printf("e=%v",e) //e=4 如果返回的是-1那说明就没有包含
12)返回子串在字符串最后一次出现的index,如没有就返回-1:strings.LastIndex("go ")
//返回子串在字符串最后一次出现的index,如没有就返回-1:strings.LastIndex("go ")
index :=strings.LastIndex("go golang","go")
fmt.Printf("index=%v\n",index)//3
13)将指定的子串替换成另外一个子串strings.Peplace(“go go hello”,“go”,“go语言”,n)n可以指定你希望替换几个,如果n=1表示全部替换
str4 :="go go hello"
str = strings.Replace(str4,"go","go语言",1)
fmt.Printf("str=%v\n",str) //str=go语言 go hello
//将n变成-1时,表示全部替换
str = strings.Replace(str4,"go","go语言",-1)
fmt.Printf("str=%v\n",str) //str=go语言 go语言 hello
14)按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组:strings.Split(“hello,world,ok”,“,”)
//按照指定的某个字符,为分割标识,
// 将一个字符串拆分成字符串数组:strArr :=strings.Split("hello,world,ok",",")
for i :=0;i<len(strArr);i++{fmt.Printf("str[%v]=%v\n",i,strArr[i])
}
fmt.Printf("strAr=%v\n",strArr)//strAr= [hello world ok]
}
//输出结果为:
str[0]=hello
str[1]=world
str[2]=ok
strAr=[hello world ok]
15)将字符串的字母进行大小写转换:strings.ToLower(“GO”) //go strings.ToUpper(“GO”) //GO
str = "goLang Hello"
str= strings.ToLower(str)
fmt.Printf("str=%v\n",str)//str=golang hello
//将字符串的字母全部转换成大写
str= strings.ToUpper(str)
fmt.Printf("str=%v\n",str)//str=GOLANG HELLO
16)将字符串左右两边的空格去掉:strings.TrimSpace(" tn a lone gropher ntrn ")
//将字符串左右两边的空格去掉:
// strings.TrimSpace(" tn a lone gropher ntrn ")
str=strings.TrimSpace(" tn a lone gropher ntrn ")
fmt.Printf("str=%v\n",str)//str=tn a lone gropher ntrn
17)将字符串左右两边指定的字符去掉:strings.Trim(“!hello!”,“!”)//[“hello”]//将左右两边!和""去掉
//将字符串左右两边指定的字符去掉
// :strings.Trim("!hello!","!")//["hello"]//将左右两边!和""去掉
str =strings.Trim("!hello!","!")
fmt.Printf("str=%v\n",str)//str=hello
18)将字符串左边指定的字符去掉;strings.TrimLeft(“! hello!”,“!”)//[“hello”]//将左边!和“”去掉
//将字符串左边指定的字符去掉;
// strings.TrimLeft("! hello!","!")//["hello"]//将左边!和“”去掉
str=strings.TrimLeft("! hello!","!")
fmt.Printf("str=%v\n",str)//str= hello!
19)将字符串右边指定的字符串去掉:strings.TrimRight(“! hello!”,“!”)//[“hello”]//将右边的!和""去掉
str=strings.TrimRight("! hello!","!")
fmt.Printf("str=%v\n",str)//str= !hello
20)判断字符串是否以指定的字符串开头:strings.HasPrefix(“ftp://192.168.10.1”,“ftp”)//true
//判断字符串是否以指定的字符串开头
// :strings.HasPrefix("ftp://192.168.10.1","ftp")//true
boo := strings.HasPrefix("ftp://192.168.10.1","ftp")
fmt.Printf("boo=%v\n",boo) //boo=true
21)判断字符串是否以指定的字符结束:strings.HasSuffix(“NLT_abc.jpg”,“abc”)//false
//判断字符串是否以指定的字符结束
// :strings.HasSuffix("NLT_abc.jpg","abc")//false
boo=strings.HasSuffix("NLT_abc.jpg","abc")
fmt.Printf("boo=%v\n",boo)//false
11.时间和日期相关的函数
说明:在编程中,程序员经常使用到日期相关的函数,比如:统计某段代码执行花费的时间
1)时间和日期相关的函数,需要导入time包
2)time.Time类型,用于表示时间
//看看日期和时间相关的函数和方法使用//1.获取当前时间now := time.Now()fmt.Printf("now=%v now type=%T",now,now)//输出结果如下
// now=2023-08-23 18:37:45.9279802 +0800 CST m=+0.008001001 now type=time.Time
3)获取当前时间的方法
now :=time.Now()//now的类型就是time.Time
4)如何获取其他的日期
//2.通过now可以获取到年,月,日,时分秒
fmt.Printf("年=%v\n",now.Year())
fmt.Printf("月=%v\n",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("月=%v\n",int(now.Month()))//转成我们喜欢的数字
5)格式化日期时间
方式1:就是使用Printf 或者SPrintf
//格式化日期和时间
//way1
fmt.Printf("当前年月日 %02d-%02d-%02d %02d:%02d:%02d \n",
now.Year(),now.Month(),now.Day(),
now.Hour(),now.Minute(),now.Second())//当前年月日 2023-08-23 19:17:28dateStr := 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",dateStr)//dateStr=当前年月日 2023-8-23-19-21-36
方式2
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()
//输出结果如下所示
2023/08/23 19:26:21
2023-08-23
19:26:21
说明:“2006/01/02 15:04:05” 这个字符串的各个数字是固定的,必须这样写。
“2006/01/02 15:04:05”这个字符串各个数字可以自由组合,这样可以按照程序需求来返回时间和日期
6)时间的常量
const{Nanosecond Duration =1 //纳秒Microsecond =1000 * Nanosecond //微秒Millisecond =1000 * Microsecond //毫秒Second =1000 * Millisecond //秒Minute = 60 *Second//分钟Hour = 60 *Minute//小时
}
常量的作用:在程序中可用于获取指定时间单位的时间,比如想得到的100毫秒 100 * time.Millisecond
7)休眠
func Sleep(d Duration)
案例:
time.Sleep(100 * time.Millisecond)//休眠100毫秒
//每隔0.1秒就打出一个数字,打印到100时就退出
i := 0
for {i++fmt.Println(i)//休眠time.Sleep(time.Millisecond * 100)if i== 100{break}
}
8)获取当前Uix时间戳和unixnano时间戳(作用是可以获取随机数字)
unix时间戳
unixnano时间戳
//unix和unixnano的使用
fmt.Printf("unix时间戳=%v unixnano时间戳=%v",now.Unix(),now.UnixNano())
//输出有以下结果:
//unix时间戳=1692792690 unixnano时间戳=1692792690199200800
实践案例:
1)编写一段代码来统计函数test3()执行的时间
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("执行这个函数一共花了%v秒",end-start)
}
12.内置函数
说明:
Golang设计者为了编程方便,提供了一些函数,这些函数可以直接使用,我们称为go的内置函数文档:https://studygolang.com/pkgdoc ->builtin
1)len:用来求长度,比如string、array、map、channel
2)new:用来分配内存,主要用来配置类型,比如int,float32,struct…返回的是指针
func main(){num1 := 100fmt.Printf("num1的类型是%T,num1的值是%v,num1的地址是%v\n",num1,num1,&num1)//输出结果如下//num1的类型是int,num1的值是100,num1的地址是0xc0420120a0num2 := new(int)//*int//num2的类型%T ==> *int//num2的值 = 地址0xc0420120b8(这个地址是系统分配,不是固定的值)//num2的地址%v==地址0xc042004030(系统分配)*num2 = 100fmt.Printf("num2的类型是%T,num2存放的值是%v,num2指向的值是%v,num2的地址是%v",num2,num2,*num2,&num2)//输出结果如下所示://num2的类型是*int,num2存放的值是0xc0420120b8,num2指向的值是0,num2的地址是0xc042004030}
3)make:用来分配内存,主要用来分配引用类型,比如chan、map、slice这个我们后面讲解
13.go的错误处理机制
先看示例代码,看看输出了什么
func test(){num1 :=10num2 :=0res :=num1/num2fmt.Println("res=",res)
}
func main(){//测试test()fmt.Println("main()下面的代码...")
}
对上面的代码进行总结
1)在默认情况下,当发生错误后(panic),程序就会退出(崩溃)
2)如果我们希望。当发生错误后,可以捕获到错误,并进行处理,保证代码可以继续执行。还可以在捕获到错误后给管理员一个提示(邮件或短信)
3)这里引出我们对错误进行处理
错误处理
1)Go语言追求简洁优雅,所以Go语言不支持传统的try…catch…finally这种处理。
2)Go中引入的处理方式是: defer,panic,recover
3)这几个异常的使用场景可以简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理
package main
import ("fmt"
)func test(){//使用defer +recover来捕获处理异常defer func() {err :=recover() //recover是内置函数,可以捕获到异常if err != nil { //说明捕获到错误fmt.Println("err=",err)}}()num1 :=10num2 :=0res :=num1/num2fmt.Println("res=",res)
}
func main(){//测试test()fmt.Println("main()下面的代码...")//执行结果如下:
err= runtime error: integer divide by zero
main()下面的代码...
错误处理的好处
进行错误处理后,程序不会轻易挂掉。如果加入预警代码,就可以让程序更加健壮,看一个案例演示:
func test(){//使用defer +recover来捕获处理异常defer func() {//err :=recover() //recover是内置函数,可以捕获到异常if err :=recover(); err != nil { //说明捕获到错误fmt.Println("err=",err)//这里就可以将错误发送给管理员...fmt.Println("发送邮件给@wew")}}()num1 :=10num2 :=0res :=num1/num2fmt.Println("res=",res)
}
func main(){//测试for {test()time.Sleep(time.Second)}fmt.Println("main()下面的代码...")
}
自定义错误
Go程序中,也支持自定义错误,使用errros.New和panic内置函数
1)errors.New(“错误说明”),会返回一个erro类型的值,表示一个错误
2)panic内置函数,接收一个interface{}类型的值(也就是任何值了)作为参数。可以接收erro类型的变量,输出错误信息,并退出程序
案例演示:
//函数去读取以配置文件init.conf的信息
//如果文件名不正确,我们就返回一个自定义的错误func readconf (name string) (err error){if name == "config.ini"{//读取return nil}else{//返回一个自定义的错误return errors.New("读取文件错误")}
}
func test02() {err :=readconf("config.ini")if err != nil{//如果读取文件发生错误就输出这个错误并终止程序panic(err)}fmt.Println("test02()继续执行")
}
对上面的代码进行总结
1)在默认情况下,当发生错误后(panic),程序就会退出(崩溃)
2)如果我们希望。当发生错误后,可以捕获到错误,并进行处理,保证代码可以继续执行。还可以在捕获到错误后给管理员一个提示(邮件或短信)
3)这里引出我们对错误进行处理
错误处理
1)Go语言追求简洁优雅,所以Go语言不支持传统的try…catch…finally这种处理。
2)Go中引入的处理方式是: defer,panic,recover
3)这几个异常的使用场景可以简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理
package main
import ("fmt"
)func test(){//使用defer +recover来捕获处理异常defer func() {err :=recover() //recover是内置函数,可以捕获到异常if err != nil { //说明捕获到错误fmt.Println("err=",err)}}()num1 :=10num2 :=0res :=num1/num2fmt.Println("res=",res)
}
func main(){//测试test()fmt.Println("main()下面的代码...")//执行结果如下:
err= runtime error: integer divide by zero
main()下面的代码...
错误处理的好处
进行错误处理后,程序不会轻易挂掉。如果加入预警代码,就可以让程序更加健壮,看一个案例演示:
func test(){//使用defer +recover来捕获处理异常defer func() {//err :=recover() //recover是内置函数,可以捕获到异常if err :=recover(); err != nil { //说明捕获到错误fmt.Println("err=",err)//这里就可以将错误发送给管理员...fmt.Println("发送邮件给@wew")}}()num1 :=10num2 :=0res :=num1/num2fmt.Println("res=",res)
}
func main(){//测试for {test()time.Sleep(time.Second)}fmt.Println("main()下面的代码...")
}
自定义错误
Go程序中,也支持自定义错误,使用errros.New和panic内置函数
1)errors.New(“错误说明”),会返回一个erro类型的值,表示一个错误
2)panic内置函数,接收一个interface{}类型的值(也就是任何值了)作为参数。可以接收erro类型的变量,输出错误信息,并退出程序
案例演示:
//函数去读取以配置文件init.conf的信息
//如果文件名不正确,我们就返回一个自定义的错误func readconf (name string) (err error){if name == "config.ini"{//读取return nil}else{//返回一个自定义的错误return errors.New("读取文件错误")}
}
func test02() {err :=readconf("config.ini")if err != nil{//如果读取文件发生错误就输出这个错误并终止程序panic(err)}fmt.Println("test02()继续执行")
}
预知后事,请看我下一篇博客啦!