不懂C呜呜呜
C++ 常用数据结构及其使用
这是我最近在自学c++过程中,意识到自己对c++的数据结构尚不熟悉,因此搜罗来的各类资料并进行了自己的理解
[TOC]
array 数组
array在功能上是为了弥补传统C语言数组的越界问题,而由C++提出的更为安全的数组容器。
array的初始化
数组array类是一个严格固定长度的容器,这意味着在声明时就需要声明长度。
1 | |
array是一个严格线性的容器,在物理上也是线性排列,可以通过偏移指针来访问元素,访问耗时恒定。
array的使用
迭代器的使用
array是一个可以通过迭代器iterator进行遍历的容器,可以通过以下方式获取和使用迭代器:
begin():获取数组开头位置的迭代器
end()
rbegin():获取数组尾部的迭代器,且反向迭代
rend()
cbegin():获取一个const的迭代器对象,无法通过const iterator去改变一个对象的值!
cend()
crbegin()
crend()
1
2
3for(auto it = arr.rbegin();it!=arr.crend();++it){
cout << *it << endl;
}
数据的访问
array是严格定长的,允许通过size()、empty()和max_size()进行容量的访问。(我不清楚max_size的作用)。
你可以通过多种方法访问一个array的数据对象
1 | |
注意array是物理上连续的,因此获取指针后可以通过跳转指针地址获取对象(但是出于安全考虑最好不要这样做)
array支持fill来进行内容的填充(不知道初始化时是否会fill全为0),还可以使用swap来交换指向的指针。
1 | |
vector 向量
vector是向量类型,是C++中重要的容器对象。其实质上也是采用数组进行实现,需要连续的存储空间,当数据极大时会导致分配内存上的困难。同时vector的优势在于智能管理,更加安全的同时还提供了大量的现有函数,支持自动增长。
vector的多种初始化方式
vector真是一个功能丰富的类呢(笑)
1 | |
vector的操作方式
vector元素访问与更改
vector支持使用[ ]进行访问,还可以通过front和back获取特殊位置元素。
1 | |
vector的元素操作方法极其丰富:
1 | |
vector的容量操作
vector是具有动态拓展性的,但是也支持用户手动干预其容量。
1 | |
vector的奇技淫巧
vector作为一个极其重要的类,其集成了多种性能较高的算法。
1 | |
deque 双向队列
deque双向队列,支持两端操作。vector是单向开口的内存空间,这意味着什么呢?意味着如果你在vector头部插入一个元素,那么会慢得离谱(这个操作是合法的但不是推荐的)。而deque则是更加好的选择,你可以在头尾很快地进行元素操作。相应的,其占用内存会更多。实际上deque包括两级结构,一级类似于vector,一级则是专门维护首地址。
deque的初始化
1 | |
deque操作
deque赋值
1 | |
deque特色
我们说过,deque和vector的区别在于对双端操作的支持更好,而且我们能发现,deque比vector多了头部操作函数,除此之外没有特别明显的使用功能区别。(ps:deque还是一个没有容量概念的容器,因此和vector不同的一点在于,vector在空间不够的时候会申请一个新的空间再经过复制和释放旧空间的过程,但是deque会直接将新空间连接到旧空间后。因此deque没有reserve空间的需要)
1 | |
list 链表
list是一个顺序容器,其通过链表实现,主要优势在于快速的插入和删除。
在功能上,vector、deque、list三者是经常被提及的选择对象,以下列出三者使用场景的不同。
vector:大量随机访问需求 && 插入删除多在尾部
list:少量访问需求 && 大量插入和删除需求
deque:大量访问需求 && 大量插入和删除需求 && 对内存要求不高
list的初始化
list的初始化与vector类似,在此按下不表
list的操作
list的操作与vector略有不同,因为其链表的性质,合并和拆分的功能较为突出。另外也注意到list的有序性要求。注意list没有随机迭代访问器,因此其sort函数是自己定义的。
1 | |
map 表
map是我们所讲到的第一个关联容器,是一个对“映射”这种逻辑关系的支持,提供一对一的存储。map这种数据结构有两种实现:其一是基于红黑树的map,其二是基于hash表的unorder_map。前者的数据有序性更好,红黑树会对数据进行自动排序,但是占用空间大;后者对查找的支持更好,但是对于复杂操作的时间效率不够好。这两种的操作完全一样,只是底层不同!
map的数据操作
map有最主要的操作是“插入”,即插入键值对,具有三种插入方式:
1 | |
map的数据查找比较反人类!,主要的方法为find
1 | |
map的数据删除有两种方式:
1 | |
set 对
set是我们谈到的第二个关联容器,可以看出,关联容器不支持顺序容器的关于位置的操作(因为实际上没有位置的概念)。set中每个元素只是一个关键字,而与map中的键值对区别开来。
set的关联容器包括两大类四种:
set:采用高效的平衡检索二叉树:红黑树
- unordered_set:基于hash函数实现的set
multiset:支持关键字重复出现的set
- unordered_multiset
set的主要作用在于查询,即查询在该结构中存在特定关键字。set的元素会默认升序排列(默认比较函数为less\
set的初始化
1 | |
set的使用
首先,set支持一般的迭代器,因此你可以利用迭代器的连续变化来获取set内的值(这里体现了迭代器的泛化性,因为set实际上不是顺序存储的,但是利用起迭代器来和顺序容器完全一样)
1 | |
其次,我们说过set的主要作用在于寻找元素是否存在,因此传统艺能不能丢(指count和find)
1 | |
set的元素增删采用insert和erase实现
1 | |
stack 栈
如何初始化一个stack?
stack 容器适配器的模板有两个参数。第一个参数是存储对象的类型,第二个参数是底层容器的类型。stack
1 | |
初始化一个堆栈时,不能在初始化列表{}中用对象来初始化,但是可以用另一个容器来初始化,只要堆栈的底层容器类型和这个容器的类型相同。
1 | |
不过stack支持拷贝构造,这意味着在初始化列表使用一个stack能够制造一个副本:
1 | |
stack类的操作
stack 是一类存储机制简单、所提供操作较少的容器。下面是 stack 容器可以提供的一套完整操作:
top():返回一个栈顶元素的引用,类型为 T&。如果栈为空,返回值未定义。
push(const T& obj):可以将对象副本压入栈顶。这是通过调用底层容器的 push_back() 函数完成的。
push(T&& obj):以移动对象的方式将对象压入栈顶。这是通过调用底层容器的有右值引用参数的 push_back() 函数完成的。
pop():弹出栈顶元素。
size():返回栈中元素的个数。
empty():在栈中没有元素的情况下返回 true。
emplace():用传入的参数调用构造函数,在栈顶生成对象。这意味着传入T对应的构造函数所需参数即可在栈顶生成对象,而不需要先生成一个T对象再push进去,更节省内存。
swap(stack
& other_stack) :将当前栈中的元素和参数中的元素交换。参数所包含元素的类型必须和当前栈的相同。对于 stack 对象有一个特例化的全局函数 swap() 可以使用。注意这实质上交换了的是指向的内存位置,因此即使size不同也可以交换。
同时stack也支持多种运算符。stack
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!