STL-序列容器

#########################################

1 STL容器

序列容器 array vector list deque
排序容器 set multiset map multimap
哈希容器 unordered_set unordered_multiset unordered_map unordered_multimap

2 STL迭代器iterator

标准容器都有迭代器,一般的使用方法是[容器类名::iterator 迭代器名;]
[*迭代器名]就表示迭代器指向的元素,说明迭代器与指针类似。

2.1 迭代器的分类与功能

为什么要分类?

因为功能不一样,随机访问迭代器功能最多,向下兼容所有迭代器的功能。

  • 前向迭代器(forward iterator)只支持++ != ==功能,对应哈希容器
  • 双向迭代器(bidirectional iterator)支持++ -- != ==功能,对应排序容器
  • 随机访问迭代器(random access iterator)支持++ -- +n -n < > != == ,对应序列容器

迭代器的应用场景?

迭代器被用来遍历数据结构,对其指向的元素进行修改,方便算法的移植与开发;

但是,迭代器不能被用来初始化元素,元素的初始化可以在定义容器时就初始化好,或者使用容器自带的添加函数,例如push、insert函数进行初始化,当容器元素增加时,以前定义的迭代器可能失效,最好是重新生成!

什么是全局begin/end(标准库)?

全局的 begin() 和 end() 函数来从容器中获取迭代器,因为当操作对象为 array 容器时,它们和 begin()/end() 成员函数是通用的。
auto first = std::begin(values);
需要注意的是,当迭代器指向容器中的一个特定元素时,它们不会保留任何关于容器本身的信息,所以我们无法从迭代器中判断,它是指向 array 容器还是指向 vector 容器。

4 序列容器

array<T,N>(数组容器)表示可以存储 N 个 T 类型的元素
vector(向量容器)用来存放 T 类型的元素
deque(双端队列容器)
list(链表容器)
forward_list(正向链表容器)

序列容器常见的函数成员

#########################################

1 array 容器

#include <array>
它比普通数组更安全,正是由于 array 容器中包含了 at() 这样的成员函数,使得操作元素时比普通数组更安全。

array 容器的大小是固定的,无法借由增加或移除元素而改变其大小,它只允许访问或者替换存储的元素。

1.1 初始化

std::array<double, 10> values;名为 values 的 array 容器,其包含 10 个浮点型元素。
std::array<double, 10> values {};所有的元素初始化为 0 
std::array<double, 10> values {0.5,1.0,1.5,,2.0};对多个元素进行初始化

1.2 访问元素

values[4] = values[3] + 2.O*values[1];
通过容器名[]的方式直接访问和使用容器中的元素,由于没有做任何边界检查,所以即便使用越界的索引值去访问或存储元素,也不会被检测到。

values.at (4) = values.at(3) + 2.O*values.at(1);
有效地避免越界访问,性能下降

1.3 遍历元素

关键字auto:告诉编译器,智能去识别数据类型。
for(auto i = 0, total =0 ; i < values.size() ; ++i)
    total += values[i];
除了全循环,还有基于范围的循环
    string str("some string.");
    // auto 类型也是 C++11 新标准中的,用来自动获取变量的类型
    for(auto c : str)
        cout << c << " ";
基于范围的引用-循环,遍历+修改
    string str("some string.");
    for(auto &c : str)
    {
        c = toupper(c);//小写to大写
        cout << c << " ";
    }
