Rumah >pembangunan bahagian belakang >Golang >Apakah hirisan dan tatasusunan dalam bahasa go
Dalam bahasa Go, tatasusunan ialah jujukan elemen panjang tetap bagi jenis tertentu Ia ialah himpunan elemen jenis data yang sama Satu tatasusunan boleh terdiri daripada sifar atau lebih elemen. Jenis yang sepadan dengan tatasusunan ialah Slice A slice ialah rujukan kepada serpihan berterusan tatasusunan, jadi serpihan ialah jenis rujukan ini boleh menjadi keseluruhan tatasusunan atau beberapa item yang dikenal pasti oleh indeks permulaan dan akhir. perlu diingatkan bahawa item yang dikenal pasti oleh indeks penamatan tidak termasuk dalam kepingan.
Persekitaran pengendalian tutorial ini: sistem Windows 7, GO versi 1.18, komputer Dell G3.
Tatasusunan ialah 同一种数据类型元素的集合
. Dalam bahasa Go, tatasusunan bermula daripada 声明时就确定
dan ahli tatasusunan boleh diubah suai apabila menggunakan, tetapi 数组大小不可变化
. Sintaks asas:
// 定义一个长度为3元素类型为int的数组a var a [3]int
Panjang tatasusunan mestilah pemalar dan 长度是数组类型的一部分。一旦定义,长度不能变
var testArray [3]int // 定义数组时,会初始化int类型为零值 var cityArray = [3]string{"北京", "上海", "深圳"} // 使用指定的初始值完成初始化Secara amnya kita boleh membiarkan pengkompil berasaskan pada nilai awal Simpulkan panjang tatasusunan sendiri
var cityArray = [...]string{"北京", "上海", "深圳"}Kita juga boleh memulakan tatasusunan dengan menyatakan nilai indeks, contohnya:
func main() { a := [...]int{1: 1, 3: 5} fmt.Println(a) // [0 1 0 5] fmt.Printf("type of a:%T\n", a) //type of a:[4]int }
func main() { var a = [...]string{"北京", "上海", "深圳"} // 方法1:for循环遍历 for i := 0; i < len(a); i++ { fmt.Println(a[i]) } // 方法2:for range遍历 for index, value := range a { fmt.Println(index, value) } }
(1) Definisi tatasusunan dua dimensi
func main() { a := [3][2]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, } fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]] fmt.Println(a[2][1]) //支持索引取值:重庆 }
(2) Traversal tatasusunan dua dimensi
func main() { a := [3][2]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, } for _, v1 := range a { for _, v2 := range v1 { fmt.Printf("%s\t", v2) } fmt.Println() } }
Nota: Tatasusunan berbilang dimensi boleh menggunakan 只有第一层
untuk membenarkan pengkompil menyimpulkan panjang tatasusunan. Contohnya: ...
a := [...][2]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, }
改变副本的值,不会改变本身的值。
func modifyArray(x [3]int) { x[0] = 100 } func modifyArray2(x [3][2]int) { x[2][0] = 100 } func main() { a := [3]int{10, 20, 30} modifyArray(a) //在modify中修改的是a的副本x fmt.Println(a) //[10 20 30] b := [3][2]int{ {1, 1}, {1, 1}, {1, 1}, } modifyArray2(b) //在modify中修改的是b的副本x fmt.Println(b) //[[1 1] [1 1] [1 1]] }
Nota:
[n]*T
, elemen di dalamnya adalah penunjuk) 数组
*[n]T
, disimpan Merupakan alamat memori tatasusunan) 指针
. Ia sangat fleksibel, 可变长度的序列
. Potongan 基于数组类型做的一层封装
支持自动扩容
ialah jenis rujukan
dan struktur dalamannya mengandungi , dan 地址
. Slices biasanya digunakan untuk beroperasi dengan cepat pada koleksi data. 长度
容量
Sekian ialah rujukan kepada serpihan bersebelahan tatasusunan, jadi hirisan ialah jenis rujukan (dan oleh itu lebih serupa dengan jenis tatasusunan dalam C/C++, atau jenis senarai dalam Python ini). boleh Ia adalah keseluruhan tatasusunan, atau ia boleh menjadi subset beberapa item yang dikenal pasti oleh indeks permulaan dan akhir Perlu diingatkan bahawa item yang dikenal pasti oleh indeks akhir tidak termasuk dalam kepingan.
Struktur dalaman hirisan dalam bahasa Go termasuk alamat, saiz dan kapasiti Kepingan biasanya digunakan untuk mengendalikan set data dengan cepat Jika set data dibandingkan dengan memotong kek, kepingan adalah "kepingan " anda mahu. , proses pemotongan termasuk di mana untuk bermula (kedudukan permulaan kepingan) dan berapa besar untuk dipotong (saiz kepingan). Kapasiti boleh difahami sebagai saiz poket yang memegang kepingan.
var name []T // name:表示变量名 // T:表示切片中的元素类型
func main() { // 声明切片类型 var a []string //声明一个字符串切片 var b = []int{} //声明一个整型切片并初始化 var c = []bool{false, true} //声明一个布尔切片并初始化 var d = []bool{false, true} //声明一个布尔切片并初始化 fmt.Println(a == nil) //true fmt.Println(b == nil) //false fmt.Println(c == nil) //false // fmt.Println(c == d) //切片是引用类型,不支持直接比较,只能和nil比较 }
dan satu 从字符串、数组、指向数组或切片的指针构造子字符串或切片
kecuali untuk sempadan indeks rendah dan tinggi. 指定low和high两个索引界限值的简单的形式
还指定容量的完整的形式
Keputusan berjalan:
// 简单切片表达式 func main() { a := [5]int{1, 2, 3, 4, 5} s := a[1:3] // s := a[low:high] fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s)) }
s:[2 3] len(s):2 cap(s):4
(1) Gunakan fungsi make() untuk membina kepingan Kami semua di atas Jika anda perlu mencipta kepingan secara dinamik berdasarkan tatasusunan, kita perlu menggunakan fungsi make() terbina dalam, formatnya adalah seperti berikut:
make([]T, size, cap)
举个栗子:
func main() { a := make([]int, 2, 10) fmt.Println(a) //[0 0] fmt.Println(len(a)) //2 fmt.Println(cap(a)) //10 }
上面代码中a
的内部存储空间已经分配了10个,但实际上只用了2个。 容量并不会影响当前元素的个数,所以len(a)
返回2,cap(a)
则返回该切片的容量。
切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中
。
切片的本质 就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)
。
举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],相应示意图如下。
切片s2 := a[3:6],相应示意图如下:
如果你懂了切片的本质,那么试试下面这个题吧!
func main() { a := [5]int{1, 2, 3, 4, 5} s := a[1:3] // s := a[low:high] fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s)) s2 := s[3:4] // 索引的上限是cap(s)而不是len(s),可能认为cap是2?切片是从原数组中元素2开始切走的 fmt.Printf("s2:%v len(s2):%v cap(s2):%v\n", s2, len(s2), cap(s2)) }
运行结果:
s:[2 3] len(s):2 cap(s):4 s2:[5] len(s2):1 cap(s2):1
s2什么鬼?[2 3][3:4]
这个能运行?如果有这样的疑惑,说明你并没有认识到切片的本质,下面我们来看一个图:
注意切片的本质是一个指向底层数组的起点的指针
,切片len有效长度
,以及cap容量
。
上面是切片s生成的过程,现在又要切片取[3:4],从s的起点开始数
,我们可以很容易看出来[3:4]是5。
切片之间是不能比较的
,我们不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil比较
。 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0
。但是我们不能说一个长度和容量都是0的切片一定是nil
,例如下面的示例:
var s1 []int //len(s1)=0;cap(s1)=0;s1==nil s2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nil s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil
所以要判断一个切片是否是空的,要是用len(s) == 0
来判断,不应该使用s == nil
来判断。
注意:nil和空不是一个概念,nil的判断是有无底层数组,s2、s3初始化了的,其实是有底层数组的,s1只是声明,因此没有底层数组为nil。是否为空,则len是否为0为唯一判断条件。
下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容
,这点需要特别注意。
func main() { s1 := make([]int, 3) //[0 0 0] s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组 s2[0] = 100 fmt.Println(s1) //[100 0 0] fmt.Println(s2) //[100 0 0] }
切片的遍历方式和数组是一致的,支持索引遍历
和for range
遍历。
func main() { s := []int{1, 3, 5} for i := 0; i < len(s); i++ { fmt.Println(i, s[i]) } for index, value := range s { fmt.Println(index, value) } }
Go语言的内建函数append()可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。
func main(){ var s []int s = append(s, 1) // [1] s = append(s, 2, 3, 4) // [1 2 3 4] s2 := []int{5, 6, 7} s = append(s, s2...) // [1 2 3 4 5 6 7] } // 这个...类似于python中的*args打散列表
注意: 通过var声明的零值切片可以在append()函数直接使用,无需初始化。
var s []int s = append(s, 1, 2, 3)
没有必要像下面的代码一样初始化一个切片再传入append()函数使用
s := []int{} // 没有必要初始化 s = append(s, 1, 2, 3) var s = make([]int) // 没有必要初始化 s = append(s, 1, 2, 3)
每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”
,此时该切片指向的底层数组就会更换
。“扩容”操作往往发生在append()函数调用时,所以
我们通常都需要用原变量接收append函数的返回值
。
可以通过查看$GOROOT/src/runtime/slice.go
源码,其中扩容相关代码如下:
newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.len < 1024 { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { newcap += newcap / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { newcap = cap } } }
func main() { a := []int{1, 2, 3, 4, 5} b := a fmt.Println(a) //[1 2 3 4 5] fmt.Println(b) //[1 2 3 4 5] b[0] = 1000 fmt.Println(a) //[1000 2 3 4 5] fmt.Println(b) //[1000 2 3 4 5] }
由于切片是引用类型
,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化
。
Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中
,copy()函数的使用方法如下:
func main() { // copy()复制切片 a := []int{1, 2, 3, 4, 5} c := make([]int, 5, 5) copy(c, a) //使用copy()函数将切片a中的元素复制到切片c fmt.Println(a) //[1 2 3 4 5] fmt.Println(c) //[1 2 3 4 5] c[0] = 1000 fmt.Println(a) //[1 2 3 4 5] fmt.Println(c) //[1000 2 3 4 5] // 再对切片c操作,就不会影响a了 }
Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:
func main() { // 从切片中删除元素 a := []int{30, 31, 32, 33, 34, 35, 36, 37} // 要删除索引为2的元素 a = append(a[:2], a[3:]...) // 把index=2之后的切片和index=2之前的切片拼接在一起 fmt.Println(a) //[30 31 33 34 35 36 37] }
切片a中删除索引为index
的元素,操作方法是a = append(a[:index], a[index+1:]...)
切片持有对底层数组的引用。只要切片在内存中,数组就不能被垃圾回收
。在内存管理方面,这是需要注意的。让我们假设我们有一个非常大的数组,我们只想处理它的一小部分
。然后,我们由这个数组创建一个切片,并开始处理切片。这里需要重点注意的是,在切片引用时数组仍然存在内存中。
一种解决方法是使用上面的copy函数,根据切片生成一个一模一样的新切片
。这样我们可以使用新的切片,原始数组可以被垃圾回收。
package mainimport ( "fmt")func countries() []string { a := []string{1, 2, 3, 4, 5} b := a[:len(a)-2] c := make([]string, len(b)) copy(c, b) // 将b的内容copy给c return c}func main() { d := countries() fmt.Println(d) }
b := a[:len(a)-2]
创建一个去掉a的尾部 2 个元素的切片 b,在上述程序的 11 行,将 切片b 复制到 切片c。同时在函数的下一行返回 切片c。现在 a 数组可以被垃圾回收, 因为数组a不再被引用。
Go 数组与像 C/C++等语言中数组略有不同:
1. Go 中的数组是值类型,换句话说,如果你将一个数组赋值给另外一个数组,那么,实际上就是将整个数组拷贝一份。因此,在 Go 中如果将数组作为函数的参数传递的话,那效率就肯定没有传递指针高了。
2. 数组的长度也是类型的一部分,这就说明[10]int和[20]int不是同一种数据类型。并且Go 语言中数组的长度是固定的,且不同长度的数组是不同类型,这样的限制带来不少局限性。
3. 而切片则不同,切片(slice)是一个拥有相同类型元素的可变长序列,可以方便地进行扩容和传递,实际使用时比数组更加灵活,这也正是切片存在的意义。而且切片是引用类型,因此在当传递切片时将引用同一指针,修改值将会影响其他的对象。
Atas ialah kandungan terperinci Apakah hirisan dan tatasusunan dalam bahasa go. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!