• Go语言和其他语言一样都有基本存储容器.可以存储一个或多个值在程序中,方便程序中多次使用容器中内容,这个容器称为:变量

  • Go语言虽然是静态类型语言,但是支持动态类型语言语法,因为Go语言希望程序员少声明变量,增加GC效率

一、变量命名规则
  • 以字母或下划线开头(Go语言中多不以_开头)

  • 后面可以是任意数量的字符、数字和下划线

  • 区分大小写

  • 不能是关键字(关键字具备特定含义),下面是Go语言的关键字

关键字如下    
breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar
  • 在同一范围内不允许出现同名变量

  • Go语言要求变量声明后至少使用一次(赋值不属于使用)

二、单个变量声明及赋值

 先声明后赋值(声明后开辟内存,不同类型变量都有不同初值)

//语法:
//1. 声明
var 变量名 类型
//2. 赋值
变量名=值

//示例:
var smallming string
smallming = "英文名"

声明并赋值(省略类型,变量类型取决于值的类型)

//语法:
var 变量名 = 值

//示例:
var smallming = "英文名"

 短变量(只能在函数内使用)

//语法:
变量名 := 值

//示例:
smallming := "英文名"
三、声明多个变量和赋值

先声明后赋值

func main() {
	var a, b, c int
	a, b, c = 1, 2, 3
	fmt.Println(a, b, c)
}

声明时赋值

func main() {
	var a, b, c, d = 1, 2, 3, false
	fmt.Println(a, b, c, d)
}

声明并赋值,推荐方式

func main() {
	var (
		a = 1
		b = true
		c = "测试"
	)
	fmt.Println(a, b, c)
}

使用短变量给多个变量赋值时,必须要保证至少有个变量是没有声明的

func main() {
	var (
		a = 1
		b = true
		c = "测试"
	)
	//短变量操作多个值时只要保证里面至少有一个新变量
	b, c, d := false, "smallming", 3
	fmt.Println(a, b, c, d)
}
① 变量作用域
  • 变量声明位置决定了变量的可访问范围(哪里能调用到变量)

  • Go语言中变量的有效范围如下

    • 函数级别:变量声明在函数内部,只有在函数内部才能访问,称变量为局部变量

    • package 包级别,在当前包下都可以访问.称变量为全局变量.变量声明在函数外面

    • 应用级别,在整个应用下任何包内都可以访问.通过首字母大小写控制

② 局部变量
  • 局部变量一定是在函数内部

  • 在哪个{}内部声明,只能在哪个{}内部访问

func test1() {
    i := 2 //从此处开始到test1结束}任何位置都能调用i
    if i>=2{
        j:=3
        fmt.Println(i+j)//此处可以访问i
    }
    fmt.Println(i)
    //fmt.Println(j)//此处不能调用j,超出声明j时{}外
}
func test2() {
    fmt.Println(i) //此处无法调用test1()中的i
}
③ 全局变量
  • 全局变量声明到函数外部,整个包都可以访问

  • 如果全局变量首字母大写,跨包也可以访问.

  • 声明全局变量时规范是

var (
    变量名
    变量名=值
)
  • 全局变量代码示例

var (
    name = "smallming"
    age  = 17
)
​
func demo1() {
    fmt.Println("名字:",name)
}
​
func demo2() {
    fmt.Println("年龄:",age)
}

 

四.数组的创建和赋值
  • 可以在声明数组时可以同时给数组赋值,赋值时要求长度必须大于等于初始值个数

  //方式一:完整写法
    var arr [3]int = [3]int{1, 2, 3}
    //方式二:短变量方式
    arr2 := [3]int{1, 2, 3}
    //方式三:长度大于初始值个数.长度为4,只给前三个元素赋值,其余元素为默认值
    arr3 := [4]int{1, 2, 3}
    //方式四:赋值时不写长度,数组长度根据元素个数确定
    arr4 := [...]int{1, 2, 3}
  • 可以通过:数组名[脚标]对数组中元素进行操作

     arr := [3]int{1, 2, 3}