迭代器遍历
for (auto first = values.begin(); first < values.end(); ++first) {
    cout << *first << " ";

2 vector 容器

#include <vector>
array 实现的是静态数组(容量固定的数组),而 vector 实现的是一个动态数组,即可以进行元素的插入和删除,在此过程中,vector 会动态调整所占用的内存空间,整个过程无需人工干预。

该容器擅长在尾部插入或删除元素,在常量时间内就可以完成,时间复杂度为O(1);

而对于在容器头部或者中部插入或删除元素,则花费时间要长一些(移动元素需要耗费时间),时间复杂度为线性阶O(n)。

2.1 初始化

std::vector<double> values;空的 vector 容器,没有为其分配内存空间
std::vector<int> primes {2, 3, 5, 7, 11, 13, 17, 19};含有 8 个素数的 vector 容器
std::vector<double> values(20);values 容器开始时就有 20 个元素,它们的默认初始值都为 0
std::vector<double> values(20, 1.0); 20 个元素的值都是 1.0
std::vector<char>value2(value);拷贝构造,value2 容器中也具有[20 个元素的值都是 1.0]

注意:圆括号 () 和大括号 {} 是有区别的,对于空的 vector 容器来说,begin() 和 end() 成员函数返回的迭代器是相等的。

2.2 capacity(容量)与size(大小)的区别

2.2.1 capacity,在不分配更多内存的情况下,容器可以保存的最多元素个数。

一旦容量发生改变,则以前产生的迭代器都将失效。

value.capacity() ;获得容量
value.reserve(20);增加20容量

2.2.2 为什么要有capacity?

每次增加一个元素都要去申请一次内存,效率很低;通常是申请大片内存,作为capacity,每次增加元素便可直接写入内存,无需申请;

而每次元素爆满之后,则需要申请一片更大的内存(基于不同编译器的capacity增长算法,有的是翻倍增长,有的是线性增长)。

2.2.3 size,实际所包含的元素个数,不能超过capacity

value.size() ;获得容器的大小

2.3 访问元素

cout << values[0] << endl;容器名[n]这种获取元素的方式,需要确保下标 n 的值不会超过容器的容量
cout << values.at(0) << endl;当传给 at() 的索引会造成越界时,会抛出std::out_of_range异常

front()back(),它们分别返回 vector 容器中第一个和最后一个元素的[引用]
    //修改首元素
    values.front() = 10;
    cout <<"values 新的首元素为:" << values.front() << endl;
    //修改尾元素
    values.back() = 20;
    cout << "values 新的尾元素为:" << values.back() << endl;

for循环全遍历、基于范围遍历,迭代器遍历

2 .4 增删元素

2.4.1 增加元素

vector增加元素只能使用vector自带的api,并且插入之后有可能导致以前的迭代器失效(capacity变化导致)

values.push_back(1);在 vector 容器尾部添加一个元素
values.emplace_back(2);功能和 push_back() 相同
emplace_back()和push_back()的区别

底层实现的机制不同。
push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);

而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。

换句话说,emplace_back() 的执行效率比 push_back() 高。由于 emplace_back() 是 C++ 11 标准新增加的,如果程序要兼顾之前的版本,还是应该使用 push_back()。

std::vector<int> demo{1,2};

//在begin+1的位置插入[3],并返回[3]的迭代器
demo.insert(demo.begin() + 1, 3);//{1,3,2}

//在end的位置插入2个元素[5,5],并返回第一个[5]的迭代器
demo.insert(demo.end(), 2, 5);//{1,3,2,5,5}

//在end的位置插入其他容器array[begin:end),并返回新插入的首元素的迭代器
std::array<int,3>test{ 7,8,9 };
demo.insert(demo.end(), test.begin(), test.end());//{1,3,2,5,5,7,8,9}

//在end的位置插入列表{10,11},并返回[10]的迭代器
demo.insert(demo.end(), { 10,11 });//{1,3,2,5,5,7,8,9,10,11}

//emplace() 每次只能插入一个元素,并返回插入元素的迭代器
demo1.emplace(demo1.begin(), 3);

2.4.2 删除元素

前面提到,无论是向现有 vector 容器中访问元素、添加元素还是插入元素,都只能借助 vector 模板类提供的成员函数,但删除 vector 容器的元素例外,完成此操作除了可以借助本身提供的成员函数,还可以借助一些全局函数。

demo.pop_back();删除demo中最后一个元素,大小(size)会减 1,但容量(capacity)不会发生改变。

auto iter = demo.erase(demo.begin() + 1);删除begin+1位置的元素,并将删除位置后续的元素陆续前移,并将容器的大小减 1。被删除的元素位置会被后面的元素补充上,并返回该元素的迭代器。

auto iter = demo.erase(demo.begin() + 1, demo.end() - 2);删除指定区间内的元素,返回补充的元素的迭代器。

