Go

标识符

在编程语言中标识符就是程序员定义的具有特殊意义的词,比如变量名、常量名、函数名等等。 Go语言中标识符由字母数字和_(下划线)组成,并且只能以字母和_开头。 举几个例子:abc, _, _123, a123

关键字

关键字是指编程语言中预先定义好的具有特殊含义的标识符。 关键字和保留字都不建议用作变量名。

Go语言中有25个关键字:

1
2
3
4
5
break        default      func         interface    select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

此外,Go语言中还有37个保留字。

1
2
3
4
5
6
7
8
9
10
Constants:    true  false  iota  nil

Types: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error

Functions: make len cap new append copy close delete
complex real imag
panic recover

变量var

变量声明

1
var 变量名 变量类型
1
2
3
4
//变量声明
var name string
var age int
var isOk bool

Go中*非全局*声明变量必须使用

批量声明变量

1
2
3
4
5
6
//批量声明
var(
name string
age int
isOk bool
)

声明变量同时赋值

1
2
//声明变量同时赋值
var name string = "boyiz"

类型推导

1
2
//类型推导(根据值判断是什么类型)
var s = "20"

短变量声明

1
2
//短变量声明 := (只能用于函数内部)
s := "嘿嘿嘿"

匿名变量

在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)

匿名变量用一个下划线_表示,匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。

1
2
3
4
5
6
7
8
9
func foo() (int, string) {
return 10, "QQWechat"
}
func main() {
x, _ := foo()
_, y := foo()
fmt.Println("x=", x)
fmt.Println("y=", y)
}

注意事项:

  1. 函数外的每个语句都必须以关键字开始(var、const、func等)
  2. :=不能使用在函数外。
  3. _多用于占位,表示忽略值。

常量const

定义之后不能被修改, 数值可作为各种类型使用

常量声明

1
const pi = 3.1415926

批量声明常量(枚举)

1
2
3
4
5
6
7
8
9
10
11
12
const(
statueOK = 200
notFound = 404
)

//const同时声明多个常量时,如果声明未赋值,则表示和上面一行的值相同。
//下面示例中,常量n1、n2、n3的值都是100。
const (
n1 = 100
n2
n3
)

iota

iota是go语言的常量计数器,只能在常量的表达式中使用。

iota在const关键字出现时将被重置为0。

const中每新增*一行*常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。

使用iota能简化定义,在定义枚举时很有用。

1
2
3
4
5
6
const (
n1 = iota //0
n2 //1
n3 //2
n4 //3
)

几个常见的iota示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//1.使用`_`跳过某些值
const (
n1 = iota //0
n2 //1
_ //2
n4 //3
)
//2.iota声明中间插队
const (
n1 = iota //0
n2 = 100 //100
n3 //100
n4 //100
)
---------------------
const (
n1 = iota //0
n2 = 100 //100
n3 = iota //2
n4 //3
)
const n5 = iota //0
---------------------
const (
d1,d2 = iota + 1,iota + 2 // iota = 0, d1=1 , d2=2
d3,d4 = iota + 1,iota + 2 // iota = 1, d3=2 , d4=3
)

定义数量级

这里的<<表示左移操作,1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8。

1
2
3
4
5
6
7
8
const (
_ = iota
KB = 1 << (10 * iota) //1024
MB = 1 << (10 * iota)
GB = 1 << (10 * iota)
TB = 1 << (10 * iota)
PB = 1 << (10 * iota)
)

fmt占位符

1
2
3
4
5
6
7
8
9
10
11
12
13
func main(){
var n = 100;
fmt.Printf("%T\n",n)//类型
fmt.Printf("%v\n",n)//查看值,基本万能
fmt.Printf("%b\n",n)//2进制
fmt.Printf("%d\n",n)//10进制
fmt.Printf("%o\n",n)//8进制
fmt.Printf("%x\n",n)//16进制
var s = "Hello"
fmt.Printf("%s\n",s)//Hello
fmt.Printf("%v\n",s)//Hello
fmt.Printf("%#v\n",s)//"Hello"
}

整型

整型分为以下两个大类: 按长度分为:int8、int16、int32、int64 对应的无符号整型:uint8、uint16、uint32、uint64

其中,uint8就是我们熟知的byte型,int16对应C语言中的short型,int64对应C语言中的long型。

类型 描述
uint8 无符号 8位整型 (0 到 255)
uint16 无符号 16位整型 (0 到 65535)
uint32 无符号 32位整型 (0 到 4294967295)
uint64 无符号 64位整型 (0 到 18446744073709551615)
int8 有符号 8位整型 (-128 到 127)
int16 有符号 16位整型 (-32768 到 32767)
int32 有符号 32位整型 (-2147483648 到 2147483647)
int64 有符号 64位整型 (-9223372036854775808 到 9223372036854775807)

特殊整型

类型 描述
uint 32位操作系统上就是uint32,64位操作系统上就是uint64
int 32位操作系统上就是int32,64位操作系统上就是int64
uintptr 无符号整型,用于存放一个指针

注意: 在使用intuint类型时,不能假定它是32位或64位的整型,而是考虑intuint可能在不同平台上的差异。

