跟我学C++中级篇——STL的容器Array
Posted 太平洋工作室
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了跟我学C++中级篇——STL的容器Array相关的知识,希望对你有一定的参考价值。
一、顺序容器Array
STL中的Array数组类型是在c++ TR1中才提出的,在之前只有Vector这个类似于数组的类型。但在实际应用中发现,vector和实际应用数组还是有非常大的区别,包括迭代器访问的控制,内存大小的控制等。用过vector的很容易发现它和实际使用中的数组的诸多不同之处。
换句话说,实际开发过程中,还是需要一个和数组高度类似的数据类型,这也是std::array的出现的一个原因,正如军事上的火力配比一样,不能出现火力空白区。那么这二者最关键的不同在哪儿呢?有两个主要方面,第一是array的内存是在栈上,而vector是在堆上。它的直接结果就是理论上讲,array的大小有上限(默认WIN是1M,LINUX是10M,不过可以动态调整),而vector理论上讲可以认为上限非常大(X64平台上,但实际仍受限于物理内存和OS系统)。同时栈和堆又导致了内存生命周期的不同,应用范围的不同等等。而栈的有限性又显示标注了array的固定大小性。第二个就是为了保持与C数组的最大的类似,其构造函数、析构函数和赋值操作符等都是隐式声明的,这也是一个从设计原理上与vector不同的地方。
二、源码分析
其源码定义为:
template <class _Ty, size_t _Size>
class array { // fixed size array of values
public:
using value_type = _Ty;
using size_type = size_t;
using difference_type = ptrdiff_t;
using pointer = _Ty*;
using const_pointer = const _Ty*;
using reference = _Ty&;
using const_reference = const _Ty&;
using iterator = _Array_iterator<_Ty, _Size>;
using const_iterator = _Array_const_iterator<_Ty, _Size>;
using reverse_iterator = _STD reverse_iterator<iterator>;
using const_reverse_iterator = _STD reverse_iterator<const_iterator>;
#if _HAS_TR1_NAMESPACE
_DEPRECATE_TR1_NAMESPACE void assign(const _Ty& _Value) {
_STD fill_n(_Elems, _Size, _Value);
}
#endif // _HAS_TR1_NAMESPACE
void fill(const _Ty& _Value) {
_STD fill_n(_Elems, _Size, _Value);
}
void swap(array& _Other) noexcept(_Is_nothrow_swappable<_Ty>::value) {
_Swap_ranges_unchecked(_Elems, _Elems + _Size, _Other._Elems);
}
_NODISCARD _CONSTEXPR17 iterator begin() noexcept {
return iterator(_Elems, 0);
}
_NODISCARD _CONSTEXPR17 const_iterator begin() const noexcept {
return const_iterator(_Elems, 0);
}
_NODISCARD _CONSTEXPR17 iterator end() noexcept {
return iterator(_Elems, _Size);
}
......
}
Array的源码定义不复杂,就是一个带有非模板类型参数的模块类。非模板类型用于指定整个模板数组的大小,使用size_t来定义。类的开始使用using重定义了一大批的类型,供后面相关代码使用。在这个模板类中重载发[]运算符并实现了at函数,这两个函数返回的是一个当前元素的引用。
另外一个需要说明的是,在这个类模板中,使用的是隐式声明的构造函数和析构函数,它的定义符合聚合初始化的规则来初始化数组。
在Array中,经历了版本的不断的迭代,从c++11到C++17再到最新的c++20,反正都是让人越来越好用的方法。这会在下面的例程中进行体现。
三、例程
看几个例子:
1、声明定义和使用
void TestArray()
{
std::array<int, 20> arr = {0}; //标准用法
std::array arr17 = {1,2,3,4,5,6,7}; //C++17自动推导
auto arr20 = {23,23,32,32,36,36};
}
2、生成一个array,std::to_array
在实际开发中,C数组在应用中经常退化为指针来使用,这也是初学者在实际开发中,对指针比较恐惧的一个原因之一,指针为啥莫名其妙又变成了数组,特别是涉及到高维数组,老程序员也会出现短暂的理解认知时间。
目前在cppreference上定义的介绍为:
namespace detail {
template <class T, std::size_t N, std::size_t... I>
constexpr std::array<std::remove_cv_t<T>, N>
to_array_impl(T (&a)[N], std::index_sequence<I...>)
{
return { {a[I]...} };
}
}
template <class T, std::size_t N>
constexpr std::array<std::remove_cv_t<T>, N> to_array(T (&a)[N])
{
//make_index_sequence就是为了节省数组长度构造的方法,否则1000个甚至更多怎么遍历?
return detail::to_array_impl(a, std::make_index_sequence<N>{});
}
它的应用方法如下:
void TestArray()
{
//这两行代码,禁止隐匿转换,否则报错
auto arr2x = std::to_array< int>({1,3,5,7,9});
//这行,会服一个警告,最好在to_array中给出大小,即注释那行
//std::array<unsigned int, 3> a20 = std::to_array<unsigned int,3>({1,3,5});
std::array<unsigned int, 3> a20 = std::to_array<unsigned int>({1,3,5});
for (int num = 0; num < a20.size(); num++)
{
std::cout<<"std array value:" << a20[num] << std::endl;
}
}
这里需要说明的是,一定要显示的使用类型,保证数据安全,这也是C/C++安全开发的一个要求。
3、做为返回值返回
在实际开发中,如果在栈区上有一个数组,一般是严格禁止做为返回值的形式酆的。在函数中返回一个栈上的值,是非常危险的,但是std::array的出现则实现了这种操作的安全性。
std::array<int,3> GetArray()
{
std::array<int, 3> arr = {1,2,3};
return arr;
}
int* GetCarr()
{
int buf[3] = {3,2,1};
return buf;
}
void TGetArray()
{
//此处调用,操作p,是不可预知的,特别是在复杂的线程环境中
int* p = GetCarr();
//这个是安全的
auto arr = GetArray();
for (int num = 0; num < arr.size(); num++)
{
std::cout << "std array value:" << arr[num] << std::endl;
}
}
比较主要的三种用法,可以自己试试。具体的操作函数操作,非常简单,这里就不再一一举例了。
四、总结
其实对于标准制定者来说,是非常左右为难的,既要兼顾易用性,又要兼顾容易理解性,还得照顾各种历史的传承,所以同学们应该明白为啥新语言一出来,就很容易被高手们一下子看到其开发的本质就在于此。因为没有历史的包袱,所以新语言能创造性的使用一些新技术新技巧,从而在某个方面迅速展露头角,(当然,也有搞不好的)但是随着时间的推进,版本的迭代,同样会变得越来越臃肿,这也是没办法的办法。
以上是关于跟我学C++中级篇——STL的容器Array的主要内容,如果未能解决你的问题,请参考以下文章
C++ :1STL 的容器概述array容器详解迭代器初步分析
C++ :1STL 的容器概述array容器详解迭代器初步分析