搜尋

首頁  >  問答  >  主體

C++ 中的可调用对象与其他编程语言中的函数对象有何异同?

C++ 中的可调用对象与其他编程语言中的函数对象有何异同?

ringa_leeringa_lee2773 天前417

全部回覆(1)我來回復

  • 迷茫

    迷茫2017-04-17 14:37:30

    題目問的其它語言,這個太廣泛了,因為程式語言這麼多,有形如C語言這種傳統的函數,也有形如java那樣無法獨立存在的函數,還有各類腳本語言的不同形式的函數。

    不過在函數是第一等公民的語言裡,要求函數能做到

    • 函數可以獨立存在,且可在任意地方執行

    • 函數的作用域是詞法作用域,且可捕捉外部變數

    • 函數可從另一個函數傳回,也可以作為別的函數的參數(高階函數)

    • 函數從別的函數生成後,所引用的作用域依舊可訪問,而不是被銷毀

    在不少腳本語言和新興語言裡,函數大都是有以上特性表現的。

    關於不同語言閉包捕捉的問題,可以參考我之前寫過的一篇文章 https://zhuanlan.zhihu.com/p/...

    再說C++,C++有四個可以用來呼叫的東西

    1. 函數

    2. 函數指標

    3. 重載了()運算子的類別

    4. lambda

    這四個東西的類型各不相同,但是都可以做到執行函數,使用上都是形如func()

    函數和函數指標在某個情況下是可以互轉的,它在C語言裡就存在,但是它存在一些難以解決的問題,包括

    • 函數型難寫,難讀

    • 函數內部無法保存狀態,沒有辦法實現外層變數擷取

    重載了()運算子的類別是C++提供的一個仿函式寫法,它可以實作不少做法

    class Test{
    public:
     Test(int a):_a(a){}
     int operator ()(int b){  
        return _a+b;
     }  
    private:
      int _a;
    };

    使用上只要這樣:

    Test sum1 = Test(1);
    std::cout << sum1(10) << '\n';
    Test sum2 = Test(10);
    std::cout << sum2(10) << '\n';

    可以看到sum1和sum2的內部都保存了一個狀態,進而表現不一,這是普通的函數和函數指標無法做到的。

    然而重載了()運算子的類別的一大缺點就是不夠輕量級,不管是佔用空間還是執行效率上和函數/函數指標都存在差距。

    C++11引進了lambda,典型的lambda是這樣

    int a = 10;
    auto func = [&a](int b){return a+b;};
    std::cout << func(10) << '\n';
    a = 20;
    std::cout << func(10) << '\n';

    可以看到lambda可以輕鬆捕捉變量,實現閉包效果,所以我們在很多場合下可以使用lambda來代替重載了()運算符的類別。

    不過lambda也有局限,首先是它的類型是無法手動寫出來的,也就是說對於同樣的兩個lambda,它的類型是不同的

    auto func = [&a](int b){return a+b;};
    auto func2 = [&a](int b){return a+b;};
    std::cout << (typeid(func).name() !=  typeid(func2).name()) << '\n';

    所以我們只能定義的時候只能用auto,而且如果我們想把lambda存到vector、list等容器裡,就必須要用包裝類std::function,它本質上是個泛型的模板類,也同樣重載了()操作符

    std::function<int(int)> func3 = func;

    使用std::function之後,lambda的輕量優勢就消失了,不過有的時候也得這麼做。

    回覆
    0
  • 取消回覆