4.6,數組參數與指針參數 二維數組參數與二維指針參數 一級指針參數

/*/=========其中註釋的是比較重要的或者是圖片表格=============自己見解、 求板磚
 
4.6,數組參數與指針參數 二維數組參數與二維指針參數 一級指針參數

/====================================================*/
4.6,數組參數與指針參數
我們都知道參數分爲形參和實參。形參是指聲明或定義函數時的參數,而實參是在調
用函數時主調函數傳遞過來的實際值。
4.6.1,一維數組參數
4.6.1.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 類型,不匹配。雖然編譯器沒有給出錯誤,但是這樣運行肯定會有問題。
如圖:
/*圖片內容出現了一個無法處理的異常 ;數據位置出了問題*/
這是一個內存異常,我們分析分析其原因。其實這裏至少有兩個嚴重的錯誤。
第一:b[10]並不存在,在編譯的時候由於沒有去實際地址取值,所以沒有出錯,但是
在運行時,將計算b[10]的實際地址,並且取值。這時候發生越界錯誤。
第二:編譯器的警告已經告訴我們編譯器需要的是一個char*類型的參數,而傳遞過去
的是一個char 類型的參數,這時候fun 函數會將傳入的char 類型的數據當地址處理,同樣
會發生錯誤。(這點前面已經詳細講解)
第一個錯誤很好理解,那麼第二個錯誤怎麼理解呢?fun 函數明明傳遞的是一個數組啊,
編譯器怎麼會說是char *類型呢?別急,我們先把函數的調用方式改變一下:
fun(b);
b 是一個數組,現在將數組b 作爲實際參數傳遞。這下該沒有問題了吧?調試、運行,
一切正常,沒有問題,收工!很輕易是吧?但是你確認你真正明白了這是怎麼回事?數組b
真的傳遞到了函數內部?
/*/=========其中註釋的是比較重要的或者是圖片表格=============
我理解的是它把首地址給了a所以再次來尋址a的時候就正常了、 求板磚
/====================================================*/
4.6.1.2,無法向函數傳遞一個數組
我們完全可以驗證一下:
void fun(char a[10])
{
int i = sizeof(a);
char c = a[3];
}
如果數組b 真正傳遞到函數內部,那i 的值應該爲10。但是我們測試後發現i 的值竟然
爲4!爲什麼會這樣呢?難道數組b 真的沒有傳遞到函數內部?是的,確實沒有傳遞過去,
這是因爲這樣一條規則:
C 語言中,當一維數組作爲函數參數的時候,編譯器總是把它解析成一個指向其首元
素首地址的指針。
這麼做是有原因的。在C 語言中,所有非數組形式的數據實參均以傳值形式(對實參
做一份拷貝並傳遞給被調用的函數,函數不能修改作爲實參的實際變量的值,而只能修改
傳遞給它的那份拷貝)調用。然而,如果要拷貝整個數組,無論在空間上還是在時間上,
其開銷都是非常大的。更重要的是,在絕大部分情況下,你其實並不需要整個數組的拷貝,
你只想告訴函數在那一刻對哪個特定的數組感興趣。這樣的話,爲了節省時間和空間,提
高程序運行的效率,於是就有了上述的規則。同樣的,函數的返回值也不能是一個數組,
而只能是指針。這裏要明確的一個概念就是:函數本身是沒有類型的,只有函數的返回值
纔有類型。很多書都把這點弄錯了,甚至出現“XXX 類型的函數”這種說法。簡直是荒唐
至極!
經過上面的解釋,相信你已經理解上述的規定以及它的來由。上面編譯器給出的提示,
說函數的參數是一個char*類型的指針,這點相信也可以理解。
既然如此,我們完全可以把fun 函數改寫成下面的樣子:
void fun(char *p)
{
char c = p[3];//或者是char c = *(p+3);
}
同樣,你還可以試試這樣子:
void fun(char a[10])
{
char c = a[3];
}
intmain()
{
char b[100] = “abcdefg”;
fun(b);
return 0;
}
運行完全沒有問題。實際傳遞的數組大小與函數形參指定的數組大小沒有關係。既然
如此,那我們也可以改寫成下面的樣子:
void fun(char a[ ])
{
char c = a[3];
}
改寫成這樣或許比較好,至少不會讓人誤會成只能傳遞一個10 個元素的數組。
4.6.2,一級指針參數
4.6.2.1,能否把指針變量本身傳遞給一個函數
我們把上一節討論的列子再改寫一下:
void fun(char *p)
{
char c = p[3];//或者是char c = *(p+3);
}
intmain()
{
char *p2 = “abcdefg”;
fun(p2);
return 0;
}
這個函數調用,真的把p2 本身傳遞到了fun 函數內部嗎?
我們知道p2 是main 函數內的一個局部變量,它只在main 函數內部有效。(這裏需要澄
清一個問題:main 函數內的變量不是全局變量,而是局部變量,只不過它的生命週期和
全局變量一樣長而已。全局變量一定是定義在函數外部的。初學者往往弄錯這點。)既然它
是局部變量,fun 函數肯定無法使用p2 的真身。那函數調用怎麼辦?好辦:對實參做一份
拷貝並傳遞給被調用的函數。即對p2 做一份拷貝,假設其拷貝名爲_p2。那傳遞到函數內
部的就是_p2 而並非p2 本身。
4.6.2.2,無法把指針變量本身傳遞給一個函數
這很像孫悟空拔下一根猴毛變成自己的樣子去忽悠小妖怪。所以fun 函數實際運行時,
用到的都是_p2 這個變量而非p2 本身。如此,我們看下面的例子:
void GetMemory(char * p, int num)
{
p = (char *)malloc(num*sizeof(char));
}
intmain()
{
char *str = NULL;
GetMemory(str,10);
strcpy(str,”hello”);
free(str);//free 並沒有起作用,內存泄漏
return 0;
}
在運行strcpy(str,”hello”)語句的時候發生錯誤。這時候觀察str 的值,發現仍然爲NULL。
也就是說str 本身並沒有改變,我們malloc 的內存的地址並沒有賦給str,而是賦給了_str。
而這個_str 是編譯器自動分配和回收的,我們根本就無法使用。所以想這樣獲取一塊內存是
不行的。那怎麼辦? 兩個辦法:
第一:用return。
char * GetMemory(char * p, int num)
{
p = (char *)malloc(num*sizeof(char));
return p;
}
intmain()
{
char *str = NULL;
str = GetMemory(str,10);
strcpy(str,”hello”);
free(str);
return 0;
}
這個方法簡單,容易理解。
第二:用二級指針。
void GetMemory(char ** p, int num)
{
*p = (char *)malloc(num*sizeof(char));
return p;
}
intmain()
{
char *str = NULL;
GetMemory(&str,10);
strcpy(str,”hello”);
free(str);
return 0;
}
注意,這裏的參數是&str 而非str。這樣的話傳遞過去的是str 的地址,是一個值。在函
數內部,用鑰匙(“*”)來開鎖:*(&str),其值就是str。所以malloc 分配的內存地址是真正
賦值給了str 本身。
另外關於malloc 和free 的具體用法,內存管理那章有詳細討論。

