网站平台做推广方案,同一个服务器可以做多个网站,网页设计作品,2022年今天新闻联播1.1 线性容器1#xff09;std::array看到这个容器的时候肯定会出现这样的问题#xff1a;为什么要引入 std::array 而不是直接使用 std::vector#xff1f;已经有了传统数组#xff0c;为什么要用 std::array?先回答第一个问题#xff0c;与 std::vector 不同#xff0c…1.1 线性容器1std::array看到这个容器的时候肯定会出现这样的问题为什么要引入 std::array 而不是直接使用 std::vector已经有了传统数组为什么要用 std::array?先回答第一个问题与 std::vector 不同std::array 对象的大小是固定的如果容器大小是固定的那么可以优先考虑使用 std::array 容器。 另外由于 std::vector 是自动扩容的当存入大量的数据后并且对容器进行了删除操作 容器并不会自动归还被删除元素相应的内存这时候就需要手动运行 shrink_to_fit() 释放这部分内存。std::vectorint 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而第二个问题就更加简单使用 std::array 能够让代码变得更加“现代化”而且封装了一些操作函数比如获取数组大小以及检查是否非空同时还能够友好的使用标准库中的容器算法比如 std::sort。使用 std::array 很简单只需指定其类型和大小即可#include iostream
#include array
#include algorithm // std::sortusing namespace std;// int *arr_p arr;
//当我们开始用上了 std::array 时难免会遇到要将其兼容 C 风格的接口这里有三种做法
void foo(int *p, int len) {return;
}int main() {// 数组大小参数必须是常量表达式constexpr int len 4;std::arrayint, len arr {1, 3, 2, 4};cout arr is empty[1] or not empty[0] ? arr.empty() endl; // 检查容器是否为空cout arrs size is: arr.size() endl; // 返回容纳的元素数// 迭代器支持cout The original arr is: endl;for (auto i : arr) {cout i ;}// 用 lambda 表达式排序std::sort(arr.begin(), arr.end(), [](int a, int b) {return b a;});// 非法,不同于 C 风格数组std::array 不会自动退化成 T*// std::arrayint, 4 arr {1,2,3,4};// C 风格接口传参// foo(arr, arr.size()); // 非法, 无法隐式转换foo(arr[0], arr.size());foo(arr.data(), arr.size());// 使用 std::sortstd::sort(arr.begin(), arr.end());cout \n;cout The sorted arr is: endl;for (auto i : arr) {cout i ;}}2std::forward_liststd::forward_list 是一个列表容器使用方法和 std::list 基本类似因此我们就不花费篇幅进行介绍了。需要知道的是和 std::list 的双向链表的实现不同std::forward_list 使用单向链表进行实现 提供了 O(1) 复杂度的元素插入不支持快速随机访问这也是链表的特点 也是标准库容器中唯一一个不提供 size() 方法的容器。当不需要双向迭代时具有比 std::list 更高的空间利用率。1.2 无序容器我们已经熟知了传统 C 中的有序容器 std::map/std::set这些元素内部通过红黑树进行实现 插入和搜索的平均复杂度均为 O(log(size))。在插入元素时候会根据 操作符比较元素大小并判断元素是否相同 并选择合适的位置插入到容器中。当对这个容器中的元素进行遍历时输出结果会按照 操作符的顺序来逐个遍历。而无序容器中的元素是不进行排序的内部通过 Hash 表实现插入和搜索元素的平均复杂度为 O(constant) 在不关心容器内部元素顺序时能够获得显著的性能提升。C11 引入了的两组无序容器分别是std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。它们的用法和原有的 std::map/std::multimap/std::set/set::multiset 基本类似 由于这些容器我们已经很熟悉了便不一一举例我们直接来比较一下std::map和std::unordered_map#include iostream
#include string
#include unordered_map
#include map
int main() {// 两组结构按同样的顺序初始化std::unordered_mapint, std::string u {{1, 1},{3, 3},{2, 2}};std::mapint, 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;
}最终的输出结果为std::unordered_map
Key:[2] Value:[2]
Key:[3] Value:[3]
Key:[1] Value:[1]
std::map
Key:[1] Value:[1]
Key:[2] Value:[2]
Key:[3] Value:[3]1.3 元组了解过 Python 的程序员应该知道元组的概念纵观传统 C 中的容器除了 std::pair 外 似乎没有现成的结构能够用来存放不同类型的数据通常我们会自己定义结构。 但 std::pair 的缺陷是显而易见的只能保存两个元素。1.3.1元组基本操作关于元组的使用有三个核心的函数std::make_tuple: 构造元组std::get: 获得元组某个位置的值std::tie: 元组拆包#include tuple
#include iostream
auto get_student(int id)
{// 返回类型被推断为 std::tupledouble, char, std::stringif (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::get0(student) , 成绩: std::get1(student) , 姓名: std::get2(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 除了使用常量获取元组对象外C14 增加了使用类型来获取元组中的对象std::tuplestd::string, double, double, int t(123, 4.5, 6.7, 8);
std::cout std::getstd::string(t) std::endl;
std::cout std::getdouble(t) std::endl; // 非法, 引发编译期错误
std::cout std::get3(t) std::endl;1.3.2运行期索引如果你仔细思考一下可能就会发现上面代码的问题std::get 依赖一个编译期的常量所以下面的方式是不合法的int index 1;
std::getindex(t);那么要怎么处理答案是使用 std::variantC 17 引入提供给 variant 的类型模板参数 可以让一个 variant 从而容纳提供的几种类型的变量在其他语言例如 Python/JavaScript 等表现为动态类型#include variant
template size_t n, typename... T
constexpr std::variantT... _tuple_index(const std::tupleT... tpl, size_t i) {if constexpr (n sizeof...(T))throw std::out_of_range(越界.);if (i n)return std::variantT...{ std::in_place_indexn, std::getn(tpl) };return _tuple_index(n sizeof...(T)-1 ? n1 : 0)(tpl, i);
}
template typename... T
constexpr std::variantT... tuple_index(const std::tupleT... tpl, size_t i) {return _tuple_index0(tpl, i);
}
template typename T0, typename ... Ts
std::ostream operator (std::ostream s, std::variantT0, Ts... const v) { std::visit([](auto x){ s x;}, v); return s;
}这样我们就能int i 1;
std::cout tuple_index(t, i) std::endl;1.3.3元组合并与遍历还有一个常见的需求就是合并两个元组这可以通过 std::tuple_cat 来实现auto new_tuple std::tuple_cat(get_student(1), std::move(t));马上就能够发现应该如何快速遍历一个元组但是我们刚才介绍了如何在运行期通过非常数索引一个 tuple 那么遍历就变得简单了 首先我们需要知道一个元组的长度可以template typename T
auto tuple_len(T tpl) {return std::tuple_sizeT::value;
}
这样就能够对元组进行迭代了
// 迭代
for(int i 0; i ! tuple_len(new_tuple); i)// 运行期索引std::cout tuple_index(new_tuple, i) std::endl;1.4总结本章简单介绍了现代 C 中新增的容器它们的用法和传统 C 中已有的容器类似相对简单可以根据实际场景丰富的选择需要使用的容器从而获得更好的性能。std::tuple 虽然有效但是标准库提供的功能有限没办法满足运行期索引和迭代的需求好在我们还有其他的方法可以自行实现。