关于C++:STL容器模板类的内部实现-array

关于C++:STL容器模板类的内部实现-array

本小白最近在学习C++中STL的相关知识,特在此做个笔记
今天看到了array类的内部实现,特此来整理一下所学

关于using关键字的使用:

在洛谷刷题时常见的用法:

using namespace std;//标准命名空间

用以指定命名空间在程序中全局可见,这种做法想必很普遍了。
现在要说的用法是第二类用法:

今天新学到的用法:

using ll=long long;//ll作为long long类型的别名

作用与c中的typedef相同,不同的是,typedef无法高效的重定义模板类型。
在在 C++98/03 中往往不得不这样写:

template <typename Val>
struct str_map
{
    typedef std::map<std::string, Val> type;
};
// ...
str_map<int>::type map1;
// ...

而在C++11中出现了一个可以重定义模板的语法:

template <typename Val>
using str_map_t = std::map<std::string, Val>;
// ...
str_map_t<int> map1;

此种用法在今天的array实现中常有见到,另外using还有关于类继承方式的用法,但今日暂且不提。

array模板类的使用:

暂且赘述array容器的使用方法
首先是array类的声明:

    array<int, 10> arr;

这便声明了一个元素种类为int,元素个数为10,名为arr的array类对象。
我们可以像之前使用c语言的数组一样使用它:

    array<int,10> arr{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    // array容器声明方法:array<元素类型> 容器名{初始化可不写};
    //此时可以像普通数组一样使用vector,如下
    int *parr = &arr[5];
    cout << arr[0] << ' ' << *(parr + 1) << endl;

输出为:

1 7

我们还可以使用迭代器,所谓迭代器,是泛型编程的组成部分,用来遍历我们的数据结构,访问数据集合中的元素:

    array<int, 10>::iterator i;//这个i就是适用于array<int, 10>的迭代器
    cout << "Uninitialized:" << endl;
    for (i = arr.begin(); i < arr.end(); i++) {
        cout << *i << ' ';
    }
    arr[5] = 100;
    cout << endl;
    for (i = arr.begin(); i < arr.end(); i++) {
        cout << *i << ' ';
    }

输出为:

Uninitialized:
-734660048 32764 18 0 -1181673808 674 -1932214893 32764 -734660048 32764
-734660048 32764 18 0 -1181673808 100 -1932214893 32764 -734660048 32764

可见,迭代器的使用方式类似于指针,实际上,对于int型数据,迭代器的内部实现就是int*,也就是指向int类型的指针。

array类的内部:

在我们的IDE环境里,右击我们代码里array单词,选择查看定义,我们应该可以打开我们的环境变量目录里的array头文件(我试了vscode与visual studio 2022都可以,别的可以试一下),我们可以看见近千行的代码,别慌,现在的光标应该移动到了array类的定义上,我来带着你刨析它。

以下头文件的内容来源:

// array standard header

// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

首先是array类的定义(大小不为零的那种奥):

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>;
    ......
    _Ty _Elems[_Size];//这是个我们常用的基本数组。
};

在这里,[Size_t]即unsigned long long。

可以看到array是个模板类,根据我们定义对象的写法也可以看出,在这里[_Ty]是一个数据元素的类型,[_Size]是数据的数量(也就是array的大小),它们为占位符,由实例化时的输入指定。
记住这一点,我们接下来会涉及到C++模版的相关知识。

我们之前提到的using用法在这里看到了,我们知道它是用来给类型起别名的,而且对模板类适用。

可以看到[value_type]即值类型重定义为[_Ty],[pointer]即指针类型为[_Ty*](即为指向[_Ty]类型的指针类型),[iterator]即迭代器类型为[_Array_iterator<_Ty, _Size>],这是一个模版类的实例。

在我们之前写的迭代器使用小试的代码里,我们有这样的语句:

    array<int, 10>::iterator i;//这个i就是适用于array<int, 10>的迭代器
    for (i = arr.begin(); i < arr.end(); i++) {
        cout << *i << ' ';
    }

可以看到i的类型是array<int, 10>的迭代器,那么arr.begin()这个array<int, 10>类的成员函数的返回值类型应该也是一个迭代器。
那么我们就以begin()的实现来研究一下。

在array.hpp文件,我们可以通过现代编辑器的大纲功能看到文件的结构(或者搜索也行),以找到begin()的定义,应该在array类的内部:

    _NODISCARD _CONSTEXPR17 iterator begin() noexcept {
        return iterator(_Elems, 0);
    }

[_NODISCARD]和[_CONSTEXPR17]和[noexcept]都是和编译方式或异常处理有关的,我们暂且不要理他。
可以看到它的返回值是iterator(_Elems, 0),那么这玩意又是啥呢,诶等等,我们好像见过它,还记得array类内的一堆重定义语句嘛,就是我们之前那段代码;

    using iterator       = _Array_iterator<_Ty, _Size>;

继续查找[_Array_iterator]的定义,这又是一个模板类:

template <class _Ty, size_t _Size>
class _Array_iterator : public _Array_const_iterator<_Ty, _Size> {
public:
    using _Mybase = _Array_const_iterator<_Ty, _Size>;
//条件编译不用管
    using iterator_category = random_access_iterator_tag;
    using value_type        = _Ty;
    using difference_type   = ptrdiff_t;
    using pointer           = _Ty*;
    using reference         = _Ty&;
    ......

那么我们之前看见的begin()的返回值便是这个类,而且返回时还应调用了这个[_Array_iterator]类的构造函数,那我们就来看看。

PS:继续深入ing。

    _CONSTEXPR17 explicit _Array_iterator(pointer _Parg, size_t _Off = 0) noexcept : _Mybase(_Parg, _Off) {}

一样,不管[_NODISCARD]、[_CONSTEXPR17]和[noexcept],我们看到[_Array_iterator]类的构造函数唯一做的就是使用输入的[_Elems]和[0]初始化了[_Mybase],而[_Mybase]在上面见到,是[_Array_const_iterator<_Ty, _Size>]的重定义。

为啥又有了个常量的迭代器啊2333
唉,都走到这一步了。

来看:

template <class _Ty, size_t _Size>
class _Array_const_iterator
//中间条件编译不用管
{
public:
//中间条件编译不用管
    using iterator_category = random_access_iterator_tag;
    using value_type        = _Ty;
    using difference_type   = ptrdiff_t;
    using pointer           = const _Ty*;
    using reference         = const _Ty&;
    enum { _EEN_SIZE = _Size }; // helper for expression evaluator(又一个官方注释,可惜我不知道这是干啥的)
//中间条件编译不用管
    _CONSTEXPR17 _Array_const_iterator() noexcept : _Ptr(), _Idx(0) {}//这个我们没有用到

    _CONSTEXPR17 explicit _Array_const_iterator(pointer _Parg, size_t _Off = 0) noexcept : _Ptr(_Parg), _Idx(_Off) {}

    _NODISCARD _CONSTEXPR17 reference operator*() const noexcept {
        return *operator->();
    }
    _NODISCARD _CONSTEXPR17 pointer operator->() const noexcept {
        //中间异常相关不用管
        return _Ptr + _Idx;
    }
//中间别的功能实现不用管
private:
    pointer _Ptr; // beginning of array
    size_t _Idx; // offset into array
//中间条件编译结束不用管
};

可以看到[_Array_const_iterator<_Ty, _Size>]有两个成员:[_Ptr]即我们的指针、[_Idx]即元素的个数(用在迭代器上就是位置或者说索引)。
回顾一下begin()的定义:

    _NODISCARD _CONSTEXPR17 iterator begin() noexcept {
        return iterator(_Elems, 0);
    }

[_Idx]初始化为0(毕竟begin()嘛),而[_Ptr]初始化为[_Elems],也就是我们常用的基本数组。
_Array_iterator<_Ty, _Size>:

    _NODISCARD _CONSTEXPR17 reference operator*() const noexcept {
        return const_cast<reference>(_Mybase::operator*());
    }

    _NODISCARD _CONSTEXPR17 pointer operator->() const noexcept {
        return const_cast<pointer>(_Mybase::operator->());
    }

而[->]操作符重载为[_Ptr + _Idx],[*]重载为[*operator->()].

至此,我们终于能访问array类里的一个元素了。

我们来总结一下:

我们研究了一下array类里begin()函数的内部实现:

  1. 用array对象的数组(首地址)与0(索引的第开始嘛)生成一个具有对应两个成员变量的类,即[_Array_const_iterator< _Ty,_Size>]类型的[_Mybase]。
  2. 把这个类作为[_Array_iterator]类型的类的成员,返回整个类。
  3. 返回值就是我们所用的迭代器,基于所定义的操作符重载,我们便能想使用指针一样使用迭代器了。

这其中蕴含着的泛型编程思想值得我们讨论一下。

STL组件之间的关系:待处理数据容纳在容器中,经由算法的处理,将结果输出到另一个容器里。(完成这种操作的正是迭代器)

看起来这句话好像是废话,但是似乎应隐隐约约能感受到这种设计的精妙:这正是符合现实生活的,更贴近面向对象编程的用意。

至于STL为什么设计成如上的样子,请您试想,应用模版,我们不拘于数据类型的限制,应用重载操作符,我们能得到统一的语句编写方式,应用如上的嵌套定义、调用呢?我们可以使代码的重用率提升,毕竟STL里的操作有部分是一样的,而且可以确保我们在不同的系统得到的结果相同。

总之,STL是C++数据结构与泛型编程中的重要知识,我们应该深入的研究它。
STL万岁(≧▽≦)/
觉得还行的看官点个攒再走吧…

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中,使用关键字new可以动态地分配内存来创建数组。要创建一个数组,可以使用以下语法:bool* array = new bool[10]。这将配一个具有10个元素的bool类型的数组。根据引用,这个数组可以是未初始化的或初始化为0。另外,引用指出new关键字不是函数,而是C++的关键字。它可以用来动态分配内存,并与delete关键字一起使用来释放分配的内存。需要注意的是,在使用new创建数组时,需要额外的内存来保存数组的大小,以便在使用delete释放数组时正确调用析构函数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [c++ new一个数组](https://blog.csdn.net/lixinglaing/article/details/81434608)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [c++ new delete new[] delete[] 底层实现](https://blog.csdn.net/cFarmerReally/article/details/54585443)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值