​
    fmt.Println(arr)
    //通过脚标对数组中元素进行重新赋值
    arr[0] = 5
    arr[1] = 6
    arr[2] = 7
    fmt.Println(arr[0], arr[1], arr[2])
  • 通过len(数组变量)获取数组长度,数组脚标最大值为长度减一,如果超出这个范围将会报错

    arr := [3]int{1, 2, 3}
    fmt.Println(len(arr))//输出:3
    arr[3]=5//错误信息:invalid array index 3 (out of bounds for 3-element array)
  4.1、数组是值类型
  • 在Go语言中数组是值类型,和之前学习的int或float64等类型相同,把一个数组变量赋值给另一个数组变量时为复制副本,重新开辟一块空间

  • 使用==比较数组中值是否相等

    arr := [3]int{1, 2, 3}
    arr2:=arr
    fmt.Println(arr,arr2)
    fmt.Printf("%p %p",&arr,&arr2)//地址不同
    fmt.Println(arr==arr2)

 

4.2、切片
  • 切片的英文名称slice

  • 切片:具有可变长度相同类型元素序列.

  • 由于长度是可变,可以解决数组长度在数据个数不确定情况下浪费内存的问题.

  • 切片和数组声明时语法最主要的区别就是长度

    var slice []string  //切片
    var array [3]string //数组
  • 切片只声明时为nil,没有开辟内存空间,不能直接操作切片,需要先初始化

    • 注意:切片只能和nil进行判断是否相等

    var slice []string  //切片
    fmt.Println(slice==nil)//输出:true
    fmt.Printf("%p",slice)//输出:0x0
4.3、定义切片
  • 通过直接指定初始值定初始化一个切片变量

    names := []string{"smallming", "佳明哥"}
    fmt.Println(names)
切片对象[脚标]
4.4、切片是引用类型
  • 引用类型在变量之间赋值时传递的是地址.引用类型变量就是这个类型的指针.切片就是引用类型

  • 值类型在变量之间赋值时传递的是值的副本

    names := []string{"smallming", "佳明哥"}
    names1 := names
    names1[0] = "张"
    fmt.Println(names, names1)//输出:[张 佳明哥] [张 佳明哥]
    fmt.Printf("%p %p",names,names1)//地址相同
 4.5、数组的遍历

关键字 range 会返回两个值,第一个值是当前迭代到的索引位置,第二个值是该位置对应元素值的一份副本

	array:=[]string{"hello","hi","heihei"}
	//使用for循环遍历
	for  a:=0;a<len(array);a++  {
		fmt.Println(a,array[a])
	}
    //使用range循环遍历
	for key,value:=range array{
		fmt.Println(key,value)
	}
  1、make函数
make(类型,初始长度[,初始容量])
    slice := make([]string, 0)     //长度为0的切片,没有第三个参数表示容量和长度相等
    slice1 := make([]string, 0, 2) //长度为0,容量为2
    fmt.Println(slice, slice1)
  • 长度表示切片中元素的实际个数,容量表示切片占用空间大小,且切片容量成倍增加.当增加到1024后按照一定百分比增加.

    • len(slice) 查看切片的长度

    • cap(slice) 查看切片的容量

    slice := make([]string, 0)     //长度为0的切片,没有第三个参数表示容量和长度相等
    slice1 := make([]string, 0, 3) //长度为0,容量为2
    fmt.Println(len(slice), cap(slice))
    fmt.Println(len(slice1), cap(slice1))
 2、append()函数
  • append()在Go语言标准库中源码如下

