C/C++中數組形參帖)

來源:互聯網
上載者:User

楔子
   去年,周星星大哥曾經在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語言,所以這一部分的文法相同。
   三、引用的意義
   
   “引用“是C++中引進的概念,C語言中沒有。它的目的在於,在某些方面取代指標。假如你認為引用和指標並無大不同,肯定會為指標報不平,頗有一種“即生亮何生瑜”的感慨;但是,引用確實有新的特色,也確實在很多地方的表現和指標有所不同,本文就是一例。使用引用,我們要把握這它最最最重要的一點,這也是它和指標最大的區別:引用一經定義,就和被它引用的變數緊緊地結合在一起,再不分開,對引用的任何操作都反映在它引用的變數上;而指標,只是訪問它指向變數的另一種方式,兩者雖有聯絡,但是並不像引用那樣密不可分。:)
  #include <iostream>
  using namespace std;
  void main()
  {
  int a = 10;
  int & a_ref = a;
  int b = 20;
  // 定義引用時就要初始化,說明引用跟它指向的元素密不可分
  //int & b_ref ; // error C2530: ''b_ref'' : references must be initialized 
  int & b_ref = b;
  int * p;
  int * q;
  //下面的結果證實了:引用一經定義,就不能再指向其他目標; 
  //把一個引用b_ref賦值給另一個引用a_ref,其實就是把b賦值給了a.
  cout << a_ref << " " << b_ref << endl;
  a_ref = b_ref;
  cout << a_ref << " " << b_ref << endl;
   
   
  cout << a << " " << b << endl;
  cout << endl;
   //即使對一個引用a_ref取地址,取得也是a的地址。已經“惡鬼附體”了:)
  p = &a;
  q = &a_ref;
  cout << p << " " << q << endl;
  cout << endl;
  //下面這段代碼展示了指標與引用的不同
  p = &a;
  q = &b;
  cout << p << " "<< q << endl; 
  p = q;
  cout << p << " "<< q << endl;
  cout << endl;
  system("pause");
  }
  下面是啟動並執行結果,以供參考:
  10 20
  20 20
  20 20
  0012FED4 0012FED4
  0012FED4 0012FEBC
  0012FEBC 0012FEBC
   四、聲明和運算式的關係
   
   這裡想說明的是,分析一個聲明可以把它看作一個運算式,按照運算式中的運算子優先順序順序來聲明。比如int (&arr)[100],你首先要找到聲明器arr,那麼&arr說明arr是一個引用。什麼引用呢?在看括弧外面,[]說明了這一個數組,100說明這個數組有100個元素,前面的int說明了這個數組的每個元素都是int型的。所以,這個聲明的意思就是:arr就是指向具有100個int型元素的數組的引用。假如你覺得這種理解很晦澀,那你就不妨用typedef來簡化聲明中的複雜的運算子優先順序關係,比如下面的形式就很好理解,其效果是和最初的那個例子是一樣的:
  #include <iostream>
  using namespace std;
  typedef int INTARR[100]; //這個,這個...也可以用運算式來理解,有點“GNU is not UNIX“的味道是吧?
  void Foo(INTARR &arr) //noh,這樣看就很明白了,就是傳了個引用進去
  {
  cout << "pass by reference: " << sizeof(arr) << endl;
  }
  void main()
  {
  INTARR a; //用類型別名來定義a
  INTARR &a_ref=a; //用類型別名來定義引用a_ref
  cout << "In main function : " << sizeof(a) << endl;
  Foo(a); 
  system("pause");
  }
   大結局
   
   
   吐沫星亂飛了半天,大家感覺還好吧,快結束了,大家再忍耐一下。看看下面這段程式:
  #include <iostream>
  using namespace std;
  void main()
  {
  int a[100];
  int * pa = a;
  int (&a_ref)[100] = a;
  cout << sizeof(a) << endl;
  cout << sizeof(pa) << endl;
  cout << sizeof(a_ref) << endl;
  system("pause");
  }
   怎麼樣,是不是對輸出結果感到很自然呢?假如是,那就好辦了。我總結一下就下課哈!^_^ 
   數組名在運算式中,往往被當作是指向首元素a[0]地址的指標,但是在sizeof(a)中,返回的結果是數組a佔用記憶體的大小;pa是指向a的指標,他也指向a[0],但是sizeof(pa)中,返回結果是pa這個指標所佔記憶體空間的大小,之所以這樣,因為pa這個指標和數組a的結合不夠緊密,屬於訪問數組a的第二被選方案;a_ref這個引用,就是對數組a的引用,就像“惡鬼附體”一樣,一旦附體附上了,你怎麼也甩不掉它,對它的任何操作,全部都反映在a上。在看本文最初的那個例子,比這個例子所增加的操作就是函數實參到形參的傳遞,我們在上面說過了,從實參到形參的傳遞可以看作是把實參賦值給形參。所以本文最初的那個例子,其實際的操作過程就和本文最後的這個例子是一樣的。所以,並非函數把數組給“降階”了,而是它原原本本就該這樣,千萬不必希奇。 
   :p
   意猶未盡,在PS一段:在C語言中,沒有引用,是怎麼解決這種問題呢。下面是常用的幾種作法:
  
   傳遞數組的時候,在增加一個參數,用來記錄數組的元素個數或者長度。main(int argc, char ** args)就是這種做法;這種方法還可以防止溢出,安全性比較高。
   
   在數組的最後一個有效元素後面作一個標誌,指明數組已經結束了。C語言中用char數組表

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.