五.组合数据类型

news2024/11/16 15:37:52

目录

1、数组类型

声明数组

初始化数组

数组赋值

访问数组元素

2、切片类型

1、定义切片

2、切片初始化

3、访问

4、空(nil)切片

5、切片的增删改查操作:

3、指针类型

1、什么是指针

2、如何使用指针、指针使用流程:

3、Go 空指针

4、指针作为函数参数

4、指针数组

二级指针

内存分配原则

4、map类型

1、定义声明 Map

2、常规操作:

1.map 增加和更新

2.map 删除

3.map 查找


1、数组类型

Go 语言提供了数组类型的数据结构。

数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的

原始类型例如整型、字符串或者自定义类型。相对于去声明number0, number1, ..., and number99 的变量,使用数组形式numbers[0], numbers[1] ..., numbers[99]更加方便且易于扩展。数组元素可以通过索引(位置)来读取(或者修改),索引从0开始,

第一个元素索引为 0,第二个索引为 1,以此类推。

使用数组的步骤

1. 声明数组并开辟空间,初始化数组。

2. 给数组各个元素赋值(默认零值)

3. 使用数组 ==>读数组,写数组

声明数组

Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:

var array_name [SIZE]array_type => var a [10]int

c语言的数组:

array_type array_name[size]; => int a [10];

以上为一维数组的定义方式。数组长度SIZE必须是整数且大于0。

数组的下标是从 0 开始的,数组下标必须在指定范围内使用,否则报 panic

数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。

数组创建后,如果没有赋值,有默认值(零值)

数值类型数组:默认值为 0 , 字符串数组: 默认值为 " " , bool 数组: 默认值为 false。

1、声明数组并开辟空间

//例如以下定义了数组 f 长度为 10,类型为 float32:

var f [10] float32

// 定义一个长度为3元素类型为int的数组a

var a [3]int

在没有明确内存存储位置的时候,默认在栈区。

其他声明方式:

[32] byte //长度为32的字节数组

[2*N] struct {x, y int32} //复杂类型数组

[100] *float64 //指针数组

[3][5] int //二维数组

[2][2][2] float64 //等同于[2]([2]([2]float64))

注意:数组的长度必须是常量,并且长度是数组类型的一部分。

一旦定义,长度不能变。 [5]int 和[10]int 是不同的类型。

1)数组的地址可以通过数组名来获取 &intArr

2)数组的第一个元素的地址,就是数组的首地址

3)数组的各个元素的地址间隔是依据数组的类型决定,比如 int64 -> 8 int32->4...

初始化数组

方法1:

初始化数组时可以使用初始化列表来设置数组元素的值。

func main() {

var testArray [3]int //数组会初始化为int类型的零值

var numArray = [3]int{1,2} //使用指定的初始值完成初始化

var cityArray = [3]string{"Beijing", "Shanghai", "Guangzhou"}

fmt.Println(testArray) //[0 0 0]

fmt.Println(numArray) //[1 2 ]

fmt.Println(cityArray) //[Beijing Shanghai Guangzhou]

}

方法2:

让编译器根据初始值的个数自行推断数组的长度:

func main() {

var testArray [3]int //数组会初始化为int类型的零值

var numArray = [...]int{1, 2} //使用指定的初始值完成初始化

var cityArray = [...]string{"Beijing", "Shanghai", "Guangzhou"}

fmt.Println(testArray) //[0 0 0]

fmt.Println(numArray) //[1 2 0]

fmt.Printf("type of numArray:%T\n", numArray) //type of

numArray:[2]int

fmt.Println(cityArray) //[Beijing Shanghai

Guangzhou]

fmt.Printf("type of numArray:%T\n", cityArray) //type of numArray:

[3]string

}

方法3:

使用指定索引值的方式初始化数组:

func main() {

a := [...]int{1: 1, 3: 5} //第一个1是下标,第二个1 是值, 3是下

标,5是值

fmt.Println(a) // [0 1 0 5]

fmt.Printf("type of a:%T\n", a) //type of a:[4]int

}

数组赋值

Go 语言的数组的赋值,即给定义好的数组指定的索引的位置设置对应的

值。

Go 语言数组赋值语法:

arr[index] = value

给定义好的数组的指定索引位置处赋值

package main

import (

"fmt"

)

func main() {

//给定义好的数组的指定索引位置处赋值

var arrHaiCoder [3]string //定义数组,没有初始化,默认是""



arrHaiCoder[0] = "Hello" //给数组元素赋值

arrHaiCoder[1] = "中华网"

arrHaiCoder[2] = "China"



fmt.Println("arrHaiCoder0 =", arrHaiCoder[0]) //数组元素的输出

fmt.Println("arrHaiCoder1 =", arrHaiCoder[1])

fmt.Println("arrHaiCoder2 =", arrHaiCoder[2])

}

数组重新赋值

数组指定索引的位置有值后,也可以通过索引重新设置值

