淺淡數組和指針

謝謝大牛的分享,看完後讓我對數組與指針有了更深刻的理解,在此謝過!

轉載:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242138.html

        http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242419.html

 

 

      很多時候,會有人說“指針和數組是相同的”,這是一種非常危險的說法,並不完全正確。在一定的上下文環境中,指針和數組是等同的,並非所有情況下如此。然而人們很多時候卻自然而然忽略了這種情況成立的條件,去假定所有情況下都是如此。下面着重談一下指針和數組的區別。

一.指針和數組的定義

      指針是指針,指針變量存儲的是一個地址,用來間接訪問數據,在32位系統下,一個指針變量(包括void指針)一般佔4個字節的空間(有的編譯器是佔2個字節)。指針可以指向任何內存空間,但不是任何內存空間都可以通過指針去訪問。

      數組是數組,定義一個數組之後,編譯器便根據該數組元素的類型和個數在內存開闢一段連續的空間來存放數據,從而直接訪問數據。

    下面看一個例子

    在file1.c中有如下代碼:

char p[100]="abcdef";

    在file2.c中有如下代碼:

複製代碼
複製代碼
#include<stdio.h>

extern char *p;

int main(void)
{
    printf("%c\n",p[1]);
    return 0;
}
複製代碼
複製代碼

    發現能夠編譯通過,但是能正確執行麼?調試發現:出現下圖這個錯誤,無法計算得到p[1]的值。原因稍後作解釋。

      從這裏就可以看出,指針和數組並不是等同的,數組的定義並不等同於指針的外部聲明(注意聲明和定義的區別,定義是爲一個變量或者對象分配內存空間,而聲明只是描述類型)。

二.指針和數組訪問時的區別

     對數組下標的引用:


     對指針的引用:

      從上面的圖中可以看出,指針和數組根本就是兩個完全不一樣的東西。對於數組,由於編譯器在編譯的時候就已經知道每個符號的地址,因此如果需要一個地 址來執行某種操作,可以直接進行操作,並不需要增加指令首先取得具體地址,對於數組就是如此;而對於指針,必須在運行時首先取得它當前的具體值然後才能進 行引用。從這點就可以解釋爲什麼上面的程序無法正確執行,因爲在file1.c中定義的p是一個數組,而在file2.c中卻聲明的是一個指針。因此在 file2.c中引用時默認p是一個指針變量,並且會把指針變量中的任何數據當做地址來處理,因此首先取原數組的前4個字節的內容:0x61 0x62 0x63 0x64構成一個地址(暫不考慮大小端的問題)0x61626364,然後按照char型讀取0x61626364這個地址中的內容,但是這個地址可能並 不是有效地地址,即使是有效地,也不是我們想要的。大家可以想一下如果在file1.c中將p定義爲指針類型,而在file2.c中將p聲明爲數組類型, 會是什麼情況?

     解決上述問題的辦法就是在任何時候保持定義和聲明一致

測試程序:

file2.c

複製代碼
複製代碼
#include<stdio.h>

extern char p[];
extern void print();

int main(void)
{
    printf("%x\n",p[0]);
    printf("%x\n",p[1]);
    printf("%08x\n",p);      //注意此時p的值是存儲原指針p(file1.c中的p)的內存單元的首地址
     print();
    return 0;
}
複製代碼
複製代碼

file1.c

複製代碼
複製代碼
#include<stdio.h>
char *p="abcdef";

void print()
{
    printf("%08x\n",p);
    printf("%08x\n",&p);
}
複製代碼
複製代碼

執行結果爲:

28
20
00424a30
00424a30
00422028
00424a30
Press any key to continue

三.一些應該注意的地方

   1.sizeof計算所佔空間時的區別。

      對於數組,sizeof計算的是整個數組所佔的空間,而在32位系統下,sizeof 指針的值始終爲4.

   2.數組名作爲左值時不能被修改,而指針作爲左值時可以被賦值。

   3.指針可以進行自增(自減)運算(void指針除外,因爲void指針無法知道步長),但是數組不能進行自增或者自減運算。
   4.理解char *p="abcde"和char str[]="abcde"的區別。

複製代碼
char *str = "abcde"; 與 char str[] ="abcde"; 有何區別?

區別還是很大的。

1,char *str這個沒有實際的內存空間,所指的只是"abcde"字符串而已,所以不能修改其值. 而char str[]這個是真正的把"abcde"保存在了內存中,有實際內存空間,所以,可以修改.

2,以指針角度來看,char *str指針可以移動(可以進行str++,str--),但是char str[]不能移動.因爲數組可以看成一個常指針.

3,大小.. 凡是指針,32位系統均是4個字節.也就是
char *str="abcde";
sizeof(str)爲4;
而數組卻可以得出其實際大小.
char str[]="abcde";
sizeof(str)爲6(末尾'\0'字符);

當然,strlen(str)都是一樣的爲5.
複製代碼

 

     上面討論了指針和數組的一些區別,然而在某些情況下,指針和數組是等同的,下面討論一下什麼時候指針和數組是相同的。

