目 录CONTENT

文章目录

Go语言入门

Sakura
2023-09-04 / 0 评论 / 0 点赞 / 22 阅读 / 48346 字 / 正在检测是否收录...

Go语言入门

1. 走入Golang

1.1 Go的项目结构

go语言项目特用的目录中的src下是不同的项目,每个项目都有自己的main函数

1.2 第一段代码Hello,World!

 package main //声明文件所在的包
 import "fmt" //引入程序所需的包,为了使用包下的函数 例如Println
 ​
 func main(){ //main 函数 程序的入口
     fmt.Println("Hello,World!") //在控制台打印输出一句话,双引号中的内容会打印输出
 }

1.2.1 go bulid 编译

进入项目文件夹中,使用go build main.go对main.go进行编译,生成main.exe文件。再用main.exe命令直接执行main.exe文件。运行结果就是控制台输出Hello,World!

go build -o 新名称.exe go语言源码文件.go可以让go源码生成一个以新名称命名的可执行文件。

1.2.2 go run 编译

go run运行的时候,将大部分操作进行隐藏,所以不会生成该go文件的编译后的可执行文件版本。而直接将结果输出在控制台上。

1.2.3 两种编译方式的区别

  • 在编译时,编译器会将程序依赖的库文件包含在可执行文件当中,所以,可执行文件变大了很多。

  • 如果我们先编译生成了可执行文件(go build),那么我们可以将该可执行文件拷贝到没有go开发环境的机器上直接运行。

  • 如果我们直接使用go run,那么另一台机器拿到源码想要执行,还是需要go语言开发环境才可以。

  • 生成的可执行文件会比go源码文件大很多,因为他要将所有依赖包添加到可执行文件当中。

1.3 语法注意点

  1. 执行入口是一个main()函数

  2. 严格区分大小写

  3. 每个语句后不需要分号,Go语言会在每个语句后面自动加分号

  4. Go编译器是一行行进行执行的,因此一行只能写一条语句,不能把多个语句写在同一个,否则报错。

  5. 定义的变量或者import导入的包没有用到,就会报错,以保证程序性能不被浪费。

  6. 大括号都是成对出现的,缺一不可。

  7. 变量的重复定义也会报错。

1.4 注释

  1. 单行注释 //

  2. 多行注释 /**/

1.5 API

Go语言中文网标准库文档

Go语言圣经

Go语言中文文档——地鼠文档

Go语言官方文档

2. 变量与数据类型

2.1 变量的四种使用方式

 package main
 ​
 import "fmt"
 ​
 func main() {
     var num int = 18
     //第一种变量使用方式,指定变量的使用类型,并且赋值
     fmt.Println(num)
     // 输出结果为18
 ​
     var num2 int
     //第二种变量使用方式,指定变量的使用类型,但是并不赋值,则会使用默认值
     fmt.Println(num2)
     // 输出结果为0
 ​
     var num3 = 10
     // 第三种使用方式,不进行变量类型定义,那么根据所赋的值进行判定变量的类型
     fmt.Println(num3)
     // 输出结果为10
 ​
     sex := "男"
     // 第四种使用方式,省略var,使用:=直接进行赋值与自动判定,不能写为=
     fmt.Println(sex)
     // 输出结果为男
 }

2.2 多变量的三种声明方式

 package main
 ​
 import "fmt"
 ​
 func main() {
     var n1, n2, n3 int
     // 多变量声明并定义类型,但因为并未赋值所以均为默认值
     fmt.Println(n1, n2, n3)
     // 输出0 0 0
 ​
     var n4, n5, n6 = 10, "leijianx", 3.8
     //声明并赋值不同类型变量
     fmt.Println(n4, n5, n6)
     // 输出10 leijianx 3.8
 ​
     n7, n8 := 9.9, "leijianx"
     // 使用:=进行声明赋值
     fmt.Println(n7, n8)
     // 输出9.9 leijianx   
 }

2.3 全局变量的声明

定义在{}内的变量为局部变量,定义在函数外的为全局变量

 package main
 ​
 import "fmt"
 ​
 var n9 = 100
 ​
 // 单次声明单个全局变量
 ​
 var (
     n10 = 101
     n11 = "leijianx"
 )
 // 单次声明多个不同类型变量
 ​
 func main() {
     fmt.Println(n9, n10, n11)
     // 输出100 101 leijianx
 }

2.4 数据类型汇总

2.4.1 基本数据类型

  • 数值型

    • 整数类型:int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,byte

    • 浮点类型:float32,float64

  • 字符型:没有单独的字符型,使用byte来保存单个字母

  • 布尔型:bool

  • 字符串:string

2.4.2 派生数据类型/复杂数据类型

  • 指针

  • 数组

  • 结构体

  • 管道

  • 函数

  • 切片

  • 接口

  • map

2.5 整数数据类型

用于存放整数

2.5.1 有符号整数类型

类型

有无符号

占用存储空间

表数范围

int8

1B

-27——27-1(-128——127)

int16

