Golang Map、List、slice切片操作

Map

Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

定义 Map

可以使用内建函数 make 也可以使用 map 关键字来定义 Map:

/* 声明变量,默认map是nil */
var map_variable = map[key_data_type]value_data_type

/* 使用make函数 */
map_variable := make(map[key_data_type]value_data_type)

如果不初始化map,name就会创建一个nil map。nil map不能用来存放键值对。

实例

下面的实例演示了创建和使用map:

package main

import "fmt"

func main() {
	var likeMap map[string]string;	// 创建集合
	likeMap = make(map[string]string)

	/* map插入key-value对,每个男生喜欢的女生 */
	likeMap[ "周星驰" ] = "朱茵";
	likeMap[ "周杰伦" ] = "昆凌";
	likeMap[ "邓超" ] = "孙俪";

	/* 通过key便利map */
	for key := range likeMap {
		fmt.Println(key, "xihuandeshi1", likeMap[key])
	}

	/* 查看集合在元素中是否存在 */
	value, ok := likeMap[ "zifeiy" ];
	if (ok) {
		fmt.Println("zifeiy喜欢的女孩是", value)
	} else {
		fmt.Println("zifeiy没有喜欢的女孩")
	}
}
/**
以上实例运行结果是:
周星驰 xihuandeshi1 朱茵
周杰伦 xihuandeshi1 昆凌
邓超 xihuandeshi1 孙俪
zifeiy没有喜欢的女孩*/

delete()函数

delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key。实例如下:

package main

import "fmt"

func main() {
	/* 创建map */
	likeMap := map[string]string {"周星驰": "朱茵", "周杰伦": "昆凌", "邓超": "孙俪", "zifeiy": "刘亦菲"}
	fmt.Println("原始喜欢:")
	/* 打印喜欢 */
	for boy := range likeMap {
		fmt.Println(boy, "喜欢的是", likeMap[boy])
	}
	/* 删除元素 */
	delete(likeMap, "zifeiy")
	fmt.Println("删除元素后喜欢:")
	/* 打印喜欢 */
	for boy := range likeMap {
		fmt.Println(boy, "喜欢的是", likeMap[boy])
	}
}

/***
输出结果如下:
原始喜欢:
周星驰 喜欢的是 朱茵
周杰伦 喜欢的是 昆凌
邓超 喜欢的是 孙俪
zifeiy 喜欢的是 刘亦菲
删除元素后喜欢:
周星驰 喜欢的是 朱茵
周杰伦 喜欢的是 昆凌
邓超 喜欢的是 孙俪

**/

List:

列表有很多种实现方法,比如 单链表  双链表等

golang中 列表container/list   内部的实现原理是双链表

创建方式:

listName := list.New()

var listName = list.List

插入普通元素

PushFront  PushBack  InsertBefore  InsertAfter

    var l1 list.List  // l1 := list.New() 一样的效果
	a := l1.PushBack("111")
	b := l1.InsertBefore(100,a)
	l1.InsertAfter("2222",b)
    l1.PushFront(200)
	for i:=l1.Front();i != nil;i = i.Next() {
		fmt.Println("l1 Element =", i.Value)
	}
/**
l1 Element = 200
l1 Element = 100
l1 Element = 2222
l1 Element = 111
**/

列表除了支持插入元素外,还支持插入列表。。。

在尾部插入列表语法:PushBackList(other *List)

    l2 := list.New()
	l2.PushFront("3333")
	l2.PushBack("444444")
	l1.PushBackList(l2)
    for i:=l1.Front();i != nil;i = i.Next() {
		fmt.Println("Element =", i.Value)
	}
 
/*
Element = 200
Element = 100
Element = 2222
Element = 111
Element = 3333
Element = 444444
*/

 在头部插入列表语法:PushFrontList(other *List)

    l3 := list.New()
	l3.PushBack("a3")
	l3.PushFront("a31")
	l3.PushFrontList(l2)
	for i:=l3.Front();i != nil;i = i.Next() {
		fmt.Println("Element =", i.Value)
	}
/*
Element = 3333
Element = 444444
Element = a31
Element = a3
*/

