首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 函數指標與函數物件深度解析

     編輯:關於C++

前言

在 C++ 程式設計中,函數指標與函數物件是實現回呼、策略模式等進階技巧的基石。本文將系統整理全域函數指標、類別成員函數指標,以及函數物件的語法、用法差異與優缺點,並提供完整編譯範例供參考。

一、全域函數指標

最基礎的形式,指向一般函數(非類別成員)。透過 typedef 定義指標型別,可提升可讀性。

#include <iostream>
using namespace std;

typedef void (*FuncPtr)(int); // 定義函數指標型別

void Drink(int i) { cout << "No. " << i << " drink..." << endl; }
void Eat(int i)   { cout << "No. " << i << " eat..." << endl; }

class TestAction {
public:
    FuncPtr testAct;
    void TestAct(int i) {
        if (testAct) testAct(i); // 呼叫指標指向的函數
    }
};

int main() {
    TestAction doact;
    doact.testAct = &Drink; // 指派函數位址
    doact.TestAct(0); doact.TestAct(1); doact.TestAct(2);
    doact.testAct = &Eat;
    doact.TestAct(0); doact.TestAct(1); doact.TestAct(2);
    return 0;
}

關鍵點: 全域函數指標語法簡單,直接賦值函數名稱或 &函數名 即可,呼叫方式如同一般函數。

二、類別成員函數指標

成員函數指標語法較複雜,需指定所屬類別,且呼叫時必須搭配物件實例(透過 .* 或 ->* 運算子)。

#include <iostream>
using namespace std;

class Action;
class TestAction;

// 定義指向 Action 成員函數的指標型別
typedef void (Action::*MemFuncPtr)(int);

class Action {
public:
    void Drink(int i) { cout << "No. " << i << " drink..." << endl; }
    void Eat(int i)   { cout << "No. " << i << " eat..." << endl; }
};

class TestAction {
public:
    MemFuncPtr testAct;
    Action* pAction; // 必須持有目標物件指標

    void TestAct(int i) {
        if (pAction && testAct)
            (pAction->*testAct)(i); // 透過物件呼叫成員函數指標
    }
};

int main() {
    Action act;
    TestAction doact;
    doact.pAction = &act;
    doact.testAct = &Action::Drink; // 取得成員函數位址
    doact.TestAct(0); doact.TestAct(1); doact.TestAct(2);
    doact.testAct = &Action::Eat;
    doact.TestAct(0); doact.TestAct(1); doact.TestAct(2);
    return 0;
}

注意: 成員函數指標必須綁定具體物件實例才能呼叫,這增加了靈活性但也帶來語法負擔。

三、函數物件

函數物件是多載 operator() 的類別實例,行為像函數卻擁有狀態與型別優勢。C++ 標準庫大量使用(如 std::greater、std::less)。

#include <iostream>
#include <functional>
using namespace std;

// 抽象基類定義介面
class Action {
public:
    virtual ~Action() {}
    int operator()(int i) { Act(i); return i; }
    virtual void Act(int i) = 0;
};

// 具體實作
class Drink : public Action {
    void Act(int i) override { cout << "No. " << i << " drink..." << endl; }
};
class Eat : public Action {
    void Act(int i) override { cout << "No. " << i << " eat..." << endl; }
};

// 使用者端
class TestAction {
public:
    void TestAct(int i, Action& act) { act(i); } // 呼叫 operator()
};

int main() {
    TestAction doact;
    doact.TestAct(0, Drink()); // 傳遞暫時物件
    doact.TestAct(1, Drink());
    doact.TestAct(2, Drink());
    doact.TestAct(0, Eat());
    doact.TestAct(1, Eat());
    doact.TestAct(2, Eat());
    return 0;
}

四、函數物件的進階優勢

1. 狀態保存與內聯最佳化

函數物件可內建資料成員,記錄先前呼叫結果;編譯器易於內聯呼叫,效能優於函數指標。

2. 泛型演算法支援

透過成員模板,函數物件可實作泛型操作,如通用的 Negate:

class Negate {
public:
    template <class T> T operator()(T t) const { return -t; }
};

void Callback(int n, const Negate& neg) {
    n = neg(n); // 呼叫重載的 ()
    cout << n << endl;
}

int main() {
    Callback(5, Negate());           // 方式一:傳遞暫時物件
    Negate neg;
    cout << neg(9.99999) << endl;    // 方式二:重複使用物件
    cout << neg(__int32(39999999)) << endl;
    return 0;
}

3. STL 演算法整合

標準演算法(如 std::sort)接受函數物件作為比較準則,型別安全且效能高:

vector<int> vi;
// ... 填入資料 ...
sort(vi.begin(), vi.end(), greater<int>()); // 降序
sort(vi.begin(), vi.end(), less<int>());    // 升序

總結比較

特性全域函數指標成員函數指標函數物件
語法複雜度低高中(需定義類別)
攜帶狀態不可不可(需外部物件)可(類別成員變數)
內聯最佳化困難困難容易
泛型支援無無支援成員模板
適用場景C 風格回呼、簡單場景需綁定特定物件的回呼STL 演算法、策略模式、需狀態的邏輯

現代 C++ 開發建議優先使用函數物件或 std::function/Lambda 表達式,僅在介接 C API 或極度簡單場合使用函數指標。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved