第 20 講:數(shù)組(二):鋸齒數(shù)組
前文我們簡(jiǎn)單介紹了數(shù)組的基本使用,包括數(shù)組的初始化、聲明以及數(shù)組的遍歷。今天我們來(lái)看另外一種數(shù)組類型:鋸齒數(shù)組。
Part 1 可拆分的數(shù)組
在前文里,我們介紹的數(shù)組是不可拆分的。所謂的不可拆分,就是數(shù)組本身無(wú)法被拆解成單個(gè)維度或低維度的數(shù)組,因?yàn)閿?shù)組的元素只是邏輯上理解成多個(gè)維度的,但在遍歷的時(shí)候,我們發(fā)現(xiàn)我們不得不依賴于 i
、j
以及 foreach
循環(huán)才可以遍歷。而這種遍歷邏輯跟 i
、j
本身是沒(méi)有關(guān)系的,這些變量?jī)H僅是用來(lái)表示一下下標(biāo)罷了。真正遍歷循環(huán),使用的 foreach
也僅僅是一層循環(huán)就可以把所有元素全部迭代完成。包括 .Length
獲取長(zhǎng)度,其實(shí)是所有數(shù)組元素的總個(gè)數(shù)。這種種跡象都表明,數(shù)組本身其實(shí)是不可拆分的。
那么,既然想要把數(shù)組拆分成更小的數(shù)組,我們擁有一種新的數(shù)組結(jié)構(gòu),叫做鋸齒數(shù)組。鋸齒數(shù)組也叫交錯(cuò)數(shù)組和不規(guī)則數(shù)組。鋸齒數(shù)組至少都需要兩個(gè)中括號(hào)來(lái)表達(dá)數(shù)組類型:
比如這則示例。我們使用 int[][]
來(lái)表示數(shù)組是一個(gè)鋸齒數(shù)組。從人的理解角度出發(fā),這種數(shù)組可以理解成 int[]
這個(gè)類型的一維數(shù)組,即每一個(gè)數(shù)組的元素都又是一個(gè)數(shù)組;從記號(hào)上理解的話,你可以當(dāng)成是:整個(gè)數(shù)組是一個(gè)大的 []
,每一個(gè)元素都是 []
的類型。這個(gè)空的中括號(hào)我們可以自動(dòng)理解為“一維數(shù)組”,那么這句話的意思就是,整個(gè)大數(shù)組是一個(gè)由 int
元素構(gòu)成的一維的數(shù)組,而每一個(gè)元素都是一維數(shù)組類型的實(shí)體。
然后稍微注意一下,new int[3][]
這里的第二個(gè)中括號(hào)是不寫數(shù)值的。即使我們可以看到,每一個(gè)數(shù)組元素都是長(zhǎng)度 3 的,但我們依舊不寫這個(gè) 3 在第二個(gè)中括號(hào)里。不不不,不是說(shuō)可以省略掉,而是根本不能寫出來(lái)。這是因?yàn)椋@種數(shù)組模型,每一個(gè)元素都是一個(gè)單獨(dú)的數(shù)組的關(guān)系,數(shù)組的長(zhǎng)度是無(wú)法從鋸齒數(shù)組上限制的。正是因?yàn)槿绱耍忼X數(shù)組允許每一個(gè)子數(shù)組
Part 2 取值
為了獲取這種數(shù)組里面的元素,我們的做法也是使用兩個(gè)中括號(hào),然后寫索引的方式來(lái)取。
這么寫,如果你不理解的話,可以這么拆開(kāi)來(lái)理解:
我相信你這種理解方式,可以幫助你理解鋸齒數(shù)組。
Part 3 遍歷
這種數(shù)組的遍歷稍微復(fù)雜一些。我們可使用如下的兩種嵌套循環(huán)來(lái)解決遍歷的問(wèn)題。
首先,第一種是采用簡(jiǎn)單的 foreach
-foreach
或 foreach
-for
循環(huán)組合來(lái)遍歷。
采用這種遍歷邏輯,我們可以無(wú)需關(guān)系數(shù)組的長(zhǎng)度,因?yàn)?foreach
自身就自動(dòng)去獲取了每一個(gè)元素。當(dāng)然,稍微復(fù)雜一點(diǎn),可以采用 foreach
-for
這個(gè)寫法也是可以的。
接著,是第二種稍微復(fù)雜一點(diǎn)的循環(huán)遍歷過(guò)程:采用 for
-for
循環(huán)組合。
for
循環(huán)的話,由于建立的關(guān)系是索引,因此我們需要先提取子數(shù)組本身,然后才是繼續(xù)內(nèi)層的 for
C# 里,數(shù)組是引用傳遞的。所謂的引用傳遞,說(shuō)白了就是,你創(chuàng)建一個(gè)新的變量,這個(gè)變量要是從別的變量里取出來(lái)的的話,那么這個(gè)新的變量和不取出來(lái)然后直接用是等價(jià)的寫法。比如上面這個(gè)例子,arr[i]
和 subarray
就是同一個(gè)東西。如果還原回去,你直接將 subarray
全部改成 arr[i]
,一點(diǎn)問(wèn)題都沒(méi)有;另一方面,引用傳遞使得 C# 語(yǔ)法更為靈活??梢钥吹?arr[i].Length
這個(gè)寫法,arr[i]
本身就是取 arr
的第 (i + 1) 個(gè)元素;取出來(lái)的結(jié)果自然是一個(gè)很普通的一維數(shù)組。我們?nèi)耘f可以直接把 arr[i]
當(dāng)成一個(gè)數(shù)組來(lái)使用,因此 .Length
直接追加在后面是不會(huì)產(chǎn)生語(yǔ)法錯(cuò)誤的。這一點(diǎn)很神奇,對(duì)吧。
當(dāng)然了,第二種理解方式,未免有些復(fù)雜。特別是我們新學(xué)習(xí)的 int[] subarray = arr[i];
的賦值邏輯,這是前文沒(méi)有介紹過(guò)的。希望你掌握這種賦值行為的邏輯以及書(shū)寫格式。另外啰嗦一下,這個(gè)賦值語(yǔ)句本身就在拆解取出數(shù)組里的每一個(gè)子數(shù)組元素,所以這個(gè)行為就是鋸齒數(shù)組的拆分。而這個(gè)寫法格式,是多維數(shù)組無(wú)法做到的。
Part 4 .Length
和 GetLength()
在鋸齒數(shù)組里的行為
前文里我們介紹過(guò) .Length
,它是用于獲取數(shù)組總元素個(gè)數(shù),以及每一個(gè)維度的元素個(gè)數(shù)。在鋸齒數(shù)組里,還是一樣的嗎?
首先是 .Length
。我們可以試著寫一段代碼:
你好好看看,輸出的結(jié)果應(yīng)該是多少。實(shí)際上,輸出語(yǔ)句有兩個(gè),對(duì)應(yīng)的結(jié)果其實(shí)是 3 和 2。第一個(gè)輸出的內(nèi)容是 arr.Length
,這個(gè)結(jié)果是 3;而第二個(gè)輸出的內(nèi)容是 arr[0].Length
arr[0]
是整個(gè)數(shù)組里的第一個(gè)子數(shù)組,然后 .Length
取的其實(shí)是第一個(gè)子數(shù)組的長(zhǎng)度;而這個(gè)數(shù)組長(zhǎng)度是 2(只有 1 和 2 兩個(gè)元素構(gòu)成),因此輸出是 2。
接著我們說(shuō)一下 GetLength
。前文介紹過(guò) GetLength
是用來(lái)獲取每個(gè)維度的元素?cái)?shù)量的。那么,既然長(zhǎng)度不一導(dǎo)致無(wú)法在初始化語(yǔ)句 new int[][]
的第二中括號(hào)里無(wú)法寫數(shù)字,GetLength
會(huì)得到什么結(jié)果呢?
首先,第一個(gè)結(jié)果是 3,它應(yīng)該和前面 .Length
的結(jié)果完全一樣;而第二個(gè)結(jié)果是多少呢?實(shí)際上并不能獲取正確的結(jié)果,而是產(chǎn)生一個(gè)異常。

它產(chǎn)生了一個(gè)叫做 IndexOutOfRangeException
的類型的異常。這個(gè)異常告訴你,整個(gè)數(shù)組并沒(méi)有你所輸入的這個(gè)維度。雖然說(shuō)這個(gè)句子不是很好懂,但是翻譯過(guò)來(lái)其實(shí)也挺好理解的:由于數(shù)組本身是一個(gè)由若干個(gè)小的一維數(shù)組構(gòu)成的一個(gè)大的一維數(shù)組,因此數(shù)組本身依舊是一維數(shù)組,如果嘗試去取第 2 個(gè)維度的話,一維數(shù)組是不具有第二個(gè)維度的,因此會(huì)產(chǎn)生這個(gè)錯(cuò)誤信息。
順帶一提。由于整個(gè)數(shù)組是一維由若干個(gè)小的數(shù)組構(gòu)成的一個(gè)大的數(shù)組,數(shù)組本身并不存在維度的概念,因此整個(gè)鋸齒數(shù)組還是一維的。這一點(diǎn)一定不要搞錯(cuò)了。我們就把這種 int[][]
的鋸齒數(shù)組稱為鋸齒一維數(shù)組。