C/C++數組和指針詳解

/****************************************************************/ios

/*            學習是合做和分享式的!c++

/* Author:Atlas                    Email:wdzxl198@163.com   編程

/*  轉載請註明本文出處:數組

*  http://blog.csdn.net/wdzxl198/article/details/9087497數據結構

/****************************************************************/app

1.引文

     本文不會對數組和指針的基本概念和操做作講解,不是很清楚的請看c/c++相關書籍,這裏默認讀者對這些內容熟知。函數

首先看一個例子:如下兩個程序,程序1使用整形變量,程序2使用整形指針,可是在編譯運行後,會是什麼樣的?學習

program 1:ui

   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?我想當你看完這篇文章,確定豁然開朗(分析見最後)。spa

       經過以上簡單的引入例子,能夠對數組和指針有個簡單認識,接下來本文從數組和指針在訪問方式上的區別、在函數調用上的區別以及數組和指針內在的聯繫上進行分析。

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: }

經過彙編代碼來看,在變量定、輸入以及輸出時看出整型變量不一樣於指向整形的指針。

 


本文爲《c++內存管理系列》的知識補充。

參考文獻:c++內存管理學習綱要

Edit by Atlas  

Time:2013/6/13  15:39

相關文章
相關標籤/搜索