2B

-32768——32767

int32

4B

-2147483648——2147483647

int64

8B

-263——263

2.5.2 无符号整数

类型

有无符号

占用存储空间

表数范围

uint8

1B

0——255

uint16

2B

0——216-1

uint32

4B

0——232-1

uint64

8B

0——264-1

2.5.3 其他整数类型

类型

有无符号

占用存储空间

表数范围

int

32位系统是4B、64位系统是8B

-2147483648——2147483647-263——263

unit

32位系统是4B、64位系统是8B

0——232-1\0——264-1

rune

等价于int32

-2147483648——2147483647

byte

等价于unit8

0——255

2.6 浮点类型

类型

存储空间

表数范围

float32

4B

-3.403E38~3.403E38

float64

8B

-1.798E308~1.798E308

Golang中也可以用科学计数法表示浮点数,float64精度高于float32所以通常推荐使用float64

2.7 字符类型

Go语言使用的是UTF-8的编码

Go语言中没有专门的字符类型,如果要存储单个字符,一般使用byte进行存储

utf-8编码查询表

2.8 转义字符

转义符

含义

Unicode值

\b

退格(backspace)

\u0008

\n

换行

\u000a

\r

回车

\u000d

\t

制表符(tab)

\u0009

"

双引号

\u0022

'

单引号

\u0027

\

反斜杠

\u005c

2.9 布尔类型

bool类型只允许取值true(1)和false(0),只占一个字节,一般用于逻辑运算和程序流程控制。

2.10 字符串类型

字符串就是一串固定长度的字符连接起来的字符序列

字符串的使用:

  • 基础定义:

 package main
 ​
 import "fmt"
 ​
 func main() {
     var s1 string = "Hello"
     fmt.Println(s1)
 }
 //输出:Hello
  • 字符串是不可变的

 package main
 ​
 import "fmt"
 ​
 func main() {
     var s2 string = "abc"
     s2[0] = 'def'
     fmt.Println(s2)
 }
 /* # command-line-arguments
 .\main.go:7:10: more than one character in rune literal
 # command-line-arguments
 .\main.go:7:10: more than one character in rune literal
 */

字符串一旦定义好,其中的字符的值就不能再更改了

  • 字符串的表现形式

如果字符串中没有特殊字符的时候,字符串用双引号:

var s3 string = "abcd"

如果字符串中有特殊符号,就使用反引号``:

 package main
 ​
 import "fmt"
 ​
 func main() {
     var s4 string = `"dwada",&`
     fmt.Println(s4)
 }
 //输出:"dwada",&
  • 字符串的拼接效果

 package main
 ​
 import "fmt"
 ​
 func main() {
     var s5 string = "abc" + "def"
     s5 += "ghi"
     fmt.Println(s5)
 }
 // 输出:abcdefghi

2.11 基本数据类型间的转换

Go在不同类型的变量之间赋值时,需要显式转换,并且只有显式转换(强制转换)

实例:

package main

import "fmt"

func main() {
	var n1 int = 10
	fmt.Printf("%T\n", n1)
	//输出:int
	var n2 float32 = float32(n1)
	fmt.Printf("%T", n2)
	//输出:float32
}

将多位变量转换为少位变量时,编译不会出错,但是会溢出。

2.12 基本数据类型转换为字符串类型

方法一:

fmt.Sprintf("%参数",表达式)

package main

import "fmt"

func main() {
	var m1 int = 90
	var s1 string = fmt.Sprintf("%d", m1)

	var m2 float32 = 90.2
	var s2 string = fmt.Sprintf("%f", m2)

	fmt.Printf("%T %v\n", s1, s1)
	//输出:string 90
	fmt.Printf("%T %q", s2, s2)
	//输出:string "90.199997"
}

方法二:

使用strconv包的函数

package main

import (
	"fmt"
	"strconv"
)

func main() {
	var n1 int = 10
	var s1 string = strconv.FormatInt(int64(n1), 10)
	fmt.Printf("%T, %v\n", s1, s1)
	// 输出:string, 10

	var n2 float64 = 4.23
	var s2 string = strconv.FormatFloat(n2, 'f', 9, 64)
	fmt.Printf("%T, %v\n", s2, s2)
	// 输出:string, 4.230000000
}

FormatInt()函数中,后面的参数base是指将其转换为几进制,但是第一个参数必须转为int64类型

FormatFloat()函数中的f表示以十进制形式输出,第三个参数代表后面保留几位小数。第四个参数代表这个小数是float64类型

2.13 将字符串类型转换为基本类型

只能用strconv函数包

package main

import (
	"fmt"
	"strconv"
)

func main() {
	var s2 string = "210"
	var num1 int64
	num1, _ = strconv.ParseInt(s2, 10, 64)
	fmt.Printf("%T,%v\n", num1, num1)
    // 输出:int64,210
}

如果是字母转换为数字,因为输入不合法,所以都为0

3. 指针

  • 指针变量接收的一定是地址值

  • 指针变量的地址不可以不匹配

