Go数组、切片与Map
数组
数组声明
package main
func main() {
// 数组声明
// 方式一:
var balance1 = [5]float32{1.0, 2.0, 3.4, 7.0, 5.0}
// 方式二:类型推导在声明数组的同时快速初始化数组:
balance2 := [5]float32{1.0, 2.0, 3.4, 7.0, 5.0}
// 方式三:如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
var balance3 = [...]float32{1.0, 2.0, 3.4, 7.0, 5.0}
// 或
balance4 := [...]float32{1.0, 2.0, 3.4, 7.0, 5.0}
// 方式四:如果设置了数组的长度,通过指定下标来初始化个别元素:
// 将索引为 1 和 3 的元素初始化,其他没赋值的默认就是0
balance5 := [5]float32{1: 2.0, 3: 7.0}
}
数组访问
用索引来访问数组元素
var salary float32 = balance[9]
遍历数组
for..range 遍历,这是 Go 语言一种独有的结构,可以用来遍历访问数组的元素。
package main
import "fmt"
func main() {
names := [3]string{"Bob", "Alice", "Tom"}
// 遍历数组,range返回索引和值。可以用 _ 忽略i或者v
for i, v := range names {
fmt.Printf("Index is %d, value is %s\n", i, v)
}
}
切片
Go 语言切片是对数组的抽象。Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型==切片==("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。比方说存放学生成绩,那学生个数是不确定的,这样就可以用切片。
切片的本质
- 切片是数组的一个==引用==,因此切片是==引用类型==,在进行传递时,遵守引用传递的机制。
- 切片本质是对数组的一个"轻量级封装",包含==指向底层数组的指针==、长度==和==容量。
- 切片可以通过
append动态扩容,数组不行。
切片定义
package main
import "fmt"
func main() {
intArr := [...]int{1, 11, 22, 55, 76}
//方法1:定义一个切片,直接引用已经存在的一个数组中的某些元素
slice := intArr[1:3]
//方法2:make切片
// var 切片名 []type = make([]type, len, [cap])
// type: 切片的数据类型
// len : 切片元素的个数
// cap :指定切片容量,可选, 如果你分配了 cap,则要求 cap>=len.
var slc []float64 = make([]float64, 5, 10)
slc2 := make([]int, 5, 10)
slc[0] = 1.0
slc[1] = 2.0
fmt.Println("slc=", slc)
//第3种方式:定义一个切片,直接就指定具体数组,使用原理类似make的方式
strSlc := []string{"Bob", "Tom", "Alice"}
}
切片 vs 数组
Go开发中,绝大多数场景都推荐用==切片==而不是数组,因为切片提供了动态长度和灵活操作的能力。
切片基本使用
package main
import "fmt"
func main() {
//演示切片的基本使用
var intArr [5]int = [...]int{1, 11, 22, 55, 76}
//声明/定义一个切片
//1. slice 就是切片名
//2. intArr[1:3] 表示 slice 引用到intArr这个数组
//3. 引用intArr数组的起始下标为 1 , 最后的下标为3(但是不包含3)。所以就是索引1,索引2的元素
slice := intArr[1:3]
fmt.Println("intArr=", intArr)
fmt.Println("slice 的元素 =", slice)
fmt.Println("slice 的元素个数 =", len(slice)) // 2
fmt.Println("slice 的容量 =", cap(slice)) // 切片的容量是可以动态变化
fmt.Printf("intArr[1]的地址=%p\n", &intArr[1])
fmt.Printf("slice[0]的地址=%p slice[0]==%v\n", &slice[0], slice[0])
slice[1] = 66 //改了Slice的元素,原数组的元素也随之改变
//添加元素
slice = append(slice, 18)
fmt.Println("intArr=", intArr)
fmt.Println("slice 的元素是 =", slice) // 11 66 18
//删除元素,没有实现具体方法,用切片实现
slice = slice[1:] //删头
slice = slice[:len(slice)-1] //删尾
slice = append(slice[:2], slice[3:]...) //删中间,靠拼接实现
}
切片的遍历
package main
import "fmt"
func main() {
strSlc := []string{"Bob", "Tom", "Alice"}
//索引遍历
for i:=0; i<3; i++{
fmt.Printf("index is %v, value is %v\n", i, strSlc[i])
}
// for range遍历
for i, v := range strSlc{
fmt.Printf("index is %v, value is %v\n", i, v)
}
}
二维数组
二维数组定义
package main
import (
"fmt"
)
func main() {
//二维数组的演示案例
/*
0 0 0 0 0 0
0 0 1 0 0 0
0 2 0 3 0 0
0 0 0 0 0 0
*/
//定义/声明二维数组,四行六列
var arr [4][6]int
//赋初值
arr[1][2] = 1
arr[2][1] = 2
arr[2][3] = 3
//遍历二维数组,按照要求输出图形
for i := 0; i < 4; i++ {
for j := 0; j < 6; j++ {
fmt.Print(arr[i][j], " ")
}
fmt.Println()
}
}
二维数组遍历
package main
import (
"fmt"
)
func main() {
//演示二维数组的遍历
var arr3 = [2][3]int{{1,2,3}, {4,5,6}}
//for循环来遍历
for i := 0; i < len(arr3); i++ {
for j := 0; j < len(arr3[i]); j++ {
fmt.Printf("%v\t", arr3[i][j])
}
fmt.Println()
}
//for-range来遍历二维数组
for i, v := range arr3 {
for j, v2 := range v {
fmt.Printf("arr3[%v][%v]=%v \t",i, j, v2)
}
fmt.Println()
}
}
集合(Map)
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是==无序==的,我们无法决定它的返回顺序,这是因为 Map 是使用 ==hash 表==来实现的。
集合声明
声明:var 变量名 map[keytype]valuetype
注意
声明是不会分配内存的,初始化需要 make 分配内存后才能赋值和使用。变量名 = make(map[keytype]valuetype)
如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对
package main
import "fmt"
func main() {
// 声明map同时make初始化
var a map[string]string = make(map[string]string, 10)
// 用短声明更简洁
b := make(map[string]string, 10)
// 不声明容量也行
c := make(map[string]string)
a["name1"] = "Bob"
a["name2"] = "Alice"
a["name3"] = "Tom"
fmt.Println(a)
}
集合遍历
package main
import "fmt"
func main() {
b := make(map[string]string, 10)
b["name1"] = "Bob"
b["name2"] = "Alice"
b["name3"] = "Tom"
// for range遍历
for k, v := range b {
fmt.Printf("Key is %v, value is %v\n", k, v)
}
}
集合增删改查
package main
import "fmt"
func main() {
b := make(map[string]string, 10)
b["name1"] = "Bob"
b["name2"] = "Alice"
b["name3"] = "Tom"
// 改
b["name1"] = "Ann"
// 如果key不存在就是增
b["name4"] = "Cary"
//删
// delete 是一个内置函数,如果 key 存在,就删除该 key-value,如果 key 不存在,不操作,但是也不会报错。
delete(b, "name4")
//遍历输出
for k, v := range b {
fmt.Printf("Key is %v, value is %v\n", k, v)
}
// 如果我们要删除 map 的所有 key ,没有专门的方法一次删除,可以遍历key, 逐个删除.
// 或者 map = make(...),make 一个新的,这样原map会被GC回收,b变成一个新的空map。
b = make(map[string]string)
}
安全取值
如果取到了一个集合中不存在的值,不会报错,而是返回一个空值,所以我们要自己处理这种异常情况,用==双返回值模式(comma ok idiom)==。
惯用写法
这是 Go 中访问 map 的惯用写法,建议始终使用双返回值模式来安全地检查 key 是否存在。
package main
import (
"fmt"
)
func main() {
nameLists := make(map[string]string)
nameLists["Bob"] = "Engineer"
nameLists["Tom"] = "Doctor"
nameLists["Alice"] = "Teacher"
// profession - key 对应的 value。如果 key 不存在,返回零值("")
// ok - key 是否存在。true = 找到,false = 未找到
profession, ok := nameLists["Bob"]
if ok {
fmt.Printf("Bob's profession is %s\n", profession)
} else {
fmt.Println("Bob's profession not found")
}
profession, ok = nameLists["Charlie"]
if ok {
fmt.Printf("Charlie's profession is %s\n", profession)
} else {
fmt.Println("Charlie is not found")
}
}
深浅拷贝 - 引用和值
深拷贝 vs 浅拷贝
- 深拷贝:复制一个变量的时候,创建了一个新的变量,并且将原始数据复制给新变量,新变量在内存中是一个新的地址,两个变量修改时不会影响其他变量
- 浅拷贝:复制一个变量时,也会创建一个新的变量,但是两个变量共享底层的数据。也就是新旧变量会指向同一个数据的内存地址。实际上算是引用同一个数据。也就意味着任意一个变量发生变更,其他的也会变。
- 值类型:复制变量的时候是深拷贝,值类型包括:
int、float、string、struct、array、bool - 引用类型:复制变量的时候是浅拷贝,引用类型包括:
slice、map、channel、interface。- 想要深拷贝切片,用
copy(new, old)
- 想要深拷贝切片,用
相关笔记: go-基础-03-变量-数据类型-运算 | go-基础-04-分支-循环 | go-基础-05-函数-包 | go-面向对象