Golang
Contents
1 安装
2 Golang语言特性
2.1 优势
-
极简单的部署方式
-
可直接变成成机器码
-
不依赖其他库
-
直接运行即可部署
-
-
静态类型语言
- 编译过程就可以检查出来隐藏的大多数问题
-
语言层面的并发
- 天生的基因支持(不是美容的)
- 很多语言是通过一层层的包装来达到这种优化的、优良的并发特性
- 充分的利用多核
- 不需要绑定CPU以及优化Go语言调度的时间片
- 天生的基因支持(不是美容的)
-
强大的标准库
-
runtime系统调度机制
- 能够帮我们做一些垃圾回收
-
高效的GC垃圾回收
- 从Go语言1.8版本后,GC增加了三色标记和混合写屏障
-
丰富的标准库
- 加解密、数学、进程、线程、网络通信、文件系统
-
-
简单易学
-
25个关键字
-
C语言简洁基因,内嵌C语法支持
-
面向对象特征(继承、多态、封装)
-
跨平台的语言
-
-
大厂领军
- 谷歌、fb、腾讯、百度、京东、新浪、七牛
2.2 Golang适合做什么(强项)
- 云计算基础设施领域
- docker、七牛云存储、kubernetes
- 基础后端软件
- tidb、influxdb
- 微服务
- go-kit、micro
- 互联网基础设施
- hyperledger
2.3 成就
docker、kubernetes
2.4 缺点
- 大部分第三方库都在github上
- 无泛化类型
- 所有的Exception都用Error来处理
- 对C的降级处理,并非无缝,没有C降级到asm那么完美(序列化问题)
3 基础语法
3.1 引入
|
|
3.2 变量的声明
- 方法一:
var 变量名 变量类型
|
|
- 方法二:
var 变量名 类型 = 初始值
|
|
- 方法三:
var 变量名 = 初始值
|
|
- 方法四:
变量名 := 初始值
|
|
- 单行多变量声明:
|
|
- 多行多变量声明:
|
|
3.3 常量与iota
-
常量用
const
关键字声明-
const a int = 100
-
多常量声明:
1 2 3 4
const ( a = 100 b = 200 )
-
-
const关键字也可以声明枚举(配合iota使用)
-
iota:可以在const()添加iota关键字,每行的iota值都会累加1,第一行iota默认值为0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// 声明枚举 const ( BEIJING = 0 SHANGHAI = 1 SHENZHEN = 2 ) // 配合iota关键字声明枚举 const ( BEIJING = iota // 0 SHANGHAI // 1 SHENZHEN // 2 ) // 面试题 const ( a, b = iota*2, iota*3 // iota = 0 => a = 0, b = 0 c, d // iota = 1 => c = 2, d = 3 e, f = iota*3, iota*4 // iota = 2 => e = 6, f = 9 )
-
3.4 函数
|
|
3.4.1 支持多返回值
-
返回多个值(匿名)
1 2 3
func foo1(a string, b int)(int, int){ return 100, 200 }
-
返回多个值(有形参名称)
1 2 3 4 5 6 7
// func fool(a string, b int)(r1 int, r2 int){ func fool(a string, b int)(r1,r2 int){ // ri r2 属于foo3的局部变量,默认值为0 r1 = 100 r2 = 200 return }
3.4.2 init函数与import导包
-
init函数:包的主入口
-
init()执行优先级大于main()
-
程序运行流程图:
-
示例如下:
- 文件结构
1 2 3 4 5 6
├── 5-init │ ├── lib1 │ │ └── lib1.go │ ├── lib2 │ │ └── lib2.go │ └── main.go
- lib1.go
1 2 3 4 5 6 7 8 9 10 11 12 13
package lib1 import "fmt" //当前lib1包提供的API // 首字母大写表示暴露出来的API(public) func Lib1Test() { fmt.Println("lib1Test()...") } func init() { fmt.Println("lib1. init() ...") }
- main.go
1 2 3 4 5 6 7 8 9 10 11
package main import ( "GolangStudy/5-init/lib1" "GolangStudy/5-init/lib2" ) func main() { lib1.lib1Test() lib2.Lib2Test() }
-
-
import导包
-
import _ "fmt"
:匿名导入fmt包,无法使用该包的方法,但是会执行当前的包内部的init()方法;解决了如果导入的包不使用会报错的问题 -
import aa "fmt"
:给fmt包起一个别名aa,aa.Println()来直接调用 -
import . "fmt"
:将当前fmt包的全部方法导入,可以直接使用,不需要fmt API来调用 -
示例:
1 2 3 4 5
import => mylib2 "GolangStudy/5-init/lib2" use => mylib2.Lib2Test() import => . "GolangStudy/5-init/lib2" use => Lib2Test()
-
3.5 指针
-
背景:改变变量值的函数
1 2 3 4 5 6 7 8 9 10
package main import "fmt" func changeValue(p int){ p = 10 } func main(){ var a int = 1 changeValue(a) fmt.Println(a) // 是1还是10呢? }
- 结果为1,因为changeValue方法只改变了p在内存中的值,内存分析如下
-
需要改变a在内存中的值:
1 2 3 4 5 6 7 8 9 10
package main import "fmt" func changeValue(p *int){ // *int表示整型地址类型,p的初始值为0x00000000 *p = 10 // *p表示通过p中存放的地址,索引到对应内存空间(a),然后改变其值为10 } func main(){ var a int = 1 changeValue(&a) // &a表示a在内存中的地址 fmt.Println(a) // 10 }
- 在内存图中体现为:
-
交换两个值的练习:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
package main import "fmt" func swap(a *bool, b *bool) { fmt.Println(a,b) // 0x14000014092 0x14000014093 temp := *a // temp = true *a = *b // *a = false *b = temp // *b = true } func main(){ var a bool = true var b bool = false swap(&a, &b) }
3.6 defer关键字
-
表示一个函数在执行结束之前的行为,类似finally关键字
1 2 3 4 5 6 7 8 9
package main import "fmt" func main(){ defer fmt.Println("defer") fmt.Println("main") } // 执行结果 // main // defer
-
defer的执行顺序
-
当一个函数有多个defer语句时,会以栈的形式执行,即先入后出
1 2 3 4 5 6
func main(){ defer fmt.Println("A") defer fmt.Println("B") defer fmt.Println("C") } // 执行结果:C B A
-
return的优先级要高于defer
-
3.7 数组
3.7.1 静态数组与动态数组
-
静态数组的声明与遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
package main import "fmt" func main(){ // var 数组名 [长度]类型{ 初始值 } var array1 [10]int // 数组中默认值为0 array3 := [3]int{1, 2} // [1, 2, 0] printArray(array3) } func printArray(arr [3]int){ // 普通for循环形式遍历 for i := 0;i < len(arr);i++ { fmt.Println(arr[i]) } fmt.Println("-------") // range关键字遍历 for index, value := range arr { fmt.Println(index, value) } arr[0] = 100 // 静态数组作为参数传入时,仅相当于值拷贝,改变数组的值不会对原数组产生影响 }
-
动态数组的声明与遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
package main import "fmt" func main(){ // 声明时中括号内为空,则表示此数组为slice切片数组 array1 := []int{1,2,3} printArray(array1) } func printArray(arr []int){ // 动态数组的传参相当于引用拷贝,改变数组的值相当于改变原数组 arr[0] =100 // range关键字遍历 for index, value := range arr { fmt.Println(index, value) } } // 输出 // 0 100 // 1 2 // 2 3
3.7.2 slice数组的声明方式
- 声明一个切片并初始化,len为3
slice1 := []int{1, 2, 3}
- 声明一个切片,并用make关键字为其开辟3个空间,默认值为0
var slice []int
slice1 = make([]int, 3)
- 声明一个切片,同时用make关键字为其开辟3个空间,初始化值为0
var slice1 []int = make([]int, 3)
- 声明slice1,同时用make关键字为其开辟3个空间,初始化值为0,通过:=推导出slice是一个切片
slice1 := make([]int, 3)
3.7.3 slice的使用
-
切片容量的追加
- 声明一个切片,利用
fmt.printf()
方法查看其长度容量等信息
1 2 3 4 5 6 7 8
func main() { // 开辟一个整型切片,长度为3,容量为5 numbers := make([]int,3,5) // %d表示整型数字,%v表示默认打印,&T表示打印类型 fmt.Printf("len = %d, cap = %d, slice = %v, type = %T",len(numbers),cap(numbers),numbers,numbers) } // 输出 // len = 3, cap = 5, slice = [0 0 0], type = []int
``
- 声明一个切片,利用
-
用
append
方法在数组末尾插入一个元素
numbers = append(numbers, 1) // 此时len = 4, cap = 5, slice = [0 0 0 1], type = []int ```
-
继续追加元素,当超过cap时,会动态开辟另外一个cap大小的内存
numbers = append(numbers, 1) // 此时len = 6, cap = 10, slice = [0 0 0 1 1 1], type = []int ```
- len和cap的关系:⻓度表示左指针⾄右指针之间的距离,容量表示左指针⾄底层数组末尾的距离
- 切片的截取
-
按范围截取
1 2 3 4 5
func (){ s := []string{'a','b','c'}// len=3,cap=3,s=['a','b','c'] // 左闭右开结构[0, 2) s1 := s[0, 2] // ['a','b'] }
注:按范围截取的切片s1,其指针与原切片一致。也就是说,修改s1就相当于修改原切片s。
1 2
s1[0] = 'g' // s = ['g', 'b', 'c']
-
copy关键字可以将底层数组的slice一起进行拷贝(深拷贝)
1 2 3 4
s2 := make([]string, 3) // s2 = [ ] // 将s中的值拷贝到s2 copy(s2, s) fmt.println(s2) //
Author gsemir
LastMod 2021-11-29