最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

第 16 講:數(shù)組和指針

2021-09-19 21:31 作者:SunnieShine  | 我要投稿

上文我們基本上了解清楚了指針的基本用法和操作,不過,光是變量操作的指針模型可能過于單一,所以今天我們要看的是數(shù)組和指針的用法。


一維數(shù)組和指針

首先講一下簡單的一維數(shù)組和指針。首先你要明白一個東西,數(shù)組它其實除了是一系列數(shù)據(jù)的統(tǒng)一管理的集合外,它還有一個用的方式:指針形式。

我們可以使用指針,將數(shù)組改變?yōu)橹羔?,然后對變量名操作來達(dá)到操作內(nèi)部數(shù)據(jù)的方式。比如之前我們講到的數(shù)組的聲明和使用。我們先來看一下基本的使用數(shù)組的方式。

這樣寫起來很輕松,不過呢,我們除了使用中括號來找出數(shù)據(jù)外,還有一種寫法:

注意看清楚,*pb 變量,一個是指針變量,另一個則是一個普通的存數(shù)值的變量。我們再看右側(cè),a 只是一個數(shù)組名啊,為什么數(shù)組名字還可以加一個 2 和 3 呢?

這是因為,數(shù)組名本身除了表示它是一個數(shù)組的名字外,它也是一個指針,且這個指針指向 a[0]。換句話說,數(shù)組名除了表示數(shù)組的名稱外,它還是一個指針變量,這個指針的位置上存的數(shù)據(jù)是 a[0] 的地址。

那么,對于地址加 2 來說,我們就很清楚了。因為數(shù)組是順序存儲數(shù)據(jù)的,每一個數(shù)據(jù)都是挨著放的,所以加 2 的話,自然就表示 a[2] 了。不過,a + 2 是真的 a[2] 嗎?當(dāng)然不是。剛說到,a 是指針,所以加了 2,還是一個指針,這個性質(zhì)是沒有變的。不會因為它是指針,加了數(shù)值之后變成了數(shù)值。所以 a + 2 其實是 &a[2]。換句話說:

最后 pq 的結(jié)果是一樣的。而

pq 也是一樣的,只要沒有找出數(shù)組存儲的范圍(如果數(shù)組只有 5 個元素,那么這里的 C 就最好別超過 4,如果 C = 5 甚至大于 5,都會出現(xiàn)超出訪問數(shù)組的范圍的隱藏 bug)。

所以,為了取出數(shù)值,還需要取值符號 *。所以相當(dāng)于這樣:

看懂上面的寫法了嗎?那么現(xiàn)在來做個等效代替:

因為取地址和取值是一對相反的運(yùn)算,所以可以同時去掉。

另外,它還可以寫作

你可以理解一下,這一點是為什么。


一維數(shù)組配合自增自減運(yùn)算符

一個復(fù)雜但重要的例子

我們來看一下,指針變量在配合自增自減運(yùn)算符后的意思的理解。我們先來看示例和運(yùn)行后的結(jié)果,來理解它們。

這里提到了七個不同的語句寫法:

  • *p++

  • (*p)++

  • *(p++)

  • *(p+1)

  • *p+1

  • *++p

  • *(++p)

最后發(fā)現(xiàn)數(shù)值除了一個數(shù)有些奇怪(*t + 1)外,其他都還算比較正?!,F(xiàn)在我們來一個一個理解。


解釋

第一個為什么是 1 呢?因為 *p++ 的自增符號在后,所以先不會改變值,而是先被取值。p 最開始指向了 a,就表示語句 p = &a[0]。由于自增符號在后,所以輸出的應(yīng)為 a[0],即 1。

第二個為什么還是 1 呢?因為剛說過了,自增符號在后,所以打不打括號其實都是先取值。所以輸出的值也是 1。

第三個為什么是 2 呢?好像怎么看,自增運(yùn)算符都不執(zhí)行啊,即使有這個括號。這其實是因為,剛才 (*q)++ 式子最開始取出了值,但因為自增在后,所以輸出原來的 1,但后面的自增符號使得 a[0] 從 1 變?yōu)榱?2。所以 *(r++) 雖然打了括號,但由于在后的關(guān)系,數(shù)值不會發(fā)生改變,但 a[0] 已經(jīng)變?yōu)榱?2,所以自然取出數(shù)值時,就是 2 了。

