这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记
1. 概述
1.1 方向
-
区块链研发
- 互联网数据库技术
- 去中心化,公开透明
-
go服务端(后端)/游戏软件开发
-
后端(美团后台流量支持):
- 支撑后台流量, 并发很厉害(排序,推荐,搜索等)
- 提供负载均衡,缓存, 容错按条件分流...
- 统计运行指标(qps等...)
-
游戏服务端(通讯, 逻辑, 数据存储...)
-
-
分布式/云计算软件开发
- 京东云后台所有服务都用go实现
1.2 特点
Go 语言保证了既能到达静态编译语言的安全和性能,又达到了动态语言开发维护的高效率,使用一个表达式来形容 Go 语言:Go = C + Python , 说明 Go 语言既有 C 静态语言程序的运行速度,又能达到 Python 动态语言的快速开发。
-
从 C 语言中继承了很多理念,包括表达式语法,控制结构,基础数据类型,调用参数传值,指针等等,也保留了和 C 语言一样的编译执行方式及弱化的指针
-
引入包的概念,用于组织程序结构,Go 语言的一个文件都要归属于一个包,而不能单独存在
-
垃圾回收机制,内存自动回收,不需开发人员管理
-
天然并发 (重要特点)
(1) 从语言层面支持并发,实现简单
(2) goroutine,轻量级线程,可实现大并发处理,高效利用多核。
(3) 基于 CPS 并发模型(Communicating Sequential Processes )实现
-
吸收了管道通信机制,形成 Go 语言特有的管道channel 通过管道 channel , 可以实现不同的goroute之间的相互通信。
-
函数可以返回多个值。举例:
//写一个函数,实现同时返回 和,差 //go 函数支持返回多个值 func getSumAndSub(n1 int, n2 int) (int, int ) { //go 语句后面不要带分号. sum := n1 + n2 sub := n1 - n2 return sum , sub } -
新的创新:比如切片 slice、延时执行 defer
Go语言安装之后,D:\Go目录下一共有9个目录与9个文件,如下图
api — 目录,包含所有API列表,方便IDE使用
bin— 目录,存放编译后的可执行文件, 自带一些go的指令:go/godoc/gofmt...
blog— 目录,
doc— 目录,帮助文档
lib— 目录,
misc— 目录,
pkg— 目录,存放编译后的包文件。pkg中的文件是Go编译生成的
src— 目录,存放项目源文件, 放了go的源代码
注:一般,bin和pkg目录可以不创建,go命令会自动创建(如 go install),只需要创建src目录即可。
Authors— 文件,作者列表,用记事本打开
CONTRIBUTING.md— 文件,
CONTRIBUTORS— 文件,
favicon.ico— 文件,
LICENSE— 文件,license,用记事本打开
PATENTS— 文件,
README.md— 文件,
robots.txt— 文件,使用robots.txt阻止对网址的访问,详情查看support.google.com/webmasters/…
VERSION— 文件,版本信息,用记事本打开
1.3 语法习惯
-
不带分号(Go 编译器是一行行进行编译的)
-
大括号不能换行,否则报错
-
在 go 中,每个文件都必须归属于一个包, 所以第一行必须写
package 包名 package main // 必须打main包 // main函数必须在main目录下,包名则必须和上级目录名一致(main) // 一个项目必须有且只有一个main目录(或main包) // 只有在main包下的main函数才是程序入口 -
导入包示例:
import "fmt" // fmt包含go的打印语句: fmt.Println("hello world") // 输出多行: fmt.Println("hello ", "world") -
定义方法:
func name -
编译(会在该目录下生成一个.exe可执行文件):
go build hello.go执行(exe文件不依赖于go的开发环境,因为已经打包好了, 所以exe文件比起.go文件会变大):
#windows hello.exe #Linux&mac(Linux直接就是一个可执行文件,没有.exe) ./hello直接运行(相当于运行脚本文件, 不生成exe文件):
go run hello.go -
Go 应用程序的执行入口是 main()函数
严格区分大小写
-
go 语言定义的变量或者 import 的包如果没有使用到,代码不能编译通过
-
注释://or/**/
-
变量名、函数名、常量名:采用驼峰法
如果变量名、函数名、常量名首字母大写,则可以被其他的包访问;如果首字母小写则只能在本包中使用(注:可以简单的理解成,首字母大写是公有的,首字母是私有的),在 golan没有public, private等关键字
文件命名:一律采用小写,不用驼峰式
测试文件以xx_test.go结尾
2. 基础语法
2.1 变量
变量表示内存中的一个存储区域, 该区域有自己的名称(变量名)和类型(数据类型
变量有三种声明方式:
package main
import "fmt"
func main() {
var i int = 10 // var 变量名 类型 = 值
var j = 1.2 // var 变量名 = 值, 行判定变量类型,类型推导
name := "szc" // 变量名 := 值,自动推导类型并且省略var
fmt.Println("i = ", i, ", j = " , j , ", name = ", name)
}
-
第一种:指定变量类型,声明后若不赋值,使用默认值
例如int/小数默认值是0,字符串默认空串
-
第二种:根据值自行判定变量类型(类型推导)
-
第三种:省略 var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误
-
使用细节:如果声明时不赋值就使用第一种方法,否则使用二三更方便
声明多个变量同样有三种方法:
var a, b, c float32 // 单精度小数
var a, sex, b = 1, "male", 7
a, sex, b := 2, "male", 4
声明多个全局变量: 在 go 中函数外部定义变量就是全局变量
go中的变量指定数据类型之后不能更改数据类型
go声明变量时一般通过自动推导数据类型的方法声明
注意:
下划线”_”本身在Go中是一个特殊的标识符,称为空标识符。可以代表任何其它的标识符,但是它对应的值会被忽略(比如:忽略某个返回值)。所以仅能被作为占位符使用,不能作为标识符使用
示例:
//写一个函数,实现同时返回 和,差
//go 函数支持返回多个值
func getSumAndSub(n1 int, n2 int) (int, int ) {
//go 语句后面不要带分号.
sum := n1 + n2
sub := n1 - n2
return sum , sub
}
func main() {
// 只取出第一个返回值
sum, _ := getSumAndSub(1,3)
fmt.Println(sum)
}
2.2 常量
常量必须赋初值,而且不可更改
const tax int = 1
const name_ = "szc"
const b = 4 / 2
// const b_ = getVal() // 编译期值不确定
// num := 1
// const b_ = num / 2 // 编译期值不确定
// const tax_0 int // 必须赋初值
// tax = 2 // 不可修改
常量只能修饰布尔、数值、字符串类型
也可以这么声明常量,可以在函数里面声明:
const (
a = iota //0
b = iota //1
c = iota //2
d,e = iota,iota //3 3
)
fmt.Println(a, b, c, d, e) // 0 1 2 3 3 依次加1
上面b和c可以不写= iota,但是a必须写
2.3 数据类型
Golang 中没有专门的字符类型,如果要存储单个字符(字母),一般使用 byte 来保存。
字符串(string类型)就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。也就是说对于传统的字符串是由字符组成的,而 Go 的字符串不同,它是由字节组成的, Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本
字符串的两种表示形式
(1) 双引号, 会识别转义字符
(2) 反引号(``),以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果
在 go 中,数据类型都有一个默认值,当程序员没有赋值时,就会保留默认值,在 go 中,默认值又叫零值
在go中转换数据类型需要float32(变量名)之类的进行转换, 不能直接转换
字符串格式化输出:
fmt.Printf("s=%v\n", s) # 通用格式化输出,输出效果最简
fmt.Printf("s=%+v\n", s) # 通用格式化输出,输出效果多一点,例如map的key
fmt.Printf("s=%#v\n", s) # 通用格式化输出,输出效果最多,例如带上数据类型
2.4 指针
获取变量的地址,用&,比如:
var num int
//获取 num 的地址:
&num
指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值比如:
var ptr *int = &num
如图:
获取指针类型所指向的值,使用:*,比如:
var ptr *int = &num
//使用*ptr 获取 ptr 指向的值:
var i int = *ptr
总结:
-
指针存放的是变量的地址
-
&表示取变量地址
-
*在声明变量时表示声明的是指针
在取值时表示从这个地址中拿出对应的值
每个值类型都有一个相对于的指针类型, 通过指针修改值会直接影响原来的变量, 示例:
var num int = 10
var ptr *int = &num
//使用*ptr 获取 ptr 指向的值:
*ptr = 100
// num被改成了100
2.5 值类型与引用类型
- 值类型:变量直接存储值,内存通常在栈中分配
- 引用类型:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由 GC 来回收
内存的栈与堆示意图:
2.6 运算符
go可以++--, python不行, 但是Golang 的++ 和 -- 只能写在变量的后面,不能写在变量的前面,即:只有 a++ a--没有 ++a --a
对于除号 "/",它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。 例如:x := 19/5 ,结果是3, 这个和Java是一样的
逻辑运算符与Java一致
golang设计原则:一件事情有且只有一种方法完成, 因此没有三元运算符
2.7 键盘输入
- 导入 fmt 包
- 调用 fmt 包的 fmt.Scanln() 或者 fmt.Scanf()
示例:
package main
import (
"fmt"
)
func main() {
//要求:可以从控制台接收用户信息,【姓名,年龄,薪水, 是否通过考试 】。
//方式1 fmt.Scanln
//1先声明需要的变量
var name string
var age byte
var sal float32
var isPass bool
fmt.Println("请输入姓名 ")
//当程序执行到 fmt.Scanln(&name),程序会停止在这里,等待用户输入,并回车
fmt.Scanln(&name)
fmt.Println("请输入年龄 ")
fmt.Scanln(&age)
fmt.Println("请输入薪水 ")
fmt.Scanln(&sal)
fmt.Println("请输入是否通过考试 ")
fmt.Scanln(&isPass)
fmt.Printf("名字是 %v \n 年龄是 %v \n 薪水是 %v \n 是否通过考试 %v \n", name, age, sal, isPass)
//方式2:fmt.Scanf,可以按指定的格式输入
fmt.Println("请输入你的姓名,年龄,薪水, 是否通过考试, 使用空格隔开")
fmt.Scanf("%s %d %f %t", &name, &age, &sal, &isPass)
fmt.Printf("名字是 %v \n年龄是 %v \n 薪水是 %v \n 是否通过考试 %v \n", name, age, sal, isPass)
}
2.8 流程控制语句
go的if后面不需要接小括号(如果你写了,你的编译器会自动去掉小括号), 但是必须接大括号
go的switch不需要加break,会自动跳出,switch支持任何数据类型,甚至可以在case里面写if的那种判断条件,从而不需要写太多ifelse,使得程序结构更加清晰易懂, golang 的 case 后的表达式可以有多个,使用 逗号 间隔, 都没有匹配成功则执行 default 的语句块
switch 穿透-fallthrough ,如果在 case 语句块后增加 fallthrough ,则会继续执行下一个 case,也叫 switch 穿透
switch代码:
package main
import (
"fmt"
)
func main() {
var n1 int32 = 51
var n2 int32 = 20
switch n1 {
case n2, 10, 5 : // case 后面可以有多个表达式
fmt.Println("ok1")
case 90 :
fmt.Println("ok2~")
default:
fmt.Println("哎呀呀...")
}
//switch 后也可以不带表达式,类似 if --else分支来使用。【案例演示】
var age int = 10
switch {
case age == 10 :
fmt.Println("age == 10")
case age == 20 :
fmt.Println("age == 20")
default :
fmt.Println("没有匹配到")
}
//case 中也可以对 范围进行判断
var score int = 90
switch {
case score > 90 :
fmt.Println("成绩优秀..")
case score >=70 && score <= 90 :
fmt.Println("成绩优良...")
case score >= 60 && score < 70 :
fmt.Println("成绩及格...")
default :
fmt.Println("不及格")
}
//switch 的穿透 fallthrought
var num int = 10
switch num {
case 10:
fmt.Println("ok1")
fallthrough //默认只能穿透一层
case 20:
fmt.Println("ok2")
fallthrough
case 30:
fmt.Println("ok3")
default:
fmt.Println("没有匹配到..")
}
}
go只有for循环没有while和do while
for代码:
// 其中下面这里任何一部分都可以省略
for 循环变量初始化; 循环条件; 循环变量迭代 {
循环操作(语句)
}
// 第二种用法:当while用
for 循环判断条件 {
//循环执行语句
}
// 第三种用法
// 等价 for ; ; {}是一个无限循环,通常需要配合 break 语句使用
for {
//循环执行语句
}
// 第四种用法
// Golang 提供 for-range 的方式,可以方便遍历字符串和数组
//字符串遍历方式2-for-range
str = "abc~ok上海"
for index, val := range str {
fmt.Printf("index=%d, val=%c \n", index, val)
}
// 对于 for-range 遍历方式而言,是按照字符方式遍历。因此如果有字符串有中文,也是ok的
// 但是如果直接遍历,如果字符串含有中文,那么传统的遍历字符串方式会错误出现乱码
// 原因是传统的对字符串的遍历是按照字节来遍历,而一个汉字在 utf8 编码是对应 3 个字节。
// 如何解决 需要要将str 转成 []rune 切片:
str = "abc~ok上海"
str2 := []rune(str)
for i:=0;i<len(str2);i++ {
fmt.Printf("%v \n", str2[i])
}
break 语句用于终止某个语句块的执行,用于中断当前 for 循环或跳出 switch 语句
break 语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块
continue 语句用于结束本次循环,继续执行下一次循环。
continue 语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环
goto:
- Go 语言的 goto 语句可以无条件地转移到程序中指定的行。
- goto 语句通常与条件语句配合使用。可用来实现条件转移,跳出循环体等功能。
- 在 Go 程序设计中一般不主张使用 goto 语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难
func main() {
var n int = 30
演示goto的使用
fmt.Println("ok1")
if n > 20 {
goto label1
}
fmt.Println("ok2")
fmt.Println("ok3")
fmt.Println("ok4")
label1:
fmt.Println("ok5")
fmt.Println("ok6")
fmt.Println("ok7")
}
return:
- 如果 return 是在普通的函数,则表示跳出该函数,即不再执行函数中 return 后面代码,也可以理解成终止函数
- 如果 return 是在 main 函数,表示终止 main 函数,也就是说终止程序
2.9 字符串常用函数
-
统计字符串的长度,按字节 len(str)
-
字符串遍历,同时处理有中文的问题 r := []rune(str)
-
字符串转整数: n, err := strconv.Atoi("12")
-
整数转字符串 str = strconv.Itoa(12345)
-
字符串 转 []byte: var bytes = []byte("hello go")
-
[]byte 转 字符串: str = string([]byte{97, 98, 99})
-
查找子串是否在指定的字符串中: strings.Contains("seafood", "foo") //true
-
不区分大小写的字符串比较(== 是区分字母大小写的): fmt.Println(strings.EqualFold("abc", "Abc")) // true
-
按 照 指 定 的 某 个 字 符 , 为 分 割 标 识 , 将 一 个 字 符 串 拆 分 成 字 符 串 数 组 :
strings.Split("hello,wrold,ok", ",")
2.10 时间与日期函数
时间和日期相关函数,需要导入 time 包
package main
import (
"fmt"
"time"
)
func main() {
//看看日期和时间相关函数和方法使用
//1. 获取当前时间
now := time.Now()
fmt.Printf("now=%v now type=%T\n", now, now)
//2.通过now可以获取到年月日,时分秒
fmt.Printf("年=%v\n", now.Year())
fmt.Printf("月=%v\n", now.Month())
fmt.Printf("月=%v\n", int(now.Month()))
fmt.Printf("日=%v\n", now.Day())
fmt.Printf("时=%v\n", now.Hour())
fmt.Printf("分=%v\n", now.Minute())
fmt.Printf("秒=%v\n", now.Second())
//格式化日期时间
fmt.Printf("当前年月日 %d-%d-%d %d:%d:%d \n", now.Year(),
now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
dateStr := fmt.Sprintf("当前年月日 %d-%d-%d %d:%d:%d \n", now.Year(),
now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
fmt.Printf("dateStr=%v\n", dateStr)
//格式化日期时间的第二种方式
fmt.Printf(now.Format("2006-01-02 15:04:05"))
fmt.Println()
fmt.Printf(now.Format("2006-01-02"))
fmt.Println()
fmt.Printf(now.Format("15:04:05"))
fmt.Println()
fmt.Printf(now.Format("2006"))
fmt.Println()
//需求,每隔1秒中打印一个数字,打印到100时就退出
//需求2: 每隔0.1秒中打印一个数字,打印到100时就退出
// i := 0
// for {
// i++
// fmt.Println(i)
// //休眠
// //time.Sleep(time.Second)
// time.Sleep(time.Millisecond * 100)
// if i == 100 {
// break
// }
// }
//Unix和UnixNano的使用
fmt.Printf("unix时间戳=%v unixnano时间戳=%v\n", now.Unix(), now.UnixNano())
}
3. 函数,包和异常处理
3.1 函数与包
基本语法:
//请编写要给函数,可以计算两个数的和和差,并返回结果
func getSumAndSub(n1 int, n2 int) (int, int) {
sum := n1 + n2
sub := n1 - n2
return sum, sub
}
区分相同名字的函数、变量等标识符
当程序文件很多时,可以很好的管理项目
控制函数、变量等访问范围,即作用域
Ø 打包基本语法
package 包名
Ø 引入包的基本语法
import "包的路径"
在 import 包时,路径从 $GOPATH 的 src 下开始,不用带 src , 编译器会自动从 src 下开始引入
为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的 public ,这样才能跨包访问, 首字母小写,只能被本包文件使用,其它包文件不能使用,类似 privat
在访问其它包函数,变量时,其语法是 包名.函数名
如果包名较长,Go 支持给包取别名, 注意细节:取别名后,原来的包名就不能使用了( 如果给包取了别名,则需要使用别名来访问该包的函数和变量)
在同一包下,不能有相同的函数名(也不能有相同的全局变量名),否则报重复定义
如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用
Go 函数不支持函数重载
在 Go 中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用
函数函数既然是一种数据类型,因此在 Go 中,函数可以作为形参,并且调用
为了简化数据类型定义,Go 支持自定义数据类型:
基本语法:
type 自定义数据类型名 数据类型
// 理解: 相当于一个别名案例:
type myInt int // 这时 myInt就等价int来使用了
type mySum func (int, int) int
// 这时 mySum 就等价 一个 函数类型 func (int, int) int
支持对函数返回值命名, 这样子return的时候就可以直接写return不写变量名了
go支持函数可变参数:
init函数:
每一个源文件都可以包含一个 init 函数,该函数会在 main 函数执行前,被 Go 运行框架调用,也就是说 init 会在 main 函数前被调用
如果一个文件同时包含全局变量定义,init 函数和 main 函数,则执行的流程: 全局变量定义->init 函数->main 函数
细节说明: 面试题:案例如果 main.go 和 utils.go 都含有 变量定义,init 函数时,执行的流程又是怎么样的呢
匿名函数
分为定义时直接调用, 给个名字再用, 以及全局匿名函数:
package main
import (
"fmt"
)
var (
//fun1就是一个全局匿名函数
Fun1 = func (n1 int, n2 int) int {
return n1 * n2
}
)
func main() {
//在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次
//案例演示,求两个数的和, 使用匿名函数的方式完成
res1 := func (n1 int, n2 int) int {
return n1 + n2
}(10, 20)
fmt.Println("res1=", res1)
//将匿名函数func (n1 int, n2 int) int赋给 a变量
//则a 的数据类型就是函数类型 ,此时,我们可以通过a完成调用
a := func (n1 int, n2 int) int {
return n1 - n2
}
res2 := a(10, 30)
fmt.Println("res2=", res2)
res3 := a(90, 30)
fmt.Println("res3=", res3)
//全局匿名函数的使用
res4 := Fun1(4, 9)
fmt.Println("res4=", res4)
}
闭包---doc文档188页
函数的defer:
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等) ,为了在函数执行完毕后,及时的释放资源,Go 的设计者提供 defer (延时机制), 如图:
因此可以把一些关闭资源之类的语句写在defer后面
- 当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈中[我为了讲课方便,暂时称该栈为 defer 栈], 然后继续执行函数下一个语句。
- 当函数执行完毕后,在从 defer 栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制, 也就是把defer语句反过来执行)
- 在 defer 将语句放入到栈时,也会将相关的值拷贝同时入栈。因此在defer语句后面修改变量的值, 但是defer执行的时候变量还是之前的
函数的值传递与运用传递:
- 值类型:基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct
- 引用类型:指针、slice 切片、map、管道 chan、interface 等都是引用类型
- 其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝取决于拷贝的数据大小,数据越大,效率越低
go的内置函数:
new:用来分配内存,主要用来分配值类型,比如 int、float32,struct...返回的是指针
num := new(int)
len:用来求长度,比如 string、array、slice、map、channel
make:用来分配内存,主要用来分配引用类型,比如 channel、map、slice
3.2 异常处理
- Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种处理。
- Go 中引入的处理方式为:defer, panic, recover
- 这几个异常的使用场景可以这么简单描述:Go 中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理
示例代码:
package main
import (
"fmt"
_ "time"
"errors"
)
func test() {
//使用defer + recover 来捕获和处理异常
defer func() {
err := recover() // recover()内置函数,可以捕获到异常
if err != nil { // 说明捕获到错误
fmt.Println("err=", err)
//这里就可以将错误信息发送给管理员....
fmt.Println("发送邮件给admin@sohu.com~")
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res=", res)
}
//函数去读取以配置文件init.conf的信息
//如果文件名传入不正确,我们就返回一个自定义的错误
func readConf(name string) (err error) {
if name == "config.ini" {
//读取...
return nil
} else {
//返回一个自定义错误
return errors.New("读取文件错误..")
}
}
func test02() {
err := readConf("config2.ini")
if err != nil {
//如果读取文件发送错误,就输出这个错误,并终止程序
panic(err)
}
fmt.Println("test02()继续执行....")
}
func main() {
//测试
// test()
// for {
// fmt.Println("main()下面的代码...")
// time.Sleep(time.Second)
// }
//测试自定义错误的使用
test02()
fmt.Println("main()下面的代码...")
}
4. 数组和切片
数组:
注意:数组也是一种数据类型,在 Go 中,数组是值类型, 也就是说Go 的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响, 如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)
数组定义:
var 数组名 [数组大小]数据类型
var a [5]int
如果初始化时没有给定数组大小, 那么此时就是一个切片(slice)而不是数组了
在go中数组可以使用range遍历, 返回下标与元素值:
for i, v := range 数组名{
}
切片:
- 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
- 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度 len(slice)都一样。
- 切片的长度是可以变化的,因此切片是一个可以动态变化数组。
- 切片定义的基本语法:
var 切片名 []类型
var a [] int
这里切片的定义和python不同, 类似Java的List,
个人理解:
GO的切片其实就是把数组或者make的东西通过[:]切片赋值给一个变量, 这样子这个变量的数据类型就是引用类型切片类型了
切片使用注意事项:
-
切片初始化时 var slice = arr[startIndex:endIndex]
说明:从 arr 数组下标为 startIndex,取到 下标为 endIndex 的元素(不含 arr[endIndex])。
-
切片初始化时,仍然不能越界(也就是说不能在使用的时候越界)。范围在 [0-len(arr)] 之间,但是可以动态增长.
-
简写(类似python)
var slice = arr[0:end]
//可以简写
var slice = arr[:end]
var slice = arr[start:len(arr)]
//可以简写:
var slice = arr[start:]
var slice = arr[0:len(arr)]
//可以简写:
var slice = arr[:]
- cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
-
切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者 make 一个空间供切片来使用
-
切片还可以继续切片套娃
-
用 append 内置函数,可以对切片进行动态追加
切片 append 操作的底层原理分析:
切片 append 操作的本质就是对数组扩容
go 底层会创建一下新的数组 newArr(安装扩容后大小)
将 slice 原来包含的元素拷贝到新的数组 newArr
slice 重新引用到 newArr
注意 newArr 是在底层来维护的,程序员不可见.
-
切片的拷贝操作
切片使用 copy 内置函数完成拷贝
拷贝是深拷贝, 拷贝之后空间就不是指向同一块了, 之前的将数组赋值给切片其实空间也都是那一块
-
string 底层是一个 byte 数组,因此 string 也可以进行切片处理
但是注意:string 是不可变的,也就说不能通过 str[0] = 'z' 方式来修改字符串
5. map
6. 面向对象
7. 文件操作(包括JSON)
8. 抓包生成代码
抓包自动生成代码!!! 右键网页检查,接着找到那个请求对应的响应,右键copy-->copy cURL然后去代码生成网站 代码生成网站:curlconverter.com/#go , 运行完会生成一大串JSON除此之外还有一个根据JSON生成结构体的网站: oktools.net/json2go
socks5协议开发代理服务器
\