注意事项: 获取对象的长度的内建len()函数返回的长度可以根据不同平台的字节长度进行变化。实际使用中,切片或 map 的元素数量等都可以用int来表示。在涉及到二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用intuint

数字字面量语法(Number literals syntax)

Go1.13版本之后引入了数字字面量语法,这样便于开发者以二进制、八进制或十六进制浮点数的格式定义数字,例如:

v := 0b00101101, 代表二进制的 101101,相当于十进制的 45。 v := 0o377,代表八进制的 377,相当于十进制的 255。 v := 0x1p-2,代表十六进制的 1 除以 2²,也就是 0.25。

而且还允许我们用 _ 来分隔数字,比如说: v := 123_456 表示 v 的值等于 123456。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 十进制
var a int = 10
fmt.Printf("%d \n", a) // 10
fmt.Printf("%b \n", a) // 1010 占位符%b表示二进制

// 八进制 以0开头
var b int = 077
fmt.Printf("%o \n", b) // 77

// 十六进制 以0x开头
var c int = 0xff
fmt.Printf("%x \n", c) // ff
fmt.Printf("%X \n", c) // FF

浮点型

Go语言支持两种浮点型数:float32float64。这两种浮点型数据格式遵循IEEE 754标准: float32 的浮点数的最大范围约为 3.4e38,可以使用常量定义:math.MaxFloat32float64 的浮点数的最大范围约为 1.8e308,可以使用一个常量定义:math.MaxFloat64

1
2
3
4
5
6
7
func main(){
f1 := 1.23456
fmt.Printf("%T\n",f1)//float64 ——默认go中的使用float64类型
f2 := float32(1.23456)
fmt.Printf("%T\n",f2)//float32
f1 = f2 //错误❌,类型不同无法赋值
}

复数

complex64和complex128

1
2
3
4
5
6
var c1 complex64
c1 = 1 + 2i
var c2 complex128
c2 = 2 + 3i
fmt.Println(c1)
fmt.Println(c2)

复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位。

image-20211220141621997

布尔值

Go语言中以bool类型进行声明布尔型数据,布尔型数据只有true(真)false(假)两个值。

注意:

  1. 布尔类型变量的默认值为false
  2. Go 语言中不允许将整型强制转换为布尔型.
  3. 布尔型无法参与数值运算,也无法与其他类型进行转换。
1
2
3
4
b1 := true;
var b2 bool
fmt.Printf("%T\n",b1) //bool
fmt.Printf("%T value:%v \n",b2,b2) //bool value:false

字符串

Go语言中的字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样。 Go 语言里的字符串的内部实现使用UTF-8编码。

字符串的值为双引号(")中的内容, 单引号(')表示为字符

字符串转义符

转义符 含义
\r 回车符(返回行首)
\n 换行符(直接跳到下一行的同列位置)
\t 制表符
\' 单引号
\" 双引号
\\ 反斜杠

多行字符串

Go语言中要定义一个多行字符串时,就必须使用反引号字符,反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。

1
2
3
4
5
s1 := `第一行
第二行
第三行
`
fmt.Println(s1)

字符串的常用操作

方法 介绍
len(str) 求长度
+或fmt.Sprintf 拼接字符串
strings.Split 分割
strings.contains 判断是否包含
strings.HasPrefix,strings.HasSuffix 前缀/后缀判断
strings.Index(),strings.LastIndex() 子串出现的位置
strings.Join(a[]string, sep string) join操作
1
2
3
4
5
6
7
8
9
fmt.Println(len(s3))
//字符串拼接
name := "hello"
world := "world"
ss := name + world
fmt.Println(ss)//helloworld
ss1 := fmt.Sprintf("%s%s",name,world)
fmt.Printf("%s%s",name,world)//helloworld
fmt.Println(ss1)//helloworld

if

if的条件不需要括号

if的条件可以赋值,变量作用域只在if语句范围内

1
2
3
4
5
6
7
8
9
func main() {
const filename = "abc.txt"
contents , err := ioutil.ReadFile(filename)
if err!= nil {
fmt.Println(err)
}else {
fmt.Printf("%s\n" , contents)
}
}
1
2
3
4
5
	if contents , err := ioutil.ReadFile(filename);err!= nil {
fmt.Println(err)
}else {
fmt.Printf("%s\n" , contents)
}

特殊写法:在 if 表达式之前添加一个执行语句,再根据变量值进行判断

1
2
3
4
5
6
7
8
9
func ifDemo() {
if score := 65; score >= 90 {
fmt.Println("A")
} else if score > 75 {
fmt.Println("B")
} else {
fmt.Println("C")
}
}

switch

switch后面可以没有表达式

switch可以自动break,除非使用fallthrough

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func grade(score int ) string  {
g := ""
switch {
case score<0|| score>100:
panic(fmt.Sprintf("Error score : %d", score)) //报错,中断程序执行
case score< 60:
g = "F"
case score<80:
g = "C"
case score<90:
g = "B"
case score<=100:
g = "A"
default:
panic(fmt.Sprintf("Error score : %d", score))
}
return g
}

一个分支可以有多个值,多个case值中间使用英文逗号分隔。

1
2
3
4
5
6
7
8
9
10
func testSwitch() {
switch n := 7; n {
case 1, 3, 5, 7, 9:
fmt.Println("奇数")
case 2, 4, 6, 8:
fmt.Println("偶数")
default:
fmt.Println(n)
}
}
image-20211220151047233

for

for条件不需要括号

for可以省略 初始条件、结束条件,递增表达式

省略初始条件,相当于while

image-20211220152538914
1
2
3
4
5
6
7
8
func convertToBin(n int) string {
result := ""
for ;n>0;n /= 2 {
lsb := n%2
result = strconv.Itoa(lsb)+result
}
return result
}
1
2
3
4
5
6
7
8
9
10
func printFile(fileName string)  {
file ,err := os.Open(fileName)
if err != nil {
fmt.Println(err)
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Printf("%s\n" , scanner.Text())
}
}
1
2
3
4
5
func forever()  {  //死循环
for {
fmt.Println("abc")
}
}

for range(键值循环)

Go语言中可以使用for range遍历数组、切片、字符串、map 及通道(channel)。 通过for range遍历的返回值有以下规律:

  1. 数组、切片、字符串返回索引和值。
  2. map返回键和值。
  3. 通道(channel)只返回通道内的值。

goto

goto语句通过标签进行代码间的无条件跳转。goto语句可以在快速跳出循环、避免重复退出上有一定的帮助。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func gotoDemo1() {
var breakFlag bool
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
// 设置退出标签
breakFlag = true
break
}
fmt.Printf("%v-%v\n", i, j)
}
// 外层for循环判断
if breakFlag {
break
}
}
}