3.1 指针的使用

package main

import "fmt"

func main() {
	var age int = 18
	fmt.Println(&age) //0xc00001c098
	//&符号加上变量名字,就可以获取变量的地址

	//定义一个指针变量
	var p *int = &age
	fmt.Println(p)
	//输出:0xc00001c098
	fmt.Println(&p) //0xc00000a030
	fmt.Println(*p)
	//解处指针,就又叫解封指针
	//输出:18
}
  • 使用指针改变变量值

package main

import "fmt"

func main() {
	var num int = 10
	fmt.Println(num)
	//输出:10

	var ptr *int = &num
	*ptr = 20
	fmt.Println(num)
	//输出:20
}

4. 标识符

变量,方法,只要是起名字的地方,那个名字就是标识符。

标识符的定义规则:

  1. 组成部分可以由数字,字母,下划线组成。此处字母定义比较宽泛,汉字也可以。

  2. 不可以以数字开头,严格区分大小写,不能包含空格,不可以使用关键字

  3. 需要注意增加可读性

  4. 下划线在Go语言中成为空标识符。所以在此仅作为占位符使用,不能单独作为标识符使用。

  5. 长度无限制,但是尽量不要太长。

4.1 标识符起名规则

  1. 包名:尽量保持package的名字与目录保持一致,尽量采取有意义的包名,简短,有意义,不要和标准库起冲突。

    • 但是main函数所在的包建议定义为main包,如果不定义为main包,那么就得不到可执行文件

  2. 变量名、函数名、常量名,尽量采用驼峰法命名

  3. 如果变量名、函数名、常量名首字母大写,则可以被其它的包访问;如果首字母小写,则只能在本包中使用

注意:

import导入语句通常放在文件开头包声明语句的下面。导入的包名需要使用双引号包裹起来。包名是从&GOPATH/src/后开始计算的。

5. 运算符

  1. 算术运算符:+、-、*、/、%、++、--

  2. 赋值运算符:=、+=、-=、*=、/=、%=

  3. 关系运算符:==、!=、>、<、>=、<=

  4. 逻辑运算符:&&、||、!

  5. 位运算符:&、|、^

  6. 其他运算符:&、*

5.1 运算符优先级别

优先级

分类

运算符

结合性

1

逗号运算符

,

从左到右

2

赋值运算符

=、+=、-=、*=、/=、%=、>=、<<=、&=、^=、|=

从右到左

3

逻辑或

||

从左到右

4

逻辑与

&&

从左到右

5

按位或

|

从左到右

6

按位异或

^

从左到右

7

按位与

&

从左到右

8

相等/不等

==、!=

从左到右

9

关系运算符

<、<=、>、>=

从左到右

10

位移运算符

<<、>>

从左到右

11

加法/减法

+、-

从左到右

12

乘法/除法/取余

*、/、%

从左到右

13

单目运算符

!、*、&、++、--、+、-

从右到左

14

后缀运算符

()、[]、->

从左到右

提高优先级可以使用()

6. 流程控制

分为顺序结构、分支结构、循环结构

6.1 分支结构——if

基本语法:
if 条件表达式{
		逻辑代码
	}
  • 当条件表达式为true时,就会执行其中的逻辑代码

  • if 和 表达式之间一定要有空格

  • 在Go语言中,{}是必须有的,就算if只执行一条代码

6.1.1 if的单分支

案例:

package main

import "fmt"

func main() {
	var count int = 100

	if count < 200 {
		fmt.Println("库存不足")
	}

}

此案例也可等价为:

package main

import "fmt"

func main() {
	if count := 100; count < 200 {
		fmt.Println("库存不足")
	}
}

6.1.2 if的双分支

基本语法:
if 条件表达式 {
    逻辑代码1
} else {
    逻辑代码2
}

以上是正确的代码规范,有且只有这种写法

下面这种就是错误写法:

if 条件表达式 {
    逻辑代码1
} 
else {
    逻辑代码2
}

案例:

package main

import "fmt"

func main() {
	if count := 200; count < 200 {
		fmt.Println("库存不足")
	} else {
		fmt.Println("库存充足")
	}
}

6.1.3 if多分支

基本语法:
if 条件表达式1 {
    逻辑代码1
} else if 条件表达式2 {
    逻辑代码2
}…… else {
    逻辑代码n
}

案例:

package main

import "fmt"

func main() {
	if count := 200; count < 200 {
		fmt.Println("库存不足")
	} else if count == 200 {
		fmt.Println("库存刚好")
	} else {
		fmt.Println("库存充足")
	}
}

6.2 分支结构——switch

基本语法:
switch 表达式 {
    case 值1,值2,...:
    	语句块1
    case 值3,值4,...:
    	语句块2
    ……
    default:
    	语句块
}

