Heim >Backend-Entwicklung >Golang >Was sind Slices und Arrays in der Go-Sprache?

Was sind Slices und Arrays in der Go-Sprache?

青灯夜游
青灯夜游Original
2022-12-21 19:17:247583Durchsuche

In der Go-Sprache ist ein Array eine Folge von Elementen fester Länge eines bestimmten Typs. Es handelt sich um eine Sammlung von Elementen desselben Datentyps. Ein Array kann aus null oder mehr Elementen bestehen. Der einem Array entsprechende Typ ist Slice. Ein Slice ist ein Referenztyp. Dieses Fragment kann das gesamte Array oder einige Elemente sein, die durch die Start- und Endindizes identifiziert werden. Es ist zu beachten, dass die durch den Abschlussindex identifizierten Elemente nicht im Slice enthalten sind.

Was sind Slices und Arrays in der Go-Sprache?

Die Betriebsumgebung dieses Tutorials: Windows 7-System, GO Version 1.18, Dell G3-Computer.

1. Array

Ein Array ist eine Sammlung von Elementen desselben Datentyps. In der Go-Sprache wird das Array anhand der Deklaration bestimmt und die Array-Mitglieder können bei Verwendung geändert werden, die Array-Größe kann jedoch nicht geändert werden. Grundlegende Syntax: 同一种数据类型元素的集合。 在Go语言中,数组从声明时就确定,使用时可以修改数组成员,但是数组大小不可变化。 基本语法:

// 定义一个长度为3元素类型为int的数组a
var a [3]int

数组的长度必须是常量,并且长度是数组类型的一部分。一旦定义,长度不能变

1、数组的初始化

(1)方法一

	var testArray [3]int               // 定义数组时,会初始化int类型为零值
	var cityArray = [3]string{"北京", "上海", "深圳"} // 使用指定的初始值完成初始化

(2)方法二

一般情况下我们可以让编译器根据初始值的个数自行推断数组的长度

var cityArray = [...]string{"北京", "上海", "深圳"}

(3)方法三

我们还可以使用指定索引值的方式来初始化数组,例如:

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
}

2、数组的遍历

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)
	}
}

3、多维数组

Go语言是支持多维数组的,我们这里以二维数组为例(数组中又嵌套数组)。

(1)二维数组的定义

func main() {
	a := [3][2]string{
		{"北京", "上海"},
		{"广州", "深圳"},
		{"成都", "重庆"},
	}
	fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]]
	fmt.Println(a[2][1]) //支持索引取值:重庆
}

(2)二维数组的遍历

func main() {
	a := [3][2]string{
		{"北京", "上海"},
		{"广州", "深圳"},
		{"成都", "重庆"},
	}
	for _, v1 := range a {
		for _, v2 := range v1 {
			fmt.Printf("%s\t", v2)
		}
		fmt.Println()
	}
}

注意: 多维数组只有第一层可以使用...来让编译器推导数组长度。例如:

a := [...][2]string{
	{"北京", "上海"},
	{"广州", "深圳"},
	{"成都", "重庆"},
}

4、数组是值类型

数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。

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]]
}

注意:

  • 数组支持 “==“、”!=” 操作符,因为内存总是被初始化过的。
  • [n]*T表示指针数组(这是一个数组,里面元素是一个个的指针)
  • *[n]T表示数组指针 (这是一个指针,存的是一个数组的内存地址)

二、切片

切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容

切片是一个 引用类型,它的内部结构包含地址长度容量。切片一般用于快速地操作一块数据集合。

切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型),这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内。

Go语言中切片的内部结构包含地址、大小和容量,切片一般用于快速地操作一块数据集合,如果将数据集合比作切糕的话,切片就是你要的“那一块”,切的过程包含从哪里开始(切片的起始位置)及切多大(切片的大小),容量可以理解为装切片的口袋大小。

1、切片的定义

声明切片类型的基本语法如下:

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、切片的长度和容量

切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。

3、切片表达式

切片表达式从字符串、数组、指向数组或切片的指针构造子字符串或切片。它有两种变体:一种指定low和high两个索引界限值的简单的形式,另一种是除了low和high索引界限值外还指定容量的完整的形式

// 简单切片表达式
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))
}

Die Länge des Arrays muss konstant sein und die länge ist Teil des Array-Typs. Einmal definiert, kann die Länge nicht geändert werden

1 Initialisierung des Arrays

(1) Methode 1

s:[2 3] len(s):2 cap(s):4
(2) Methode 2

Im Allgemeinen können wir den Compiler die Länge des Arrays basierend auf der Anzahl der Anfangswerte ableiten lassen

make([]T, size, cap)
🎜🎜( 3) Methode drei🎜🎜Wir können das Array auch initialisieren, indem wir den Indexwert angeben, zum Beispiel:🎜
func main() {
	a := make([]int, 2, 10)
	fmt.Println(a)      //[0 0]
	fmt.Println(len(a)) //2
	fmt.Println(cap(a)) //10
}

🎜🎜2. Array-Traversal🎜

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))
}

🎜🎜3. Mehrdimensionale Arrays🎜

🎜Die Go-Sprache unterstützt mehrdimensionale Arrays. Hier nehmen wir ein zweidimensionales Array als Beispiel (Arrays sind in Arrays verschachtelt). ). 🎜🎜🎜 (1) Definition eines zweidimensionalen Arrays 🎜
s:[2 3] len(s):2 cap(s):4
s2:[5] len(s2):1 cap(s2):1
🎜
🎜 (2) Durchquerung eines zweidimensionalen Arrays 🎜
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
🎜🎜🎜Hinweis: 🎜🎜 Mehrdimensionales Array Nur die erste Ebene kann ... verwenden, damit der Compiler die Array-Länge ableiten kann. Beispiel: 🎜
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]
}

