C++ 11 新容器和新算法

目录

新容器

forward_list

Abstract

How

Demo

array

Abstract

Comparewith vector

Compare with original array

How

Demo

tuple

Abstract

How

无序容器

Abstract

Demo

新算法

Abstract

Demo


新容器

forward_list

Abstract

Forward lists are sequence containers that allow constant time insert and erase operations anywhere within the sequence.

Forward lists are implemented as singly-linked lists; Singly linked lists can store each of the elements they contain in different and unrelated storage locations. The ordering is kept by the association to each element of a link to the next element in the sequence.

前向列表是序列容器,允许在序列的任何地方进行常数时间的插入和删除操作。

转发列表实现为单链表;单链表可以将它们包含的每个元素存储在不同且不相关的存储位置。顺序是通过链接到序列中的下一个元素的每个元素的关联来保持的。

forward_list容器和list容器在设计上的主要区别是,前者在内部只保留到下一个元素的链接,而后者为每个元素保留两个链接:一个指向下一个元素,一个指向上一个元素,允许在两个方向上高效迭代,但每个元素消耗额外的存储空间,插入和删除元素的时间开销略高。因此,Forward_list对象比list对象更有效,尽管它们只能向前迭代。

How

head file

#include <forward_list>
using namespace std;

member funtion

before_begin() 返回一个前向迭代器,其指向容器中第一个元素之前的位置。

begin() 返回一个前向迭代器,其指向容器中第一个元素的位置。

end() 返回一个前向迭代器,其指向容器中最后一个元素之后的位置。

cbefore_begin() 和 before_begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。

cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。

cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。

empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。

max_size() 返回容器所能包含元素个数的最大值。这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数。

front() 返回第一个元素的引用。

assign() 用新元素替换容器中原有内容。

push_front() 在容器头部插入一个元素。

emplace_front() 在容器头部生成一个元素。该函数和 push_front() 的功能相同,但效率更高。

pop_front() 删除容器头部的一个元素。

emplace_after() 在指定位置之后插入一个新元素,并返回一个指向新元素的迭代器。和 insert_after() 的功能相同,但效率更高。

insert_after() 在指定位置之后插入一个新元素,并返回一个指向新元素的迭代器。

erase_after() 删除容器中某个指定位置或区域内的所有元素。

swap() 交换两个容器中的元素,必须保证这两个容器中存储的元素类型是相同的。

resize() 调整容器的大小。

clear() 删除容器存储的所有元素。

splice_after() 将某个 forward_list 容器中指定位置或区域内的元素插入到另一个容器的指定位置之后。

remove(val) 删除容器中所有等于 val 的元素。

remove_if() 删除容器中满足条件的元素。

unique() 删除容器中相邻的重复元素,只保留一个。

merge() 合并两个事先已排好序的 forward_list 容器,并且合并之后的 forward_list 容器依然是有序的。

sort() 通过更改容器中元素的位置,将它们进行排序。

reverse() 反转容器中元素的顺序。

notice

forward_list 容器中是不提供 size() 函数的,但如果想要获取 forward_list 容器中存储元素的个数,可以使用头文件 中的 distance() 函数

#include<iostream>
#include<vector>
#include<cstdio>
#include<string>
#include <forward_list>
#include <iterator>

using namespace std;
int main() {
    forward_list<int> my_words{1, 2, 3, 5};
    int count = distance(my_words.begin(), my_words.end());
    cout << count << endl;    
  return 0;
}

forward_list 容器迭代器的移动除了使用 ++ 运算符单步移动,还能使用 advance() 函数

#include <iostream>
#include <forward_list>
using namespace std;
int main()
{
    std::forward_list<int> values{1,2,3,4};
    auto it = values.begin();
    advance(it, 2);
    while (it!=values.end())
    {
        cout << *it << " ";
        ++it;
    }
    return 0;
}

Demo

// forward_list::emplace_front
#include <iostream>
#include <forward_list>

int main ()
{
  std::forward_list< std::pair<int,char> > mylist;

  mylist.emplace_front(10,'a');
  mylist.emplace_front(20,'b');
  mylist.emplace_front(30,'c');

  std::cout << "mylist contains:";
  for (auto& x: mylist)
    std::cout << " (" << x.first << "," << x.second << ")";

  std::cout << std::endl;
  return 0;
}

array

Abstract

Arrays are fixed-size sequence containers: they hold a specific number of elements ordered in a strict linear sequence.

Internally, an array does not keep any data other than the elements it contains (not even its size, which is a template parameter, fixed on compile time). It is as efficient in terms of storage size as an ordinary array declared with the language's bracket syntax ([]). This class merely adds a layer of member and global functions to it, so that arrays can be used as standard containers.

