Golang基础

这篇博客详细介绍了Golang的流程控制,包括单分支、双分支、多分支判断和switch...case语句,强调了switch在简洁高效方面的优势。此外,文章还深入探讨了循环语句的多种形式,如for循环,以及break、continue、goto和return的用法。接着,博客讲解了函数的概念、语法、底层分析和注意事项,包括函数的多重返回值和匿名函数。最后,提到了包、字符串处理、时间和日期的相关知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇内容: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 语句

  1. case/switch 后是一个表达式(即:常量值、变量、一个有返回值的函数等都可以)
V00La1
2. case后的各个表达式的值的数据类型,**必须和 switch 的表达式数据类型一致**。
efCPJx
3. case后面可以带**多个表达式,使用逗号间隔**。比如case表达式1,表达式2...
qcYTRu
4. case后 面的表达式如果是**常量值(字面量)**,则 **要求不能重复**
BapqCr
5. case 后面不需要带 break ,程序匹配到一个 case 后就会执行对应的代码块,然后退出 switch,如果一个都匹配不到,则执行default。
  1. default 语句不是必须的。

  2. switch 后面可以不带表达式,类似 if-else 分支来使用。

G10xYm
8. switch 后也可以直接声明一个变量,分号结束,不推荐。
y1jfv1
9. switch 穿透 flthrough,如果在 case 语句块后增加 fallthrough ,则会继续执行下一个case,也叫switch穿透。
FOA6q9
10. Type Switch:switch 语句还可以被用于type -switch来判断某个interface 变量中实际指向的 变量类型【还没有学 interface】

switch 和 if 的比较

  • 如果判断的具体数据不多,并且符合整数、浮点数、字符、字符串这几种类型,建议使用 switch 语句,简洁高效。
  • 其他情况:对区间判断和结果为 bool 类型的判断,使用 if,if 使用范围更广。

循环语句

For 循环的表现形式

  1. 传统意义上的 for 循环。

Golang 里面的 for 循环和和传统的没太大的语法差别。而循环条件是返回一个布尔表达式。

package main
import (
	"fmt"
)

func main()  {
	for i:=1; i < 10; i++ {
		fmt.Println("hello world")
	}
}

这里最终运行完成之后, i 的值为 11

  1. for 循环的第二种方式:只有一个条件语句
    • 变量初始化变量迭代写到其它位置,相当于把 ; 给省略了。
// for 循环的第二种写法
j := 1  // 循环变量初始化
for j < 10{ //  循环条件
    fmt.Println("hello world~~~")
    j++ //  循环变量迭代
}
  1. for 循环还可以什么条件都没有

这个书写形式也就是 Golang 当中 while 语句的书写形式,因此 Go 里面没有 while 或者 do while的书写形式。被整合进了 for 语句当中。

// for 循环的第三种写法,这种写法通常会配合 break 使用
k:=1
for {
    if k <= 10{
        fmt.Println("ok~")
    }else{
        break
    }
    k++
}
  1. 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次

需要注意的细节:

  1. 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 标签的使用一样。
NX0iYK
## 跳转控制语句

goto

  1. Go 语言的 goto 语句可以无条件的转移到程序中指定的行。
  2. goto 语句通常与条件语句配合使用。可以用来进行条件转移,跳出循环体等功能。
  3. 在 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返回值列表.
}
  1. 形参列表:表示函数的输入
  2. 函数中的语句:表示为了实现某一功 能代码块
  1. 函数可以有返回值,也可以没有
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
HJNZ2uz6naURYTs
## return 语法

Go 和 Python 一样,函数可以返回多个值

func 函数名 (形参列表) (返回值类型列表){
    语句...
    return 返回值列表
}
  1. 如果返回多个值,在接受时,希望忽略某个返回值,则使用 _ 符号表示占位忽略
  2. 如果返回值类型只有一个,(返回值类型列表)可以不写 ()