package main

import (

"fmt"

)

func main() {

//数组指定索引的位置有值后,也可以通过索引重新设置值

var arrHaiCoder = [3]string{"Hello", "中华网", "China"} //初始化列

表初始化数组

arrHaiCoder[2] = "Openlab" //指定修改第三个元素

fmt.Println("arrHaiCoder0 =", arrHaiCoder[0])

fmt.Println("arrHaiCoder1 =", arrHaiCoder[1])

fmt.Println("arrHaiCoder2 =", arrHaiCoder[2])

}

注意:数组的长度必须是常量,并且长度是数组类型的一部分。

一旦定义,长度不能变。 [5]int和[10]int是不同的类型。

var a [3] int

var b [4] int

a = b //不能这样做,因为a和b是不同类型

访问数组元素

数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括

号中为索引的值。

例如:

float32 salary = balance[9]

以上实例读取了数组balance第10个元素的值。

以下演示了数组完整操作(声明、赋值、访问)的实例:

package main 
import "fmt" 
func main() { 
var n [10]int /* n 是一个长度为 10 的数组 */ 
var i,j int 
/* 为数组 n 循环赋值 */ 
for i = 0; i < 10; i++ { 
 n[i] = i + 100 /* 设置元素为 i + 100 */ 
} 
/* 输出每个数组元素的值 */ 
for j = 0; j < 10; j++ { 
 fmt.Printf("Element[%d] = %d\n", j, n[j] ) 
} 
} 

以上实例执行结果如下:

Element[0] = 100

Element[1] = 101

Element[2] = 102

Element[3] = 103

Element[4] = 104

Element[5] = 105

Element[6] = 106

Element[7] = 107

Element[8] = 108

Element[9] = 109

遍历数组的第二种方法:

func main() { 
var a = [...]string{"Beijing", "Shanghai", "Shenzhen"} 
//方法2:for range遍历 
for index, value := range a { 
 fmt.Println(index, value) //index数组下标,value值
} 
} 

课堂练习:

1、创建一个 byte 类型的 26 个元素的数组,分别 放置'A'-'Z‘。

使用 for 循环访问所有元素并打印出来。提示:字符数据运算 'A'+1 -

> 'B'

2、请求出一个数组的最大值,并得到对应的下标。

3、用for-range方法遍历数组,并请求出一个数组的所有数字之和以及平均值。

2、切片类型

Go 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,

Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,

可以追加元素,在追加时可能使切片的容量增大。

切片,这是一个在go语言中引入的新的理念。它有一些特征如下:

对数组抽象

数组长度不固定

可追加元素

切片容量可增大

1、定义切片

你可以声明一个未指定大小的数组来定义切片:

var identifier []type

切片不需要说明长度。

eg: var s []int //定义一个整形大小不定的切片,变量名称 s 。

除此之外 切片还有其他几种定义方式:

var (

a []int // nil切片,和nil相等,一般用来

表示一个不存在的切片

b = []int{} // 空切片,和nil不相等,一般用

来表示一个空的集合

c = []int{1, 2, 3} // 有3个元素的切片,len=3,

cap=3

d = c[:2] // 有2个元素的切片,len=2,

cap=3

e = c[0:2:cap(c)] // 有2个元素的切片,len=2,cap=3

详见切片初始化

f = c[:0] // 有0个元素的切片,len=0,

cap=3 详见切片初始化

g = make([]int, 3) // 有3个元素的切片,len=3,cap=3

详见切片初始化

h = make([]int, 2, 3) // 有2个元素的切片,len=2,cap=3

详见切片初始化

i = make([]int, 0, 3) // 有0个元素的切片,len=0,cap=3

详见切片初始化

)

本质上来说切片本身是一个三个字段的数据结构,详见附录长度与容量

type SliceHeader struct {

Data uintptr //指向底层数组的指针;

Len int //切片中元素的个数;len(s)获取;

Cap int //切片的容量(不需重新分配内存前,可容纳的元素

数量);cap(s)获取;

}

区分数组的声明和切片的声明方式

当使用字面量来声明切片时,其语法与使用字面量声明数组非常相似。

二者的区别是:如果在 [] 运算符里指定了一个值,那么创建的就是数组而

不是切片。

只有在 [] 中不指定值的时候,创建的才是切片

2、切片初始化

s :=[] int {1,2,3 }

直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其

cap=len=3

s := arr[:]

初始化切片s,是数组arr的引用

s := arr[startIndex:endIndex]

将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片

s := arr[startIndex:]

缺省endIndex时将表示一直到arr的最后一个元素

s := arr[:endIndex]

缺省startIndex时将表示从arr的第一个元素开始

s1 := s[startIndex:endIndex]

通过切片s初始化切片s1

s :=make([]int,len,cap)

通过内置函数make()初始化切片s,[]int 标识为其元素类型为int的切片

3、访问

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

s[i] = 10 //写操作

v := s[i] //读操作