注意事项:

  1. switch后是一个表达式

  2. case后的各个值的数据类型,必须和switc的表达式数据类型一致

  3. case后面可以带多个表达式,使用逗号间隔。

  4. case后的表达式如果是常量值,则要求不能重复

  5. case后面不需要带break,程序匹配到一个case后就会执行对应的代码块,然后退出switch。默认执行default。

  6. default并非必须带的

  7. switch后也可以以不带表达式,当作if分支进行使用

  8. switch后也可以直接声明一个变量进行使用,需要分号隔开。

  9. 在case语句之后增加fallyhrough,则会继续执行下一个case,也叫switch穿透

  10. default可以放在任意位置上,不一定非要放在最后

案例:

package main

import "fmt"

func main() {
	var score int = 87

	switch score / 10 {
	case 10:
		fmt.Println("评分为A")
	case 9:
		fmt.Println("评分为B")
	case 8:
		fmt.Println("评分为C")
	case 7:
		fmt.Println("评分为D")
	default:
		fmt.Println("不及格")
	}
}

6.3 循环结构——for

GO语言中只有for循环,没有while或者do-while循环

基本语法:
for (初始表达式;布尔表达式;迭代因子) {
    循环体;
}

案例:

package main

import "fmt"

func main() {
	//实现求和1、2、3、4、5的功能
	var i int = 1
	var sum int = 0

	for i = 1; i <= 5; i++ {
		sum += 1
	}

	fmt.Println(sum)
}

6.3.1 for range

for range是Go语言中的一种迭代结构,许多情况下,for range可以遍历数组、切片、字符串、map以及通道.for range语法上类似于其他语言中的foreach语句,一般形式为:

基本语法:
for key,val := range coll {
    
}

案例:

package main

import "fmt"

func main() {
	var str string = "hello"

	for i, value := range str {
		fmt.Printf("索引值为:%d,%c\n", i, value)
	}
	/*
		输出:
			索引值为:0,h
			索引值为:1,e
			索引值为:2,l
			索引值为:3,l
			索引值为:4,o
	*/
}

等价于:

package main

import "fmt"

func main() {
	var str string = "hello"

	for i := 0; i < len(str); i++ {
		fmt.Printf("%c\n", str[i])
	}
	/*
		输出:
			h
			e
			l
			l
			o
	*/
}

6.4 关键字

6.4.1 关键字——break

停止正在进行中的循环

基础用法:

package main

import "fmt"

func main() {
	var sum int = 0

	for i := 1; i <= 100; i++ {
		sum += i
		fmt.Println(sum)
		if sum >= 20 {
			break
		}
	}
}
  • 可以用在switch分支中,但是switch中一般不需要

  • 在未指定情况下只能停止“离他最近”的一个循环,但是可以用标签进行指定,例如:

    package main
    
    import "fmt"
    
    func main() {
    lable1:
    	for i := 1; i <= 5; i++ {
    		for j := 2; j <= 4; j++ {
    			fmt.Printf("i:%v,j:%v \n", i, j)
    			if i == 2 && j == 2 {
    				break lable1
    			}
    		}
    	}
    }

6.4.2 关键字——countinue

结束本次循环,并继续下一次循环

package main

import "fmt"

func main() {
	for i := 1; i <= 100; i++ {
		if i%6 != 0 {
			continue
		}
		fmt.Println(i)
	}
}
  • 也可以像break一样使用标签来选择,下一次循环的循环体

6.4.3 关键字——goto

不推荐使用,对程序有害,避免程序流混乱

案例:

package main

import "fmt"

func main() {
	fmt.Println("1")
	fmt.Println("2")
	if true {
		goto lable
	}
	fmt.Println("3")
	fmt.Println("4")
	fmt.Println("5")
lable:
	fmt.Println("6")

}

6.4.4 关键字——return

会结束当前函数,也可以返回值。案例看下一章——函数

7. 自定义函数

函数的使用可以减少代码冗余,增加可维护性,提高代码复用

定义:为完成某一功能的程序指令(语句)的集合,称为函数

基本语法:
func 函数名(形参列表)(返回值类型列表){
	执行语句……
    return + 返回值列表
}

案例:

package main

import "fmt"

func cal(num1 int, num2 int) int {
	var sum int = 0
	sum += num1 + num2
	return sum
}

func main() {
	sum := cal(10, 20)
	fmt.Println(sum)
}

函数命名规范:

  1. 遵循标识符命名法

  2. 首字母不能是数字

  3. 首字母大写该函数可以被本包其他文件和其他包文件使用(类似public)

  4. 首字母小写只能被本包使用,其他包文件不能使用(类似private)

如果要返回多个参数,需要在返回值列表中列出

案例:

package main

import "fmt"

func cal(num1 int, num2 int) (int, int) {
	var sum int = 0
	sum += num1 + num2

	var re int = 0
	re = num1 - num2

	return sum, re
}

func main() {
	sum, re := cal(10, 20)
	fmt.Println(sum, re)
}

如果不想接收某个值,用_进行代替接收即可

7.1 函数详解

7.1.1 传入可修改参数

image-20230717103810074

要传入可以改变值的函数时,传入参数需要是指针类型

案例:

package main

import "fmt"

