函式 (C++)

函式是執行某項作業的程式碼區塊。 函式可以選擇性地定義輸入參數,以讓呼叫端將引數傳遞給函式。 函式可以選擇性地傳回值做為輸出。 函式適用於將一般作業封裝在單一可重複使用的區塊中,而且其名稱最好可清楚描述該函式的作用。 下列函式接受來自呼叫端的兩個整數,並傳回其總和;ab類型的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)」,函式定義只能出現在程式中一次。

函式宣告的必要組件如下:

  1. 傳回型別,指定函式傳回的值型別,如果沒有 void 傳回任何值,則為 。 在 C++11 中,是有效的傳回類型, auto 可指示編譯程式從 return 語句推斷類型。 在 C++14 中, decltype(auto) 也允許。 如需詳細資訊,請參閱下面的<傳回型別中的類型推斷>。

  2. 函式名稱,其開頭必須是字母或底線,且不能包含空格。 一般而言,標準連結庫函式名稱中的前置底線表示私用成員函式,或非成員函式不適合您的程式代碼使用。

  3. 參數清單,是以大括號分隔並以逗號隔開的零或多個參數集合,指定類型以及選擇性指定可用來存取函式主體內值的本機名稱。

函式宣告的選擇性組件如下:

  1. 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;
    };
    
  2. 連結規格或 externstatic

    //Declare printf with C linkage.
    extern "C" int printf( const char *fmt, ... );
    
    

    如需詳細資訊,請參閱 翻譯單位和連結

  3. inline,指示編譯程式以函式程式代碼本身取代函式的每個呼叫。 如果函式執行速度快速並在效能關鍵的程式碼區段中重複予以叫用,則內嵌有助於提高效能。

    inline double Account::GetBalance()
    {
        return balance;
    }
    

    如需詳細資訊,請參閱 內嵌函式

  4. noexcept表達式,指定函式是否可以擲回例外狀況。 在下列範例中,如果 is_pod 表達式評估為 true,函式就不會擲回例外狀況。

    #include <type_traits>
    
    template <typename T>
    T copy_object(T& obj) noexcept(std::is_pod<T>) {...}
    

    如需詳細資訊,請參閱noexcept

  5. (僅限成員函式)cv 限定符,指定函 const 式為 或 volatile

  6. (僅限成員函式) virtualoverridefinalvirtual 指定可以在衍生類別中覆寫函式。 override 表示衍生類別中的函式會覆寫虛擬函式。 final 表示無法在任何進一步衍生類別中覆寫函式。 如需詳細資訊,請參閱 虛擬函式

  7. (僅限成員函式) static 套用至成員函式表示函式未與類別的任何物件實例相關聯。

  8. (僅限非靜態成員函式)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());
}

從函式傳回多個值

有各種方法可從函式傳回多個值:

  1. 封裝具名類別或結構物件中的值。 呼叫端必須能夠看見類別或結構定義:

    #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;
    }
    
  2. 傳回 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;
    }
    
  3. 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;
    }
    
  4. 除了使用傳回值本身之外,您還可以定義任意數目的參數來使用傳址來「傳回」值,讓函式可以修改或初始化呼叫端所提供的物件值。 如需詳細資訊,請參閱 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) 查閱
預設引數
內嵌函式