上一篇内容:Golang入门
流程控制
单分支控制
if
语法格式:
// if 后面可以有一个语句
// 例如 if err:revover(); err!=nil {
if 条件表达式 {
// 执行代码块
}
代码示例:
package main
import "fmt"
func main() {
var age int
fmt.Println("请输入年龄:")
fmt.Scanln(&age )
if age > 18{
fmt.Println("你年龄大于18,要对自己的行为负责!")
}
}
打印输出:
请输入年龄:
21
你年龄大于18,要对自己的行为负责!
双分支语句
if-else
语法格式:
if 条件表达式 {
// 执行代码块
}else{
// 执行代码块
}
示例代码:
package main
import "fmt"
func main() {
var age int
fmt.Println("请输入年龄:")
fmt.Scanln(&age )
if age > 18{
fmt.Println("你年龄大于18,要对自己的行为负责!")
}else{
fmt.Println("你的年龄不大这次放过你了")
}
}
打印输出:
请输入年龄:
13
你的年龄不大这次放过你了
多分支判断
if else if
package main
import "fmt"
func main() {
var score int
fmt.Println("请输入成绩:")
fmt.Scanln(&score)
if score > 18{
fmt.Println("你年龄大于18,要对自己的行为负责!")
}else if score > 80 && score <= 90{
fmt.Println("你的年龄不大这次放过你了")
}else if score >= 60 && score <=80{
fmt.Println("奖励一个 ipad")
}else{
fmt.Println("什么都不奖励")
}
}
打印输出:
请输入成绩:
12
什么都不奖励
switch…case 语句
- case/switch 后是一个表达式(即:常量值、变量、一个有返回值的函数等都可以)




-
default 语句不是必须的。
-
switch 后面可以不带表达式,类似
if-else
分支来使用。



