函數是執行某種操作的代碼塊。函數可以選擇性地定義使調用方可以將實參傳遞到函數中的輸入形參。函數可以選擇性地傳回值作為輸出。函數可用於在單個可重用塊中封裝常用操作(理想情況是使用可清晰地描述函數行為的名稱)。以下函數從調用方接受兩個整數並返回其總和;a 和 b 是 int 類型的參數。
int sum(int a, int b){ return a + b;}
可以從程式中任意數量的位置調用函數。傳遞給函數的值是實參,其類型必須與函數定義中的形參類型相容。
int main(){ int i = sum(10, 32); int j = sum(i, 66); cout << "The value of j is" << j << endl; // 108}
對於函數長度沒有實際限制,不過良好的設計應以執行單個明確定義的任務的函數為目標。複雜演算法應儘可能分解成易於理解的更簡單函數。
在類範圍中定義的函數稱為成員函數。在 C++ 中(與其他語言不同),函數還可以在命名空間範圍中定義(包括隱式全域命名空間)。這類函數稱為 free 函數或非成員函數;它們在標準庫中廣泛使用。
函式宣告的各個部分
最小函式宣告包含傳回型別、函數名和參數列表(可能為空白),以及向編譯器提供附加說明的可選關鍵字。函數定義包含聲明以及函數體(這是大括弧之間的所有代碼)。後接分號的函式宣告可以出現在程式中的多個位置處。它必須在每個翻譯單元中對該函數的任何調用之前出現。根據單個定義規則 (ODR),函數定義必須僅在程式中出現一次。
函式宣告的必需部分有:
傳回型別,指定函數將返回的值的類型,如果不返回任何值,則為 void。在 C++11 中,auto 是有效傳回型別,可指示編譯器從返回語句推斷類型。在 C++14 中,還允許使用 decltype(auto)。有關詳細資料,請參閱下面的“傳回型別中的類型推導”。
函數名,必須以字母或底線開頭,不能包含空格。一般而言,標準庫函數名中的前置底線指示私人成員函數,或不是供你的代碼使用的非成員函數。
參數列表(一組用大括弧限定、逗號分隔的零個或多個參數),指定類型以及可以用於在函數體內訪問值的可選局部變數名。
函式宣告的可選部分有:
constexpr,指示函數的傳回值是常量值,可以在編譯時間進行計算。
constexpr float exp(float x, int n){ return n == 0 ? 1 : n % 2 == 0 ? exp(x * x, n / 2) : exp(x * x, (n - 1) / 2) * x;};
其 linkage 規範(extern 或 static)。
Declare printf with C linkage.extern "C" int printf( const char *fmt, ... );
inline,指示編譯器將對函數的每個調用替換為函數代碼本身。在某個函數快速執行並且在效能關鍵程式碼片段中重複調用的情況下,內聯可以協助提高效能。
inline double Account::GetBalance(){ return balance;}
noexcept,指定函數是否可以引發異常。在下面的樣本中,函數在 is_pod 運算式計算結果為 true 時不引發異常。
#include <type_traits>template <typename T>T copy_object(T& obj) noexcept(std::is_pod<T>) {...}
僅限成員函數)cv 限定符,指定函數是 const 還是 volatile。
(僅限成員函數)virtual、override 或 final。 virtual 指定可以在衍生類別中重寫函數。 override 表示衍生類別中的函數在重寫虛函數。 final 表示函數不能在任何進一步的衍生類別中進行重寫。
(僅限成員函數僅)應用於成員函數的 static 表示函數不與類的任何對象執行個體關聯。
(僅限非靜態成員函數)ref 限定符,向編譯器指定隱式對象參數 (*this) 是右值引用與左值引用時要選擇的函數重載。
下圖顯示了函數定義的各個部分。灰色地區是函數體。
函數定義部分
函數定義
函數體內聲明的變數稱為局部變數。它們會在函數退出時超出範圍;因此,函數應永遠不返回對局部變數的引用!
函數模板
函數模板類似於類模板;它基於模板參數產生具體功能。在許多情況下,模板能夠推斷型別參數,因此無需顯式指定它們。
template<typename Lhs, typename Rhs>auto Add2(const Lhs& lhs, const Rhs& rhs){ return lhs + rhs;}auto a = Add2(3.13, 2.895); // a is a doubleauto b = Add2(string{ "Hello" }, string{ " World" }); // b is a std::string有關詳細資料,請參閱函數模板
函數形參和實參
函數具有零種或多種類型的逗號分隔參數列表,其中每個參數都具有可以用於在函數體內訪問它的名稱。函數模板可以指定其他類型或值參數。調用方傳遞實參(其類型與形參列表相容的具體值)。
預設情況下,參數通過值傳遞給函數,這意味著函數會收到所傳遞的對象的副本。對於大型物件,棄置站台可能成本高昂,並非始終必要。若要使實參通過引用(特別是左值引用)進行傳遞,請向形參添加引用限定符:
void DoSomething(std::string& input){...}
當函數修改通過引用傳遞的參數時,它會修改原始對象,而不是本機複本。若要防止函數修改這類實參,請將形參限定為
const&:void DoSomething(const std::string& input){...}
C++ 11:若要顯式處理通過右值引用或通過左值引用傳遞的實參,請對形參使用雙與號以指示通用引用:
void DoSomething(const std::string&& input){...}
只要關鍵字 void 是實參聲明列表中的第一個也是唯一一個成員,那麼在形參聲明列表中使用單個關鍵字 void 聲明的函數就沒有實參。列表中的其他地方的 void 類型的參數產生錯誤。例如:
// OK same as GetTickCount()long GetTickCount( void );
請注意,儘管指定 void 參數是非法(此處所述的除外),但派生自類型 void 的類型(如指向 void 的指標和 void 的數組)可以出現在參數聲明列表中的任何位置。
預設參數
函數簽名中的最後一個或幾個形參可能會分配有預設實參,這意味著調用方可能會在調用函數時省略實參(除非要指定某個其他值)。
int DoSomething(int num, string str, Allocator& alloc = defaultAllocator){ ... }// OK both parameters are at endint DoSomethingElse(int num, string str = string{ "Working" }, Allocator& alloc = defaultAllocator){ ... }// C2548: 'DoMore': missing default parameter for parameter 2int DoMore(int num = 5, // Not a trailing parameter! string str, Allocator& = defaultAllocator){...}
函數傳回型別
函數可能不會返回另一個函數或內建數組;但是,它可以返回指向這些類型的指標,或產生函數對象的 lambda。除了這些情況,函數可以返回處於範圍內的任何類型的值,或者它可以返回任何值(在這種情況下傳回型別是 void)。
結尾傳回型別
“普通”傳回型別位於函數簽名左側。結尾傳回型別位於簽名的最右側,前面帶有 -> 運算子。當傳回值的類型取決於模板參數時,結尾傳回型別在函數模板中尤其有用。
template<typename Lhs, typename Rhs>auto Add(const Lhs& lhs, const Rhs& rhs) -> decltype(lhs + rhs){ return lhs + rhs; }
當 auto 與結尾傳回型別結合使用時,它對於 decltype 運算式產生的任何內容都只用作預留位置,本身不執行類型推導。
傳回型別中的類型推導 (C++14)
在 C++14 中,可以使用 auto 指示編譯器從函數體推斷傳回型別,而不必提供結尾傳回型別。請注意,auto 始終推導為按值返回。使用 auto&& 可指示編譯器推導引用。
在此樣本中,auto 會推導為 lhs 和 rhs 之和的非常量值副本。
template<typename Lhs, typename Rhs>auto Add2(const Lhs& lhs, const Rhs& rhs){ return lhs + rhs; //returns a non-const object by value}
請注意,auto 不會保留它推導的類型的常量性。對於傳回值需要保留其參數的常量性或引用性的轉寄函數,可以使用 decltype(auto) 關鍵字,該關鍵字使用 decltype 類型推斷規則並保留所有類型資訊。 decltype(auto) 可以用作左側的普通傳回值,或結尾傳回值。
下面的樣本(基於來自 N3493 的代碼)示範的 decltype(auto) 用於採用在模板執行個體化之前未知的傳回型別實現函數參數的完美轉寄。
template<typename F, typename Tuple = tuple<T...>, int... I>decltype(auto) apply_(F&& f, Tuple&& args, index_sequence<I...>) { return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);}template<typename F, typename Tuple = tuple<T...>, typename Indices = make_index_sequence<tuple_size<Tuple>::value >> decltype( auto) apply(F&& f, Tuple&& args) { return apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices());}}
函數局部變數
在函數主體內聲明的變數稱為局部變數。非靜態局部變數僅在函數體中可見,如果它們在堆棧上聲明,則會在函數退出時超出範圍。構造局部變數並通過值返回它時,編譯器通常可以執行傳回值最佳化以避免不必要的複製操作。如果通過引用返回局部變數,則編譯器會發出警告,因為調用方為使用該引用而進行的任何嘗試會在局部變數已銷毀之後進行。
局部靜態對象將在 atexit 指定的終止期間銷毀。如果某個靜態對象由於程式的控制流程跳過了其聲明而未構造,則不會嘗試銷毀該對象。
靜態局部變數
在 C++ 中,局部變數可以聲明為靜態。變數僅在函數體中可見,但是對於函數的所有執行個體,存在變數的單個副本。
函數指標
C++ 通過與 C 語言相同的方式支援函數指標。但是更加型別安全的替代方法通常是使用函數對象。
建議使用 typedef 聲明函數指標類型的別名(如果聲明返回函數指標類型的函數)。例如
typedef int (*fp)(int);fp myFunction(char* s); // function returning function pointer
如果不執行此操作,則函式宣告的正確文法可以通過用函數名稱和參數列表替換標識符(上例中為 fp)來從函數指標的聲明符文法推匯出,如下所示:
int (*myFunction(char* s))(int);
前面的聲明與使用上面的 typedef 的聲明等效。