[C++ Primer Note5] 函式
- 大多數型別都能用作函式的返回型別,一種特殊的返回型別是void ,它表示函式不返回任何值。函式的返回型別不能是陣列型別或函式型別 ,但可以是指向陣列或函式的指標。
- 在C++中,名字有作用域 ,物件有生命週期 :
- 名字的作用域是程式文字的一部分,名字在其中可見
- 物件的生命週期是程式執行過程中該物件存在的一段時間
- 對於普通區域性變數對應的物件來說,當函式的控制路徑經過變數定義語句時建立該物件,當到達定義所在的塊末尾時銷燬它。我們把只存在於塊執行期間的物件稱為自動物件 (automatic object)。當塊的執行結束後,塊中建立的自動物件的值就變成未定義的了。
-
某些時候,有必要令區域性變數的生命週期貫穿函式呼叫及之後的時間。可將區域性變數定義成static
型別從而獲得這樣的物件。區域性靜態物件(local static object)
在程式執行流第一次經過物件定義語句時初始化,並且知道程式終止時才被銷燬,即使物件所在的函式結束執行也不會對它有影響。
5.和其他名字一樣,函式的名字也必須在使用之前宣告。類似於變數,函式只能定義一次,但可以宣告多次。函式的宣告和函式的定義非常類似,唯一的區別是函式宣告無須函式體,用一個分號替代即可。函式的三要素(返回型別,函式名,形參型別)描述了函式的介面,說明了呼叫該函式所需的全部資訊,函式宣告也稱作函式原型(function prototype) - 函式應該在標頭檔案中宣告而在原始檔中定義,同時定義函式的原始檔應該把含有函式宣告的標頭檔案包含進來,編譯器負責驗證函式的定義和宣告是否匹配。
- 當形參是引用型別時,我們說它對應的實參被引用傳遞(passed by reference) 。當實參的值被拷貝給形參時,形參和實參是兩個相互獨立 的物件。我們說這樣的實參被值傳遞(passed by value)
- 指標的行為和其他非引用型別一樣,當執行指標拷貝操作時,拷貝的是指標的值,兩個指標是不同的指標。因為指標可以使我們間接地訪問它所指的物件,所以通過指標可以修改它所指物件的值,但本質上還是屬於值傳遞 。
- 在C++中,建議使用引用型別的形參替代指標來訪問外部物件。
- 拷貝大的類型別物件或者容器物件比較低效,甚至有的類型別(包括IO型別在內)根本不支援拷貝操作。這種情況下函式只能通過引用形參 訪問該型別的物件。
- 如果函式無需改變引用形參的值,最好將其宣告為常量引用 。
- 一個函式只能返回一個值,然而有時函式需要同時返回多個值,引用形參為我們一次返回多個結果 提供了有效的途徑。我們可以利用引用形參來儲存需要返回的多個值。
- 當用實參初始化形參時會忽略掉形參的頂層const,傳給它常量物件或者非常量物件都是可以的。
- 陣列的兩個特殊性質對我們定義和使用作用在陣列上的函式有影響,這兩個性質分別是,不允許拷貝陣列 以及使用陣列時通常會將其轉換成指標 。所以當我們為函式傳遞一個數組時,實際上傳遞的是指向陣列首元素的指標。儘管不能以值傳遞的方式傳遞陣列,但是我們可以形參寫成類似陣列的形式:
- void print(const int*)
- void print(const int[])
-
void print(const int[10]) 這裡的維度只是我們期望的,實際上不一定
儘管表現形式不同,但上面的三個函式是等價的 :每個函式的唯一實參都是const int*型別的。
- 因為陣列是以指標的形式傳遞給函式的,所以一開始函式並不知道陣列的確切尺寸,呼叫者應該為此提供一些額外的資訊。管理指標形參有三種常用的技術:
- 使用結束標記:比如C風格字串
- 使用標準庫規範:傳遞指向陣列首元素和尾後元素的指標
- 傳遞一個表示陣列大小的形參
- 當函式不需要對陣列元素執行寫操作時,陣列形參應該是指向const的指標
- C++允許將變數定義成陣列的引用,基於同樣的道理,形參也可以是陣列的引用。此時,引用形參繫結到陣列上 。但此時函式只能作用於固定大小 的陣列。
//形參是陣列的引用,維度是型別的一部分 void print(int (&arr)[10]){ for (auto elem : arr) cout<<elem<<endl; }
- 當將多維陣列傳遞給函式時,真正傳遞的是指向陣列首元素的指標,所以首元素本身就是一個陣列 。指標就是一個指向陣列的指標,陣列第二維(以及後面所有維度)的大小都是陣列型別的一部分,不能省略 。
void print(int (*matrix)[10]),int roSize);
上述宣告將matrix宣告成指向含有10個整數的陣列的指標。
- 命令列選項通過兩個(可選的)形參傳遞給main函式:
int main(int argc,char *argv[]){...}
- 為了編寫能處理不同數量實參的函式,C++11標準提供了兩種主要方法:如果所有實參型別相同,可以傳遞一個initializer_list 的標準庫型別;如果型別不同,我們可以編寫一種特殊的函式,也就是所謂的可變引數模板 。除此以外,還有一種省略符形參 ,不過一般用於與C函式互動的介面程式。
- 返回void的函式不要求非得有return語句,因為這類函式最後一句後面會隱含執行return
- 在含有return語句的迴圈後面應該也有一條return語句,因為迴圈中的return不一定會被執行到
- 函式返回的值用於初始化呼叫點的一個臨時量,該臨時量就是函式呼叫的結果。如果返回引用型別,則不需要拷貝。
- 必須要注意的是,千萬不要返回區域性物件的引用或指標 ,因為函式完成後,它所佔用的儲存空間也隨之被釋放掉,所以要想確保返回值安全,我們不妨提問:引用所引的是在函式之前已經存在的哪個物件?
- C++11標準規定函式可以返回花括號包圍的值的列表(這與之前所述的列表初始化語法實際上相照應),比如用於返回一個vector<int> 型別。如果返回內建型別則花括號只能包含一個值。
- 之前介紹過,如果函式的返回值不是void,那麼它必須返回一個值。但這條規則有一個例外:我們允許main沒有return語句結束。如果控制流到達了main函式的結尾處而沒有return語句,編譯器將隱式地插入一條返回0的return語句。
- 因為陣列不能被拷貝,所以函式不能返回陣列,但是可以返回陣列的指標和引用。使用類型別名可以簡化宣告過程。
typedef int arrT[10];//arrT表示含有10個整數的陣列 arrT* func(int i);//func返回一個指向含有10個整數的陣列的指標
如果不使用類型別名,返回陣列指標 的函式形式如下:
int (*func(int i)) [10];
這裡的*表示函式func的返回結果可以執行解引用 操作。
- C++11標準中有一種可以簡化上述func宣告的方法,就是使用尾置返回型別(trailing return type)
auto func(int i) -> int (*)[10];
- 還有一種情況,如果我們知道函式返回的指標指向哪個陣列,就可以使用decltype 關鍵字宣告返回型別:
int odd[]={1,3,5,7,9}; int even[]={0,2,4,6,8}; decltype(odd) *arrPtr(int i){ return (i%2) ? &odd : &even; }
decltype並不負責把陣列型別轉換成對應的指標,所以decltype的結果是一個數組,要想表示返回指標還必須在宣告時加一個* 。
- 對於過載而言,頂層const不影響傳入函式的物件。一個擁有頂層const的形參無法和另一個沒有頂層const的形參區分開來。
- 對於底層const,形參是某種型別的指標或引用,則可以區分const。即使非常量也能被底層const所指,但是編譯器會優先非常量版本的函式。
- 可以將const_cast 和過載搭配起來使用
- 當有多個過載函式可以匹配呼叫但每一個都不是明顯的最佳選擇時也將發生錯誤,稱為二義性呼叫(ambiguous call) 。
- 在不同的作用域中無法過載函式名
- C++同樣支援預設引數 ,預設引數必須在引數列表的最右邊
string screen(int hz=24,int wid=80,char backgrnd=' ');
- 在給定作用域中一個形參只能被賦予一次預設引數,後續宣告智慧給沒有預設值的形參新增預設實參。
- 只要表示式的值能轉換成形參所需的型別,該表示式就能作為預設實參。
- 將函式指定為行內函數(inline) ,通常就是將它在呼叫點”內聯地“展開。只需要在函式的返回型別前面加上關鍵字inline 。行內函數一般用於優化規模較小,流程直接,頻繁呼叫的函式。
- constexpr函式 是指能用於常量表達式的函式,函式的返回值和所有形參型別都得是字面值型別。
- assert 是一種預處理巨集,assert巨集用一個表示式作為它的條件:
assert(expr);
如果表示式為假,assert輸出資訊並終止程式執行;如果為真,assert什麼也不做。
- assert的行為依賴於一個名為NDEBUG 的預處理變數的狀態。如果定義NDEBUG,assert什麼也不做。預設情況下沒有定義NDEBUG,此時assert執行執行時檢查。
- 函式指標指向的是函式而非物件。和其他指標一樣,函式指標指向某種特定型別。函式的型別由它的返回型別 和形參型別 共同決定,與函式名 無關。要想宣告一個可以指向函式的指標,只需要用指標替換函式名 即可。
bool (*pf)(const string &,const string &);
- 當我們把函式名作為一個值使用時,該函式自動轉換成指標 。此時取地址符是可選 的。
- 我們還能直接使用指向函式的指標 呼叫該函式,無須提前解引用指標。
- 指向不同函式型別的指標不存在 轉換規則,但是我們可以和往常一樣為函式指標賦一個nullptr 或者值為0的整型常量表達式,表示該指標沒有指向任何一個函式 。
- 如果定義了指向過載函式的指標,指標型別必須與過載函式中的某一個精確匹配 。
- 和陣列類似,雖然不能定義函式型別的形參,但是形參可以是指向函式的指標 。此時,形參看起來是函式型別,實際上卻是當成指標使用:
void test(int pf(int));//等價的宣告 void test(int (*pf)(int));
我們可以直接把函式名作為實參使用 ,此時它會自動轉換成指標。
- 類型別名和decltype能讓我們簡化宣告:
//Func和Func2是函式型別 typedef bool Func(); typedef decltype(test) Func2(); //FuncP和FuncP2是指向函式的指標 typedef bool (*FuncP)(); typedef decltype(test) *Func2();
因為decltype返回函式型別,所以要加上*才能得到指標。
- 和陣列類似,雖然不能返回一個函式,但是能返回指向函式型別 的指標。與往常一樣,最簡單的方法是宣告一個類型別名,但此時返回型別不會自動轉換成指標,我們必須顯式地將返回型別指定為指標 。
int (*f1(int)) (int *,int);//也能直接宣告一個函式指標返回值
我們還可以使用尾置返回型別 的方式宣告一個返回函式指標的函式:
auto f1(int)->int (*)(int*,int);
- 如果我們明確知道返回的函式哪一個,可以參考上文所述返回陣列指標一樣使用decltype 簡化書寫函式指標返回型別的過程。但記得要顯式加上*表示我們需要返回指標 。