// The append built-in function appends elements to the end of a slice. If
// it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
//  slice = append(slice, elem1, elem2)
//  slice = append(slice, anotherSlice...)
// As a special case, it is legal to append a string to a byte slice, like this:
//  slice = append([]byte("hello "), "world"...)
func append(slice []Type, elems ...Type) []Type
  • 可以向切片中添加一个或多个值,添加后必须使用切片接收append()函数返回值

    s := make([]string, 0)
    fmt.Println(len(s), cap(s))//输出:0 0
    s = append(s, "老张", "佳明哥")
    fmt.Println(len(s), cap(s))//输出:2 2
    s = append(s, "smallming")
    fmt.Println(len(s), cap(s))//输出:3 4
  • 如果添加一次添加多个值,且添加后的长度大于扩容一次的大小,容量和长度相等.等到下次添加内容时如果不超出扩容大小,在现在的基础上进行翻倍

    s := make([]string, 0)
    fmt.Println(len(s), cap(s)) //输出:0 0
    s = append(s, "老张", "佳明哥")
    fmt.Println(len(s), cap(s)) //输出:2 2
    s = append(s, "smallming")
    fmt.Println(len(s), cap(s)) //输出:3 4
    s = append(s, "4", "5", "6", "7", "8", "9")
    fmt.Println(len(s), cap(s)) //输出:9 9
    s = append(s,"10")
    fmt.Println(len(s), cap(s)) //输出:10 18
  • 也可以把一个切片的内容直接添加到另一个切片中.需要注意语法中有三个点

    s := make([]string, 0)
    s1 := []string{"smallming", "佳明哥"}
    s = append(s, s1...) //注意此处,必须有三个点
    fmt.Println(s)

 

4.5、通过数组产生切片
  • 定义数组后,取出数组中一个片段,这个片段就是切片类型,(names[0:]->表示从脚标0开始到最后一个结束,当结束脚标为最后一个脚标时可省略最后一个脚标)

    names := [3]string{"老张", "佳明哥", "smallming"}
    s := names[0:2]     //包前不包后 注意此处数组内是0:n格式
    s1 := names[0:]    
    fmt.Printf("%T", s) //输出:[]string
    fmt.Println(s)      //输出:[老张 佳明哥]
    fmt.Println(s1)      //输出:[老张 佳明哥 smallming]
  • 切片是指针,指向数组元素地址,修改切片的内容,数组的内容会跟随变化

    names := [3]string{"老张", "佳明哥", "smallming"}
    s := names[0:2] //包前不包后
    fmt.Printf("%p %p",s,&names[0])//输出的地址是相同的
    s[0] = "Go语言"
    fmt.Println(s)     //输出:[Go语言 佳明哥]
    fmt.Println(names) //输出:[Go语言 佳明哥 smallming]
  • 当切片内容在增加时

    • 如果增加后切片的长度没有超出数组,修改切片也是在修改数组

    • 如果增加后切片的长度超出数组,会重新开辟一块空间放切片的内容

    • 通过下面代码也正面了切片中内容存在一块连续空间(和数组一样)

    names := [3]string{"老张", "佳明哥", "smallming"}
    s := names[0:2] //包前不包后
    fmt.Printf("%p %p\n",s,&names[0])
    s[0] = "Go语言"
    s=append(s,"区块链")
    fmt.Println(s)     //输出:[Go语言 佳明哥 区块链]
    fmt.Println(names) //输出:[Go语言 佳明哥 区块链]
    fmt.Printf("%p %p\n",s,&names[0])//地址相同
​
    s=append(s,"超出了数组长度")
    fmt.Println(s)     //输出:[Go语言 佳明哥 区块链 超出了数组长度]
    fmt.Println(names) //输出:[Go语言 佳明哥 区块链]
    fmt.Printf("%p %p\n",s,&names[0])//切片地址改变
4.6、删除实现
  • Go语言标准库中没有提供删除的函数

  • 切片也可以取其中的一段形成子切片,利用这个特性可以实现删除效果

    num := []int {0,1,2,3,4,5,6}
    //要删除脚标为n的元素
    n:= 2
    num1 :=num[0:n]
    num1= append(num1,num[n+1:]...)
    fmt.Println(num1)

 

package main

import (
	"fmt"
	"strings"
)