switch 和 if 的比较:
- 如果判断的具体数据不多,并且符合整数、浮点数、字符、字符串这几种类型,建议使用 switch 语句,简洁高效。
- 其他情况:对区间判断和结果为 bool 类型的判断,使用 if,if 使用范围更广。
循环语句
For 循环的表现形式
- 传统意义上的
for
循环。
Golang 里面的 for 循环和和传统的没太大的语法差别。而循环条件是返回一个布尔表达式。
package main
import (
"fmt"
)
func main() {
for i:=1; i < 10; i++ {
fmt.Println("hello world")
}
}
这里最终运行完成之后, i 的值为 11
- for 循环的第二种方式:只有一个条件语句
- 将变量初始化和变量迭代写到其它位置,相当于把
;
给省略了。
- 将变量初始化和变量迭代写到其它位置,相当于把
// for 循环的第二种写法
j := 1 // 循环变量初始化
for j < 10{ // 循环条件
fmt.Println("hello world~~~")
j++ // 循环变量迭代
}
- for 循环还可以什么条件都没有
这个书写形式也就是 Golang 当中 while 语句的书写形式,因此 Go 里面没有 while 或者 do while的书写形式。被整合进了 for 语句当中。
// for 循环的第三种写法,这种写法通常会配合 break 使用
k:=1
for {
if k <= 10{
fmt.Println("ok~")
}else{
break
}
k++
}
for - range
表达式
假设我们现在要遍历一个字符串,传统上我们会如下进行遍历:
var s string = "qwer!"
for i := 0; i < len(s); i++ {
fmt.Printf("%c\n", s[i])
}
而通过 for-range
我们可以这么遍历:
- 对于 for-range 遍历方式而言,是按照字符方式进行遍历。
for index, value := range s{
fmt.Printf("index=%d, value=%c\n",index, value)
}
打印输出:
index=0, value=q
index=1, value=w
index=2, value=e
index=3, value=r
index=4, value=!
注意事项和细节说明:
如果我们的字符串含有中文,那么传统的遍历字符串方式,就是错误,会出现乱码。原因是传统的对字符串的遍历是按照字节来遍历,而一个汉字在 utf8 编码是对应 3 个字节。
如何解诀:
法1:需要要将 str 转成 []rune
切片。
var s string = "qwer!项炤程"
s2 := []rune(s)
for i := 0; i < len(s2); i++ {
fmt.Printf("%c\n", s2[i])
}
法2:通过 for-range 的形式来进行遍历
var s string = "qwer!项炤程"
// for range 是按照字符的方式来进行遍历的
for index, value := range s{
fmt.Printf("index=%d, value=%c\n",index, value)
}
练习
- 打印1~100之间所有是9的倍数的整数的个数及总和
package main
import (
"fmt"
)
func main() {
var count int = 0
var sum int = 0
for i:=1;i<=100;i++{
if i % 9 == 0{
count ++
sum += i
}
}
fmt.Printf("count=%v,sum=%v\n", count, sum)
}
打印输出:
count=11,sum=594
- 完成下面表达式输出:
0 + 6 = 6
1 + 5 = 6
2 + 4 = 6
3 + 3 = 6
4 + 2 = 6
5 + 1 = 6
6 + 0 = 6
var sum int = 6
for i := 0; i <=6; i ++{
fmt.Printf("%v + %v = %v\n",i,sum - i,sum)
}
break 关键字
break 用于终止 某个语句块,用于中断当前 for 循环或跳出 switch 语句:
......
break
......
}
- 随机打印 0-100 的整数,记录打印
rand.Seed(time.Now().Unix())
var count int = 0
for{
count ++
n := rand.Intn(100) + 1
fmt.Println("n = ",n)
if n == 99{
break
}
}
fmt.Printf("一共耗费:%v次",count)
打印输出:
n = 97
n = 3
...
...
n = 99
一共耗费:22次
需要注意的细节:
- break 语句出现多层嵌套的语句块中时,可以通过标签 指明要终止的是哪一层语句块
label2:
for i:=0; i< 4; i++{
// label1:
for j := 0;j < 10; j++{
if j ==2{
// break
break label2 // 输出: j = 0 \n j = 1
}
fmt.Println("j=", j)
}
}
- break 会默认跳出最近的
for
循环 - break 后面可以指定标签,跳出标签对应的 for 循环。
continue 关键字
介绍:
- continue 语句用于 结束本次循环,继续执行下一次循环。
- continue 语句出现在多层嵌套的循环语句中,可以通过标签指明要跳过的哪一层循环,和前面的 break 标签的使用一样。

goto
- Go 语言的 goto 语句可以无条件的转移到程序中指定的行。
- goto 语句通常与条件语句配合使用。可以用来进行条件转移,跳出循环体等功能。
- 在 Go 程序设计中一般不主张使用goto语句,以免造成程序流程的混乱,是理解和调试程序都产生困难。
基本语句:
goto label
...
label:statement
示例:
package main
import "fmt"
func main() {
fmt.Println("ok1")
goto label
fmt.Println("ok2")
fmt.Println("ok3")
fmt.Println("ok4")
label:
fmt.Println("ok5")
}
打印输出:
ok1
ok5
return
return使用在方法或者函数中,表示跳出所在的方法或函数
函数
概念
为完成某一功能的程序指令(语句)的集合,称为函数。在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("res=", result)
}
打印输出:
res= 3.5
底层分析
package main
import "fmt"
func test(n1 int){
n1 = n1 +1
fmt.Println("test() n1=",n1)
}
func main() {
n1 := 10
// 调用 test
test(n1)
fmt.Println("main() n1=", n1)
}
打印输出:
test() n1= 11
main() n1= 10

