第02部分 Go开发学习 第7章 最常用的复杂数据类型 - map、数组、 列表和切片
第7章 最常用的复杂数据类型 - map、数组、 列表和切片
7.1 python的数组细节
对于Python列表-我们可以由此得出结论,对于每个新元素,我们需要另外八个字节来引用新对象。新的整数对象本身消耗28个字节。列表“ lst”的大小,不包含元素的大小,可以使用以下公式计算:
64 + 8 * len(lst) + *+ len(lst) ** 28
python中的list是python的内置数据类型,list中的数据类不必相同的,而array的中的类型必须全部相同。在list中的数据类型保存的是数据的存放的地址,简单的说就是指针,并非数据,这样保存一个list就太麻烦了,例如list1=[1,2,3,'a']需要4个指针和四个数据,增加了存储和消耗cpu。
array底层是C语言。有步长strides,dimensions,data三属性。固定类型的numpy类型的数组array缺乏这种灵活,但是更方便进行存储和处理数据。
96 + n * 8 Bytes
数组还有一个特性:数组的大小一开始需要指定好,后期动态扩充会拷贝值
对于机器学习的同学来说:数组用的更多,一般都是用numpy的array。算法上不会使用list
from array import array
#arr的类型 https://docs.python.org/3/library/array.html
a = array('b', [1, 3, 5])
for item in a:
print(item)
#使用切片获取
numbers_list = [2, 5, 62, 5, 42, 52, 48, 5]
numbers_array = array('i', numbers_list)
print(numbers_array[2:5]) # 3rd to 5th
print(numbers_array[:-5]) # beginning to 4th
print(numbers_array[5:]) # 6th to end
print(numbers_array[:]) # beginning to end
#添加元素
numbers = array('i', [1, 2, 3, 5, 7, 10])
# changing first element
numbers[0] = 0
print(numbers) # Output: array('i', [0, 2, 3, 5, 7, 10])
# changing 3rd to 5th element
numbers[2:5] = array('i', [4, 6, 8])
print(numbers) # Output: array('i', [0, 2, 4, 6, 8, 10])
#通过extend和append方法扩展数组
numbers = array('i', [1, 2, 3])
numbers.append(4)
print(numbers) # Output: array('i', [1, 2, 3, 4])
# extend() appends iterable to the end of the array
numbers.extend([5, 6, 7])
print(numbers) # Output: array('i', [1, 2, 3, 4, 5, 6, 7])
#使用+连接数组
odd = array('i', [1, 3, 5])
even = array('i', [2, 4, 6])
numbers = array('i') # creating empty array of integer
numbers = odd + even
print(numbers)
7.2 go语言的数组
机器学习的同学对python的array应该很了解,但是做web开发的同学对python的array使用的不多
Go 语言提供了数组类型的数据结构。 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。
数组元素可以通过索引(位置)来读取(或者修改),索引从0开始,第一个元素索引为 0,第二个索引为 1,以此类推。数组的下标取值范围是从0开始,到长度减1。
数组一旦定义后,大小不能更改。
数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。
相对于去声明 number0, number1, ..., number99 的变量,使用数组形式 numbers[0], numbers[1] ..., numbers[99] 更加方便且易于扩展。
数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。

