Go 언어의 슬라이스와 배열이란 무엇입니까?

青灯夜游
풀어 주다: 2022-12-21 19:28:28
원래의
7498명이 탐색했습니다.

Go 언어에서 배열은 특정 유형의 고정 길이 요소의 시퀀스입니다. 배열은 0개 이상의 요소로 구성될 수 있습니다. 배열에 해당하는 유형은 슬라이스입니다. 슬라이스는 배열의 연속 조각에 대한 참조이므로 이 조각은 전체 배열이거나 시작 및 끝 인덱스로 식별되는 일부 항목일 수 있습니다. 종료 인덱스에 의해 식별된 항목은 조각에 포함되지 않는다는 점에 유의해야 합니다.

Go 언어의 슬라이스와 배열이란 무엇입니까?

이 튜토리얼의 운영 환경: Windows 7 시스템, GO 버전 1.18, Dell G3 컴퓨터.

1. 배열

배열은 동일한 데이터 유형의 요소 모음입니다. Go 언어에서는 선언 시점부터 배열이 결정되며, 배열 멤버는 사용 중에 수정이 가능하지만, 배열 크기는 변경할 수 없습니다. 기본 구문: 同一种数据类型元素的集合。 在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))
}
로그인 후 복사

배열의 길이는 일정해야 하며 길이는 배열 유형의 일부입니다. 한번 정의된 길이는 변경할 수 없습니다.

1. 배열 초기화

(1) 방법 1

s:[2 3] len(s):2 cap(s):4
로그인 후 복사
(2) 방법 2

일반적으로 컴파일러가 초기 값의 개수에 따라 배열의 길이를 추론하도록 할 수 있습니다

make([]T, size, cap)
로그인 후 복사
로그인 후 복사
🎜🎜( 3) 방법 3🎜🎜또한 인덱스 값을 지정하여 배열을 초기화할 수도 있습니다. 예:🎜
func main() {
	a := make([]int, 2, 10)
	fmt.Println(a)      //[0 0]
	fmt.Println(len(a)) //2
	fmt.Println(cap(a)) //10
}
로그인 후 복사
로그인 후 복사

🎜🎜2. 배열 순회🎜

🎜🎜3. 다차원 배열🎜

🎜Go 언어는 다차원 배열을 지원합니다. 여기서는 2차원 배열을 예로 들어 보겠습니다. ). 🎜🎜🎜 (1) 2차원 배열 정의 🎜
s:[2 3] len(s):2 cap(s):4
s2:[5] len(s2):1 cap(s2):1
로그인 후 복사
로그인 후 복사
🎜
🎜 (2) 2차원 배열 순회 🎜
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
로그인 후 복사
로그인 후 복사
🎜🎜🎜참고: 🎜🎜 다차원 배열 첫 번째 수준에서만 ...를 사용하여 컴파일러가 배열 길이를 추론할 수 있습니다. 예: 🎜
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. 배열은 값 유형입니다.🎜

🎜배열은 값 유형입니다. 할당 및 매개변수 전달은 전체 배열을 복사합니다. 따라서 는 자신의 값을 변경하지 않고 복사본의 값을 변경합니다. 🎜
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)
	}
}
로그인 후 복사
로그인 후 복사
🎜🎜🎜참고: 🎜🎜🎜
  • 어레이는 메모리가 항상 초기화되기 때문에 "==", "!=" 연산자를 지원합니다.
  • [n]*T는 포인터 배열을 나타냅니다(이것은 배열이며 내부 요소는 하나씩 포인터입니다)
  • *[n]T는 배열 포인터를 나타냅니다(이것은 배열의 메모리 주소를 저장하는 포인터입니다)
🎜
🎜🎜2. Slice🎜🎜🎜Slice는 동일한 유형의 요소가 포함된 가변 길이 시퀀스입니다. 이는 배열 유형을 기반으로 한 캡슐화 계층입니다. 매우 유연하며 자동 확장을 지원합니다. 🎜🎜A 슬라이스는 🎜🎜참조 유형🎜🎜이며 내부 구조에는 주소, 길이용량이 포함됩니다. 슬라이스는 일반적으로 데이터 모음에 대해 신속하게 작업하는 데 사용됩니다. 🎜🎜슬라이스는 배열의 연속 조각에 대한 참조이므로 슬라이스는 참조 유형입니다(C/C++의 배열 유형 또는 Python의 목록 유형과 더 유사함). 이 조각은 전체 배열이 될 수 있습니다. , 시작 및 끝 인덱스로 식별되는 일부 항목의 하위 집합일 수도 있습니다. 끝 인덱스로 식별되는 항목은 조각에 포함되지 않습니다.

Go 언어의 슬라이스 내부 구조에는 주소, 크기 및 용량이 포함됩니다. 슬라이스는 일반적으로 데이터 세트를 빠르게 조작하는 데 사용됩니다. 데이터 세트를 케이크 자르기에 비유하면 슬라이스입니다. "원피스" 절단 과정에는 시작할 위치(슬라이스의 시작 위치)와 절단할 크기(슬라이스의 크기)가 포함됩니다. 용량은 슬라이스를 담는 주머니의 크기로 이해할 수 있습니다. 🎜

🎜🎜1. 슬라이스 정의🎜

🎜슬라이스 유형 선언의 기본 구문은 다음과 같습니다. 🎜
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 s []int
s = append(s, 1, 2, 3)
로그인 후 복사
로그인 후 복사

🎜🎜2. 슬라이스의 길이와 용량🎜

🎜슬라이스에는 자체 길이와 용량이 있습니다. 내장된 len() 함수를 사용하여 길이와 빌드된 값을 찾을 수 있습니다. -cap() 함수에서 슬라이스의 용량을 찾습니다. 🎜

🎜🎜3. 슬라이스 표현식 🎜

🎜슬라이스 표현식 문자열, 배열 또는 배열이나 슬라이스에 대한 포인터에서 하위 문자열이나 슬라이스를 구성합니다.< /code >. 여기에는 두 가지 변형이 있습니다. <code>낮은 인덱스 경계와 높은 인덱스 경계를 ​​지정하는 간단한 형식과 낮은 인덱스 경계와 높은 인덱스 경계 외에 용량을 지정하는 완전한 형식입니다. . 🎜🎜🎜완전한 슬라이싱 표현은 쓸모가 없습니다. 여기서는 간단한 슬라이싱 표현만 이야기하겠습니다! 🎜🎜
s := []int{}  // 没有必要初始化
s = append(s, 1, 2, 3)

var s = make([]int)  // 没有必要初始化
s = append(s, 1, 2, 3)
로그인 후 복사
로그인 후 복사
🎜실행 결과: 🎜
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) make() 함수를 사용하여 슬라이스 구성 🎜🎜우리 모두는 배열을 기반으로 슬라이스를 생성했습니다. make() 함수에서 형식은 다음과 같습니다. 🎜
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],相应示意图如下。

Go 언어의 슬라이스와 배열이란 무엇입니까?

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

Go 언어의 슬라이스와 배열이란 무엇입니까?

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

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

Go 언어의 슬라이스와 배열이란 무엇입니까?

上面是切片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
		}
	}
}
로그인 후 복사
로그인 후 복사

Go 언어의 슬라이스와 배열이란 무엇입니까?

(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视频教程编程教学

위 내용은 Go 언어의 슬라이스와 배열이란 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!