24、Go 复合数据类型 - 数组
hi,我是温新
Go 语言中,复合类型有:array(数组)、slice(切片)、哈希表(map)、解构体(struct)。
数组的概念
**数组是相同类型的一组数据构成的长度固定的序列。**其数据类型包含基本数据类型、复合数据类型和自定义类型。数组中的每一项是数组的元素。数组名是数组的唯一标识,数组的每一个元素都是没有名字的,只能通过索引下标进行访问。
数组声明的语法
Go 语言中,数组声明需要指定元素类型及元素个数。语法如下:
// 格式
var 变量名 [数组长度] 数据类型
定义数组
1、数组一旦声明,其存储的数据类型和长度便不能修改;
2、数组初始化时,其每个元素都被初始化为对应类型的零值;
3、数组字面量初始化数组时,数组元素个数不能大于指定的数组长度;
声明数组
package main
import "fmt"
func main() {
// 声明数组,不赋值,默认用对应类型的零值
var array [5]int
// 因此数组的值都是 0
fmt.Println(array)
// 数组赋值
array[0] = 10
fmt.Println(array)
}
输出结果为
[0 0 0 0 0]
数组字面量创建数组
使用数组字面量可以快速创建并初始化数组,数组字面量可以声明数组中元素的数量,并指定每个元素的值。
指定数组长度
package main
import "fmt"
func main() {
/**
* 声明一个包含 5 个元素的整型数组
* 使用具体值初始化每个元素
*/
arr := [5]int{1, 2, 3, 4, 5}
fmt.Println(arr)
}
不指定数组长度,使用 ...
package main
import "fmt"
func main() {
/**
* 声明一个字符串型数组
* 使用具体值初始化数组的每个元素
* 数组容量由初始化值的数据决定
*/
arr := [...]string{"王美丽", "郝帅"}
fmt.Println(arr)
}
为数组下标指定值
package main
import "fmt"
func main() {
// 为数组下标指定值
arr := [5]int{0: 10, 2: 30}
fmt.Println(arr)
}
输出结果
// 结果
[10 0 30 0 0]
数组的访问与修改
Go 语言中,数组通过下标(索引位置)来读取或修改数组元素。数组下标从 0 开始,第一个元素索引为 0,第二个为 1,以此类推。
访问数组元素
package main
import "fmt"
func main() {
arr := [3]string{"王美丽", "大漂亮", "王小丽"}
// 通过下标取出数组元素
fmt.Println(arr[1])
// 遍历数组元素
for i := 0; i < len(arr); i++ {
fmt.Printf("数组下标 %d 的值是 %s \n", i, arr[i])
}
}
输出结果
大漂亮
数组下标 0 的值是 王美丽
数组下标 1 的值是 大漂亮
数组下标 2 的值是 王小丽
通过 Go 的内置函数 len()
可以获取数组的长度。
修改数组元素
1、访问数组元素时,使用[下标]
的方式,修改数组元素的值时,也使用这种方式。
package main
import "fmt"
func main() {
// 声明一个包含 5 个元素的整型数组,并初始化值
arr := [5]int{10, 20, 30, 40, 50}
// 通过索引修改数组元素的值
arr[0] = 100
fmt.Println(arr) // 输出结果 [100 20 30 40 50]
}
2、数组的值可以是指针,使用 *
访问元素指针所指向的值
package main
import "fmt"
func main() {
/**
* 声明包含 5 个元素的整型数组
* 使用指针初始化化索引为 0 和 1 的数组元素
**/
arr := [5]*int{0: new(int), 1: new(int)}
*arr[0] = 10
*arr[1] = 20
fmt.Println(*arr[0])
fmt.Println(*arr[1])
}
这里需要需要注意,声明指针类型数组时使用了 *int
,初始化数组元素值时使用了 new
函数。关于 new
函数会在后续的文章中学习。
3、数组是值类型
在 Go 语言中,数组是一个值类型,而不是引用类型。当数组被分配给一个新变量时,会将原数组复制出一份分配给新变量。因此,对新变量进行修改时,原数组不受影响。
package main
import "fmt"
func main() {
// 声明并初始化一个数组
names := [2]string{"王美丽", "王小丽"}
// 数组数组值给新变量
copyNames := names
// 修改新变量的数组元素值
copyNames[0] = "郝帅"
fmt.Println("原数组的值", names)
fmt.Println("新数组的值", copyNames)
}
输出结果
原数组的值 [王美丽 王小丽]
新数组的值 [郝帅 王小丽]
从输出结果中可以看出,修改新变量数组元素的值不会影响原始数组。
数组作为一个变量类型,它包括数组长度
和每个元素的类型
两个部分。只有这个两个部分全部相同的数组才是类型相同的数组,才可以进行相互赋值,不然会报错。
如果是指针数组进行复制,那么只会复制指针的值,而不会复制指针所指向的值,而此时无论是修改哪个数组的值,彼此都会受到影响,案例如下:
package main
import "fmt"
func main() {
// 指针赋值
arr := [2]*int{0: new(int), 1: new(int)}
c := arr
*c[0] = 10
fmt.Println("原数组的值", *arr[0])
fmt.Println("新数组的值", *c[0])
}
输出结果如下
原数组的值 10
新数组的值 10
多维数组
数组本身只有一个维度,但可以组合多个数组创建多维数组。多维数组用于管理具有依赖关系的数据。
多维数组声明语法
// 格式
var 变量名 [组度][长度]...[长度N] 变量类型
二维数组
二维数组是最简单的数组,其本质也是一个一维数组,只是数组成员由基本数据类型变成了构造数据类型。
二维数组定义方式:
// 格式
var 变量名 [长度][长度] 变量类型
二维数组案例
声明二维数组
package main
import "fmt"
func main() {
/**
* 方式一 先声明后赋值
**/
// 声明一个二维整型数组,两个维度分别存储 4 个元素和 2 个元素
var arr [2][2]int
// 赋值
arr = [2][2]int{
{10, 11},
{20, 21},
}
fmt.Println(arr)
/**
* 方式二:数组字面量声明并初始化数组
**/
arr1 := [2][2]int{
{10, 10},
{20, 20},
}
fmt.Println(arr1)
}
多维数组的访问还是使用 []
。
二维数组的操作
package main
import "fmt"
func main() {
arr := [2][2]int{
{10, 10},
{20, 20},
}
// 访问数组元素
fmt.Println(arr[0][1])
// 修改数组元素
arr[0][0] = 100
fmt.Println(arr)
// 遍历二维数组
for i := 0; i < len(arr); i++ {
for j := 0; j < len(arr[0]); j++ {
fmt.Printf("arr[%d][%d] = %d\n", i, j, arr[i][j])
}
}
}
三维数组
三维数组本质上还是一个数组,只是数组成员由基本数据类型变成了构造数据类型(二维数组),其语法如下:
var 变量名 [长度][长度][长度]
将数组传递给函数
在 Go 语言中,数组是一个值类型,所有值类型变量在赋值和作为参数传递时都会产生一次复制动作。若将数组作为函数的参数,那么函数在调用时数组会复制一份作为函数参数。因此,在函数体中无法修改传入的数组的内容,因为函数体内操作的数组只是一个副本。
从内存和性能角度看,在函数间传递数组是一个开销很大的操作。尤其很大的数组。这时可以传递数组的指针。案例如下:
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5}
// 数组值传递
foo(arr)
// 传递指针
boo(&arr)
fmt.Println("原数组:", arr)
}
func foo(array [5]int) {
fmt.Println(array)
}
func boo(array *[5]int) {
fmt.Println("boo 函数传递指针后的数组的值", *array)
//// 修改数组的值
(*array)[0] = 100
fmt.Println("boo 函数内通过指针修改数组元素值", *array)
}
输出结果
[1 2 3 4 5]
boo 函数传递指针后的数组的值 [1 2 3 4 5]
boo 函数内通过指针修改数组元素值 [100 2 3 4 5]
原数组: [100 2 3 4 5]
传递指针可以有效地利用内存,性能也更好。由于传递的是指针,若改变指针指针的向,也会改变共享内存中的值。使用切片可以处理这类共享问题。