迭代方式访问

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

1、for循环方式的迭代:

var slice = []string{"Red", "Yellow", "Blue", "Green", "Gray"}

// for 循环迭代

for i:=0; i<len(slice); i++ {

fmt.Println(i, slice[i])

}

2、range遍历

for index, value := range slice{

fmt.Printf("index: %d, value: %s\n", index, value)

}

range返回的第二个值是对应元素的一份副本,不能用于修

改;若要修改则需要通过索引:

迭代方式遍历时,不能对切片进行操作(添加、或删除元

素),否则会引发异常。

3、len() 和 cap() 函数

切片是可索引的,并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

以下为具体实例:

package main

import "fmt"

func main() {

var numbers = make([]int,3,5)

printSlice(numbers)

}

func printSlice(x []int){

fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)

}

以上实例运行输出结果为:

len=3 cap=5 slice=[0 0 0]

4、空(nil)切片

一个切片在未初始化之前默认为 nil,长度为 0,实例如下:

package main

import "fmt"

func main() {

var numbers []int

printSlice(numbers)

if(numbers == nil){

fmt.Printf("切片是空的")

}

}

func printSlice(x []int){

fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)

}

以上实例运行输出结果为:

len=0 cap=0 slice=[]

切片是空的

5、切片的增删改查操作:

1. 切片尾部新增元素

var slice []int

// 新增一个元素

slice = append(slice, 1)

// 新增多个元素

slice = append(slice, 1, 2)

// 新增多个元素, 切片作为参数,需要使用 ... 运算符来辅助解构切片

var newSlice = []int{1, 2, 3}

slice = append(slice, newSlice...) // ...不能省略

2. 切片首部新增元素

// 切片首部增加元素

var slice = []int{1, 2}

// 首部增一个元素

slice = append([]int{5}, slice...)

// 首部增多个元素

var newSlice = []int{5, 6, 7}

slice = append(newSlice, slice...)

3. 切片中间新增元素

// 切片中间某个位置插入元素

var slice = []int{1, 2, 3}

// 比如需要插入到元素索引i后, 则先以 i+1 为切割点,把 slice 切割成两

半,

// 索引 i 前数据:slice[:i+1], 索引 i 后的数据: slice[i+1:]

// 然后再把 索引 i 后的数据: slice[i:]合并到需要插入的元素切片中如:

append([]int{6, 7}, slice[i:]...)

// 最后再把合并后的切片合并到 索引 i 前数据:slice[:i]

// 如在元素索引1后增加元素

slice = append(slice[:2], append([]int{6, 7}, slice[2:]...) ...)

删除操作:

var slice = []int{1, 2, 3, 4, 5, 6}

// 从切片首部删除

slice = slice[1:]

// 从切片尾部删除2个

slice = slice[:len(slice) - 2]

// 从切片中间删除, 如从索引为i,删除2个元素(i+2)

slice = append(slice[:1], slice[3:]...)

其他操作:

// 修改元素

var slice = []int{1, 2, 3}

slice[1] = 6

// 查找元素

var slice = []int{1, 2, 3}

log.Println("slice[1]=", slice[1])

// 试图访问超出其长度的元素就会报错

a := slice[4]

log.Println(a) // runtime error: index out of range [4] with length 3

课堂练习:

使用切片的增删改查功能完成一个简单用户信息录入和维护程序,参考之 前的图书管理系统。

内存分配

3、指针类型

Go 语言指针

我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存

地址。

只需要记住以下几点:

&变量名: 获取变量的内存地址

*pointor: 通过指针获取指针对应变量的值,也称为解引用运算符

Go语言中的指针不能进行偏移和运算,只能读取指针的位置,

接下来让我们来一步步学习 Go 语言指针。

以下实例演示了变量在内存中地址:

package main 
import "fmt" 
func main() { 
 var a int = 10 
 fmt.Printf("变量的地址: %x\n", &a ) 
} 

执行以上代码输出结果为:

变量的地址: 20818a220

现在我们已经了解了什么是内存地址和如何去访问它。接下来我们将具体

介绍指针。

1、什么是指针

一个指针变量可以指向任何一个值的内存地址它指向那个值的内存地址。

类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:

var var_name *var-type

var-type 为指针类型,

var_name 为指针变量名, * 号用于指定变量是作为一个指针。

以下是有效的指针声明:

var ip *int /* 指向整型*/ var a int

var fp *float32 /* 指向浮点型 */ var b float32

本例中这是一个指向 int 和 float32 的指针。

指针的默认值或零值始终为nil。 或者,您可以说未初始化的指针将始终具

有nil值。

2、如何使用指针、指针使用流程:

• 定义指针变量。

• 为指针变量赋值。

• 访问指针变量中指向地址的值。

案例1:

package main 
import "fmt" 
func main() { 
 var a int= 20 /* 声明实际变量 */ 
 var ip *int /* 声明指针变量 */ 
 ip = &a /* 指针变量的存储地址 */ 
 fmt.Printf("a 变量的地址是: %p\n", &a ) 
 /* 指针变量的存储地址 */ 
 fmt.Printf("ip 变量的存储地址: %p\n", ip ) 
 /* 使用指针访问值 */ 
 fmt.Printf("*ip 变量的值: %d\n", *ip ) 
} 

以上实例执行输出结果为:

a 变量的地址是: 20818a220

ip 变量的存储地址: 20818a220

*ip 变量的值: 20

在Go语言中的值类型(int、float、bool、string、array、struct)都有对应

的指针类型,

如:*int、*int64、*string等。

说明一下:

Go 编译器自行决定变量分配在堆栈或堆上,以保证程序的正确性。如果一个局部变量在函数返回后仍然被使用,这个变量会从heap,而不是stack中分配内存。

3、Go 空指针

指针的五大雷区:

1.申请内存不一定成功,要做返回值判断

2.申请了不一定能用

3.用的时候不一定干净

4.用完不一定归还

5.归还后不一定能用

当一个指针被定义后没有分配到任何变量时,它的值为 nil。

nil 指针也称为空指针。

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空

值。

一个指针变量通常缩写为 ptr。

查看以下实例:

package main 
import "fmt" 
func main() { 
 var ptr *int 
 fmt.Printf("ptr 的值为 : %v\n", ptr ) 
 fmt.Printf("ptr 的值为 : %#v\n", ptr )  //加上#会把类型打印出来
} 

以上实例输出结果为:

ptr 的值为 : <nil>

ptr 的值为 : (*int)(nil)

空指针判断:

if(ptr != nil) /* ptr 不是空指针 */

if(ptr == nil) /* ptr 是空指针 */

比如:

package main

import "fmt"

func main() {

var ptr *int

fmt.Printf("ptr 的值为 : %x\n", ptr )

//或者进行判空操作

if(ptr != nil){

fmt.Println("内存地址为",ptr)

}else {

fmt.Println("ptr 为空指针")

}

}

其实可以动态申请一块内存:

package main

import "fmt"

func main(){

var intP *int = new(int) //new(int) 分配一块内存空间,数据类型是int ,堆区指针,空值

fmt.Println(*intP) //0

}

new() ==>可以动态申请一块堆区内存,并将该内存初始化为0值。

4、指针作为函数参数

Go 语言允许向函数传递指针,只需要在函数定义的参数上设置为指针类

型即可。

以下实例演示了如何向函数传递指针,并在函数调用后修改函数内的

值,:

package main

import "fmt"

func main() {

/* 定义局部变量 */

var a int = 100

var b int= 200

fmt.Printf("交换前 a 的值 : %d\n", a )

fmt.Printf("交换前 b 的值 : %d\n", b )

/* 调用函数用于交换值

* &a 指向 a 变量的地址

* &b 指向 b 变量的地址

*/

swap(&a, &b)

fmt.Printf("交换后 a 的值 : %d\n", a )

fmt.Printf("交换后 b 的值 : %d\n", b )

}

func swap(x *int, y *int) {

var temp int

temp = *x /* 保存 x 地址的值 */

*x = *y /* 将 y 赋值给 x */

*y = temp /* 将 temp 赋值给 y */

}

以上实例允许输出结果为:

交换前 a 的值 : 100

交换前 b 的值 : 200

交换后 a 的值 : 200

交换后 b 的值 : 100

课堂练习:详见指针练习题

4、指针数组

在我们了解指针数组前,先看个实例,定义了长度为 3 的整型数组:

package main 
import "fmt" 
const MAX int = 3 
func main() { 
 a := [MAX]int{10,100,200} 
 var i int 
 for i = 0; i < MAX; i++ { 
 fmt.Printf("a[%d] = %d\n", i, a[i] ) 
 } 
} 

以上代码执行输出结果为:

a[0] = 10

a[1] = 100

a[2] = 200

有一种情况,我们可能需要保存数组,这样我们就需要使用到指针。

以下声明了整型指针数组:

var ptr [MAX] *int

ptr 为整型指针数组。因此每个元素都指向了一个值。

以下实例的三个整数将存储在指针数组中:

package main 
import "fmt" 
const MAX int = 3 
func main() { 
 a := [MAX]int{10,100,200} 
 var i int 
 var ptr [MAX]*int //由指针构成的数组,成员都是 *int 类型的指针 
 for i = 0; i < MAX; i++ { 
 ptr[i] = &a[i] //指针数组赋值,确保指针数组中的每一个元 
素,每一个指针指向有效内存 
 } 
 for i = 0; i < MAX; i++ { 
 fmt.Printf("a[%d] = %d\n", i,*ptr[i] ) //通过指针数组中的每一个 
指针元素来访问原始数组 
 } 
} 

尝试一下以上代码执行输出结果为:

a[0] = 10

a[1] = 100

a[2] = 200

