目錄
上一章節:
一、引言
一、函數式編程基礎
三、Lambda 表達式
作用:
Lambda 表達式捕獲值的方式:
注意:
四、函數對象
函數對象與普通函數對比:
五、函數適配器
1、適配普通函數
2、適配 Lambda 表達式
3、適配函數對象(仿函數)
使用場景
六、bind函數適配器
七、函數式編程的應用
八、總結
下一章節:
上一章節:
十三、C++速通秘籍—PIMPL編程原則-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/147190679?spm=1001.2014.3001.5502
一、引言
這里主要介紹一下當下C++編程開發中比較常見的一種范式——函數式編程, 它以函數為核心,強調不可變性、高階函數等概念,為我們處理復雜邏輯提供了新的視角和方法。這里給大家做一個簡單的入門,以及筆者自己接觸到的函數式編程的方式方法。
一、函數式編程基礎
- 不可變性:在函數式編程里,數據一旦創建就不可改變。比如在傳統 C++ 中,我們可能會這樣寫代碼:
int a = 5;
a = 10; // 修改變量a的值
而在函數式編程理念下,我們更傾向于通過函數調用來產生新的值,而不是修改原有變量。例如:
int add(int num) { return num + 5;
}int result = add(5); // result為10,沒有修改傳入的參數
- 高階函數:是指接受函數作為參數,或者返回一個函數的函數。C++ 中的std::for_each 就是一個高階函數的例子:
#include <iostream>
#include <algorithm>
#include <vector>void print(int num)
{ std::cout << num << " ";
}int main()
{ std::vector<int> vec = {1, 2, 3, 4, 5};std::for_each(vec.begin(), vec.end(), print);return 0;
}
這里std::for_each 接受了print 函數作為參數,對容器中的每個元素進行操作。
- 閉包:閉包是一個函數對象,它可以捕獲其創建環境中的變量。在 C++ 中,Lambda 表達式就常用來創建閉包。例如:
#include <iostream>
#include <vector>int main()
{ int factor = 2;auto multiply = [factor](int num) {return num * factor; }; std::vector<int> vec = {1, 2, 3}; for (int num : vec) { std::cout << multiply(num) << " "; } return 0;
}
這里multiply 這個 Lambda 表達式捕獲了外部的factor 變量,形成了閉包。
- 惰性求值:惰性求值是指表達式只有在真正需要結果時才進行計算。在 C++ 中,雖然沒有像某些函數式編程語言那樣原生支持惰性求值,但我們可以通過一些技巧來模擬。比如自定義一個延遲計算的類模板。
三、Lambda 表達式
Lambda 表達式是 C++ 函數式編程中非常重要的一部分。它允許我們在代碼中快速定義匿名函數。
其本質是匿名函數,能夠捕獲一定范圍的變量,與普通函數不同,可以在函數內部定義;
例如,要對一個整數數組進行排序,我們可以使用 Lambda 表達式來指定排序規則:
#include <iostream>
#include <algorithm>
#include <vector>int main()
{ std::vector<int> vec = {5, 3, 1, 4, 2}; std::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; }); for (int num : vec) { std::cout << num << " "; } return 0;
}
這里的 Lambda 表達式[](int a, int b) { return a < b; } 定義了升序排序的比較規則。
作用:
- 簡化程序結構,因為優化了函數命名與函數傳參;
- 提高程序運行效率,因為優化了函數調用、函數返回等消耗;
- 適用于簡單功能的函數優化;
Lambda 表達式捕獲值的方式:
捕獲方式 | 說明 |
---|---|
= | 按值捕獲,lambda內部可以使用,但是無法更改值 |
& | 按地址捕獲,lambda內部可以使用,同時也更改了實際值 |
變量名 | 按值捕獲,可用不可改 |
&變量名 | 引用捕獲,可用可改 |
副本捕獲 | c++14后可以自定義變量名 = 捕獲變量,但是無法通過副本名改變變量名 |
#include <iostream>int main(int argc, char **argv)
{int num1 = 5;int num2 = 6;auto func_add = [&num1,num2]() //num1可修改,num2不可修改{num1 = 7;return num1 + num2;};auto func_add1 = [=](int a, int b){a = 1; //這里修改的只是形參a/b的值,不會改變num1與num2的值b = 1;return a + b;};auto func_add2 = [&]{// num1 = 1; //按引用傳遞,可用可修改num1與num2的值return num1 + num2;};std::cout<<func_add()<<std::endl;std::cout<<"num1 = "<<num1<<" num2 = "<<num2<<std::endl;std::cout<<func_add1(num1,num2)<<endl;std::cout<<"num1 = "<<num1<<" num2 = "<<num2<<std::endl;std::cout<<func_add2()<<endl;std::cout<<"num1 = "<<num1<<" num2 = "<<num2<<std::endl;return 0;
}
注意:
lambda 不可以包含static修飾的變量及全局變量;且避免復雜化 ;
四、函數對象
- 函數對象的定義和使用:函數對象是一個類或結構體,它重載了函數調用運算符"()"。例如:
#include <iostream>
#include <string>
#include <functional>using namespace std;template <typename T>
class Add
{
public:Add() = default;void operator()(T &&a, T &&b) //重載函數運算符,采用的是萬能引用{cout<<a+b<<endl;}
};int main(int argc ,char **argv)
{Add<int> c_add;c_add.operator()(5,6); //利用成員函數的形式調用c_add(5,6); //采用函數成員方式plus<int> p1;cout<<p1(5,6)<<endl; //使用系統函數對象庫return 0;
}
這里Adder 類就是一個函數對象, 通過重載() 運算符,使得它的對象可以像函數一樣被調用。
- STL 中的函數對象:C++ STL 中提供了很多預定義的函數對象,如std::plus、std::less 等。例如使用std::plus 來對兩個數求和:
#include <iostream>
#include <functional>int main()
{ std::plus<int> plus_op; int result = plus_op(5, 3); std::cout << result << std::endl; return 0;
}
C++ STL 提供了豐富的算法,這些算法很多都體現了函數式編程的思想。比如std::accumulate 可以用來對容器中的元素進行累加:
#include <iostream>
#include <numeric>
#include <vector>int main() {std::vector<int> vec = {1, 2, 3, 4, 5}; int sum = std::accumulate(vec.begin(), vec.end(), 0); std::cout << sum << std::endl; return 0;
}
函數對象與普通函數對比:
- 函數對象比一般函數更靈活,因為它可以擁有狀態(state),事實上,對于相同的函數對象可以設置兩個狀態不同的實例;普通函數沒有狀態;
- 每個函數對象都有其類型,因為你可以將函數對象的類型當做template參數傳遞,從而指定某種行為;
- 執行速度上,函數對象通常比函數指針更快;
五、函數適配器
????????在編程中用于封裝和管理函數或可調用對象(如函數指針、函數對象、Lambda 表達式等 ),讓函數使用更靈活通用。以 C++ 為例,其標準庫中的 std::function 是常用的函數適配器,本質是類模板 。它能 存儲、復制和調用任何可調用對象,為不同可調用對象提供統一調用接口,使用者無需關心其具體類型。std::function ?,表明可適配接收兩個int 參數并返回int 類型值的可調用對象 。
實例:
1、適配普通函數
#include <iostream>
#include <functional>
int add(int a, int b) {return a + b;
}
int main() {std::function<int(int, int)> func = add; std::cout << func(3, 4) << std::endl; return 0;
}
2、適配 Lambda 表達式
#include <iostream>
#include <functional>
int main() {std::function<int(int, int)> func = [](int a, int b) {return a * b;};std::cout << func(3, 4) << std::endl; return 0;
}
3、適配函數對象(仿函數)
#include <iostream>
#include <functional>
struct Subtract {int operator()(int a, int b) const {return a - b;}
};
int main() {std::function<int(int, int)> func = Subtract();std::cout << func(5, 3) << std::endl; return 0;
}
Subtract 結構體定義了函數調用運算符() ?,是函數對象 。std::function 將其包裝后,可通過func 調用實現減法。
使用場景
- 泛型編程:模板函數中,可將不同類型可調用對象(函數指針、Lambda、函數對象等)包裝后作為參數傳遞,使模板函數能處理多種調用邏輯,增強代碼通用性與靈活性。例如編寫通用算法模板,可接收不同比較規則的函數包裝器實現自定義排序等操作。
- 回調函數?:在事件驅動編程(如圖形界面開發、網絡編程 )中,常需設置回調函數。用函數包裝器可方便存儲和管理這些回調,在特定事件發生時調用。如注冊按鈕點擊事件回調,可將處理邏輯寫成普通函數、Lambda 等,再用函數包裝器管理并傳遞給按鈕組件。
- 異步編程?:多線程或異步任務場景下,函數包裝器可存儲要在新線程或異步環境執行的函數。如使用std::thread創建線程時,可將函數包裝器作為線程執行任務,方便管理任務邏輯。
- 日志記錄與性能監控?:通過包裝器,可在函數執行前后添加日志記錄代碼,記錄輸入參數、執行時間等信息,輔助調試和性能優化;也能進行性能分析,記錄函數執行耗時、資源占用等指標。
- 權限驗證與異常處理?:在函數執行前,利用包裝器進行權限驗證,確保只有有權限用戶能調用;執行過程中捕獲異常并處理,如打印錯誤信息、進行重試等操作 ,增強程序穩定性與安全性。
六、bind函數適配器
(1)、主要用在 函數已經存在,但是現有參數較多,減少實際所需參數個數的一種方法;
(2)、本質, bind也是一個函數模板,返回值是一個仿函數 ,是可調用對象;
(3)、bind可以綁定的對象:①普通函數;②lambda表達式;③函數對象;④類的成員函數;⑤類的數據成員;
#include <iostream>
#include <functional>
using namespace std;template <typename T>
class Add
{
public:T operator()(T a, T b, T c){print();return a + b;}void operator()(const T &a){cout << a << endl;}void print(){cout << "function add!" << endl;}int m_result;
};int add(int a, int b, int c)
{cout << "a = " << a << " b = " << b << endl;return a + b + c;
}int main()
{//普通函數function<int(int,int)> my_add = std::bind(add,std::placeholders::_1,std::placeholders::_2,0);cout << my_add(5,6) << endl;function<int()> my_add2 = std::bind(add,7,8,0);cout << my_add2() << endl;//lambda表達式auto lambda_func = [=](int a, int b, int c){return a + b + c;};function<int(int,int)> my_add3 = std::bind(lambda_func,std::placeholders::_2,std::placeholders::_1,0);cout << my_add3(3,4) << endl;function<int()> my_add4 = std::bind(lambda_func,3,4,0);cout << my_add4() << endl;//函數對象Add<int> c_add;function<int(int,int)> my_add5 = std::bind(c_add,std::placeholders::_2,std::placeholders::_1,0);cout << my_add5(4,5) << endl;function<int()> my_add6 = std::bind(c_add,5,6,0);cout << my_add6() << endl;return 0;
}
七、函數式編程的應用
- 數據處理:在處理大量數據時,函數式編程可以讓代碼更簡潔和易于理解。比如對一個包含學生成績的數組進行篩選,找出成績大于 80 分的學生,使用函數式編程風格的代碼可能如下:
#include <iostream>
#include <vector>
#include <algorithm>
struct Student { std::string name; int score;
};int main()
{ std::vector<Student> students = {{"Alice", 85}, {"Bob", 70}, {"Charlie", 90}}; std::vector<Student> high_scores; std::copy_if(students.begin(), students.end(), std::back_inserter(high_scores), [](const Student& s) {return s.score > 80; }); for (const auto& student : high_scores) { std::cout << student.name << " : " << student.score << std::endl; } return 0;
}
- 并發編程:函數式編程的不可變性等特性在并發編程中很有優勢,因為不可變的數據不用擔心多線程訪問時的競爭問題。例如,在使用std::async 進行異步任務時,可以傳遞函數式風格的函數對象。
- 機器學習:在機器學習領域,函數式編程可以用于數據預處理、模型訓練過程中的函數組合等場景。比如對數據集進行一系列的變換操作,可以通過組合不同的函數來實現。
八、總結
C++ 函數式編程為我們提供了一種強大且優雅的編程方式,無論是處理簡單邏輯還是復雜的應用場景,都能展現出其獨特的魅力。通過深入理解和應用這些概念,我們可以編寫出更高效、更易維護的代碼。
下一章節:
十五、C++速通秘籍—異常處理-CSDN博客
https://blog.csdn.net/weixin_36323170/article/details/147195953
