文章目录
- 一、Go环境
- 二、Go依赖
- 2.1 go deps
- 2.2 go module(1.13+默认的包管理工具)
- 三、go module
- 四、go语法
- 4.1 变量
- 4.2 常量
- 4.3 数据类型
- 4.3.1 整型
- 4.3.2 数字字面量语法
- 4.3.2 浮点型
- 4.3.3 复数
- 4.3.4 布尔值
- 4.3.5 字符串 【区别Java】
- 4.3.6 字符串转义符
- 4.3.7 多行字符串
- 4.3.8 byte和rune类型
- 4.3.9 修改字符串
- 4.4 类型转换
- 五、流程控制 if for
- 5.1 if
- 5.2 for
- 5.3 for range
- 5.4 switch case
- 5.5 goto
- 5.6 break
- 5.7 continue
- 六、数组
- 6.1 数组
- 6.2 多维数组
- 6.3 初始化
- 6.4 数组循环
- 6.5 数组的本质
- 6.6 数组copy
- 七、切片
- 7.1 本质
- 7.2 go 的slice 和 数组比较
- 7.3 slice语法
- 7.3.1 声明
- 7.3.2 切片表达式
- 7.3.3 make
- 7.3.4 切片判断
- 7.3.5 slice的赋值拷贝
- 7.3.6 slice 的遍历
- 7.3.7 API
- 7.3.8 examples
- 八、map
- 8.1 声明
- 8.2 判断某个键是否存在
- 8.3 遍历map
- 8.4 删除
- demos
一、Go环境
安装步骤略。
可使用go version
判断是否安装成功
较高版本go会自动添加 GOROOT GOPATH(反正1.15可以)
- go build
- go build -o “x.exe”
- go run main.go
- go install 先编译,然后拷贝到
$GOPATH/bin
中
二、Go依赖
问:为啥要有go 依赖的?
答: 早期,go所有依赖的三方库都在 GOPATH 下,同一个库的只能保存一个版本的代码,若不同的项目依赖同一个三方库的不同版本,怎破?
目前已引入vendor
模式,若项目目录下有vendor
目录,go工具链优先使用vendor
内的包。godep
就是一个实现vendor
模式 的三方依赖管理工具。
2.1 go deps
比较旧的项目会使用go deps
工具,已经淘汰了
2.2 go module(1.13+默认的包管理工具)
三、go module
go module 导入本地包,分两种情况:
- 1、一个项目下的包
- 同一个项目(project) 可以定义多个包 (package)
- 2、
前面几节都是工程化的方面的内容,下面是语法方面的。
四、go语法
4.1 变量
var name string
var age = 18
func main() {
n := 10
}
func main() {
x, _ := foo()
_, y := foo()
fmt.Println("x=", x)
fmt.Println("y=", y)
}
- 变量先声明再使用;同一个作用域不能重复声明
- 跟java类似,在声明时会初始化时机默认值
- 整型–>0
- 浮点型–> 0
- 字符串变量 --> 空串 【这个与java倒是不一样】
- 切片、函数、指针变量 --> nil
- 支持类型推导
- 支持函数内 短声明
- 支持匿名变量
- 匿名变量不占用命名空间,不分配内存,也不存在重复声明(lua中的哑元变量)
- 字符 _ 用于占位,表示忽略值
4.2 常量
const a = 10
const(
c = "ht"
d = 1.0
)
const(
c = "ht"
d
)
const (
e = iota //常量计数器。简单说就是 const 语句块的行索引。主要用于定义 枚举
f //
g
h
)
const (
n1 = iota //0
n2 //1
_ // 使用 _ 跳过某些值
n4 //3
)
- 常量定义的时候, 必须赋值,且之后不能再去更改了。
- 支持一次声明多个常量
- 一次声明多个常量时,若省略了值,则表示和上一行值相同
-
iota
: const 语句块的行索引,枚举中用得比较多
4.3 数据类型
除了基本的整型、浮点型、布尔型、字符串之外,还有数组、切片、函数、结构体、map、通道等。可见,Go中的数据类型十分丰富。
4.3.1 整型
- uinit8 --> 无符号 8位 整型(0 --> 255),其实就是
byte
- …
下面介绍特殊整型:
- uint --> 32位系统上是uint 32 (无符号32位整型), 64位系统上是 uint64 (无符号64位整型)
- int --> 32位系统上是 int32 (有符号32位整型),64位系统上是 int64(有符号64位整型)
- uintptr --> 无符号整型,用于存放一个指针
注意:
- 在使用int 和 uint ,不能假定是32 或 64 位,要考虑不同平台上的差异
-
len()
返回值根据不同平台而不同。切片、map 元素数量可以用int
表示。在涉及二进制运输、读写文件时,为保持文件结构不受平台影响,不要使用int
uint
4.3.2 数字字面量语法
主要是为了方便以二进制、八进制、十六进制浮点数定义数字。
4.3.2 浮点型
- float32
- float64
4.3.3 复数
- complex64
- complex128
4.3.4 布尔值
也不过是true false .
go语言中:
- 不能将整型强转为布尔值
- 布尔数不能参与数值运算,不能与其他类型数据转换
4.3.5 字符串 【区别Java】
Go中的字符串是原生数据类型,使用字符串跟使用int 这些数据类型一样。Go字符串内部使用 UTF-8 编码。可以直接 s:= "非AscII字符"
4.3.6 字符串转义符
\r 回车
\n 换行
\t 制表符
\' 单引
\'' 双引
\\ 反斜杠
4.3.7 多行字符串
s1 := `第一行
第二行
第三行
`
fmt.Println(s1)
反引号间换行被视为字符串的换行,所有的转义字符都无效,文本原样输出
4.3.8 byte和rune类型
Go中的字符有两种:
- uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。
- rune类型,代表一个 UTF-8字符。
当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32。
UTF-8 编码下,一个中文字符占3~4 个字节,我们不能简单按照字节去修改包含中文的字符串,否则可能乱码。
GO的字符串底层是一个byte数组,可以和byte[]
(切片)相互转化。字符串的长度就是 byte[]
的长度。rune类型
用来表示 utf-8 字符,一个 rune字符由一个或多个byte 组成。
Go中字符串也是不能修改的。我们常说的修改一个字符串的操作实际上是复制一个新字符串的过程。
注意,在java中, 字符串是一个char[],和go还是不一样的。
举个栗子:
简单粗暴地记住一句话即可:
遍历字符串的元素,要使用 for range,否则遍历的是底层byte数组。
func main(){
var s = "iam中国人"
// 12 个 字符 = iam 三个字符 + (中国人) * 3 个UTF-8字符
fmt.Println(len(s))
// 105(i)97(a)109(m)228(ä)184(¸)173()229(å)155()189(½)228(ä)186(º)186(º)
// 乱码 --> 底层是 byte 数组,len() 得到的是 字符串的底层 数组的 长度,如此遍历的也是 byte
// 数组的每个元素
for i := 0; i < len(s); i++ {
fmt.Printf("%v(%c)", s[i],s[i])
}
fmt.Println()
// 105(i)97(a)109(m)20013(中)22269(国)20154(人)
// 正常输出
for _, v := range s {
fmt.Printf("%v(%c)", v,v)
}
}
4.3.9 修改字符串
要修改字符串,需要先将其转换成[]rune或[]byte
,完成后再转换为string
。无论哪种转换,都会重新分配内存,并复制字节数组。
s2 := "白萝卜" // 字符串
s3 := []rune(s2) // 强转为 rune 切片
s3[0] = '红'
fmt.Print(string(s3)) //rune强转为字符串
c1 := "红" // string
c2 := '红' // int32
fmt.Printf("c1: %T, c2: %T \n", c1, c2)
c3 := "H" // string
c4 := 'H' // int32
fmt.Printf("c3: %T, c4: %T", c3, c4)
c5:= byte(c4) // uint8 ,其实就是 byte
fmt.Printf("c5 : %T \n", c5) // uint8
fmt.Printf("c5: %d", c5) // 72
4.4 类型转换
Go语言中只有强制类型转换,没有隐式类型转换。该语法只能在两个类型之间支持相互转换的时候使用。
T(表达式)
语法实现了强转
同时,%T
可以打印出变量的数据类型
fmt.Printf("%T", "字符串")
五、流程控制 if for
5.1 if
5.2 for
go 语言中,for 可以当做while一样来用
func fun2(){
i := 1
for i<=10{
fmt.Println(i)
i++
}
}
OR:
死循环
for {
循环体语句
}
5.3 for range
for range遍历数组、切片、字符串、map 及通道(channel)
- 数组 、切片、字符串 返回索引和值
- map ,返回key和值
- channel ,返回 通道内的值
func fn3() {
s := "hello你好" // 这里的 “好” 的索引并不是 6,而是 8 ,这跟java 的string还确实差异很大。go的string 是个字节数组,不是字符数组
for i,v := range s{ //i 索引 ; v 值
fmt.Printf("%d %c \n", i,v)
}
}
5.4 switch case
fallthrough语法可以执行满足条件的case的下一个case(为了兼容C语言)
func fallthroughDemo() {
s := "a"
switch {
case s == "a":
fmt.Print("1")
fallthrough
case s == "b": // 这一句也会执行
fmt.Print("2")
case s == "c":
fmt.Print("3")
default:
fmt.Print("...")
}
}
5.5 goto
略。goto 尽量不要用
5.6 break
break可以结束 for switch select
的代码块。
break 还可以 在语句后面加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的 for switch select
代码块上。
5.7 continue
结束本次循环,开始下一次循环。continue
语句后添加标签时,表示开始标签对应的循环
六、数组
6.1 数组
数组: 同一种数据类型元素的集合。使用时,可以修改数组成员,但是不能改变数组大小。定义如:
var a [5]int
- 数组的长度必须是常量,并且长度是数组类型的一部分
- 数组长度定义后,不能再去修改
-
[5]int [10]int
是不同类型的数组
6.2 多维数组
go 支持多维数组
6.3 初始化
数组有多种初始化方式。
func fn5() {
var a = [3]string{"a","b","c"}
fmt.Println(a)
var b = [...]int{1,2,3}
fmt.Println(b)
c:=[...]int{1:1,4:4}
fmt.Print(c)
}
6.4 数组循环
- for 循环
- for …range …
6.5 数组的本质
1、数组是值类型:
赋值和传参会复制整个数组,若改变副本的值,不会改变本身的值。
2、数组支持 “==“、”!=” 操作符,因为内存总是被初始化过的。
3、[n]*T
表示指针数组,*[n]T
表示数组指针 。
6.6 数组copy
func fn31() {
var a = []int{1,2,3} //slice a
a1 := a
fmt.Println(a)
fmt.Println(a1)
a1[1] =200
fmt.Println(a1)
var a2 []int
copy(a2, a)
fmt.Println(a2) // a2 没有初始化,因此打印出 [] ,本质是nil
fmt.Println(a2 == nil)
var a3 = make([]int, 0, 10)
copy(a3, a1)
fmt.Println(a3) // a3虽然初始化了,但是由于 长度为0, 在copy 时并不会自动扩容,因此还是 []
fmt.Println(a3 == nil)
var a4 = make([]int, 3, 10)
copy(a4, a1)
fmt.Println(a4) // [1,200,3]
}
七、切片
7.1 本质
slice 本质就是个数组指针 + 长度 + 容量。
切片本身不存值,存值的是切片指向的底层数组
切片指向的数组的内存必须是连续的
go的切片类似Java 的ArrayList 。不过使用起来差别还是挺大的。
7.2 go 的slice 和 数组比较
- Slice 是动态数组,可变长;数组长度是数组的一部分,是定长的
- Slice 是传址,而数组是传值
7.3 slice语法
7.3.1 声明
var name []T
跟数组声明比较起来,只不过是不需要指定的 长度而已。
var a []string // 声明字符串切片
var c = []string {} // 声明字符串切片并初始化。通常声明即可操作,不需要特地初始化
var d = []bool {true} // // 声明字符串切片并初始化,并给元素赋值
fmt.Println(a == nil) //true 底层数组未指定,因此为nil
fmt.Println(c == nil) //false
fmt.Println(len(d)) // len() 内置函数(builtin)求长度 (长度=1)
fmt.Println(cap(d)) // cap() 内置函数 求容量capacity (容量=1)
7.3.2 切片表达式
指从 数组、字符串,指向数组或字符串的指针构造字符串或切片。有两种形式:简单表达式 + 完整表达式。
简单表达式:
func fn9(){
a := [5]int{1,2,3,4,5} // 这是一个数组
b := a[1:3] // 这是一个从数组 构造的 slice, 左闭右开,即取了 a[1] a[2]作为 切片的新元素
fmt.Printf("%T %v \n", b, b)
fmt.Printf("len:%v \n", len(b))
fmt.Printf("cap:%v \n", cap(b)) // 容量 = 原数组容量5 - 起始切片索引(1) = 4
fmt.Println("---------")
c:= b[3:4] //切片再切片 , 右边索引的最大值= cap(b),也就是 4
fmt.Printf("%v \n", c)
fmt.Printf("len:%v \n", len(c))
fmt.Printf("cap:%v \n", cap(c))
}
7.3.3 make
上一节,是基于 数组来构造一个切片,假如我们要动态创建一个切片,
则 make([]T, size, cap)
func fn10() {
a:= make([]int, 10) // make 关键字 --> 创建 切片
fmt.Printf("%T \n", a) //[]int --> illustrates that it's a slice
fmt.Printf("len:%v \n", len(a)) // len:10 --> 注意,len并不是0
fmt.Printf("cap:%v \n", cap(a)) //cap :10
fmt.Printf("%v \n", a) //[0 0 0 0 0 0 0 0 0 0] --> 每个元素都有默认值
}
当然,也可以:
a:= make([]int, 2,10) 这样指定 len 和 cap
7.3.4 切片判断
1、判断切片是否为空不能使用s == nil
,而应该使用len == 0
(任何情况下都是如此,即使是声明但是没有初始化的)
2、切片之间不能比较,不能使用==
直接判断两个切片是否具有完全一样的元素。切片只能和 nil
比较。
一个 nil
值的切片,没有底层数组,切片长度、容量都为0。但是反过来则不一定:我们不能说,一个长度、容量=0 的切片一定是nil
。
var s1 []int // s1: len (0), cap (0), (==nil?) true --> 只声明,未初始化
s2 := []int{} // s2: len (0), cap (0), (==nil?) false --> 声明并初始化了
s3 := make([]int,0) // s3: len (0), cap (0), (==nil?) false --> 声明并初始化了
7.3.5 slice的赋值拷贝
拷贝前后,两个slice实际上共享了同一个底层数组,因此对一个slice的增删改会影响到另一个slice。【传址】
7.3.6 slice 的遍历
slice的遍历:
- for
- for range
7.3.7 API
- append
- append操作可能引起扩容,扩容策略会因为元素类型的不同而不同
- copy --> 复制切片
- 切片中删除元素
- slice并没有直接删除元素的API
- 要从切片a中删除索引为index的元素,操作方法是
a = append(a[:index], a[index+1:]...)
【Java表示:这种用法实在是繁琐啊。。】
7.3.8 examples
1、case1 :append
var b = make([]string, 5, 10)
fmt.Println("before append:", b) // [ ] --> 这里是 空串组成的 切片
for i := 0; i < 10; i++ {
b = append(b, fmt.Sprintf("%v", i))
}
fmt.Println("after append", b) // [ 0 1 2 3 4 5 6 7 8 9] --> 这里 前面几个元素还是空串
fmt.Println("after append,the len is:", len(b)) //15
fmt.Println("after append,the cap is:", cap(b)) //20
假如:第一句代码指定len = 0,也就是说:
var b = make([]string, 5, 10)
则前5个元素就不是空串了
2、case2 :排序
内置的sort包对数组var a = [...]int{3, 7, 8, 9, 1}
进行排序
func fn13() {
var a = []int{1, 5, 4, 9, 8, 0}
sort.Sort(sort.IntSlice(a))
fmt.Println(a)
}
3、case3: 删除
func fn14(){
a := [3]int{1,3,5} // a is an array
fmt.Println(a, len(a), cap(a))
b := a[:] //b is a slice
fmt.Println(b, len(b), cap(b))
b = append(b[:1], b[2:]...) // 实际效果是删除了 元素 “3”,本质上会修改底层数组
fmt.Println(b, len(b), cap(b)) // [1 5] 2 3
fmt.Println(a) // [1 5 5] --> 这里会修改 底层数组第二个值为 5
// 假如我们从切片修改一个值
b[0] =100
fmt.Println(a) //[100 5 5] --> 依然改动了底层数组的值
}
八、map
8.1 声明
map是 引用类型,必须先初始化才能使用。
声明语法: map[KeyType]ValueType
map类型的变量默认初始值为<nil>
,需要先make
func fn22() {
// 方法1
var dict =make(map[string]int, 10)
dict["a"] =1
dict["b"] =2
fmt.Println(dict)
fmt.Printf("%T \n", dict) // map[string]int
// 方式2
var dict2 = map[string]int{
"c":3,
"d":4,
}
fmt.Println(dict2)
}
8.2 判断某个键是否存在
go提供了一种很有意思的方法来查实某个key是否存在:
v, exists := dict["aa"]
if exists {
fmt.Println(v)
}else{
fmt.Println("this map has no key of \"aa\"")
}
8.3 遍历map
- for range
func fn23() {
var m = make(map[string]int, 1)
m["key1"] = 1
m["key2"] = 2
// 遍历
for k, v := range m {
fmt.Println("k-v ", k, v)
}
// 若只需要key
for k := range m {
fmt.Println(k, m[k])
}
}
8.4 删除
delete(map,key)
var m = make(map[string]string, 0)
m["key1"] = "value1"
m["key2"] = "value2"
for k := range m {
if k == "key1" {
delete(m, k) // delete by key
}
}
fmt.Println(m)
demos
case1: 按指定顺序遍历map
func fn25() {
// 产生随机数
rand.Seed(time.Now().UnixNano())
var m = make(map[string]int, 20)
for i := 0; i < 20; i++ {
var r = rand.Intn(100)
m[fmt.Sprintf("%v", i)] = r
}
fmt.Println(m)
var keys = make([]string,0,20)
for k := range m {
keys = append(keys,k)
}
fmt.Println(keys)
sort.Strings(keys)
for k := range m {
fmt.Println(k, " ", m[k])
}
}
case2:值为切片的map
func fn27() {
var m = make(map[string][]string, 2)
fmt.Println(m)
var key = "a"
value, exists := m[key]
if !exists {
value = make([]string, 0)
}
value = append(value, "aaaa")
m[key] = value
for _, v := range m {
fmt.Println(v)
}
}
case3: 值为map的切片
func fn26() {
var s = make([]map[string]int, 0)
var m1 = make(map[string]int, 1)
m1["a"] = 1
var m2 = make(map[string]int, 2)
m2["b"] = 2
m2["c"] = 3
s = append(s, m1, m2)
fmt.Println(s)
}
case4: 写一个程序,统计一个字符串中每个单词出现的次数。比如:”how do you do”中how=1 do=2 you=1。
func fn28() {
var s = "how do you do "
var arr = strings.Fields(s)
fmt.Printf("arr len:%v \n", len(arr))
var countMap = make(map[string]int)
for _, v := range arr {
count := countMap[v]
countMap[v]= count + 1
}
for k, v := range countMap {
fmt.Printf("%v --> %v \n", k, v)
}
}
case5: 错误范例:使用前要记得初始化
func fn29(){
var s = make([]map[string]int, 1, 10)
s[0]["a"] = 1 // throw an exception: assignment to entry in nil map
s[1] ["b"] = 2
fmt.Println(s)
}
``