第四個則比較好理解,*(s + 1) 自然取的是 a[1] 的值,故為 6。

第五個,這里括號不見了,可是為什么變成3了呢?*t + 1 語句下,是先取了 a[0] 的值,然后對值再加 1,所以自然是 2 + 1(注意是 2 哈,剛才已經(jīng)改為 2 了,不要帶 1 進(jìn)去)。

第六個,*++u,自增符號在前,所以自然是對原本指向的 a[0] 往后移動一位,變?yōu)橹赶?a[1]。或者換句話說,++u 其實也就是 a + 1,或者是 &a[1]。所以加星號表示取其值,所以是 a[1] 的值,也就是 6 了。

第七個,和剛才不加括號是一樣的效果。

可能你會問第一個,為什么 *p++(*p)++ 不同。我是這么解釋的:*p++ 是相當(dāng)于 *(p++) 的,這個自增符號不管在哪里,始終要記得,因為它挨著變量,所以它是先會被打括號的;而取值符號 *,不一定會直接挨著變量,所以有時候并不會先計算。所以我們有一個結(jié)論:*p++*(p++)一樣,*++p*(++p)一樣。剛才我們在計算第六個的時候,就知道了,自增是挨著變量的,不論前后的關(guān)系,既然挨著,就肯定是先計算的咯。

所以,優(yōu)先級什么的都喂狗去吧!根本不需要去死記硬背的。

從中我們可以總結(jié)以下內(nèi)容:

  • *p++ 等價于 *(p++) 等價于 *((&a[0])++) 等價于 *(&a[0]++) 等價于兩個語句 a[0]; &a[1];,自增符號在后,顯示數(shù)據(jù)時,自增符號不會起作用,即使使用完畢后,因為后面是 &a[1],對后續(xù)變量的操作也沒有任何影響(指針變動而不是數(shù)值變動),所以并沒有用。

  • *++p 等價于 *(++p) 等價于 *(++(&a[0])) 等價于 *(++&a[0]) 等價于 *(&a[1]) 等價于 a[1],自增符號在前,取 a[1]。

  • *p + 1 等價于 *(&a[0]) + 1 等價于 a[0] + 1

  • (*p)++ 等價于 (*(&a[0]))++ 等價于 (a[0])++ 等價于 a[0]++,即 a[0] 使用完畢后自增 1。

最后考一個問題:

看一下,注釋表示當(dāng)前位置輸出的數(shù)值。請嘗試?yán)斫庖幌聻槭裁础?/span>


二維數(shù)組和指針

二維數(shù)組是既有行又有列的數(shù)組,所以相當(dāng)于行、列均有數(shù)組的指針。

和剛才一樣,我們先定義一個數(shù)組。

然后,這個 a 變量除了是數(shù)組的變量名外,還是一個指針。不過因為是二維的,就稍顯麻煩。

我們可以嘗試將這里的a理解成一個二重指針。它是指向指向數(shù)值的指針的指針。很繞對吧~

其實是這個意思:

因為說的是二重指針,所以在聲明指針變量時,需要兩個星號。它等效于這句話:

首先我們使用一重指針指向行上的位置,因為數(shù)組名的緣故,所以指針指向的是最初的位置 a[0];但是因為 a[0] 在二維數(shù)組下并不是一個數(shù),而是一系列數(shù)據(jù)(可以看作是一個數(shù)組,數(shù)組內(nèi)各自又有一個單獨(dú)的數(shù)組),所以,&a[0]實際還不夠,還要取出列下標(biāo)為 0 的情況,所以有兩層括號和兩層取地址符號。

那么,普通的指針可以這么表示:

那么,假設(shè)要取 a[0][1] 的值,可以這么做:

看看為什么。首先 *a 表示是 a 數(shù)組的第一行的所有數(shù)據(jù),即 &a[0],所以上述語句可以等效為

但是依然不夠。因為 a[0] 我們剛才說過,a[0] 是一組數(shù)據(jù),雖然光禿禿在這里寫著,沒有地址符也沒有取值符,但始終記得它相當(dāng)于是一個數(shù)組名叫 a[0]。

然后 a[0] + 1 表示將列往后移動一位,即相當(dāng)于一維數(shù)組的指針 a + 1 這樣的存在。

