第六届字节跳动青训营第九课 | 青训营

75 阅读14分钟

目录

一、引入

1.1 为什么go语言?

1.2 Go语言的特点:

1.3 Golang的开发工具的介绍:

1.4 windows下配置环境变量:

1.5 linux搭建Go开发环境:

1.7 Golang执行流程的分析:

1.7.1 go build ***.go + ***.exe 与 go run ***.go的区别

1.7.2 什么是编译?

1.8 Go程序开发的注意事项:

二、Golang的基本语法

2.1 常量:

2.2 Golang的变量及使用:

2.2.1 使用变量的过程:

2.2.2 多个变量的一次性定义

2.2.3 全局变量的一次性定义

2.2.4 注意:

2.2.5 变量的作用域

2.3 数据类型:

2.3.1 基本数据类型:

2.3.2 派生/复杂数据类型:

2.3.3 基本数据类型的默认值(又称为零值)

2.4 Go语言数据类型转换:

2.4.1 基本数据类型间的相互转换

2.4.2 基本数据类型 与 string类型的转换:

2.4.3 字符串 转换成 基本数据类型:

2.5 一维、二维数组:

2.5.1 一维数组:

2.5.2 二维数组

2.6 切片slice

2.7 string字符串

2.8 map

2.8.1 map关系数组

2.8.2 map切片:

2.8.3 map排序:

2.8.4 map的使用细节:

2.9 指针

2.10 值类型和引用类型

2.11 标识符:

2.11.1 标识符的概念:

2.11.2 标识符命名的规范:

2.11.3 系统保留关键字

2.11.4 预定义标识符:

2.12 运算符:

2.12.1 算数运算符:

2.12.2 关系运算符:

2.12.3 逻辑运算符:

2.12.4 赋值运算符:

2.12.5 位运算符:

2.12.6 取址符:

2.12.7 运算符的优先级:

2.13 流程控制

2.13.1 顺序控制

2.13.2 分支控制

2.13.3 循环控制

2.14 函数:

2.14.1 写法

2.14.2 打包/引包的方法:

2.14.3 函数调用的底层机制:

2.14.4 递归调用:

2.14.5 函数的注意事项:

2.14.6 init函数的基本介绍:

2.14.7 匿名函数:

2.14.8 闭包:

2.14.9 defer的基本使用:

2.14.10 字符串常用的系统函数:

2.14.11 时间和日期相关的函数:

2.14.12 内置函数builtin:

2.15 Go语言错误处理机制:

三、面向对象编程

3.1 引入

3.2 结构体:

3.2.1 使用细节

3.2.2 创建结构体变量 和 访问结构体字段:

3.2.3 结构体的内存分配机制:

3.3 方法的声明、调用和传参机制:

3.3.1 方法和函数的区别:

3.3.2 方法的声明和定义:

3.3.3 方法的访问范围和传参机制:

3.3.4 自定义数据类型都可以有方法:

3.3.5 结构体中的String()方法

3.3.6 工厂模式:

3.4 接口

3.4.1 go语言核心interface:

3.4.2 接口的应用场景:

3.4.3 接口的注意细节:

3.4.4 interface的最佳实践:

3.4.5 接口与继承的关系:

3.4.6 接口、继承解决的问题不同:

3.4.7 类型断言:

3.4.8 类型断言的最佳实践:

3.5 封装、继承和多态

3.5.1 封装

3.5.2 继承

3.5.3 多态

四、文件

4.1 文件的基础认识

4.2 读取文件内容并显示在终端

4.2.1 带缓冲区的方式:

4.2.2 一次性读取的方式:

4.3 给文件写内容

4.3.1 打开文件

4.3.2 使用带缓存的方式写文件:

4.4 读写文件

4.5 扩展应用

4.5.1 将文件的内容,拷贝到另一个文件中

4.5.2 判断一个文件/文件夹是否存在的方法

4.5.3 统计文件中,英文、数字、空格 和 其他字符的数量:

4.6 获取命令行参数

4.6.1 os.Args获取命令行参数

