C語言:陣列和指標的區別 - 程式前沿

文章推薦指數: 80 %
投票人數:10人

轉載請註明來源實際上關於陣列與指標的區別這個問題在《C專家程式設計》已經有很詳細的闡釋,但我想用自己的語言說一說我的理解。

陣列是指標? 程式語言前端開發IOS開發Android開發雲端運算人工智慧伺服器搜尋資料庫軟體開發工具C語言:陣列和指標的區別2018.07.28程式語言c,C語言,debug,it語言,r語言操作語言,r語言邏輯語言,sql語言c語言,was語言,彙編,指標,陣列HOME程式語言C語言:陣列和指標的區別Advertisement轉載請註明來源http://blog.csdn.net/imred/article/details/45441457實際上關於陣列與指標的區別這個問題在《C專家程式設計》已經有很詳細的闡釋,但我想用自己的語言說一說我的理解。

目錄1.陣列是指標?2.陣列是靜態常量指標(static/Compile-timeconstant)?3.陣列是動態常量指標(const/Runtimeconstant)?4.陣列是什麼?4.1.左值和右值4.2.陣列就是陣列!4.3.陣列作為左值4.4.陣列作為右值5.總結陣列是指標?最近在做資料結構課設,其中一個函式發生了令人費解的錯誤,簡化後的程式碼如下:#include intmain() { charfoo[]="abcde"; char**bar=&foo; printf("%c\n",*(*bar)); return0; }程式執行到printf語句後便會掛掉,除錯時會提示一個SIGSEGV訊號,根據原來的經驗,這時程式試圖訪問本不應該訪問的記憶體。

原來在C語言課堂上老師經常提到陣列就是一個指標,指標也可以像陣列那樣用使用中括號的方式來進行記憶體訪問。

以這樣的想法來分析前面的程式:foo是一個字元指標,即foo的值即為“abcde”的首字元“a”的地址,*foo即為‘a’;那麼foo這個指標一定存在某個記憶體單元,&foo獲得這個記憶體單元的地址,即pfoo是指向foo的指標,那麼*pfoo得到foo,*(*pfoo)應該得到‘a’了;這樣理解的話,程式是不應該有問題的。

下面我們使用指標代替陣列來實現上面的程式:#include #include intmain() { char*foo=(char*)malloc(sizeof(char)*2); *foo='a'; *(foo1)=0; char**pfoo=&foo; printf("%c\n",*(*pfoo)); return0; }程式這次執行結果和預料的相同,輸出一個字母a。

由此可見,陣列就是指標,這種說法是錯誤的。

陣列是靜態常量指標(static/Compile-timeconstant)?有人認為陣列是一個靜態常量,即陣列名代表一個靜態的地址值,在編譯時確定,下面程式碼可以證偽這種說法intmain() { charfoo[]={'a'}; staticchar*p=foo; return0; }使用gcc編譯時會有以下錯誤:error:initializerelementisnotconstant可見陣列名並不是代表一個靜態量,並非地址常量。

如果定義foo時加上static限定符,編譯就會通過,此時陣列名才代表了一個靜態量。