eg: Point5.go

课堂练习

1、找出数组中和为指定值的两个元素的下标,

比如从数组[1, 3, 5, 7, 8]中找出和为8的两个元素的下标分别为(0,3)和

(1,2)。

2、写一个程序,获取一个变量num的地址,并显示到终端

二级指针

指向指针的指针 =》 二级指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量

为指向指针的指针变量。

当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,

第二个指针存放变量的地址:

指向指针的指针变量声明格式如下:

var ptr **int

以上指向指针的指针变量为整型。

访问指向指针的指针变量值需要使用两个 * 号,如下所示:

package main

import "fmt"

func main() {

var a int

var ptr *int

var pptr **int

a = 3000

/* 指针 ptr 地址 */

ptr = &a

/* 指向指针 ptr 地址 */

pptr = &ptr

/* 获取 pptr 的值 */

fmt.Printf("变量 a = %d\n", a )

fmt.Printf("指针变量 *ptr = %d\n", *ptr )

fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)

}

尝试一下以上实例执行输出结果为:

指向指针的指针变量 **pptr = 3000

变量 a = 3000

指针变量 *ptr = 3000

内存分配原则

Tiny对象分配流程

1、判断对象大小是否小于maxSmallSize=32KB,如果小于32KB则进入

Tiny对象或小对象申请流程,

否则进入大对象申请流程。

2、判断对象大小是否小于maxTinySize=16B并且对象中是否包含指针,如

果大于16B或包含指针,

则进入小对象申请流程,否则进入Tiny对象申请流程

3、Tiny对象申请流程后,会先获取mcache目前的tinyoffset,再根据申请

tiny对象的大小及mcache.tinyoffset值,

进行内存对齐,计算出满足内存对齐后的对象插入位置offset

4、如果从插入位置offset插入对象后,不超出16B,并且存在待分配的tiny

空间,则将对象填充到该tiny空间,

并将地址返回给M,结束内存申请

5、如果当前的tiny空间不足,则通过nextFreeFast(span)查找span中一个

可用对象地址,存在则返回地址,

并结束内存申请

6、如果span中不存在一个可用对象,则调用

mcache.nextFree(tinySpanClass)从mcentral申请1个相同规格的

msapn。申请成功则结束流程

小对象分配流程

1、进入小对象申请流程后,通过mcache.alloc(spc)获取1个指定规格的

mspan

2、通过nextFreeFast(span)查找span中一个可用对象地址,存在则返回地

址给协程逻辑层P,

P得到内存空间,流程结束

3、如果不存在可用对象,则通过mcache.nextFree(tinySpanClass)中

mcache.refill(spc)从mcentral申请

1个相同规格的msapnmcache.refill(spc)中,会首先尝试通过mcentral的

noempty list获取mspan,

获取不到则在尝试通过mcentral的empty list获取mspan(1.16之后,

通过mcentral.cacheSpan()

从partial set获取mspan,获取不到则从full set获取可回收的

mspan)。mcache成功获取mcentral返回

的mspan后,返回可用对象地址,结束申请流程

4、mcache中empty List(1.16之后,full set)也没有可回收的mspan,则

会调用mcache.grow()函数,

从mheap中申请内存

5、mheap收到内存请求从其中一个heapArena从取出一部分pages返回给

mcentral;当mheap没有足够

的内存时,mheap会向操作系统申请内存,将申请的内存也保存到

heapArena中的mspan中。

mcentral将从mheap获取的由Pages组成的mspan添加到对应的span

class链表或集合中

6、最后协程业务逻辑层得到该对象申请到的内存,流程结束

大对象分配流程

1、进入大对象分配流程后,会调用mcache.allocLarge()方法申请大对象

2、mcache.allocLarge()中主要的mspan申请链路为:mheap.alloc ->

mheap.allocSpan,

mheap.allocSpan为申请mspan的核心方法。mheap.allocSpan会首先判

断申请的page数是否小于

P.pageCache的最大page数,如果P.pageCache满足需要,则会从

P.mspancache获取mspan地址给P,

流程结束

3、P.pageCache不足,则对mheap加锁,从mheap.pageAlloc这种Radix

tree(基数树)数据结构中查找

可用的page,协程逻辑层P得到内存,流程结束

4、mheap.pageAlloc中查找不存在可用的page,则调用mheap.grow()向操

作系统申请内存。申请成功后,

再次从mheap.pageAlloc中查找可以page,P得到内存后,流程结束

4、map类型

Map 是一种无序的键值对的集合。

Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的

值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是

无序的,

我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

1、定义声明 Map

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

/* 声明变量,默认 map 是 nil */

var map_variable map[keyType] valueType

eg:var a map[int]int

key 可以是什么类型 :golang 中的 map的 key 可以是很多种类型,

比如 bool, 数字,string, 指针, channel , 还可以是只 包含前面几个类

型的 接口, 结构体, 数组

