目录
- 11.1 函数和Lambdas表达式
- 11.1.1 函数作为谓词
- 11.1.2 Lambda函数
- 11.1.3 函子(functor)
- 11.2 算法\<algorithm>
- 11.3 Ranges&View
- 11.3.1 ranges(c++20)
- 11.3.2 views(c++20)
- 11.3.3 Ranges&Views
11.1 函数和Lambdas表达式
11.1.1 函数作为谓词
谓词是一个布尔值函数。
加上谓词的模板
// 一般情况template<typenameIt>Itfind(It first,It last,????pred){for(autoit=first;it!=last;++it){if(pred(*it))returnit;}returnlast;}// 加上谓词的模板// Pred:我们谓词的类型。编译器会通过隐式实例化帮我们弄清楚这一点!template<typenameIt,typename Pred>// find_if函数新名字,pred:我们的谓词,作为参数传递Itfind_if(It first,It last,Pred pred){for(autoit=first;it!=last;++it){// 我们正在对每个元素调用我们的谓词。一旦找到一个匹配的,我们就返回。if(pred(*it))returnit;}returnlast;}使用:
// 案例1boolisVowel(charc){c=toupper(c);returnc=='A'||c=='E'||c=='I’||c=='O'||c=='U’;}std::string corlys="Lord of the Tides";autoit=find_if(corlys.begin(),corlys.end(),isVowel);*it='0';// “L0rd of the Tides”// 案例2boolisPrime(size_t n){if(n<2)returnfalse;for(size_t i=3;i<=std::sqrt(n);i++)if(n%i==0)returnfalse;returntrue;}std::vector<int>ints={1,0,6};autoit=find_if(ints.begin(),ints.end(),isPrime);assert(it==ints.end());传递函数使我们能够用用户定义的行为来泛化算法。
Pred是一个函数指针。
find_if(corlys.begin(),corlys.end(),isVowel);// Pred = bool(*)(char) 函数返回布尔类型,函数指针,接受一个char类型作为参数find_if(ints.begin(),ints.end(),isPrime);// Pred = bool(*)(int)但是,函数指针的泛化性差:
boollessThan5(intx){returnx<5;}boollessThan6(intx){returnx<6;}boollessThan7(intx){returnx<7;}find_if(begin,end,lessThan5);find_if(begin,end,lessThan6);find_if(begin,end,lessThan7);11.1.2 Lambda函数
Lambda函数是从封闭作用域捕获状态的函数。
- 先明确两个关键概念
- Lambda 函数:本质是编程语言中一种匿名、轻量级的函数(没有正式函数名,代码简洁),常见于 Python、Java、C++ 等语言,多用于临时需要一个简单函数的场景(比如排序、过滤数据)。
- 封闭作用域:指定义 Lambda 函数的 “外部环境”—— 比如 Lambda 在一个普通函数内部定义,那么这个普通函数的作用域(包含其中的变量、参数等)就是 Lambda 的 “封闭作用域”。
- 核心:“捕获状态” 的含义
“捕获状态” 即 Lambda 函数能访问并使用封闭作用域中的变量 / 数据,而不是只能用自身参数或全局变量。 这是 Lambda 区别于 “纯局部函数” 的关键 —— 它像 “记住了自己诞生时的环境”,能把外部环境的 “状态”(变量值等)“带在身上” 使用。
intn;std::cin>>n;autolessThanN=[n](intx){returnx<n;};find_if(begin,end,lessThanN);autolambda=[capture-values](arguments){returnexpression;}[x](arguments)// captures x by value (makes a copy)[x&](arguments)// captures x by reference[x,y](arguments)// captures x, y by value[&](arguments)// captures everything by reference[&,x](arguments)// captures everything except x by reference[=](arguments)// captures everything by valueLambda函数也可以没有捕获参数。
std::stringcorlys="Lord of the tides";autoit=find_if(corlys.begin(),corlys.end(),[](autoc){c=toupper(c);returnc=='A'||c=='E'||c=='I'||c=='O'||c=='U’;});此时,编译器就会自动为auto生成模板
autolessThanN=[n](autox){returnx<n;};// 编译器转化为:template<typenameT>autolessThanN=[n](T x){returnx<n;};11.1.3 函子(functor)
定义:函子(functor)是任何定义了operator()运算符的对象。
实例:
template<typenameT>structstd::greater{booloperator()(constT&a,constT&b)const{returna>b;}};std::greater<int>g;g(1,2);// falsetemplate<>// 这是对MyType类型的模板特化,也是为自定义类型创建哈希函数的方法之一structstd::hash<MyType>{size_toperator()(constMyType&v)const{// Crazy, theoretically rigorous hash function// approved by 7 PhDs and Donald Knuth goes herereturn...;}};MyType m;std::hash<MyType>hash_fn;hash_fn(m);// 125123201 (for example)由于函子是对象,所以也有状态
structmy_functor{booloperator()(inta)const{returna*value;}// 状态int value;};my_functor f;f.value=5;f(10);// 50当使用Lambda函数时,函子类型就生成了
当使用范围for时,迭代器类型就生成了
可以将一段代码转换成背后更为详细的代码。
实用网站:cpp代码的背后
当使用范围for时,迭代器类型就生成了:
同理,当使用Lambda函数时,函子类型就生成了(语法糖罢了):
std::function 是函数 /lambda 表达式的一种通用类型
- 任何函数对象 /lambda 表达式 / 函数指针都可以被转换为该类型
- 它的速度会稍慢一些
- 我通常会使用 auto 模板,而不用担心类型问题!
std::function<bool(int,int)>less=std::less<int>{};std::function<bool(char)>vowel=isVowel;std::function<int(int)>twice=[](intx){returnx*2;};std::function<bool(int, int)> less = std::less<int>{}:用std::function存储标准库中的less仿函数(比较两个 int 大小)std::function<bool(char)> vowel = isVowel:存储自定义函数isVowel(判断字符是否为元音)std::function<int(int)> twice = [](int x) { return x * 2;}:存储 lambda 表达式(实现整数翻倍功能)
11.2 算法<algorithm>
<algorithm>是一组模板函数的集合
<algorithm>是 C++ 标准库的核心头文件之一,其核心作用是提供通用的算法工具集,这些工具本质上就是通过预设逻辑实现对数据的 “检查(inspect)” 与 “转换(transform)”,无需开发者重复编写底层逻辑。
11.3 Ranges&View
11.3.1 ranges(c++20)
范围(Ranges)是标准模板库(STL)的一个新版本。
范围:范围是任何具有起点和终点的事物。
intmain(){std::vector<char>v={'a','b','c','d','e’};autoit=std::ranges::find(v,'c');}intmain(){std::vector<char>v={'a','b','c','d','e'};// Search from 'b' to 'd’autofirst=v.begin()+1;autolast=v.end()-1;autoit=std::ranges::find(first,last,'c');}concept约束特性:
template<classT>// 通过 concept 定义,要求类型 T 必须有begin()和end()方法,即能获取起始和结束迭代器conceptrange=requires(T&t){ranges::begin(t);ranges::end(t);};template<classT>// 是一种特殊的范围,其迭代器必须满足输入迭代器(input iterator)的要求conceptinput_range=ranges::range<T>&&std::input_iterator<ranges::iterator_t<T>>;// 以find算法为例,它明确使用了input_range概念来约束参数,确保传入的范围符合算法的使用要求template<ranges::input_range R,classT,classProj=std::identity>borrowed_iterator_t<R>find(R&&r,constT&value,Proj proj={});11.3.2 views(c++20)
视图(Views):一种组合算法的方式
视图是一个范围,它延迟地适配另一个范围。
std::vector<char>v={'a','b','c','d','e'};// Filter -- Get only the vowelsstd::vector<char>f;std::copy_if(v.begin(),v.end(),std::back_inserter(f),isVowel);// Transform -- Convert to uppercasestd::vector<char>t;std::transform(f.begin(),f.end(),std::back_inserter(t),toupper);// { 'A', 'E' }/////////////////////////////////////////////// 用viewstd::vector<char>letters={'a','b','c','d','e'};autof=std::ranges::views::filter(letters,isVowel);//f 是一个视图!它接收一个底层范围 letters// 并生成一个只包含元音的新范围!autot=std::ranges::views::transform(f,toupper);//t 是一个视图!它接收一个底层范围 f// 并生成一个包含大写字符的新范围!autovowelUpper=std::ranges::to<std::vector<char>>(t);我们可以使用运算符 | 将视图链接在一起
std::vector<char>letters={'a','b','c','d','e'};std::vector<char>upperVowel=letters|std::ranges::views::filter(isVowel)|std::ranges::views::transform(toupper)|std::ranges::to<std::vector<char>>();// upperVowel = { 'A', 'E' }11.3.3 Ranges&Views
ranges是立即执行的!
// This actually sorts vec, RIGHT NOWWW!!!!std::ranges::sort(v);C++20 中引入的std::ranges::views的 “惰性 (lazy)” 特性:
- **惰性求值 (Lazy Evaluation)**:
std::ranges::views不会立即执行操作,而是在真正需要结果时才会计算。这与立即执行的算法形成对比,后者会马上处理数据并生成新容器。 - 代码解析:
letters | views::filter(isVowel) | views::transform(toupper)只是创建了一个 “视图”,定义了要执行的操作序列,但并未实际执行。- 只有当调用
std::ranges::to<std::vector<char>>(view)时,才会真正执行过滤和转换操作,生成包含大写元音字母的向量。
你可能会喜欢范围 / 视图的原因?
✅ 少担心迭代器
✅ 受约束的算法意味着更好的错误消息
✅ 超级易读的函数式语法
你可能会不喜欢范围 / 视图的原因?
❌ 它们非常新,尚未完全具备所有功能
❌ 缺乏编译器支持
❌ 与手工编写的版本相比性能有所下降