/****************************************************************/
/* 學習是合作和分享式的!
/* Author:Atlas /* 轉載請注明本文出處:
/****************************************************************/
1.引文
本文不會對數組和指針的基本概念和操作做講解,不是很清楚的請看c/c++相關書籍,這裡默認讀者對這些內容熟知。
首先看一個例子:以下兩個程序,程序1使用整形變量,程序2使用整形指針,但是在編譯運行後,會是什麼樣的?
program 1:
1: #include <iostream> 2: int main(){ 3: int int_input; 4: cin>>int_input; 5: cout<<(int_input + 4)<<endl; 6: return 0; 7: }
program 2:
1: #include <iostream> 2: int main(){ 3: int *int_ptr = new int[1]; 4: cin>>*int_ptr; 5: cout<< (*int_ptr + 4)<<endl; 6: delete(int_ptr); 7: return 0; 8: }
答案顯而易見,都是需要你輸入一個整數,然後輸出剛才輸入的值。問題就來了,這兩個程序運行結果是一樣的,你能說這兩個程序是完全一樣的麼?? Eexactly,NO! Why?我想當你看完這篇文章,肯定豁然開朗(分析見最後)。
通過以上簡單的引入例子,可以對數組和指針有個簡單認識,接下來本文從數組和指針在訪問方式上的區別、在函數調用上的區別以及數組和指針內在的聯系上進行分析。
2.在內存訪問方式上,數組和指針的區別
本小節講述對數組的引用和對指針的引用的不同之處,學習資料來源於《expert c programing》。
為了對數組和指針對內存訪問方式的理解,首先對“地址y”和“地址y的內容”做簡單的區別,詳細內容如下圖:
(ps:這個圖中文字描述上,我覺的語言欠妥。什麼是X所代表的地址?左值在編譯時可知,左值表示存儲結果的地方?等等,我看了原版,感覺也不是翻譯的問題,讓人非似懂的。大家的意見呢?)
編譯器為每個變量分配地址(左值)。這個地址在編譯時可知且一直存在,而它的右值在運行時才能知道。通俗的說:每個變量都有一個地址,這個地址在編譯時可以知道,而地址裡存儲的內容(也就是變量的右值)只有在運行時才能知道。如果需要用到變量的值(也就是已知地址存儲的值),那麼編譯器發出指令從指定地址讀入變量值並放入相應寄存器中。
(1)數組的訪問方式
關鍵之處在於每個符號的地址在編譯時可知,如果編譯器需要一個地址(比如說加上偏移量)來執行某種操作,可以可以直接操作。相反、對於指針,必須在運行時取得它的地址,然後才能
對它進行接觸引用操作。下圖A展示了對數組下標的引用,體現數組的訪問形式:
看到這裡,就明白為什麼extern char a[]和extern char a[100]相同的原因了。這兩個聲明都是提示a是一個數組,也就是一個內存地址,數組內的字符可以由這個地址(加偏移量)找到。
(2)指針的訪問方式
若聲明的是一個指針,如 extern char *p,它表示p指向一個字符,為了取得這個字符,必須知道地址p的內容,把它作為字符的地址並從這個地址中取得這個字符。如下圖B所示,體現指針訪問的形式。
(3)若“定義為指針,但是以數組的方式引用”,這樣會發生什麼?
當一個外部數組的實際定義是一個指針,但卻以數組的方式對其引用時,會發生些什麼?會向圖A中方式直接的引用訪問?事實是編譯器所執行的是圖B中的對內存間接引用。因為我們告訴編譯器擁有的一個指針,如圖C所示。
我們這裡對照圖C中的訪問方式:
1: char *p = "abcdefgh"; ... p[3];
與圖A中的訪問方式:
1: char a[] = "abcdefgh"; ... a[3];
這兩種方式都可以取得字符‘d’,但是途徑不一致。
(4)數組和指針的一些特點對比
表1 數組和指針的區別
指針
數組
保存數據的地址 保存數據
間接訪問數據,首先取得指針的內容,把它作為地址,然後從這個地址提取數據。
如果指針有一個下標[i],就把指針的內容加上i作為地址,從中提取數據 直接訪問數據,a[i]只是簡單的以a+i為地址取得數據
通常用於動態數據結構 通常用於存儲固定數目且數據類型相同的元素
相關的函數為malloc(),free() 隱式分配和刪除
通常指向匿名數據 自身即為數據名
數組和指針可以在定義中用字符串常量進行初始化,盡管看上去一樣,底層的機制卻不同。
定義指針時,編譯器並不為它所指向的對象分配空間,只為指針本身分配空間。除非在定義同時付給一個指針一字符竄常量進行初始化。
如:char *p = "breadfruit"; (注意:只有對字符串常量才是如此,不能指望為浮點數之類的常量分配空間例如:float *p = 3.1415 ,!!!這是錯誤的)
一般情況下初始化指針時創建的字符串變量被定義為只讀。如果試圖修改就會出現未定義的行為。
數組可以用字符串常量進行初始化:
char a[] = “gooseberry“;
與指針相反,由字符串常量初始化的數組可以修改的。
3再論數組和指針
在內存訪問方式上,數組和指針存在區別。意識到數組和指針是不同的,但是在實際應用中有時候數組和指針卻是相同的!?多麼神奇!如果你是編程老手,就會知道在實際應用中數組和指針可以互換的情形要比兩者不可互換的情形更為常見。
(1)“聲明”和“使用”情形下
聲明本身還可以進一步分為三種情況:
1)外部數組的聲明(external array)
2)數組的定義(它是聲明的一種特殊情況,它分配內存空間,並可能提供一個初值)
3)函數參數的聲明
所有作為函數參數的數組名在編譯時是會轉換為指針(有種說法是“退化為指針”,一個意思),而其他情況的聲明,數組的聲明就是數組 ,指針的聲明就是指針,兩者不混淆。但是在使用數組時,數組總是可以寫成指針的形式,兩者可以互換。如下圖所示:
上圖在聲明和使用上說明了數組和指針在什麼情況下可以互換使用,即是相同的。然而,數組和指針在編譯器處理時是不同的,在運行時的表現形式也是不同的,並可能產生不同的代碼,這點是需要牢記的!具體原因,如果看了前面的第一部分內容,很容易理解。
(2)什麼時候數組和指針是相同的
在C語言標准中,對此有如下說明
1)表達式中的數組名(與聲明不同)被編譯器當作一個指向該數組第一個元素的指針;
在表達式中,指針和數組是可以互換的,因為他們在編譯器中最終形式都是指針。如何理解這句話,請看下面的這個例子。
例子:
1: int a[10],*p,i = 2;可以通過以下任一種方式訪問a[i]
p=a;
p[i];
p = a;
*(p+i);
p = a+i;
*p;
原因是對數組的引用如a[i]在編譯時總是被編譯器改寫成*(a+i)的形式。
2)下標總是與指針的偏移量相同;
C語言中,把數組下標作為指針的偏移量。根本原因是指針和偏移量是底層硬件所使用的基本模型。
3)在函數參數的聲明中,數組名被編譯器當作指向該數組第一個元素的指針;
理解這句話首先要明白形參和實參的區別,
術語 定義 例子
形參(parameter) 它是一個變量,在函數定義或者函數聲明的原型中定義。又稱“形式參數(formal parameter)” int power(int base,int n);
base 和 n 都是形參
實參(argument) 在實際調用一個函數時所傳遞給函數的值。又稱“實際參數(actual parameter)” i = power(10,j);
10和j都是實參。
在函數參數這種特殊情況下,編譯器必須把數組名當作指向該數組第一個元素的指針形式。編譯器只向函數傳遞數組的地址,而不是整個數組的拷貝。
(3)數組和指針可交換型總結
a)對於a [i]這種形式的訪問數組,通常被解釋為指針形式*(a + i) 也就是上文中所說的“表達式”的情形
b)指針就是指針,沒有說指針轉化為數組的情況,你可以用下標的形式去訪問指針,但一般都是指針作為函數參數時,而且傳入的是一個數組
c)在函數參數的聲明中,數組可以看做指針,(也只有這種情況)
d)當把一個數組定義為函數參數時,可以定義為數組,也可以是指針
e)其他的所有情況,聲明和定義必須匹配。如果定義了一個數組,在其他文件中對它也必須聲明為數組。指針也一樣。
(4)數組和指針參數是如何被編譯器修改的
看到文章這裡,可以說是到一個結束段落了,你基本理解了數組和指針的不同和相同,學習更多知識需要自己的實踐操作!!!哦,忘了還有對引文中問題的解答,看下第四部分吧。
上部分中提到編譯器對數組到指針的修改,所以這裡補充一點東西。
“數組名被改寫成一個指針參數”規則不是遞歸定義的。就是說數組的數組會被改寫成“數組的指針”,而不是“指針的指針”。
實參
所匹配的形式參數
數組的數組 char c[8][10]; char(*)[10]; 數組指針
指針數組 char *c[15]; char **c; 指針的指針
數組指針(行指針) char (*c)[64]; char (*c)[64]; 不改變
指針的指針 char **c; char **c; 不改變
4 對引文中問題的解答
這裡我們在內存中方式的不同來看這個問題。查看其匯編代碼答案就一目了然了。
第一個程序:整型變量的
1: #include <iostream> 2: int main(){ 3: int int_input; 4: cin>>int_input; 5: cout<<(int_input + 4)<<endl; 6: return 0; 7: }匯編程序:
1: 2212: main(){ 2: 00401000 push ebp 3: 00401001 mov ebp,esp 4: 00401003 sub esp,44h 5: 00401006 push ebx 6: 00401007 push esi 7: 00401008 push edi 8: 2213: int int_input; 9: 2214: cin>>int_input; 10: 00401009 lea eax,[ebp-4] 11: 0040100C push eax 12: 0040100D mov ecx,offset cin (00414c58) 13: 00401012 call istream::operator>> (0040b7c0) 14: 2215: cout<<(int_input+4)<<endl; 15: 00401017 push offset endl (00401070) 16: 0040101C mov ecx,dword ptr [ebp-4] 17: 0040101F add ecx,4 18: 00401022 push ecx 19: 00401023 mov ecx,offset cout (00414c18) 20: 00401028 call ostream::operator<< (0040b3e0) 21: 0040102D mov ecx,eax 22: 0040102F call ostream::operator<< (00401040) 23: 2216: return 0; 24: 00401034 xor eax,eax 25: 2217: }
第二個程序:整形指針的
1: #include <iostream>
2: int main(){ 3:
int *int_ptr = new int[1];
4: cin>>*int_ptr;
5: cout<< (*int_ptr + 4)<<endl;
6: delete(int_ptr);
7: return 0;
8: }匯編程序:
1: 2212: main(){ 2: 00401000 push
ebp 3: 00401001 mov
ebp,esp 4: 00401003 sub
esp,4Ch 5: 00401006 push
ebx 6: 00401007 push
esi 7: 00401008 push
edi 8: 2213:
int *int_ptr = new int[1];
9: 00401009 push
4 10: 0040100B call
operator new (004011b0)
11: 00401010 add
esp,4 12: 00401013 mov
dword ptr [ebp-8],eax
13: 00401016 mov
eax,dword ptr [ebp-8]
14: 00401019 mov
dword ptr [ebp-4],eax 15: 2214:
cin>>*int_ptr; 16: 0040101C mov
ecx,dword ptr [ebp-4] 17: 0040101F push
ecx 18: 00401020 mov
ecx,offset cin (00414c38)
19: 00401025 call
istream::operator>> (0040b8a0) 20: 2215:
cout<< (*int_ptr + 4)<<endl; 21: 0040102A push
offset endl (004010a0) 22: 0040102F mov
edx,dword ptr [ebp-4] 23: 00401032 mov
eax,dword ptr [edx] 24: 00401034 add
eax,4 25: 00401037 push
eax 26: 00401038 mov
ecx,offset cout (00414bf8) 27: 0040103D call
ostream::operator<< (0040b4c0) 28: 00401042 mov
ecx,eax 29: 00401044 call
ostream::operator<< (00401070) 30: 2216:
delete(int_ptr); 31: 00401049 mov
ecx,dword ptr [ebp-4] 32: 0040104C mov
dword ptr [ebp-0Ch],ecx 33: 0040104F mov
edx,dword ptr [ebp-0Ch] 34: 00401052 push
edx 35: 00401053 call
operator delete (00401120) 36: 00401058 add
esp,4 37: 2217: return 0; 38: 0040105B xor
eax,eax 39: 2218: }
通過匯編代碼來看,在變量定、輸入以及輸出時看出整型變量不同於指向整形的指針。