注意: slice, map 还有 function 不可以,因为这几个没法用 == 来

判断

value 可以是什么类型: value 的类型和 key 基本一样,通常为: 数字(整数,

浮点数),string,map,struct

map 声明的举例:

var a map[string]string

var b map[string]int

var c map[int]string

var d map[string] map[string]string

注意:声明是不会分配内存的,初始化需要 make ,分配内存后才能

赋值和使用

/* 使用 make 函数 初始化map */

map_variable = make(map[key] value)

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

值对

实例

下面实例演示了创建和读取map:

package main

import "fmt"

func main() {

//声明map

var countryCapitalMap map[string]string

/* 创建集合在使用map前,首先要 make 分配数据空间。*/

countryCapitalMap = make(map[string]string)

/* map 插入 key-value 对,各个国家对应的首都 */

//使用 map

countryCapitalMap["France"] = "Paris"

countryCapitalMap["Italy"] = "Rome"

countryCapitalMap["Japan"] = "Tokyo"

countryCapitalMap["India"] = "New Delhi"

/* 使用 key 输出 map 值 */

for country := range countryCapitalMap {

fmt.Println("Capital of",country,"is",countryCapitalMap[country])

}

}

以上实例运行结果为:

Capital of France is Paris

Capital of Italy is Rome

Capital of Japan is Tokyo

Capital of India is New Delhi

另外map的声明方式:

heroes := map[string]string{

"no1" : "chengdu", // 注意不能少了” ,“号

"no2" : "beijing",

"no3" : "wuhan",

}

fmt.Println(heroes)

fmt.Println(heroes["no2"])

举例演示一个 key-value 的 value 是 map 的案例

比如:我们要存放 3 个学生信息, 每个学生有 name 和 sex 信息

思路: map[string] map[string]string

stuMap := make(map[string] map[string]string)

stuMap["stu01"] = make(map[string]string)

stuMap["stu01"]["name"] = "zhangsan"

stuMap["stu01"]["age"] = "10岁"

stuMap["stu01"]["addr"] = "xi'an"

stuMap["stu02"] = make(map[string]string)

stuMap["stu02"]["name"] = "lisi"

stuMap["stu02"]["age"] = "9岁"

stuMap["stu02"]["addr"] = "chognqing"

fmt.Println(stuMap)

fmt.Println(stuMap["stu01"])

结果:

2、常规操作:

1.map 增加和更新

map["key"] = value //如果 key 还没有,就是增加,如果 key 存在就是修改

cities := make(map[string]string)

cities["no1"] = "上海"

cities["no2"] = "西安"

cities["no3"] = "天津"

fmt.Println(cities)

//因为no3 这个key值已经存在,所以下面的就是修改,若无就是增加

cities["no3"] = "天津..."

fmt.Println(cities)

2.map 删除

delete(map,"key") ,

delete 是一个内置函数,如果 key 存在,就删除该 key-value,如果 key 不存

在, 不操作,但是也不会报错

delete() 函数

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

实例如下:

package main 
import "fmt" 
func main() { 
 /* 创建 map */ 
 countryCapitalMap := map[string]string{"France":"Paris","Italy":"Rome","Japan":"Tokyo","India":"New Delhi"} 
 fmt.Println("原始 map") 
 /* 打印 map */ 
 for country := range countryCapitalMap { 
 fmt.Println("Capital of",country,"is",countryCapitalMap[country]) 
 } 
 /* 删除元素 */ 
 delete(countryCapitalMap,"France"); 
 fmt.Println("Entry for France is deleted") 
 fmt.Println("删除元素后 map") 
 /* 打印 map */ 
 for country := range countryCapitalMap { 
 fmt.Println("Capital of",country,"is",countryCapitalMap[country]) 
 } 
} 

以上实例运行结果为:

原始 map

Capital of France is Paris

Capital of Italy is Rome

Capital of Japan is Tokyo

Capital of India is New Delhi

Entry for France is deleted

删除元素后 map

Capital of Italy is Rome

Capital of Japan is Tokyo

Capital of India is New Delhi

注意如果要全部删除,两种方式

1.遍历所有key,逐一删除直接

2.直接make一个新空间。

cities := make(map[string]string)

fmt.Println(cities)

3.map 查找

//演示map查找

val , _:=cities["no2"] //返回值有两个第一个是key,第二个是成功失败的bool结果

fmt.Printf("找到了值为%v",val)

说明:如果 cities 这个 map 中存在 "no2" , 那么 findRes 就会返回 true,

否则返回 flase

判断某个键是否存在 bool类型的 返回值来做处理

Go语言中有个判断map中键是否存在的特殊写法,格式如下:

举个例子:

func main() { 
scoreMap := make(map[string]int) 
scoreMap["张三"] = 90 
scoreMap["小明"] = 100 
// 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型 
的零值 
v, ok := scoreMap["张三"] 
if ok { 
 fmt.Println(v) 
} else { 
 fmt.Println("查无此人") 
} 
} 