remove()用于删除容器中指定元素时,常和 erase() 成员函数搭配使用
auto iter = std::remove(demo.begin(), demo.end(), 3);
demo.erase(iter, demo.end());

remove前,vector<int>demo{ 1,3,3,4,3,5 };
remove后,size:6
capacity:6
demo:1 3 3 4 3 5

auto iter = std::remove(demo.begin(), demo.end(), 3);

remove后,size:6
capacity:6
demo:1 4 5 4 3 5
erase后,size:3
capacity:6
demo:1 4 5

如果想删除容器中所有的元素,则可以使用 clear() 成员函数

demo.clear();
size is :0
capacity is :6

3 deque 容器

#include <deque>
double-ended queue ,又称双端队列容器
deque 容器也擅长在序列尾部添加或删除元素(时间复杂度为O(1)),而不擅长在序列中间添加或删除元素。

deque 容器也可以根据需要修改自身的容量和大小。
deque 还擅长在序列头部添加或删除元素,所耗费的时间复杂度也为常数阶O(1)。并且更重要的一点是,deque 容器中存储元素并不能保证所有元素都存储到连续的内存空间中。

3.1 deque在逻辑上连续,物理上不保证连续是什么意思?

deque是连续空间(至少逻辑上看来如此),连续线性空间总令我们联想到array或vector。

array无法成长,vector虽可成长,却只能向尾端成长,而且其所谓的成长原是个假象,事实上是(1)另觅更大空间;(2)将原数据复制过去;(3)释放原空间三部曲。如果不是vector每次配置新空间时都有留下一些余裕,其成长假象所带来的代价将是相当高昂。

deque系由一段一段的定量连续空间构成。一旦有必要在deque的前端或尾端增加新空间,便配置一段定量连续空间,串接在整个deque的头端或尾端。

deque的最大任务,便是在这些分段的定量连续空间上,维护其整体连续的假象,并提供随机存取的接口。避开了“重新配置、复制、释放”的轮回,代价则是复杂的迭代器架构。

既然deque需要维护多个连续内存片段,肯定缺不了map(注意,不是STL的map容器),是一小块连续空间,其中每个元素(此处称为一个节点,node)都是指针,指向另一段(较大的)连续线性空间,称为缓冲区。缓冲区才是deque的储存空间主体。

3.2 初始化

std::deque<int> d;空 deque 容器
std::deque<int> d(10);具有 10 个元素(默认都为 0)的 deque 容器
std::deque<int> d(10, 5);包含 10 个元素(值都为 5)的 deque 容器
std::deque<int> d2(d);拷贝构造,需要保证元素类型一致
//拷贝普通数组,创建deque容器
int a[] = { 1,2,3,4,5 };
std::deque<int>d(a, a + 5);
//拷贝其他所有类型的容器
std::array<int, 5>arr{ 11,12,13,14,15 };
std::deque<int>d(arr.begin()+2, arr.end());//拷贝arr容器中的{13,14,15}

3.3 访问元素

d[1] = d[0];容器名[n]修改指定下标位置处的元素,可能越界
d.at(1) = d.at(0);at() 成员函数会自行判定访问位置是否越界,如果越界则抛出std::out_of_range异常
front()back(),它们分别返回 vector 容器中第一个和最后一个元素的引用
    //修改首元素
    d.front() = 10;
    cout << "deque 新的首元素为:" << d.front() << endl;
    //修改尾元素
    d.back() = 20;
    cout << "deque 新的尾元素为:" << d.back() << endl;

for循环全遍历、基于范围遍历,迭代器遍历

3.4 增删元素

在实际应用中,常用 emplace()、emplace_front() 和 emplace_back() 分别代替 insert()、push_front() 和 push_back()