​编辑

4.6.2 flag包解析命令行参数

4.7 json数据格式

4.7.1 基本介绍:

4.7.2 json数据格式说明:

4.7.3 json序列化:

4.7.4 json反序列化

五、单元测试

六、goroutine协程和channel管道

6.1 goroutine协程

6.1.1 进程和线程:

6.1.2 并行和并发:

6.1.3 Go协程和Go主线程

6.1.4 goroutine的调度模型:MPG模式

6.1.5 goroutine协程存在的问题

6.2 channel管道

6.2.1 介绍

6.2.2 基本使用:

6.3 channel的遍历和关闭:

6.3.1 channel的关闭:

6.3.2 channel的遍历:

6.4 goroutine和channel的结合案例:

6.4.1 两个协程同时操作一个管道

6.4.2 多个协程同时操作多个管道

6.5 管道阻塞机制:

6.6 channel使用注意事项:

6.6.1 声明channel为只读/只写属性:

6.6.2 select解决管道取数据的阻塞问题:

6.6.3 recover解决协程中出现panic,而导致整个程序崩溃的问题:

七、反射

7.1 引入:

7.2 基本介绍:

7.3 反射重要的函数和概念:

7.4 反射的实践:

一、引入 1.1 为什么go语言? 1、计算机硬件技术更新频繁,目前的主流编程语言不能合理利用多核多CPU的优势提升软件系统的性能。

2、目前的编程语言:不够简洁、风格不统一、计算能力不强、处理大并发不够好。

3、c/c++运行速度快,但编译的速度却很慢,且存在内存泄漏的风险。

1.2 Go语言的特点: 1、Go = C + Python;

具有c语言,静态编译的安全和性能; 又达到了python语言,动态语言开发维护的高效率; 2、从c语言继承了很多概念:表达式语法、控制结构、基础数据类型、调用参数传值,指针等,也保留了c语言一样的编译执行方式及弱化的指针;

3、引入包的概念:用于组织程序结构,Go语言的一个文件要归属一个包,不能单独存在;

    包的初始化过程(如下图):Go引导程序,会先对整个程序的包进行初始化;

    Go语言包的初始化有如下特点:

包初始化程序从 main 函数引用的包开始,逐级查找包的引用,直到找到没有引用其他包的包,最终生成一个包引用的有向无环图;

Go 编译器会将有向无环图转换为一棵树,然后从树的叶子节点开始逐层向上对包进行初始化;

单个包的初始化过程如上图所示,先初始化常量,然后全局变量,最后执行包的 init 函数。

    包的三大作用:

区别相同名字的函数、变量等标识符; 当程序文件很多时,可以很好的管理项目; 控制函数、变量等访问范围,即作用域; 包的相关说明:

打包:package 包名;引入包:import 包路径; import包时,路径是从$GOPATH的src下开始的,不用再带src,编译器自动从src开始引入 ; 文件包名 和 文件所在的文件夹的名字,一般是一致的,均为小写; 为了让其他包的文件可以访问到本包的函数,则该函数名的首字母需要大写,类似于其他语言的public,这样才能挎包访问;挎包访问的格式:包名.函数名(); 同一个包下,不能有相同函数名的函数;也不能有相同的全局变量名; 4、垃圾回收机制:内存自动回收,不需要开发人员管理;

5、天然并发:

从语言底层支持并发,实现简单; goroutine轻量级线程,可实现大并发处理,高效利用多核; 基于CPS(communicating sequential processes)实现; 6、管道通信机制:吸收了管道通信机制,形成了Go语言特有的管道channel;通过管道,可以实现不同的goroutine之间的相互通信;

7、支持函数有多个返回值;

8、切片slice、延时执行defer等;

1.3 Golang的开发工具的介绍: VSCode、Eclipse、vim、sublime text等。 搭建SDK(software development kit)软件开发工具包,用./bin/go.exe来编译和运行go代码。 VScode安装Golang插件时,遇到的问题及解决方案:

1.4 windows下配置环境变量: windows系统在查找可执行文件时,在当前目录下如果不存在,则windows系统会在系统中已有的一个path的环境变量指定的目录中查找,故需要将go所在的路径C:/Golang/bin定义在path环境变量中,以便在任何目录下和都可以执行go指令。

在系统变量path中,增加C:\Golang\bin; 新建GOPATH环境变量,用来存放go的项目的工作路径F:\Go_Project; 1.5 linux搭建Go开发环境: 查看linux系统的位数32/64,在终端输入: uname -a 查看; 将go1.9.2linux-amd64.tar.gz放入SharedDocuments_ubuntu文件夹下,在linux终端,通过cd ./mnt/hgfs/SharedDocuments_ubuntu/下,可查看到该压缩文件,并输入tar -zxvf go1.9.2linux-amd64.tar.gz -c /home/guangyuansun1/; linux下配置环境变量,在/etc/profile文件夹下添加三条语句: export GOROOT = /home/guangyuansun1/go export PATH = PATH:PATH:GOROOT/bin export GOPATH = /home/guangyuansun1/Go_Projects/ 1.7 Golang执行流程的分析: 1.7.1 go build ***.go + ***.exe 与 go run ***.go的区别 1)如果先编译go build .go并生成可执行文件,则能够将可执行文件移植到没有go开发环境的机器上运行.exe。如果直接调用 go run ***.go,必须使机器上配置了go开发环境;

2)在编译时,编译器会将程序运行依赖的库文件包含在可执行文件中,所以,编译后生成的可执行文件比.go文件大很多;

1.7.2 什么是编译? 有了go源文件之后,通过编译器将其编译成机器能够识别的二进制码文件; 通过go build ***.go,可以完成编译的过程,windows会生成.exe文件,linux则会生成.o文件,但都是可执行的二进制文件; 可以通过go build -o m_hello.exe hello.go来指定可执行文件的名字; package main // 表示 hello.go 文件所在的包是 main; import "fmt" // 引入fmt包(format),以便调用fmt包中的函数 func main() { // func(function)是关键字 fmt.Println("hello world!") }

// 在cmd终端执行,并跳转到.go文件所在的文件夹位置 go build hello.go // 编译后,会生成hello.exe可执行文件文件 hello.exe // 运行hello.exe可执行文件

​// 或者直接用 go run hello.go,会将编译和运行合并执行 1.8 Go程序开发的注意事项: Go源文件是.go文件结尾; Go语言会在每行后自动加分号; Go编译器是一行行进行编译的,故在写程序要保证一行就一条语句; Go语言定义的变量未使用 或者 import的包没有使用,代码不能编译通过; Go语言的注释comment :1)提高代码的可阅读性;2)多行注释的快捷键:ctrl + /; Go语言常用的转义字符escape char:

\t:制表符,对齐作用 \n:换行符 \:一个
":一个" \r:是一个回车,用来将/r之后的内容覆盖掉开头的内容

package main // 表示 hello.go 文件所在的包是 main; import "fmt" // 引入fmt包(format),以便调用fmt包中的函数 func main() { // func(function)是关键字 fmt.Println("hello world!") fmt.Println("F:\\go.exe") // 两个//,表示一个// fmt.Println("hello"world"") // 这里的/"/"是为了输出的内容 fmt.Println("***** world\rhello") // 这里会将/r之后的内容,用来覆盖开头的内容并输出 } Golang的API学习网站:Go官方网站、 Go语言中文网 - Golang中文社区

Golang中调用一个函数的方式:import 包 包名.函数名();

Dos的基本操作原理:cmd(Dos操作系统的终端) --> 输入的命令会用Dos来对指令进行解析,并响应(绝对路径:从当前盘开始定位;相对路径:从当前位置开始定位)。

Dos常用的操作指令 作用 dir 显示一个目录中的文件和子目录 cd 显示当前目录的名称或将其更改 cls 清除屏幕 cmd 打开另一个 Windows 命令解释程序窗口 help 提供 Windows 命令的帮助信息 tree 以图形方式显示驱动程序或路径的目录结构 mkdir 创建一个空目录 rd /s ***

