[TOC]
## 內部實現
數組是 **元素類型相同** 且**長度固定** 的存儲在內存中 數據類型。長度固定,是數組和切片最明顯的區別。
數組存儲的類型可以是內置類型,比如整型或者字符串,也可以是自定義的數據結構。
這里的索引,一直都是0,1,2,3這樣的,因為其元素類型相同,我們也可以使用反射,獲取類型占用大小,進行移位,獲取相應的元素。
## 概念
* 數組長度是數組類型的一部分
* 數組在go中為值類型
* 數組的指針和指針數組
* 指針數組指的是數組的元素是指針
* 數組的指針
* new方法創建指向數組的指針
* 占用連續的內存,索引訪問的效率非常高
## 聲明和初始化
### 聲明原則
1. 指明存儲的 *元素的數量*
2. 指明存儲的 *數據的類型*
~~~
var <varname> [n]<type>, n>0
~~~
>[info] 以上我們聲明了一個數組array,但是還沒有對他進行初始化,這時候數組array里面的值,是對應元素類型的零值,也就是說,現在這個數組是5個0
>數組一旦聲明后,其元素類型和大小都不能變了,如果還需要存儲更多的元素只能通過創建一個新的數組,然后把原來數組的數據復制過去
### 初始化
剛剛聲明的數組已經被默認的元素類型零值初始化了,可以采用如下辦法再次進行初始化:
~~~
var array [5]int
array = [5]int{1,2,3,4,5}
或者
var array [5]int = [5]int{1,2,3,4,5}
~~~
這兩步比較繁瑣,Go為我們提供了`:=`操作符,可以讓我們在創建數組的時候直接初始化。
~~~
array := [5]int{1,2,3,4,5}
~~~
>[info]這種簡短變量聲明的方式不僅適用于數組,還適用于任何數據類型,這也是Go語言中常用的方式。
### 自動計算數組的長度
有時候我們更懶,連數組的長度都不想指定,不過沒有關系,使用...代替就好了,Go會自動推導出數組的長度。
~~~
array := [...]int{1,2,3,4,5}
~~~
假如我們只想給索引為1和3的數組初始化相應的值,其他都為0怎么做呢,直接的辦法有:
~~~
array := [5]int{0,1,0,4,0}
~~~
還有一種更好的辦法,上面講默認初始化為零值,那么我們就可以利用這個特性,只初始化索引1和3的值:
~~~
array := [5]int{1:1,3:4}
~~~
## 使用數組
### 訪問元素
數組的訪問非常簡單,通過索引即可,操作符為`[]`。
~~~
array := [5]int{1:1,3:4}
fmt.Printf("%d",array[1])
~~~
### 修改元素
~~~
array:=[5]int{1:1,3:4}
fmt.Printf("%d\n",array[1])
array[1] = 3
fmt.Printf("%d\n",array[1])
~~~
### 遍歷數組 傳統方法
~~~
func main() {
array := [5]int{1: 1, 3: 4}
for i := 0; i < 5; i++ {
fmt.Printf("索引:%d,值:%d\n", i, array[i])
}
}
~~~
### 遍歷數組 go風格方法
使用for range 循環
~~~
func main() {
array := [5]int{1: 1, 3: 4}
for i, v := range array {
fmt.Printf("索引:%d,值:%d\n", i, v)
}
}
~~~
## 二維數組
### 聲明
```
var <varname> [m][n]<type>, n>0
```
### 聲明并定義
```
var <varname> [m][n]<type> = {{x,x,x,x,x},{x,x,x,x,x}}
```
> len(array) 行數
> len(array[0]) 列數
### 同樣類型的數組
同樣類型的數組是可以相互賦值的,不同類型的不行,會編譯錯誤。那么什么是呢?Go語言規定,必須是**長度一樣**,并且每個**元素的類型一樣**的數組,才是同樣類型的數組。
~~~
array := [5]int{1: 1, 3: 4}
var array1 [5]int = array //success
var array2 [4]int = array1 //error
~~~
## 指針數組
和數組本身差不多,只不過元素類型是指針。
~~~
array := [5]*int{1: new(int), 3:new(int)}
~~~
這樣就創建了一個指針數組,并且為索引1和3都`創建了內存空間`,其他索引是指針的零值nil,這時候我們要修改指針變量的值也很簡單,如下即可:
~~~
array := [5]*int{1: new(int), 3:new(int)}
*array[1] = 1
~~~
>[danger]以上需要注意的是,只可以給索引1和3賦值,因為只有它們分配了內存,才可以賦值,如果我們給索引0賦值,運行的時候,會提示無效內存或者是一個nil指針引用。
~~~
panic: runtime error: invalid memory address or nil pointer dereference
~~~
要解決這個問題,我們要通過`array[0] =new(int)`給索引0分配內存 ,然后再進行賦值修改。
~~~
array := [5]*int{1: new(int), 3:new(int)}
array[0] =new(int)
*array[0] = 2
fmt.Println(*array[0])
~~~
## 函數間傳遞數組
數組是值傳遞。
~~~
func main() {
array := [5]int{1: 2, 3:4}
modify(array)
fmt.Println(array) # 值不變
}
func modify(a [5]int){
a[1] = 3
fmt.Println(a)
}
~~~
### 數組的指針傳遞
~~~
func modify(a *[5]int){
(*a)[1] = 3
arr[3] = 4 //兩種格式
}
func main() {
array := [5]int{1: 2, 3:4}
modify(&array)
fmt.Println(array)
}
~~~
這是傳遞數組的指針的例子,會發現數組被修改了。所以這種情況雖然節省了復制的內存,但是要謹慎使用,因為一不小心,就會修改原數組,導致不必要的問題。
>[info]也可以通過函數的返回值實現數組的修改
>[danger]這里注意,數組的指針和指針數組是兩個概念,數組的指針是`*[5]int`,指針數組是`[5]*int`,注意*的位置。
經常會見到: `p , *p , &p` 三個符號
~~~
p是一個指針變量的名字,表示此指針變量指向的內存地址,如果使用%p來輸出的話,它將是一個16進制數。
*p表示此指針指向的內存地址中存放的內容,一般是一個和指針類型一致的變量或者常量。
&是取地址運算符,&p就是取指針p的地址。
等會,怎么又來了個地址,它到底和p有什么區別?區別在于,指針p同時也是個變量,既然是變量,編譯器肯定要為其分配內存地址,就像程序中定義了一個int型的變量i,編譯器要為其分配一塊內存空間一樣。而&p就表示編譯器為變量p分配的內存地址,而因為p是一個指針變量,這種特殊的身份注定了它要指向另外一個內存地址,程序員按照程序的需要讓它指向一個內存地址,這個它指向的內存地址就用p表示。而且,p指向的地址中的內容就用*p表示。
~~~
## 練習
用切片實現斐波那契數列
### 冒泡排序
~~~
func Bubble() {
var array [7]int = [7]int{7, 5, 1, 9, 0, 2, 8}
for i := 0; i <len(array); i++ {
for j := 0; j <len(array) - i - 1; j++ {
if array[j] > array[j+1] {
array[j], array[j+1] = array[j+1], array[j]
}
}
}
fmt.Println(array)
}
~~~
數組的比較
長度相同可以比較