使用goto语句能简化代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func gotoDemo2() {
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
// 设置退出标签
goto breakTag
}
fmt.Printf("%v-%v\n", i, j)
}
}
return
// 标签
breakTag:
fmt.Println("结束for循环")
}

break

break语句可以结束forswitchselect的代码块。

break语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的forswitchselect的代码块上。

1
2
3
4
5
6
7
8
9
10
11
12
func breakDemo() {
BREAKDEMO:
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
break BREAKDEMO
}
fmt.Printf("%v-%v\n", i, j)
}
}
fmt.Println("...")
}

continue

continue语句可以结束当前循环,开始下一次的循环迭代过程,仅限在for循环内使用。

continue语句后添加标签时,表示开始标签对应的循环。

1
2
3
4
5
6
7
8
9
10
11
12
func continueDemo() {
forloop1:
for i := 0; i < 5; i++ {
// forloop2:
for j := 0; j < 5; j++ {
if i == 2 && j == 2 {
continue forloop1
}
fmt.Printf("%v-%v\n", i, j)
}
}
}

函数

没有默认参数,可选参数

函数可以返回多个值

1
2
3
4
5
6
func div(a,b int) (int ,int)  {
return a/b,a%b
}
func main() {
fmt.Println(div(13,4))
}

返回多个值时,可以取名字(仅用于非常简单的函数 ,对于调用者没差别 )

1
2
3
4
5
6
7
8
func div(a,b int) (q,r int)  {
q = a/b
r = a%b
return
}
func main() {
a,b :=div(13,4)
}

函数的参数也可以是函数

调用时也可直接写匿名函数

1
2
3
4
5
6
7
func apply(op func(int,int) int , a,b int) int {
fmt.Println("Calling %s with %d %d \n",
runtime.FuncForPC(reflect.ValueOf(op).Pointer()).Name(),
a,
b)
return op(a,b)
}
image-20211224145326043

可变参数列表(不确定个数的参数)

1
2
3
4
5
6
7
8
9
10
11
func sum(numbers ...int) int {
s := 0
for i := range numbers{
s += numbers[i]
}
return s
}
func main(){
fmt.Println(sum(1,2,3,4,5))
}

指针

指针不能运算

1
2
3
4
5
6
func main() {
var a int = 2
var po *int = &a
*po = 3
fmt.Println(a)
}

Go语言只有值传递一种方式

image-20211228234745031
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//交换两个数
func swap1(a ,b *int ) {
*a ,*b = *b ,*a
}
func swap2(a ,b int ) (int,int){
return b, a
}
func main() {
a,b := 3,4
swap1(&a,&b)
fmt.Println(a,b)

a ,b := swap2(3,4)
fmt.Println(a,b)
}

数组

Go中一般不使用数组,而使用切片

1
2
3
4
5
6
7
8
9
10
func main() {
//初始化数组
var arr1 [5]int
arr2 := [3]int{1,3,5}
arr3 := [...]int{2,4,6,8}//不指定长度
//二维数组
var grid [3][3] int
fmt.Println(arr1,arr2,arr3)
fmt.Println(grid)
}

遍历数组

1
2
3
4
5
6
7
8
9
10
11
//遍历数组
for i:=0;i<len(arr3);i++{
fmt.Println(arr3[i])
}
for i:=range arr3{
fmt.Println(arr3[i])
}
//i——下标,v——值
for i,v:=range arr3{
fmt.Println(i,v)
}

