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 语法注意点
执行入口是一个main()函数
严格区分大小写
每个语句后不需要分号,Go语言会在每个语句后面自动加分号
Go编译器是一行行进行执行的,因此一行只能写一条语句,不能把多个语句写在同一个,否则报错。
定义的变量或者import导入的包没有用到,就会报错,以保证程序性能不被浪费。
大括号都是成对出现的,缺一不可。
变量的重复定义也会报错。
1.4 注释
单行注释
//
多行注释
/**/
1.5 API
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进行存储
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. 标识符
变量,方法,只要是起名字的地方,那个名字就是标识符。
标识符的定义规则:
组成部分可以由数字,字母,下划线组成。此处字母定义比较宽泛,汉字也可以。
不可以以数字开头,严格区分大小写,不能包含空格,不可以使用关键字
需要注意增加可读性
下划线在Go语言中成为空标识符。所以在此仅作为占位符使用,不能单独作为标识符使用。
长度无限制,但是尽量不要太长。
4.1 标识符起名规则
包名:尽量保持package的名字与目录保持一致,尽量采取有意义的包名,简短,有意义,不要和标准库起冲突。
但是main函数所在的包建议定义为main包,如果不定义为main包,那么就得不到可执行文件
变量名、函数名、常量名,尽量采用驼峰法命名
如果变量名、函数名、常量名首字母大写,则可以被其它的包访问;如果首字母小写,则只能在本包中使用
注意:
import导入语句通常放在文件开头包声明语句的下面。导入的包名需要使用双引号包裹起来。包名是从&GOPATH/src/后开始计算的。
5. 运算符
算术运算符:+、-、*、/、%、++、--
赋值运算符:=、+=、-=、*=、/=、%=
关系运算符:==、!=、>、<、>=、<=
逻辑运算符:&&、||、!
位运算符:&、|、^
其他运算符:&、*
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:
语句块
}
注意事项:
switch后是一个表达式
case后的各个值的数据类型,必须和switc的表达式数据类型一致
case后面可以带多个表达式,使用逗号间隔。
case后的表达式如果是常量值,则要求不能重复
case后面不需要带break,程序匹配到一个case后就会执行对应的代码块,然后退出switch。默认执行default。
default并非必须带的
switch后也可以以不带表达式,当作if分支进行使用
switch后也可以直接声明一个变量进行使用,需要分号隔开。
在case语句之后增加fallyhrough,则会继续执行下一个case,也叫switch穿透
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)
}
函数命名规范:
遵循标识符命名法
首字母不能是数字
首字母大写该函数可以被本包其他文件和其他包文件使用(类似public)
首字母小写只能被本包使用,其他包文件不能使用(类似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 传入可修改参数
要传入可以改变值的函数时,传入参数需要是指针类型
案例:
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"
main函数必须放在main包下
函数调用的时候必须定位在所在的包
函数名首字母大写才能被访问
一个包中不能用名字相同的函数
包名可以和文件夹名不同,但是同级的不同go文件的函数,包名必须要相同
可以给包起别名,但是起了别名之后只能使用别名
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 字符串函数
统计字符串长度:len(str)
字符串遍历: r := []rune(str)
字符串转整数:n,err := strconv.Atoa("66")
整数转字符串:str = strconv.Itoa(6887)
查找子串是否在指定字符串中:strings.Contains("javaandgolang","go")
统计一个字符串有几个指定的子串:strings.Count("javaandgolang","a")
不区分大小写的字符串比较:fmt.Println(strings.EqualFold("go","Go"))
返回子串在字符串第一次出现索引值,如果没有则返回-1:strings.lIndex("javaandgolang","a")
字符串替换:strings.Replace("golangandjavagogo","go","golang",n) //n是指定希望可以替换几个,如果是n == -1则表示全部替换,替换两个n就是2
按照指定的某个字符,为分割标识符,将一个字符串拆分成字符串数组:strings.Split("go=python-java","-")
将字符串的字母进行大小写转换:strings.ToLower("Go") strings,ToUpper("go")
将字符串左右两边的空格去掉:strings.TrimSpace("go and java")
将字符串左右两边的指定字符去掉:strings.Trim("~golang~","~")
将字符串左边/右边指定字符去掉:strings.TrimeLeft(TrimRight)("~golang~","~")
判断字符串是否以指定的字符串开头:strings.HasPrefix("leijianx","lei")
判断字符串是否以指定的字符结束:strings.HasSuffix("leijianx",”ianx")
8.2日期与时间的函数
需要导入time包
获取当前时间需要调用NOW函数:now := time.NOW 返回值是一个结构体,对应的类型是time.Time
日期的格式化:
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 数组的遍历
普通for循环,就不再演示了
键值循环
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 数组注意细节
长度属于数字类型的一部分
package main import "fmt" func main() { var arr1 = [3]int{3, 6, 9} fmt.Printf("数组的类型为: %T", arr1) //数组的类型为: [3]int }
Go数组属性类型,在默认情况下是值传递,因此会进行值拷贝
如果想在其他函数中去修改原来的数组,可以使用引用传递(指针方式)
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 二维数组
二维数组的内存分析:
二维数组的遍历:
双重for循环遍历,就不再赘述了
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 切片的内存分析
切片的底层数据结构包含三部分:
指向底层数组的指针
切片的长度
切片的容量
因此当数组改变时,切片的值也会因此改变
11.2 切片的定义
定义一个切片,然后让切片去引用一个已经创建好的数组
var arr [6]int = [6]int{3,6,9,10} slice := arr[1:3]
通过make内置函数创建切片。
package main import "fmt" func main() { // 第一个参数是切片的类型,第二个是切片的长度,第三个是切片的容量 slice := make([]int,4,20) fmt.Println(slice) }
定一个切片,直接就指定具体数组,使用原理类似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 切片的注意事项
切片定义之后不能直接使用,需要让其引用到一个数组,或者make一个空间供切片进行使用
切片使用不能越界
切片可以继续切片
简写方式:
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[:]
切片可以动态增长
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是创建一个新的数组,并将该数添加到新数组当中,因此需要切片重新接收。
切片的拷贝
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不存在,那么就不操作,也不会报错。
清空操作:
如果要删除map里的所有key,没有一个专门的办法一次删除,只能遍历所有key,逐个进行删除
也可以使用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
评论区