完成一个需求:输入两个数,再输入一个运算符(±*/),得到结果
使用传统方法:
func main(){
/*
请大家完成这样一个需求:
输入两个数,在输入一个运算符(+-* / ,得到结果)*/
var n1 float64 =1.2
var n2 float64 =2.3
var operator byte ='/'
var res float64
switch operator {
case '+':
res = n1 + n2
case '-':
res = n1 - n2
case '*':
res = n1 * n2
case '/':
res = n1 / n2
default:
fmt.Println("操作符错误")
}
fmt.Println("res=",res)
}
分析代码上的问题:
为完成某一个功能的程序指令(语句)的集合,称为函数
在Go中,函数分为:自定义函数、系统函数(查看GO编程手册)
函数基本语法:
func 函数名 (形象列表) (返回值列表){
执行语句...
return 返回值列表
}
1)形参列表:表示函数的输入
2)函数中的语句:表示为了实现某一个功能的代码块
3)函数可以有返回值,也可以没有
案例入门:
//将计算的功能放到一个函数中,在需要的时候进行调用
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
case '%':
res = n1 % n2
default:
fmt.Println("操作符错误")
}
return res
}
//调用函数:
func main(){
result := cal(3.2,2.1,'-') //输入参数就可以对这个函数调用
fmt.Println("result=",result) //1.1
}
为什么要用包
1)在实际的开发中,我们往往需要在不同的文件中,去调用其他文件的定义的函数,比如main.go中,去使用utils.go文件中的函数,如何实现?
2)现在有两个程序员共同开发一个go项目,两个程序员都想定义定义个交cal名字的函数怎么办
包的原理图
包的本质实际上就是创建不同的文件夹,来存储程序文件
此图用来说明一下包的原理图:
包的示意图
包的介绍
包的基本概念:
说明:go的每一个文件都是属于一个包的,也就是说go是以包的形式来管理文件和项目目录结构的
包的三大作用
打包的基本语法
pacakage 包名
引入包的基本语法
import "包的路径"
包使用的快速入门案例
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下面
对上图的说明:
(1)在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其他栈的空间区分开来
(2)在每个函数对应的栈中,数据空间都是独立的,不会混淆
(3)当一个函数调用完毕后,程序会销毁这个函数的栈空间
案例理解:
1)传入一个数进行加1的操作
package main
import (
"fmt"
)
//编写一个函数 test
func test(n1 int){
n1 = n1 + 1
fmt.Println("n1=",n1)
}
func main(){
n1 :=10
//调用test
test(n1)
}
2)计算两个数,并返回 getSum
func getSum(n1 int,n2 int) int {
//当函数有、return语句时,就是将结果返回给调用者
//即谁调用我,我就返回给谁
return n1 + n2
}
//调用
func main(){
n1 :=10
//调用test
test(n1)
sum := getSum(1,3)
fmt.Println("getSum得到的值是:",sum)//4
}
基本语法
Go函数支持返回多个值,这一点是其他编程语言没有的
func 函数名 (形参列表) (返回值类型列表){
语句...
return 返回值列表
}
注意:
(1)如果返回多个值时,在接收时,希望忽略某个返回值则使用_符号表示占位忽略
(2)如果返回值只有一个,(返回值类型列表) 可以不写()
案例演示
请编写函数,可以计算两个数的和和差,并返回结果
func getSumAndSub(n1 int,n2 int) (int,int) {
sum := n1 +n2
sub := n1 -n2
return 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个桃子。问题:最初共有多少个桃子
思路分析:
//猴子吃桃问题
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
}
}
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 + 10
fmt.Println("test2(n1)=",n1)
}
func main(){
//这里不能使用n1,因为n1是test函数的局部变量
// fmt.Println("n1=",n1)
n1 := 20
test2(n1)//30 不会影响到外面的值
fmt.Println("n1=",n1) //20
}
6)如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。从效果上来看类似引用
//n1就是*int类型
func test3(n1 *int) {
*n1 = *n1 + 10
fmt.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 + 10
fmt.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 :=getSum
fmt.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 :=getSum
fmt.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 myInt
var num2 int
num1 = 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 main
import (
"fmt"
)
//编写一个sum函数,可以求出 1到多个int的和
func sum(n1 int,args... int) int{
sum := n1
//遍历arges
for 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=float32
return 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
}
基本介绍
每一个源文件都可以包含一个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{ //1
fmt.Println("test")
return 90
}
//init函数,通常可以在init函数中完成初始调用
func init(){//先执行 2
fmt.Println("init()....")
}
func main(){ //3
fmt.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 = 100
Name= "初始化"
}
//调用
package main
import (
"fmt"
//引入包
"go_code/functions/funint/utils"
)
var age = test()
//为了看到全局变量是先被初始化的,我们这里先写函数
func test()int{ //1
fmt.Println("test")
return 90
}
//init函数,通常可以在init函数中完成初始调用
func init(){//先执行 2
fmt.Println("init()....")
}
func main(){ //3
fmt.Println("main()...age=",age)
fmt.Println("Age=",utils.Age,"Name",utils.Name)
}
3)面试题:案例如果main.go和utils.go都含有变量定义,init函数时,执行的流程又是怎么样
介绍:
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
}
介绍:
闭包就是一个函数与其相关的引用环境组合成一个整体(实体)
案例演示:
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
fmt.Println(f(2)) //13
fmt.Println(f(3)) //16
}
对上面代码的说明和总结
1)AddUpper是一个函数,返回额度数据类型是fun(int)int
2)闭包的说明:
var n int = 10
return func (x int)int{
n = n+ x
return n
}
返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成了一个整体,构成闭包
3)可以这样理解:闭包是类,函数是操作,n是字段
4)当我们反复调用f函数时,因为n是初始化1次,因此每调用一次就进行累加
5)我们要搞清楚闭包的关键,就是要分析出返回的函数他使用(引用)到哪些变量,因为函数和它引用的变量是一个整体
//累加器
func Addupper() func (int) int {
var n int = 10
var str string ="hello"
return func (x int)int{
n = n+ x
str += "x"
fmt.Println("str=",str)
return n
}
}
func main(){
//使用前面的代码
f := Addupper()
fmt.Println(f(1)) //11
fmt.Println(f(2)) //13
fmt.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
}
为什么需要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 + n2
fmt.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=11
n2++ //n2=21
res := n1 + n2
fmt.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)这种机制非常简洁,程序员们不用再为什么时机关闭资源而烦心。
基本介绍
我们在讲解函数注意事项和使用细节时,已经讲过值类型和引用类型了,这里我们在系统的总结一下,因为这是重难点,值类型参数默认就是值传递了,而引用类型参数默认就是引用传递
两种传递方式
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 := 10
Name := "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 int
fmt.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(" ")
}
说明:字符串在我们程序开发中,使用的是非常多的,常用的函数需要参考官方编程手册
1)统计字符串的长度,按字节len(str)
func main(){
//统计字符串的长度,按字节len(str)
str := "hello"
str2 := "hello我"//go的编码统一utf8(asciii的字符(字母和数字)占一个字节,一个汉字占三个字节)
fmt.Println("str len =",len(str)) //str len =5
fmt.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,16
str=strconv.FormatInt(123,2)
fmt.Printf("123对应的二进制是%v\n",str)//1111011
str=strconv.FormatInt(123,16)
fmt.Printf("123对应的十六进制是%v\n",str)//7b
8)查找子串是否在指定的字符串中:string.Contains(“sefood”,“food”) //true
//查找子串是否在指定的字符串中:string.Contains("sefood","food") //trues
b := strings.Contains("seafood","foo")
fmt.Printf("b=%v",b)//true
9)统计一个字符串有几个指定的子串: strings.Count(“ceheese”,“e”)//4
//统计一个字符串有几个指定的子串:
//strings.Count("ceheese","e")//4
c :=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
说明:在编程中,程序员经常使用到日期相关的函数,比如:统计某段代码执行花费的时间
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:28
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",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)
}
说明:
Golang设计者为了编程方便,提供了一些函数,这些函数可以直接使用,我们称为go的内置函数文档:https://studygolang.com/pkgdoc ->builtin
1)len:用来求长度,比如string、array、map、channel
2)new:用来分配内存,主要用来配置类型,比如int,float32,struct…返回的是指针
func main(){
num1 := 100
fmt.Printf("num1的类型是%T,num1的值是%v,num1的地址是%v\n",num1,num1,&num1)
//输出结果如下
//num1的类型是int,num1的值是100,num1的地址是0xc0420120a0
num2 := new(int)//*int
//num2的类型%T ==> *int
//num2的值 = 地址0xc0420120b8(这个地址是系统分配,不是固定的值)
//num2的地址%v==地址0xc042004030(系统分配)
*num2 = 100
fmt.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这个我们后面讲解
先看示例代码,看看输出了什么
func test(){
num1 :=10
num2 :=0
res :=num1/num2
fmt.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 :=10
num2 :=0
res :=num1/num2
fmt.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 :=10
num2 :=0
res :=num1/num2
fmt.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 :=10
num2 :=0
res :=num1/num2
fmt.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 :=10
num2 :=0
res :=num1/num2
fmt.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()继续执行")
}
预知后事,请看我下一篇博客啦!