4、指针&数组&切片

1、指针

引入:
指针对于性能的影响是不言而喻的,如果你想要做的是系统编程、操作系统或者网络应用,指针更是不可或缺的一部分。Go 语言为程序员提供了控制数据结构的指针的能力;但是,你不能进行指针运算。
程序在内存中存储它的值,每个内存块(或字)有一个地址,通常用十六进制数表示,如:0x6b0820,而一个指针变量指向了一个值的内存地址。
Go 语言的取地址符是 &;*号来获取指针所指向的内容,这里的*号是一个类型更改器。

//一个指针变量在32位机器上占用4个字节,在64位机器上占用8个字节,并且与它所指向的值的大小无关。当然,可以声明指针指向任何类型的值来表明它的原始性或结构性;go指针类似于java中的引用。指针创建后,被指向的变量也保存在内存中,直到没有任何指针指向它们,所以从它们被创建开始就具有相互独立的生命周期。
var a int = 10   
fmt.Printf("变量的地址: %x\n", &a  )
//这个地址可以存储在一个叫做指针的特殊数据类型中,声明格式var var_name *var-type
var intP *int//引用上例
intP=&a//intP存储了a的内存地址;它指向了a的位置,它引用了变量a。
b=*intP//符号 * 可以放在一个指针前,这被称为反引用(或者内容或者间接引用)操作符;另一种说法是指针转移。
c=&10//不能得到一个文字或常量的地址
var p *int
*p=0//对一个空指针的反向引用是不合法的,并且会使程序崩溃

一个指针导致的间接引用(一个进程执行了另一个地址),指针的过度频繁使用也会导致性能下降。

指针的应用:
☆定义一个指针数组来存储地址
☆Go 支持指向指针的指针:
可以进行任意深度的嵌套,导致你可以有多级的间接引用,但在大多数情况这会使你的代码结构不清晰。
☆通过引用或地址传参,在函数调用时可以改变其值:
这样不会传递变量的拷贝。指针传递是很廉价的,只占用 4 个或 8 个字节。当程序在工作中需要占用大量的内存,或很多变量,或者两者都有,使用指针会减少内存占用和提高效率。

2、数组与切片slice

数组:
数组是由一个固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成,数组元素可以重新赋值。数组的每个元素都可以通过下标来访问。元素的数目,也称为长度或者数组大小必须是固定的并且在声明该数组时就给出(编译时需要知道数组长度以便分配内存);数组长度最大为 2Gb。

声明格式:
var name [size]type
name2 :=[…]int{1,2,3} // 数组长度根据初始化值的个数来计算(成为切片?)
name3 :=[…]int{99:-1} // 定义一个含有100个元素的数组,最后一个元素被初始化为-1,其他元素为0
name4 :=[10]{1,2,3} // 从左边起开始忽略,除了前三个元素外其他元素都为0。
name5 :=new([5]int) // 数组是值类型,可以通过new函数创建,返回其指针

//数组长度也是数组类型的一个组成部分
a :=[3]int{1,2,3}//初始化数组中 {} 中的元素个数不能大于 [] 中的数字
a=[4]int{1,2,3,4}//编译错误,类型不同
var arr=new([5]int)//类型:*[5]int
arr2:=*arr1
arr2[2]=100

数组的初始化与遍历:

//使用for循环赋值与遍历
func main(){
	var arr [5]int
	for i:=0;i<len(arr);i++{
	arr[i]=i*2
	}
	for i:=0;i<len(arr);i++{
	fmt.Printf("index is %d ,value is %d",i,arr[i])
	}
}
//for range方式
func main(){
	a := []{1,2,3}
	for i :=range a{
	fmt.Println("index: ",i,"value: ",a[i])
	}
}

多维数组:
声明格式与一位数组相似:
var a [3][6]int

作为函数参数:
因为函数参数传递的机制导致传递大的数组类型将是低效的,并且对数组参数的任何的修改都是发生在复制的数组上,并不能直接修改调用时原始的数组变量。go语言可以通过指针来传递数组参数,而且也允许在函数内部修改数组的值,但是数组依然是僵化的类型,因为数组的类型包含了僵化的长度信息。

切片slice:
切片是一个 长度可变的数组!
一个slice是一个轻量级的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用一个数组对象。一个slice由三个部分构成:指针、长度和容量。长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置(容量即数组的长度)。内置的len和cap函数分别返回slice的长度和容量。多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠。

优点:
因为切片是引用,所以它们不需要使用额外的内存并且比使用数组更有效率,所以在 Go 代码中 切片比数组更常
用。

声明格式:
var r []type // 切片不需要说明长度。未初始化之前默认为 nil,长度为 0。
slice2 := make([]T, length, capacity)// 使用make()函数来创建切片,capacity是可选参数;make(T) 返回一个类型为 T 的初始值,它只适用于3种内建的引用类型:切片、map 和 channel