rd /q/s ***

删除***文件,并且带询问(推荐使用)

删除***文件,并且不带询问

exit 退出 cmd.exe 程序(命令解释程序) echo *** 在命令行窗口显示***的内容,或将命令回显打开或关闭 echo .> .txt 创建.txt文件 del .txt 删除.txt文件 copy .txt destination_path 将.txt文件拷贝到destination_path目标路径 move ***.txt destination_path 将一个或多个文件从一个目录移动到另一个目录(剪切) 二、Golang的基本语法 2.1 常量: 常量要从const修饰,且在定义是必须初始化;

常量表达式的值在编译期计算,而不是在运行期;

常量间的所有算术运算、逻辑运算和比较运算的结果也是常量;对常量的类型转换操作或以下函数调用都是返回常量结果:len、cap、real、imag、complex和unsafe.Sizeof;

常量只能修饰bool类型、数值类型(int、float系列)、string类型;

语法:const identifier [type] = value;

iota常量生成器:用于生成一组以相似规则初始化的常量。在一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一;

const ( a = itoa // a==0 b // b==1 c // c==2 )

const ( a = 1 b = 2 )

const ( a = itoa b, c = itoa, itoa // a==0,b==1,c==1 ) const ( a = itoa b = itoa c = itoa // a==0,b==1,c==2 )

常见的常量类型:

常量名大小写可以控制访问范围:大写则可以挎包访问,小写则不可以;

2.2 Golang的变量及使用: 变量,是程序的基本组成单位,是内存中一个数据存储空间的表示,故通过变量名可以访问到变量。这个内存空间,有自己的类型(变量的数据类型)和名称(变量名)。

2.2.1 使用变量的过程: 1)声明变量;2)赋值;3)使用;(其中,1)和 2)可以结合使用)

package main

import "fmt" import "reflect"

func main() { var i int // 声明整型变量,整型的默认值是0 i = 10 // 给整型变量赋值 //var i int = 10 // 声明并给整型变量赋值

// 变量类型推导:
i = 10.0   
fmt.Printf("type(i): %T\n", i)   
j := 'a'     // 等价于 var j char   j = 'a'
fmt.Println("j's type: ", reflect.TypeOf(j)) 
k := "str"   // 等价于 var k string = "str"
fmt.Println("k's type: ", reflect.TypeOf(k))

fmt.Println("i =", i)
fmt.Printf("j = %c\n", j)
fmt.Println("k =", k)

}

// 产生1~100的随机数: package main import "fmt" import "math/rand" import "time"

