當前位置:編程學習大全網 - 編程語言 - C中數組和指針到底是否相同?

C中數組和指針到底是否相同?

指針是C/C++的精華,而指針和數組又是壹對歡喜冤家,很多時候我們並不能很好的區分指針和數組,對於剛畢業的計算機系的本科生很少有人能夠熟練掌握指針以及數組的用法和區別。造成這種原因可能跟現在大學教學以及現在市面上流行的很多C或者C++教程有關,這些教程雖然通俗易懂,但是在很多關鍵性的地方卻避而不談或者根本闡述不清楚,甚至很多時候闡述的是錯誤的觀點。壹般最初學習C/C++的時候接觸到的都是這類教程,學習效果可想而知。對於初學者選擇好的教程真的很關鍵,因為先入為主,壹旦妳接受了錯誤的觀點或者思想即使後來知道了也壹時很難糾正過來(我是深有體會),在此我推薦三本很適合於初學者的教程:

《The C Programming Language》Brian W. Kernighan和Dennis M. Ritchie的經典著作(K&R聖經)

《C ++ Primer》Stanley B. Lippman, Josée Lajoie, Barbara E. Moo C++經典權威著作

《Pointers on C》Kenneth A.Reek

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

壹.指針和數組的定義

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

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

下面看壹個例子

在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"的區別。

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存儲的地址; 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;

}

  • 上一篇:編程零基礎如何學好Java軟件開發?
  • 下一篇:本人零基礎,打算現在去培訓機構學半年c++,不知到時能找到工作麽?
  • copyright 2024編程學習大全網