因為一直對這幾種函數參數的傳遞方式理解的不是很透徹,花了一段時間仔細捋清了他們之間的區別。這個問題也是編程初級階段會經常遇到的問題,也是有可能在面試中遇到的基本問題,在此進行了簡單的總結一下,一是加深自己的理解,二是希望幫助遇到同樣問題的同學,希望能幫你們快速透徹的理解他們。
主要以實現交換兩個整形值(老生常談的話題了)為載體進行透徹的說明,我們的主要方法就是利用最基本的輸入輸出功能來看函數執行前和執行后,參數地址和值的變化來看函數“做了什么事情”。
針對每一個不同的函數,主要從展現了如下幾個方面:(1)函數開始執行前的參數的狀態,包括參數的地址和值(2)函數實現了什么樣的操作(3)函數執行后的參數的狀態。最后我們簡單的總結了這5個函數為什么有的能實現目的;而有的不能實現目的,進一步解釋了這些不能實現目的函數他們到底做了哪些事情(通過前后參數狀態的變化來反映)。
在main函數中,我們首先顯示出實參的地址,當將實參傳入到函數中,我們顯示被調函數中變量的地址。只要被調函數中變量地址和實參的地址一樣,我們認為是直接操作變量而不是操作變量的”副本“;如果被調函數中變量的地址與實參地址不同,則認為是對實參進行了一次拷貝,即新建了一個實參的”副本“,這個”副本“的值和實參值一樣,被調函數在后續的操作中都是對這個”副本“進行操作的,而副本的改變和原實參無任何關系。當被調函數結束后,該副本做為局部變量而結束生命周期。
首先看一下5個常見交換值的函數(有的能實現,有的不能實現交換目的)。依據上述原理,如果交換函數中是對真正實參(數據或指針)的操作,則認為能實現交換目的;而如果只是對實參副本進行交換操作,則認為不能達到交換目的。
本文未從理論上進行過多的論述,需要的同學可以自行搜索一下相關資料。
<span style="font-family:SimSun;">//交換兩個數字的值,由於函數中較多的使用了cout來顯示相關的值,所以使原本簡單的函數看起來很長</span><span style="font-family:SimHei;">。</span> void change0(int a1,int a2) { //這個函數不能實現目的 cout<<"change0 執行前:"<<endl; cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl; cout<<"a1="<<a1<<",a2="<<a2<<endl; int temp=a1; a1=a2; a2=temp; cout<<"change0 執行后:"<<endl; cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl; cout<<"a1="<<a1<<",a2="<<a2<<endl; }
void change1(int *a1,int *a2)
{
//能實現目的
cout<<"change1 執行前:"<<endl;
cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl;
cout<<"*a1="<<*a1<<",*a2="<<*a2<<endl;
cout<<"a1="<<a1<<",a2="<<a2<<endl;
int temp;
temp=*a1;
*a1=*a2;
*a2=temp;
cout<<"change1 執行后:"<<endl;
cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl;
cout<<"*a1="<<*a1<<",*a2="<<*a2<<endl;
cout<<"a1="<<a1<<",a2="<<a2<<endl;
}
void change2(int *a1,int *a2)
{
//這個函數不能實現目的
cout<<"change2 執行前:"<<endl;
cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl;
cout<<"*a1="<<*a1<<",*a2="<<*a2<<endl;
cout<<"a1="<<a1<<",a2="<<a2<<endl;
int *temp=a1;
a1=a2;
a2=temp;
cout<<"change2 執行后:"<<endl;
cout<<"&a1="<<&a1<<",&a2="<<&a2<<endl;
cout<<"*a1="<<*a1<<",*a2="<<*a2<<endl;
cout<<"a1="<<a1<<",a2="<<a2<<endl;
}
void change3(int &a,int &b)
{
cout<<"change3 執行前:"<<endl;
cout<<"a="<<a<<",b="<<b<<endl;
cout<<"&a="<<&a<<",&b="<<&b<<endl;
int temp=a;
a=b;
b=temp;
cout<<"change3 執行后:"<<endl;
cout<<"a="<<a<<",b="<<b<<endl;
cout<<"&a="<<&a<<",&b="<<&b<<endl;
}
void change4(int **a,int **b)
{
cout<<"change4 執行前:"<<endl;
cout<<"&a="<<&a<<",&b="<<&b<<endl;
cout<<"a="<<a<<",b="<<b<<endl;
cout<<"*a="<<*a<<",*b="<<*b<<endl;
cout<<"**a="<<**a<<",**b="<<**b<<endl;
int **temp = new int *;
*temp = *a;
*a = *b;
*b = *temp;
cout<<endl;
cout<<"change4 執行后:"<<endl;
cout<<"&a="<<&a<<",&b="<<&b<<endl;
cout<<"a="<<a<<",b="<<b<<endl;
cout<<"*a="<<*a<<",*b="<<*b<<endl;
cout<<"**a="<<**a<<",**b="<<**b<<endl;
}
change0不能實現目的。簡單的說就是值傳參只是對實參的副本進行了操作,並不能對實參本身進行操作。這點會通過下面的例子體現出來。
change1能實現目的,但要注意,這種改變是接改變m和n 的值,而pm和pn的指向未改變(與change4正好相反)。從圖示中能清楚的看出change1中實現的操作。圖中每個圓圈或方塊代表一個變量,上方為自己實際的內存地址,其中的內容為他的值。從圖中可以看出,change1執行前,生成了兩個實參的副本(左側黃色部分),注意副本的內存地址和實參的不一樣的(不然怎么叫重新生成的),他們的值為各自對應實參的值。change1實際上的操作為將變量m和n中的值進行互換,而兩個實參的副本還是指向原來的位置(即值不變)。chang1 執行后如圖底部所示,指針pm和pn指向的位置不變,但該位置中存儲的內容發生了變化。
change2不能實現交換的目的,從圖中我們可以看書,change2中完成的操作只是將兩個實參副本的值互換了,即只把這倆副本指向的位置改變了(當然,在change2執行結束后他們將消亡),而我們的目的是想改變pm和pn的指向,或互換m和n的值。change2不能實現目的的原因和change0的原因相同,都只是改變了副本的值,而真正的實參沒進行任何操作。所以仍各自保持原來的值不變。
change3能實現,他是通過引用的方式實現傳遞參數的。即直接對傳入參數進行操作,而不是對生成的副本進行操作。這樣在change3中對參數的任何操作都能直接對參數產生影響,如圖所示,當change3執行完畢后,變量m和n的值已經改變了。
change4是用雙重指針進行傳參,他是通過改變指針pm和pn的指向來實現的,而實際上m和n的值都未改變。但從圖中可以看出,change4執行后,ppm和ppn的指向位置未改變,而指向位置的內容卻發生了改變,即執行前后ppm始終指向pm,ppn始終指向pn,只不過是執行后pm和pn的指向發了改變。
總結:
利用判斷被調函數是對實參操作還是對實參副本操作來判斷一個交換函數是否真能實現交換操作;
利用顯示變量的內存地址來判斷是“實參”還是“實參副本”。