所以這里就表示為了 &a[0][1]??蔀槭裁催€帶個指針呢?因為剛才就說過,一維數(shù)組的指針就帶有取地址符號,即 a + 1 等效于 &a[1],所以自然二維數(shù)組的指針也有這樣的符號了,故 a[0] + 1 就表示 &a[0][1] 了。所以取出值還得需要一個取值符號,故 *(&a[0][1]) 就變?yōu)榱?a[0][1] 了。

所以上述語句就變?yōu)椋?/span>

明白意思了嗎?

所以第 i 行第 j 列的數(shù)值用指針表示為

三維數(shù)組?

三維數(shù)組也是,只是指針變?yōu)槿刂羔樍T了,只是就不多闡述了。它的表示可以類比這個推導(dǎo)出來:

把數(shù)組賦值給指針的退化賦值形式

前文提到了很多有關(guān)數(shù)組的聲明(定義)和賦值方式。不過數(shù)組是可以賦值給一個指針的。比如下面這樣:

這樣表示出來的 series 是一個指針,它依然指向的是 series 賦值的這個整型數(shù)組的首元素地址。所以這種方式幾乎可以認(rèn)為是等價于 int series[] = { 1, 3, 5, 7, 9 };,唯一的區(qū)別是,數(shù)組格式的聲明可以為中括號指定元素總數(shù)(5),而指針形式則不可能這么寫了。

我們常把數(shù)組賦值給指針的這種賦值行為被稱為退化賦值(Degenerated Assignment),因為它損失了數(shù)組長度這一個信息。不過,這種退化賦值卻在 C 語言里使用得非常廣泛。下面要講到的數(shù)組傳參就是典型的其中一種使用方式。


數(shù)組傳參

數(shù)組是可以用于傳參的。換句話說,數(shù)組是可以當(dāng)形式參數(shù)(形參)傳遞的。不過,古老的 C 語言只允許數(shù)組的指針寫法傳遞,而且還有著另外的寫法。因為這里用的類型不多,所以只講解一維和二維數(shù)組的傳參。

不過在此之前,我們要搞清楚數(shù)組指針和指針數(shù)組的區(qū)別。這個名字有些繞,我們不用管名字,看它怎么用。


數(shù)組指針和指針數(shù)組

首先我們來看以下兩種寫法:

它們的區(qū)別就只是星號和數(shù)組變量名稱有沒有括號。這里要告訴大家,其實它們是有完全不一樣的用途的。第一個是一個數(shù)組(p[20] 明顯是一個數(shù)組的定義嘛),不過加了個星號,被定義為了指針,但其實這個寫法是這樣的:

換句話說,我們借用本文最開頭的第三種理解思路:

其實就看得很清楚了。它是一個數(shù)組,有 20 個元素組成。不過這個數(shù)組全存放的是指針罷了。

這種數(shù)組稱為指針數(shù)組(存放指針的數(shù)組)。

下面這個呢?被打了括號,所以完全不同。

因為括號的關(guān)系,星號不可被拆開。所以它是什么呢?它是一個可以指向一個數(shù)組的指針。換句話說,如果我有100 個數(shù)據(jù),被認(rèn)為劃分為了 5 個數(shù)組,每一個數(shù)組都有 20 個元素,并且它們是隨便劃分的,所以毫無關(guān)聯(lián)。

這個時候,我們可以讓這個指向數(shù)組的指針(即數(shù)組指針)分別去指向這不同的 5 個數(shù)組,來測試這些數(shù)據(jù)。

有了這些基礎(chǔ)的理論后,我們來看一下示例。


一維數(shù)組傳參

首先我們試想一個場景,求一個數(shù)組下的最大數(shù)值。這個時候,我們可以這么做:

這樣,一個數(shù)組的最大值的算法就寫好了。接下來我們來看一下,指針寫法(因為 C 語言要用數(shù)組的指針寫法來傳遞形參)。

所以首先,我們要把上面的 a[] 這個詭異的中括號里值都沒有的寫法給它改掉,改為指針寫法:

就可以了。所以在聲明時,按照可以去掉變量名稱的手段,我們可以將剛才的程序的完整版寫成這樣:

當(dāng)然,max 內(nèi)的 a[i] 等使用到數(shù)組的地方的,也可以改寫為指針寫法。當(dāng)然,這里不改也沒有關(guān)系。

注意數(shù)組傳參的過程,直接把數(shù)組名稱寫上去,而不要地址符號,也不要中括號,因為它傳遞的是一個地址數(shù)值,表示 &a[0]。


&a[0]a&a?

或許有小伙伴會對一維數(shù)組的指針三種寫法提出問題:如果我們把數(shù)組命名為 a 的話,它同時等價于首元素的地址,即 &a[0],那么,&a 表示什么?按照道理來說,&a 表示的是這個數(shù)組變量本身的地址,而這個變量的地址不應(yīng)該就是 &a[0] 嗎?所以這么說來的話,a、&a&a[0] 三者不是就完全一樣了嗎?

我想告訴你的是,這個說法目前來說確實是正確的。我們也確實經(jīng)常寫 a 來表示首元素地址,也有時候會用 &a 來“嚴(yán)謹(jǐn)?shù)亍北磉_(dá)數(shù)組變量本身的地址,而由于數(shù)組變量本身而言,從這里開始存放的一系列元素就是這數(shù)組里的東西,所以 &aa 這么看是等價的。不過……

在使用 +- 的運(yùn)算符的時候,就可以看出它們的區(qū)別。

來看這個執(zhí)行邏輯,x、yz 都得到什么結(jié)果?第一個是把數(shù)組地址加上 1,它表示往下移動 1 個單位,所以等價于 &a[0] + 1,即 &a[1];而 z 也就是這個結(jié)果。那么中間的 y 呢?它其實表示的是,以數(shù)組整體大小為單位,往下跨越一個完整大小的空間,所以它一口氣就相當(dāng)于往后移動到指向不存在的 a[4] 上去了。所以,請不要這樣使用指針來操作數(shù)組,會立馬超出訪問范圍,出現(xiàn)致命的 bug。


二維數(shù)組傳參

二維數(shù)組依然要取出最大值,就不容易了,因為它有兩個維度。于是我們先要改寫上面的 max 函數(shù)。

注意這里,傳參的寫法,是一個數(shù)組指針,即指向數(shù)組的指針。這一點也很好理解。因為二維數(shù)組本身應(yīng)當(dāng)在處理成指針寫法時可以改為二重指針,但因為傳參的緣故,它的行列都可以變動,所以我們不能在處理函數(shù)時,行、列同時變動,即必須是“一靜一動”,不然程序就不受控了。所以我們要保證其中一個維度是靜止的(即數(shù)組形式),而另外一個維度則用指針來操作,就很輕松。所以寫成了數(shù)組指針寫法。剛才也告訴大家,數(shù)組指針是一種指針,不過這個指針是指向一個數(shù)組而不是一個數(shù)值的。當(dāng)改變指針的值時,也就相當(dāng)于移動了指針,達(dá)到測試和使用二維數(shù)組之中“一個維度下的數(shù)組”的操作。

那么它的聲明就省略變量名即可,但是因為只能省略變量名,所以括號是不能少的。

令人遺憾的返回數(shù)組的函數(shù)

聽說你想返回數(shù)組?

我們有時候也嘗試把一個數(shù)組作為返回值返回到外界來,但很遺憾的是,我們無法把返回數(shù)組的函數(shù)寫成符合我們原本理解的可能寫法:

這兩種寫法都是不允許的。所以我們?nèi)绾伪WC返回一個數(shù)組呢?

返回數(shù)組只能使用前文提到的退化賦值的方式。如果你有一個數(shù)組需要返回,我們只能寫成指針的方式返回,所以實際上的寫法是這樣的

這種寫法沒有問題,不過,很遺憾的是,它少了一個提示信息,即數(shù)組的長度,這樣返回的數(shù)組很容易出現(xiàn)越界的問題,所以你可能還會在這種函數(shù)里加上一個 out 參數(shù)表示數(shù)組長度。

不過…… 還有一個遺憾的地方。在 C 語言里,所有在函數(shù)里聲明的變量都是跟著棧內(nèi)存走的。這有什么意義呢?跟著棧走的變量聲明,會在函數(shù)執(zhí)行完畢后自動銷毀,所以,如果你嘗試返回一個函數(shù)內(nèi)定義出來的數(shù)組的話,只能很遺憾地通知你,在函數(shù)執(zhí)行完畢后,這塊內(nèi)存會被銷毀,所以你返回的這個指針其實是銷毀前的這個結(jié)果的這塊內(nèi)存,但它目前已經(jīng)被銷毀。

所以,目前我們只靠前文學(xué)到的知識點還無法做到不銷毀這些自己聲明和定義的內(nèi)容。等我們接觸到結(jié)構(gòu)體和堆內(nèi)存的時候,我們才會提到了。


總結(jié)、懸空指針和野指針的概念

所以,這一節(jié)的所有文字其實就想告訴你兩個點:第一,返回數(shù)組的函數(shù)是做不到的。非得返回數(shù)組,你也只能退化賦值,返回一個指針;第二,返回的數(shù)組對象的這個指針,即使被成功返回了,也逃不過函數(shù)銷毀時立刻銷毀內(nèi)部定義的所有變量的厄運(yùn)。所以,不要嘗試在函數(shù)里聲明(定義)一個數(shù)組,然后用指針形式返回這個數(shù)組(的首地址),這樣依然會失敗。而且,這個指針由于失去了原本指向的內(nèi)容,所以這個時候這個對象還被稱為懸空指針(Dangling Pointer)。

另外,還存在一個說法,叫做野指針(Wild Pointer),它表示一個沒有初始化的指針變量。這個指針由于沒有初始化,就和普通變量一樣,它內(nèi)部存儲的值是不明的。不過由于它是指針的關(guān)系,內(nèi)部的數(shù)值很可能是隨機(jī)的,所以這意味著這個指針表示的意思就是“隨機(jī)指向”。這是一個很可怕的概念。隨機(jī)指向很有可能破壞電腦。


static 修飾數(shù)組參數(shù)

當(dāng)數(shù)組傳入函數(shù)里的時候,我們常??梢詫@個函數(shù)的參數(shù)加以修飾。在 C99 標(biāo)準(zhǔn)里,我們甚至可以對一個數(shù)組函數(shù)的長度作出修飾。

我們?nèi)绻褦?shù)組參數(shù)用數(shù)組格式書寫到參數(shù)上的時候,我們可以使用 static 數(shù)字 的方式來說明,這個數(shù)組參數(shù)傳入的元素總數(shù)最多只能有 10 個。當(dāng)然,這個用法有時候很有用,但有時候很雞肋。


指針的運(yùn)算

在 C 語言里,我們?yōu)橹羔樢蔡峁┝朔奖愕倪\(yùn)算符,使得我們可以通過運(yùn)算符來操作和計算內(nèi)存地址。

上文我們其實已經(jīng)用到了一部分這樣的運(yùn)算符,諸如 ++、-- 這些符號,下面要提到的是這樣的一些運(yùn)算符:

  • +:計算兩個地址的和,或是為一個地址增加偏移量。比如 a 是一個數(shù)組,a + 3 將會把當(dāng)前地址往后移動 3 個元素單位。

  • -:和上面的算法相反,這里是減法。

  • *:將地址數(shù)值相乘。這個用法很少。

  • /:將地址數(shù)值相除。這個用法也很少。

  • %:把兩個地址數(shù)值以整數(shù)方式執(zhí)行取模運(yùn)算。這個用法也很少。

比如上述示例里就展示了指針的減法和數(shù)值的減法的區(qū)別。distance 是直接把兩個指針相減,得到的是 pq 它們兩個在內(nèi)存里相差的距離,而 valueDifference 表示的是 pq 兩個變量指向的變量的存儲數(shù)值之差。

第 16 講:數(shù)組和指針的評論 (共 條)

分享到微博請遵守國家法律
张家川| 苏尼特左旗| 淅川县| 山丹县| 兴宁市| 玉溪市| 杭锦旗| 阿勒泰市| 思茅市| 海伦市| 长治市| 南投县| 梁河县| 琼结县| 康乐县| 平武县| 陇西县| 天等县| 南部县| 巴中市| 双桥区| 双峰县| 云梦县| 平顶山市| 马山县| 祁阳县| 邹平县| 连城县| 都安| 临武县| 连山| 光山县| 南丹县| 中牟县| 靖江市| 泸定县| 九江市| 汉川市| 缙云县| 雷州市| 浮山县|