函式 (C++)
函式是執行某項作業的程式碼區塊。 函式可以選擇性地定義輸入參數,以讓呼叫端將引數傳遞給函式。 函式可以選擇性地傳回值做為輸出。 函式適用於將一般作業封裝在單一可重複使用的區塊中,而且其名稱最好可清楚描述該函式的作用。 下列函式接受來自呼叫端的兩個整數,並傳回其總和;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++ 中,函式也可以定義於命名空間範圍 (包括隱含全域命名空間)。 這類函式稱為 免費函 式或 非成員函式;這些函式在標準連結庫中廣泛使用。
函式可能會多載,這表示不同版本的函式可能會因為型式參數的數目和/或類型而不同,共用相同的名稱。 如需詳細資訊,請參閱 函數多載。
函式宣告的組件
最小函 式宣告 包含傳回型別、函式名稱和參數清單(可能是空的),以及提供更多指示給編譯程式的選擇性關鍵詞。 下列範例是函式宣告:
int sum(int a, int b);
函式定義是由宣告所組成,加上 主體,也就是大括弧之間的所有程序代碼:
int sum(int a, int b)
{
return a + b;
}
後面接著一個分號的函式宣告可能會出現在程式的多個位置。 它必須出現在每個轉譯單位中該函式的任何呼叫之前。 根據「一個定義規則 (ODR)」,函式定義只能出現在程式中一次。
函式宣告的必要組件如下:
傳回型別,指定函式傳回的值型別,如果沒有
void
傳回任何值,則為 。 在 C++11 中,是有效的傳回類型,auto
可指示編譯程式從 return 語句推斷類型。 在 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; };
連結規格或
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>) {...}
如需詳細資訊,請參閱
noexcept
。(僅限成員函式)cv 限定符,指定函
const
式為 或volatile
。(僅限成員函式)
virtual
、override
或final
。virtual
指定可以在衍生類別中覆寫函式。override
表示衍生類別中的函式會覆寫虛擬函式。final
表示無法在任何進一步衍生類別中覆寫函式。 如需詳細資訊,請參閱 虛擬函式。(僅限成員函式)
static
套用至成員函式表示函式未與類別的任何物件實例相關聯。(僅限非靜態成員函式)ref 限定符,指定編譯程式在隱含對象參數 (
*this
) 是右值參考與左值參考時,要選擇的函式多載。 如需詳細資訊,請參閱 函數多載。
函式定義
函 式定義 包含宣告和函式主體,以大括弧括住,其中包含變數宣告、語句和表達式。 下列範例顯示完整的函式定義:
int foo(int i, std::string s)
{
int value {i};
MyClass mc;
if(strcmp(s, "default") != 0)
{
value = mc.do_something(i);
}
return value;
}
主體內宣告的變數稱為區域變數。 它們會在函式結束時消失;因此,函式永遠不應該傳回區域變數的參考!
MyClass& boom(int i, std::string s)
{
int value {i};
MyClass mc;
mc.Initialize(i,s);
return mc;
}
const 和 constexpr 函式
您可以將成員函式宣告為 const
,以指定不允許函式變更類別中任何數據成員的值。 藉由將成員函式宣告為 const
,您可以協助編譯程式強制執行 const-correctness。 如果有人錯誤地嘗試使用宣告為 const
的函式來修改物件,則會引發編譯程序錯誤。 如需詳細資訊,請參閱 const。
將函式宣告為 constexpr
,當它產生的值可能在編譯時期判斷。 constexpr 函式通常會比一般函式更快執行。 如需詳細資訊,請參閱constexpr
。
函式樣板
函式樣板與類別樣板類似;它會根據樣板引數產生具象函式。 在許多情況下,樣板可以推斷類型引數,因此不需要明確指定它們。
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 double
auto 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: 若要明確處理 rvalue-reference 或 lvalue-reference 所傳遞的自變數,請在 參數上使用 double-ampersand 來表示通用參考:
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 end
int DoSomethingElse(int num,
string str = string{ "Working" },
Allocator& alloc = defaultAllocator)
{ ... }
// C2548: 'DoMore': missing default parameter for parameter 2
int DoMore(int num = 5, // Not a trailing parameter!
string str,
Allocator& = defaultAllocator)
{...}
如需詳細資訊,請參閱 預設自變數。
函式傳回型別
函式可能不會傳回另一個函式或內建數位列;不過,它可以傳回這些類型的指標,或 會產生函式物件的 Lambda。 除了這些情況之外,函式可能會傳回範圍中任何型別的值,或者它可能不會傳回任何值,在此情況下傳回型別為 void
。
尾端傳回類型
"ordinary" 傳回型別位於函式簽章左邊。 尾端傳回型別位於簽章最右邊,前面加上 ->
運算符。 傳回值的類型取決於樣板參數時,尾端傳回類型特別適用於函式樣板。
template<typename Lhs, typename Rhs>
auto Add(const Lhs& lhs, const Rhs& rhs) -> decltype(lhs + rhs)
{
return lhs + rhs;
}
當與尾端傳回型別搭配使用時 auto
,它只會做為任何 decltype 表達式產生的佔位元,而且本身不會執行類型推算。
函式區域變數
函式主體內宣告的變數稱為局部變數,或只是局部變數。 非靜態局部變數只能在函式主體內顯示,如果在函式結束時在堆棧上宣告,則會超出範圍。 當您建構局部變數並傳回值時,編譯程式通常可以執行 具名傳回值優化 ,以避免不必要的複製作業。 如果您以傳址方式傳回區域變數,則編譯器會發出警告,因為呼叫端使用該參考的任何嘗試都是在終結區域變數之後。
在 C++ 中,區域變數可能宣告為靜態。 變數只會顯示在函式主體內,但函式的所有執行個體都有變數的單一複本。 區域靜態物件會在 atexit
指定的終止時被終結。 如果未建構靜態對象,因為程式的控件流程略過其宣告,則不會嘗試終結該物件。
傳回型別的類型推算 (C++14)
在 C++14 中,您可以使用 auto
指示編譯程式從函式主體推斷傳回型別,而不需要提供尾端傳回型別。 請注意, auto
一律會依值推算回 。 使用 auto&&
指示編譯器推斷參考。
在此範例中, auto
會推斷為 lhs 和 rhs 總和的非 const 值複本。
template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
return lhs + rhs; //returns a non-const object by value
}
請注意, auto
不會保留它所推斷之類型的 const-ness。 若要轉送傳回值必須保留其自變數的 const-ness 或 ref-ness 的函式,您可以使用 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());
}
從函式傳回多個值
有各種方法可從函式傳回多個值:
封裝具名類別或結構物件中的值。 呼叫端必須能夠看見類別或結構定義:
#include <string> #include <iostream> using namespace std; struct S { string name; int num; }; S g() { string t{ "hello" }; int u{ 42 }; return { t, u }; } int main() { S s = g(); cout << s.name << " " << s.num << endl; return 0; }
傳回 std::tuple 或 std::p air 物件:
#include <tuple> #include <string> #include <iostream> using namespace std; tuple<int, string, double> f() { int i{ 108 }; string s{ "Some text" }; double d{ .01 }; return { i,s,d }; } int main() { auto t = f(); cout << get<0>(t) << " " << get<1>(t) << " " << get<2>(t) << endl; // --or-- int myval; string myname; double mydecimal; tie(myval, myname, mydecimal) = f(); cout << myval << " " << myname << " " << mydecimal << endl; return 0; }
Visual Studio 2017 15.3 版和更新版本 (適用於
/std:c++17
模式和更新版本):使用結構化系結。 結構化系結的優點是儲存傳回值的變數會在宣告時初始化,在某些情況下可能會更有效率。 在語句auto[x, y, z] = f();
中,括弧會導入並初始化整個函式區塊範圍內的名稱。#include <tuple> #include <string> #include <iostream> using namespace std; tuple<int, string, double> f() { int i{ 108 }; string s{ "Some text" }; double d{ .01 }; return { i,s,d }; } struct S { string name; int num; }; S g() { string t{ "hello" }; int u{ 42 }; return { t, u }; } int main() { auto[x, y, z] = f(); // init from tuple cout << x << " " << y << " " << z << endl; auto[a, b] = g(); // init from POD struct cout << a << " " << b << endl; return 0; }
除了使用傳回值本身之外,您還可以定義任意數目的參數來使用傳址來「傳回」值,讓函式可以修改或初始化呼叫端所提供的物件值。 如需詳細資訊,請參閱 Reference-Type 函式自變數。
函式指標
C++ 支援函式指標的方式與 C 語言相同。 不過,較具類型安全的替代方案通常是使用函式物件。
如果宣告傳回函式指標類型的函式,建議您使用 typedef
宣告函式指標類型的別名。 例如:
typedef int (*fp)(int);
fp myFunction(char* s); // function returning function pointer
如果未這麼做,則函式宣告的適當語法可能會從函式指標的宣告子語法推斷,方法是將標識符 (fp
在上述範例中) 取代為函式名稱和自變數清單,如下所示:
int (*myFunction(char* s))(int);
上述宣告相當於使用 typedef
先前的宣告。
另請參閱
函式多載
具有變數引數清單的函式
明確的預設和已刪除的函式
函式上的引數相依名稱 (Koenig) 查閱
預設引數
內嵌函式
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應