func main() {
	var s string="smallming";
    //创建切片,切片是引用类型,声明之后并未分配空间,
	var split []string;
    fmt.Printf("%p",split)    //0x0 地址是空的
	fmt.Println(split1==nil)   //true
	split= strings.Split(s, "m");
	fmt.Println(split)
	sprintf := fmt.Sprintf("%s", strings.Split(s, "m"));
	fmt.Println("接收到的数组为"+sprintf)
	fmt.Println(strings.Join(split, "m"))

}

 

五. map
  • map以散列表方式存储键值对集合

  • map中每个元素都是键值对

map[key]Value
  • key是操作map的唯一标准.可以通过key对map中元素进行增加/删除/修改/查看

  • key是唯一的,添加重复的key会覆盖之前的元素.

  • map是值类型,只声明时为空指针(nil)

    var m map[string]int
    fmt.Println(m == nil) //输出:true
    fmt.Printf("%p", m)   //输出:0x0
  • map读写数据时并不是并发安全的,可以结合RWMutex保证并发安全(RWMutex在后面讲解)

5.1、实例化map的几种方式
  • 使用make函数实例化一个没有初始值的map

    m := make(map[string]string)
    fmt.Println(m==nil)//输出:false
    fmt.Printf("%p", m)//输出:内存地址
  • 可以在声明map时直接给map赋初始值.注意初始值在一行和在多行写时的语法区别

    • map中元素键值对语法满足: key:value

    • key和value的类型必须和map[key]value类型严格对应

    m := map[string]string{"name": "smallming", "address": "北京海淀"}
    m1 := map[string]string{
        "name":     "smallming",
        "addresss": "北京海淀",
    }
    fmt.Println(m, m1)
5.2、操作map中的元素
  • 使用key判断,如果key不存在向map中新增数据,如果key存在会覆盖map中元素

    m := make(map[string]int)
    m["money"] = 5
    fmt.Println(m) //输出:map[money:5]
    m["money"] = 6
    fmt.Println(m) //map[money:6]
  • Go语言标准库中提供了对map元素删除的函数,使用顶层delete()即可完成删除

    • 如果key存在执行删除元素

    • 如果key不存在,map中内容不变,也不会有错误

    m := make(map[string]int)
    m["money"] = 5
    delete(m, "没有的key")
    fmt.Println(m) //输出:map[money:5]
    delete(m, "money")
    fmt.Println(m) //输出:map[]
  • 获取map中指定key对应的值

    • 使用:map变量[key]获取key对应的值

    • 如果key不存在返回map[key]Value中Value类型的默认值.例如:Value是string类型就返回""

    • 返回值可以是一个,也可以是两个.

      • 一个表示key对应的值

      • 两个分别表示:key对应的值和这个key是否存在

    m := map[string]string{"name": "smallming", "address": "北京海淀"}
    fmt.Println(m["name"]) //输出:smallming
    fmt.Println(m["age"])  //输出:空字符串
    value, ok := m["age"]
    fmt.Println(value, ok) //输出:空字符串 false
  • 如果希望把map中所有元素都遍历,可以使用for结合range实现

    hello:=map[string]int{"hello1":1,"hello2":2};
	fmt.Println(hello["hello1"])
	fmt.Println(hello["hello2"])
	for key,value:=range hello{
		fmt.Println(key,value)
	}
六.双向链表概述
  • 双向链表结构如下

  •  

  • 双向链表结构中元素在内存中不是紧邻空间,而是每个元素中存放上一个元素和后一个元素的地址

    • 第一个元素称为头(head)元素,前连接(前置指针域)为nil

    • 最后一个元素称为尾(foot)元素,后连接(后置指针域)为nil

  • 双向链表的优点:

    • 在执行新增元素或删除元素时效率高,获取任意一个元素,可以方便的在这个元素前后插入元素

    • 充分利用内存空间,实现内存灵活管理

    • 可实现正序和逆序遍历

    • 头元素和尾元素新增或删除时效率较高

  • 双向链表的缺点

    • 链表增加了元素的指针域,空间开销比较大

    • 遍历时跳跃性查找内容,大量数据遍历性能低