4.6.3,二維數組參數與二維指針參數
前面詳細分析了二維數組與二維指針,那它們作爲參數時與不作爲參數時又有什麼區
別呢?看例子:
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 個指針的一維數組,同樣把這個一維數
組也改寫爲指針的形式,那就得到上面的寫法。
上面討論了這麼多,那我們把二維數組參數和二維指針參數的等效關係整理一下:
/*/=====================================
數組參數					等效的指針參數
數組的數組:char a[3][4]		數組的指針:char (*p)[10]
指針數組: char *a[5]			 指針的指針:char **p
/=====================================*/
這裏需要注意的是:C 語言中,當一維數組作爲函數參數的時候,編譯器總是把它解析
成一個指向其首元素首地址的指針。這條規則並不是遞歸的,也就是說只有一維數組纔是
如此,當數組超過一維時,將第一維改寫爲指向數組首元素首地址的指針之後,後面的維
再也不可改寫。比如:a[3][4][5]作爲參數時可以被改寫爲(*p)[4][5]。
至於超過二維的數組和超過二級的指針,由於本身很少使用,而且按照上面的分析方法
也能很好的理解,這裏就不再詳細討論。有興趣的可以好好研究研究。
數組參數等效的指針參數
數組的數組:char a[3][4] 數組的指針:char (*p)[10]
指針數組: char *a[5] 指針的指針:char **p

 
 
4.6,數組參數與指針參數 二維數組參數與二維指針參數 一級指針參數

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章