- C++語言程序設計
- 千鋒教育高教產品研發部編著
- 5039字
- 2019-07-30 17:37:46
2.8 函數
函數是對處理問題過程的一種抽象,通常在編程中將功能獨立且經常被使用的某種功能抽象為函數。在C++語言中,函數同樣重要,它是面向對象程序設計中對于某種功能的抽象,對于代碼重用和提高程序的可靠性是非常重要的。
2.8.1 函數的定義
函數可以理解為實現某種功能的代碼塊,這樣當程序中需要這個功能時就可以直接調用,而不必每次都編寫一次,就好比生活中使用計算器來計算,當需要計算時,直接使用計算器輸入要計算的數,計算完成后生成計算結果,而不必每次計算都通過手寫演算出結果。在程序中,如果想多次輸出“拼搏到無能為力,堅持到感動自己!”,就可以將這個功能寫成函數,具體示例如下:

void表示該函數沒有返回值,Output是為函數取的名字,Output后面有一對小括號,小括號中代表函數的參數,假如沒有參數,小括號內為空。函數的主體從左大括號開始,到右大括號結束,中間是函數的功能。
當需要使用該函數時,就可以在程序中寫入下面語句:
Output();
這就是調用函數,程序執行到這里,就會立即跳轉到Output()函數的定義部分去執行(定義部分就是實現函數功能的部分),當函數執行完畢后,再跳回到原始位置繼續往下執行。
從上述示例中,可以得出函數的定義,其語法格式如下:

C++函數的定義包括函數名、參數、返回類型和函數體,其中函數名、參數列表和返回值類型一起組成函數頭,它是函數的接口,即調用這個函數時需要知道這些信息,而函數體是函數真正的實現。
在C++中,如果函數的定義在調用之前,則可以直接調用,但如果函數的定義出現在調用之后,則要先進行函數聲明。為了提高程序的可讀性,一般程序需要函數的聲明。在C++中,函數聲明包括函數返回類型、函數名和完整的形式參數列表。
函數名是一個標識符,它的命名規則與變量相同。在給函數命名時,應盡量使名字能夠代表函數所完成的功能,這樣可以增強程序的可讀性。
函數參數是函數完成功能所需要輸入的信息,如定義一個求兩個整數和的函數,那么這兩個數就要作為參數。一個函數可以有零個或多個任意數據類型的參數,參數之間用“,”隔開,參數名也是標識符。例如,下面語句是求兩個整數和的函數定義,其中a與b就是兩個參數,具體示例如下:

在上述函數定義中,參數a、b的值是從調用函數的地方傳遞過來的,在定義函數時還沒有具體的值,因此稱為形式參數,簡稱形參。與形式參數對應的是實際參數,即在調用這個函數時要傳遞給形式參數的具體量,簡稱實參。例如,下面的程序調用了add函數,其中i與j就是實際參數,具體示例如下:

在調用函數時,實參將自己的值傳遞給形參。在上面的程序中,i將自己的值1傳遞給對應的形參a,j將自己的值2傳遞給對應的形參b,這樣a的值為1,b的值為2,如圖2.37所示。

圖2.37 實參與形參
函數的返回類型是函數在調用結束后返回值的數據類型,可以是除數組以外的任意類型。函數體中的return語句用來返回函數的結果,這個語句指示系統結束當前函數的執行,返回到調用這個函數的地方繼續執行。如果定義的函數返回類型是void,則表示函數沒有返回值,具體示例如下:
return;
此處也可以不寫return語句,函數在執行到函數語句末尾的“}”自動結束調用返回。
當函數有返回值時,可以用下面的任意一種格式。具體示例如下:
return 表達式; return (表達式);
上述語句中表達式的值就是函數需要返回的值。
函數體是由一些語句組成的,這些語句共同完成了函數的功能。函數體中的語句可以是任意形式的語句,包括常量和變量的定義語句、表達式語句和流程控制語句等。如果變量的定義在函數體內,則這個變量稱為局部變量,只能在這個函數體中使用;如果變量的定義在函數體外,則這個變量稱為全局變量,可以在所有函數中使用。
接下來通過一個案例來演示函數聲明、實現及調用,如例2-20所示。
例2-20

運行結果如圖2.38所示。

圖2.38 例2-20運行結果
在例2-20中,主函數中定義了兩個整型變量,并從鍵盤讀入這兩個變量的值,然后調用函數add求這兩個數的和并輸出,注意區分實參a、b與形參a、b。
上例中函數的調用過程可以分為以下4步:
- 當函數調用開始時,建立調用函數的棧空間,保存調用函數的運行狀態和返回地址,先將函數進棧,并為函數的形式參數按其數據類型分配動態內存。
- 將實參的值對應傳遞給形參。
- 執行函數體。
- 當執行到return語句或函數結束的“}”時,系統為返回值按返回值類型分配臨時單元,并將返回值放入該單元,函數出棧,清理函數所占內存,返回值的臨時單元參與主調函數中的所在表達式運算后銷毀,繼續主調函數的執行。
2.8.2 函數的參數傳遞
1. 普通型形式參數
函數形參與實參均為普通變量,函數調用時將實參的值復制一份給形參,在被調函數中,對形參的任何操作都不會影響實參的值。接下來演示普通型形式參數作為函數參數,如例2-21所示。
例2-21

