记录篇-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 – 返回用于比较键的相等性的函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值