为什么需要函数
- 防止代码冗余
- 利于代码维护
函数基本概念
- 为完成某一功能的程序指令(语句)的集合,称为函数
- 在go中,函数分为:自定义函数、系统函数(查看go编程手册)
函数的基本语法
func 函数名(形参列表) (返回值类型列表) {
语句
...
return 返回值列表
}
- 形参列表: 表示函数的输入
- 函数中的语句: 表示为了实现某一功能代码块
- 函数可以有返回值,也可以没有
package main
import (
"fmt"
)
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
}
func main() {
var n1 float64 = 1.2
var n2 float64 = 2.3
var operator byte = '+'
result := cal(n1, n2, operator)
fmt.Println("result=", result)
}
包
- 在实际的开发中,我们往往需要在不同的文件中,去调用其它文件的定义的函数,比如main.go中,去使用utils.go文件中的函数
- 现在有两个程序员共同开发一个go项目,程序员小明希望定义函数Cal,程序员小强也想定义函数也叫Cal
包的原理
包的本质实际上就是创建不同的文件夹,来存放程序文件
包的基本概念
go的每一个文件都属于一个包,也就是说go是以包的形式来管理文件和项目目录结构的
包的三大作用
- 区分相同名字的函数、变量等标识符
- 当程序文件很多时,可以很好的管理项目
- 控制函数、变量等访问范围,即作用域
包的相关说明
- 打包基本语法
package 包名
- 引入包的基本语法
import "包的路径"
go相互调用函数,我们将func Cal定义到文件utils.go,将utils.go放到一个包中,当其它文件需要使用到utils.go的方法时,可以import 该包,就可以使用了
package main
import (
"fmt"
"go_code/project01/utils"
)
func main() {
var n1 float64 = 1.2
var n2 float64 = 2.3
var operator byte = '+'
result := utils.Cal(n1, n2, operator)
fmt.Println("result=", result)
}
package utils
import (
"fmt"
)
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文件夹对应的包名就是utils,文件的包名通常和文件所在的文件夹名一致,一般为小写字母
- 当一个文件要使用其它包函数或变量时,需要先引入对应的包
import (
"包名"
"包名"
)
-
package指令在文件第一行,然后是import指令
-
在import包时,路径从$GOPATH的src下开始,不用带src,编译器会自动从src下开始引入
-
为了让其它包的文件可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的public,这样才能跨包访问
-
在访问其它包函数,变量时,其语法是 包名.函数名
-
如果包名较长,go支持给包取别名 (注意:取别名后,原来的包名就不能使用了)
说明:如果给包取了别名,则需要使用别名来访问该包的函数和变量
- 在同一包下,不能有相同的函数名(也不能有相同的全局变量名)
- 如果你要编译成一个可执行程序文件,就需要将这个包声明为main,即 package main 这个就是一个语法规范,如果你是写一个库,包名可以自定义
d:\goproject\src
go build -o main.exe go_code\chapter06\fun\main\
d:\goproject\src
go build -o ..\bin\main.exe go_code\chapter06\fun\main\
函数-调用过程
栈:先入后出
说明:
- 在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其它的栈的空间区分开来
- 在每个函数对应的栈中,数据空间是独立的,不会混淆
- 当一个函数调用完毕(执行完毕)后,程序会销毁这个函数对应的栈空间
计算两个数,并返回
package main
import (
"fmt"
)
func test(n1 int) {
n1 = n1 + 1
fmt.Println("test() n1 =", n1)
}
func getSum(n1 int, n2 int) (int) {
sum := n1 + n2
fmt.Println("getSum sum = ", sum)
return sum
}
func main() {
n1 := 10
test(n1)
fmt.Println("main() n1 =", n1)
sum := getSum(10, 20)
fmt.Println("main sum = ", sum)
}
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() {
res1, res2 := getSumAndSub(1, 2)
fmt.Printf("res1=%v res2=%v\n", res1, res2)
_, res3 := getSumAndSub(3, 9)
fmt.Println("res3=", res3)
}
函数的递归调用
一个函数在函数体内又调用了本身,称为递归调用
package main
import (
"fmt"
)
func test(n int) {
if n > 2 {
n--
test(n)
}
fmt.Println("n=", n)
}
func main() {
test(4)
}
运行结果
n= 2
n= 2
n= 3
package main
import (
"fmt"
)
func test(n int) {
if n > 2 {
n--
test(n)
} else {
fmt.Println("n=", n)
}
}
func main() {
test(4)
}
运行结果
n= 2
函数递归需要遵守的原则
- 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
- 函数的局部变量是独立的,不会相互影响
- 递归必须向退出递归的条件逼近,否则就是无限递归
- 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁
斐波那契数列 1,1,2,3,5,8,13…
package main
import (
"fmt"
)
func fbn(n int) (int) {
if n == 1 || n == 2 {
return 1
} else {
return fbn(n-1) + fbn(n-2)
}
}
func main() {
res := fbn(3)
fmt.Println("res=", res)
fmt.Println("res=", fbn(4))
fmt.Println("res=", fbn(5))
fmt.Println("res=", fbn(6))
}
已知 f(1)=3; f(n)=2*f(n-1)+1 求f(n)的值
package main
import (
"fmt"
)
func f(n int) (int) {
if n == 1 {
return 3
} else {
return 2 * f(n-1) + 1
}
}
func main() {
fmt.Println("res=", f(4))
fmt.Println("res=", f(5))
fmt.Println("res=", f(6))
}
有一堆桃子,猴子第一天吃了其中的一半,并再多吃一个,以后每天猴子都吃其中的一半,然后再多吃一个,当到第十天时,想再吃时(还没吃),发现只有1个桃子了,问:最初共多少个桃子?
package main
import (
"fmt"
)
func f(n int) (int) {
if n >= 1 && n < 10 {
return (f(n+1) +1 ) *2
}
if n == 10 {
return 1
} else {
return -1
}
}
func main() {
fmt.Println("第1天桃子数=", f(1))
}
package main
import "fmt"
func peach(n int) (int) {
if n > 10 || n < 1 {
fmt.Println("输入天数不对")
return -1
}
if n == 10 {
return 1
} else {
return (peach(n + 1) + 1) * 2
}
}
func main() {
res := peach(1)
fmt.Println(res)
}
函数使用注意事项
- 函数的形参列表可以是多个,返回值列表也可以是多个
- 形参列表和返回值列表的数据类型可以是值类型和引用类型
- 函数的命名遵循标识符命名规范,首字母大写该函数可以被本包文件和其它包文件使用,类似public,首字母小写,只能被本包文件使用,类似private
- 函数中的变量是局部的,函数外不生效
- 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值
package main
import (
"fmt"
)
func test(n1 int) {
n1 = n1 + 10
fmt.Println("test() n1= ", n1)
}
func main() {
num := 20
test(num)
fmt.Println("main() num= ", num)
}
- 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址& , 函数内以指针的方式操作变量
package main
import (
"fmt"
)
func test(n1 *int) {
*n1 = *n1 + 10
fmt.Println("test() n1= ", *n1)
}
func main() {
num := 20
test(&num)
fmt.Println("main() num= ", num)
}
- go函数不支持函数重载
- 在go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量。通过该变量可以对函数调用
package main
import (
"fmt"
)
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)
fmt.Println("res= ", res)
}
- 函数既然是一种数据类型,因此在go中,函数可以作为形参,并且调用
package main
import (
"fmt"
)
func getSum(n1 int, n2 int) (int) {
return n1 + n2
}
func myFun(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)
fmt.Println("res= ", res)
res2 := myFun(getSum, 50, 60)
fmt.Println("res2= ",res2)
}
- 为了简化数据类型定义,go支持自定义数据类型
语法:
type 自定义数据类型名 数据类型 //相当于一个别名
type myInt int // myInt就等价int来使用了
type mySum func(int,int) int //mySum就等价一个函数类型 func (int,int) int
package main
import (
"fmt"
)
func main() {
//myInt 本质也是int类型,但go认为myInt和int是两种类型
type myInt int
var num1 myInt
var num2 int
num1 = 40
num2 = int(num1)
fmt.Println("num1=", num1, "num2=", num2)
}
package main
import (
"fmt"
)
type myFunType func(int, int) int
func getSum(n1 int, n2 int) (int) {
return n1 + n2
}
func myFun2(funvar myFunType, num1 int, num2 int) (int) {
return funvar(num1, num2)
}
func main() {
res3 := myFun2(getSum, 500, 600)
fmt.Println("res3=", res3)
}
- 支持对函数返回值命名
package main
import (
"fmt"
)
func getSumAndSub(n1 int, n2 int) (sum int, sub int) {
sub = n1 - n2
sum = n1 + n2
return
}
func main() {
a, b := getSumAndSub(1, 2)
fmt.Printf("a=%v b=%v\n", a, b)
}
- 使用 _ 标识符,忽略返回值
- go支持可变参数
//支持0到多个参数
func sum(args... int) (sum int) {
}
//支持1到多个参数
func sum(n1 int, args... int) (sum int) {
}
说明:
- args不是固定的,可自定义
- args是slice切片,通过args[index]可以访问到各个值
- 如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后
package main
import (
"fmt"
)
func sum(n1 int, args... int) (int) {
sum := n1
for i := 0; i< len(args); i++ {
sum += args[i]
}
return sum
}
func main() {
res4 := sum(10, 0, -1, 90, 10, 100)
fmt.Println("res4=", res4)
}
package main
import "fmt"
func sum(n1 int, vars... int) int {
sum := n1
for i := 0; i < len(vars); i++ {
sum += vars[i]
}
return sum
}
func main() {
res4 := sum(10, 0, -1, 90, 10, 20)
fmt.Println("res4=", res4)
}
编写函数swap(n1 *int, n2 *int) 可以交换n1 和 n2的值
package main
import (
"fmt"
)
func swap(n1 *int, n2 *int) {
t := *n1
*n1 = *n2
*n2 = t
}
func main() {
a := 10
b := 20
swap(&a, &b)
fmt.Printf("a=%v, b=%v\n", a, b)
}
init函数
每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被go运行框架调用,也就是说init会在main函数前被调用
package main
import (
"fmt"
)
func init() {
fmt.Println("init()...")
}
func main() {
fmt.Println("main()...")
}
运行结果
init()...
main()...
init函数的注意事项
- 如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程
全局变量定义---> init函数---> main函数
package main
import (
"fmt"
)
var age = test()
func test() (int) {
fmt.Println("test()")
return 90
}
func init() {
fmt.Println("init()...")
}
func main() {
fmt.Println("main()... age=", age)
}
- init函数最主要的作用,就是完成一些初始化的工作
package utils
import (
"fmt"
)
var Age int
var Name string
func init() {
fmt.Println("utils 包的 init()...")
Age = 100
Name = "tom"
}
package main
import (
"fmt"
"go_code/project01/utils"
)
var age = test()
func test() (int) {
fmt.Println("test()")
return 90
}
func init() {
fmt.Println("init()...")
}
func main() {
fmt.Println("main()... age=", age)
fmt.Println("Age=", utils.Age, "Name=", utils.Name)
}
运行结果:
utils包 init()...
test()...
init()...
main()... age= 90
Age= 100 Name= tom
如果main.go 和 utils.go 都含有变量定义,init函数时,执行流程
匿名函数
go支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用
匿名函数使用方式
- 在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次
package main
import "fmt"
func main() {
res1 := func (n1 int, n2 int) int {
return n1 + n2
}(10, 20)
fmt.Println("res1=", res1)
}
- 将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数
package main
import "fmt"
func main() {
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)
}
全局匿名函数
如果将匿名函数赋给一个全局变量,那么这个匿名函数就成为一个全局匿名函数,可以在程序有效
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)
}
闭包
闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
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))
fmt.Println(f(2))
fmt.Println(f(3))
}
运行结果
11
13
16
说明:
- AddUpper是一个函数,返回的数据类型是 fun(int) int
- 闭包说明
var n int = 10
return func(x int) int {
n = n + x
return n
}
返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成一个整体,构成闭包
- 可以这样理解: 闭包是类,函数是操作,n是字段。函数和它使用到n构成闭包
- 反复的调用f函数时,因为n是初始化一次,因此每调用一次就进行累计
- 闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包
package main
import "fmt"
func AddUpper() func (int) int {
var n int = 10
var str = "hello"
return func(x int) int {
n = n + x
str += string(36)
fmt.Println("str=", str)
return n
}
}
func main() {
f := AddUpper()
fmt.Println(f(1))
fmt.Println(f(2))
fmt.Println(f(3))
}
运行结果
str= hello$
11
str= hello$$
13
str= hello$$$
16
1.编写一个函数makeSuffix(suffix string) 可以接收一个文件后缀名(比如 .jpg),并返回一个闭包
2. 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如 .jpg),则返回文件名.jpg 如果已经有 .jpg后缀,则返回原文件名
3. 使用闭包方式完成
4. strings.HasSuffix 该函数可以判断某个字符串是否有指定的后缀
package main
import (
"fmt"
"strings"
)
func makeSuffix(suffix string) func (string) string {
return func(name string) string {
if ! strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main() {
//返回一个闭包
f2 := makeSuffix(".jpg")
fmt.Println("文件名处理后=", f2("winter"))
fmt.Println("文件名处理后=", f2("bird.jpg"))
}
说明:
- 返回的匿名函数和 makeSuffix(suffix string)的 suffix变量组合成一个闭包,因为返回的函数引用到suffix这个变量
- 闭包的好处, 传统方法需要每次都传入后缀名,而闭包因为可以保留上次引用的某个值,所以传入一次就可以反复使用
函数的 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() {
res := sum(10, 20)
fmt.Println("res=", res)
}
执行结果
ok3 res= 30
ok2 n2= 20
ok1 n1= 10
res= 30
defer的注意事项
- 当go执行到一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入到一个栈中,然后继续执行函数下一个语句
- 当函数执行完毕后,再从defer栈中,依次从栈顶取出语句执行(注: 栈 先入后出的机制)
- 在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)
n1++
n2++
res := n1 + n2
fmt.Println("ok3 res=", res)
return res
}
func main() {
res := sum(10, 20)
fmt.Println("res=", res)
}
运行结果
ok3 res= 32
ok2 n2= 20
ok1 n1= 10
res= 32
defer最主要的价值
当函数执行完毕后,可以及时的释放函数创建的资源
func test() {
file = openfile(文件名)
defer file.close()
//其它代码
}
func test() {
connect = openDatabse()
defer connect.close()
//其它代码
}
说明:
- 在golang编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源),可以执行defer file.Close() defer connect.Close()
- 在defer后,可以继续使用创建资源
- 当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源
函数参数传递方式
值类型参数默认就是 值传递
引用类型参数默认就是 引用传递
其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,
不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,
一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低
值类型和引用类型
- 值类型: 基本数据类型 int系列, float系列, bool, string 、 数组、 结构体 struct
- 引用类型: 指针、slice切片、map、 管道 chan 、 interface 等都是引用类型
值传递和引用传递使用特点
- 值类型默认是值传递:变量直接存储值,内存通常在栈中分配
- 引用类型默认是引用传递: 变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收
- 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址& , 函数内以指针的方式操作变量。从效果上看类似引用
package main
import (
"fmt"
)
func test(n1 *int) {
*n1 = *n1 + 10
fmt.Println("test() n1=", *n1)
}
func main() {
num := 20
test(&num)
fmt.Println("main() num=", num)
}
变量作用域
- 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
- 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
- 赋值语句不能在函数体外
package main
import (
"fmt"
)
var age int = 50
var Name string = "jack"
func test() {
age := 10
Name := "tom"
fmt.Println("age=", age)
fmt.Println("Name=", Name)
}
func main() {
fmt.Println("age=", age)
fmt.Println("Name=", Name)
test()
}
- 如果变量是在一个代码块,比如 for/if中,那么这个变量的作用域就在该代码块
package main
import "fmt"
func main() {
for i := 0; i <= 10; i++ {
fmt.Println("i=", i)
}
var i int
for i = 0; i <= 10; i++ {
fmt.Println("i=", i)
}
fmt.Println("i=", i)
}
运行结果
i= 0
i= 1
i= 2
i= 3
i= 4
i= 5
i= 6
i= 7
i= 8
i= 9
i= 10
i= 0
i= 1
i= 2
i= 3
i= 4
i= 5
i= 6
i= 7
i= 8
i= 9
i= 10
i= 11
package main
import "fmt"
var name = "tom"
func test01() {
fmt.Println(name)
}
func test02() {
name := "jack"
fmt.Println(name)
}
func main() {
fmt.Println(name)
test01()
test02()
test01()
}
运行结果
tom
tom
jack
tom
package main
import "fmt"
var name = "tom"
func test01() {
fmt.Println(name)
}
func test02() {
name = "jack"
fmt.Println(name)
}
func main() {
fmt.Println(name)
test01()
test02()
test01()
}
运行结果
tom
tom
jack
jack
编写一个函数,从终端输入一个整数打印对应的金字塔
package main
import "fmt"
func printPyramid(totalLevel int) {
for i := 1; i <= totalLevel; i++ {
for k := 1; k <= totalLevel - i; k++ {
fmt.Print(" ")
}
for j := 1; j <= 2 * i - 1; j++ {
fmt.Print("*")
}
fmt.Println()
}
}
func main() {
var n int
fmt.Println("请输入金字塔的层数")
fmt.Scanln(&n)
printPyramid(n)
}
字符串常用的系统函数
builtin
- 统计字符串的长度,按字节 len(str)
package main
import "fmt"
func main() {
str := "hello北"
fmt.Println("str len=", len(str))
}
- 字符串遍历,同时处理有中文的问题 r := []rune(str)
package main
import "fmt"
func main() {
str2 := "hello北京"
r := []rune(str2)
for i := 0; i < len(r); i++ {
fmt.Printf("字符=%c\n", r[i])
}
}
- 字符串转整数: n, err := strconv.Atoi(“12”)
package main
import (
"fmt"
"strconv"
)
func main() {
n, err := strconv.Atoi("12")
if err != nil {
fmt.Println("转换错误", err)
} else {
fmt.Println("转换的结果是", n)
}
}
- 整数转字符串 str = strconv.Itoa(12345)
package main
import (
"fmt"
"strconv"
)
func main() {
str := strconv.Itoa(12345)
fmt.Printf("str=%v, str=%T", str, str)
}
- 字符串转 []byte: var bytes = []byte(“hello go”)
package main
import "fmt"
func main() {
var bytes = []byte("hello go")
fmt.Printf("bytes=%v\n", bytes)
}
- []byte 转 字符串: str = string([]byte{97,98,99})
package main
import "fmt"
func main() {
str := string([]byte{97, 98, 99})
fmt.Printf("str=%v\n", str)
}
- 10进制 转 2,8,16进制: str = strconv.FormatInt(123, 2)// 2 -> 8, 16
package main
import (
"fmt"
"strconv"
)
func main() {
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
package main
import (
"fmt"
"strings"
)
func main() {
b := strings.Contains("seafood", "mary")
fmt.Printf("b=%v\n", b)
}
- 统计一个字符串有几个指定的子串: strings.Count(“ceheese”, “e”) //4
package main
import (
"fmt"
"strings"
)
func main() {
num := strings.Count("ceheese", "e")
fmt.Printf("num=%v\n", num)
}
- 不区分大小写的字符串比较(== 是区分字母大小写的): fmt.Println(strings.EqualFold(“abc”, “Abc”)) //true
package main
import (
"fmt"
"strings"
)
func main() {
b := strings.EqualFold("abc", "Abc")
fmt.Printf("b=%v\n", b)
fmt.Printf("结果", "abc" == "Abc")
}
- 返回子串在字符串第一次出现的index值,如果没有返回-1: strings.Index(“NLT_abc”, “abc”) //4
package main
import (
"fmt"
"strings"
)
func main() {
index := strings.Index("NLT_abcabcabc", "abc")
fmt.Printf("index=%v\n", index)
}
- 返回子串在字符串最后一次出现的index, 如果没有返回-1: strings.LastIndex(“go golang”, “go”)
package main
import (
"fmt"
"strings"
)
func main() {
index := strings.LastIndex("go golang", "go")
fmt.Printf("index=%v\n", index)
}
- 将指定的子串替换成 另一个子串: strings.Replace(“go go hello”, “go”, “go语言”, n) n可以指定替换几个, 如果 n=-1 表示全部替换
package main
import (
"fmt"
"strings"
)
func main() {
str2 := "go go hello"
str := strings.Replace(str2, "go", "北京", -1)
fmt.Printf("str=%v str2=%v\n", str, str2)
}
str = "go go hello"
str2 = strings.Replace(str, " ", "\n", -1)
fmt.Println(str2)
- 按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组: strings.Split(“hello,world,ok”, “,”)
package main
import (
"fmt"
"strings"
)
func main() {
strArr := strings.Split("hello,world,ok", ",")
fmt.Printf("strArr=%v\n", strArr)
for i :=0; i < len(strArr); i++ {
fmt.Printf("str[%v]=%v\n", i, strArr[i])
}
}
- 将字符串的字母进行大小写转换: strings.ToLower(“Go”) //go strings.ToUpper(“Go”)//GO
package main
import (
"fmt"
"strings"
)
func main() {
str := "goLang Hello"
str = strings.ToLower(str)
fmt.Printf("str=%v\n", str)
str = strings.ToUpper(str)
fmt.Printf("str=%v\n", str)
}
- 将字符串左右两边的空格去掉: strings.TrimSpace(" tn a lone gopher ntm ")
package main
import (
"fmt"
"strings"
)
func main() {
str := strings.TrimSpace(" tn a lone gopher ntrn ")
fmt.Printf("str=%v\n", str)
}
- 将字符串左右两边指定的字符去掉: strings.Trim("! hello!", “!”) //[“hello”]
package main
import (
"fmt"
"strings"
)
func main() {
str := strings.Trim("! he!llo! ", " !")
fmt.Printf("str=%q\n", str)
}
- 将字符串左边指定的字符串去掉: strings.TrimLeft("! hello! “, " !”)
package main
import (
"fmt"
"strings"
)
func main() {
str := strings.TrimLeft("! hello! ", " !")
fmt.Printf("str=%q\n", str)
}
- 将字符串右边指定的字符去掉: strings.TrimRight("! hello! “, " !”)
package main
import (
"fmt"
"strings"
)
func main() {
str := strings.TrimRight("! hello! ", " !")
fmt.Printf("str=%q\n", str)
}
- 判断字符串是否以指定的字符串开头: strings.HasPrefix(“ftp://192.168.10.1”, “ftp”) //true
package main
import (
"fmt"
"strings"
)
func main() {
b := strings.HasPrefix("ftp://192.168.10.1", "ftp")
fmt.Printf("b=%v\n", b)
}
- 判断字符串是否以指定的字符串结束: strings.HasSuffix(“NLT_abc.jpg”, “abc”) //false
package main
import (
"fmt"
"strings"
)
func main() {
b := strings.HasSuffix("NLT_abc.jpg", "abc")
fmt.Printf("b=%v\n", b)
}
时间和日期相关函数
- 时间和日期相关函数,需要导入time包
package time
import "time"
//time包提供了时间的显示和测量用的函数,日历的计算采用的是公历
- time.Time类型,用于表示时间
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Printf("now=%v\nnow type=%T\n", now, now)
}
- 如何获取到其它的日期信息
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Printf("now=%v\nnow type=%T\n", now, 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())
}
- 格式化日期时间
方式1: 使用 Printf 或者 SPrintf
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
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)
}
方式2: 使用time.Format()方法完成
2006-01-02 15:04:05 是固定的,但可以组合
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Printf(now.Format("2006-01-02 15:04:05"))
fmt.Println()
fmt.Printf(now.Format("2006-01-02"))
fmt.Println()
fmt.Printf("15:04:05")
}
说明:
“2006-01-02 15:04:05” 这个字符串的各个数字是固定的,必须这样写
“2006-01-02 15:04:05” 这个字符串各个数字可以自由组合,这样可以按程序需求来返回时间和日期
- 时间的常量
const(
Nanosecond Duration = 1 //纳秒
Microsecond = 1000 * Nanosecond //微妙
Millisecond = 1000 * Microsecond //毫秒
Second = 1000 * Millisecond //秒
Minute = 60 * Second //分钟
Hour = 60 * Minute //小时
)
常量的作用: 在程序中可用于获取指定时间单位的时间, 比如想得到100毫秒 100 * time.Millisecond
package main
import (
"fmt"
"time"
)
func main() {
i := 0
for {
i++
fmt.Println(i)
time.Sleep(time.Millisecond * 100)
if i == 100 {
break
}
}
}
- time的 Unix 和 UnixNano的方法
https://golang.google.cn/pkg/time/#Time.Unix
func (Time) Unix
func (t Time) Unix() int64
Unix返回t作为Unix时间,这是自1970年1月1日UTC以来经过的秒数
func (Time) UnixNano
func (t Time) UnixNano() int64
UnixNano返回t作为Unix时间,这是自1970年1月1日UTC以来经过的纳秒数。如果Unix时间(以纳秒为单位)不能用int64(1678年之前或2262年之后的日期)表示,则结果是不确定的。请注意,这意味着在零时间调用UnixNano的结果是不确定的
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Printf("unix时间戳=%v unixnano时间戳=%v\n", now.Unix(), now.UnixNano())
}
统计函数test03执行的时间
package main
import (
"fmt"
"strconv"
"time"
)
func test03() {
str := ""
for i := 0; i < 100000; i++ {
str += "hello" + strconv.Itoa(i)
}
}
func main() {
start := time.Now().Unix()
test03()
end := time.Now().Unix()
fmt.Printf("执行test03()耗费时间为%v秒\n", end - start)
}
内置函数
go语言设计者为了编程方便,提供了一些函数,这些函数可以直接使用,称为go的内置函数
https://studygolang.com/pkgdoc
- len: 用来计算长度, 比如 string, array, slice, map, channel
- new: 用来分配内存,主要用来分配值类型, 比如 int, float32, struct … 返回的是指针
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 = 100
fmt.Printf("num2的类型%T, num2的值=%v, num2的地址%v\nnum2这个指针, 指向的值=%v", num2, num2, &num2, *num2)
}
- make: 用来分配内存,主要用来分配引用类型, 比如 channel, map, slice
错误处理
错误代码
package main
import "fmt"
func test() {
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res=", res)
}
func main() {
test()
fmt.Println("main下面的代码...")
}
在默认情况下,当发生错误后(panic),程序就会退出(崩溃)
说明
- go语言追求简洁优雅,所以,go语言不支持传统的 try…catch…finally 这种处理
- go中引入的处理方式为: defer, panic, recover
- go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理
使用defer+recover来处理错误
package main
import (
"fmt"
"time"
)
func test() {
defer func() {
err := recover()
if err != nil {
fmt.Println("err=", err)
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res=", res)
}
func main() {
test()
for {
fmt.Println("main()下面的代码...")
time.Sleep(time.Second)
}
}
错误处理的好处
进行错误处理后,程序不会轻易挂掉,如果加入预警代码,就可以让程序更加的健壮
package main
import (
"fmt"
"time"
)
func test() {
defer func() {
err := 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 (
"errors"
"fmt"
)
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()下面的代码...")
}