[5]int[3]int是不同类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func printArray(arr [5]int)  {
for i,v:=range arr{
fmt.Println(i,v)
}
}
func main() {
//初始化数组
var arr1 [5]int
arr2 := [3]int{1,3,5}
arr3 := [...]int{2,4,6,8,10}//不指定长度

printArray(arr1)
printArray(arr2)//错误:cannot use arr2 (type [3]int) as type [5]int in argument to printArray
printArray(arr3)
}

数组是值类型——在调用时对数组进行一个拷贝,不会修改原数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func printArray(arr [5]int)  {
arr[1] = 100
for i,v:=range arr{
fmt.Print(i,v)
}
}
func main() {
//初始化数组
var arr1 [5]int
fmt.Println("调用printArray(arr1)")
printArray(arr1)//0 0 1 100 2 0 3 0 4 0
fmt.Println("输出arr1")
fmt.Println(arr1) //[0 0 0 0 0] 原数组并未改变
}

可使用指针类型调用时修改数组

1
2
3
4
5
6
func printArray(arr *[5]int)  {
arr[1] = 100
for i,v:=range arr{
fmt.Println(i,v)
}
}

二维数组

二维数组定义:

1
2
3
4
5
6
7
8
9
func main() {
a := [3][2]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}
fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]]
fmt.Println(a[2][1]) //支持索引取值:重庆
}

遍历:

1
2
3
4
5
6
for _, v1 := range a {
for _, v2 := range v1 {
fmt.Printf("%s\t", v2)
}
fmt.Println()
}

切片Slice

Slice底层没有数据,是对底层array的一个view

Slice可以向后扩展,不能向前扩展

使用内置的len()函数求长度,使用内置的cap()函数求切片的容量

image-20220101161346256
1
2
3
4
5
6
7
func main() {
arr := [...]int{0,1,2,3,4,5,6,7,8,9}
fmt.Println(arr[2:6]) //[2 3 4 5]
fmt.Println(arr[2:]) //[2 3 4 5 6 7 8 9]
fmt.Println(arr[:6]) //[0 1 2 3 4 5]
fmt.Println(arr[:]) //[0 1 2 3 4 5 6 7 8 9]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func updateSlice(s []int)  {
s[0] = 100
}
func main() {
arr := [...]int{0,1,2,3,4,5,6,7,8,9}
s1 := arr[2:]
fmt.Println("s1=",s1) //[2 3 4 5 6 7 8 9]
s2 := arr[:]
fmt.Println("s2=",s2) //[0 1 2 3 4 5 6 7 8 9]
fmt.Println("after updateSlice(s1)")
updateSlice(s1)
fmt.Println(s1) //[100 3 4 5 6 7 8 9]
fmt.Println(arr) //[0 1 100 3 4 5 6 7 8 9]
fmt.Println("after updateSlice(s2)")
updateSlice(s2)
fmt.Println(s2) //[100 1 100 3 4 5 6 7 8 9]
fmt.Println(arr) //[100 1 100 3 4 5 6 7 8 9]

fmt.Println("Reslice")
fmt.Println(s2) //[100 1 100 3 4 5 6 7]
s2 =s2[:5]
fmt.Println(s2) //[100 1 100 3 4]
s2 =s2[2:]
fmt.Println(s2) //[100 3 4]
}

Slice的扩展

image-20220101152727693
  • (勘误)上图应为 s2:=s1[3:5]

  • s[i]不能超越len(s),向后扩展不能超越底层数组cap(s)

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
arr := [...]int{0,1,2,3,4,5,6,7}
s1 := arr[2:6]
fmt.Println("s1=",s1) //[2 3 4 5]
s2 := s1[3:5] //[5 6]
fmt.Println("s2=",s2)
fmt.Printf("s1=%v , len(s1)=%d , cap(s1)=%d \n",
s1 , len(s1) , cap(s1)) //s1=[2 3 4 5] , len(s1)=4 , cap(s1)=6
fmt.Printf("s2=%v , len(s2)=%d , cap(s2)=%d \n",
s2 , len(s2) , cap(s2)) //s2=[5 6] , len(s2)=2 , cap(s2)=3

}

Slice中添加元素append

  1. 若添加元素超越原始slice长度,系统会重新分配一个新的更大的底层数组,并将原数据copy过去。
  2. 由于值传递的缘故,必须接收append的返回值——> s = append(s,val)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
arr := [...]int{0,1,2,3,4,5,6,7}
s1 := arr[2:6]
fmt.Println("s1=",s1) //[2 3 4 5]
s2 := s1[3:5] //[5 6]
s3:=append(s2,10)
fmt.Println("s3=",s3) //[5 6 10]
fmt.Println("arr=",arr) //[0 1 2 3 4 5 6 10]
s4:=append(s3,11) //s4 和 s5 不再是对arr的view,系统重新分配了一个新的arr
fmt.Println("s4=",s4) //[5 6 10 11]
fmt.Println("arr=",arr) //[0 1 2 3 4 5 6 10]
s5:=append(s4,12)
fmt.Println("s5=",s5) //[5 6 10 11 12]
fmt.Println("arr=",arr) //[0 1 2 3 4 5 6 10]
}

