Go基础
1、包
1.1 概述
- 每个Go程序都是由包构成的
- 每个.go都要属于一个包
- 程序从main包开始
1.2 导入包
-
通过
import
关键字来导入包 -
自定义的包从
go.mod
文件所在的那个目录算起 -
导入多个包用()括起来
-
导入包可以指定别名
-
import test "Go-basic/testpakage"
-
后面再使用的时候就用
test.*
即可
-
-
若在报名前用一个
.
,表示直接导入到该代码中,后面使用的时候直接使用变量名
,无需使用包名.变量名
-
import . "Go-basic/testpakage"
-
1.2.2 匿名导入
-
可以匿名导入一个包(用下划线来代表匿名导入),导入后代码中不能使用该包中方法和变量,一般用来执行该包的一些初始方法
-
import _ "Go-basic/testpakage"
-
1.3 已导出
- 在一个包下的变量和方法来说,如果首字母大写,代表该变量和方法是已导出的,可以在别的包中使用
- 小写的话,仅能在同一个包下使用,一个包可以包含多个.go文件
2、函数
2.1 函数定义格式
2.1.1 一般定义格式
-
函数通过
func
关键字定义,一般格式为-
func 函数名(参数列表) 返回值类型 { }
-
func add(x int, y int) int { return x + y; }
-
-
注意,类型在变量名后面,相同的类型,类型可以只保留一个
-
func add(x, y int) int { return x + y; }
-
2.1.2 多值返回
-
函数可以多值返回,返回多个值,一旦返回数量大于1时,需要用括号括起来,一个返回值时可以不用括号
-
func swap(x int, y int) (int, int) { return y, x }
-
2.1.3 命名返回值
-
函数可以直接在返回类型那里定义返回的变量名,在后面的函数体中可以直接使用,无需在创建
-
此时一个返回值,也需要用括号括起来
-
func swap(x int, y int) (a int, b int) { a = y b = x return } func add(x int, y int) (c int) { c = x + y return }
-
3、变量
3.1 概述
-
通过
var
关键字来声明一个变量,基本格式为var 变量名 变量类型
-
var
定义的变量可以出现在包中(函数外),也可以出现在函数中-
import "fmt" // 在函数外定义 var a int = 9 func main() { // 在函数内定义 var b int = 10 fmt.Println(b) }
-
3.2 变量初始化
-
变量可以直接初始化,如果没有直接初始化,会被赋予一个默认值
-
如果直接初始化后,可以省略变量类型,go会从变量值中推断出变量类型
-
func main() { // 省略了int var a = 100 // 完整形式 var b int = 10 // 未直接初始化,会有一个默认值,此时int不能省略 var c int fmt.Println(a, b, c) }
-
3.3 短变量声明
-
可以使用简洁赋值语句
:=
来赋值,无需关键字var
,和变量类型-
func main() { // 直接使用:=来赋值,会自动推断变量类型 a := 100 fmt.Println(a, b, c) }
-
-
注意:只能在函数内使用,不能在函数外使用,函数外必须使用关键字
var
4、数据类型
4.1 基本数据类型
-
整数类型:
-
int(常用) int8 int16 int32 int64 uint(常用) uint8 uint16 uint32 uint64 uintptr byte // uint8 的别名 rune // int32 的别名,表示一个 Unicode 码点
-
在未指定变量类型时,整数默认为int类型
-
在未指定变量类型时,字符默认为int32类型
-
-
布尔类型
bool
-
字符串类型
string
-
浮点数类型
float32 float64
- 在未指定变量类型时,浮点数默认为float64类型
-
复数类型
complex64 complex128
- 在未指定变量类型时,复数默认为complex128类型
-
指针类型
uintptr
-
注意事项
int
,uint
和uintptr
在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽
4.2 零值
- 数值类型的零值为0
- 布尔类型的零值为false
- 字符串类型的零值为""(注意不是nil,是空字符串)
4.3 类型转化
-
T(v)
可以将值v转换为T类型,需要显示转换,别名之间(int32<->rune, byte<->uint8)
转换无需显示说明,但是自定义的别名就需要显示转换了-
type i int func main() { // 默认为int类型 a := 1 var b int32 = 3 // int32和rune可以直接转换 var c rune = b //var d i = a,无法直接转换,自定义的别名必须强转 fmt.Println(a, b, c) }
-
5、常量
-
常量是使用
const
关键字来声明的量-
// float64可以省略,但是必须初始化 const Pi float64 = 3.14
-
-
和
var
用法类似,但是必须要初始化,不能使用:=
6、程序结构
6.1 for循环
-
Go中只有一种循环
for
循环 -
for循环中没有小括号,必须要有大括号
-
func main() { // 不能有小括号 for i := 0; i < 10; i++ { fmt.Println(i) } }
-
-
for循环的不同格式
-
func main() { // 一般形式 for i := 0; i < 10; i++ { fmt.Println(i) } // 类似于while var i int for i != 10 { fmt.Println(i) i++ } // 无限循环 for { fmt.Println("无限循环") } }
-
6.2 if语句
-
if语句也没有小括号,但是必须有大括号
-
if语句格式
-
func main() { var a int // 不用小括号 if a == 0 { fmt.Println("a = 0") } else if a == 1 { fmt.Println("a = 1") } else { fmt.Println("a != 0 && a != 1") } // 条件前面可以有个简单的语句 if f := 0; a > 0 { fmt.Println(f) } }
-
6.3 switch语句
-
Go中
switch
语句无需break
字段,它只会运行选中的case
语句运行,如果想继续运行需要fallthrough
关键字-
func main() { a := 1 switch a { case 0: fmt.Println(a) case 1: fmt.Println(a) // 打印两遍1 fallthrough case 2: // 这个语句也会运行 fmt.Println(a) default: fmt.Println(a) } }
-
-
switch
中也可以加一个简单语句,类似if语句-
func main() { a := 1 switch a = 2; a { case 0: fmt.Println(a) case 1: fmt.Println(a) fallthrough case 2: fmt.Println(a) default: fmt.Println(a) } }
-
-
没有条件的
switch
语句,case中的条件哪个为true
,执行哪个case的语句-
func main() { a := 1 switch { case a == 0: fmt.Println(a) case a > 0: fmt.Println(a) default: fmt.Println(a) } }
-
6.4 defer语句
-
defer 语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但在外层函数返回后才执行
-
如果有多个defer的函数,那么函数会被压进一个栈中,服从后进先出的原则
-
package main import "fmt" func main() { fmt.Println("counting") for i := 0; i < 10; i++ { defer fmt.Println(i) } fmt.Println("done") } // 结果为: //counting //done //9 //8 //7 //6 //5 //4 //3 //2 //1 //0
-
7、其他结构
7.1 Go指针
-
Go拥有指针,指针保存了值的内存地址
-
*T
,表示指向T
类型值的地址,零值为nil
-
var p *int
-
&
操作符会生成一个指向其操作数的指针 -
i := 32 p = &i
-
*
操作符可以表示指针指向的值 -
fmt.Println(*p) // 通过指针 p 读取 i *p = 21 // 通过指针 p 设置 i
-
也就是间接引用,和重定向
-
注意:Go中没有指针运算,不能+,-
7.2 结构体struct
-
一个结构体就是一组字段
-
type node struct { value int next *node }
-
-
结构体字段通过
.
来访问-
func main() { node1 := node{1, nil} fmt.Println(node1.value) }
-
-
对于结构体指针,想要访问其中的字段,可以通过
(*p).value
来访问,但是也可以直接用p.value
来访问,更简便-
func main() { node1 := node{1, nil} p := &node1 // 两者等效 fmt.Println(p.value) fmt.Println((*p).value) fmt.Println(node1.value) }
-
-
结构体的创建
-
通过直接列出字段的值来赋值
-
func main() { var ( node1 = node{1, nil} // 指定特定字段赋值,其他字段为零值 node2 = node{value: 1} node3 = node{next: nil} node4 = node{} node5 = &node{2, nil} ) fmt.Println(node1, node2, node3, node4, node5) } // 打印结果 // {1 <nil>} {1 <nil>} {0 <nil>} {0 <nil>} &{2 <nil>}
-
7.3 数组
-
类型
[n]T
表示有n个T类型值的数组,长度是数组类型的一部分,n不一样类型就不一样 -
定义格式为
-
// 一般定义 var arr [8]int arr = [8]int{1,2,3,4,5,6,7,8} // 简单定义 arr1 := [8]int{1,2,3,4,5,6,7,8} // 不知道后面个数,注意这里是定义了一个数组,不是切片 arr2 := [...]int{1,2,3,4,5,6,7,8} // 这是切片,不是数组 arr3 := []int{1,2,3,4,5,6,7,8}
-
-
数组的长度不可更改,是固定的
7.4 切片
7.4.1 切片概述
-
数组的长度固定,但是切片的长度可变
-
类型
[]T
,表示一个类型T的切片 -
// 这是切片,不是数组 arr3 := []int{1,2,3,4,5,6,7,8}
-
切片可以通过下标来截取,不包括high的位置,数组也可以截取,但是被截取的部分就变成了切片类型
-
a[low : high]
-
注意
- 切片并没有实际存储,它只是底层数组的一个引用
- 底层仍然是一个数组在存储数据
- 更改切片的内容实际是会修改底层数组的内容
- 如果有其它切片也引用这个数组,那么其他切片内容也会改变
-
切片的构造
- 在创建一个切片时,首先会在底层创建一个数组,然后将它的引用给这个切片
-
切片的默认行为
- 切片的下界默认为0,上界为切片的长度
7.4.2 切片的长度与容量
-
长度与容量是两个概念,可以通过len(s)和cap(s)来获取
-
切片的长度为它实际包含的元素个数
-
切片的容量为从切片引用的第一个元素(不是底层数组的第一个元素)到底层数组最后一个元素的个数
-
package main import "fmt" func main() { s := []int{2, 3, 5, 7, 11, 13} printSlice(s) // 截取切片使其长度为 0 s = s[:0] printSlice(s) // 拓展其长度,能够往后扩展,只要不超过底层数组的范围,但是没办法向前扩展 s = s[:4] printSlice(s) // 舍弃前两个值 s = s[2:] printSlice(s) } func printSlice(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) } // 输出结果为 // len=6 cap=6 [2 3 5 7 11 13] // len=0 cap=6 [] // len=4 cap=6 [2 3 5 7] // len=2 cap=4 [5 7]
-
切片的零值是nil,表示没有底层数组,且cap和len都为0的切片
7.4.3 用 make 创建切片
-
切片用内置函数make来创建,返回一个创建好的参数
-
func make([]T, len, cap) []T
-
-
使用方法
-
package main import "fmt" func main() { // 后两个参数分别代表长度和容量,没指定容量的时候,容量和长度相同 s := make([]int, 5, 10) s1 := make([]int, 10) printSlice(s) printSlice(s1) } func printSlice(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) } // 输出结果为 // len=5 cap=10 [0 0 0 0 0] // len=10 cap=10 [0 0 0 0 0 0 0 0 0 0]
-
7.4.4 切皮的切片
-
切片可以包含任何东西,甚至是切片的切片
-
s := make([][]string, 5) board := [][]string{ []string{"_", "_", "_"}, []string{"_", "_", "_"}, []string{"_", "_", "_"}, }
7.4.5 向切片中追加元素
- 通过apped可以向切片中追加元素
func append(s []T, vs ...T) []T
- 向切片中追加切片,需要在后面加
...
来展开 - 当
s
的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
7.4.6 range
for
循环的range
形式可遍历切片或映射。- 当使用
for
循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。 - 可以用_来忽略
7.5 map映射
-
定义格式
-
// 这个只是定义,不能直接使用,需要make使用 var m map[string]int // 这个可以直接使用,make函数同切片 m1 := make(map[string]int) m1["a"] = 1 fmt.Println(m1["a"])
-
7.6 函数
-
函数也可以直接被定义
-
也可以作为参数和返回值
-
package main import "fmt" func main() { f := func(x, y int) int { return x + y } fmt.Println(f(1, 2)) } // 打印结果为3
7.7 方法
-
Go语言没有类,可以给结构体定义方法
-
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }
-
-
方法只是带有接收者参数的函数
-
也可以为非结构体声明方法,但是只能为同一个包下的类型声明方法,主要就是type来定义的类型
-
如果接收者是个指针类型,那么方法里面对结构体的修改会影响到调用的结构体,否则方法里面处理的是结构体的副本
-
带指针参数的函数,必须传递指针类型的参数
-
带指针类型接收者的方法,调用时可以直接
v.method
,但这并不意味着两者相同,只是编译器会自动转换
7.8 接口
- 接口类型 是由一组方法签名定义的集合。接口类型的变量可以保存任何实现了这些方法的值。
- 接口的实现无需显示说明,这样某个类型的方法实现了接口中的所有方法,就认为该类型实现了该接口
7.8.1 接口值
- 接口也是个值,保存了具体底层类型的具体值,比如说某个类型实现了该接口,将该类型赋予接口时,接口保存的值,就是该类型的具体值
- 接口可以用作函数参数和返回值
- 接口值调用方法时会执行其底层类型的同名方法。
- 对于底层值为nil的接口值,在调用方法的时候,也会被调用
- 保存了具体值nil的接口自身并不为nil
- nil 接口值既不保存值也不保存具体类型。
7.8.2 空接口
-
空接口可以保存任何值
-
interface{}
-
7.9 类型断言与类型选择
7.9.1 类型断言
-
类型断言 提供了访问接口值底层具体值的方式。
-
t := i.(T)
-
该语句断言接口值
i
保存了具体类型T
,并将其底层类型为T
的值赋予变量t
。 -
若
i
并未保存T
类型的值,该语句就会触发一个恐慌。 -
为了 判断 一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。
-
t, ok := i.(T)
-
若
i
保存了一个T
,那么t
将会是其底层值,而ok
为true
。 -
否则,
ok
将为false
而t
将为T
类型的零值,程序并不会产生恐慌。
7.9.2 类型选择
-
类型选择 是一种按顺序从几个类型断言中选择分支的结构。
-
switch v := i.(type) { case T: // v 的类型为 T case S: // v 的类型为 S default: // 没有匹配,v 与 i 的类型相同 }
-
类型选择中的声明与类型断言
i.(T)
的语法相同,只是具体类型T
被替换成了关键字type
。 -
一般用于判断接口值i的底层数据类型