Unlike the other standard containers, arrays have a fixed size and do not manage the allocation of its elements through an allocator: they are an aggregate type encapsulating a fixed-size array of elements. Therefore, they cannot be expanded or contracted dynamically (see vector for a similar container that can be expanded).

Zero-sized arrays are valid, but they should not be dereferenced (members front, back, and data).

Unlike with the other containers in the Standard Library, swapping two array containers is a linear operation that involves swapping all the elements in the ranges individually, which generally is a considerably less efficient operation. On the other side, this allows the iterators to elements in both containers to keep their original container association.

Another unique feature of array containers is that they can be treated as tuple objects: The <array> header overloads the get function to access the elements of the array as if it was a tuple, as well as specialized tuple_size and tuple_element types.


数组是固定大小的序列容器:它们以严格的线性序列保存特定数量的元素。

在内部,数组不保留它所包含的元素以外的任何数据(甚至不保留它的大小,这是一个模板参数,在编译时固定)。就存储大小而言,它与使用语言的方括号语法([])声明的普通数组一样有效。这个类只添加了一层成员和全局函数,这样数组就可以用作标准容器。

与其他标准容器不同,数组具有固定大小,不通过分配器管理其元素的分配:它们是封装了固定大小的元素数组的聚合类型。因此,它们不能动态地展开或收缩(参见vector获取可展开的类似容器)。

零大小的数组是有效的,但是不应该解除对它们的引用(成员前面、后面和数据)。

与标准库中的其他容器不同,交换两个数组容器是一种线性操作,需要分别交换范围内的所有元素,这种操作的效率通常要低得多。另一方面,这允许两个容器中的元素的迭代器保持它们原来的容器关联关系。

数组容器的另一个独特特性是它们可以被视为元组对象:Header重载get函数来访问数组的元素,就像它是一个元组一样,以及专门的tuple_size和tuple_element类型。

Comparewith vector

std::vector 不同,std::array 对象的大小是固定的,如果容器大小是固定的,那么可以优先考虑使用 std::array 容器。 另外由于 std::vector 是自动扩容的,当存入大量的数据后,并且对容器进行了删除操作, 容器并不会自动归还被删除元素相应的内存,这时候就需要手动运行 shrink_to_fit() 释放这部分内存。

std::vector<int> v;
std::cout << "size:" << v.size() << std::endl;         // 输出 0
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 0

// 如下可看出 std::vector 的存储是自动管理的,按需自动扩张
// 但是如果空间不足,需要重新分配更多内存,而重分配内存通常是性能上有开销的操作
v.push_back(1);
v.push_back(2);
v.push_back(3);
std::cout << "size:" << v.size() << std::endl;         // 输出 3
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 4

// 这里的自动扩张逻辑与 Golang 的 slice 很像
v.push_back(4);
v.push_back(5);
std::cout << "size:" << v.size() << std::endl;         // 输出 5
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 8

// 如下可看出容器虽然清空了元素,但是被清空元素的内存并没有归还
v.clear();                                             
std::cout << "size:" << v.size() << std::endl;         // 输出 0
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 8

// 额外内存可通过 shrink_to_fit() 调用返回给系统
v.shrink_to_fit();
std::cout << "size:" << v.size() << std::endl;         // 输出 0
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 0

Compare with original array

使用 std::array 能够让代码变得更加“现代化”,而且封装了一些操作函数,比如获取数组大小以及检查是否非空,同时还能够友好的使用标准库中的容器算法,比如 std::sort。

How

使用 std::array 很简单,只需指定其类型和大小即可,下面主要讲如何适配原生的数组接口

std::array<int, 4> arr = {1, 2, 3, 4};

arr.empty(); // 检查容器是否为空
arr.size();  // 返回容纳的元素数

// 迭代器支持
for (auto &i : arr)
{
    // ...
}

// 用 lambda 表达式排序
std::sort(arr.begin(), arr.end(), [](int a, int b) {
    return b < a;
});

// 数组大小参数必须是常量表达式
constexpr int len = 4;
std::array<int, len> arr = {1, 2, 3, 4};
void foo(int *p, int len) {
    return;
}

std::array<int, 4> arr = {1,2,3,4};

// C 风格接口传参
// foo(arr, arr.size()); // 非法, 无法隐式转换
foo(&arr[0], arr.size());
foo(arr.data(), arr.size());

// 使用 `std::sort`
std::sort(arr.begin(), arr.end());

Demo

// arrays as tuples
#include <iostream>
#include <array>
#include <tuple>

int main ()
{
  std::array<int,3> myarray = {10, 20, 30};
  std::tuple<int,int,int> mytuple (10, 20, 30);

  std::tuple_element<0,decltype(myarray)>::type myelement;  // int myelement

  myelement = std::get<2>(myarray);
  std::get<2>(myarray) = std::get<0>(myarray);
  std::get<0>(myarray) = myelement;

  std::cout << "first element in myarray: " << std::get<0>(myarray) << "\n";
  std::cout << "first element in mytuple: " << std::get<0>(mytuple) << "\n";

  return 0;
}