函数的注意事项

  1. 函数的形参列表可以是多个,返回值列表也可以是多个。
  2. 形参列表和返回值列表的数据类型可以是值类型和引用类型。
  3. 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其它包文件使用,类似public, 首字母小写,只能被本包文件使用,其它包文件不能使用,类似private。
  4. 函数中的变量是局部的,函数外不生效[案例说明]
  5. 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。
  6. 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。
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
  1. Go 函数不支持函数的重载。

  2. 在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
  1. 函数既然是一种数据类型,因此在 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
  1. 为了简化数据类型定义,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)
}
WNqy08
打印输出:
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
  1. 针对函数返回值命名
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
  1. 使用_ 标识符,忽略返回值

  2. Go 支持可变参数,相当于 Python 中的 *args

// 支持 0 到多个参数
func sum(args... int)sum int{
    
}
// 支持1 到多个参数
func sum(n1 int, args int)sum int{
    
}

说明:

  1. args 是 slice 切片,通过 args[index] 可以访问到各个值
  2. 如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后。
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
  • 练习
5yL3Nq
YyRCgs
编写一个函数 swap(n1 *int, n2 *int)可以交换 n1 和 n2 的值
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()...

注意事项和细节

  1. 如果一个文件同时包含全局变定义init 函数main函数,则执行的流程全局变量定义->init函数> main函数
  2. init 函数是为了完成一些初始化的工作,示例:

util.go:

8iHf4v

main.go:

OBd4FY

面试题:案例如果 main.go 和 utils.go 都含有变量定义,init函数时, 执行的流程又是怎么样的呢?

JRH8EX

匿名函数

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

说明:

  1. 返回的是一个匿名函数。但是这个匿名函数引用到函数外的 n。因此这个匿名函数就和 n 形成一个整体,构成闭包。

  2. 当我们反复调用 f 函数时,因为 n 是初始化一次,因此每调用一次就进行了累加。

  3. 要弄清楚闭包的关键,就是分析出返回函数使用到了哪些变量(因为函数和它引用到的变量共同构成了闭包)。

优点:

我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入后缀名,比如 jpg ,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。

defer

介绍

  1. 当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈中【暂时称该栈为 defer 栈】,然后继续执行函数下一个语句。
  2. 当函数执行完毕后,在从 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
  1. 在 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 最主要的价值是在,当函数执行完毕后,可以及时的释放函数创建的资源。

9eeaPY
1. 通常做法是,创建资源后,比如打开了文件,获取了数据库的链接,或者是锁资源,可以立即执行 `defer file.close()` 或者 `defer.connect.close()`。
  1. 在 defer 后,可以继续使用创建资源。

  2. 当函数执行完毕后,系统会依次取出 defer 栈中,取出语句,关闭资源。

  3. 这种机制较为简洁。

包的本质实际上就是创建不同的文件夹,来存放程序文件

打包的基本语法:

  • package 包名

引入包的基本语法:

  • import “包的路径”

1. 快速入门

包快速入门 Go 相互调用函数,我们将 funcCal 定义到文件 utils.go,将 utils.go 放到一个包中,当其它文件需要使用到 utils.go 的方法时,可以 import 该包,就可以使用了。【为演示:新建项目目录结构】

项目目录结构如下:

wJf6Lb
- 这里我们最好建立一个 `go.mod`的文件:`go mod init go_code`。

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. 注意事项

  1. 在给一个文件打包时,该包对应一个文件夹比如这里的 utils 文件夹对应的包名就是 utils,文件的包名通常和文件所在的文件夹名一致般为小写字母。

  2. 当一个文件要使用其它包函数或变量时,需要引入对应的包。

    • package 在文件的第一行,然后是 import 指令。

    • 推荐开启 GO111MODULE = on, 然后再在自定义的目录下创建工程文件。如果 GO111MODULE = off ,那么我们创建的工程都要在$GOPATH/src 这个目录下面,否则包的引用将出错。

  3. 为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的 public ,这样才能跨包访问。比如 utils.go 的

EaVHXIcNpJqBDid
4. 在访问其它函数时,其语法是 **包名.函数名,比如这里的main.go 文件中**
Q4GcNDWpdBt9xqi
5. 如果包名太长,Go 支持给包取别名,注意细节:取别名后,原来的包名就不能用了。
  1. 在同一个包下,不能有相同的函数名(也不能有相同的全局变量名),否则重复定义。

  2. 如果你要编译成一个可执行程序文件,就需要将这个包声明为 main,即package main,这个就是一个语法规范,如果你是写一个库,包名可以自定义。

    • 我们只要切换到 main 目录下,然后执行 go build 这一命令。

    • 此时我们就可以看到文件夹里面生成了一个 main 的可执行文件。