Slice的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func printSlice(s []int) {
fmt.Printf("%v , len=%d , cap=%d \n",s ,len(s),cap(s))
}
func main() {

//定义一个slice
var s []int // Zero value for slice is nil
for i := 0;i<100;i++ {
printSlice(s)
s = append(s,2*i+1)
}
fmt.Println(s)

//定义一个slice
s1 :=[]int{2,4,6,8}
printSlice(s1) //[2 4 6 8] , len=4 , cap=4

//定义一个slice
s2 := make([]int,16) //长度为16的slcie
printSlice(s2) //[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] , len=16 , cap=16

//定义一个slice
s3 := make([]int,16,32) //长度为16,cap为32 的slcie
printSlice(s3) //[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] , len=16 , cap=32

}

Slice的copy

1
copy(destSlice, srcSlice []T)  //srcSlice: 数据来源切片 ,destSlice: 目标切片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {

//定义一个slice
s1 :=[]int{2,4,6,8}
printSlice(s1)

//定义一个slice
s2 := make([]int,16) //长度为16的slcie
printSlice(s2)

copy(s2,s1)
printSlice(s2) //[2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0] , len=16 , cap=16

}

Slice的delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func main() {

//定义一个slice
s1 :=[]int{2,4,6,8}
printSlice(s1)

//定义一个slice
s2 := make([]int,16) //长度为16的slcie
printSlice(s2)

copy(s2,s1)
printSlice(s2) //[2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0] , len=16 , cap=16

s2 = append(s2[:3],s2[4:]...) //删除第四个元素
printSlice(s2) //[2 4 6 0 0 0 0 0 0 0 0 0 0 0 0] , len=15 , cap=16

front := s2[0] //删除第一个元素
s2 = s2[1:]
printSlice(s2) //[4 6 0 0 0 0 0 0 0 0 0 0 0 0] , len=14 , cap=15

tail := s2[len(s2)-1] //删除最后一个元素
s2 = s2[:len(s2)-1]
printSlice(s2) //[4 6 0 0 0 0 0 0 0 0 0 0 0] , len=13 , cap=15

fmt.Println(front,tail)
}

slice判空

使用len(s) == 0来判断,而不应该使用s == nil来判断

slice不能直接比较

不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil比较。 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。但是我们不能说一个长度和容量都是0的切片一定是nil

1
2
3
var s1 []int         //len(s1)=0;cap(s1)=0;s1==nil
s2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nil
s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil

slice赋值拷贝

拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容

1
2
3
4
5
6
7
func main() {
s1 := make([]int, 3) //[0 0 0]
s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组
s2[0] = 100
fmt.Println(s1) //[100 0 0]
fmt.Println(s2) //[100 0 0]
}

slice遍历

1
2
3
4
5
6
7
8
9
10
11
func main() {
s := []int{1, 3, 5}

for i := 0; i < len(s); i++ {
fmt.Println(i, s[i])
}

for index, value := range s {
fmt.Println(index, value)
}
}

Map

1
2
3
4
map[KeyType]ValueType  //KeyType:表示键的类型 , ValueType:表示键对应的值的类型

//复合Map:
map[K1]map[K2]V

map使用哈希表,可以比较相等

除了slice、map、function的内建类型都可以作为key

struct类型不包含上述字段,也可作为key

map的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//map的创建
func main() {
//map[K]V
m1:= map[string] string{
"name":"zzz",
"couse":"golang",
"site":"study",
"quality":"good",
}
fmt.Println(m1) //map[couse:golang name:zzz quality:good site:study]

m2 := make(map[string]int)// m2 == empty map
fmt.Println(m2) //map[]

var m3 map[string]int // m3 == nil
fmt.Println(m3) //map[]
}

map的遍历

用range遍历,但不保证顺序,需手动对key排序

1
2
3
4
5
6
7
8
9
10
//遍历map,key是无序的,每次遍历结果可能不同
for k,v := range m1 {
fmt.Println(k,v)
}
for k := range m1 {
fmt.Println(k) //只要key
}
for _,v := range m1 {
fmt.Println(v) //只要value
}

map值的获取

获取元素:m[key],key不存在时,返回value类型的初始值

value, ok := m[key] 来判断是否存在key

1
2
3
4
5
couseName := m1["couse"]
fmt.Println(couseName) //golang

causeName := m1["cause"]
fmt.Println(causeName) //空串
1
2
3
4
5
6
7
8
9
10
11
couseName,ok := m1["couse"] //判断key是否存在
fmt.Println(couseName,ok) //golang true

causeName,ok := m1["cause"]
fmt.Println(causeName,ok) // false

if couseName,ok := m1["couse"];ok {
fmt.Println(couseName)
}else{
fmt.Println("key does not exit")
}

map的删除

1
delete(m1,"name")

元素为map类型的切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
var mapSlice = make([]map[string]string, 3)
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
//index:0 value:map[]
//index:1 value:map[]
//index:2 value:map[]

fmt.Println("after init")
// 对切片中的map元素进行初始化
mapSlice[0] = make(map[string]string, 10)
mapSlice[0]["name"] = "小王子"
mapSlice[0]["password"] = "123456"
mapSlice[0]["address"] = "沙河"
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
}
//index:0 value:map[address:沙河 name:小王子 password:123456]
//index:1 value:map[]
//index:2 value:map[]

