GO語言—————7.1 宣告和初始化

FLy_鵬程萬里發表於2018-07-01

7.1 宣告和初始化

7.1.1 概念

陣列是具有相同 唯一型別 的一組已編號且長度固定的資料項序列(這是一種同構的資料結構);這種型別可以是任意的原始型別例如整型、字串或者自定義型別。陣列長度必須是一個常量表示式,並且必須是一個非負整數。陣列長度也是陣列型別的一部分,所以[5]int和[10]int是屬於不同型別的。陣列的編譯時值初始化是按照陣列順序完成的(如下)。

注意事項 如果我們想讓陣列元素型別為任意型別的話可以使用空介面作為型別(參考 第 11 章)。當使用值時我們必須先做一個型別判斷(參考 第 11 章)。

陣列元素可以通過 索引(位置)來讀取(或者修改),索引從 0 開始,第一個元素索引為 0,第二個索引為 1,以此類推。(陣列以 0 開始在所有類 C 語言中是相似的)。元素的數目,也稱為長度或者陣列大小必須是固定的並且在宣告該陣列時就給出(編譯時需要知道陣列長度以便分配記憶體);陣列長度最大為 2Gb。

宣告的格式是:

var identifier [len]type

例如:

var arr1 [5]int

每個元素是一個整型值,當宣告陣列時所有的元素都會被自動初始化為預設值 0。

arr1 的長度是 5,索引範圍從 0 到 len(arr1)-1

第一個元素是 arr1[0],第三個元素是 arr1[2];總體來說索引 i 代表的元素是 arr1[i],最後一個元素是arr1[len(arr1)-1]

對索引項為 i 的陣列元素賦值可以這麼操作:arr[i] = value,所以陣列是 可變的

只有有效的索引可以被使用,當使用等於或者大於 len(arr1) 的索引時:如果編譯器可以檢測到,會給出索引超限的提示資訊;如果檢測不到的話編譯會通過而執行時會 panic:(參考 第 13 章

runtime error: index out of range

由於索引的存在,遍歷陣列的方法自然就是使用 for 結構:

  • 通過 for 初始化陣列項
  • 通過 for 列印陣列元素
  • 通過 for 依次處理元素

示例 7.1 for_arrays.go

package main
import "fmt"

func main() {
	var arr1 [5]int

	for i:=0; i < len(arr1); i++ {
		arr1[i] = i * 2
	}

	for i:=0; i < len(arr1); i++ {
		fmt.Printf("Array at index %d is %d\n", i, arr1[i])
	}
}

輸出結果:

Array at index 0 is 0
Array at index 1 is 2
Array at index 2 is 4
Array at index 3 is 6
Array at index 4 is 8

for 迴圈中的條件非常重要:i < len(arr1),如果寫成 i <= len(arr1) 的話會產生越界錯誤。

IDIOM:

for i:=0; i < len(arr1); i++{
	arr1[i] = ...
}

也可以使用 for-range 的生成方式:

IDIOM:

for i,_:= range arr1 {
...
}

在這裡i也是陣列的索引。當然這兩種 for 結構對於切片(slices)(參考 第 7 章)來說也同樣適用。

問題 7.1 下面程式碼段的輸出是什麼?

a := [...]string{"a", "b", "c", "d"}
for i := range a {
	fmt.Println("Array item", i, "is", a[i])
}

Go 語言中的陣列是一種 值型別(不像 C/C++ 中是指向首元素的指標),所以可以通過 new() 來建立: var arr1 = new([5]int)

那麼這種方式和 var arr2 [5]int 的區別是什麼呢?arr1 的型別是 *[5]int,而 arr2的型別是 [5]int

這樣的結果就是當把一個陣列賦值給另一個時,需要在做一次陣列記憶體的拷貝操作。例如:

arr2 := *arr1
arr2[2] = 100

這樣兩個陣列就有了不同的值,在賦值後修改 arr2 不會對 arr1 生效。

所以在函式中陣列作為引數傳入時,如 func1(arr2),會產生一次陣列拷貝,func1 方法不會修改原始的陣列 arr2。

如果你想修改原陣列,那麼 arr2 必須通過&操作符以引用方式傳過來,例如 func1(&arr2),下面是一個例子

示例 7.2 pointer_array.go:

package main
import "fmt"
func f(a [3]int) { fmt.Println(a) }
func fp(a *[3]int) { fmt.Println(a) }

func main() {
	var ar [3]int
	f(ar) 	// passes a copy of ar
	fp(&ar) // passes a pointer to ar
}

輸出結果:

[0 0 0]
&[0 0 0]

另一種方法就是生成陣列切片並將其傳遞給函式(詳見第 7.1.4 節)。

練習

練習7.1:array_value.go: 證明當陣列賦值時,發生了陣列記憶體拷貝。

練習7.2:for_array.go: 寫一個迴圈並用下標給陣列賦值(從 0 到 15)並且將陣列列印在螢幕上。

練習7.3:fibonacci_array.go: 在第 6.6 節我們看到了一個遞迴計算 Fibonacci 數值的方法。但是通過陣列我們可以更快的計算出 Fibonacci 數。完成該方法並列印出前 50 個 Fibonacci 數字。

7.1.2 陣列常量

如果陣列值已經提前知道了,那麼可以通過 陣列常量 的方法來初始化陣列,而不用依次使用 []= 方法(所有的組成元素都有相同的常量語法)。

示例 7.3 array_literals.go

package main
import "fmt"

func main() {
	// var arrAge = [5]int{18, 20, 15, 22, 16}
	// var arrLazy = [...]int{5, 6, 7, 8, 22}
	// var arrLazy = []int{5, 6, 7, 8, 22}
	var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}
	// var arrKeyValue = []string{3: "Chris", 4: "Ron"}

	for i:=0; i < len(arrKeyValue); i++ {
		fmt.Printf("Person at %d is %s\n", i, arrKeyValue[i])
	}
}