Go 和 Python 一样,函数可以返回多个值
func 函数名 (形参列表) (返回值类型列表){
语句...
return 返回值列表
}
- 如果返回多个值,在接受时,希望忽略某个返回值,则使用
_
符号表示占位忽略 - 如果返回值类型只有一个,(返回值类型列表)可以不写 ()
函数的注意事项
- 函数的形参列表可以是多个,返回值列表也可以是多个。
- 形参列表和返回值列表的数据类型可以是值类型和引用类型。
- 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其它包文件使用,类似public, 首字母小写,只能被本包文件使用,其它包文件不能使用,类似private。
- 函数中的变量是局部的,函数外不生效[案例说明]
- 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。
- 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。
package main
import "fmt"
func test03(n1 *int){
*n1 = *n1 + 10
fmt.Println("test02() n1=", *n1)
}
func main() {
// n1 := 10
// // 调用 test
// test(n1)
// fmt.Println("main() n1=", n1)
// sum := getSum(10, 20)
num := 20
test03(&num)
fmt.Println("main sum =",num)
}
打印输出:
test02() n1= 30
main sum = 30
-
Go 函数不支持函数的重载。
-
在Go中,函数也是一种数据类型, 可以赋值给一个变量, 则该变量就是一个函数类型的变量了,通过该变量可以对函数调用。
package main
import "fmt"
func getSum(n1 int, n2 int) int {
return n1 + n2
}
func main() {
a := getSum
fmt.Printf("a 的类型是%T, \n getsum 类型是 %T\n",a, getSum)
}
打印输出:
a 的类型是func(int, int) int,
getsum 类型是 func(int, int) int
- 函数既然是一种数据类型,因此在 Go 中,函数也可以作为形参,并且调用。
package main
import "fmt"
func myFun(funcvar func(n1 int, n2 int) int, num1 int, num2 int) (int){
return funcvar(num1, num2)
}
func main() {
res2 := myFun(getSum, 50, 60)
fmt.Println("res2:",res2)
}
打印输出:
res2: 110
- 为了简化数据类型定义,Go 支持自定义数据类型
基本语法: type 自定义数据类型名数据类型// 理解:相当于一个别名
案例: type myInt int // 这时myInt就等价int来使用了.
案例: type mySum func (int, int)int // 这时mySum就等价一个函数类型func (int, int) int
package main
import "fmt"
func main() {
// var num int
// fmt.Println("num=", num)
type myInt int // 给 int 取了别名, 在 go 中, myInt 和 int 虽然都是 int 类型, 但是 go 认为这俩个还是俩不同类型
var num1 myInt //
var num2 int
num1 = 40
num2 = int(num1) // 这里依然需要强转,因为 Go 认为myInt 和Int 是两种类型.
fmt.Println("num1=", num1,"num2=", num2)
}

num1= 40 num2= 40
- 针对函数进行自定义类型
package main
import "fmt"
type myFunType func(int, int) int // 这个时候
// func myFun(funcvar func(n1 int, n2 int) int, num1 int, num2 int) (int){
func myFun(funcvar myFunType, num1 int, num2 int) (int){
return funcvar(num1, num2)
}
func main() {
res3:= myFun(getSum, 500, 600)
fmt.Println("res3=", res3)
}
打印输出:
res3= 1100
- 针对函数返回值命名
package main
import "fmt"
// 支持对返回值命名
func getSumAndSub(n1 int, n2 int) (sum int,sub int) {
// 函数里面赋值的时候就不需要考虑返回值的顺序关系了
sum = n1 + n2
sub = n1 - n2
return
}
func main() {
// 指定返回值名称案例
sum, sub := getSumAndSub(60, 50)
fmt.Printf("sum= %v, sub = %v",sum, sub)
}
打印输出:
sum= 110, sub = 10
-
使用
_
标识符,忽略返回值 -
Go 支持可变参数,相当于 Python 中的
*args
// 支持 0 到多个参数
func sum(args... int)sum int{
}
// 支持1 到多个参数
func sum(n1 int, args int)sum int{
}
说明:
- args 是 slice 切片,通过 args[index] 可以访问到各个值。
- 如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后。
package main
import "fmt"
func sumFunc(n1 int, args ... int) (s int) {
s = n1
// 遍历 args
for i := 0; i < len(args);i++{
s += args[i]
}
return
}
func main() {
// 测试可变参数返回值
s := sumFunc(1,2,3,4,5)
fmt.Printf("sum=%v", s)
}
打印输出:
sum=15
- 练习


package main
import "fmt"
func swape(n1 *int, n2 *int){
// 注意这里需要用指针类型, 而不能用基本数据类型来进行交换
// n1, n2 = n2, n1
*n1, *n2 = *n2, *n1
}
func main() {
a := 1
b := 2
fmt.Printf("a=%v,b = %v",a,b)
swape(&a,&b)
fmt.Printf("a=%v,b = %v",a,b)
}
打印输出:
a=1,b = 2a=2,b = 1
init 函数
使用
每个源文件都可以包含个init函数,该函数会在main函数执行前,被Go运行框架调用,也
就是说 init 会在 main 函数前被调用。
package main
import "fmt"
// 通常可以在 init 函数中完成初始化工作
func init() {
fmt.Println("init()...")
}
func main() {
fmt.Println("main()...")
}
打印输出:
init()...
main()...
注意事项和细节
- 如果一个文件同时包含全局变定义,init 函数 和 main函数,则执行的流程全局变量定义->init函数> main函数。
- init 函数是为了完成一些初始化的工作,示例:
util.go:

main.go:

面试题:案例如果 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)
// 通过 a 完成调用
a := func(n1 int, n2 int) int {
return n1 - n2
}
res2 := a(40, 50)
fmt.Println("a=", res2)
}
打印输出:
res1= 30
a= -10
- 全局匿名函数:
package main
import (
"fmt"
)
var (
// func1 就是一个全局匿名函数
func1 = func(n1 int, n2 int)int{
return n1 * n2
}
)
func main() {
res3 :=func1(2,3)
fmt.Println("res3=", res3)
}
打印输出:
res3= 6
闭包
package main
import "fmt"
func funcUpper()(func(int) int){
// n 类似于类对象, 当函数调用后, 每次调用都是访问的同一个 n
var n int = 10
return func (x int) int{
n = n + x
return n
}
}
func main() {
f := funcUpper()
fmt.Println(f(1))
fmt.Println(f(1))
}
打印输出:
9
10
说明:
-
返回的是一个匿名函数。但是这个匿名函数引用到函数外的 n。因此这个匿名函数就和 n 形成一个整体,构成闭包。
-
当我们反复调用 f 函数时,因为 n 是初始化一次,因此每调用一次就进行了累加。
-
要弄清楚闭包的关键,就是分析出返回函数使用到了哪些变量(因为函数和它引用到的变量共同构成了闭包)。
优点:
我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入后缀名,比如 jpg ,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。
defer
介绍
- 当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈中【暂时称该栈为 defer 栈】,然后继续执行函数下一个语句。
- 当函数执行完毕后,在从 defer 栈中,依次从栈顶取出语句执行(注:遵守栈先入后出的机
制),所以同学们看到前面案例输出的顺序。
package main
import (
"fmt"
)
func sum(n1 int, n2 int) int {
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 将语句放入到栈时,也会将相关的值拷贝同时入栈。
package main
import (
"fmt"
)
func sum(n1 int, n2 int) int {
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() {
sum(10, 20)
}
打印输出:
ok3 res= 32
ok2 n2= 20
ok1 n1= 10
defer 的价值
defer 最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源。

-
在 defer 后,可以继续使用创建资源。
-
当函数执行完毕后,系统会依次取出 defer 栈中,取出语句,关闭资源。
-
这种机制较为简洁。
包
包的本质实际上就是创建不同的文件夹,来存放程序文件。
打包的基本语法:
- package 包名
引入包的基本语法:
- import “包的路径”
1. 快速入门
包快速入门 Go 相互调用函数,我们将 funcCal 定义到文件 utils.go,将 utils.go 放到一个包中,当其它文件需要使用到 utils.go 的方法时,可以 import 该包,就可以使用了。【为演示:新建项目目录结构】
项目目录结构如下:

utils.go:
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
}
main.go 当中的代码如下:
package main
import (
"fmt"
"go_code/chapter06/functiondemo01/utils"
)
func main() {
var n1 float64 = 1.2
var n2 float64 = 2.3
var operator byte = '+'
result := utils.Cal(n1, n2, operator)
fmt.Println("res=", result)
}
打印输出:
res= 3.5
2. 注意事项
-
在给一个文件打包时,该包对应一个文件夹比如这里的 utils 文件夹对应的包名就是 utils,文件的包名通常和文件所在的文件夹名一致般为小写字母。
-
当一个文件要使用其它包函数或变量时,需要引入对应的包。
-
package 在文件的第一行,然后是 import 指令。
-
推荐开启 GO111MODULE = on, 然后再在自定义的目录下创建工程文件。如果 GO111MODULE = off ,那么我们创建的工程都要在
$GOPATH/src
这个目录下面,否则包的引用将出错。
-
-
为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的 public ,这样才能跨包访问。比如 utils.go 的


-
在同一个包下,不能有相同的函数名(也不能有相同的全局变量名),否则重复定义。
-
如果你要编译成一个可执行程序文件,就需要将这个包声明为 main,即package main,这个就是一个语法规范,如果你是写一个库,包名可以自定义。
-
我们只要切换到
main
目录下,然后执行go build
这一命令。 -
此时我们就可以看到文件夹里面生成了一个 main 的可执行文件。
-
字符串
和字符串有关的常用函数有如下几个,其余的可以参考官方文档。
常用方式
- 统计字符串的长度,按字节(byte)
str := "hello北" // golang 的编码统一为 utf-8 (字母和数字占一个字节, 汉字占`三个字节`)
fmt.Println("str len = ", len(str))
打印输出:
str len = 8
- 字符串遍历, 同时有中文问题
r:= []rune(str)
str2 := "hello北京"
r := []rune(str2)
for i := 0; i < len(r); i++ {
fmt.Printf("字符 = %c\n", r[i])
}
字符 = h
字符 = e
字符 = l
字符 = l
字符 = o
字符 = 北
字符 = 京
- 字符串转整数:
n, err := strconv.Atoi("12")
n, err := strconv.Atoi("123")
// nil 类似于 None 但还是有一定区别的
if err != nil {
fmt.Println("转化错误")
} else {
fmt.Println("转成的结果是:", n)
}
打印输出:
转成的结果是: 123
- 整数转字符串
str = strconv.Itoa(12345)
str = strconv.Itoa(12345)
fmt.Printf("str=%v, str=%T\n", str, str)
打印输出:
str=12345, str=string
- 字符串转 []byte 切片:
var bytes = []byte("hello.go")
var bytes = []byte("hello go")
fmt.Printf("bytes=%v\n", bytes)
打印输出:
bytes=[104 101 108 108 111 32 103 111]
[]btte
转字符串:str = string([]byte{97,98,99})
str = string([]byte{97, 98, 99})
fmt.Println(str)
打印输出:
abc
- 10 进制转 2,8 16 进制:
str = strconv.FormatInt(123, 2) // 2-> 8, 16
, 返回对应的字符串
str = strconv.FormatInt(123, 2)
fmt.Printf("123 对应的二进制是:%v\n", str)
str = strconv.FormatInt(123, 16)
fmt.Printf("123 对应的16进制是:%v,type=%T\n", str, str)
打印输出:
123 对应的二进制是:1111011
123 对应的16进制是:7b,type=string
- 查找子串是否在指定的字符串中:
string.Contains("seafood", "foo") // true
b := strings.Contains("seafood", "fd")
fmt.Printf("b = %v\n", b)
打印输出:
b = false
- 统计一个字符串有几个指定的子串, strings.Count(“caheee”,“e”) // 4
num := strings.Count("cuuuh", "e")
fmt.Printf("num = %v\n", num)
打印输出:
num = 0
- 不区分大小写的字符串比较(==是区分大小写的):
fmt.Println(strings.EqualFold("abc"))
b = strings.EqualFold("abc", "Abc")
fmt.Printf("b = %v\n", b) // true
fmt.Println("结果", "abc" == "Abc") // 区分大小写
打印输出:
b = true
结果 false
- 返回子串在字符串第一次出现的 index 值, 如果没有返回 -1,
strings.Index("NLT_abc", "abc") //4
index := strings.Index("NLT_abc", "abc")
fmt.Printf("index=%v \n", index)
打印输出:
index=4
- 返回子串在字符串最后一次出现的index, 如没有返回-1 :
strings LastIndex("go golang". "go")
index = strings.LastIndex("go golang", "go")
fmt.Printf("index=%v\n", index)
打印输出:
index=3
- 将指定的子串替换成另外一个子串:
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\n", str)
打印输出:
str=北京 北京 hello
- 按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组:
strings. Split("hello,wrold,ok", ",")
strArray := strings.Split("hello,world,ok", ",")
for i:=0;i<len(strArray);i++{
fmt.Printf("str[%v] = %v,",i,strArray[i])
}
fmt.Printf("strArr=%v\n", strArray)
打印输出:
str[0] = hello,str[1] = world,str[2] = ok,strArr=[hello world ok]
- 将字符串的字母进行大小写的转换:
strings.ToLower("Go") // go strings.ToUpper("Go") // GO
str = "goLang Helllo"
str = strings.ToUpper(str)
fmt.Println(str)
打印输出:
GOLANG HELLLO
- 将字符串左右两边的空格去掉:
strings.TrimSpace(" tn a lone gopher ntm" )
str = strings.TrimSpace(" tn a lone gopher ntm" )
fmt.Printf("str=%q\n",str) // q 用来给值加双引号
打印输出:
str="tn a lone gopher ntm"
- 将字符串左右两边指定的字符去掉:
strings.Trim("! hello!", " !")
! 和 "” 去掉。
str = strings.Trim("! hello!", " !")
fmt.Printf("str=%q\n",str)
打印输出:
str="hello"
- 将字符串左边指定的字符去掉:
strings.TrimLeft("! hello!","!") // ["hello"]
strings.TrimLeft("! hello!","!")
- 将字符串右边指定的字符去掉: strings.TrimRight("! hello! ", “!”) // [“hello”] //将右边!和"去掉
strings.TrimRight("! hello! ", "!")
- 判断字符串是否以指定的字符串开头:
strings.HasPrefix(" fp://92/ 168.10.1", "ftp")// tme
b = strings.HasPrefix("ftp://192.168.10.1", "ftp")// tme
fmt.Printf("b=%v\n",b)
打印输出:
b=true
- 判断字符串是否以指定的字符串结束:
strings.Hasuffx("NLT_ abc.jpg", "abc) //false
b = strings.HasSuffix("NLT_ abc.jpg", "abc") //false
fmt.Printf("b=%v", b)
打印输出:
b=false
切片
时间和日期
日期和时间相关函数都是放在 time 包里面。
获取时间
- 获取当前时间
package main
import (
"fmt"
"time"
)
func main() {
// 看看日期和时间相关函数和方法
// 1. 获取当前时间
now := time.Now()
fmt.Printf("now =%v, type=%T\n", now, now)
}
打印输出:
now =2021-11-17 13:11:28.55616 +0800 CST m=+0.000114418, type=time.Time
- 获取其他信息
// 2. 通过 now 可以获取年月日, 时分秒
fmt.Printf("年=%v\n", now.Year())
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())
打印输出:
年=2021
月=11
日=17
时=13
分=11
秒=28
格式化日期时间
方式一:
使用 Printf 或者 Sprintf
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)
打印输出:
当前年月日 2021-11-17 13:26:59
dateStr=当前年月日 2021-11-17 13:26:59
方式二:
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()
打印输出:
171711-11-17 13:26:59
2021-11-17
13:26:59
**注意:**上面的 2006-01-02 14: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
结合 sleep
来使用时间常量
// 需求: 每隔一秒钟打印一个数字, 打印到 100 就退出
num:=0
for{
num ++
fmt.Println(num)
if num == 100{
break
}
// 休眠
time.Sleep(time.Millisecond*100)
}
打印输出:
1
2
3
...
99
100
获取 Unix 时间戳
// Unix 和 UnixNano 的使用
fmt.Printf("unix 时间戳=%v Unixnano 时间戳=%v\n", now.Unix(), now.UnixNano())
打印输出:
unix 时间戳=1637129965 Unixnano 时间戳=1637129965528273000
计算函数执行时间
package main
import (
"fmt"
"strconv"
"time"
)
func test3() {
str := ""
for i:= 0; i < 100000;i ++{
str += "hello" + strconv.Itoa(i)
}
}
func main() {
// 在执行 test03 前, 先获取到当前的 Unix 时间戳
start := time.Now().Unix()
test3()
end := time.Now().Unix()
fmt.Printf("执行 test03()为%v秒\n", end- start)
}
打印输出:
执行 test03()为3秒
内置函数
Golang 设计者为了编程方便,提供了一些列的函数,这些函数可以直接使用。具体文档可以参看:https://pkg.go.dev/builtin
下面来介绍其中的几个内置函数:
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)
// num2 的类型是 %T => *int
// num2的值 = 地址 0x14000096020
// num2的地址 %v = 地址 0x1400008c020
// num2 指向的值 = 100
*num2 = 100
fmt.Printf("num2 的类型是%T, num2的值是=%v, num2 的地址是 %v, num2 指向的值是:%v\n", num2, num2, &num2, *num2)
}
打印输出:
num1 的类型 int,num1 的值=100, num1 的地址=0x14000096008
num2 的类型是*int, num2的值是=0x14000096020, num2 的地址是 0x1400008c020, num2 指向的值是:100
内存图:

make
用来分配内存,主要用来分配引用类型,比如 chan,map, slice。
var slice []float64 = make([]float64, 5, 10)
slice[1] = 10
slice[2] = 20
fmt.Println(slice)
fmt.Printf("slice size = %v", cap(slice))
打印输出:
[0 10 20 0 0]
slice size = 10strSlice= [tom jack marry]
异常
- Go语言追求简洁优雅。所以,Go语言不支持传统的
try catch... finally
这种处理。 - Go中引入的处理方式为: defer,panic, recover
- 这几个异常的使用场景可以这么简单描述:Go中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理。
捕获 1/0 异常
package main
import (
// "errors"
"fmt"
)
func test() {
// 使用 defer + recover 的方式来捕获异常
defer func () {
err := recover() // recover 是一个内置函数,可以捕获到异常
if err != nil{
fmt.Println("err = ", err)
// 这里就可以将错误信息发送给管理员
fmt.Println("发送邮件给管理员admin@souhu.com")
}
}()
num1 := 10
num2 := 0
res := num1 / num2 //panic: runtime error: integer divide by zero
fmt.Println("res=", res)
}
func main() {
test()
fmt.Println("我来了...")
}
打印输出:
err = runtime error: integer divide by zero
发送邮件给管理员admin@souhu.com
我来了...
自定义异常
Go 当中,使用 errors.New 和 panic 内置函数来自定义异常。
- errors.New(“错误说明”),会返回一个 error 类型的值,表示一个错误。
- panic 内置函数,接受一个 interface{} 类型的值(也就是任何值了)作为参数。可以接受 error 类型的变量,输出错误信息,并退出程序。
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 后面的代码继续执行")
}
func main() {
// test()
// fmt.Println("我来了...")
// 测试自定义错误的使用
test02()
fmt.Println("main() 下面的代码...")
}
打印输出:
test02 后面的代码继续执行
main() 下面的代码...
我们如果更改一下readConf
里面的条件,即可产生错误的抛出。
总结:Go 里面的 panic
就类似于 raise,errors.New
就类似于 Exception 基类,而 raise 就被 return 给替代了。