C語言標準對此作了說明:

規則1:表達式中的數組名被編譯器當做一個指向該數組第一個元素的指針;

          注:下面幾種情況例外

          1)數組名作爲sizeof的操作數

          2)使用&取數組的地址

規則2:下標總是與指針的偏移量相同;

規則3:在函數參數的聲明中,數組名被編譯器當做指向該數組第一個元素的指針。

       規則1和規則2結合在一起理解,就是對數組下標的引用總是可以寫成“一個指向數組的起始地址的指針加上偏移量”。如a[i]總是被編譯器解析爲*(a+i)的形式。

 

規則1:表達式中的數組名總被編譯器解析爲指針,因此如下語句int a[3];int *p=a;是可以正確編譯執行的。在表達式中a被解析爲指向數組第一個元素的指針,那麼賦值符號兩邊的類型匹配,因此可以正確編譯執行。

規則2:下標總是和指針的偏移量相同。C語言中將數組的下標改寫成指針偏移量的主要原因在於指針和偏移量是底層硬件所使用的基本類型。如a[i]中 的i總被編譯器解析爲偏移量,所以a[i]總是被改寫成*(a+i)的形式,a是指向數組第一個元素的指針,加上偏移量i,表示該指針向後移i個步長,然 後取a+i所在單元的內容。由此就可以解釋爲什麼C語言中數組的下標可以爲負,而且在我看來,C語言中不檢查數組的下標是否越界同樣跟這個有關,如下面這 段程序:

複製代碼
複製代碼
#include<stdio.h>

int main(void)
{
    int a[3]={1,2,3};
    int *p=(a+3);
    printf("%d\n",p[-1]);
    return 0;
}
複製代碼
複製代碼

程序執行結果爲3,雖然下標爲-1,但是被編譯器解析爲偏移量,因此相當於*(p-1)。

規則3:在函數參數的聲明中,數組名被編譯器當做指向該數組第一個元素的指針。在C語言中將形參的數組和指針等同起來是出於效率的考慮。假如不這麼 做,將整個數組的每個元素的值都拷貝一份進行傳遞,這樣無論在時間上還是空間上的開銷都可能是非常大的。但是又要能操作到數組中的元素,只需將數組第一個 元素的地址傳遞給調用函數,然後通過指針去訪問想要訪問的空間,這樣一來時空消耗將大大減少。因此在函數內部,編譯器始終把參數中聲明的數組名當做一個指 向數組第一個元素的指針,這樣一來,編譯器可以產生正確代碼,並不需要對數組和指針這兩種情況作區分。因此void fun(int a[]);和void fun(int *a)兩種形式的效果完全等同,在函數內部去引用a的話,始終都會被編譯器認爲是指針。因爲void fun(int a[]);這種形式最終還是會被編譯器解析爲void fun(int *a);這種形式告訴我們調用時必須傳遞一個指向整型數據的指針。所以下面這段代碼可以正確編譯和執行:

複製代碼
複製代碼
#include<stdio.h>

void fun(int a[])
{
    printf("%d\n",a[0]);
}
int main(void)
{
    int a[3]={1,2,3};
    int *p1,*p2;
    int b=4;
    p1=a;
    p2=&b;
    fun(a);
    fun(&a[1]);
    fun(p1);
    fun(p2);
    fun(&b);
    return 0;
}
複製代碼
複製代碼

運行結果:

區分幾個表達式的含義:

&p,p,a,&a

&p:表示取存儲指針變量p的內存單元的地址;  sizeof(&p)=4;   ( &p就是指向p的指針,即二級指針)

p:表示取指針變量p存儲的地址;                     sizeof(p)=4;

a:表示取數組第一個元素的地址;                    sizeof(a)=3*4=12;

&a:表示取整個數組的首地址;                        sizeof(&a)=4(在VC++6.0中該值爲12,我認爲是錯誤的,因爲其類型是數組指針)

雖然a和&a的值相同,但是所表達的含義完全不同,a表示取數組第一個元素的地址,而&a表示取數組的首地址。它們所代表的類型也 完全不同,a是一個int型指針,而&a是一個int (*p)[]型指針,即數組指針(在後續文章中會作解釋)。所以a+1和&a+1得到的結果不同,a+1表示將指向該數組的第一個元素的指針向後 移一個步長(這裏的步長爲數組元素類型所佔的字節數);而&a+1表示將指向該數組的指針向後移動一個步長(而此處的步長爲數組元素個數*元素類 型所佔的字節數)。

如下程序輸出結果爲:

複製代碼
複製代碼
#include<stdio.h>

int main(void)
{
    int a[3]={1,2,3};
    int *p=a;
    printf("%08x\n",&p);
    printf("%08x\n",p);
    printf("%08x\n",&p+1);
    printf("%08x\n",p+1);
    printf("%08x\n",a);
    printf("%08x\n",&a);
    printf("%08x\n",a+1);
    printf("%08x\n",&a+1);    //注意輸出結果
    return 0;
}
複製代碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章