楔子
去年,周星星大哥曾經在VCKBASE/C++論壇發表過一篇文章“數組引用"以避免"數組降階”,當時我不能深入理解這種用法的含義;時隔一年,我的知識有幾經錘鍊,終於對此文章漸有所悟,所以把吾所知作想詳細道來,竟也成了一篇文章。希望本文能對新手有所啟迪,同時也希望大家發現本文中的疏漏之處後不吝留言指教。
故事起源於周星星大哥給出的兩個Demo,為了節省地方,我把兩個Demo合二為一,也能說明同樣的問題:
#include <iostream>
using namespace std;
void Foo1(int arr[100])
{
cout << "pass by pointer: " << sizeof(arr) << endl;
}
void Foo2(int (&arr)[100])
{
cout << "pass by reference: " << sizeof(arr) << endl;
}
void main()
{
int a[100];
cout << "In main function : " << sizeof(a) << endl;
Foo1(a);
Foo2(a);
}其運行結果如下:In main function : 400
pass by pointer: 4
pass by reference: 400
這段代碼說明了,如果數組形參是數組名形式(或者指標形式,下文討論)時,使用sizeof運算子,將得不到原來數組的長度;如果用傳遞原數組引用的方法,則沒有問題。
這段代碼的確很難理解,因為這短短的十幾行涉及到了形參與實參的關係、數組名和指標的關係、引用的意義、聲名和運算式的關係這4大類問題,只要有1條理解不透、或者理解不正確,就理解不透上面的這段代碼。本文也就從這4個問題入手,把這4個問題首先解決掉,然後再探討上面的這段代碼。雖然這樣看來很是繁複,但是我認為從根上入手來理解、學習,是條似遠實近的道路。
一、函數形參和實參的關係
void Foo(int a);
Foo(10);
這裡的a叫做形式參數(parameter),簡稱形參;這裡的10叫做實際參數(argument),簡稱實參。形參和式參之間是什麼關係呢?他們是賦值的關係,也就是說:把實參傳遞給形參的過程,可以看作是把實參賦值給形參的過程。上面的例子中,實參10傳遞給形參a,就相當於a=10;這個賦值的過程。(因為資料類型多的很,無法舉例子舉全面,所以這裡就不舉例子了;如果覺得不好理解,就在vc中寫個sample調試一下各種資料類型的情況,你就能夠驗證這個結論了。)
二、數組名和指標的關係
這個問題是個曆史性的問題了,在C語言中,數組名是當作指標來處理的。更確切的說,數組名就是指向數組首元素地址的指標,數組索引就是距數組首元素地址的位移量。理解這一點很重要,很多數組應用的問題就是有此而起的。這也就是為什麼C語言中的數組是從0開始計數,因為這樣它的索引就比較好對應到位移量上。在C語言中,編譯過程中遇到有數組名的運算式,都會把數組名替換成指標來處理;編譯器甚至無法區分a[4]和4[a]的區別!*2 但是下面這一點需要注意:
int a[100];
int *b;
這兩者並不等價,第一句話聲明了數組a,並定義了這個數組,它有100個int型元素,sizeof(a)將得到整個數組所佔的記憶體大小,是400;第二句話只是聲明並定義了一個int型的指標,sizeof(b)將得到這個指標所佔的記憶體大小,是4。所以說,雖然數組名在運算式中一般會當作指標來處理,但是數組名和指標還是有差距的,最起碼有a==&a[0]但是sizeof(a)!=sizeof(a[0])。
並且在ANSI C標準中,也明文規定:在函數參數的聲明中,數組名北邊一起當作指向該數組第一個元素的指標。所以,下面的幾種書寫形式是等效的:
void Foo1(int arr[100]){}
void Foo2(int arr[]){}
void Foo3(int *arr){}
C++儘可能的全面相容C語言,所以這一部分的文法相同。