C語言陣列引數與指標引數

2021-07-04 08:29:06 字數 4056 閱讀 2530

** : 

我們都知道引數分為形參和實參。形參是指宣告或定義函式時的引數,而實參是在呼叫函式時主調函式傳遞過來的實際值。

1、能否向函式傳遞乙個陣列?

看例子:

void fun(char a[10])

intmain()

先看上面的呼叫,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 型別,不匹配。雖然編譯器沒有給出錯誤,但是這樣執行肯定會有問題。如圖:

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

第一:b[10]並不存在,在編譯的時候由於沒有去實際位址取值,所以沒有出錯,但是在執行時,將計算b[10]的實際位址,並且取值。這時候發生越界錯誤。

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

第乙個錯誤很好理解,那麼第二個錯誤怎麼理解呢?fun 函式明明傳遞的是乙個陣列啊,編譯器怎麼會說是char *型別呢?別急,我們先把函式的呼叫方式改變一下:

fun(b);

b 是乙個陣列,現在將陣列b 作為實際引數傳遞。這下該沒有問題了吧?除錯、執行,一切正常,沒有問題,收工!很輕易是吧?但是你確認你真正明白了這是怎麼回事?陣列b真的傳遞到了函式內部?

2、無法向函式傳遞乙個陣列

我們完全可以驗證一下:

void fun(char a[10])

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

c 語言中,當一維陣列作為函式引數的時候,編譯器總是把它解析成乙個指向其首元素首位址的指標。

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

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

void fun(char *p)

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

void fun(char a[10])

intmain()

執行完全沒有問題。實際傳遞的陣列大小與函式形參指定的陣列大小沒有關係。既然如此,那我們也可以改寫成下面的樣子:

void fun(char a[ ])

改寫成這樣或許比較好,至少不會讓人誤會成只能傳遞乙個10 個元素的陣列。

1、能否把指標變數本身傳遞給乙個函式

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

void fun(char *p)

intmain()

這個函式呼叫,真的把p2 本身傳遞到了fun 函式內部嗎?

我們知道p2 是main 函式內的乙個區域性變數,它只在main 函式內部有效。(

這裡需要澄清乙個問題:main 函式內的變數不是全域性變數,而是區域性變數,只不過它的生命週期和全域性變數一樣長而已。全域性變數一定是定義在函式外部的。初學者往往弄錯這點。

)既然它是區域性變數,fun 函式肯定無法使用p2 的真身。那函式呼叫怎麼辦?好辦:對實參做乙份拷貝並傳遞給被呼叫的函式。即對p2 做乙份拷貝,假設其拷貝名為_p2。那傳遞到函式內部的就是_p2 而並非p2 本身。

2、無法把指標變數本身傳遞給乙個函式

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

void getmemory(char * p, int num)

intmain()

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

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

第一:用return。

char * getmemory(char * p, int num)

intmain()

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

第二:用二級指標。

void getmemory(char ** p, int num)

intmain()

注意,這裡的引數是&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 語言中,當一維陣列作為函式引數的時候,編譯器總是把它解析成乙個指向其首元素首位址的指標。這條規則並不是遞迴的,也就是說只有一維陣列才是如此,當陣列超過一維時,將第一維改寫為指向陣列首元素首位址的指標之後,後面的維再也不可改寫。比如:a[3][4][5]作為引數時可以被改寫為(*p)[4][5]。

至於超過二維的陣列和超過二級的指標,由於本身很少使用,而且按照上面的分析方法也能很好的理解,這裡就不再詳細討論。有興趣的可以好好研究研究。

C 引數陣列與陣列引數

1 陣列作為引數來傳遞時,當呼叫這個函式時裡面的引數需要new乙個陣列 2 引數陣列就是在函式的陣列引數前面加上params,當呼叫函式傳遞陣列時不需要再new乙個,只需要傳遞任意個跟陣列引數型別相同的數就可以 using system namespace 1 引數陣列 return sum sta...

C語言 陣列引數

c語言規定陣列變數arr,本身就是位址 指向該陣列第乙個元素的型別的指標 arr也是位址 指向整個陣列的型別的指標 所以陣列引數就是指標引數 pragma warning disable 4996 include 引用函式庫 include void test int a void main pri...

陣列引數和指標引數

我們都知道c語言中的陣列引數會退化為指標,那有沒有想過為什麼呢?由於c語言當初開發是為unix作業系統的,對於作業系統來說效率是非常重要的,當引數傳遞的時候如果拷貝整個陣列執行效率將大大下降而且引數位於棧上,太大的陣列拷貝將導致棧溢位,所以會將陣列名看做常量指標傳陣列首元素位址,其實二維陣列引數同樣...