扫描二维码关注公众号,回复: 4025858 查看本文章
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:] //与string字符串截断类似
s := arr[:endIndex]
s :=make([]int,len,cap) 

示意图:
moth是一个数组;slice值包含指向第一个slice元素的指针。(复制一个slice只是对底层的数组创建了一个新的slice别名)(绝对不要用指针指向 slice。slice并不是一个纯粹的引用类型)
在这里插入图片描述
切片的比较:
只能与nil相比较,原因:第一个原因,一个slice的元素是间接引用的,一个slice甚至可以包含自身。第二个原因,因为slice的元素是间接引用的,一个固定值的slice在不同的时间可能包含不同的元素,因为底层数组的元素可能会被修改。如果你需要测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断。除了文档已经明确说明的地方,所有的Go语言函数应该以相同的方式对待nil值的slice和0长度的slice。
内置函数:

func main(){
	s := []int{1}
	s=append(s,2,3,4,5,6,)//append追加元素
	copy(s,s1)//copy复制,两个slice可以共享同一个底层数组,甚至有重叠也没有问题。
	fmt.Println(s1)
	sl = sl[0:len(sl)+1]//改变切片长度的过程称之为切片重组 reslicing
}

多维切片:
切片通常也是一维的,但是也可以由一维组合成高维。通过分片的分片(或者切片的数组),长度可以任意动态变化,所以 Go 语言的多维切片可以任意切分。而且,内层的切片必须单独分配(通过 make 函数)。

典例bytes包:
读写长度未知的 bytes 最好使用bytes包中的buffer类型,这种实现方式比使用 += 要更节省内存和 CPU,尤其是要串联的字符串数目特别多的时候。此类型有Read 和 Write 方法。

var buffer bytes.Buffer//声明
var r *bytes.Buffer = new(bytes.Buffer)//获取指针
var buffer bytes.Buffer
for {
	if s, ok := getNextString(); ok { //method getNextString() not shown here
	buffer.WriteString(s)
	} else {
	break
	}
}
fmt.Print(buffer.String(), "\n")

For——range结构:
这种结构常用于数组和切片
for index,value :=range slice{}//第一个返回值是数组或者切片的索引,第二个是在该索引位置的值;他们都是仅在 for 循环内部可见的局部变量。value 只是 slice 某个索引位置的值的一个拷贝,不能用来修改 slice 该索引位置的值。

应用:
☆从字符串生成字节切片:
假设 s 是一个字符串(本质上是一个字节数组),那么就可以直接通过 c := []byte(s) 来获取一个字节的切片 c。还可以通过 copy 函数来达到相同的目的: copy(dst []byte, src string) 。由于Unicode 字符会占用 2 个字节,有些甚至需要 3 个或者 4 个字节来进行表示。如果发现错误的 UTF8 字符,可以使用 c := []int32(s) 语法,这样切片中的每个 int 都会包含对应的 Unicode 代码,因为字符串中的每次字符都会对应一个整数。类似的,您也可以将字符串转换为元素类型为 rune 的切片: r := []rune(s) 。可以通过代码 len([]int32(s)) 来获得字符串中字符的数量,但使用 utf8.RuneCountInString(s) 效率会更高一点。
☆字符串和切片的内存结构:
在内存中,一个字符串实际上是一个双字结构,即一个指向实际数据的指针和记录字符串长度的整数。因为指针对用户来说是完全不可见,因此我们可以依旧把字符串看做是一个值类型,也就是一个字符数组。
在这里插入图片描述
☆修改字符串中的某个字符:
Go 语言中的字符串是不可变的,必须先将字符串转换成字节数组,然后再通过修改数组中的元素值来达到修改字符串的目的,最后将字节数组转换回字符串格式。

s := "hello"
c := []byte(s)
c[0] = 'c'
s2 := string(c) // s2 == "cello"

☆搜索及排序切片和数组:
标准库提供了 sort 包来实现常见的搜索和排序操作。您可以使用 sort 包中的函数 func Ints(a []int) 来实现对 int类型的切片排序,为了检查某个数组是否已经被排序,可以通过函数 IntsAreSorted(a []int) bool 来检查,如果返回 true 则表示已经被排序。类似的,可以使用函数 func Float64s(a []float64) 来排序 float64 的元素,或使用函数 func Strings(a []string) 排序字符串元素。
想要在数组或切片中搜索一个元素,该数组或切片必须先被排序(因为标准库的搜索算法使用的是二分法)。然后可以使用函数 func SearchInts(a []int, n int) int 进行搜索,并返回对应结果的索引值。

☆切片和垃圾回收:
切片的底层指向一个数组,该数组的实际容量可能要大于切片所定义的容量。只有在没有任何切片指向的时候,底层的数组内存才会被释放,这种特性有时会导致程序占用多余的内存。

猜你喜欢

转载自blog.csdn.net/ao__ao/article/details/83590863