泛型算法
泛型算法基础知识
- 所在头文件
#include <algorithm> //基本算法
#include <numeric> //数值算法
#include <ranges> //C++20引入
- 方法和泛型算法同名时优先使用方法,泛型算法为满足通用性损失了一部分性能
- 迭代器作为不同数据类型沟通的桥梁 std::sort(std::begin(x), std::end(x))
泛函算法分类
- 读算法 给定迭代空间,读取其中元素并运算,不会修改迭代空间
T = std::accumulate(it1, it2, T init, Binary Operation op) //op二元运算符,默认为+,可lambda表达式
std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sum = std::accumulate(v.begin(), v.end(), 0);
int product = std::accumulate(v.begin(), v.end(), 1, std::multiplies<int>());
auto dash_fold = [](std::string a, int b) {
return std::move(a) + '-' + std::to_string(b);
};
std::string s = std::accumulate(std::next(v.begin()), v.end(),
std::to_string(v[0]), // start with first element
dash_fold);
// Right fold using reverse iterators
std::string rs = std::accumulate(std::next(v.rbegin()), v.rend(),
std::to_string(v.back()), // start with last element
dash_fold);
std::cout << "sum: " << sum << '\n'
<< "product: " << product << '\n'
<< "dash-separated string: " << s << '\n'
<< "dash-separated string (right-folded): " << rs << '\n';
It find(it1, it2, const T& value);
- 写算法
void fill(it1, it2, const T& value); 将多个元素赋值为value
void fill_n(it, size_t count, const T& value); 赋值n个value
- 读+写
outit transform(it1, it2, outit1, unary operation); //it1--it2 读区间 outit1 写入区间开头 outit指向转换后的最后一个元素
outit copy(it1, it2, outit1);
//删除重复元素
sort(it1, it2)
it unique(it1, it2)
//输入122334 转换后为123444
//输出it指向重复元素的开头 例如此时指向最后第二个4
迭代器分类
头文件 #include
- 输入迭代器,可读可递增 InputIt 典型应用find
- 输出迭代器,可写可递增 OutputIt 典型应用copy
- 前向迭代器,可读写可递增 ForwardIt 典型应用replace
- 双向迭代器,可读写可递增递减 BirdirIt 典型应用reverse
- 随机访问迭代器,可读写可增减一个整数 RandomIt 典型应用sort
- 特殊迭代器
- back_insert_ierator front_insert_iterator
#include <iterator> #include <deque> int main() { std::deque<int> a; std::back_insert_iterator<std::deque<int>> it(a); it = 1; it = 2; //调用push_back在结尾插入 std::fill_n(it, 10, 3); }
- istream_iterator ostream_iterator
#include <iterator> #include <sstream> std::istringstream str("1, 2, 3, 4"); std::istream_iterator<int> x(str); std::istream_iterator<int> y{}; //用于表示结尾 for(; x != y; ++x) { std::cout<<*x<<std::endl; //从第一个int开始寻找到下一个int,需手动++x;才后移 } std::copy(std::istream_iterator<int>(str), std::istream_iterator{}, std::ostream_iterator<int>(std::cout, " ")); //绑定的输出流和分割符
- 反向迭代器 rbegin rend
- move_iterator
#include <vector> #include <algorithm> #include <string> #include <iterator> int main() { std::vector<std::string> v{"this", "_", "is"}; std::string concat = std::accumulate(std::make_move_iterator(v.begin()), std::make_move_iterator(v.end()), std::string()); // concat this_is }
- 哨兵(sentinel)
并发算法C++17
#include <execution>
incline constexpr std::execution::sequenced_policy seq{}; //顺序执行
incline constexpr std::execution::parallel_policy par{}; //并行执行
incline constexpr std::execution::parallel_unsequenced_policy par_unseq{};
incline constexpr std::execution::unsequenced_policy unseq{};
应用
#include <execution>
std::vector<double> vals(10000);
for(auto& d : vals)
{
d = static_cast<double>(rd());
}
for(int i = 0; i < 5; i++)
{
using namespace std::chrono;
syd::vector<double> sorted(vals);
const auto startTime = high_resolution_clock::now();
std::sort(std::execution::par, sorted.begin(), sorted.end());
const auto endTime = high_resolution_clock::now();
std::cout << duration_cast<duration<double, std::milli>>(endTime - startTime).count() << std::endl;
}
bind
泛型算法允许通过可调用对象自定义计算逻辑细节如:
transform(InIt1, InIt2, Out1, unary operation op)
outIt copy_if(InIt1, InIt2, Out It, unary predicate pred)
sort(RandomIt1, RandomIt2, compare)
bool MyPredict(int val)
{
return val > 3;
}
int main()
{
std::vector<int> x{1, 2, 3, 4, 5, 6};
std::vector<int> y;
syd::copy_if(x.begin(), x.end(), std::back_inserter(y), MyPredict); //456
}
可调用对象:
- 函数指针:直观,但定义位置受限,不能在函数中定义函数
- 类:功能强大,但书写麻烦
- bind:逻辑复杂时难懂
- lambda:厉害
bind应用
bind1st bind2st
#include <functional>
#include <algorithm>
std::vector<int> x{1, 2, 3, 4, 5, 6};
std::copy_if(x.begin(), x.end(), std::back_inserter(y), std::bind1st(std::greater<int>(), 3));
//3绑定greater的第一个参数 复制后为12
std::copy_if(x.begin(), x.end(), std::back_inserter(y), std::bind1st(std::greater<int>(), 3));
//3绑定greater的第二个参数 复制后为456
bind
template <typename T>
bool Mypredict(T a, T b)
{
return a > b;
}
int main()
{
std::vector<int> x{1, 2, 3, 4, 5, 6};
std::vector<int> y;
using namespace std::placeholders;
std::copy_if(x.begin(), x.end(), std::back_inserter(y), std::bind(Mypredict, _1, 3)); //复制后为456
(_1,3)表示调用时Mypredict函数的参数列表 即Mypredict(each_x,3)
这里的_1中的1表示调用bind时传入的第一个参数
例如:auto x = std::bind(Mypredict, _2, _1);
x(3, 4) //true 3替换_1 4替换_2 Mypredict(4,3) 因此返回true
}
注意:构造bind时会将参数赋值
void proc(int& x)
{
++x;
}
int x = 0;
auto b = std::bind(proc, x); //传入的x会被复制一份再传入proc
b();
std::cout<<x; //还是0
如果实在想传入引用,可以auto b = std::bind(proc, std::ref(x)); 或cref(x)
lambda表达式
底层翻译成类来实现
定义
auto x = [](int val){return val>3;}; //自动推导出返回类型
x(5) //true
auto x = [](int val){return val*val;); -> double //显示指定返回类型
捕获
- 针对局部对象的捕获
int y = 10;
auto x = [y](int val){return val > y;};
- 局部静态对象无需捕获
static int y = 10;
auto x = [](int val){return val > y;};
- 值捕获
int y = 10;
auto x = [y](int val) mutable {++y; return val > y;};
x(5) //false
y //10没有改变
- 引用捕获
auto x = [&y](int val) {++y; return val > y;};
x(5) //false
y //11改变了
- 值和引用同时用
auto x = [&y, z](int val) {...};
- 自动值捕获
auto x = [=](int val) {...};
- 自动引用捕获
auto x = [&](int val) {...};
- 自动+值捕获
auto x = [&, y](int val) {...};
- *this捕获
struct str
{
auto fun()
{
int val = 3;
auto lam = [val, *this]() //使用*this会将对象内容保存至lam内部
{
return val > x;
};
return lam();
}
int x;
};
- 初始化捕获 C++14
int x = 3;
auto lam = [y = x](int val)
{
return val > y;
};
std::string a = "hello";
auto lam = [y = std::move(a)]()
{
std::cout << y;
};
lambda其他基础知识
- mutable说明符
默认为const,不能改变‘类’内的值
int y = 3;
auto lam = [](int val) constexpr
{
++y;
return val > y;
};
- constexpr说明符 C++17
auto lam = [](int val) constexpr
{
return val + 1;
}
constexpr int val = lam(100);
- 带模板参数 C++20
auto lam = []<typename T>(T val)
{
return val + 1;
};
lambda高级使用
- IIFE 立即调用函数表达式
const auto val = [z = x + y](){return z;}();
- C++14 自动引入模板
auto lam = [](auto x){return x + 1;};
- 递归调用
普通函数为什么能递归调用:由于函数对象在执行到函数体之前就已经建立好了,因此不会报错。但lambda表达式不同,其还没有构造好。
auto factorial = [](int n)
{
auto f_imp1 = [](int n, const auto& imp1) ->int
{
return n > 1 ? n*imp1(n-1, imp1) : 1;
};
return f_imp1(n, f_imp1);
};
为什么可以?
factorial内部没有factorial,f_imp1内部没有f_imp1
f_imp1定义时利用C++14中的const auto&定义了一个模板,在调用f_impl时实例化
不指定->int会报错,因为不知道f_imp1返回值类型(用了auto,需要从return语句来推断),而return语句中有imp1,就得知道imp1的类型,要知道imp1类型就得知道f_imp1类型,反反复复,最终还是不知道,报错。