声明和初始化数组
需要指明数组的大小和存储的数据类型。
var balance [10] float32
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
初始化数组中 {} 中的元素个数不能大于 [] 中的数字。如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:
var balance = []float32{1000.0, 2.0, 3.4, 7.0, 50.0}
balance[4] = 50.0
数组的其他创建方式:
var a [4] float32 // 等价于:var arr2 = [4]float32{}
fmt.Println(a) // [0 0 0 0]
var b = [5] string{"ruby", "王二狗", "rose"}
fmt.Println(b) // [ruby 王二狗 rose ]
var c = [5] int{'A', 'B', 'C', 'D', 'E'} // byte
fmt.Println(c) // [65 66 67 68 69]
d := [...] int{1,2,3,4,5}// 根据元素的个数,设置数组的大小
fmt.Println(d)//[1 2 3 4 5]
e := [5] int{4: 100} // [0 0 0 0 100]
fmt.Println(e)
f := [...] int{0: 1, 4: 1, 9: 1} // [1 0 0 0 1 0 0 0 0 1]
fmt.Println(f)
访问数组元素
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] )
}
}
数组的长度
通过将数组作为参数传递给len函数,可以获得数组的长度。
package main
import "fmt"
func main() {
a := [...]float64{67.7, 89.8, 21, 78}
fmt.Println("length of a is",len(a))
}
您甚至可以忽略声明中数组的长度并将其替换为…让编译器为你找到长度。这是在下面的程序中完成的。
package main
import (
"fmt"
)
func main() {
a := [...]int{12, 78, 50} // ... makes the compiler determine the length
fmt.Println(a)
}
遍历数组:
package main
import "fmt"
func main() {
a := [...]float64{67.7, 89.8, 21, 78}
for i := 0; i < len(a); i++ { //looping from 0 to the length of the array
fmt.Printf("%d th element of a is %.2f\n", i, a[i])
}
}
使用range遍历数组:
package main
import "fmt"
func main() {
a := [...]float64{67.7, 89.8, 21, 78}
sum := float64(0)
for i, v := range a {//range returns both the index and value
fmt.Printf("%d the element of a is %.2f\n", i, v)
sum += v
}
fmt.Println("\nsum of all elements of a",sum)
}
如果您只需要值并希望忽略索引,那么可以通过使用_ blank标识符替换索引来实现这一点。
for _, v := range a { //ignores index
}
数组是值类型
数组是值类型 Go中的数组是值类型,而不是引用类型。这意味着当它们被分配给一个新变量时,将把原始数组的副本分配给新变量。如果对新变量进行了更改,则不会在原始数组中反映。
package main
import "fmt"
func main() {
a := [...]string{"USA", "China", "India", "Germany", "France"}
b := a // a copy of a is assigned to b
b[0] = "Singapore"
fmt.Println("a is ", a)
fmt.Println("b is ", b)
}
数组的大小是类型的一部分。因此[5]int和[25]int是不同的类型。因此,数组不能被调整大小。不要担心这个限制,因为切片的存在是为了解决这个问题。
package main
func main() {
a := [3]int{5, 78, 8}
var b [5]int
b = a //not possible since [3]int and [5]int are distinct types
}
var al []int //创建slice
//sl := make([]int,10) //创建有10个元素的slice
sl:=[3]int{1,2,3} //创建有初始化元素的slice
s2:=[4]int{1,2,3,4} //创建有初始化元素的slice
fmt.Printf("%T, %T, %T", al, sl, s2)
7.3. go语言的切片
1. 什么是切片
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大
Go的切片类型为处理同类型数据序列提供一个方便而高效的方式。 切片有些类似于其他语言中的数组,但是有一些不同寻常的特性。 本文将深入切片的本质,并讲解它的用法。
2. 定义切片
// 第一种
var identifier []type
//第二种 使用make
var slice1 []type = make([]type, len)
简写成
slice1 := make([]type, len)
#使用make来创建slice,map,chanel说明如下
#第三种,通过对数组操作返回
course := [5]string{"django", "tornado", "scrapy", "python", "asyncio"}
subCourse := course[1:2]
fmt.Printf("%T", subCourse)
3. 切片初始化
s :=[] int {1,2,3 }
直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3
s := arr[:]
创建 slice 的方式有以下几种:
| 序号 | 方式 | 代码示例 |
|---|---|---|
| 1 | 直接声明 | var slice []int |
| 2 | new | slice := *new([]int) |
| 3 | 字面量 | slice := []int{1,2,3,4,5} |
| 4 | make | slice := make([]int, 5, 10) |
| 5 | 从切片或数组“截取” | slice := array[1:5] 或 slice := sourceSlice[1:5] |
- 用法
- 底层存储
- go和python的切片区别
- slice扩容机制
append 函数的参数长度可变,因此可以追加多个值到 slice 中,还可以用 ... 传入 slice,直接追加一个切片。
append函数返回值是一个新的slice,Go编译器不允许调用了 append 函数后不使用返回值。
使用 append 可以向 slice 追加元素,实际上是往底层数组添加元素。但是底层数组的长度是固定的,如果索引 len-1 所指向的元素已经是底层数组的最后一个元素,就没法再添加了。
这时,slice 会迁移到新的内存位置,新底层数组的长度也会增加,这样就可以放置新增的元素。同时,为了应对未来可能再次发生的 append 操作,新的底层数组的长度,也就是新 slice 的容量是留了一定的 buffer 的。否则,每次添加元素的时候,都会发生迁移,成本太高。
当原 slice 容量小于 1024 的时候,新 slice 容量变成原来的 2 倍 原 slice 容量超过 1024,新 slice 容量大家可以通过源码了解, 文章推荐:juejin.cn/post/684490…
7.4. 切片原理
7.5. go语言的map
map创建
要求所有的key的数据类型相同,所有value数据类型相同(注:key与value可以有不同的数据类型)
// 1 字面值
{
m1 := map[string]string{
"m1": "v1", // 定义时指定的初始key/value, 后面可以继续添加
}
_ = m1
}
// 2 使用make函数
{
m2 := make(map[string]string) // 创建时,里面不含元素,元素都需要后续添加
m2["m2"] = "v2" // 添加元素
_ = m2
}
// 定义一个空的map
{
m3 := map[string]string{}
m4 := make(map[string]string)
_ = m3
_ = m4
}
map中key的类型
map中的每个key在keys的集合中是唯一的,而且需要支持 == or != 操作
key的常用类型:int, rune, string, 结构体(每个元素需要支持 == or != 操作), 指针, 基于这些类型自定义的类型
// m0 可以, key类型为string, 支持 == 比较操作
{
var m0 map[string]string // 定义map类型变量m0,key的类型为string,value的类型string
fmt.Println(m0)
}
// m1 不可以, []byte是slice,不支持 == != 操作,不可以作为map key的数据类型
{
//var m1 map[[]byte]string // 报错: invalid map key type []byte
//fmt.Println(m1)
// 准确说slice类型只能与nil比较,其他的都不可以,可以通过如下测试:
// var b1,b2 []byte
// fmt.Println(b1==b2) // 报错: invalid operation: b1 == b2 (slice can only be compared to nil)
}
// m2 可以, interface{}类型可以作为key,但是需要加入的key的类型是可以比较的
{
var m2 map[interface{}]string
m2 = make(map[interface{}]string)
//m2[[]byte("k2")]="v2" // panic: runtime error: hash of unhashable type []uint8
m2[123] = "123"
m2[12.3] = "123"
fmt.Println(m2)
}
// m3 可以, 数组支持比较
{
a3 := [3]int{1, 2, 3}
var m3 map[[3]int]string
m3 = make(map[[3]int]string)
m3[a3] = "m3"
fmt.Println(m3)
}
// m4 可以,book1里面的元素都是支持== !=
{
type book1 struct {
name string
}
var m4 map[book1]string
fmt.Println(m4)
}
// m5 不可以, text元素类型为[]byte, 不满足key的要求
{
// type book2 struct {
// name string
// text []byte //没有这个就可以
// }
//var m5 map[book2]string //invalid map key type book2
//fmt.Println(m5)
}
map的增删改
// 创建
m := map[string]string{
"a": "va",
"b": "vb",
}
fmt.Println(len(m)) // len(m) 获得m中key/value对的个数
// 增加,修改
{
// k不存在为增加,k存在为修改
m["c"] = ""
m["c"] = "11" // 重复增加(key相同),使用新的值覆盖
fmt.Printf("%#v %#v\n", m, len(m)) // map[string]string{"a":"va", "b":"vb", "c":"11"} 3
}
// 查
{
// v := m[k] // 从m中取键k对应的值给v,如果k在m中不存在,则将value类型的零值赋值给v
// v, ok := m[k] // 从m中取键k对应的值给v,如果k存在,ok=true,如果k不存在,将value类型的零值赋值给v同时ok=false
// 查1 - 元素不存在
v1 := m["x"] //
v2, ok2 := m["x"]
fmt.Printf("%#v %#v %#v\n", v1, v2, ok2) // "" "" false
// 查2 - 元素存在
v3 := m["a"]
v4, ok4 := m["a"]
fmt.Printf("%#v %#v %#v\n", v3, v4, ok4) //"va" "va" true
}
// 删, 使用内置函数删除k/v对
{
// delete(m, k) 将k以及k对应的v从m中删掉;如果k不在m中,不执行任何操作
delete(m, "x") // 删除不存在的key,原m不影响
delete(m, "a") // 删除存在的key
fmt.Printf("%#v %#v\n", m, len(m)) // map[string]string{"b":"vb", "c":"11"} 2
delete(m, "a") // 重复删除不报错,m无影响
fmt.Printf("%#v %#v\n", m, len(m)) /// map[string]string{"b":"vb", "c":"11"} 2
}
map的遍历
- 遍历的顺序是随机的
- 使用for range遍历的时候,k,v使用的同一块内存,这也是容易出现错误的地方
m := map[string]int{
"a": 1,
"b": 2,
}
for k, v := range m {
fmt.Printf("k:[%v].v:[%v]\n", k, v) // 输出k,v值
}
map遍历易错点举例
由于遍历的时候,遍历v使用的同一块地址,同时这块地址是临时分配的。虽然v的地址没有变化,但v的内容在一直变化,当遍历完成后,v的内容是map遍历时最后遍历的元素的值(map遍历无序,每次不确定哪个元素是最后一个元素)。当程序将v的地址放入到slice中的时候,slice在不断地v的地址插入,由于v一直是那块地址,因此slice中的每个元素记录的都是v的地址。因此当打印slice中的内容的时候,都是同一个值
m := map[string]int{
"a": 1,
"b": 2,
}
var bs []*int
for k, v := range m {
fmt.Printf("k:[%p].v:[%p]\n", &k, &v) // 这里的输出可以看到,k一直使用同一块内存,v也是这个状况
bs = append(bs, &v) // 对v取了地址
}
// 输出
for _, b := range bs {
fmt.Println(*b) // 输出都是1或者都是2
}