c++11的特性-std新增功能
前言
本文是记录个人对c++11新特性的学习,文中所用可能会采用其他大神的图、思路、例子等,请大神们见谅。本博客文章是在学习过程的一些总结,整理出来,分享给大家,希望对各位读者有帮助,文章中的总结可能存在很多不完整或有错误的地方,也希望读者指出。
1、std::funtion和bind绑定器
首先介绍一下可调用对象的几种定义:函数指针、具有operator()成员函数的类的对象、可被转换成函数指针的类对象、类成员函数指针。
1.1、std::function
1)是一个可调用对象包装器,是一个类模板,可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。
2)定义格式:std::function<函数类型>。
3)可以取代函数指针的作用,因为它可以延迟函数的执行,特别适合作为回调函数使用。它比普通函数指针更加的灵活和便利
// 普通函数
int add(int a, int b){return a+b;}
// lambda表达式
auto mod = [](int a, int b){ return a % b;}
// 函数对象类
struct divide{
int operator()(int denominator, int divisor){
return denominator/divisor;
}
};
//共享了int(int ,int)形式,所以可以通过std::function 写成如下格式
std::function<int(int ,int)> a = add;
std::function<int(int ,int)> b = mod ;
std::function<int(int ,int)> c = divide();
#include <functional>
#include <iostream>
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_+i << '\n'; }
int num_;
};
void print_num(int i)
{
std::cout << i << '\n';
}
struct PrintNum {
void operator()(int i) const
{
std::cout << i << '\n';
}
};
int main()
{
// 存储自由函数
std::function<void(int)> f_display = print_num;
f_display(-9); //输出:-9
// 存储lambda表达式
std::function<void()> f_display_42 = []() { print_num(42); };
f_display_42(); //输出:42
// 存储std::bind绑定的对象
std::function<void()> f_display_31337 = std::bind(print_num, 31337);
f_display_31337();//输出:31337
// 存储类成员函数
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
const Foo foo(314159);
f_add_display(foo, 1);//输出:314160
f_add_display(314159, 1);//输出:314160
// 存储类数据成员访问器的调用
std::function<int(Foo const&)> f_num = &Foo::num_;
std::cout << "num_: " << f_num(foo) << '\n';//num_: 314159
// 存储成员函数及对象的调用
using std::placeholders::_1;
std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 );
f_add_display2(2);//输出:314161
// 存储对成员函数和对象指针的调用
std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
f_add_display3(3);//输出:314162
// 存储对仿函数的调用
std::function<void(int)> f_display_obj = PrintNum();
f_display_obj(18);//输出:18
}
1.2、std::bind
1)是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
2)将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。
std::bind主要有以下两个作用:
将可调用对象和其参数绑定成一个防函数;
只绑定部分参数,减少可调用对象传入的参数
1)绑定普通函数
double my_divide (double x, double y) {return x/y;}
//bind的第一个参数是函数名,普通函数做实参时,会隐式转换成函数指针。
//因此std::bind (my_divide,_1,2)等价于std::bind (&my_divide,_1,2);
//_1表示占位符,位于中,std::placeholders::_1
auto fn_half = std::bind (my_divide,_1,2);
std::cout << fn_half(10) << '\n';
2)绑定类成员函数
class Foo {
void print_sum(int n1, int n2)
{
std::cout << n1+n2 << '\n';
}
int data = 10;
};
int main()
{
/*
bind绑定类成员函数时,第一个参数表示对象的成员函数的指针,第二个参数表示对象的地址。
必须显示的指定&Foo::print_sum,因为编译器不会将对象的成员函数隐式转换成函数指针,所以必须在Foo::print_sum前添加&;
使用对象成员函数的指针时,必须要知道该指针属于哪个对象,因此第二个参数为对象的地址 &foo
*/
Foo foo;
auto f = std::bind(&Foo::print_sum, &foo, 95, std::placeholders::_1);
f(5); // 100
}
3)参数绑定
格式:auto newCallable=bind(callable,arg_list);
bind的第一个参数为一个可调用对象,
arg_list是调用对象的参数列表,可以包含 _ 1, _ 2等这样的占位符,用于占据调用对象的参数位置
#include<bits/stdc++.h>
using namespace std;
using namespace placeholders;
int sum(int a, int b, int c)
{
if (a > b)return a + c;
return b + c;
}
int main(void)
{
//包含占位符, 也可以调换参数顺序
auto add = bind(sum, _1, _2, 10);
auto add2 = bind(sum, _2, _1, 10);
int t = add(20, 10), t1 = add2(10, 20);
cout << t << " " << t1 << endl;
return 0;
}
4)指向成员函数的指针
#include <iostream>
struct Foo {
int value;
void f() { std::cout << "f(" << this->value << ")\n"; }
void g() { std::cout << "g(" << this->value << ")\n"; }
};
void apply(Foo* foo1, Foo* foo2, void (Foo::*fun)()) {
(foo1->*fun)(); // call fun on the object foo1
(foo2->*fun)(); // call fun on the object foo2
}
/*
成员函数指针的定义:void (Foo::*fun)(),调用是传递的实参: &Foo::f;
fun为类成员函数指针,所以调用是要通过解引用的方式获取成员函数*fun,即(foo1->*fun)();
*/
int main() {
Foo foo1{1};
Foo foo2{2};
apply(&foo1, &foo2, &Foo::f);
apply(&foo1, &foo2, &Foo::g);
}
1.3、function和bind使用示例
#include <iostream>
#include <functional>
void test1(){std::cout<<"function"<<std::endl;}
int test2(int i){ return i; }
int test3(int i, int j){ return i+j; }
struct A{
void foo(int i){ std::cout<<i<<std::endl; }
};
int main() {
std::function<void()> fn1 = std::bind(test1);
std::function<int(int)> fn2 = std::bind(test2, std::placeholders::_1);
std::function<int(int, int)> fn3 = std::bind(test3, std::placeholders::_1, std::placeholders::_2);
std::function<int(int)> fn4 = std::bind(test3, 3, std::placeholders::_1);
std::function<int()> fn5 = std::bind(test3, 3, 4);
A a;
std::function<void(int)> fn6 = std::bind(&A::foo, &a, std::placeholders::_1);
fn1(); //输出function
std::cout<<fn2(1)<<std::endl; //输出1
std::cout<<fn3(2, 3)<<std::endl;//输出5
std::cout<<fn4(3)<<std::endl; //输出6
std::cout<<fn5()<<std::endl; //输出7
fn6(8); //输出8
}
2、std::array
是具有固定大小的数组。并不支持添加或删除元素等改变大小的操作。也就是说,当定义一个array时,除了指定元素类型,还要指定容器大小
优势:
1)除了有内置数组支持随机访问、效率高、存储大小固定等特点外,
2)支持迭代器访问、获取容量、获得原始指针等高级功能
3)不会退化成指针给开发人员造成困惑
2.1、使用细节
1)包含头文件:# include
2)指定其数据类型和大小,两者不可或缺。且array的大小不能使用变量来指定(内置数组可以)。
3)可以使用{}来直接初始化,也可以使用另外的array来构造,但不可以使用内置数组来构造
# include <iostream>
# include <array>
int main(int argc, char const *argv[])
{
std::array<int, 5> a0 = {0, 1, 2, 3, 4}; //正确
std::array<int, 5> a1 = a0; //正确
int m = 5;
int b[m]; //正确,内置数组
std::array<int, 5> a2; //正确
std::array<int, m> a3; //错误,array不可以用变量指定
std::array<int, 5> a4 = b; //错误,array不可以用数组指定
return 0;
}
2.2、接口函数
访问函数 | 作用描述 |
---|---|
at | 访问指定的元素,同时进行越界检查 |
[] | 访问指定的元素(下标访问) |
front | 访问第一个元素 |
front | 访问最后一个元素 |
data | 返回指向内存中数组第一个元素的指针 |
empty | 检查容器是否为空 |
size | 返回容纳的元素数 |
max_size | 返回可容纳的最大元素数 |
fill | 以指定值填充容器 |
swap | 交换内容 |
迭代器相关函数
迭代器函数名 | 作用描述 |
---|---|
begin | 返回指向容器第一个元素的迭代器 |
end | 返回指向容器尾端的迭代器 |
rbegin | 返回指向容器最后元素的逆向迭代器 |
rend | 返回指向前端的逆向迭代器 |
2.3、std其它函数
1)std::sort 排序函数
2)std::reverse 反转函数
3)std::reverse_copy 反转拷贝函数
3、std::forward_list
单向链表或叫正向列表
优势:
1)具有插入、删除表项速度快、消耗内存空间少的特点,但只能向前遍历。与其它序列容器(array、vector、deque)相比,forward_list在容器内任意位置的成员的插入、提取(extracting)、移动、删除操作的速度更快,因此被广泛用于排序算法。
2)当不需要双向迭代的时候,与std::list相比,该容器具有更高的空间利用率
3) 插入和移除元素,不会导致指向其他元素的pointer、reference 和 iterator 失效
缺点:
1)是不能在随机访问任意成员(每个元素保存了定位前一个元素及后一个元素的信息)
2)没有size()函数。获取个数需要用std::distance(_begin, _end)算法。
4、std::unordered_map
1)unordered_map 是关联容器,含有带唯一键的键-值 pair 。搜索、插入和元素移除拥有平均常数时间复杂度
2)元素在内部不以任何特定顺序排序,而是组织进桶中。元素放进哪个桶完全依赖于其键的哈希。这允许对单独元素的快速访问,因为一旦计算哈希,则它准确指代元素所放进的桶。
3)std::unordered_map 满足容器 (Container) 、知分配器容器 (AllocatorAwareContainer) 、无序关联容器 (UnorderedAssociativeContainer) 的要求
4)本质区别在于std::map底层使用红黑树,而std::unordered_map使用的是hash map
关于unordered_map和map的使用场景:
查找速度:unordered_map底层是一个hash _ table,一般来说hash函数,hash冲突解决的好,可以达到O(1);map底层是一棵红黑树,效率大概是O(logn)
内存问题,unordered_map是利用了空间换时间,哈希表的元素越少,hash冲突可能性也就越少,效-也就越接近于O(1),但是这样一来它的空间利用率也越小;所以哈希表的装填因子大概在0.7-0.8。
成员函数 | 函数说明 |
---|---|
构造函数 | 构造 unordered_map |
析构函数 | 析构 unordered_map |
operator= | 赋值给容器 |
get_allocator | 返回相关的分配器 |
迭代器相关 | 函数说明 |
---|---|
begincbegin | 返回指向起始的迭代器 |
endcend | 返回指向末尾的迭代器 |
容量相关 | 函数说明 |
---|---|
empty | 检查容器是否为空 |
size | 返回容纳的元素数 |
max_size | 返回可容纳的最大元素数 |
修改器 | 函数说明 |
---|---|
clear | 清除内容 |
emplace | 原位构造元素 |
emplace_hint | 使用提示原位构造元素 |
erase | 擦除元素 |
swap | 交换内容 |
insert | 插入元素或结点 (C++17 起) |
extract(C++17) | 从另一容器释出结点 |
merge(C++17) | 从另一容器接合结点 |
try_emplace(C++17) | 若键不存在则原位插入,若键存在则不做任何事 |
insert_or_assign(C++17) | 插入元素,或若键已存在则赋值给当前元素 |
查找 | 函数说明 |
---|---|
at | 访问指定的元素,同时进行越界检查 |
operator[] | 访问或插入指定的元素 |
count | 返回匹配特定键的元素数量 |
find | 寻找带有特定键的元素 |
equal_range | 返回匹配特定键的元素范围 |
contains(C++20) | 检查容器是否含有带特定键的元素 |
桶接口 | 函数说明 |
---|---|
begin(size_type)cbegin(size_type) | 返回一个迭代器,指向指定的桶的开始 |
end(size_type)cend(size_type) | 返回一个迭代器,指向指定的桶的末尾 |
bucket_count | 返回桶数 |
max_bucket_count | 返回桶的最大数量 |
bucket_size | 返回在特定的桶中的元素数量 |
bucket | 返回带有特定键的桶 |
哈希策略 | 函数说明 |
---|---|
load_factor | 返回每个桶的平均元素数量 |
max_load_factor | 管理每个桶的平均元素数量的最大值 |
rehash | 至少为指定数量的桶预留存储空间。这会重新生成哈希表 |
reserve | 为至少为指定数量的元素预留存储空间。这会重新生成哈希表 |
观察器 | 函数说明 |
---|---|
hash_function | 返回用于对关键哈希的函数 |
key_eql | 返回用于比较键的相等性的函数 |
5、std::unordered_set
1)无序集合是不按特定顺序存储唯一元素的容器,并可以根据其值快速检索单个元素。
2)同set一样,key就是元素的值,而且类型为const,不可在容器中修改,只支持添加和删除。
3)在内部,unordered_set中的元素未按任何特定顺序排序,而是根据其哈希值组织到存储桶中,以允许直接通过其值快速访问各个元素(平均平均时间复杂度恒定)。
4)尽管unordered_set容器通过其键访问单个元素的速度要比set容器快,但是通常通过它们的元素子集进行范围迭代的效率较低
5)std::set底层是红黑树,std::unordered_set底层是hash表
成员函数:
构造函数 – 构造 unordered_set
析构函数 – 析构 unordered_set
operator= – 赋值给容器
get_allocator – 返回相关的分配器
迭代器:
begin/cbegin – 返回指向起始的迭代器
end/cend – 返回指向末尾的迭代器
容量:
empty – 检查容器是否为空
size – 返回容纳的元素数
max_size – 返回可容纳的最大元素数
修改器:
clear – 清除内容
insert – 插入元素或结点 (C++17 起)
emplace – 原位构造元素
emplace_hint – 使用提示原位构造元素
erase – 擦除元素
swap – 交换内容
extract(C++17) – 从另一容器释出结点
merge(C++17) – 从另一容器接合结点
查找:
count – 返回匹配特定键的元素数量
find – 寻找带有特定键的元素
contains(C++20) – 检查容器是否含有带特定键的元素
equal_range – 返回匹配特定键的元素范围
桶接口:
begin(size_type)/cbegin(size_type) – 返回一个迭代器,指向指定的桶的开始
end(size_type)/cend(size_type) – 返回一个迭代器,指向指定的桶的末尾
bucket_count – 返回桶数
max_bucket_count – 返回桶的最大数量
bucket_size – 返回在特定的桶中的元素数量
bucket – 返回带有特定键的桶
哈希策略
load_factor – 返回每个桶的平均元素数量
max_load_factor – 管理每个桶的平均元素数量的最大值
rehash – 为至少为指定数量的桶预留存储空间。这会重新生成哈希表。
reserve – 为至少为指定数量的元素预留存储空间。这会重新生成哈希表。
观察器
hash_function – 返回用于对关键哈希的函数
key_eq – 返回用于比较键的相等性的函数