func main() { // 设置随机数种子,time.Now().Unix():返回的1970:0:0到现在的秒数 rand.Seed(time.Now().Unix()) //生成1~100的随机数 n := rand.Intn(100) + 1 fmt.Println(n) } 2.2.2 多个变量的一次性定义 // golang一次定义多个变量的三种方式: var i1, j1, k1 int i1, j1, k1 = 10, 10, 10 var i2, j2, k2 = 20, 20.0, "str2" i3, j3, k3 := 20, 30.0, "str3" fmt.Println("i1 =", i1, "j1 =", j1, "k1 =", k1) fmt.Println("i2 =", i2, "j2 =", j2, "k2 =", k2) fmt.Println("i3 =", i3, "j3 =", j3, "k3 =", k3) 2.2.3 全局变量的一次性定义 package main

import "fmt"

// 全局变量的一次性定义 var ( i4 = 40 j4 = 40.0 k4 = "str4" )

func main() { fmt.Println("i4 =", i4, "j4 =", j4, "k4 =", k4) } 2.2.4 注意: 数据值可以在同一类型范围内不断变化; 变量在同一作用域内不能重名; 变量 = 变量名 + 数据类型 + 变量值; golang语言中声明的变量没有赋值,则会有默认值,int:0、float:0、string:""; 程序中的+号:两边是数值,则是加法运算;两边是字符串,则是字符串的拼接; 2.2.5 变量的作用域 局部变量:函数内部声明和定义、使用; 全局变量:函数外声明和定义、使用,该变量在整个包中有效(如果首字母大写,则在整个程序中的其他包中也是有效); 当全局变量和局部变量同名时,会采用“就近原则”; // 全局变量的定义时的注意事项: var n1 int = 10 // 正确 n2 := 10 // 报错,因为该语句等价于 var n2 int n2 = 10,故报错发生了(第二条语句,不能在函数外进行赋值操作) 2.3 数据类型: 2.3.1 基本数据类型: 数值型(整型(int、int8(代表8位的整型)、int16、int 32、int64、uint、uint8、uint16、uint32、uint64、byte)、浮点型(float32、float64))、字符型(没有专门的字符型,使用byte来保存单个字符)、布尔型、字符串(go将string归为基本数据类型)

2.3.2 派生/复杂数据类型: 指针Pointer、数组、结构体struct、管道Channel、函数(也是一种数据类型)、切片slice、接口interface、map

2.3.3 基本数据类型的默认值(又称为零值) 数据类型 默认值 整型 0 浮点型 0 字符串" "" 布尔类型 false 注:

格式化输出fmt.printf()中,%v表示按照变量的值输出,%T表示输出变量的类型; golang中查看变量占用的字节数,可以调用unsafe包中的Sizeof()函数; 写程序时整型变量采用 “保小不保大” 的原则; Golang中统一采用utf-8编码; 有符号整型

类型 有无符号 占用存储空间 整数的范围 int8 有 1字节 -128 ~ 127 int16 有 2字节 -pow(2, 15) ~ pow(2, 15) - 1 int32 有 4字节 -pow(2, 31) ~ pow(2, 31) - 1 int64 有 8字节 -pow(2, 63) ~ pow(2, 63) - 1 无符号整型

类型 有无符号 占用存储空间 整数范围 uint8 无 1字节 0 ~ 255 uint16 无 2字节 0 ~ pow(2, 16) - 1 uint32 无 4字节 0 ~ pow(2, 32) - 1 uint64 无 8字节 0 ~ pow(2, 64) - 1 其他整型

类型 有无符号 占用存储空间 整数的范围 备注 int 有 32/64位系统占用4/8 bytes -pow(2, 31) ~ pow(2, 31) - 1 / -pow(2, 63) ~ pow(2, 63) - 1 整型默认是int型 uint 无 32/64位系统占用4/8 bytes 0 ~ pow(2, 32) - 1 / 0 ~ pow(2, 64) - 1 rune 有 与int32一样 -pow(2, 31) ~ pow(2, 31) - 1 等价于int32,表示一个Unicode码 byte 无 与uint8等价 0 ~ 255 当要存储字符时,选用byte 浮点类型:

浮点数都是有符号位的,浮点数 = 符号位(浮点数都有符号位) + 指数位 + 尾数位。 浮点数会造成数据的精度损失,尽可能地选用float64类型。 浮点数的字段长度和范围是固定的,不受操作系统OS的影响。 类型 占用存储空间 浮点数的范围 单精度float32 4 bytes float64位 比 float32位 精度更高 双精度float64 8 bytes 浮点数默认是float64的 字符类型char:

golang中没有专门的字符类型,如果要存储单个字符(字母),则用字节byte来保存;且golang的字符串也是由单个的byte字节组成的。Go语言中的字符采用的是UTF-8编码(兼容ASCII码)。

utf-8中,英文字符占1 byte,中文字符占用3 byte

var c3 int = '北' fmt.Printf("c3 =%c, c3对应的码值:%d", c3, c3) Go语言中,字符本质上是一个整数,直接输出的是该字符对应的UTF-8编码的码值;故也可以用码值进行运算,因为均采用的是Unicode编码;

字符在计算机中的存储:字符 -> 对应的码值 -> 二进制 -> 存储;(读取的过程则是反过来执行的);通过字符编码表,可以查询字符与码值的对应关系。