第一種變化:

var arrAge = [5]int{18, 20, 15, 22, 16}

注意 [5]int 可以從左邊起開始忽略:[10]int {1, 2, 3} :這是一個有 10 個元素的陣列,除了前三個元素外其他元素都為 0。

第二種變化:

var arrLazy = [...]int{5, 6, 7, 8, 22}

... 可同樣可以忽略,從技術上說它們其實變化成了切片。

第三種變化:key: value syntax

var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}

只有索引 3 和 4 被賦予實際的值,其他元素都被設定為空的字串,所以輸出結果為:

Person at 0 is
Person at 1 is
Person at 2 is
Person at 3 is Chris
Person at 4 is Ron

在這裡陣列長度同樣可以寫成 ... 或者直接忽略。

你可以取任意陣列常量的地址來作為指向新例項的指標。

示例 7.4 pointer_array2.go

package main
import "fmt"

func fp(a *[3]int) { fmt.Println(a) }

func main() {
	for i := 0; i < 3; i++ {
		fp(&[3]int{i, i * i, i * i * i})
	}
}

輸出結果:

&[0 0 0]
&[1 1 1]
&[2 4 8]

幾何點(或者數學向量)是一個使用陣列的經典例子。為了簡化程式碼通常使用一個別名:

type Vector3D [3]float32
var vec Vector3D

7.1.3 多維陣列

陣列通常是一維的,但是可以用來組裝成多維陣列,例如:[3][5]int[2][2][2]float64

內部陣列總是長度相同的。Go 語言的多維陣列是矩形式的(唯一的例外是切片的陣列,參見第 7.2.5 節)。

示例 7.5 multidim_array.go

package main
const (
	WIDTH  = 1920
	HEIGHT = 1080
)

type pixel int
var screen [WIDTH][HEIGHT]pixel

func main() {
	for y := 0; y < HEIGHT; y++ {
		for x := 0; x < WIDTH; x++ {
			screen[x][y] = 0
		}
	}
}

7.1.4 將陣列傳遞給函式

把一個大陣列傳遞給函式會消耗很多記憶體。有兩種方法可以避免這種現象:

  • 傳遞陣列的指標
  • 使用陣列的切片

接下來的例子闡明瞭第一種方法:

示例 7.6 array_sum.go

package main
import "fmt"

func main() {
	array := [3]float64{7.0, 8.5, 9.1}
	x := Sum(&array) // Note the explicit address-of operator
	// to pass a pointer to the array
	fmt.Printf("The sum of the array is: %f", x)
}

func Sum(a *[3]float64) (sum float64) {
	for _, v := range a { // derefencing *a to get back to the array is not necessary!
		sum += v
	}
	return
}

輸出結果:

The sum of the array is: 24.600000

但這在 Go 中並不常用,通常使用切片(參考 第 7.2 節)。








相關文章