当前位置 : 主页 > 编程语言 > java >

爱上开源之golang入门至实战第四章-数组 隐藏的性能陷阱

来源:互联网 收集:自由互联 发布时间:2022-08-15
4.2.1 数组 数组是具有相同唯一类型的一组已编号且长度固定的数据序列(所有的数据项目都是相同的数据类型);元素的数据类型可以是任意的原始类型例如整型、字符串或者自定义类


爱上开源之golang入门至实战第四章-数组 隐藏的性能陷阱_大数据

 

4.2.1 数组

数组是具有相同唯一类型的一组已编号且长度固定的数据序列(所有的数据项目都是相同的数据类型);元素的数据类型可以是任意的原始类型例如整型、字符串或者自定义类型。数组长度必须是一个常量表达式,且必须是一个非负整数。 相对于去声明 number0, number1, ..., number99 的变量,使用数组形式 numbers[0], numbers[1] ..., numbers[99] 更加方便且易于扩展。

爱上开源之golang入门至实战第四章-数组 隐藏的性能陷阱_初始化_02

 

数组长度也是数组类型的一部分,不同的长度,不同的数据类型都属于不同的数组类型; 数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。数组元素可以通过 索引(位置)来读取(或者修改),数组以 0 开始在所有类 C 语言中是相似的)。元素的数目,也称为长度 或者数组大小必须是固定的并且在声明该数组时就给出(编译时需要知道数组长度以便分配内存);数组长度最大为 2Gb。

4.2.1.1 声明

Go 语言数组声明必须指定元素类型及元素个数,语法格式如下:

var variable_name [SIZE] variable_type
或者多维数组

var variable_name [SIZE] []variable_type

下面是示例代码

var names [10]string // 声明长度为10的字符串数组
var ages [10]int32 // 声明长度为10的整数32数组
var conns [20]*net.Conn // 声明长度为20的Conn指针数组
var objects [5]sturct{Name:string} // 声明长度为5的自定义结构体数组

4.2.1.2 初始化

可以直接通过和花括号{}赋值的方式进行初始化;格式如下:

variable_name = [SIZE] variable_type{n1[,n2,n3]}

下面是示例代码

var arr [3]int
arr = [3]int{1, 2, 3}

声明数组的同时快速初始化数组

var arr = [3]int{1, 2, 3}
arr2 = [2]int{1, 2}

通过指定下标来初始化元素

costs := [5]float32{1:2.0,3:7.0}

初始化数组中 {} 中的元素个数不能大于 [] 中的数字。如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小;

如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:

var costs = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

costs := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

以上实例读取了第五个元素。数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推;

数组的元素如果没有进行初始化,Golang会按照变量的初始化的规则对数组类型里的元素进行初始化;例如每个元素是一个整型值,当声明数组时所有的元素都会被自动初始化为默认值 0, 例如每个元素如果是字符串类型,当声明数组时所有的元素会被自动初始为默认值“”(空字符串);如果每个元素是一个某种类型的指针,当声明数组时所有的元素会被自动初始为默认值nil(空指针);

示例

var arr = [...]int{1, 2, 3}

fmt.Printf("%v %T %d\n", arr, arr, unsafe.Sizeof(arr))

var arr2 [2]string
arr2 = [2]string{}

fmt.Printf("%v %T %d\n", arr2, arr2, unsafe.Sizeof(arr2))

var aa []int16
fmt.Printf("%v %T %d\n", aa, aa, unsafe.Sizeof(aa))

var twoDim [3][3]int16
twoDim = [3][3]int16{[3]int16{1, 2, 3}, [3]int16{4, 5, 6}, [3]int16{7, 8, 9}}
fmt.Printf("%v %T %d\n", twoDim, twoDim, unsafe.Sizeof(twoDim))

var twoSlice [3][]int16
twoSlice = [3][]int16{[]int16{1, 2, 3}, []int16{4, 5, 6}, []int16{7, 8, 9}}
fmt.Printf("%v %T %d\n", twoSlice, twoSlice, unsafe.Sizeof(twoSlice))

代码结果

=== RUN TestLesson04_1
[1 2 3] [3]int 24
[ ] [2]string 32
[] []int16 24
[[1 2 3] [4 5 6] [7 8 9]] [3][3]int16 18
[[1 2 3] [4 5 6] [7 8 9]] [3][]int16 72
--- PASS: TestLesson04_1 (0.00s)

思考

var twoDim [3][3]int16 和 var twoSlice [3][]int16 有什么区别

4.2.1.3 访问

数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。例如:

var cost float32 = costs[2]

以上实例读取了数组 costs第3个元素的值。