func test(num *int) {
	*num = 30

}

func main() {
	var num int = 1
	test(&num)
	fmt.Println(num)
}

7.1.2 可变参数

因为Go语言不支持重载,即重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

可变参数由...来表示,说明该参数个数不确定。可传入零到任意个数量的数组。

函数内部处理参数的时候是将可变参数当作切片来进行处理的

案例:

package main

import "fmt"

func test(args ...int) {
	for i := 0; i < len(args); i++ {
		fmt.Println(args[i])
	}
}

func main() {
	test()
	test(2)
	test(1, 2, 3, 4)
}

7.1.3 函数等同于数据类型

在go语言中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数进行调用。

案例:

package main

import "fmt"

func test(num *int) {
	*num = 30
}

func main() {
	var num int = 1
	a := test
	a(&num)
	fmt.Println(num)
}

函数既然是一种数据类型,那么在Go语言中,函数可以作为形参,并且被调用。

案例:

package main

import "fmt"

func test(num int) {
	num = 30
}

func test2(num1 int, test func(int)) {
	fmt.Println("for test test2")
}

func main() {
	var num int = 1
	a := test
	test2(num, test)
	test2(2, a)

}

只要函数的形参类型与test2中所需函数的形参类型相同即可使用。

7.1.4 自定义数据类型

为了简化数据类型定义,Go支持自定义数据类型

基本语法:
type 自定义数据类型名 数据类型

案例:

package main

import "fmt"

func main() {
	type lei int

	var num1 lei = 30
	fmt.Printf("%T", num1)
	//输出:main.lei
}

相当于起了个别名,但是Go进行编译的时候,并不会将Int与lei当作同一种数据类型进行处理。两个变量的值不能互相赋值或者互相比较等

也可以对函数进行命名,来进行使用

案例:

package main

import "fmt"

func test(num int) {
	fmt.Println(num)
}

func test02(num int, num1 float32, testFunc func(int)) {
	fmt.Println("02")
}

type myFunc func(int)

func test03(num int, testFunc myFunc) {
	fmt.Println("03")
}

func main() {
	a := test
	test03(10, test)
	test03(1, a)
}

Go语言同时也支持对函数返回值进行命名

案例:

package main

import "fmt"

func test(num int, num2 int) (int, int) {
	result01 := num + num2
	result02 := num - num2
	return result01, result02
} //返回值顺序不能写错

func test01(num1 int, num2 int) (sub int, sum int) {
	sum = num1 + num2
	sub = num1 - num2
	return sub, sum
}//提前命名,不用纠结返回值顺序

func main() {
	sum, cal := test(10, 29)
	fmt.Println(sum, cal)
	sum1, cal1 := test01(27, 49)
	fmt.Println(sum1, cal1)
}

7.2 包的引入

package关键字后跟的是包的命名。包的声明和所在文件夹同名

一般引入本地包,只需要从GOPATH处开始检索包的位置即可

但是我的GO环境配置不知道出了什么问题,只能用gmod初始化之后用gmod进行导入

"my-module/src/test1/Exforuse"

  1. main函数必须放在main包下

  2. 函数调用的时候必须定位在所在的包

  3. 函数名首字母大写才能被访问

  4. 一个包中不能用名字相同的函数

  5. 包名可以和文件夹名不同,但是同级的不同go文件的函数,包名必须要相同

  6. 可以给包起别名,但是起了别名之后只能使用别名

7.3 init函数

每一个源文件都可以包含一个init函数,该函数会在main函数之前调用,被go框架调用

init函数会比main函数先执行,不过全局变量的调用先于init函数

7.4 匿名函数

如果我们只希望一个函数制备使用一次,那么可以使用匿名函数

使用方式:

package main

import (
	"fmt"
)

func main() {
	result := func(num1 int, num2 int) int {
		return num1 + num2
	}(10, 20)//匿名函数

	fmt.Println(result)
}

若想要重复使用该匿名函数,可以将这个匿名函数赋予一个变量

若想要匿名函数在整个程序中都有效,可以将其赋予全局变量

7.5 闭包

解释:闭包就是一个函数和其相关的引用环境组合的一个整体

本质依旧是一个匿名函数,只是这个函数引入外界的变量/参数

即:匿名函数+外界引入的变量/参数 = 闭包

特点:1. 返回的是一个匿名函数,但是这个匿名函数引用到函数外的变量/参数,因此这个匿名函数就和变量/参数形成一个整体,构成闭包。2. 闭包中使用的变量/参数会一直保存在内存中,所以会一直使用,意味着闭包不能滥用

使用方法:

package main

import (
	"fmt"
)

// getsum函数返回值为一个函数,这个函数的参数是一个int类型的参数,返回值也是int类型
func getsum() func(int) int {
	var sum int = 0
	return func(num int) int {
		sum = sum + num
		return sum
	} //这个就是闭包
}

func main() {
	f := getsum()
	fmt.Println(f(1)) //1
	fmt.Println(f(2)) //3
}

因为闭包中那个变量的参数是一直存储在内存中的,数值不会初始化,所以这是闭包的特点也是闭包的缺点

7.6 defer

为了在函数执行完毕之后及时释放资源,所以提供了defer关键字

go中,程序遇到defer关键字,不会立即执行defer关键字,而是将defer后的语句压入一个栈中,然后继续执行函数后面的语句

package main

import "fmt"

func add(num1 int, num2 int) int {
	defer fmt.Println("num1 = ", num1)
	defer fmt.Println("num2 = ", num2)

	var sum int = num1 + num2
	fmt.Println("sum=", sum)
	return sum
}

func main() {
	fmt.Println(add(30, 60))
	/*
			输出:
		sum= 90
		num2 =  60
		num1 =  30
		90
	*/
}

因为栈是先进后出,所以最先defer的语句最后执行所以打印顺序为sum->num2->num1;而且压入栈之后,运行时之后根据压入栈时的状态进行运行,不会管接下来是否有所更改。

应用场景:想关闭某个使用的资源,在使用的时候直接随手defer,因为defer有延迟执行机制,省心。

8. 系统函数

8.1 字符串函数

  1. 统计字符串长度:len(str)

  2. 字符串遍历: r := []rune(str)

  3. 字符串转整数:n,err := strconv.Atoa("66")

  4. 整数转字符串:str = strconv.Itoa(6887)

  5. 查找子串是否在指定字符串中:strings.Contains("javaandgolang","go")

  6. 统计一个字符串有几个指定的子串:strings.Count("javaandgolang","a")

  7. 不区分大小写的字符串比较:fmt.Println(strings.EqualFold("go","Go"))

  8. 返回子串在字符串第一次出现索引值,如果没有则返回-1:strings.lIndex("javaandgolang","a")

  9. 字符串替换:strings.Replace("golangandjavagogo","go","golang",n) //n是指定希望可以替换几个,如果是n == -1则表示全部替换,替换两个n就是2

  10. 按照指定的某个字符,为分割标识符,将一个字符串拆分成字符串数组:strings.Split("go=python-java","-")

  11. 将字符串的字母进行大小写转换:strings.ToLower("Go") strings,ToUpper("go")

  12. 将字符串左右两边的空格去掉:strings.TrimSpace("go and java")

  13. 将字符串左右两边的指定字符去掉:strings.Trim("~golang~","~")

  14. 将字符串左边/右边指定字符去掉:strings.TrimeLeft(TrimRight)("~golang~","~")

  15. 判断字符串是否以指定的字符串开头:strings.HasPrefix("leijianx","lei")

  16. 判断字符串是否以指定的字符结束:strings.HasSuffix("leijianx",”ianx")

8.2日期与时间的函数

需要导入time包

  1. 获取当前时间需要调用NOW函数:now := time.NOW 返回值是一个结构体,对应的类型是time.Time

  2. 日期的格式化:

    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.Println(datestr)
    }
    //输出:当前年月日: 2023-8-21 时分秒: 9-23-24

8.3 内置函数

不需要导包的函数,我们称之为内置函数/内建函数

内置函数的存放位置在于builtin包下

常用函数:

  • len函数:统计字符串长度,按字节进行统计

  • new函数:分配内存,主要用来分配值类型

  • make函数:分配函数,主要用来分配引用类型

后面两个相当于指针

9. 错误处理

9.1 defer + recover

错误处理/捕获机制

package main

import "fmt"

func main() {
	test()
	fmt.Println("运行成功")
}

func test() {
	//利用defer+recover来捕获错误
	defer func() {
		//调用recover内置函数,可以捕获错误
		err := recover()
		//如果没有捕获错误就返回零值:nil
		if err != nil {
			fmt.Println("错误已经捕获")
			fmt.Println("err是:", err)
		}
	}() //最后的括号是为了调用该函数
	num1 := 10
	num2 := 0
	result := num1 / num2
	fmt.Println(result)
}

/*
输出:
错误已经捕获
err是: runtime error: integer divide by zero
运行成功
*/

9.2 自定义错误

自定义错误需要调用errors包下的函数:函数返回error类型

package main

import (
	"errors"
	"fmt"
)

func main() {
	err := test()
	if err != nil {
		fmt.Println("错误:", err)
	}
	fmt.Println("运行成功")
}

func test() (err error) {
	num1 := 10
	num2 := 0
	if num2 == 0 {
		//抛出自定义错误
		return errors.New("除数不能为零")
	} else {
		result := num1 / num2
		fmt.Println(result)
	}
	return nil
}

/*
输出:
错误: 除数不能为零
运行成功
*/

如果在出现错误以后,想要后续代码不进行执行,进行程序中断,退出程序可以借助builtin包下的内置函数——panic

package main

import (
	"errors"
	"fmt"
)

func main() {
	err := test()
	if err != nil {
		fmt.Println("错误:", err)
		panic(err)
	}
	fmt.Println("运行成功")
}

func test() (err error) {
	num1 := 10
	num2 := 0
	if num2 == 0 {
		//抛出自定义错误
		return errors.New("除数不能为零")
	} else {
		result := num1 / num2
		fmt.Println(result)
	}
	return nil
}

/*
输出:
错误: 除数不能为零
panic: 除数不能为零
*/

10. 数组

引入数组:

package main

import "fmt"

func main() {
	//给出五个学生的成绩,求出成绩的总和,平均数:
	var scores [5]int
	scores[0] = 98
	scores[1] = 56
	scores[2] = 76
	scores[3] = 34
	scores[4] = 70

	sum := 0
	for i := 0; i < len(scores); i++ {
		sum += scores[i]
	}

	avg := sum / len(scores)

	fmt.Printf("成绩的总和: %v,成绩的平均数: %v", sum, avg)
	//输出:成绩的总和: 334,成绩的平均数: 66
}

10.1 数组内存分析

package main

import "fmt"

func main() {
	var arr [3]int64

	fmt.Println(len(arr)) //3

	fmt.Println(arr) //[0 0 0]

	fmt.Println("arr的地址: %p", &arr) //arr的地址: %p &[0 0 0]

	fmt.Println("arr第一个空间的地址: %p", &arr[0]) //arr第一个空间的地址: %p 0xc000010138

	fmt.Println("arr第一个空间的地址: %p", &arr[1]) //arr第一个空间的地址: %p 0xc000010140

	fmt.Println("arr第一个空间的地址: %p", &arr[2]) //arr第一个空间的地址: %p 0xc000010148

}

数组优点:地址连续存储,存储结构简单有效,所以存取很快速

10.2 数组的遍历

  1. 普通for循环,就不再演示了

  2. 键值循环

    for range结构,是go语言特有的一种迭代结构,可以遍历数组,切片,字符串,map及通道,for range语法上类似于其他语言的foreach语句,一般形式为:

    for key,val := range coll{
    	...
    }

    coll中就是你要遍历的内容

    每次遍历得到的索引用key接收,每次遍历得到的索引位置上的值用val

    key和val在其中算是局部变量

10.3 数组的初始化

package main

import "fmt"

func main() {
	var arr1 [3]int = [3]int{3, 6, 9}
	fmt.Println(arr1)
	//[3 6 9]
    
	var arr2 = [3]int{1, 4, 7}
	fmt.Println(arr2)
	//[1 4 7]
    
	var arr3 = [...]int{6, 4, 7}
	fmt.Println(arr3)
	//[6 4 7]
    
	var arr4 = [...]int{2: 6, 0: 33, 1: 99, 3: 88}
	fmt.Println(arr4)
	//[33 99 6 88]
}

10.4 数组注意细节

  1. 长度属于数字类型的一部分

    package main
    
    import "fmt"
    
    func main() {
    	var arr1 = [3]int{3, 6, 9}
    	fmt.Printf("数组的类型为: %T", arr1)
    	//数组的类型为: [3]int
    }
  2. Go数组属性类型,在默认情况下是值传递,因此会进行值拷贝

  3. 如果想在其他函数中去修改原来的数组,可以使用引用传递(指针方式)

package main

import "fmt"

func main() {
	var arr1 = [3]int{3, 6, 9}
	test(&arr1)
	fmt.Println(arr1) //[7 6 9]
}

func test(arr *[3]int) {
	(*arr)[0] = 7
}

10.5 二维数组

二维数组的内存分析:

二维数组的遍历:

  1. 双重for循环遍历,就不再赘述了

  2. for range循环

    package main
    
    import "fmt"
    
    func main() {
    	var arr [3][3]int = [3][3]int{{1, 4, 7}, {2, 5, 8}, {3, 6, 9}}
    	fmt.Println(arr)
    	fmt.Println("____________________________")
    
    	for key, val := range arr {
    		for k, v := range val {
    			fmt.Printf("arr[%v][%v] = %v", key, k, v)
    		}
    		fmt.Println()
    	}
    }
    
    /*
    输出:
    [[1 4 7] [2 5 8] [3 6 9]]
    ____________________________
    arr[0][0] = 1arr[0][1] = 4arr[0][2] = 7
    arr[1][0] = 2arr[1][1] = 5arr[1][2] = 8
    arr[2][0] = 3arr[2][1] = 6arr[2][2] = 9
    
    */

11. 切片

切片是建立在数组之上的抽象,它构建在数组之上并且提供更强大的能力和便捷

切片是对数组一个连续片段的引用。所以切片是一个引用类型,这个片段可以是整个数组,或者是由起始终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口

package main

import "fmt"

func main() {
	var arr [6]int = [6]int{3, 6, 9, 1, 4, 7}
	//切片构建在数组之上,int类型,arr是原数组
	//[1:3]切片是切出的一个片段,索引是从1开始的,到三结束,不包含三,左开右闭
	var sliceforarr []int = arr[1:3]
	
	fmt.Println(sliceforarr) //[6 9]
}

11.1 切片的内存分析