tuple

Abstract

Class template std::tuple is a fixed-size collection of heterogeneous values. It is a generalization of std::pair.

类模板std::tuple是一个固定大小的异构值集合。它是std::pair的一种加强版本,std::pair 的缺陷是显而易见的,只能保存两个元素

How

关于元组的使用有三个核心的函数:

  1. std::make_tuple: 构造元组
  2. std::get: 获得元组某个位置的值
  3. std::tie: 元组拆包
#include <tuple>
#include <iostream>

auto get_student(int id)
{
    // 返回类型被推断为 std::tuple<double, char, std::string>

    if (id == 0)
        return std::make_tuple(3.8, 'A', "张三");
    if (id == 1)
        return std::make_tuple(2.9, 'C', "李四");
    if (id == 2)
        return std::make_tuple(1.7, 'D', "王五");
    return std::make_tuple(0.0, 'D', "null");
    // 如果只写 0 会出现推断错误, 编译失败
}

int main()
{
    auto student = get_student(0);
    std::cout << "ID: 0, "
    << "GPA: " << std::get<0>(student) << ", "
    << "成绩: " << std::get<1>(student) << ", "
    << "姓名: " << std::get<2>(student) << '\n';

    double gpa;
    char grade;
    std::string name;

    // 元组进行拆包
    std::tie(gpa, grade, name) = get_student(1);
    std::cout << "ID: 1, "
    << "GPA: " << gpa << ", "
    << "成绩: " << grade << ", "
    << "姓名: " << name << '\n';
}

std::get 除了使用常量获取元组对象外,C++14 增加了使用类型来获取元组中的对象

std::tuple<std::string, double, double, int> t("123", 4.5, 6.7, 8);
std::cout << std::get<std::string>(t) << std::endl;
std::cout << std::get<double>(t) << std::endl; // 非法, 引发编译期错误,弱国tuple中只有一个double就可以
std::cout << std::get<3>(t) << std::endl;

获取长度

template <typename T>
auto tuple_len(T &tpl) {
    return std::tuple_size<T>::value;
}

无序容器

Abstract

std::map/std::set,这些元素内部通过红黑树进行实现, 插入和搜索的平均复杂度均为 O(log(size))。在插入元素时候,会根据 < 操作符比较元素大小并判断元素是否相同, 并选择合适的位置插入到容器中。当对这个容器中的元素进行遍历时,输出结果会按照 < 操作符的顺序来逐个遍历。

而无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(constant), 在不关心容器内部元素顺序时,能够获得显著的性能提升。

但是hash表的建立相对比较耗时(需要解决hash冲突),因此插入的效率稍显逊色;

C++11 引入了的两组无序容器分别是:std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。

它们的用法和原有的 std::map/std::multimap/std::set/set::multiset 基本类似。

因此在容器中元素比较少,并且查询要求比较多的场景中,无序容器有更好的性能。

Demo

#include <iostream>
#include <string>
#include <unordered_map>
#include <map>

int main() {
    // 两组结构按同样的顺序初始化
    std::unordered_map<int, std::string> u = {
        {1, "1"},
        {3, "3"},
        {2, "2"}
    };
    std::map<int, std::string> v = {
        {1, "1"},
        {3, "3"},
        {2, "2"}
    };

    // 分别对两组结构进行遍历
    std::cout << "std::unordered_map" << std::endl;
    for( const auto & n : u)
        std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n";

    std::cout << std::endl;
    std::cout << "std::map" << std::endl;
    for( const auto & n : v)
        std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n";
}

新算法

Abstract

all_of:检测表达式是否对范围[first, last)中所有元素都返回true,如果都满足,则返回true

any_of:检测表达式是否对范围[first, last)中至少一个元素返回true,如果满足,则返回true

none_of:检测表达式是否对范围[first, last)中所有元素都不返回true,如果都不满足,则返回true,否则返回false,用法和上面一样

find_if_not:找到第一个不符合要求的元素迭代器,和find_if相反
copy_if:复制满足条件的元素

minmax_element:返回容器内最大元素和最小元素位置

is_sorted、is_sorted_until:返回容器内元素是否已经排好序。

itoa:对容器内的元素按序递增

Demo

std::vector<int> l(10);
std::iota(l.begin(), l.end(), 19); // 19为初始值
for (auto n : l) std::cout << n << ' ';
// 19 20 21 22 23 24 25 26 27 28

int main() {
    std::vector<int> v = {3, 9, 1, 4, 2, 5, 9};

    auto result = std::minmax_element(v.begin(), v.end());
    std::cout << "min element at: " << *(result.first) << '\n';
    std::cout << "max element at: " << *(result.second) << '\n';
    return 0;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ym影子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值