deque<int>d;
    //调用push_back()向容器尾部添加数据。
    d.push_back(2); //{2}
    //调用pop_back()移除容器尾部的一个数据。
    d.pop_back(); //{}
    //调用push_front()向容器头部添加数据。
    d.push_front(2);//{2}
    //调用pop_front()移除容器头部的一个数据。
    d.pop_front();//{}
    //调用 emplace 系列函数,向容器中直接生成数据。
    d.emplace_back(2); //{2}
    d.emplace_front(3); //{3,2}
    //emplace() 需要 2 个参数,第一个为指定插入位置的迭代器,第二个是插入的值。
    d.emplace(d.begin() + 1, 4);//{3,4,2}
    //erase()可以接受一个迭代器表示要删除元素所在位置
    //也可以接受 2 个迭代器,表示要删除元素所在的区域。
    d.erase(d.begin());//{4,2}
    d.erase(d.begin(), d.end());//{},等同于 d.clear()

4 list 容器

#include <list>
该容器的底层是以双向链表的形式实现的。这意味着,list 容器中的元素可以分散存储在内存空间里,而不是必须存储在一整块连续的内存空间中。

list 容器具有一些其它容器(array、vector 和 deque)所不具备的优势,即它可以在序列已知的任何位置快速插入或删除元素(时间复杂度为O(1))。并且在 list 容器中移动元素,也比其它容器的效率高。

需要对序列进行大量添加或删除元素的操作,而直接访问元素的需求却很少,这种情况建议使用 list 容器存储序列。

使用 list 容器的缺点是,它不能像 array 和 vector 那样,通过位置直接访问元素。
举个例子,如果要访问 list 容器中的第 6 个元素,它不支持容器对象名[6]这种语法格式,正确的做法是从容器中第一个元素或最后一个元素开始遍历容器,直到找到该位置。

4.1 初始化

std::list<int> values;空的 list 容器
std::list<int> values(10);包含 10 个元素,每个元素的值都为相应类型的默认值(int类型的默认值为 0)
std::list<int> values(10, 5);包含 10 个元素并且值都为 5 的 values 容器
std::list<int> value2(value);拷贝构造,需要确保元素类型一致
//拷贝普通数组,创建list容器
int a[] = { 1,2,3,4,5 };
std::list<int> values(a, a+5);
//拷贝其它类型的容器,创建 list 容器
std::array<int, 5>arr{ 11,12,13,14,15 };
std::list<int>values(arr.begin()+2, arr.end());//拷贝arr容器中的{13,14,15}

4.2 访问元素

list 容器不支持随机访问,未提供下标操作符 []at() 成员函数。
//使用begin()/end()迭代器遍历元素
for (std::list<char>::iterator it = values.begin(); it != values.end(); ++it)
    std::cout << *it;

front()back() 成员函数,可以分别获得 list 容器中第一个元素和最后一个元素的[引用]
cout << mylist.front() << " " << mylist.back() << endl;

4.3 增删元素

值得一提的是,list 容器在进行插入(insert())、接合(splice())等操作时,都不会造成原有的 list 迭代器失效,甚至进行删除操作,而只有指向被删除元素的迭代器失效,其他迭代器不受任何影响。

4.3.1 增加元素

push_front():向 list 容器首个元素前添加新元素;
push_back():向 list 容器最后一个元素后添加新元素;
emplace_front():在容器首个元素前直接生成新的元素;
emplace_back():在容器最后一个元素后直接生成新的元素;
emplace():在容器的指定位置直接生成新的元素;
insert():在指定位置插入新元素;
splice():将其他 list 容器存储的多个元素添加到当前 list 容器的指定位置处。

std::list<int> values{1,2,3};
values.push_front(0);//{0,1,2,3}
values.push_back(4); //{0,1,2,3,4}
values.emplace_front(-1);//{-1,0,1,2,3,4}
values.emplace_back(5);  //{-1,0,1,2,3,4,5}
values.emplace(values.end(), 6);//{-1,0,1,2,3,4,5,6}//emplace(pos,value),其中 pos 表示指明位置的迭代器,value为要插入的元素值

