一.函數的參數傳遞
函數參數的傳遞有兩種方式,值調用和引用調用。
1.值調用
值調用就是調用函數時,給形參分配內存空間,將實參拷貝給形參,之後的函數執行中形參和實參就脫離了關係,誰都不影響誰。也就是值調用時,只是用實參的值初始化下形參,之後兩者互不影響。
例子:
#include<iostream>
using namespace std;
void Swap(int a, int b);
int main()
{
int x=5, y=10;
cout<<"x="<<x<<" y="<<y<<endl;
Swap(x,y);
cout<<"x="<<x<<" y="<<y<<endl;
return 0;
}
void Swap(int a, int b)
{
int t;
t=a;
a=b;
b=t;
}
運行結果是:
x=5 y=10
x=5 y=10
從運行結果中,可以看出,本來我們想調用Swap函數把x和y的值交換過來,但是事與願違。這就是因爲,這是值調用的方式,x和y分別傳值給a和b後,a跟b的值在Swap內怎樣變化都影響不到x和y,所以兩次輸出x和y的值沒有變化。
2.引用調用
如果我們想讓被調函數中對形參做的修改同樣對主調函數中的實參有效,用值調用不行,那怎麼辦呢?答案是用引用調用。
引用是一種特殊類型的變量,我們可以認爲它是另一個變量的別名,利用引用名和原來的變量名訪問變量的效果是一樣的。引用的形式是:類型標識符 &引用名。比如:
int i, j;
int &ri=i; // 建立一個int類型的引用ri,並將其初始化爲變量i的一個別名
j=10;
ri=j; // 相當於 i=j;
聲明一個引用時,必須同時對它進行初始化,使它指向一個已存在的對象;一旦一個引用被初始化後,就不能改爲指向其它對象。簡單說就是引用定義的時候就指定它指向的變量,之後就不能變了。
引用可以作爲形參,比如void swap(int& a, int& b) {...}。這個時候引用就不需要對其初始化了,因爲形參只是類型說明,主調函數調用這個函數時纔會爲形參分配內存,也纔會用實參來初始化形參。用引用調用後,形參就是實參的別名而已,對形參做的任何改變都會影響實參發生同樣的改變。引用調用說明如下:
#include<iostream>
using namespace std;
void Swap(int& a, int& b);
int main()
{
int x=5, y=10;
cout<<"x="<<x<<" y="<<y<<endl;
Swap(x,y);
cout<<"x="<<x<<" y="<<y<<endl;
return 0;
}
void Swap(int& a, int& b)
{
int t;
t=a;
a=b;
b=t;
}
運行結果就跟我們想的一樣了:
x=5 y=10
x=10 y=5
引用調用後x和y的值成功的交換了。值調用和引用調用的區別只是函數的形參寫法不同,主調函數調用被調函數時的調用語句是一樣的。
二.帶默認形參值的函數
函數在聲明時可以預先定義默認的形參值。調用時若給出實參則用實參初始化形參,如果沒有給出實參則採用預先定義的默認形參值。例如:
int add(int x=5,int y=6) // 定義默認形參值
{
return x+y;
}
int main()
{
add(10,20); // 用實參來初始化形參,實現10+20
add(10); // 形參x採用實參值10,y採用默認值6,實現10+6
add(); // x和y都採用默認值,分別爲5和6,實現5+6
return 0;
}
默認參數值必須按照從右向左的順序定義。在有默認值的形參右面,不能出現無默認值的形參。也就是說應該把有默認值的形參都一塊放到右邊,不能讓有默認值的跟沒默認值的形參穿插着放。因爲在調用時,實參初始化形參是按從左向右的順序。比如:
int add(int x,int y=5,int z=6); //正確
int add(int x=1,int y=5,int z); //錯誤
int add(int x=1,int y,int z=6); //錯誤
調用出現在函數體實現之前時,默認形參值必須在函數原型中也就是聲明時給出;而當調用出現在函數體實現之後時,默認形參值需在函數實現時給出。例如:
調用在實現前時:
int add(int x=5,int y=6);
int main()
{
add(); //調用在實現前
return 0;
}
int add(int x,int y)
{
return x+y;
}
調用在實現後時:
int add(int x=5,int y=6)
{
return x+y;
}
int main()
{
add(); //調用在實現後
return 0;
}
在相同的作用域內,默認形參值的說明應保持惟一,但如果在不同的作用域內,允許說明不同的默認形參。這裏的作用域是指直接包含函數原型說明的大括號所界定的範圍。例:
int add(int x=1,int y=2);
int main()
{
int add(int x=3,int y=4);
add();//使用局部默認形參值(實現3+4)
return 0;
}
void fun()
{ ...
add();//使用全局默認形參值(實現1+2)
}
三.內聯函數
函數雖然有很多優點,比如代碼複用,便於維護等。但是調用函數時需要保存現場和返回地址,被調函數執行完後還要取出這些值繼續執行,這些過程在時間和空間方面都有開銷。對於一些規模小、功能簡單的函數可以定義成內聯函數。內聯函數在調用時不需要那些轉移帶來的開銷,它是在編譯的時候把函數體代碼嵌入到所有調用它的語句處,我們可以認爲直接把函數體的代碼放那裏了,當然也不完全一樣,畢竟它可能有參數。
內聯函數定義時使用關鍵字inline,語法形式如下:
inline 類型標識符 被調函數名(含類型說明的形參表)
使用內聯函數時必須注意:內聯函數體內不能有循環語句和switch語句;內聯函數的定義必須出現在內聯函數第一次被調用之前;對內聯函數不能進行異常接口聲明,就是不能聲明可能拋出的異常。
內聯函數應該是語句比較少、結構比較簡單的函數,不應該是複雜的函數,因爲它對編譯器來說就是代碼,如果很複雜會造成代碼膨脹,反而增大開銷,這種情況下其實多數編譯器就都會自動把它作爲普通函數來處理了。一般來說,包含循環語句的函數一定不能定義成內聯函數。舉一個內聯函數的例子:
#include<iostream>
using namespace std;
inline double CalArea(double radius)
{
return 3.14*radius*radius;
}
int main()
{
double r(3.0);
double area;
area=CalArea(r);
cout<<area<<endl;
return 0;
}
運行結果是:28.26