Go语言理论一
Posted 知其黑、受其白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言理论一相关的知识,希望对你有一定的参考价值。
阅读目录
- 理论
- 结构体
- 1.1. 类型别名和自定义类型
- 1.2. 结构体 struct
- 1.3. 匿名结构体
- 1.3.1. 创建指针类型结构体
- 1.3.2. 取结构体的地址实例化
- 1.3.3. 结构体初始化
- 1.3.4. 使用键值对初始化
- 1.3.5. 使用值的列表初始化
- 1.3.6. 结构体内存布局
- 1.3.7. 构造函数
- 1.3.8. 方法和接收者
- 1.3.9. 指针类型的接收者
- 1.3.10. 值类型的接收者
- 1.3.11. 什么时候应该使用指针类型接收者
- 1.3.12. 任意类型添加方法
- 1.3.13. 结构体的匿名字段
- 1.3.14.嵌套结构体
- 1.3.15. 嵌套匿名结构体
- 1.3.16. 嵌套结构体的字段名冲突
- 1.3.17. 结构体的“继承”
- 1.3.18. 结构体字段的可见性
- 1.3.19. 结构体与JSON序列化
- 1.3.20. 结构体标签(Tag)
- 1.3.21. 删除map类型的结构体
- 1.3.22. 实现map有序输出(面试经常问到)
- 延迟调用(defer)
- 接口
- 并发编程
- select 多路复用
- 并发安全和锁
- Sync
.
理论
Go语言的主要特征
1.自动立即回收。
2.更丰富的内置类型。
3.函数多返回值。
4.错误处理。
5.匿名函数和闭包。
6.类型和接口。
7.并发编程。
8.反射。
9.语言交互性。
UTF-8 和 Unicode 有何区别?
- Unicode 是「字符集」
- UTF-8 是「编码规则」
字符集:
为每一个「字符」分配一个唯一的 ID(学名为码位 / 码点 / Code Point);
编码规则:
将「码位」转换为字节序列的规则(编码/解码 可以理解为 加密/解密 的过程)
Go语言命名
1.Go的函数、变量、常量、自定义类型、包(package)的命名方式遵循以下规则:
1)首字符可以是任意的Unicode字符或者下划线
2)剩余字符可以是Unicode字符、下划线、数字
3)字符长度不限
Go语言内置函数
- append – 用来追加元素到数组、slice中,返回修改后的数组、slice。
- close – 主要用来关闭 channel。
- delete – 从map中删除 key 对应的 value。
- panic – 停止常规的 goroutine (panic 和 recover:用来做错误处理)
- recover – 允许程序定义 goroutine 的 panic 动作。
- real – 返回 complex 的实部 (complex、real imag:用于创建和操作复数)
- imag – 返回 complex 的虚部。
- make – 用来分配内存,返回 Type 本身(只能应用于 slice, map, channel)
- new – 用来分配内存,主要用来分配值类型,比如 int、struct。返回指向Type的指针。
- cap – capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
- copy – 用于复制和连接 slice,返回复制的数目。
- len – 来求长度,比如string、array、slice、map、channel ,返回长度
- print、println – 底层打印函数,在部署环境中建议使用 fmt 包
内置接口 error
// 只要实现了Error()函数,返回值为String的都实现了err接口
type error interface
Error() String
init 函数
1 init 函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等。
2 每个包可以拥有多个 init 函数。
3 包的每个源文件也可以拥有多个 init 函数。
4 同一个包中多个 init 函数的执行顺序 go 语言没有明确的定义(说明)
5 不同包的 init 函数按照包导入的依赖关系决定该初始化函数的执行顺序。
6 init 函数不能被其他函数调用,而是在main函数执行之前,自动被调用。
init 函数和 main 函数的异同
相同点:
两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用。
不同点:
init 可以应用于任意包中,且可以重复定义多个。
main 函数只能用于main包中,且只能定义一个。
两个函数的执行顺序:对同一个go文件的init()调用顺序是从上到下的。
Go 语言内置的运算符
- 算术运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 赋值运算符
算数运算符
关系运算符
逻辑运算符
位运算符
赋值运算符
基本类型
类型 | 长度(字节) | 默认值 | 说明 |
---|---|---|---|
bool | 1 | false | |
byte | 1 | 0 | uint8 |
rune | 4 | 0 | Unicode Code Point, int32 |
int, uint | 4或8 | 0 | 32 或 64 位 |
int8, uint8 | 1 | 0 | -128 ~ 127, 0 ~ 255,byte是uint8 的别名 |
int16, uint16 | 2 | 0 | -32768 ~ 32767, 0 ~ 65535 |
int32, uint32 | 4 | 0 | -21亿~ 21亿, 0 ~ 42亿,rune是int32 的别名 |
int64, uint64 | 8 | 0 | |
float32 | 4 | 0.0 | |
float64 | 8 | 0.0 | |
complex64 | 8 | ||
complex128 | 16 | ||
uintptr | 4或8 | 以存储指针的 uint32 或 uint64 整数 | |
array | 值类型 | ||
struct | 值类型 | ||
string | "" | UTF-8 字符串 | |
slice | nil | 引用类型 | |
map | nil | 引用类型 | |
channel | nil | 引用类型 | |
interface | nil | 接口 | |
function | nil | 函数 |
byte 和 rune 类型
Go 语言的字符有以下两种:
-
uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。
-
rune类型,代表一个 UTF-8字符。
package main
import (
"fmt"
)
func main()
traversalString()
// 遍历字符串
func traversalString()
// byte
s := "wgchen博客"
for i := 0; i < len(s); i++
fmt.Printf("%v(%c) ", s[i], s[i])
fmt.Println()
//rune
for _, r := range s
fmt.Printf("%v(%c) ", r, r)
fmt.Println()
E:\\go_test>go run main.go
119(w) 103(g) 99(c) 104(h) 101(e) 110(n) 229(å) 141() 154(š) 229(å) 174(®) 162(¢)
119(w) 103(g) 99(c) 104(h) 101(e) 110(n) 21338(博) 23458(客)
E:\\go_test>
数组 Array
1、数组:是同一种数据类型的固定长度的序列。
2、数组定义:var a [len]int
,比如:var a [5]int
,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。
3、长度是数组类型的一部分,因此,var a[5] int
和 var a[10]int
是不同的类型。
4、数组可以通过下标进行访问,下标是从 0
开始,最后一个元素下标是:len-1
for i := 0; i < len(a); i++
for index, v := range a
5、访问越界,如果下标在数组合法范围之外,则触发访问越界,会 panic。
6、数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
7、支持 "=="、"!="
操作符,因为内存总是被初始化过的。
8、指针数组 [n]*T
,数组指针 *[n]T
。
数组初始化
一维数组
全局:
var arr0 [5]int = [5]int1, 2, 3
var arr1 = [5]int1, 2, 3, 4, 5
var arr2 = [...]int1, 2, 3, 4, 5, 6
var str = [5]string3: "hello world", 4: "tom"
局部:
a := [3]int1, 2 // 未初始化元素值为 0。
b := [...]int1, 2, 3, 4 // 通过初始化值确定数组长度。
c := [5]int2: 100, 4: 200 // 使用索引号初始化元素。
d := [...]struct
name string
age uint8
"user1", 10, // 可省略元素类型。
"user2", 20, // 别忘了最后一行的逗号。
package main
import (
"fmt"
)
var arr0 [5]int = [5]int1, 2, 3
var arr1 = [5]int1, 2, 3, 4, 5
var arr2 = [...]int1, 2, 3, 4, 5, 6
var str = [5]string3: "hello world", 4: "tom"
func main()
a := [3]int1, 2 // 未初始化元素值为 0。
b := [...]int1, 2, 3, 4 // 通过初始化值确定数组长度。
c := [5]int2: 100, 4: 200 // 使用引号初始化元素。
d := [...]struct
name string
age uint8
"user1", 10, // 可省略元素类型。
"user2", 20, // 别忘了最后一行的逗号。
fmt.Println(arr0, arr1, arr2, str)
fmt.Println(a, b, c, d)
[1 2 3 0 0] [1 2 3 4 5] [1 2 3 4 5 6] [ hello world tom]
[1 2 0] [1 2 3 4] [0 0 100 0 200] [user1 10 user2 20]
多维数组
全局
var arr0 [5][3]int
var arr1 [2][3]int = [...][3]int1, 2, 3, 7, 8, 9
局部:
a := [2][3]int1, 2, 3, 4, 5, 6
b := [...][2]int1, 1, 2, 2, 3, 3
// 第 2 纬度不能用 "..."。
package main
import (
"fmt"
)
var arr0 [5][3]int
var arr1 [2][3]int = [...][3]int1, 2, 3, 7, 8, 9
func main()
a := [2][3]int1, 2, 3, 4, 5, 6
b := [...][2]int1, 1, 2, 2, 3, 3
// 第 2 纬度不能用 "..."。
fmt.Println(arr0, arr1)
fmt.Println(a, b)
输出结果:
[[0 0 0] [0 0 0] [0 0 0] [0 0 0] [0 0 0]] [[1 2 3] [7 8 9]]
[[1 2 3] [4 5 6]] [[1 1] [2 2] [3 3]]
值拷贝行为会造成性能问题,通常会建议使用 slice,或数组指针。
package main
import (
"fmt"
)
func test(x [2]int)
fmt.Printf("x: %p\\n", &x)
x[1] = 1000
func main()
a := [2]int
fmt.Printf("a: %p\\n", &a)
test(a)
fmt.Println(a)
输出结果:
a: 0xc42007c010
x: 0xc42007c030
[0 0]
内置函数 len 和 cap 都返回数组长度 (元素数量)。
package main
func main()
a := [2]int
println(len(a), cap(a)) // 2 2
切片 Slice
需要说明,slice 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。
1、切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
2、切片的长度可以改变,因此,切片是一个可变的数组。
3、切片遍历方式和数组一样,可以用 len() 求长度。表示可用元素数量,读写操作不能超过该限制。
4、cap 可以求出 slice 最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array)
,其中 array 是 slice 引用的数组。
5、切片的定义:var 变量名 []
类型,比如 var str []string var arr []int
。
6、如果 slice == nil
,那么 len、cap 结果都等于 0。
创建切片的各种方式
package main
import "fmt"
func main()
//1.声明切片
var s1 []int
if s1 == nil
fmt.Println("是空")
else
fmt.Println("不是空")
// 2.:=
s2 := []int
// 3.make()
var s3 []int = make([]int, 0)
fmt.Println(s1, s2, s3)
// [] [] []
// 4.初始化赋值
var s4 []int = make([]int, 0, 0)
fmt.Println(s4)
// []
s5 := []int1, 2, 3
fmt.Println(s5)
// [1 2 3]
// 5.从数组切片
arr := [5]int1, 2, 3, 4, 5
var s6 []int // 前包后不包
s6 = arr[1:4]
fmt.Println(s6)
// [2 3 4]
切片初始化
全局:
var arr = [...]int0, 1, 2, 3, 4, 5, 6, 7, 8, 9
var slice0 []int = arr[start:end]
var slice1 []int = arr[:end]
var slice2 []int = arr[start:]
var slice3 []int = arr[:]
var slice4 = arr[:len(arr)-1]
//去掉切片的最后一个元素
局部:
arr2 := [...]int9, 8, 7, 6, 5, 4, 3, 2, 1, 0
slice5 := arr[start:end]
slice6 := arr[:end]
slice7 := arr[start:]
slice8 := arr[:]
slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素
package main
import (
"fmt"
)
var arr = [...]int0, 1, 2, 3, 4, 5, 6, 7, 8, 9
var slice0 []int = arr[2:8]
// 可以简写为 var slice []int = arr[:end]
var slice1 []int = arr[0:6]
// 可以简写为 var slice[]int = arr[start:]
var slice2 []int = arr[5:10]
// var slice []int = arr[:]
var slice3 []int = arr[0:len(arr)]
// 去掉切片的最后一个元素
var slice4 = arr[:len(arr)-1]
func main()
fmt.Printf("全局变量:arr %v\\n", arr)
fmt.Printf("全局变量:slice0 %v\\n", slice0)
fmt.Printf("全局变量:slice1 %v\\n", slice1)
fmt.Printf("全局变量:slice2 %v\\n", slice2)
fmt.Printf("全局变量:slice3 %v\\n", slice3)
fmt.Printf("全局变量:slice4 %v\\n", slice4)
fmt.Printf("-----------------------------------\\n")
arr2 := [...]int9, 8, 7, 6, 5, 4, 3, 2, 1, 0
slice5 := arr[2:8]
slice6 := arr[0:6] //可以简写为 slice := arr[:end]
slice7 := arr[5:10] //可以简写为 slice := arr[start:]
slice8 := arr[0:len(arr)] //slice := arr[:]
slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素
fmt.Printf("局部变量: arr2 %v\\n", arr2)
fmt.Printf("局部变量: slice5 %v\\n", slice5)
fmt.Printf("局部变量: slice6 %v\\n", slice6)
fmt.Printf("局部变量: slice7 %v\\n", slice7)
fmt.Printf("局部变量: slice8 %v\\n", slice8)
fmt.Printf("局部变量: slice9 %v\\n", slice9)
E:\\go_test>go run main.go
全局变量:arr [0 1 2 3 4 5 6 7 8 9]
全局变量:slice0 [2 3 4 5 6 7]
全局变量:slice1 [0 1 2 3 4 5]
全局变量:slice2 [5 6 7 8 9]
全局变量:slice3 [0 1 2 3 4 5 6 7 8 9]
全局变量:slice4 [0 1 2 3 4 5 6 7 8]
-----------------------------------
局部变量: arr2 [9 8 7 6 5 4 3 2 1 0]
局部变量: slice5 [2 3 4 5 6 7]
局部变量: slice6 [0 1 2 3 4 5]
局部变量: slice7 [5 6 7 8 9]
局部变量: slice8 [0 1 2 3 4 5 6 7 8 9]
局部变量: slice9 [0 1 2 3 4 5 6 7 8]
E:\\go_test>
通过 make 来创建切片
var slice []type = make([]type, len)
slice := make([]type, len)
slice := make([]type, len, cap)
读写操作实际目标是底层数组,只需注意索引号的差别。
package main
import (
"fmt"
)
func main()
data := [...]int0, 1, 2, 3, 4, 5
s := data[2:4]
s[0] += 100
s[1] += 200
fmt.Println(s)
fmt.Println(data)
[102 203]
[0 1 102 203 4 5]
可直接创建 slice 对象,自动分配底层数组。
package main
import "fmt"
func main()
// 通过初始化表达式构造,可使用索引号。
s1 := []int0, 1, 2, 3, 8: 100
fmt.Println(s1, len(s1), cap(s1))
// 使用 make 创建,指定 len 和 cap 值。
s2 := make([]int, 6, 8)
fmt.Println(s2, len(s2), cap(s2))
// 省略 cap,相当于 cap = len。
s3 := makeGo语言面向函数和接口编程