值为切片类型的map

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
var sliceMap = make(map[string][]string, 3)
fmt.Println(sliceMap) //map[]
fmt.Println("after init")
key := "中国"
value, ok := sliceMap[key]
if !ok {
value = make([]string, 0, 2)
}
value = append(value, "北京", "上海")
sliceMap[key] = value
fmt.Println(sliceMap) //map[中国:[北京 上海]]
}
1
2
3
4
5
6
7
8
9
10
11
func main() {
type Map map[string][]int
m := make(Map)
s := []int{1, 2}
s = append(s, 3)
fmt.Printf("%+v\n", s)//[1 2 3]
m["q1mi"] = s
s = append(s[:1], s[2:]...)
fmt.Printf("%+v\n", s)//[1 3]
fmt.Printf("%+v\n", m["q1mi"])//[1 3 3]
}

寻找最长不含有重复字符的子串

image-20220101171444963
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func lenOfNonRepeatingSubStr(s string) int {
lastOccurred:= make(map[byte]int)
start:=0
maxLength := 0

for i,ch := range []byte(s){
lastI,ok := lastOccurred[ch]
// lastI 为 lastOccurred[ch]所获得的ch的下标
// ok 为 lastOccurred[ch]中ch是否存在
if ok&&lastI>=start { //为true表示当前字母存在,start值 = 字母上一次出现的位置后移一位
start = lastI +1
}
if i-start+1>maxLength {
maxLength= i-start+1
}
lastOccurred[ch]=i //不存在则加入,存在则修改为最新一次出现的位置
}
return maxLength
}

字符串

rune相当于go中的char

  • 使用range遍历pos,rune对
  • 使用utf8.RuneCountInString(s)获得字符数量
  • 使用len()获得字节长度
  • 使用[] byte获得字节
  • 其他字符操作:strings包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func main() {
s:="Hi好好学习!" //UTF-8
for _,b := range []byte(s) {
fmt.Printf("%X ",b) //48 69 E5 A5 BD E5 A5 BD E5 AD A6 E4 B9 A0 EF BC 81 一个中文三个字节
}
fmt.Println()
for i,ch := range s { //ch是一个rune
fmt.Printf("(%d %X) ",i,ch)//(0 48) (1 69) (2 597D) (5 597D) (8 5B66) (11 4E60) (14 FF01)
}
fmt.Println()

fmt.Println(utf8.RuneCountInString(s)) //7

bytes := []byte(s)
for len(bytes)> 0 {
ch,size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
fmt.Printf("%c ",ch)//H i 好 好 学 习 !
}
fmt.Println()
for i,ch := range []rune(s){
fmt.Printf("(%d %c) ",i,ch)//(0 H) (1 i) (2 好) (3 好) (4 学) (5 习) (6 !)
}
}

类型别名和自定义类型

在Go语言中有一些基本的数据类型,如string整型浮点型布尔等数据类型, Go语言中可以使用type关键字来定义自定义类型。

自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。

1
2
3
//自定义类型
//将MyInt定义为int类型
type MyInt int

类型别名是Go1.9版本添加的新功能。

类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。

1
type TypeAlias = Type

runebyte就是类型别名,定义如下:

1
2
type byte = uint8
type rune = int32

类型定义和类型别名的区别

类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
//类型定义
type NewInt int

//类型别名
type MyInt = int

func main() {
var a NewInt
var b MyInt

fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt
fmt.Printf("type of b:%T\n", b) //type of b:int
}

结果显示a的类型是main.NewInt,表示main包下定义的NewInt类型。

b的类型是intMyInt类型只会在代码中存在,编译完成时并不会有MyInt类型。

结构体

Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct

Go语言中通过struct来实现面向对象。

1
2
3
4
5
type 类型名 struct {
字段名 字段类型
字段名 字段类型

}

其中:

  • 类型名:标识自定义结构体的名称,在同一个包内不能重复。
  • 字段名:表示结构体字段名。结构体中的字段名必须唯一。
  • 字段类型:表示结构体字段的具体类型。

结构体实例化

只有当结构体实例化时,才会真正地分配内存。

结构体本身也是一种类型,可以像声明内置类型一样使用var关键字声明结构体类型。

1
var 结构体实例名 结构体类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type person struct {
name string
city string
age int8
}

func main() {
var p1 person
p1.name = "正方形"
p1.city = "南京"
p1.age = 18
fmt.Printf("p1=%v\n", p1) //p1={正方形 南京 18}
fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"正方形", city:"南京", age:18}
}

匿名结构体

1
2
3
4
5
6
func main() {
var user struct{Name string; Age int}
user.Name = "小王子"
user.Age = 18
fmt.Printf("%#v\n", user)
}

指针类型结构体

使用new关键字对结构体进行实例化,得到的是结构体的地址。 格式如下:

1
2
3
var p2 = new(person)
fmt.Printf("%T\n", p2) //*main.person
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}

从打印的结果可以看出p2是一个结构体指针。

取结构体的地址实例化

使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作。

1
2
3
4
5
6
7
p3 := &person{}
fmt.Printf("%T\n", p3) //*main.person
fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0}
p3.name = "正方形"
p3.age = 18
p3.city = "南京"
fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"正方形", city:"南京", age:18}

