天天看點

C語言數組參數與指針參數

我們都知道參數分為形參和實參。形參是指聲明或定義函數時的參數,而實參是在調用函數時主調函數傳遞過來的實際值。

1、能否向函數傳遞一個數組?看例子:

void fun(char a[10])

{

   char c = a[3];

}

intmain()

   char b[10] = “abcdefg”;

   fun(b[10]);

   return 0;

先看上面的調用,fun(b[10]);将b[10]這個數組傳遞到fun 函數。但這樣正确嗎?b[10]是代表一個數組嗎?

顯然不是,我們知道b[0]代表是數組的一個元素,那b[10]又何嘗不是呢?隻不過這裡數組越界了,這個b[10]并不存在。但在編譯階段,編譯器并不會真正計算b[10]的位址并取值,是以在編譯的時候編譯器并不認為這樣有錯誤。雖然沒有錯誤,但是編譯器仍然給出了兩個警告:

   warning c4047: 'function' : 'char *' differs in levels of indirection from 'char '

   warning c4024: 'fun' : different types for formal and actual parameter 1

這是什麼意思呢?這兩個警告告訴我們,函數參數需要的是一個char*類型的參數,而實際參數為char 類型,不比對。雖然編譯器沒有給出錯誤,但是這樣運作肯定會有問題。如圖:

C語言數組參數與指針參數

這是一個記憶體異常,我們分析分析其原因。其實這裡至少有兩個嚴重的錯誤。

第一:b[10]并不存在,在編譯的時候由于沒有去實際位址取值,是以沒有出錯,但是在運作時,将計算b[10]的實際位址,并且取值。這時候發生越界錯誤。

第二:編譯器的警告已經告訴我們編譯器需要的是一個char*類型的參數,而傳遞過去的是一個char 類型的參數,這時候fun 函數會将傳入的char 類型的資料當位址處理,同樣會發生錯誤。(這點前面已經詳細講解)

第一個錯誤很好了解,那麼第二個錯誤怎麼了解呢?fun 函數明明傳遞的是一個數組啊,編譯器怎麼會說是char *類型呢?别急,我們先把函數的調用方式改變一下:

   fun(b);

b 是一個數組,現在将數組b 作為實際參數傳遞。這下該沒有問題了吧?調試、運作,一切正常,沒有問題,收工!很輕易是吧?但是你确認你真正明白了這是怎麼回事?數組b真的傳遞到了函數内部?

2、無法向函數傳遞一個數組

我們完全可以驗證一下:

   int i = sizeof(a);

如果數組b 真正傳遞到函數内部,那i 的值應該為10。但是我們測試後發現i 的值竟然為4!為什麼會這樣呢?難道數組b 真的沒有傳遞到函數内部?是的,确實沒有傳遞過去,這是因為這樣一條規則:

c 語言中,當一維數組作為函數參數的時候,編譯器總是把它解析成一個指向其首元素首位址的指針。

這麼做是有原因的。在c 語言中,所有非數組形式的資料實參均以傳值形式(對實參做一份拷貝并傳遞給被調用的函數,函數不能修改作為實參的實際變量的值,而隻能修改傳遞給它的那份拷貝)調用。然而,如果要拷貝整個數組,無論在空間上還是在時間上,其開銷都是非常大的。更重要的是,在絕大部分情況下,你其實并不需要整個數組的拷貝,你隻想告訴函數在那一刻對哪個特定的數組感興趣。這樣的話,為了節省時間和空間,提高程式運作的效率,于是就有了上述的規則。同樣的,函數的傳回值也不能是一個數組,而隻能是指針。這裡要明确的一個概念就是:函數本身是沒有類型的,隻有函數的傳回值才有類型。很多書都把這點弄錯了,甚至出現“xxx 類型的函數”這種說法。簡直是荒唐至極!

經過上面的解釋,相信你已經了解上述的規定以及它的來由。上面編譯器給出的提示,說函數的參數是一個char*類型的指針,這點相信也可以了解。既然如此,我們完全可以把fun 函數改寫成下面的樣子:

void fun(char *p)

   char c = p[3];//或者是char c = *(p+3);

同樣,你還可以試試這樣子:

   char b[100] = “abcdefg”;

運作完全沒有問題。實際傳遞的數組大小與函數形參指定的數組大小沒有關系。既然如此,那我們也可以改寫成下面的樣子:

void fun(char a[ ])

改寫成這樣或許比較好,至少不會讓人誤會成隻能傳遞一個10 個元素的數組。

1、能否把指針變量本身傳遞給一個函數

我們把上一節讨論的列子再改寫一下:

   char *p2 = “abcdefg”;

   fun(p2);

這個函數調用,真的把p2 本身傳遞到了fun 函數内部嗎?

我們知道p2 是main 函數内的一個局部變量,它隻在main 函數内部有效。(

這裡需要澄清一個問題:main 函數内的變量不是全局變量,而是局部變量,隻不過它的生命周期和全局變量一樣長而已。全局變量一定是定義在函數外部的。初學者往往弄錯這點。)既然它是局部變量,fun 函數肯定無法使用p2 的真身。那函數調用怎麼辦?好辦:對實參做一份拷貝并傳遞給被調用的函數。即對p2 做一份拷貝,假設其拷貝名為_p2。那傳遞到函數内部的就是_p2 而并非p2 本身。

2、無法把指針變量本身傳遞給一個函數

這很像孫悟空拔下一根猴毛變成自己的樣子去忽悠小妖怪。是以fun 函數實際運作時,用到的都是_p2 這個變量而非p2 本身。如此,我們看下面的例子:

void getmemory(char * p, int num)

   p = (char *)malloc(num*sizeof(char));

   char *str = null;

   getmemory(str,10);

   strcpy(str,”hello”);

   free(str);//free 并沒有起作用,記憶體洩漏

在運作strcpy(str,”hello”)語句的時候發生錯誤。這時候觀察str 的值,發現仍然為null。也就是說str 本身并沒有改變,我們malloc 的記憶體的位址并沒有賦給str,而是賦給了_str。

而這個_str 是編譯器自動配置設定和回收的,我們根本就無法使用。是以想這樣擷取一塊記憶體是不行的。那怎麼辦? 兩個辦法:

第一:用return。

char * getmemory(char * p, int num)

   return p;

   str = getmemory(str,10);

   free(str);

這個方法簡單,容易了解。

第二:用二級指針。

void getmemory(char ** p, int num)

   *p = (char *)malloc(num*sizeof(char));

   getmemory(&str,10);

注意,這裡的參數是&str 而非str。這樣的話傳遞過去的是str 的位址,是一個值。在函數内部,用鑰匙(“*”)來開鎖:*(&str),其值就是str。是以malloc 配置設定的記憶體位址是真正指派給了str 本身。

另外關于malloc 和free 的具體用法,記憶體管理那章有詳細讨論。

前面詳細分析了二維數組與二維指針,那它們作為參數時與不作為參數時又有什麼差別呢?看例子:

   void fun(char a[3][4]);

我們按照上面的分析,完全可以把a[3][4]了解為一個一維數組a[3],其每個元素都是一個含有4 個char 類型資料的數組。上面的規則,“c 語言中,當一維數組作為函數參數的時候,編譯器總是把它解析成一個指向其首元素首位址的指針。”在這裡同樣适用,也就是說我們可以把這個函數聲明改寫為:

   void fun(char (*p)[4]);

這裡的括号絕對不能省略,這樣才能保證編譯器把p 解析為一個指向包含4 個char 類型資料元素的數組,即一維數組a[3]的元素。

同樣,作為參數時,一維數組“[]”号内的數字完全可以省略:

   void fun(char a[ ][4]);

不過第二維的維數卻不可省略,想想為什麼不可以省略?

注意:如果把上面提到的聲明void fun(char (*p)[4])中的括号去掉之後,聲明“void f un(char *p[4])”可以改寫成:

   void fun(char **p);

這是因為參數*p[4],對于p 來說,它是一個包含4 個指針的一維數組,同樣把這個一維數組也改寫為指針的形式,那就得到上面的寫法。

上面讨論了這麼多,那我們把二維數組參數和二維指針參數的等效關系整理一下:

C語言數組參數與指針參數

這裡需要注意的是:c 語言中,當一維數組作為函數參數的時候,編譯器總是把它解析成一個指向其首元素首位址的指針。這條規則并不是遞歸的,也就是說隻有一維數組才是如此,當數組超過一維時,将第一維改寫為指向數組首元素首位址的指針之後,後面的維再也不可改寫。比如:a[3][4][5]作為參數時可以被改寫為(*p)[4][5]。

至于超過二維的數組和超過二級的指針,由于本身很少使用,而且按照上面的分析方法也能很好的了解,這裡就不再詳細讨論。有興趣的可以好好研究研究。

繼續閱讀