十四、C++速通秘籍—函數式編程

目錄

上一章節:

一、引言

一、函數式編程基礎

三、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; } 定義了升序排序的比較規則。

作用:

  1. 簡化程序結構,因為優化了函數命名與函數傳參;
  2. 提高程序運行效率,因為優化了函數調用、函數返回等消耗;
  3. 適用于簡單功能的函數優化;

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;
}

函數對象與普通函數對比:

  1. 函數對象比一般函數更靈活,因為它可以擁有狀態(state),事實上,對于相同的函數對象可以設置兩個狀態不同的實例;普通函數沒有狀態;
  2. 每個函數對象都有其類型,因為你可以將函數對象的類型當做template參數傳遞,從而指定某種行為;
  3. 執行速度上,函數對象通常比函數指針更快;

五、函數適配器

????????在編程中用于封裝和管理函數或可調用對象(如函數指針、函數對象、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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/79414.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/79414.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/79414.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

大模型Rag-指令調度

本文主要記錄根據用戶問題指令&#xff0c;基于大模型做Rag&#xff0c;匹配最相關描述集進行指令調度&#xff0c;可用于匹配后端接口以及展示答案及圖表等。 1.指令查詢處理邏輯 1.實現思路 指令識別&#xff1a;主要根據用戶的問題q計算與指令描述集is [i0, ... , im]和指…

音視頻學習 - ffmpeg 編譯與調試

編譯 環境 macOS Ventrua 13.4 ffmpeg 7.7.1 Visual Studio Code Version: 1.99.0 (Universal) 操作 FFmpeg 下載源碼 $ cd ffmpeg-x.y.z $ ./configure nasm/yasm not found or too old. Use --disable-x86asm for a crippled build.If you think configure made a mistake…

golang-常見的語法錯誤

https://juejin.cn/post/6923477800041054221 看這篇文章 Golang 基礎面試高頻題詳細解析【第一版】來啦&#xff5e; 大叔說碼 for-range的坑 func main() { slice : []int{0, 1, 2, 3} m : make(map[int]*int) for key, val : range slice {m[key] &val }for k, v : …

音視頻之H.265/HEVC預測編碼

H.265/HEVC系列文章&#xff1a; 1、音視頻之H.265/HEVC編碼框架及編碼視頻格式 2、音視頻之H.265碼流分析及解析 3、音視頻之H.265/HEVC預測編碼 預測編碼是視頻編碼中的核心技術之一。對于視頻信號來說&#xff0c;一幅圖像內鄰近像素之間有著較強的空間相關性,相鄰圖像之…

基于政務問答的dify接口請求測試

Dify 的智能體后端服務 API 為開發者提供便捷方式&#xff0c;能讓前端應用直接調用大語言模型能力。在請求時&#xff0c;需先前往應用左側導航的 “API Access” 部分&#xff0c;在此可查看文檔和管理訪問憑據。為保障安全&#xff0c;API 密鑰應通過后端調用&#xff0c;避…

VMware Workstation 保姆級 Linux(CentOS) 創建教程(附 iso)

文章目錄 一、下載二、創建 一、下載 CentOS-7.9-x86_64-DVD-2009.iso 二、創建 VMware Workstation 保姆級安裝教程(附安裝包) VMware Workstation 保姆級安裝教程(附安裝包) VMware Workstation 保姆級安裝教程(附安裝包)

擴增子分析|基于R語言microeco包進行微生物群落網絡分析(network網絡、Zi-Pi關鍵物種和subnet子網絡圖)

一、引言 microeco包是福建農林大學姚敏杰教授團隊開發的擴增子測序集成分析。該包綜合了擴增子測序下游分析的多種功能包括群落組成、多樣性、網絡分析、零模型等等。通過簡單的幾行代碼可實現復雜的分析。因此&#xff0c;microeco包發表以來被學界廣泛關注&#xff0c;截止2…

GO語言-數據類型

文章目錄 變量定義1. 整數類型2. 浮點類型3. 字符類型4. 布爾類型5. 字符串類型5.1 字符串的本質5.2 常用字符串處理函數(strings包)5.3 修改字符串的方式 6. 數據默認值7. 類型轉換 變量定義 代碼如下&#xff1a; package mainimport "fmt"var i1 1000 var i2 i…

線性代數 | 知識點整理 Ref 2

注&#xff1a;本文為 “線性代數 | 知識點整理” 相關文章合輯。 因 csdn 篇幅合并超限分篇連載&#xff0c;本篇為 Ref 2。 略作重排&#xff0c;未整理去重。 圖片清晰度限于引文原狀。 如有內容異常&#xff0c;請看原文。 【數學】線性代數知識點總結 阿巴 Jun 于 2024-…

JavaSE學習(前端初體驗)

文章目錄 前言一、準備環境二、創建站點&#xff08;創建一個文件夾&#xff09;三、將站點部署到編寫器中四、VScode實用小設置五、案例展示 前言 首先了解前端三件套&#xff1a;HTML、CSS、JS HTML&#xff1a;超文本標記語言、框架層、描述數據的&#xff1b; CSS&#xf…

java + spring boot + mybatis 通過時間段進行查詢

前端傳來的只有日期內容&#xff0c;如&#xff1a;2025-04-17 需要在日期內容的基礎上補充時間部分&#xff0c;代碼示例&#xff1a; /*** 日志查詢&#xff08;分頁查詢&#xff09;* param recordLogQueryDTO 查詢參數對象* return 日志列表*/Overridepublic PageBean<…

解決ubuntu自帶火狐瀏覽器無法播放視頻問題

TIPS:一般執行完1 就可以了 首先安裝必要的媒體編解碼器和插件&#xff1a; # 安裝常用媒體編解碼器和插件 sudo apt update sudo apt install -y ubuntu-restricted-extras# 安裝額外的編解碼器 sudo apt install -y ffmpeg# 安裝其他視頻相關包 sudo apt install -y libavc…

計算機網絡:流量控制與可靠傳輸機制

目錄 基本概念 流量控制&#xff1a;別噎著啦&#xff01; 可靠傳輸&#xff1a;快遞必達服務 傳輸差錯&#xff1a;現實中的意外 滑動窗口 基本概念 換句話說&#xff1a;批量發貨排隊驗收 停止-等待協議 SW&#xff08;發1份等1份&#xff09; 超時重傳&#xff1a;…

Android組件刷新

Android中刷新View的方法有以下幾種&#xff1a; 調用invalidate()方法&#xff0c;該方法會使View樹中的所有視圖無效或臟&#xff0c;等待下一次繪制時重新繪制。例如&#xff1a; mCustomView.invalidate(); 調用postInvalidate()方法&#xff0c;該方法類似于invalidate()…

Pycharm(十四)函數

一、函數概述 函數也叫方法,可以用function(函數,功能),method(方法)來表示。函數是把具有獨立功能的代碼封裝到一起,使其成為具有獨立功能的代碼集。 它的好處:1.提高代碼的復用性;2.模塊化編程。 1.1 定義格式 def 函數名(形式參數1,形式參數2...): 函數體,就是邏…

Oracle測試題目及筆記(多選)

所有題目來自于互聯網搜索 在以下概要文件的陳述中&#xff0c;哪兩個是正確的&#xff1f; &#xff08;D 和 E&#xff09; A&#xff0e; 概要文件不能被用來為賬戶加鎖 B&#xff0e; 概要文件不能被用來控制資源使用 C&#xff0e; 數據庫管理員可以使用概要文件更改用戶密…

DDoS攻防實戰指南——解析企業級防護五大解決方案

一、流量清洗中心的智能化演進 云清洗服務已從被動響應轉向主動防御。基于全球Anycast網絡的分布式清洗節點&#xff0c;可在攻擊發生時將流量牽引至專用清洗集群。阿里云2023年實測數據顯示&#xff0c;其新一代清洗設備對SYN Flood的識別準確率達99.97%&#xff0c;誤殺率控…

Ubuntu多用戶VNC遠程桌面環境搭建:從零開始的完整指南

引言: 在當今遠程工作盛行的時代,搭建一個安全、高效的多用戶遠程桌面環境變得越來越重要。本文將為您提供一個從零開始的完整指南,教您如何在Ubuntu系統上搭建多用戶VNC遠程桌面環境。無論您是系統管理員、開發團隊負責人,還是想要為家庭成員提供遠程訪問的技術愛好者,這…

數據結構專題 - 線性表

線性表是數據結構中最基礎、最常用的數據結構之一&#xff0c;它在實際應用中非常廣泛。無論是操作系統中的內存管理&#xff0c;還是數據庫中的索引結構&#xff0c;線性表都扮演著重要角色。 一、線性表的概念與抽象數據類型 1.1 線性表的邏輯結構 線性表是由n&#xff08…

使用wpa_cli和wpa_supplicant配置Liunx開發板的wlan0無線網

目錄 1 簡單介紹下wpa_cli和wpa_supplicant 1.1 wpa_supplicant 簡介 1.2 wpa_cli 簡介 1.3 它們之間的關系 2 啟動wpa_supplicant 3 使用rz工具把wpa_cli命令上傳到開發板 4 用wpa_cli配置網絡 參考文獻&#xff1a; 1 簡單介紹下wpa_cli和wpa_supplicant 1.1 wpa_su…