🎜🎜4. Arrays sind Werttypen🎜

🎜Arrays sind Werttypen. Daher ändert den Wert der Kopie, ohne seinen eigenen Wert zu ändern. 🎜
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)
	}
}
🎜🎜🎜Hinweis: 🎜🎜🎜
  • Arrays unterstützen die Operatoren „==", „!=", da der Speicher immer initialisiert wird.
  • [n]*T stellt ein Array von Zeigern dar (dies ist ein Array, und die darin enthaltenen Elemente sind nacheinander Zeiger)
  • *[n]T stellt einen Array-Zeiger dar (dies ist ein Zeiger, der die Speicheradresse eines Arrays speichert)
🎜🎜🎜2. Slice ist eine Sequenz variabler Länge mit Elementen desselben Typs. Es handelt sich um eine Kapselungsschicht basierend auf dem Array-Typ. Es ist sehr flexibel und unterstützt die automatische Erweiterung. 🎜🎜Ein Slice ist ein 🎜🎜Referenztyp🎜🎜 und seine interne Struktur enthält Adresse, Länge und Kapazität. Slices werden im Allgemeinen verwendet, um einen Datensatz schnell zu bearbeiten. 🎜🎜Ein Slice ist ein Verweis auf ein kontinuierliches Fragment eines Arrays, daher ist ein Slice ein Referenztyp (also eher dem Array-Typ in C/C++ oder dem Listentyp in Python ähnlich). Dieses Fragment kann das gesamte Array sein Es kann sich auch um eine Teilmenge einiger durch den Start- und Endindex identifizierter Elemente handeln. Es ist zu beachten, dass die durch den Endindex identifizierten Elemente nicht im Slice enthalten sind.

Die interne Struktur von Slices in der Go-Sprache umfasst im Allgemeinen Adresse, Größe und Kapazität, um einen Datensatz schnell zu verarbeiten „Dann“ „Ein Stück“, der Schneidevorgang umfasst, wo begonnen werden soll (die Startposition der Scheibe) und wie groß geschnitten werden soll (die Größe der Scheibe). Die Kapazität kann als die Größe der Tasche verstanden werden, die die Scheibe enthält Scheibe. 🎜

🎜🎜1. Definition von Slices🎜

🎜Die grundlegende Syntax zum Deklarieren von Slice-Typen lautet wie folgt: 🎜
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打散列表
🎜Zum Beispiel: 🎜
var s []int
s = append(s, 1, 2, 3)

🎜🎜2. Die Länge und Kapazität des Slice🎜

🎜Der Slice hat seine eigene Länge und Kapazität, um die Länge und die integrierte Funktion zu ermitteln. in cap() Funktion, um die Kapazität des Slice zu ermitteln. 🎜

🎜🎜3. Slice-Ausdruck 🎜

🎜Slice-Ausdruck Konstruiert einen Teilstring oder ein Slice aus einem String, einem Array oder einem Zeiger auf ein Array oder einen Slice. Es gibt zwei Varianten: eine einfache Form, die <code>zwei Indexgrenzen angibt, niedrig und hoch, und eine vollständige Form, die zusätzlich zur unteren und oberen Indexgrenze die Kapazität angibt. 🎜🎜🎜Vollständige Slicing-Ausdrücke sind nutzlos, hier sprechen wir nur über einfache Slicing-Ausdrücke! 🎜🎜
s := []int{}  // 没有必要初始化
s = append(s, 1, 2, 3)

var s = make([]int)  // 没有必要初始化
s = append(s, 1, 2, 3)
🎜Ausführungsergebnisse: 🎜
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
		}
	}
}
🎜🎜🎜 (1) Verwenden Sie die Funktion make(), um Slices zu erstellen🎜🎜Wir haben alle Slices basierend auf Arrays erstellt. Wenn wir ein Slice dynamisch erstellen müssen, müssen wir die eingebaute Funktion verwenden. In der Funktion make() ist das Format wie folgt: 🎜
make([]T, size, cap)
  • 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)则返回该切片的容量。

(2)切片的本质

切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中

切片的本质 就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)

举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],相应示意图如下。

Was sind Slices und Arrays in der Go-Sprache?

切片s2 := a[3:6],相应示意图如下:

Was sind Slices und Arrays in der Go-Sprache?

如果你懂了切片的本质,那么试试下面这个题吧!

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容量

Was sind Slices und Arrays in der Go-Sprache?

上面是切片s生成的过程,现在又要切片取[3:4],从s的起点开始数,我们可以很容易看出来[3:4]是5。

(3)切片不能直接比较

切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和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为唯一判断条件。

(4)切片的赋值拷贝

下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

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]
}

(5)切片遍历

切片的遍历方式和数组是一致的,支持索引遍历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)
	}
}

(6)append()方法为切片添加元素

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函数的返回值

(7)切片的扩容策略

可以通过查看$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
		}
	}
}

Was sind Slices und Arrays in der Go-Sprache?

(8) 使用copy()函数复制切片

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了
}

(9)从切片中删除元素

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:]...)

(10)内存优化

切片持有对底层数组的引用。只要切片在内存中,数组就不能被垃圾回收。在内存管理方面,这是需要注意的。让我们假设我们有一个非常大的数组,我们只想处理它的一小部分。然后,我们由这个数组创建一个切片,并开始处理切片。这里需要重点注意的是,在切片引用时数组仍然存在内存中。

一种解决方法是使用上面的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视频教程编程教学

Das obige ist der detaillierte Inhalt vonWas sind Slices und Arrays in der Go-Sprache?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn