C語言中指標與陣列的區別與聯絡

2021-08-28 23:02:29 字數 3380 閱讀 6656

好久不寫東西了,從畢業以來,整個人都懶散了很多。今天終於鼓起勇氣,來寫一點兒東西……

指標與陣列對於c語言程式設計師來說肯定不會陌生,一說起這個話題,我就想起了曾經被記憶體、位址、位址裡的內容這些概念狂虐時的情形。經過三年的學習,加上最近又看了一些這方面的書籍,現在自我感覺對這方面已經有了乙個比較全面的理解,分享出來,和大家共勉。

1.指標與陣列的愛恨情仇

為什麼這一塊兒內容很繞呢?我想主要有兩個方面的原因,一是指標本身就是c語言中最為難掌握的內容。二是指標與陣列之間的曖昧關係讓人頭疼。

針對第乙個方面,我想沒有什麼特殊的好辦法,只有你把教科書上指標這一部分吃透,並且要經歷過一段時間的使用經驗,甚至是幾個令人抓狂的bug,才會對指標有所了解。這裡給大家兩個經驗,一是要把記憶體的概念和指標同時來學習,因為指標本來就是放的記憶體位址,沒有記憶體的概念,指標是很難學好的。二是如果要想深入理解指標,最好去讀一些有關組合語言的書籍,看看指標在彙編層面上的表示,相信你對指標的理解會上公升乙個檔次。這裡推薦大家看《深入理解計算機系統》一書,這本書是難得一見的好書,應該是每乙個程式設計師必看的經典之一。

針對第二個方面,也就是指標與陣列的關係,我們可以從下面這個方面入手。在c語言建立之初,很大一部分c語言的使用者是編譯器的設計者,所以為了迎合這些人的口味兒,c語言做了很多特定的規則,這些規則後來在標準化的過程中被委員會採納,成了語言標準的一部分。具體到指標與陣列這一塊兒,我們同樣從兩個方面來看,一是作為乙個語言,陣列是必須要支援的一種陣列型別,原因很簡單,陣列是線性表的直接體現。而從編譯器設計者的角度來看,如果為陣列專門設計一套實現標準會非常繁雜(事實上,後來c++完成了這一任務,它就是標準庫中的vector容器)。這一對矛盾最後以雙方的相互妥協得以解決,而解決方法就是利用現有的指標來間接實現陣列。然而,陣列與指標終究不是乙個東西,所以,語言在設計過程中就必須在二者之間和稀泥。到最後,就形成了這樣一對令人頭大的冤家。了解了這一背景,你就不會為他倆撲朔迷離的關係而感到驚訝了。

2.陣列的引用被轉化為指標加偏移量的引用

從上面我們知道,編譯器為了簡化對陣列的支援,實際上是利用指標實現了對陣列的支援。具體來說,就是將表示式中的陣列元素引用轉化為指標加偏移量的引用。這麼說大家可能不理解,首先什麼是引用呢?引用其實就是使用,從編譯器的角度來看,就是從乙個符號找到對應記憶體並進行讀寫的過程。為了理解這乙個過程,我們先來看看對於乙個普通變數,編譯器是怎麼做的。

比如我們用c語言寫了這樣的語句

int a;

a = 3;

編譯器為了完成這兩句**,首先在編譯過程中要建立乙個符號表,樣子大概如下圖:

然後在執行過程中,編譯器發現a=3這句**時,會在符號表裡找a對應的位址,然後把3放入對應的位址,即這裡的0x1000。那如果是乙個指標呢?即如果是*p=3會怎麼做呢?首先,符號表變成了這個樣子

在執行過程中,編譯器遇到*p=3時,首先要從符號表中找到p的位址0x1004,然後取出0x1004中的內容,這裡假設為0x2000,最後把3放到0x2000記憶體位址中,即*(0x1000)=3。

下面再來看一下指標加偏移量的引用方式,還以上面的指標p為例,讓我們來看一下*(p+2)=3的實現過程。首先,編譯器從符號表中找到p然後,取出裡面的內容0x2000,再根據其型別(int*),做乙個運算,0x2000+2×sizeof(int)=0x2008。所以編譯器會把3放入0x2008這個記憶體位址。整個過程可表示為*(*(0x1004)+2)=3。從這裡也可以看出為什麼指標必須有型別,因為在引用過程中要用到指標所指型別的長度。