遍历数组的方法既可以for 条件循环,也可以使用 for-range。这两种 for 结构对于切片(slices)来说也同样适用。

以下演示了数组完整操作(声明、赋值、访问)的实例:

实例

package charpter04

import "fmt"
import "testing"

func TestLesson04_3(t *testing.T) {
var n [10]int /* n 是一个长度为 10 的数组 */
var i, j int

/* 为数组 n 初始化元素 */
for i = 0; i < len(n); i++ {
n[i] = i + 100 /* 设置元素为 i + 100 */
}

/* 输出每个数组元素的值 */
for j = 0; j < len(n); j++ {
fmt.Printf("Element[%d] = %d\n", j, n[j])
}
}=== RUN TestLesson04_3
Element[0] = 100
Element[1] = 101
Element[2] = 102
Element[3] = 103
Element[4] = 104
Element[5] = 105
Element[6] = 106
Element[7] = 107
Element[8] = 108
Element[9] = 109
--- PASS: TestLesson04_3 (0.00s)


使用 for-range


func TestLesson04_4(t *testing.T) {
// 声明数组的同时快速初始化数组
costs := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

/* 输出数组元素 */
for i, one := range costs {
fmt.Printf("costs[%d] = %f\n", i, one)
}

costs2 := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
/* 输出每个数组元素的值 */
for j, one := range costs2 {
fmt.Printf("costs2[%d] = %f\n", j, one)
}

// 将索引为 1 和 3 的元素初始化
costs3 := [5]float32{1: 2.0, 3: 7.0}
for k, one := range costs3 {
fmt.Printf("costs3[%d] = %f\n", k, one)
}
}=== RUN TestLesson04_4
costs[0] = 1000.000000
costs[1] = 2.000000
costs[2] = 3.400000
costs[3] = 7.000000
costs[4] = 50.000000
costs2[0] = 1000.000000
costs2[1] = 2.000000
costs2[2] = 3.400000
costs2[3] = 7.000000
costs2[4] = 50.000000
costs3[0] = 0.000000
costs3[1] = 2.000000
costs3[2] = 0.000000
costs3[3] = 7.000000
costs3[4] = 0.000000
--- PASS: TestLesson04_4 (0.00s)

注意事项

Go中的数组是一个值(valuetype)类型(不像 C/C++ 中是指向首元素的指针),所有的值类型变量,在复制和作为参数传递时,都将产生一次复制动作,如果将数组作为函数的参数类型,则在函数调用时参数数据将发生数据复制,因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是传入数组的一个副本。可以直接使用=来复制两个数组类型相同的数组对象(包括数组的长度,数组中元素的类型)

func modify(array [5]int) {
array[0] = -1
fmt.Printf("%v %T %d\n", array, array, unsafe.Sizeof(array))
}

func TestLesson04_5(t *testing.T) {
array := [5]int{1, 2, 3, 4, 5}
modify(array)
fmt.Printf("%v %T %d\n", array, array, unsafe.Sizeof(array))
}

输出结果

=== RUN TestLesson04_5
[-1 2 3 4 5] [5]int 40
[1 2 3 4 5] [5]int 40
--- PASS: TestLesson04_5 (0.00s)

比较两个数组是否相等; 如果两个数组类型相同(包括数组的长度,数组中元素的类型)的情况下,我们可以直接通过较运算符(==和 !=)来判断两个数组是否相等,只有当两个数组的所有元素都是相等的时候数组才是相等的,不能比较两个类型不同的数组,否则程序将无法完成编译

一个数组可以由零个或多个元素组成,一旦声明了,数组的长度就固定了,不能动态变化。len() 和 cap() 返回结果始终一样。如果超过了数组固定的长度; 编译时会报错:invalid array index x (out of bounds for x-element array)

func TestLesson04_6(t *testing.T) {
array := [5]int{1, 2, 3, 4, 5}
array[5] = 5
fmt.Printf("%v %T %d\n", array, array, unsafe.Sizeof(array))
}# go-in-practice/code/charpter-01 [go-in-practice/code/charpter-01.test]
.\lesson03_test.go:176:8: invalid argument: array index 5 out of bounds [0:5]

爱上开源之golang入门至实战第四章-数组 隐藏的性能陷阱_数组元素_03

 

特别注意

由于数组是值类型(value-type);作为参数或者返回值时;在传递值过程中;会进行内存中的值复制;如果数组的长度过大;会有较大的内存复制上的开销;所以建议此情况下,使用切片或者使用数组的指针对象进行传递

上一篇:ESTL表达式(一)
下一篇:没有了
网友评论