切片的底层数据结构包含三部分:

  1. 指向底层数组的指针

  2. 切片的长度

  3. 切片的容量

因此当数组改变时,切片的值也会因此改变

11.2 切片的定义

  1. 定义一个切片,然后让切片去引用一个已经创建好的数组

    var arr [6]int = [6]int{3,6,9,10}
    slice := arr[1:3]

  2. 通过make内置函数创建切片。

    package main
    
    import "fmt"
    
    func main() {
    	// 第一个参数是切片的类型,第二个是切片的长度,第三个是切片的容量
    	slice := make([]int,4,20)
    	fmt.Println(slice)
    }

  3. 定一个切片,直接就指定具体数组,使用原理类似make方式

    package main
    
    import "fmt"
    
    func main() {
    	slice2 := []int{1, 4, 7}
    	fmt.Println(slice2)
    }
    //此处切片容量等同于切片长度

11.3 切片的遍历

package main

import "fmt"

func main() {
	slice2 := []int{1, 4, 7}
	for i := 0; i < len(slice2); i++ {
		fmt.Printf("slice[%v] = %v \t", i, slice2[i])
	}
	//slice[0] = 1    slice[1] = 4    slice[2] = 7

	for i, v := range slice2 {
		fmt.Printf("下标: %v, 元素: %v \n", i, v)
	}
	//下标: 0, 元素: 1 
	//下标: 1, 元素: 4                                                 
	//下标: 2, 元素: 7                                 
}

11.4 切片的注意事项

  1. 切片定义之后不能直接使用,需要让其引用到一个数组,或者make一个空间供切片进行使用

  2. 切片使用不能越界

  3. 切片可以继续切片

  4. 简写方式:var slice = arr[0:end] --> var slice = arr[:end] var slice = arr[start:len(arr)] --> var slice = arr[start:] var slice = arr[0:len(arr)] --> var slice = arr[:]

  5. 切片可以动态增长

    package main
    
    import "fmt"
    
    func main() {
    	var arr [6]int = [6]int{1, 4, 7, 3, 6, 9}
    	var slice []int = arr[1:4]
    
    	fmt.Println(slice) //[4 7 3]
    
    	slice = append(slice, 88, 55)
    	fmt.Println(slice) //[4 7 3 88 55]
    }

    此处使用append是创建一个新的数组,并将该数添加到新数组当中,因此需要切片重新接收。

  6. 切片的拷贝

    package main
    
    import "fmt"
    
    func main() {
    	var a []int = []int{1, 2, 43}
    
    	var b []int = make([]int, 10)
    
    	copy(b, a) //将a中对应的元素拷贝到b对应的数组当中
    	fmt.Println(b)//[1 2 43 0 0 0 0 0 0 0]
    }

12. 映射——map

此处使用的都是键值对存储信息,键值对就是一对匹配的信息

可以通过键来获取对应的值

12.1 基本语法

var map变量名 map[keytype]valuetype

slice \ map \ function不可以用作类型type

只声明map的时候不会分配空间,需要用make申请来分配空间

package main

import "fmt"

func main() {
    var a map[int]string

    a = make(map[int]string, 10)
    a[201] = "ss"
    a[22] = "fs"
    a[411] = "fse"

    fmt.Println(a) //map[22:fs 201:ss 411:fse]
}

map的key存储是无序的,key的部分不能重复,重复的key后来的value会替换前一个value,value可以重复

make函数的第二个参数size可以省略,只分配一个空间。

12.1.1三种创建map的方法

package main
import "fmt"func main() {
var a map[int]stringa = make(map[int]string, 10)
a[201] = "ss"
a[22] = "fs"
a[411] = "fse"fmt.Println(a) //map[22:fs 201:ss 411:fse]
}
package mainimport "fmt"func main() {a := map[int]string{201: "ss",22: "fs",411: "fse",}fmt.Println(a) //map[22:fs 201:ss 411:fse]}
package mainimport "fmt"func main() {a := make(map[int]string)a[201] = "ss"a[22] = "fs"a[411] = "fse"fmt.Println(a) //map[22:fs 201:ss 411:fse]}

12.1.2 map的基本操作

增加和更新:

map['key'] = value 如果key还没有那就是增加,如果已经存在,那就是更新

删除:

delete(map,"key") delete是一个内置函数,如果key存在,就删除key-value键值对,如果key的value不存在,那么就不操作,也不会报错。

清空操作:

  1. 如果要删除map里的所有key,没有一个专门的办法一次删除,只能遍历所有key,逐个进行删除

  2. 也可以使用map = make(...),使得make一个全新的,让原来的变成一个垃圾,被gc回收

查找操作:

value,bool = map[key] value为返回的value,bool为是否返回,要么true要么false

package main

import "fmt"

func main() {
    a := map[int]string{
       201: "ss",
       22:  "fs",
       411: "fse",
    }
    value, flag := a[201]
    fmt.Println(value, flag)//ss true
}

获取长度:

len函数

遍历map:

for-range

0

评论区