Go_读书笔记_01

Go_读书笔记_01

数组、字符串和切片

go语言数组、切片和字符串三者的关系

# 1. 在底层原始数据有着相同的内存结构;在上层,因为语法的限制而有着不同的行为表现。
# 2. 数组:一种值类型;
#					虽然数组的元素可以被修改,但数组本身的赋值和函数传参都是以整体复制的方式处理的。
# 3. 字符串:
#	         底层数据也是对应的字节数组,但字符串的只读属性禁止了在程序中对底层字节数组元素的修改。
#          字符串赋值只是复制了数据地址和对应的长度,而不会导致底层数据的复制。
# 4. slice(切片)
#          切片的结构和字符串结构类似;但解除了只读限制。
#          切片的底层数据虽然也是对应数据类型的数组,但每个切片还有独立的长度和容量信息,切片赋值和函数传参时也是将切片透信息
#          部分按值传递处理。因为切片头含有底层数据的指针,所以它的赋值也不会导致底层数据的复制。

其实go语言的赋值函数传参规则很简单,除闭包函数以引用的方式对外部变量访问之外,其它赋值和函数传参都以传值的方式处理

数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。数组的长度是数组类型的一部分,不同长度或不同类型组成的数组都是不同的类型。

// 数组的定义方式
var a [3]int                 // 定义长度为3的int数组,元素全部为0
var b =[...]int{1,2,3}       // 定义长度为3int数组
var c =[...]int{2:3,1:2}     // 定义长度为3的int数组,{0,2,3}
var d =[...]int{1,2,4:5,6}   // 定义长度为6的int数组 {1,2,0,0,5,6}

go语言中数组是值语义。一个数组变量即表示整个数组。

可以将数组看作一个特殊的结构体,结构体的字段名对应数组的索引,同时结构体成员的数目是固定的(即len())。内置函数len()可用于计算数组的长度,cap()可用于计算数组的容量。

// 常见的遍历数组的方式

for i:=rang a {
  fmt.Printf("a[%d]  -->%d \n",i,a[i])
}

for i,v:=range b {
  fmt.Printf("b[%d]  -->%d \n",i,v)
}

for i:=0;i<len(c);i++ {
   fmt.Printf("c[%d]  -->%d \n",i,c[i])
}

for- range方式遍历数组,性能可能会好一些,因为这种遍历保证不会出现数组越界的情况,每一轮迭代对数组元素的访问时可以省去对下标越界的判断。

字符串

一个字符串是一个不可改变的字节序列,字符串通常用来表示人类可读的文本数据。

字符串的元素不可修改,是一个只读的字节数组。go语言的源代码中文本字符串通常被解释为采用了UTF8编码的Unicode码点(rune)序列。


go语言字符串的底层结构在reflect.StringHeader中定义:

type StringHeader struct{
  Data uintptr   // 指向字符串的底层字节数组
  Len int        // 字符串的字节的长度
}

字符串其实是一个结构体,因此字符串的赋值操作也就是refect.Stringheader结构体的复制过程,并不会涉及底层字节数组的复制。


字符串虽然不是切片,但是支持切片操作,不同位置的切片底层访问的是同一块内存数据(因为字符串是只读的,所以相同的字符串面值常量通常对应同一个字符串常量)

s :="hello,world"
hello :=s[:5]
world :=s[7:]

切片

**切片(slice)**就是一种简化版的动态数组。


切片的结构定义,reflect.SliceHeader

type SliceHeader struct {
  Data uintptr      // 切片指向的底层数组
  Len int           // 表示切片的长度, len<=cap
  Cap int           // 表示切片指向的内存空间的最大容量
}

切片的定义方式:

var (

  a []int          // nil切片,  和nil相等,一般用来表示一个不存在的切片
  b =[]int{}       // 空切片,和nil不相等,一般用来表示一个空的集合
  c =[]int{1,2,3}  // 有三个元素的切片, len = cap =3
  d =c[:2]         // 有两个元素的切片, len=2  cap =3
  e =c[0:2:cap(c)] // 有两个元素的切片, len=2   cap =3
  f =c[:0]         // 有0个元素的切片,len=0  cap=3
  g =make([]int,3) // 有3个元素的切片,len  = cap = 3
  h =make([]int,2,3) // 有2个元素的切片,len  = 2 cap = 3
  i =make([]int,0,3) // 有0个元素的切片,len  = 0  cap = 3
)

添加切片元素

内置的泛型函数append()可以在切片的尾部追加n个元素

var a []int
a =append(a,1)                    // 追加一个元素
a =append(a,1,2,3)                // 追加多个元素,手写解包方式
a =append(a,[]int{1,2,3}...)      // 追加一个切片,切片需要解包

不过要注意的是,在容量不足的情况下,append()操作会导致重新分配内存,可能导致巨大的内存分配和复制数据的代价。即使容量足够,依然需要用append()函数的返回值来更新切片本身,因为新切片的长度已经发生了变化。


向切片的开头添加元素:

var a =[]int{1,2,3}
a =append([]int{0},a...)                    // 在开头添加一个元素
a =append([]int{-3,-2,-1},a...)             // 在开头添加一个切片

在开头一般都会导致内存的重新分配,而且会导致已有的元素全部重新复制一次。因此在头追加元素的性能要比尾部追加性能差很多。


删除切片的元素

根据删除元素的位置,分为头删除、中间删除、尾部删除等情况。其中尾部删除性能最佳。

a =[]int{1,2,3}
a =a[:len(a)-1]                         // 删除尾部1个元素
a =a[:len(a)-N]                         // 删除尾部n个元素

删除开头元素,可以采用直接移动数据指针的方式:

a =[]int{1,2,3,4}
a =a[1:]                                // 删除开头1个元素
a =a[N:]                                // 删除开头N个元素

删除开头的元素也可以不移动数据指针,而将后面的数据向开头移动,可以用append()原地完成,(指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化)

a =[]int{1,2,3,4}
a =append(a[:0],a[1:]...)                               // 删除开头1个元素
a =append(a[:0],a[N:]...)                               // 删除开头N个元素

也可以用copy()完成删除开头的元素:

a =[]int{1,2,3,4}
a =a[:copy(a,a[1:])]                             // 删除开头1个元素
a =a[:copy(a,a[N:])]                               // 删除开头N个元素

对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用append()copy()原地完成。

a =[]int{1,2,3, ...}
a =append(a[:i], a[i+1]...)          // 删除中间1个元素
a =append(a[:i], a[i+N]...)          // 删除中间N个元素

a =a[:i+copy(a[:i], a[i+1]...)]          // 删除中间1个元素
a =a[:i+copy(a[:i], a[i+N]...)]          // 删除中间N个元素

切片高效操作的要点:要降低内存分配的次数,尽量保证append()操作不会超过cap的容量,降低触发内存分配的次数和每次分配内存的大小。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值