p3.name = "正方形"其实在底层是(*p3).name = "正方形",这是Go语言帮我们实现的语法糖。

使用键值对初始化

使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值。

1
2
3
4
5
6
p5 := person{
name: "正方形",
city: "南京",
age: 18,
}
fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"正方形", city:"南京", age:18}

也可以对结构体指针进行键值对初始化,例如:

1
2
3
4
5
6
p6 := &person{
name: "正方形",
city: "南京",
age: 18,
}
fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"正方形", city:"南京", age:18}

当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值。

1
2
3
4
p7 := &person{
city: "南京",
}
fmt.Printf("p7=%#v\n", p7) //p7=&main.person{name:"", city:"南京", age:0}

使用值的列表初始化

初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值:

1
2
3
4
5
6
p8 := &person{
"正方形",
"南京",
18,
}
fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"正方形", city:"南京", age:18}

使用这种格式初始化时,需要注意:

  1. 必须初始化结构体的所有字段。
  2. 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
  3. 该方式不能和键值初始化方式混用。

结构体内存布局

结构体占用一块连续的内存。

空结构体不占用空间的。

1
2
3
4
5
6
7
8
9
10
11
12
13
type test struct {
a int8
b int8
c int8
d int8
}
n := test{
1, 2, 3, 4,
}
fmt.Printf("n.a %p\n", &n.a)//n.a 0xc0000a0060
fmt.Printf("n.b %p\n", &n.b)//n.a 0xc0000a0061
fmt.Printf("n.c %p\n", &n.c)//n.a 0xc0000a0062
fmt.Printf("n.d %p\n", &n.d)//n.a 0xc0000a0063

面向对象

go中仅支持封装,不支持继承和多态

go没有class,只有struct

结构定义,无论是地址还是结构本身,一律使用.来访问成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type treeNode struct {
value int
left,right *treeNode
}
func main() {
var root treeNode
fmt.Println(root)//{0 <nil> <nil>}

root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right.left = new(treeNode)

nodes := []treeNode{
{value: 3},
{},
{7,nil,&root},
}
fmt.Println(nodes)
}
1
2
3
4
5
6
7
//自定义工厂函数
func createNode(value int) *treeNode { //工厂函数,设置初始化 值
return &treeNode{value:value} //返回了局部变量的地址
}
//main()
root.left.right = createNode(1)

给结构定义方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//(node treeNode)——>方法的接收者
func (node treeNode) print() {
fmt.Print(node.value)
}
//赋值失败,值传递。
func (node treeNode) setValue(value int) {
node.value = value
}
//赋值成功,只有指针才能改变结构内容。(指针作为方法接收者)
func (node *treeNode) setValue(value int) {
node.value = value
}

//main()
root.right.left.setValue(99)
root.right.left.print()

nil也可以调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (node *treeNode) setValue(value int)  {
if node == nil{
fmt.Println("node is nil")
return
}
node.value = value
}
//main()
var pRoot *treeNode
pRoot.setValue(100)
pRoot = &root
pRoot.setValue(200)
pRoot.print()

遍历treeNode

1
2
3
4
5
6
7
8
func (node *treeNode) tranverse()  {
if node == nil{
return
}
node.left.tranverse()
node.print()
node.right.tranverse()
}

值接收者 VS 指针接收者

  • 要改变内容必须要用指针接收者
  • 结构过大也要考虑使用指针接收者
  • Tip:一致性:如果有指针接收者,最好都是用指针接收者

封装

  • 名字一般使用CamelCase
  • 首字母大写:public(针对于包)
  • 首字母小写:private(针对于包)

  • 每个目录只能有一个包
  • main包 包含一个可执行入口
  • 为结构定义的方法必须放在同一个包内,可以是不同文件

扩充系统类型或者别人的类型

定义别名:最简单

使用组合:最常用

Embedding 内嵌:省代码,一种语法糖,不易懂

依赖管理

三个阶段:GOPATH,GOVENDOR,go mod

GOPATH,GOVENDOR 已过时

go mod 主流

image-20220112225646510

要启用go module支持首先要设置环境变量GO111MODULE,通过它可以开启或关闭模块支持,它有三个可选值:offonauto,默认值是auto

  1. GO111MODULE=off禁用模块支持,编译时会从GOPATHvendor文件夹中查找包。

  2. GO111MODULE=on启用模块支持,编译时会忽略GOPATHvendor文件夹,只根据 go.mod下载依赖。

  3. GO111MODULE=auto,当项目在$GOPATH/src外且项目根目录有go.mod文件时,开启模块支持。

使用 go module 管理依赖后会在项目根目录下生成两个文件go.modgo.sum

设置GOPROXY:go env -w GOPROXY=https://goproxy.cn,direct

常用的go mod命令

1
2
3
4
5
6
7
8
go mod download    下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录)
go mod edit 编辑go.mod文件
go mod graph 打印模块依赖图
go mod init 初始化当前文件夹, 创建go.mod文件
go mod tidy 增加缺少的module,删除无用的module
go mod vendor 将依赖复制到vendor下
go mod verify 校验依赖
go mod why 解释为什么需要依赖

迁移到go mod管理

1、创建go.mod文件 go mod init xxx文件名