4、map的遍历

Go语言中使用for range遍历map。

func main() {

scoreMap := make(map[string]int)

scoreMap["张三"] = 90

scoreMap["小明"] = 100

scoreMap["王五"] = 60

//常规遍历方式

for k, v := range scoreMap {

fmt.Println(k, v) //输出键值对

}

}

但我们只想遍历key的时候,可以按下面的写法:

func main() {

scoreMap := make(map[string]int)

scoreMap["张三"] = 90

scoreMap["小明"] = 100

scoreMap["王五"] = 60

for k := range scoreMap {

fmt.Println(k) //只输出键key

}



for _,v :=range scoreMap {

fmt.Println(v) //只输出值value

}

}

注意: 遍历map时的元素顺序与添加键值对的顺序无关。

复杂map遍历:

for k1, v1 := range stuMap {

fmt.Println("k1 =", k1)

for k2, v2 := range v1 {

fmt.Printf("\t k2 =%v v2=%v \n", k2, v2)

}

fmt.Println()

}

结果:

按照指定顺序遍历map

import ( 
"fmt" 
"math/rand" 		//随机数包
"time" 			//日期时间包,制作随机数的种子
"sort" 		   	//内置排序包	
) 
 func main() { 
 rand.Seed(time.Now().UnixNano()) //初始化随机
 var scoreMap = make(map[string]int, 200)   //定义并声明一个容量为200个string-int的map
 for i := 0; i < 100; i++ { 
 key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串 
                                  //%d 数字 %2d两个数字 %02d不够的时候左补0
                                //stu00 stu01....
 value := rand.Intn(100) //生成0~99的随机整数 
 scoreMap[key] = value 
 } 
     
 //取出map中的所有key存入切片keys 
 var keys = make([]string, 0, 200) 
 for key := range scoreMap { 
 keys = append(keys, key)  //append切片的增加函数
 } 
     
 //对切片进行排序 
 sort.Strings(keys) 
     
 //按照排序后的key遍历map 
 for _, key := range keys { 
 fmt.Println(key, scoreMap[key]) 
 } 
} 

课堂练习:

1、定义一个id和姓名的map来存储学生信息,并可以遍历输出。

2、定义一个姓名和级别的map类存储学生的成绩信息,并可以完成基本的增删 改查。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/706089.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:如何将Python打包-一个SEO优化指南

如何将Python打包 - 一个SEO优化指南 作为一名拥有10年Python编程经验的工程师&#xff0c;我意识到很多Python开发者面临一个共同的问题&#xff1a;如何将他们的Python项目打包并发布到PyPI上&#xff1f;打包一个Python项目不仅可以让您的代码更加组织化&#xff0c;也可以…

如何拆分PDF?拆分PDF软件分享!​

那么如何拆分PDF&#xff1f;PDF是一种流行的电子文档格式&#xff0c;它可以在不同的操作系统和设备上进行查看和共享&#xff0c;而不会因为不同的软件或硬件而出现兼容性问题。同时&#xff0c;在使用的过程中&#xff0c;PDF拆分PDF文件是一个比较常见的需求&#xff0c;它…

threejs入门

个人博客地址: https://cxx001.gitee.io 前言 随着HTML5的发布&#xff0c;我们可以通过WebGL在浏览器上直接使用显卡资源来创建高性能的二维和三维图形&#xff0c;但是直接使用WebGL编程来创建三维场景十分复杂而且还容易出问题。而使用Three.js库可以简化这个过程&#xff…

机器学习——决策树1(三种算法)

要开始了…内心还是有些复杂的 因为涉及到熵…单纯的熵&#xff0c;可以单纯 复杂的熵&#xff0c;如何能通俗理解呢… 我也没有底气&#xff0c;且写且思考吧 1. 决策树分类思想 首先&#xff0c;决策树的思想&#xff0c;有点儿像KNN里的KD树。 KNN里的KD树&#xff0c;是每…

如何将非平稳的时间序列变为平稳的时间序列?

可以采用现代信号处理算法&#xff0c;比如小波分解&#xff0c;经验模态分解&#xff0c;变分模态分解等算法。 以经济金融领域的数据为例&#xff0c;经济金融领域的数据作为一种时间序列&#xff0c;和我们平常工程领域分析的信号具有相同特性。一般来说&#xff0c;信号是…

在 Maya、ZBrush 和 Arnold 中重塑来自邪恶西部的 Edgar Gravenor

今天瑞云渲染小编给大家带来Giancarlo Penton 介绍的Edgar Gravenor项目背后过程&#xff0c;展示了皮肤纹理和头发是如何制作的&#xff0c;并解释了详细的服装是如何设置的。 介绍 大家好&#xff0c;我的名字是Giancarlo Penton。我是一名3D角色艺术家&#xff0c;最近毕业…

从零开始 Spring Boot 53:JPA 属性转换器