字符串

和字符串有关的常用函数有如下几个,其余的可以参考官方文档。

常用方式

  1. 统计字符串的长度,按字节(byte)
str := "hello北" // golang 的编码统一为 utf-8 (字母和数字占一个字节, 汉字占`三个字节`)
fmt.Println("str len = ", len(str))

打印输出:

str len =  8
  1. 字符串遍历, 同时有中文问题 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
字符 = 北
字符 = 京
  1. 字符串转整数: n, err := strconv.Atoi("12")
n, err := strconv.Atoi("123")
// nil 类似于 None  但还是有一定区别的
if err != nil {
    fmt.Println("转化错误")
} else {
    fmt.Println("转成的结果是:", n)
}

打印输出:

转成的结果是: 123
  1. 整数转字符串 str = strconv.Itoa(12345)
str = strconv.Itoa(12345)
fmt.Printf("str=%v, str=%T\n", str, str)

打印输出:

str=12345, str=string
  1. 字符串转 []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]
  1. []btte 转字符串: str = string([]byte{97,98,99})
str = string([]byte{97, 98, 99})
fmt.Println(str)

打印输出:

abc
  1. 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
  1. 查找子串是否在指定的字符串中:string.Contains("seafood", "foo") // true
b := strings.Contains("seafood", "fd")
fmt.Printf("b = %v\n", b)

打印输出:

b = false
  1. 统计一个字符串有几个指定的子串, strings.Count(“caheee”,“e”) // 4
num := strings.Count("cuuuh", "e")
fmt.Printf("num = %v\n", num)

打印输出:

num = 0
  1. 不区分大小写的字符串比较(==是区分大小写的):fmt.Println(strings.EqualFold("abc"))
b = strings.EqualFold("abc", "Abc")
fmt.Printf("b = %v\n", b)         // true
fmt.Println("结果", "abc" == "Abc") //  区分大小写

打印输出:

b = true
结果 false
  1. 返回子串在字符串第一次出现的 index 值, 如果没有返回 -1,strings.Index("NLT_abc", "abc") //4
index := strings.Index("NLT_abc", "abc")
fmt.Printf("index=%v \n", index)

打印输出:

index=4 
  1. 返回子串在字符串最后一次出现的index, 如没有返回-1 : strings LastIndex("go golang". "go")
index = strings.LastIndex("go golang", "go")
fmt.Printf("index=%v\n", index)

打印输出:

index=3
  1. 将指定的子串替换成另外一个子串: 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
  1. 按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组: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]
  1. 将字符串的字母进行大小写的转换: strings.ToLower("Go") // go strings.ToUpper("Go") // GO
str = "goLang Helllo"
str = strings.ToUpper(str)
fmt.Println(str)

打印输出:

GOLANG HELLLO
  1. 将字符串左右两边的空格去掉: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"
  1. 将字符串左右两边指定的字符去掉: 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! ", "!") 
  1. 判断字符串是否以指定的字符串开头: 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
  1. 判断字符串是否以指定的字符串结束: strings.Hasuffx("NLT_ abc.jpg", "abc) //false
b = strings.HasSuffix("NLT_ abc.jpg", "abc") //false
fmt.Printf("b=%v", b)

打印输出:

b=false

切片

时间和日期

日期和时间相关函数都是放在 time 包里面。

获取时间

  1. 获取当前时间
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
  1. 获取其他信息
// 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

内存图:

dJISrR

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]

异常

  1. Go语言追求简洁优雅。所以,Go语言不支持传统的try catch... finally这种处理。
  2. Go中引入的处理方式为: defer,panic, recover
  3. 这几个异常的使用场景可以这么简单描述: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 内置函数来自定义异常。

  1. errors.New(“错误说明”),会返回一个 error 类型的值,表示一个错误。
  2. 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 就类似于 raiseerrors.New就类似于 Exception 基类,而 raise 就被 return 给替代了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值