陣列是動態常量指標(const/Runtimeconstant)?請看以下程式碼:intmain() { charfoo[]={'a'}; char*constbar;//為什麼是這種寫法,請自行查閱相關資料 char*baz; foo=baz;/*1*/ bar=baz;/*2*/ return0; }gcc編譯時錯誤資訊為:/*1*/error:incompatibletypeswhenassigningtotype'char[1]'fromtype'char*' /*2*/error:assignmentofread-onlyvariable'bar'12兩處出錯資訊並不相同,若陣列為動態常量指標,出錯資訊應像2那樣。

陣列是什麼?陣列既不是靜態常量,也不是指標,那麼陣列是什麼?左值和右值首先補充一些左值和右值的知識,引用《C專家程式設計》中的一段話:出現在賦值符左邊的符號有時被稱為左值,出現在賦值符右邊的符號有時被稱為右值。

編譯器為每個變數分配一個地址(左值)。

這個地址在編譯時可知,而且該變數在執行時一直儲存於這個地址。

相反,儲存於變數中的值(它的右值)只有在執行時才可知。

如果需要用到變數中儲存的值,編譯器就發出指令從指定地址讀入變數值並將它存於暫存器中。

我對左值的理解和書上有些區別,我把這裡的“符號”稱為“物件”,每一個符號都代表一個物件,物件與地址是一一對應的。

即如果宣告瞭inta,那麼a作為一個左值時,a即代表這個儲存在某個特定的地址的物件,對這個物件賦值即為把值放在這個特定的地址;a作為右值時即代表a的內容,就是一個單純的值,而不是物件。

一個值是不能作為左值的,比如一個常數1,1=a這樣的賦值語句是無法編譯通過的。

在我看來,“左值”義同“物件”,“右值”義同“值”,所以下面“左值”和“物件”指的是相同的東西。

但是“左值”又有一個子集:“可修改的左值”,只有這個子集中的東西才能放在賦值號左邊,因此我認為將引用中的第一句話修改為“出現在賦值符左邊的符號有時被稱為可修改的左值”更能表達其實際的意思。

為什麼要引出這個子集,為的就是要把陣列分出來,陣列是左值,但並不是可修改的左值,因此你也不能直接把陣列名放在等號左邊進行賦值。

陣列就是陣列!我先把結論放在這裡,然後在進行分析:陣列就是陣列,一個陣列名就代表一個陣列物件,這個物件內可以有一個或多個元素,每個元素型別都相同;正如int就是int,一個int變數名就代表一個int型別物件。

看到這裡,你可能要笑了,這不是什麼都沒說嗎,誰不知道陣列是這個意思啊,我想知道陣列和指標什麼關係。

其實對陣列的認識就是這樣一個返璞歸真過程,看我來慢慢解釋。

以下程式碼:/*1.c*/ intmain() { intfoo[]={1}; intbar=1; return0; }使用gcc將其彙編並以intel格式輸出組合語言檔案:gcc-S-masm=intel1.c關鍵部分為:movDWORDPTR[esp8],1 movDWORDPTR[esp12],1esp8位置就是那個intfoo[],esp12位置就是那個intbar。

可見,給int陣列的賦值時就像給一個int變數賦值一樣,並沒用指標來進行間接訪問,這個int陣列物件foo的記憶體地址在編譯時就確定了,是esp8;正如那個int物件bar一樣,它的記憶體地址在編譯時也確定了,是esp12。

以示區別,我將下面程式碼同樣以組合語言輸出:/*2.c*/ #include intmain() { int*foo=(int*)malloc(sizeof(int)); *foo=1; return0; }彙編的關鍵部分為:movDWORDPTR[esp],4 call_malloc movDWORDPTR[esp28],eax moveax,DWORDPTR[esp28] movDWORDPTR[eax],1前兩句為foo分配記憶體空間,第三句將分配的記憶體空間地址值賦給foo,foo的地址為esp28,編譯時已知。

下面是賦值部分,首先從foo那裡得到地址值,然後向這個地址賦值,這裡可以看出和給陣列賦值的差別,給陣列賦值時是將值直接賦到了陣列中,而不用從哪裡得到陣列的地址。

由上面可以看出,陣列更像一個普通的變數,編譯時就知道了其地址,可以直接賦值。

陣列作為左值陣列不能放在賦值號左邊,但陣列仍可以作為一個左值或者說物件出現在語句中,一個重要的例子就是取地址操作:&。

取地址操作&的運算元必須是一個左值,而不能是一個右值。

比如一個變數inta=1,&a就可以得到a的地址,但&1是非法的,一個單純的數值是沒有地址的。

那麼對於一個intfoo[],&foo會返回一個什麼樣的值呢?自然是一個指向陣列的指標咯,下面的程式可以看出來:intmain() { intfoo[1]; intbar[1]; bar=&foo;//故意觸發一個error return0; }那個賦值語句一定會觸發一個的錯誤,我們可以根據編譯輸出來確定它們的型別,錯誤為:error:incompatibletypeswhenassigningtotype'int[1]'fromtype'int(*)[1]'沒錯,&foo返回資料型別為int(*)[1],就是一個指向陣列的指標。

指向陣列?指向陣列的哪裡呢?指向陣列物件首地址,正如一個指向int物件的指標指向那個int物件佔有的兩個或四個記憶體單元的首地址一樣。

把&foo賦給一個普通的指標是可以的,不過會觸發一個warning,因為int*與int(*)[1]並不相容。

賦值後普通指標的值與&foo的值是相同的,都是陣列物件的首地址,只是普通指標把這塊記憶體當做int物件處理而已。

由於C語言是弱型別語言,你把&foo賦給int**********bar或者int*baz都是可以的,都不會導致error,只會導致warning,此時你列印出*bar或者*baz的值都是foo中第一個整數的值(前提是指標和陣列佔用空間大小相等)。

正如文章開頭的程式碼那樣,以這個整數的值作為一個地址值進行間接訪問(*(*bar))就會導致非法訪問的錯誤。

陣列作為右值陣列作為右值時會發生什麼?返回陣列物件內的所有值自然不可能,因此C語言中採取的方法是陣列作為右值時返回物件中元素型別的指標,指標指向第一個元素,類似上一個例子:intmain() { intfoo[1]; intbar[1]; bar=foo;//故意觸發一個error return0; }出錯資訊為:error:incompatibletypeswhenassigningtotype'int[1]'fromtype'int*'foo作為右值時返回了一個int*,就是這個特性給人造成了陣列就是指標的假象。

總結陣列作為左值和陣列作為右值時的區別造成了無數人的困惑與誤解:foo作為右值時確實等價於一個指標,因為陣列無法像普通物件那樣返回它的值,它的元素可能有成百上千個,但作為一個左值時——比如作為取地址操作符的運算元時,陣列就是作為一個陣列物件而出現的,而不是指標,取地址返回一個指向陣列的指標,而不是指向指標的指標。

一句話總結就是:陣列就是陣列,有著自己的特性。

(題外話:從生成的組合語言看,用指標來訪問記憶體實際上並不比使用陣列來訪問記憶體快,反而是慢了)轉載請註明來源http://blog.csdn.net/imred/article/details/45441457AdvertisementAdvertisement写评论取消回覆很抱歉,必須登入網站才能發佈留言。

近期文章Vue中容易被忽視的知識點2019.12.09if我是前端Leader,談談前端框架體系建設2019.12.09Spark入門(一)用SparkShell初嘗Spark滋味2019.12.08Spark入門(二)如何用Idea運行我們的Spark項目2019.12.08Spark入門(三)Spark經典的單詞統計2019.12.08Spark入門(四)Spark的map、flatMap、mapToPair2019.12.08Spark入門(五)Spark的reduce和reduceByKey2019.12.08Spark入門(六)Spark的combineByKey、sortBykey2019.12.08Spark入門(七)Spark的intersection、subtract、union和distinct2019.12.08Spark實戰尋找5億次訪問中,訪問次數最多的人2019.12.08AdvertisementAdvertisement



請為這篇文章評分?