insert()的用法如下
std::list<int> values{ 1,2 };
    //在begin位置插入元素[3],并返回[3]的迭代器
    values.insert(values.begin() , 3);//{3,1,2}
    //在end位置插入2个元素[5, 5],并返回第一个[5]的迭代器
    values.insert(values.end(), 2, 5);//{3,1,2,5,5}
    //在end位置插入一段array,并返回插入的首元素的迭代器
    std::array<int, 3>test{ 7,8,9 };
    values.insert(values.end(), test.begin(), test.end());//{3,1,2,5,5,7,8,9}
    //在end的位置插入一段初始化列表,并返回插入的首元素的迭代器
    values.insert(values.end(), { 10,11 });//{3,1,2,5,5,7,8,9,10,11}和 insert() 成员方法相比,splice() 成员方法的作用对象是其它 list 容器,其功能是将其它 list 容器中的元素添加到当前 list 容器中指定位置处。
	
splice()的用法如下
	创建2个list用于拼接
    list<int> mylist1{ 1,2,3,4 }, mylist2{10,20,30};
    list<int>::iterator it = ++mylist1.begin(); //指向 mylist1 容器中的元素 2
   
    //把list2接到list1中的it位置
    mylist1.splice(it, mylist2); // mylist1: 1 10 20 30 2 3 4
                                 // mylist2:空
                                 // it 迭代器仍然指向元素 2,只不过容器变为了 mylist1
    //将list1中的 it 指向的元素 2 移动到 mylist2.begin() 位置处
    mylist2.splice(mylist2.begin(), mylist1, it);   // mylist1: 1 10 20 30 3 4
                                                    // mylist2: 2
                                                    // it 仍然指向元素 2
   
    //将 [mylist1.begin(),mylist1.end())范围内的元素移动到 mylist2.begin() 位置处                 
    mylist2.splice(mylist2.begin(), mylist1, mylist1.begin(), mylist1.end());//mylist1:空
                                                                             //mylist2:1 10 20 30 

3 4 2

4.3.2 删除元素

pop_front()pop_back()clear() 的用法非常简单
list<int>values{ 1,2,3,4 };    
//删除当前容器中首个元素
values.pop_front();//{2,3,4}
   
//删除当前容器最后一个元素
values.pop_back();//{2,3}
   
//清空容器,删除容器中所有的元素
values.clear(); //{}

erase()删除元素的用法如下
iterator erase (iterator position);
可实现删除 list 容器中 position 迭代器所指位置处的元素

iterator erase (iterator first, iterator last);
可实现删除 list 容器中 first 迭代器和 last 迭代器限定区域内的所有元素(包括 first 指向的元素,但不包括 last 指向的元素)。

remove(val)根据元素的值来执行删除操作

unique()相邻元素去重,用法如下:

    list<double> mylist{ 1,1.2,1.2,3,4,4.5,4.6 };
    //删除[相邻][重复]的元素,仅保留一份
    mylist.unique();//{1, 1.2, 3, 4, 4.5, 4.6}

//二元谓词函数,参数是相邻的2个
bool demo(double first, double second)
{
    //去重规则可以是相等,也可以是相差,规则自定
    return (int(first) == int(second));
}
    //demo 为二元谓词函数,是我们自定义的去重规则
    mylist.unique(demo);

5 forward_list 容器

#include <forward_list>
效率高是选用 forward_list 而弃用 list 容器最主要的原因,换句话说,只要是 list 容器和 forward_list 容器都能实现的操作,应优先选择 forward_list 容器。

5.1 初始化

std::forward_list<int> values;空
std::forward_list<int> values(10);10个元素,初始值默认0
std::forward_list<int> values(10, 5);10个元素,初始值都是5
std::forward_list<int> value2(value);拷贝构造,前提是数据类型一致

//拷贝普通数组,创建forward_list容器
int a[] = { 1,2,3,4,5 };
std::forward_list<int> values(a, a+5);
//拷贝其它类型的容器,创建forward_list容器
std::array<int, 5>arr{ 11,12,13,14,15 };
std::forward_list<int>values(arr.begin()+2, arr.end());//拷贝arr容器中的{13,14,15}

5.2 访问元素

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

std::forward_list<int> my_words{1,2,3,4};
//头与尾的距离
int count = std::distance(std::begin(my_words), std::end(my_words));

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

std::forward_list<int> values{1,2,3,4};
auto it = values.begin();
advance(it, 2);//it += 2

#########################################

5 关联容器

#########################################

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值