最後來看一下,陣列元素的引用是如何實現的,假設我們定義了乙個陣列,並對其元素進行了引用

int b[10];

b[4] = 3;

對應的符號表變成了這個樣子

那b[4]=3如何完成呢?首先,找到符號b,然後發現其型別為int(假想表達方式,c語言中不支援這樣寫),所以計算式變成了0x1008+4×sizeof(int)=0x1018,然後把3放入0x1018就可以了。用乙個式子表達就是*(0x1008+4)=3。

從上面的定址式子可以看出,普通變數、指標、陣列三者對於編譯器的區別。具體到陣列,它即具有普通變數的直接性,即不用取兩次位址裡的內容而是取一次,同時又具有和指標相同的偏移量引用方式,即下標的實現實際是由指標加偏移量實現的。

為了表明上述事實(或者是為了提高c語言入門門檻),c語言對指標與陣列的引用方式做了可以「交叉」使用的語法規定。就上面的例子來說,如果p指標指向陣列b時,b[i]、*(b+i)、p[i]、*(p+i)都是對陣列第i個元素的正確引用方式,這也給很多c語言學習者製造了「指標和陣列一樣」的錯覺。

3.在函式形參中的表現

在向函式傳遞引數時,如果實參是乙個一維陣列,那用於接受的形參為對應的指標。也就是傳遞過去是陣列的首位址而不是整個陣列。這麼做的原因主要是效率,這是無可爭議的,但為了使用上的簡便與通用,c語言接受兩種形參的寫法,即下面兩種寫法是相同的

int foo(int *a, int n);

int foo(int a, int n);

甚至是

int foo(int a[20], int n);
在底層都是完全一樣的形式。這簡化了c語言的使用,但同時也增加了對其進一步理解的難度。正因為很多程式稀里糊塗就通過了,所以程式設計師就會一直稀里糊塗下去。至於哪種寫法好的爭論,只要你理解了,用哪種寫法到是不必強求。指標形式表明了傳參過程的實質,而陣列形式表明了這個指標對應乙個陣列實參,甚至後面的數字可以提供陣列長度的參考。

4.如何區分二者

陣列和指標的關係如此微妙,那如何區分呢?

首先說為什麼要區分,主要由兩個方面的原因,一是c語言的主要戰場還是偏向於底層的,如果使用者對這塊兒一直模糊不清,那對其他知識的理解就會有困難,比如編譯器,硬體系統等相關知識。二是在特定的情況下,二者的表現的確不同,最常見的就是sizeof關鍵字的作用結果,另外還有取址操作符&的結果等情況。

至於如何區分,個人認為凡是要區分兩個相關的概念,一定要將二者的作用區分清楚,即為什麼要有這個東西。就比如sizeof與strlen的區分,只要明白了二者各自的作用,就會發現二者其實根本沒有一點兒關係,完全是為了實現不同功能而設定的。陣列和指標也是一樣,指標是一類特殊的變數,主要用途是函式間的傳址,用這種方式來改變實參內容。而陣列是用來實現線性表的結構,用於把同類物件集中在一起放置。所以,如果下次有人問你陣列和指標的區別,你首先第一句要說的就是二者根本沒有聯絡,這麼說並這麼理解,對於弄清這兩個傢伙的關係是很有裨益的。

C語言中指標陣列與陣列指標

1 指標陣列的實質是乙個陣列,這個陣列中儲存的內容全部是指標變數。2 陣列指標的實質是乙個指標,這個指標指向的是乙個陣列。1 int p 5 int p 5 int p 5 2 一般規律 int p p是乙個指標 int p 5 p是乙個陣列 3 如果核心和 結合,表示核心是指標 如果核心和 結合,...

簡單分析C語言中指標陣列與陣列指標的區別

首先來分別看一下,指標陣列的乙個小例子 include include int lookup keyword const char key,const char table,const int size printf d n lookup keyword static keyword,dim key...

C語言中指標與陣列注意地方

一開始我以為兩個spi data out是沒有區別的。都是指標,乙個是常量,乙個是變數而已。沒有太在意。如果有人單看spi data out 0 這個有問題的話我覺得你c語言跟我一樣差。相對於這兩個指標而言這個陣列操作沒有問題。但問題出在哪呢?就是那一長串的字串。把這些一結合就產生了各種問題 程式跑...