从零开始 Spring Boot 53&#xff1a;JPA 属性转换器 图源&#xff1a;简书 (jianshu.com) 这篇文章介绍如何在 JPA&#xff08;Hibernate&#xff09;中使用属性转换器。 在前篇文章中&#xff0c;我介绍了如何使用Embedded和Embeddable将一个类型嵌入实体类&#xff0c;并映…

初识mysql之表内容的增删查改

目录 一、插入 1. 插入基础语法 2. 单行数据 全列插入 3. 多行数据 全列插入 4. 插入&#xff0c;失败则更新 5. 替换 二、基础查询 1. 查询基础语法 2. 全列查询 3. 指定列查询 4. 表达式查询 5. 结果去重 6. where条件 6.1 比较运算符与逻辑运算符 6.2 查询…

爬虫入门指南(5): 分布式爬虫与并发控制 【提高爬取效率与请求合理性控制的实现方法】

文章目录 前言多线程与多进程多线程多进程多线程和多进程的选择 使用Scrapy框架实现分布式爬虫1. 创建Scrapy项目2. 配置Scrapy-Redis3. 创建爬虫4. 启动爬虫节点5. 添加任务到队列 并发控制与限制请求频率并发控制限制请求频率 未完待续... 前言 在进行爬虫任务时&#xff0c;…

STM32外设系列—红外遥控

文章目录 一、红外遥控简介二、红外遥控的原理三、二进制脉冲编码3.1 NEC码的位定义3.2 NEC遥控指令的数据格式 四、红外遥控程序设计思路五、红外遥控程序设计5.1 红外遥控初始化程序5.2 记录高电平持续时间函数5.3 中断服务函数5.4 读取键值5.5 参数定义 六、应用实例 一、红…

ADB原理,常用命令汇总及示例

一. ADB简介 ADB&#xff0c;即 Android Debug Bridge 是一种允许模拟器或已连接的 Android 设备进行通信的命令行工具&#xff0c;它可为各种设备操作提供便利&#xff0c;如安装和调试应用&#xff0c;并提供对 Unix shell&#xff08;可用来在模拟器或连接的设备上运行各种…

基于Java+SpringBoot+vue的食品安全管理系统设计与实现

博主介绍&#xff1a;✌擅长Java、微信小程序、Python、Android等&#xff0c;专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不然下次找不到哟 Java项目精品实战案…

基于Java+Vue前后端分离网络教学平台设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

Ubuntu连不上网,在windows安装docker后

在windows上安装docker后&#xff0c;会依赖于virtualbox虚拟机&#xff0c;并且有虚拟网络&#xff0c;与ubuntu虚拟机网络产生冲突。 解决办法&#xff0c;打开网络适配器&#xff0c;禁用VirtualBox网络 这个时候就可以了。 ubuntu上使用docker pull镜像的时候&#xff0c…

线性代数克莱姆法则的几何含义

以二元一次方程组的求解为例&#xff1a; { a c a 1 b c b 1 c 1 a c a 2 b c b 2 c 2 \left\{\begin{array}{l} a_{c}a_{1} b_{c}b_{1} c_{1} \\ a_{c}a_{2} b_cb_{2} c_{2} \end{array}\right. {ac​a1​bc​b1​c1​ac​a2​bc​b2​c2​​ 其中 a c a_c ac​和 b c b_…

【Lua】ZeroBrane Studio免费专业IDE使用详解

▒ 目录 ▒ &#x1f6eb; 问题描述环境 1️⃣ IDE界面说明项目目录编辑器控制台窗口输出窗口选择解释器堆栈窗口监视窗口大纲窗口 2️⃣ 调试程序3️⃣ 自定义lua解释器编译自己的lua解释器增加interpreters配置文件重启IDE 4️⃣ 其它IDE比较Lua EditorVSCode &#x1f6ec; …

精密电阻的丝印识别方法

在PCB上经常会出现一些精密的电阻丝印和普通的电阻的丝印识别方式不太一样&#xff0c;比如图1所示。 图1 这种电阻的丝印主要是由两部分组成&#xff0c;第一部分是两个数字&#xff0c;第二部分是一个字母&#xff0c;电阻的阻值的计算就是根据这这个丝印编码。例如图2中的丝…

css中鼠标悬停和点击触发样式变换(:hover和:active)

效果 代码 /*hover--光标&#xff08;鼠标指针&#xff09;悬停在元素上时触发*/ .el-card:hover{background: #f5f5f6; } /*active--按下按键和松开按键之间的时间触发*/ .el-card:active{background: #e0dfdf; }

Linux--管道文件:|

作用&#xff1a; 传输资源&#xff0c;你现在可以单纯的把资源看作是数据 》管道的作用是传导数据 构成&#xff1a;入口与出口 存储&#xff1a; 内存级的文件&#xff0c;没有在磁盘上&#xff01;

Emacs之sr-speedbar替代neotree显示目录(一百一十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…