列表删除元素

    l4 := list.New()
	a = l4.PushBack("a")
	b = l4.PushFront("b")
	value := l4.Remove(a)
	fmt.Println(value)
	for i:=l4.Front();i != nil;i = i.Next() {
		fmt.Println("Element =", i.Value)
	}
/*
a
Element = b
*/

list.Remove(nil)   删除空是有问题的!!!!!!!

列表遍历

    for i := l1.Front();i!=nil;i = i.Next(){
		fmt.Println("front--->back:",i.Value)
	}
	fmt.Println("###########################")
	for i := l1.Back();i!= nil;i = i.Prev(){
		fmt.Println("back--->front:",i.Value)
	}
 
/*
   front--->back: 200
front--->back: 100
front--->back: 2222
front--->back: 111
front--->back: 3333
front--->back: 444444
###########################
back--->front: 444444
back--->front: 3333
back--->front: 111
back--->front: 2222
back--->front: 100
back--->front: 200
*/

列表元素移动

移到元素前

MoveBefore(e, mark *Element)  把e移动到makr 之前。。

如果将指定元素移动到另一元素的前面中的指定元素本来就在另一元素的前面,那么列表不会做任何的改动,或者如果指定元素不是列表中的元素,列表也不会做任何改动。

    l5 := list.New()
	a = l5.PushBack("a")
	b = l5.PushBack("b")
	l5.MoveBefore(b,a)
	for i := l5.Front();i!= nil;i = i.Next(){
		fmt.Println("l1:",i.Value)
	}
/*
b
a
*/

移到元素后  MoveAfter(e, mark *Element)

移到列表最前  MoveToFront(e *Element)

移到列表最后 MoveToBack(e *Element)

获取节点

Front() *Element

Back () *Element

Prev() *Element

Next() *Element

获取长度

Len() int

    //list to  []string
	var str []string
	for e := listName.Front(); e != nil; e = e.Next() {
		str = append(str, fmt.Sprint(e.Value))
	}
	//[]string to string
	c := strings.Join(str, ",")
	fmt.Println("string =", c)

	//string to []string
	res := strings.Split(c, ",")
	fmt.Println("res=", res)

切片 slice 

是Golang中数组之上的抽象;可按需自动增长与缩小。切片的底层是连续的内存块。

创建
切片本身是一个三个字段的数据结构:

Data:指向底层数组的指针;
Len:切片中元素的个数;len(s)获取;
Cap:切片的容量(不需重新分配内存前,可容纳的元素数量);cap(s)获取;

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

创建与初始化

有多种创建切片的方式。

make函数创建

内置的make函数可用于创建切片(长度不能超过容量):

// 创建长度为0,容量为10的切片
s := make([]int, 0, 10)
 
// 创建长度为10(存储的是对应类型的默认值,即0),容量为10的切片
s2 := make([]int, 10)
 
// 获取底层对应数据结构
low:=(*reflect.SliceHeader)(unsafe.Pointer(&s1))
fmt.Printf("Data:%d,Len:%d,Cap:%d\n",low.Data,low.Len,low.Cap)

初始化创建

根据给定的数据,自动创建并初始化切片(与数组的区别是[]中没有长度信息):

// 长度、容量都为3的切片
s := []int{1,2,3}
 
// 长度、容量都为10,最后一个元素为99,其他为默认值0
s2 := []int{9: 99}

空切片与nil

声明时不做任何初始化就会创建一个nil切片;而空切片是底层数组包含0个元素,且没有分配存储空间。nil与空切片都可直接获取长度(len)或追加元素(append)。

// 值为nil的切片
var s []int
 
// 创建空切片
s1 := make([]int, 0)
s2 := []int{}

创建共享底层数组的切片
在已有数组/切片上创建切片为:s = src[low : high : max] ,三个参数意义为

low为截取的起始下标(含), high为窃取的结束下标(不含high),max为切片保留的原切片的最大下标(不含max);
新切片从老切片的low下标元素开始,len = high - low, cap = max - low;high 和 max一旦超出在老切片中越界,就会发生runtime err,slice out of range。
没有指定max:max的值为截取对象(数组、切片)的容量。
在已有数组/切片上切出一部分可以生成新的切片,他们共享底层的数组;因此修改一个切片的内容(在不扩容的情况下),会影响到其他共享底层数组的切片:
 

// 元素范围为[i,j),k限制容量为(k-i)
s := src[i:j:k]
 
// 从i到尾部
s := src[i:]
 
// 从头到j(不包括j)
s := src[:j]
 
// 从头到尾(复制整个切片),但底层数组还是公用的
s := src[:]

访问

切片只能访问其长度范围内的内容,通过下标访问

s[i] = 10
v = s[i]

迭代

切片是一个集合,可以通过range迭代其中的元素:

for index, value := range myS{
    fmt.Printf("index: %d, value: %d\n", index, value)
}

range返回的第二个值是对应元素的一份副本,不能用于修改;若要修改则需要通过索引:

for index, _ := range myS{
    myS[index] += 1
}

迭代方式遍历时,不能有其他线程对切片进行操作(添加、或删除元素),否则会引发恐慌。

不定参数传递

切片可直接传递给‘不定参数’,即使空切片也可以:

func NilSliceParam() {
	var s []string
	multiEle(s...)
}
 
func multiEle(all ...string) {
	log.Println(all)
}

操作

通过函数append可在切片尾部追加元素;通过copy(dest,src)可复制切片,复制的长度是minimum of len(src) and len(dst)。

可对nil切片追加元素,以及求长度:

var sl []int            // nil
if sl == nil{    // true
    fmt.Println("is nill")
}
fmt.Println(sl)         // []
fmt.Println(len(sl))    // 0
sl = append(sl, 1)      // [1]

扩容

Slice依托数组实现,底层数组容量不足时可自动重新分配。追加数据通过append(返回值一定要再赋值给原slice);容量不足时会自动扩容:

  • <1024时,扩大为原来2倍;
  • >=1024时,扩大为原来的1.25倍;
    var s []int // s == nil
    fmt.Println(s) // []
    fmt.Println(len(s)) // 0
    for i := 0; i < 5; i++ {
        s = append(s, i+1)
    }
    // 容量:0,1,2,4,8

插入元素

尾部插入

在插入元素时,若空间不足则会自动重新分配新的空间:

a = append(a, 1, 2, 3)
a = append(a, []int{1,2,3}...) // 追加切片时,需要使用...解包

头部插入

头部插入时,会引起内存的分配与复制操作:

// 即使一个元素,也要以切片的形式插入
a = append([]int{1}, a...)
a = append([]int{1,2,3}, a...)

中间插入

在中间(i)插入元素:

  • 先通过append增长空间;
  • 使用copy移动后面元素;
  • 插入元素到指定位置
a = append(a, x...)        // 先扩展出足够的空间
copy(a[i+len(x):], a[i:])  // 后移len(x)个元素,空出空间
copy(a[i:], x)

删除元素

从切片中删除的元素若是指针(特别是尾部删除时,可能还会被底层数组引用中,从而无法被gc回收);为了能及时的释放,在需要在删除前,先把对应元素设置为nil再删除;

a[len(a)-1]=nil
a = a[:len(a)-1]

尾部删除

删除尾部N个元素:

a = a[:len(a)-N]

头部删除

删除头部元素有两种方式:

  • 改变数据指针(直接通过切片操作即可)
  • 不改变数据指针(移动元素)
// 删除头部N个元素,改变数据指针
a = a[N:]
 
// 删除切片头部元素,不改变数据指针:
a = append(a[:0], a[N:]...)  // 在头部追加剩余元素
a = a[:copy(a, a[N:])]       // 复制元素都头部,然后修改切片长度

中间删除

删除切片中间部分元素:

// 方法1:append追加,覆盖被删除元素
a = append(a[:i], a[i+N:]...)
 
// 方法2:复制,覆盖被删除元素
a = a[:i+copy(a[i:], a[i+N:])]

遍历时删除

要在遍历时删除,就不能用range迭代的方式:

func removeAllLessEle(queueMax []int, ele int) []int {
	// pop all ele less than ele
	for j := 0; j < len(queueMax); {
		if queueMax[j] < ele {
			queueMax = append(queueMax[:j], queueMax[j+1:]...)
		} else{
			j = j + 1
		}
	}
	return queueMax
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值