目录
1.数组
定义数组的几种方法:
var arr1 [5]int //定义一个元素为5个的数组
arr2 := [3]int{1, 3, 5} //用:=定义一个元素为3个的数组,这里要将元素写出来
arr3 := [...]int{2, 3, 4, 5, 3} //[...]表示这个数组的长度是不固定的
var grid [4][5]int
fmt.Println(arr1, arr2, arr3)
fmt.Println(grid)
遍历数组:
//遍历数组
for i, v := range arr3 { //i是数组下标,v是数组的值
fmt.Println(i, v)
}
//若不想要下标,只想取值
for _, v := range arr3 { //i是数组下标,v是数组的值
fmt.Println(v)
}
在go语言中,数组是值类型。
这就意味着在传入函数的时候,会被拷贝一遍,函数内的变动不会改变外面的值。
[10]int 和[20]int是不同的类型
func printArrary(arr [5]int) {
arr[0] = 100
for i, v := range arr { //i是数组下标,v是数组的值
fmt.Println(i, v)
}
}
fmt.Println("printArrary(arr1)")
printArrary(arr1)//这里第一个元素会变成100
fmt.Println("printArrary(arr3)")
printArrary(arr3)//这里第一个元素会变成100
fmt.Println("printArrary(arr1,arr3)")
fmt.Println(arr1, arr3)//这里两个数组的元素不会改变
那么要是想让传入的数组能够被函数改变,如何做呢?
其中一个办法是传入数组指针:
func printArraryptr(arr *[5]int) {
arr[0] = 100
for i, v := range arr { //i是数组下标,v是数组的值
fmt.Println(i, v)
}
}
fmt.Println("printArraryptr(&arr1)")
printArraryptr(&arr1)//第一个元素会编程100
fmt.Println("printArraryptr(&arr3)")
printArraryptr(&arr3)//第一个元素会变成100
fmt.Println("fmt.Println(arr1, arr3)")
fmt.Println(arr1, arr3)//两个数组的第一个元素都会变成100
但是go语言一般不用数组和数组指针当传入参数,因为太麻烦,一般是用切片。
到目前为止,以上第一部分的所有代码如下:
package main
import "fmt"
func printArrary(arr [5]int) {
arr[0] = 100
for i, v := range arr { //i是数组下标,v是数组的值
fmt.Println(i, v)
}
}
func printArraryptr(arr *[5]int) {
arr[0] = 100
for i, v := range arr { //i是数组下标,v是数组的值
fmt.Println(i, v)
}
}
func main() {
var arr1 [5]int //定义一个元素为5个的数组
arr2 := [3]int{1, 3, 5} //用:=定义一个元素为3个的数组,这里要将元素写出来
arr3 := [...]int{2, 3, 4, 5, 3} //[...]表示这个数组的长度是不固定的
var grid [4][5]int
fmt.Println(arr1, arr2, arr3)
fmt.Println(grid)
//遍历数组
for i, v := range arr3 { //i是数组下标,v是数组的值
fmt.Println(i, v)
}
//若不想要下标,只想取值
for _, v := range arr3 { //i是数组下标,v是数组的值
fmt.Println(v)
}
fmt.Println("printArrary(arr1)")
printArrary(arr1)
fmt.Println("printArrary(arr3)")
printArrary(arr3)
fmt.Println("printArrary(arr1,arr3)")
fmt.Println(arr1, arr3)
fmt.Println("printArraryptr(&arr1)")
printArraryptr(&arr1)
fmt.Println("printArraryptr(&arr3)")
printArraryptr(&arr3)
fmt.Println("fmt.Println(arr1, arr3)")
fmt.Println(arr1, arr3)
}
2.切片
2.1切片定义
arr:=[...]int{0,1,2,3,4,5,6,7}
s:=arr[2:6]
//s就是切片,s的值是[2 3 4 5]
//在计算机里面[2:6]一般是包前不包后,也就是包含下标为2的但不包含为6的。
fmt.Println("arr[:6]", arr[:6])
fmt.Println("arr[2:]", arr[2:])
fmt.Println("arr[:]", arr[:])
//arr[2:6]: [2 3 4 5]
//arr[:6]: [0 1 2 3 4 5]
//arr[2:]: [2 3 4 5 6 7]
//arr[:]:[0 1 2 3 4 5 6 7]
切片不是值类型,所以传入函数的时候不是拷贝:
func updateSlice(s []int) {
s[0] = 100
}
s1 := arr[2:] //[2 3 4 5 6 7]
updateSlice(s1)
fmt.Println(s1)//[100 3 4 5 6 7]
fmt.Println(arr)//[0 1 100 3 4 5 6 7]
2.2Reslice
切片是可以反复取的(Reslice):
fmt.Println("Reslice")//Reslic
s2 := arr[:]
fmt.Println(s2)//[0 1 100 3 4 5 6 7]
s2 = s2[:5]
fmt.Println(s2)//[0 1 100 3 4]
s2 = s2[2:]
fmt.Println(s2)//[100 3 4]
2.3切片的扩展
切片还可以扩展:
fmt.Println("Extending slice")
arr[0], arr[2] = 0, 2
s1 = arr[2:6]
s2 = s1[3:5]
fmt.Println("s1", s1)
fmt.Println("s2", s2)
//s1: [2 3 4 5],这没什么好说的
//s2,[5 6],这里s1明明没有s[4]这个元素如何取出来的呢?
s2如何能取出s[4]呢?
原因是切片是数组的一个视图,s1取数组下标为2-5的元素。
所以s1的下标为s[0]-s[3],但这时候s1是能够知道有s[4](为6),s[5](为7)的
只不过要是硬取是取不出来的,但是用s2对其取切片是能够取到的。
这就是切片扩展:
那么slice是如何实现的呢:
首先这个slice是有一个指针指向array的其中一段的开始,然后len是本切片可以直接访问的长度,cap是记录了指针到结尾的长度,所以可以实现切片扩展,只要不超过cap的长度即可。
slice是可以向后扩展的,但是不可以向前扩展。
s[i]不可以超过len(s),向后扩展不可以超越底层数组cap(s)
fmt.Println("Extending slice")
arr[0], arr[2] = 0, 2
fmt.Println("arr=", arr)
s1 = arr[2:6]
s2 = s1[3:5]
fmt.Printf("s1=%v,len(s1)=%d,cap(s1)=%d\n", s1, len(s1), cap(s1))
fmt.Printf("s2=%v,len(s2)=%d,cap(s2)=%d\n", s2, len(s2), cap(s2))
结果:
2.4向切片添加元素
s3 := append(s2, 10)
s4 := append(s3, 11)
s5 := append(s4, 12)
fmt.Println("s3,s4,s5=", s3, s4, s5)
//s2的cap是3,[5 6]后面隐藏了一个7,这时候s3执行的时候,将7换成了10
//s4,s5后面再添加就超过了cap,这时候go语言会在内部新建一个arr,也就是说s4和s5不再view当前arr了
fmt.Println("arr=", arr)
结果:
添加元素时如果超越了cap,系统会重新分配更大的底层数组,以便于能够执行添加元素的操作,这个更大的底层数组会将原来的数组拷贝过去,那么原来的数组如果程序还用到了就保留,如果程序没用到了,那么go语言的垃圾回收机制会将原来的数组回收掉。
由于值传递的髋膝,必须接收append的返回值,因为添加元素后,slice的len可能改变,cap也可能改变(创建了更大的底层数组后),所以必须用一个新的切片来接收这个append的返回值。
2.5slice ops
定义一个切片:
var s []int //声明一个切片,这时候s==nil
for i := 0; i < 100; i++ {
s = append(s, 2*i)
}
fmt.Println(s)
那么这样定义的slice,它的len和cap是多少呢:
func printSlice(s []int) {
fmt.Printf("len=%d,cap=%d\n", len(s), cap(s))
}
func main() {
var s []int //声明一个切片,这时候s==nil
for i := 0; i < 100; i++ {
printSlice(s)
s = append(s, 2*i)
}
fmt.Println(s)
}
结果为:
可以看到,len是一个一个增加,cap每次装不下了会成倍增加。
还可以这样创建slice:
s1 := []int{2, 4, 6, 8}
printSlice(s1)
s2 := make([]int, 16) //创建len为16,cap也是16的slice
printSlice(s2)
s3 := make([]int, 16, 32) //创建len为16,cap为32的slice
printSlice(s3)
复制slice:
fmt.Println("Copying slice")
copy(s2, s1) //把s1copy给s2
printSlice(s2)
删除slice中间的元素:
fmt.Println("Deleting elements from slice")
s2 = append(s2[:3], s2[4:]...)
printSlice(s2)
删除头元素:
fmt.Println("Popping from front")
front := s2[0]
s2 = s2[1:]
fmt.Println(front)
删除尾元素:
fmt.Println("Poping from back")
tail := s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(tail)
到目前为止第二部分的全部代码如下:
package main
import "fmt"
func updateSlice(s []int) {
s[0] = 100
}
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s := arr[2:6]
fmt.Println("arr[2:6]", s)
//s就是切片,s的值是[2 3 4 5]
//在计算机里面[2:6]一般是包前不包后,也就是包含下标为2的但不包含为6的。
fmt.Println("arr[:6]", arr[:6])
fmt.Println("arr[2:]", arr[2:])
fmt.Println("arr[:]", arr[:])
s1 := arr[2:]
//s2 := arr[:]
updateSlice(s1)
fmt.Println(s1)
fmt.Println(arr)
fmt.Println("Reslice")
s2 := arr[:]
fmt.Println(s2)
s2 = s2[:5]
fmt.Println(s2)
s2 = s2[2:]
fmt.Println(s2)
fmt.Println("Extending slice")
arr[0], arr[2] = 0, 2
fmt.Println("arr=", arr)
s1 = arr[2:6]
s2 = s1[3:5]
fmt.Printf("s1=%v,len(s1)=%d,cap(s1)=%d\n", s1, len(s1), cap(s1))
fmt.Printf("s2=%v,len(s2)=%d,cap(s2)=%d\n", s2, len(s2), cap(s2))
fmt.Println("---------------------------")
s3 := append(s2, 10)
s4 := append(s3, 11)
s5 := append(s4, 12)
fmt.Println("s3,s4,s5=", s3, s4, s5)
//s2的cap是3,[5 6]后面隐藏了一个7,这时候s3执行的时候,将7换成了10
//s4,s5后面再添加就超过了cap,这时候go语言会在内部新建一个arr,也就是说s4和s5不再view当前arr了
fmt.Println("arr=", arr)
}
//--------------------------------------------------------------------
package main
import "fmt"
func printSlice(s []int) {
fmt.Printf("%v,len=%d,cap=%d\n", s, len(s), cap(s))
}
func main() {
fmt.Println("Creating slice")
var s []int //声明一个切片,这时候s==nil
for i := 0; i < 100; i++ {
printSlice(s)
s = append(s, 2*i)
}
fmt.Println(s)
s1 := []int{2, 4, 6, 8}
printSlice(s1)
s2 := make([]int, 16) //创建len为16,cap也是16的slice
printSlice(s2)
s3 := make([]int, 16, 32) //创建len为16,cap为32的slice
printSlice(s3)
fmt.Println("Copying slice")
copy(s2, s1) //把s1copy给s2
printSlice(s2)
fmt.Println("Deleting elements from slice")
s2 = append(s2[:3], s2[4:]...)
printSlice(s2)
fmt.Println("Popping from front")
front := s2[0]
s2 = s2[1:]
fmt.Println(front)
fmt.Println("Poping from back")
tail := s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(tail)
fmt.Println(s2)
}
3.Map
3.1map的定义
map的定义:
map[key的类型]值的类型
复合map的定义:
map[key1的类型]map[key2的类型]值的类型
map的定义:
m1 := map[string]string{ //第一种定义方式
"name": "maoweiyang",
"age": "18",
"yanzhi": "good",
}
m2 := make(map[string]int) //第二种定义方式,m2==empty map
var m3 map[string]int //第三种定义方式,m3==nil
fmt.Println(m1)
fmt.Println(m2)
fmt.Println(m3)
3.2map的操作
map的遍历:
fmt.Println("Traversing map")
for k, v := range m1 {
fmt.Println(k, v)
}
注意:map里面的元素是无序的。
map取值:
fmt.Println("Getting values")
myname := m1["name"]
fmt.Println(myname)
注意:当key填写错误的时候,go并不会报错,而是会取出一个空值。
那么如何判断所取关键字对应的元素在不在:
fmt.Println("Getting values")
myname, ok := m1["name"]
fmt.Println(myname, ok)
myneme, ok := m1["neme"]
fmt.Println(myneme, ok)
结果为:
所以一般这样操作:
if myname, ok := m1["neme"]; ok {
fmt.Println(myname, ok)
} else {
fmt.Println("key does not exist")
}
删除元素:
fmt.Println("Deleting values")
age, ok := m1["age"]
fmt.Println(age, ok)
delete(m1, "age")
age, ok = m1["age"]
fmt.Println(age, ok)
关于map的key:
- map使用哈希表,必须可以比较相等。
- 除了slice,map,function的内建类型都可以作为key。
- Struct类型不包含上述字段,也可以作为key。
到目前为止第三部分的所有代码如下:
package main
import "fmt"
func main() {
m1 := map[string]string{ //第一种定义方式
"name": "maoweiyang",
"age": "18",
"yanzhi": "good",
}
m2 := make(map[string]int) //第二种定义方式,m2==empty map
var m3 map[string]int //第三种定义方式,m3==nil
fmt.Println(m1)
fmt.Println(m2)
fmt.Println(m3)
fmt.Println("Traversing map")
for k, v := range m1 {
fmt.Println(k, v)
}
fmt.Println("Getting values")
myneme, ok := m1["neme"]
fmt.Println(myneme, ok)
if myname, ok := m1["neme"]; ok {
fmt.Println(myname, ok)
} else {
fmt.Println("key does not exist")
}
fmt.Println("Deleting values")
age, ok := m1["age"]
fmt.Println(age, ok)
delete(m1, "age")
age, ok = m1["age"]
fmt.Println(age, ok)
}
3.3map的一个例题
题目:给定一个字符串s,请你找出其中不含有重复字符的 最长子串的长度 。
例如:
abcabcbb : 3
bbbbbbb:1
pwwkew:3
思路:
start:当前检测的无重复子串的开始位置。
x:需要检查其是否需要加入当前无重复子串的元素。
lastOccurred[X]:x最后出现的位置。
lastOccurred[X]不存在,或者小于start的位置,那么无需操作
lastOccurred[X]大于等于start的位置,那么更新start到lastOccurred[X]+1
package main
import "fmt"
func lengthofNonRepeatingSubStr(s string) int {
lastOccurred := make(map[byte]int)
start := 0 //当前检测的无重复子串的开始位置。
maxLength := 0
for i, ch := range []byte(s) {
//ch最后出现的位置存在且在start后。
if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
start = lastI + 1 //start移动到ch最后出现的位置的后面一位。
}
if i-start+1 > maxLength {
//如果当前无重复字符串比记录的长度要长
maxLength = i - start + 1
}
lastOccurred[ch] = i //更新ch最后出现的位置
}
return maxLength
}
func main() {
fmt.Println(lengthofNonRepeatingSubStr("abcabcbb"))
fmt.Println(lengthofNonRepeatingSubStr("bbbbb"))
fmt.Println(lengthofNonRepeatingSubStr("pwwkew"))
fmt.Println(lengthofNonRepeatingSubStr(""))
fmt.Println(lengthofNonRepeatingSubStr("b"))
fmt.Println(lengthofNonRepeatingSubStr("abcdef"))
}
4.字符和字符串的处理
rune就是go的char类型
func main() {
s := "Yes我爱我的祖国!" //UTF-8编码
for _, b := range []byte(s) {
fmt.Printf("%X ", b)
}
fmt.Println()
}
结果:
可以看到,UTF-8的编码方式中,英文字符一个占1个字节(8位),中文字符一个占3个字节。
for i, ch := range s { // ch is a rune
fmt.Printf("(%d %X)", i, ch)
}
fmt.Println()
可以看到,ch是一个rune类型,它将string进行了UTF-8的解码,然后进行unicode编码,然后放在了rune类型里面。
统计rune的数目:
fmt.Println("Rune count", utf8.RuneCountInString(s)) //结果为10
遍历s:
bytes := []byte(s)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes) //ch是字符,size是位置,英文隔1,中文隔3
bytes = bytes[size:]
fmt.Printf("%c ", ch)
}
fmt.Println()
其他的一些字符串的操作:
Fields, Split, Join
Contains, Index
ToLower,ToUpper
Trim, TrimRight,TrimLeft
到这里为止,第四部分的全部代码如下:
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "Yes我爱我的祖国!" //UTF-8编码
for _, b := range []byte(s) {
fmt.Printf("%X ", b)
}
fmt.Println()
for i, ch := range s { // ch is a rune
fmt.Printf("(%d %X)", i, ch)
}
fmt.Println()
fmt.Println("Rune count", utf8.RuneCountInString(s)) //结果为10
bytes := []byte(s)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes) //ch是字符,size是位置,英文隔1,中文隔3
bytes = bytes[size:]
fmt.Printf("%c ", ch)
}
fmt.Println()
for i, ch := range []rune(s) {
fmt.Printf("(%d %c)", i, ch)
}
fmt.Println()
}
5.结构体和方法
go语言仅支持封装,不支持继承和多态。
结构体定义以及声明:
package main
import "fmt"
type treeNode struct {
value int
left, right *treeNode
}
func createNode(value int) *treeNode { //工厂函数
return &treeNode{value: value}
}
func main() {
var root treeNode
root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}
root.right.left = new(treeNode)
root.left.right = createNode(2)
nodes := []treeNode{
{value: 3},
{},
{6, nil, &root},
}
fmt.Println(nodes)
}
注意:上面用了自定义工厂函数来创建结构体,且返回了局部变量的地址(go语言是允许这样的),用这种方法来代替构造函数的功能,go语言中是没有构造函数这种说法的。
在go语言的结构体中,函数如何实现呢:
func (node treeNode) print() {
fmt.Println(node.value)
}
函数有个接收者,这个接收者是在函数名字前面价格括号,里面协商接收的结构体类型。
使用时候:
root.print()
这里实际上也相当于一个参数传递,也是值传递:
func (node treeNode) setValue(value int) {
node.value = value
}
root.right.left.setValue(4)
root.right.left.print()
fmt.Println()
上述代码打印结果还是0,是改不掉的,即这里也是值传递。
那么如果希望改变:
func (node *treeNode) setValue(value int) {
node.value = value
}
root.right.left.setValue(4)
把接收者改为指针,但是调用的时候还是照常调用。
在go语言中,参数是指针的时候可以用地址调用也可以用值调用,当参数是值的时候,可以用地址调用也可以用值调用,编译器可以帮我们处理这些不对应的关系。
只有指针才可以改变结构内容。
nil指针也可以调用方法。
实现一个遍历节点的函数:
func (node *treeNode) traverse() {
if node == nil {
return
}
node.left.traverse()
node.print()
node.right.traverse()
}
要改变内容的话必须使用指针接收者。
结构过大也需要考虑使用指针接收者,因为拷贝会影响性能。
一致性:有指针接收者,最好都是指针接收者。
6.封装
在go语言中名字一般使用CamelCase
首字母大写代表:public
首字母小写代表:private
这个public或者private是针对包来说的。
每个目录一个包
main包包含可执行入口
为结构定义的方法必须放在同一个包内
但可以是不同的文件
tree目录下的包为tree(当然包名可以和目录名不一样),ebtry目录下的包名为main,那么main包下的代码要用tree包里面的结构和方法,就必须将tree给import进去,“包名.结构体”使用,注意tree包里面的名字都要首字母大写。
7.扩展已有类型
如何扩充系统类型或者别人的类型:定义别名或者使用组合
例子一:
定义别名:最简单
type myTreeNode struct {
node *tree.Node
}
func (myNode *myTreeNode) postOrder() {
if myNode == nil || myNode.node == nil {
return
}
left := myTreeNode{myNode.node.Left}
left.postOrder()
right := myTreeNode{myNode.node.Right}
right.postOrder()
myNode.node.Print()
}
例子二:
使用组合:最常用
package queue
type Queue []int
func (q *Queue) Push(v int) {
*q = append(*q, v)
}
func (q *Queue) Pop() int {
head := (*q)[0]
*q = (*q)[1:]
return head
}
func (q *Queue) IsEmpty() bool {
return len(*q) == 0
}
package main
import (
"Basic_grammar_go/queue"
"fmt"
)
func main() {
q := queue.Queue{1}
q.Push(2)
q.Push(3)
fmt.Println(q.Pop())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
}
例子三:
使用内嵌的方式来扩展已有的类型:需要省下很多代码
package main
import (
"Basic_grammar_go/tree"
"fmt"
)
type myTreeNode struct {
*tree.Node //Ebedding 内嵌,不要名字,那么取值的时候直接忽略前面的,直到Node
}
func (myNode *myTreeNode) postOrder() {
if myNode == nil || myNode.Node == nil {
return
}
left := myTreeNode{myNode.Left}
left.postOrder()
right := myTreeNode{myNode.Right}
right.postOrder()
myNode.Node.Print()
}
func main() {
root := myTreeNode{&tree.Node{Value: 3}}
root.Left = &tree.Node{}
root.Right = &tree.Node{5, nil, nil}
root.Right.Left = new(tree.Node)
root.Left.Right = tree.CreateNode(2)
nodes := []tree.Node{
{Value: 3},
{},
{6, nil, nil},
}
fmt.Println(nodes)
root.Print()
root.Right.Left.SetValue(4)
root.Right.Left.Print()
fmt.Println()
root.Traverse()
root.postOrder()
}
8.go语言的依赖管理
依赖管理的三个阶段 GOPATH,GOVENDOR,go mod
8.1 GOPATH
你告诉我一个GOPATH,你要的所有依赖包,我都去这个GOPATH里面找。
8.2GOVENDOR
每个项目都有自己的vendor目录,存放第三方库
大量第三方依赖管理工具:glide,dep,go dep,...
8.3go mod
例如:
go get -u go.uber.org/zap
会安装zap1.12
那么改为1.11怎么改
//安装1.11版本
go get -u go.uber.org/zap@v1.11
//清除1.12
go mod tidy