Go 言語では、配列は特定の型の固定長要素のシーケンスです。同じデータ型の要素のコレクションです。配列は 0 個以上の要素で構成できます。配列に対応する型はスライスです。スライスは配列の連続フラグメントへの参照であるため、スライスは参照型です。このフラグメントは、配列全体、または開始インデックスと終了インデックスによって識別される一部の項目にすることができます。サブセット、終端インデックスによって識別される項目はスライスに含まれないことに注意してください。
このチュートリアルの動作環境: Windows 7 システム、GO バージョン 1.18、Dell G3 コンピューター。
配列は、同じデータ型の要素のコレクションです。 Go 言語では、配列は
宣言された時点で決まります 配列のメンバーは使用中に変更できますが、
配列のサイズは変更できません 基本構文:
// 定义一个长度为3元素类型为int的数组a var a [3]int配列の長さは定数である必要があり、
長さは配列型の一部です。一度定義した長さは変更できません
var testArray [3]int // 定义数组时,会初始化int类型为零值 var cityArray = [3]string{"北京", "上海", "深圳"} // 使用指定的初始值完成初始化一般に、初期値の数に基づいてコンパイラに配列の長さを推測させることができます
var cityArray = [...]string{"北京", "上海", "深圳"}#インデックス値を指定して配列を初期化することもできます。たとえば、
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) } }
注: 多次元配列 最初のレベル のみが ...
を使用してコンパイラに配列の長さを推定させることができます。例: <pre class="brush:js;toolbar:false;">a := [...][2]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}</pre>
<pre class="brush:js;toolbar:false;">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]]
}</pre>
注:
配列では、メモリが限られているため、「==」、「!=」演算子がサポートされています。常に初期化されています。、内部の要素は 1 つずつポインターです)
* [n]T です)
カプセル化層 です。非常に柔軟性が高く、
自動拡張をサポートしています。
スライスは
であり、その内部構造には address、length、
capacity# が含まれています##。スライスは通常、データのコレクションを迅速に操作するために使用されます。 スライスは配列の連続フラグメントへの参照であるため、スライスは参照型です (つまり、C/C の配列型、または Python のリスト型に似ています)。これは配列全体であるか、開始インデックスと終了インデックスによって識別される一部の項目のサブセットである可能性があります。終了インデックスによって識別される項目はスライスに含まれないことに注意してください。
Go 言語のスライスの内部構造には、アドレス、サイズ、容量が含まれます。スライスは通常、データ セットをすばやく操作するために使用されます。データ セットをケーキのカットにたとえると、スライスは「ピース」です。切断プロセスには、どこから開始するか (スライスの開始位置)、どのくらいの大きさに切断するか (スライスのサイズ) が含まれます。容量は、スライスを保持するポケットのサイズとして理解できます。
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比较 }2. スライスの長さと容量
がインデックスの下限値と上限値に加えて容量も指定する完全な形式です。値。フォーム。
完全なスライス式は役に立ちません。ここでは単純なスライス式についてのみ説明します。
// 简单切片表达式 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) make() 関数を使用してスライスを構築します私たちは皆、以下に基づいてスライスを作成しました。配列 スライスを動的に作成する必要がある場合は、組み込みの make() 関数を使用する必要があります。形式は次のとおりです。
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)是一个拥有相同类型元素的可变长序列,可以方便地进行扩容和传递,实际使用时比数组更加灵活,这也正是切片存在的意义。而且切片是引用类型,因此在当传递切片时将引用同一指针,修改值将会影响其他的对象。
以上がGo言語のスライスと配列とは何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。