6.1、 双向链表容器List
  • 在Go语言标准库的container/list 包提供了双向链表List

  • List结构体定义如下

    • root表示根元素

    • len表示链表中有多少个元素

// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List struct {
    root Element // sentinel list element, only &root, root.prev, and root.next are used
    len  int     // current list length excluding (this) sentinel element
}
  • 其中Element结构体定义如下

    • next表示下一个元素,使用Next()可以获取到

    • prev表示上一个元素,使用Prev()可以获取到

    • list表示元素属于哪个链表

    • Value表示元素的值,interface{}在Go语言中表示任意类型

  // Element is an element of a linked list.
  type Element struct {
    // Next and previous pointers in the doubly-linked list of elements.
    // To simplify the implementation, internally a list l is implemented
    // as a ring, such that &l.root is both the next element of the last
    // list element (l.Back()) and the previous element of the first list
    // element (l.Front()).
    next, prev *Element
​
    // The list to which this element belongs.
    list *List
​
    // The value stored with this element.
    Value interface{}
  }
6.2、操作List
  • 直接使用container/list包下的New()新建一个空的List, list与切片、Map不一样,没有具体元素类型的限制。
    在java、c++等里面,list的成员必须是同一个数据类型,但是Go语言中却允许list里插入任意类型成员。
    建议使用New()实现list。

    mylist := list.New()
    fmt.Println(mylist)       //输出list中内容
    fmt.Println(mylist.Len()) //查看链表中元素的个数
    fmt.Printf("%p", mylist)  //输出地址
  • Go语言标准库中提供了很多向双向链表中添加元素的函数

    //添加到最后,List["a"]
    mylist.PushBack("a")
    //添加到最前面,List["b","a"]
    mylist.PushFront("b") 
    //向第一个元素后面添加元素,List["b","c","a"]
    mylist.InsertAfter("c", mylist.Front()) 
    //向最后一个元素前面添加元素,List["b","c","d","a"]
    mylist.InsertBefore("d", mylist.Back()) 
  • 取出链表中的元素

    fmt.Println(mylist.Back().Value)  //最后一个元素的值
    fmt.Println(mylist.Front().Value) //第一个元素的值
​
    //只能从头向后找,或从后往前找,获取元素内容
    n := 5
    var curr *list.Element
    if n > 0 && n <= mylist.Len() {
        if n == 1 {
            curr = mylist.Front()
        } else if n == mylist.Len() {
            curr = mylist.Back()
        } else {
            curr = mylist.Front()
            for i := 1; i < n; i++ {
                curr = curr.Next()
            }
        }
    } else {
        fmt.Println("n的数值不对")
    }
    //遍历所有值
    for e := mylist.Front(); e != nil; e = e.Next() {
        fmt.Println(e.Value)
    }
  • 移动元素的顺序

    mylist.MoveToBack(mylist.Front()) //把第一个移动到后面
    mylist.MoveToFront(mylist.Back()) //把最后一个移动到前面
    mylist.MoveAfter(mylist.Front(),mylist.Back())//把第一个参数元素,移动到第二个参数元素后面
    mylist.MoveBefore(mylist.Front(),mylist.Back())//把第一个参数元素,移动到第二个参数元素前面
  • 删除元素

mylist.Remove(mylist.Front())

 

	mylist := list.New()
	mylist.PushBack(1)
	mylist.PushBack("hello")
	mylist.PushBack(2.0)
	for e:=mylist.Front();e!=nil;e=e.Next() {
		fmt.Println(e.Value)
	}
    // 1
    // hello
    // 2.0   *go语言中list中可以存放不同类型的数据
    
一.双向循环链表
  • 循环链表特点是没有节点的指针域为nil,通过任何一个元素都可以找到其他元素

  • 环形链表结构如下

 
  • 双向循环链表和双向链表区别

    • 双向循环链表没有严格意义上的头元素和尾元素

    • 没有元素的前连接和后连接为nil

    • 一个长度为n的双向循环链表,通过某个元素向某个方向移动,在查找最多n-1次后一定会找到另一个元素

    • 双向循环链表,默认第一元素为头元素,头元素即代表链表中的一个元素有代表整个双向循环链表,并且头元素是不可以被删除和覆盖的,因为它既是元素又代表链表。

