田媛
摘要:當(dāng)函數(shù)參數(shù)為地址類型時,可以有多種定義形式,因而讀者易產(chǎn)生迷惑,通過對各種實例的分析和證明,介紹了按各種形式定義的參數(shù)的本質(zhì);同時研究了當(dāng)?shù)刂奉愋蛯崊⒑托螀㈩愋筒煌瑫r的自動轉(zhuǎn)換。
關(guān)鍵詞:地址;形式參數(shù);實際參數(shù);類型轉(zhuǎn)換
中圖分類號:TP311 文獻標識碼:A 文章編號:1009-3044(2017)29-0118-02
當(dāng)函數(shù)形式參數(shù)(簡稱形參)類型為普通類型的數(shù)值類型時,參數(shù)的定義形式以及不同當(dāng)形參和實參類型不符時發(fā)生的自動轉(zhuǎn)換,各類教科書已進行了詳盡講解。而當(dāng)函數(shù)形參類型為地址類型時,它的定義形式多種多樣,而且當(dāng)?shù)刂奉愋蛯崊⒑托螀l(fā)生類型不匹配時的數(shù)據(jù)轉(zhuǎn)換,還很少有資料專門研究總結(jié)。
本文針對這些問題,通過舉合適的例做了證明和研究,對各種地址類型的形參的形式及本質(zhì)做了總結(jié),同時研究了地址類型數(shù)據(jù)的自動轉(zhuǎn)換。
1 函數(shù)形參為一級指針
當(dāng)函數(shù)形參類型為一級指針(即普通變量的地址)時,它的形式通常有一級指針和普通一維數(shù)組兩種形式。
void fun1(char *a) //此處等價于(char a[ ]),數(shù)組的維數(shù)無需指出
{ int i;
for(i=0;i<=5;i++)
{ putchar(*(a+i)); //此處等價于putchar(a[i]);
}
}
main( )
{ char b[]="a1b2c3";
char *p;
p=b; // 等價于 p=&b[0];
fun1(b); // 等價于fun1(p);
}
此處形參的兩種定義形式char *a與 char a[ ]是等價的,形參a的本質(zhì)就是一個一級指針(指向普通變量的指針)。當(dāng)我們需要對數(shù)組變量操作時,形參通常寫成數(shù)組的形式,更加便于讀者的理解,而當(dāng)對普通變量操作時,形參通常寫成一級指針形式。程序執(zhí)行結(jié)果如圖1。
2 函數(shù)形參為數(shù)組指針
當(dāng)函數(shù)形參類型為數(shù)組指針(指向一維數(shù)組的指針)時,其形式通常為數(shù)組指針(很多資料中也稱為行指針)和二維數(shù)組。
void fun1(char (*a)[4]) //此處等價于(char a[ ][4]),數(shù)組的第一維無需指出
//若參數(shù)說明為char a[ ][ ],則編譯就會發(fā)生錯誤,二維數(shù)組必須指定列維數(shù)
{ int i;
for(i=0;i<3;i++)
puts(*(a+i)); //此處等價于 puts(a[i])
}
main()
{ char b[3][4]={"abc","xyz","aaa"};
char (*p)[4];
p=b; // 此處等價于 p=&b[0];
fun1(p); //fun1(b);運行結(jié)果都一樣
}
程序運行結(jié)果為:
了解了數(shù)組指針形式的本質(zhì)后,我們可以把數(shù)組指針擴展到多維數(shù)組,比如對于三維數(shù)組形式,void fun( char p[ ][4][3])和void fun( char (*p)[4][3])是等價的。
3 函數(shù)形參為二級指針
當(dāng)函數(shù)形參類型為二級指針(指向一級指針的指針)時,它的形式通常有二級指針形式和指針數(shù)組形式。
void fun(char **a) //此處等價于(char *a[ ])
{ int i;
for(i=0;i<3;i++)
puts(*(a+i)); //或puts(a[i])
}
main( )
{ char *p1[3],**p2;
char b[3][7]={"abccde","deawff","gaaahi"};
p1[0]=b[0];
p1[1]=b[1];
p1[2]=b[2];
p2=&p1[0]; /p1[0]存放的是地址 ,&p1[0]則是p1[0]的地址,也即指針的指針
fun(p2);//等價于fun(p1)
}
程序運行結(jié)果為:
對于此處指針形式的研究,可以參照在1中分析的 char *a與char a[ ]作為參數(shù)形式時本質(zhì)是一致的,那么char **a與char *a[ ]也是一致的,我們已經(jīng)做實驗驗證。
在實際編程中,讀者可依據(jù)自己的喜好選擇參數(shù)的定義形式,通常情況下,指針數(shù)組的形式更加易于理解,但當(dāng)我們探究清楚參數(shù)類型的本質(zhì)后,就可以靈活自如使用。
在1、2和3中我們總結(jié)了有關(guān)形式參數(shù)類型為地址類型的各種定義形式,并且在實際調(diào)用中,實參的類型和形參都一致,那么當(dāng)函數(shù)調(diào)用時,實參并不是和形參同樣類型的地址數(shù)值時,又會發(fā)生什么樣的情況?
4 指針類型數(shù)據(jù)的轉(zhuǎn)換
有關(guān)數(shù)據(jù)類型的自動轉(zhuǎn)換,對于非地址類型的數(shù)據(jù)已有很多例題和資料講述,在指針中只強調(diào)了強制類型轉(zhuǎn)換,而地址類型的自動轉(zhuǎn)換卻很少提及。
經(jīng)常在各類計算機考試中或教科書中有這樣的題目:
Char str[10];下列調(diào)用正確的是:
A)gets(str) B)gets(&str) C)gets(str[0])
給出的正確答案往往是A,其實B也是正確的。
在函數(shù)調(diào)用及賦值中,地址類型的數(shù)據(jù)間也會發(fā)生自動轉(zhuǎn)換。endprint
系統(tǒng)函數(shù)puts的函數(shù)原型是int puts(const char *s),我們利用1中分析,此處等價于int puts(const char s[ ]),顯然,參數(shù)s要求傳遞的是一個列地址,而在4中所舉的例子,實參str、str+1、str+2是行地址,但我們可以看到程序運行的結(jié)果依然正確。這里就是因為實參和形參不一致時,當(dāng)它們的類型是地址類型也會發(fā)生自動轉(zhuǎn)換。
main()
{ int (*ptr1)[2]; //此處ptr1是一個數(shù)組指針,指向長度為2的數(shù)組
int a[6]={0,1,2,3,4,5};
int *ptr=&a;
printf("(1)%d,%d \n",*(ptr),*(ptr+2));
/*此處的ptr指針應(yīng)該指向一個普通的int變量,所以應(yīng)該存放a數(shù)組成員的地址,而此處&a并不代表a[0]的地址,它是一個行地址,在編譯時,系統(tǒng)會有警告出現(xiàn)。但系統(tǒng)會進行數(shù)據(jù)的自動轉(zhuǎn)換,將這個行地址轉(zhuǎn)換成一個列地址,因而 printf("%d,%d",*(ptr),*(ptr+2))會輸出a[0]和a[2]的值。*/
ptr1=a+1;
/*常規(guī)的賦值,ptr1應(yīng)該存放一個行地址,而此處a+1顯然是一個列地址,是成員a[1]的地址。但此處系統(tǒng)會進行自動轉(zhuǎn)換,將列地址轉(zhuǎn)換為了行地址。*/
printf("(2)%d,%d \n",(*ptr)[0],(*ptr)[1]); //輸出a[1],a[2]的值
printf("(3)%d,%d \n ",(*(ptr+1))[0], (*(ptr+1))[1]);//輸出a[3],a[4]的值
}
地址類型的自動數(shù)據(jù)轉(zhuǎn)換,通常發(fā)生在行地址和列地址之間。弄懂了這種賦值的自動轉(zhuǎn)換,那么在函數(shù)調(diào)用中出現(xiàn)了類型不一致時也是如此。如下例:
main()
{ char str[3][4]={"abc","123","x8y"};
puts(str+1); // 系統(tǒng)自動轉(zhuǎn)化為puts(str[1]),輸出串123
}
5 結(jié)束語
本文通過實例說明如下兩點:
1) 總結(jié)了函數(shù)形參類型為地址類型數(shù)據(jù)時的各種不同定義形式及本質(zhì)。
2) 當(dāng)?shù)刂奉愋蛿?shù)據(jù)之間進行賦值和參數(shù)傳遞時,存在數(shù)據(jù)的自動轉(zhuǎn)換。
參考文獻:
[1] 丁留海. C語言指針的底層原理[J]. 電子技術(shù)與軟件工程, 2016(21):257-258.
[2] 譚浩強. C語言程序設(shè)計[M]. 3版.北京: 清華大學(xué)出版社, 2005.
[3] 蔣清明. C語言程序設(shè)計[M]. 北京: 人民郵電出版社, 2005.endprint