2、go build ./…

接口

接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。

interface是一组method的集合,接口做的事情就像是定义一个协议(规则),不关心对方是什么类型,只关心对方能做什么。这是duck-type programming的一种体现,只要一个物体能像鸭子一样叫那我们就可以称它为鸭子;只要一个软件能存储和查询数据我们就可以称它为数据库;只要一台机器有洗衣服和甩干的功能我们就可以称它为洗衣机。

Go语言中的接口(interface)是一种类型,是一种抽象的类型。

接口的定义

1
2
3
4
5
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2

}

其中:

  • 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
  • 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
1
2
3
4
5
6
7
//接口由使用者定义
type Retriever interface {
Get(url string) string
}
func download(r Retriever)string { //使用者
return r.Get("http://www.baidu.com")
}

接口的实现是隐式的,只需要实现接口里的方法。

接口变量

  • 接口变量自带指针
  • 接口变量同样采用值传递,几乎不需要使用接口的指针
  • 指针接收者实现只能以指针方式使用;值接收者都可以

接口的组合(嵌套)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Retriever interface {
Get(url string) string
}
func download(r Retriever)string { //使用者
return r.Get("http://www.baidu.com")
}
type Poster interface {
Post(url string,form map[string]string) string
}
func post(poster Poster) {
poster.Post("http://www.baidu.com", map[string]string{
"name":"baidu",
"value":"百度",
})
}
type RetrieverPoster interface { //接口的组合
Retriever
Poster
}

空接口,任意类型 interface{}

空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。

空接口类型的变量可以存储任意类型的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
// 定义一个空接口x
var x interface{}
s := "Hello"
x = s
fmt.Printf("type:%T value:%v\n", x, x)
i := 100
x = i
fmt.Printf("type:%T value:%v\n", x, x)
b := true
x = b
fmt.Printf("type:%T value:%v\n", x, x)
}

空接口的应用

  • 空接口类型作为函数的参数

    1
    2
    3
    4
    // 空接口作为函数参数
    func show(a interface{}) {
    fmt.Printf("type:%T value:%v\n", a, a)
    }
  • 空接口类型作为map的value

    1
    2
    3
    4
    var m = make(map[string]interface{})
    m["name"] = "正方形"
    m["age"] = 18
    m["hobby"] = []string{"music","dance","game"}

接口类型断言

一个接口的值(简称接口值)是由一个具体类型具体类型的值两部分组成的。这两部分分别称为接口的动态类型动态值

语法格式:

1
2
3
x.(T)
//x:表示类型为interface{}的变量
//T:表示断言x可能是的类型。
image-20220119154517043
1
2
3
4
5
6
7
8
9
10
var x interface{}
x = "hello"
x = 18
x = true
res,ok :=x.(string) //类型断言。类型不对ok=false,res为string类型的零值。
if !ok {
fmt.Println("不是字符串")
} else{
fmt.Println("是字符串",res)
}

​ 使用switch进行类型断言

1
2
3
4
5
6
7
8
9
10
switch v := x.(type) {
case string:
fmt.Printf("字符串,value=%v\n", v)
case int:
fmt.Printf("int类型,value=%v\n", v)
case bool:
fmt.Printf("布尔类型,value=%v\n", v)
default:
fmt.Println("unsupport type!")
}

函数与闭包

image-20220121141505185
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// adder()的返回类型为 func(int) int
func adder() func(int) int {
sum:=0
return func(i int) int {
sum+=i
return sum
}
}

//"正统"的函数式编程
type iAdder func(int) (int,iAdder)
func adder2(base int) iAdder {
return func(i int) (int, iAdder) {
return base+i,adder2(base+i)
}
}

func main() {
a := adder() //a的类型为func(int) int
for i := 0; i <10 ; i++ {
fmt.Println(a(i))
}

a := adder2(0)
for i := 0; i < 10; i++ {
var s int
s , a = a(i)
fmt.Println(s)
}
}

defer调用

  • 确保调用在函数结束时发生
  • 参数在defer语句时计算
  • defer列表为后进先出

错误处理

1
err = errors.New("this is a custom error")
1
2
3
4
5
6
7
8
file,err := os. Open ("abc.txt")
if err!=nil{
if pathError, ok := err. (*os. PathError); ok {
fmt. Println(pathError.Err) //自定义实现error接口,如上,则输出:this is a custom error
} else {
fmt. Println("unknown error", err)
}
}

goroutine

协程Corutine

  • 是一个轻量级“线程”,

  • 是一个非抢占式多任务处理,由协程主动交出控制权

  • 编译器/解释器/虚拟机层面的多任务

  • 多个协程可能在一个或多个线程上运行

image-20220129152202183
1
2
3
4
5
6
7
8
9
10
func main() {
for i := 0; i < 1000; i++ {
go func(i int) {
for {
fmt.Printf("hello, goroutine %d \n",i)
}
}(i)
}
time.Sleep(time.Millisecond)
}

goroutine定义

  • 任何函数只需要加上go就能送给调度器运行
  • 不需要在定义时区分是否是异步函数
  • 调度器在合适的点进行切换
  • 使用-race来检测数据访问冲突
image-20220129151615017