二.Go语言中的双向循环链表
  • 在container/ring包下结构体Ring源码如下

    • 官方明确说明了Ring是循环链表的元素,又是环形链表.

    • 实际使用时Ring遍历就是环形链表第一个元素

// A Ring is an element of a circular list, or ring.
// Rings do not have a beginning or end; a pointer to any ring element
// serves as reference to the entire ring. Empty rings are represented
// as nil Ring pointers. The zero value for a Ring is a one-element
// ring with a nil Value.
//
type Ring struct {
    next, prev *Ring
    Value      interface{} // for use by client; untouched by this library
}
  • Go语言标准库中对container/ring包提供的API如下

    type Ring
        //实例化长度为n的环形链表
        func New(n int) *Ring
        //长度
        func (r *Ring) Len() int
        //下一个元素
        func (r *Ring) Next() *Ring
        //上一个元素
        func (r *Ring) Prev() *Ring
        //移动n次,支持负数
        func (r *Ring) Move(n int) *Ring
        //合并s和r
        func (r *Ring) Link(s *Ring) *Ring
        //删除r后面n%r.Len()元素,删除多个,当前元素前面的不删除
        func (r *Ring) Unlink(n int) *Ring
        //循环遍历,i是当前元素的值
        func (r *Ring) Do(f func(interface{}))
三.代码演示
  • 实例化、赋值、遍历

package main

import (
	"container/ring"
	"fmt"
)

func main() {
	//代表整个循环链表,又代码第一个元素
	r:=ring.New(5)
	r.Value = 0              //0即是头元素又代表整个链表,通过r.Value为链表元素进行赋值。
	r.Next().Value =1        //向后赋值
	r.Next().Next().Value= 2
	//r.Next().Next().Next().Value=3
	//r.Next().Next().Next().Next().Value=4
	r.Prev().Value=4        //向前赋值,由于循环链表是一个环状结构,所以重复的链表脚标即覆盖       
	r.Prev().Prev().Value=3

	//fmt.Println(r.Move(-2).Value)  //Move源码中调用的就是根据Move(参数a)中a是否大于0而调    
                                     //用r.Next()和r.Prev()

	//增加
	//r1:=ring.New(1)
	//r1.Value=5
	//r.Next().Link(r1)              //通过Link向原链表中添加新的元素,默认天加到头元素即第 
                                     //一个创建的元素后,r.Next().Link即添加到首元素下一个 
                                     //元素后

	//删除
	//参数n取值为n%r.len()     
	r.Unlink(1)                    //通过Unlink方法,将链表中头元素后边的n个元素删除。此处是 
                                   //删除了头元素后边的第一个元素即元素值为1的元素。
                                   //虽然是循环链表但是不允许删除头元素和头元素之前的元素。
	//循环链表有几个元素,func执行几次,i代表当前执行元素的内容,遍历链表所有元素
	r.Do(func(i interface{}) {
		fmt.Println(i)
	})
}
  • 实例化后的r就是链表中第一个创建的元素.可以找到元素的前后元素

    fmt.Println(r.Next().Value)//输出:1
    fmt.Println(r.Next().Next().Value)//输出:2
    fmt.Println(r.Next().Next().Next().Value)//输出:0
    fmt.Println(r.Move(-1).Value)//输出:2
    fmt.Println(r.Prev().Value)//输出:2
  • 可以向环形链表添加或删除链表

    s := ring.New(1)
    s.Value = 13
    //r是哪个元素,就把新的链表添加到哪个元素后面
    r.Link(s)
    r.Do(func(i interface{}) {
        fmt.Print(i, " ")
    })
    fmt.Println("")
    //从r元素向后,n/r.Len()个元素被删除,当前元素和前面的保留
    r.Unlink(1)
    r.Do(func(i interface{}) {
        fmt.Print(i, " ")
    })