運行結果如圖2.39所示。

圖2.39 例2-21運行結果
在例2-21中,從運行結果可發現,a、b的值并沒有交換。這是因為,調用函數swap時,實參a、b的值會復制一份給形參a、b,在執行swap函數時,交換的是形參a、b的值,swap函數執行結束,形參a、b釋放內存空間,這期間并沒有改變實參中a、b的值,因此,打印結果中a、b值并不發生變化。
2. 指針型形式參數
當函數的形參是指針時,指針的值是一個地址,因而可以通過指針來間接訪問該地址對應的內存空間。這種指針型形式參數提供了一種可以間接修改調用該函數的參數值的方法,但這種方法因其容易出錯,所以很少使用,讀者通過下面例題有所了解即可,如例2-22所示。
例2-22

運行結果如圖2.40所示。

圖2.40 例2-22運行結果
在例2-22中,從運行結果可發現,a、b的值交換了。這是因為,調用函數swap時,實參a、b的地址會復制一份給形參指針變量a、b,在執行swap函數時,通過?訪問指針變量a、b指向的內存空間,即實參a、b的值,這樣就實現了交換a、b的值。
3. 數組型形式參數
在C++中,當形參被定義為數組時,數組參數自動轉換為指針參數,因此調用函數時實際上是將實參(也是一個數組)的首地址傳遞給形參(一個指針變量),如例2-23所示。
例2-23

運行結果如圖2.41所示。

圖2.41 例2-23運行結果
在例2-23中,第3~17行代碼為冒泡排序,具體過程如圖2.42所示。數組參數實際上是一個指針,這個指針指向實參數組的首元素,因此可以通過指針來間接修改實參數組元素的值,也就是說,在函數中對數組參數所做的改變會影響到實參。

圖2.42 冒泡排序過程
2.8.3 函數與引用
當指針作為函數參數時,形參的改變可以影響到實參,但在函數中反復使用指針,容易發生錯誤且難以理解。如果以引用作為函數形參,則既可以實現指針所帶來的功能,而且更加高效。使用引用作函數形參時只需在函數定義時將形參前加上引用運算符“&”即可,如例2-24所示。
例2-24

運行結果如圖2.43所示。

圖2.43 例2-24運行結果
在例2-24中,swap()函數中的&a和&b就是引用作為函數形參,在執行swap(a,b)時,雖然看起來像是簡單的變量傳遞,但實際上由于形參被聲明成是實參的內存空間的引用,函數中對形參a、b的操作就是對所引用的實參a、b的內存空間的操作。
一個函數也可以返回為引用類型。返回引用的函數可以使函數出現在等號的左邊,但是要求函數必須返回全局變量,如例2-25所示。
例2-25

運行結果如圖2.44所示。

圖2.44 例2-25運行結果
在例2-25中,函數CalArea用來計算一個圓的面積,形參r用來指定圓的半徑。由于函數名前有運算符“&”,表示函數返回一個引用,因此該函數中return后面必須是一個已分配的內存空間的標識,不能是表達式。由于函數調用后,函數中的局部變量的內存空間被釋放,因而函數不能返回一個局部變量的內存空間的引用。第12行變量a2引用函數CalArea返回的內存空間的值,因此area的值改變后,a2的值也會隨之改變。第14行函數作為左值并進行賦值,此時area、a2的值也發生變化。
2.8.4 函數與const
const是不變的意思,這個關鍵字經常出現在函數的定義中,根據其出現在函數不同的位置,大致可以分為3類:修飾函數參數、修飾函數返回值和修飾類的成員函數。本節先講解前兩類,后一類在以后的章節中再講解。
const修飾函數參數表示函數體中不能修改參數的值(參數本身的值或參數其中包含的值),具體示例如下:

函數返回值為const的情形只用在函數返回為引用的時候。當把返回為引用的函數再用const限定后,就表示這個函數不能作為左值使用,即不能被賦值。如例2-25中的CalArea()函數,如果定義成如下形式:
const double&CalArea(double r);
此時,執行下面的語句,會發生錯誤。
CalArea(5.0)=6.0f;
2.8.5 內聯函數
在編寫程序時,經常會遇到短小且使用頻繁的代碼,這時把這些代碼寫成函數,由于函數調用時,額外開銷非常大,會降低程序的運行效率;但不寫成函數,每次重復寫相同的代碼,程序的可讀性降低。這種情況在C語言中通過宏函數來解決,由于宏只是簡單的替換,不會進行類型檢查等工作,很可能帶來一些潛在的錯誤。在C++中通過內聯函數可以解決這個問題,內聯函數在實現過程上與宏函數相似,在編譯時用函數體代替函數調用,節省執行時間。定義內聯函數的方法很簡單,即在函數頭前面加上關鍵字inline,其語法格式如下:

例如,定義一個求兩個整數和的函數為內聯函數,具體示例如下:

其中,add函數是內聯函數。在程序中出現的該函數的調用函數將用該函數的函數體代替,而不是轉去調用該函數,因此內聯函數可以提高運行效率。
內聯函數的定義是有限制的,并不是所有的函數都可以定義成內聯的,C++對內聯函數的限制如下:
- 在內聯函數中不能定義任何靜態變量。
- 內聯函數中不能有復雜的流程控制語句,如循環語句、switch語句、goto語句等。
- 內聯函數不能遞歸。
- 內聯函數中不能聲明數組。
如果定義的內聯函數比較復雜,違反了上述要求,那么即使使用inline限定,系統也將自動忽略inline關鍵字,把它當作普通函數處理。
2.8.6 默認參數的函數
C++是對C語言的改進,一方面是使得編譯器能檢查出更多的錯誤,另一方面減少編碼的復雜程度。基于這兩個方面,C++的函數中引入了默認參數的函數概念,即在定義或聲明函數時給形參一個默認值,在調用函數時,如果不傳遞實參就使用默認參數值,如例2-26所示。
例2-26

運行結果如圖2.45所示。

圖2.45 例2-26運行結果
在例2-26中,第7行調用函數時,沒有傳遞實參,形參a、b就使用默認值,最終函數返回3。第8行調用函數時,傳遞3給形參a,形參b使用默認值2,最終函數返回5。第9行調用函數時,傳遞5給形參a,傳遞6給形參b,此時沒有使用默認值,最終函數返回11。
在使用默認參數時,需要注意以下幾點。
- 默認值的指定只可在函數聲明中出現一次。如果函數沒有聲明,則只能在函數定義中指定。
- 指定默認參數的順序是自右向左。如果一個參數指定了默認值,則其右邊的參數一定也要指定默認值。
- 默認參數函數調用時,實參列表遵循從左向右依次匹配的原則。
- 默認值不可以是局部變量。
2.8.7 函數重載
在實際開發中,有時候需要實現幾個功能類似的函數,只是有些細節不同。例如,求兩個數的和,這兩個數可以是int、double等類型,在C語言中,由于每個函數必須有唯一的函數名,因此需要有如下兩個函數:
int addInt(int a,int b); double addDouble(double a,double b);
這兩個函數功能是相同的,都是求兩個數的和。由于不同的函數名,給使用者帶來諸多不便。因此考慮是否可以用同一個名字代替這兩個函數名,在調用時根據參數的不同確定調用哪個函數,這便是C++提供的函數重載機制,上述兩個函數可以使用同一個名字add,具體示例如下:
int add(int a,int b); double add(double a,double b);
上述兩個函數就構成了函數重載,每個函數對應著不同的實現,即各自有自己的函數體。讀者可能會疑惑在調用函數時編譯器如何選擇這些函數,具體原則如下:
(1)編譯器根據重載函數的形式參數類型或參數個數的不同進行選擇,因此構成重載函數必須在形式參數類型和參數個數上至少有一處不相同。例如,有3個同名函數,具體示例如下:
double fun(int a,double b); double fun(double a,int b); int fun(double a,int b);
上述代碼中,第一個函數與第二個函數構成函數重載,因為函數的參數類型不相同。而第二個函數與第三個函數僅僅是函數的返回類型不同,因此不能構成函數重載。
(2)編譯器選擇重載函數是按一定的順序將實參類型與所有被調用的重載函數的形參類型一一比較進行匹配,具體按如下順序匹配:首先選擇嚴格匹配的函數,再選擇通過自動類型轉換匹配的函數,最后選擇通過強制類型轉換匹配的函數。
接下來演示函數重載的用法,如例2-27所示。
例2-27

運行結果如圖2.46所示。

圖2.46 例2-27運行結果
在例2-27中,主函數中4次調用add函數,當傳入不同的參數時調用對應的函數,在這個過程中,編譯器會根據傳入的參數與重載函數按照上面的順序進行匹配,然后根據匹配結果調用不同的函數。
在使用具有默認參數的函數重載時需要注意調用時可能會發生歧義,具體示例如下:
void func(int a); void func(int a,double b=0);
當函數調用語句為“func(5);”時,它既可以調用第一個函數,也可以調用第二個函數,編譯器無法確定調用哪一個函數,即發生了歧義,因此對函數進行重載時應避免設置默認參數。
- 解構產品經理:互聯網產品策劃入門寶典
- LabVIEW Graphical Programming Cookbook
- Java高并發核心編程(卷2):多線程、鎖、JMM、JUC、高并發設計模式
- 深入理解Django:框架內幕與實現原理
- 深入淺出Java虛擬機:JVM原理與實戰
- Oracle Database In-Memory(架構與實踐)
- DevOps入門與實踐
- 深度學習:算法入門與Keras編程實踐
- C++新經典
- Protocol-Oriented Programming with Swift
- Learning PHP 7
- 匯編語言編程基礎:基于LoongArch
- Monitoring Docker
- Python趣味創意編程
- 軟技能2:軟件開發者職業生涯指南