c++第九天 -- STL

cpp 复制代码
#include<iostream>
using namespace std;

class IntArray{
private:
    int* data;
    int size;
    int capacity;
public:
    IntArray() : data(nullptr),size(0),capacity(0) {}
    ~IntArray() {delete[] data;}

    void append(int value){
        if(size == capacity){
            int newCap = capacity == 0 ? 4 : capacity * 2;
            int* newData = new int[newCap];
            for(int i = 0;i < size;++i) newData[i] = data[i];
            delete[] data;
            data = newData;
            capacity = newCap;
        }
        data[size++] = value;
    }

    void printAll() const{
        for(int i = 0;i < size;++i) cout << data[i] << " ";
        cout << endl;
    }
//这里就是插入排序
    bool insert(int idx,int value){
        if(idx < 0 || idx > size) return false;
        append(0);  //扩容
        for(int i = size - 1;i > idx;--i) data[i] = data[i-1];
        data[idx] = value;
        return true;
    }

    bool remove(int idx){
        if(idx < 0 || idx >= size) return false;
        for(int i = idx;i < size - 1;++i) data[i] = data[i+1];
        --size;
        return true;
    }

    int& operator[](int idx) {return data[idx];}
    
    int find(int value) const{
        for(int i = 0; i < size;++i) if(data[i] == value) return i;
        return -1;
    }

    void clear() {size = 0;}
    int length() const {return size;}

};

int main()
{
    IntArray arr;
    int n;
    cin >> n;
    for(int i = 0;i < n;++i){
        int num;
        cin >> num;
        arr.append(num);
    }

    arr.printAll();

    //测试insert功能
    int insertIndex,insertElement;
    cin >> insertIndex >> insertElement;
    if(arr.insert(insertIndex,insertElement)){
        cout << "插入成功,插入后的数组: ";
        arr.printAll();
    }
    else{
        cout << "插入失败,索引位置不合法" << endl;
    }

    //测试remove功能
    int removeIndex;
    cin >> removeIndex;
        if (arr.remove(removeIndex)) {
        cout << "移除成功,移除后的数组: ";
        arr.printAll();
    }
    else {
        cout << "移除失败,索引位置不合法。" << endl;
    }
     // 测试 [] 功能
    int accessIndex;
    cin >> accessIndex;
    cout << "访问结果: " << arr[accessIndex] << endl;

    int newElement;
    cin >> newElement;
    cout << "查找元素 " << newElement << " 的位置: " << arr.find(newElement) << endl;

    // 测试 clear 功能
    arr.clear();
    cout << "清空数组后,数组长度: " << arr.length() << endl;

    cin >> newElement;
    cout << "查找元素 " << newElement << " 的位置: " << arr.find(newElement) << endl;

    return 0;
}

以期中测试的题目为例来体验模板的价值。

使用 C++ 语言设计并实现一个名为 IntArray 的类,该类用于封装一个整型数组,使用动态内存(即通过 new 和 delete 操作符)来存储整型数组元素。该类能实现的功能有:

append功能:在数组的末尾添加一个新的整型元素。如果数组容量不足,需要进行扩容操作。

insert功能:在指定的索引位置插入一个新的整型元素。如果索引位置不合法(小于 0 或者大于数组的当前元素数量),则不进行插入操作,并返回 false;如果数组容量不足,需要进行扩容操作。插入成功后返回 true。

remove功能:移除指定索引位置的元素。如果索引位置不合法(小于 0 或者大于等于数组的当前元素数量),则不进行移除操作,并返回 false;否则,移除元素并返回 true。移除元素后,后续元素需要向前移动。

\[\]功能:获取指定索引位置的元素。如果索引位置不合法(小于 0 或者大于等于数组的当前元素数量),则输出"error:out_of_range",同时返回0。

clear功能:移除数组中的所有元素。

length功能:返回数组中当前元素的数量。

find功能:在数组中查找指定的元素,返回该元素第一次出现的索引。如果未找到该元素,则返回 -1。

printAll功能:按顺序输出数组中的所有元素,元素之间用空格分隔,最后换行。

主函数输入初始整数的个数n,以及n个整数保存到整型数组对象中,然后分别调用对象的各个方法,验证功能的有效性。

主函数的代码已经给出,不要修改,在指定的位置完成IntArray类的代码即可。

STL主要由5大模块组成,彼此协同工作,形成"数据结构+算法"的高效解决方案:

①容器(Containers)------存储数据的"盒子"

容器是用于存储和组织数据的类模板,根据数据结构特性分为两类:

序列式容器:数据按插入顺序存储,元素位置与值无关(类似"数组")。常见类型:vector(动态数组,随机访问高效)、list(双向链表,插入删除高效)、deque(双端队列,头尾操作高效)。

关联式容器:数据按键值(Key)存储,支持快速查找(类似"字典")。常见类型:set(无重复元素的集合,默认排序)、map(键值对映射,默认按键排序);C++11后新增unordered_set/unordered_map(基于哈希表,查找平均O(1))。

②算法(Algorithms)------操作数据的"工具"

算法是处理容器中数据的通用函数模板,覆盖排序、查找、遍历、修改等常见操作,例如:

sort()(排序)、find()(查找)、reverse()(反转)、copy()(复制)、accumulate()(求和)。

算法不依赖具体容器类型,通过迭代器与容器解耦("算法操作迭代器,而非直接操作容器")。

③迭代器(Iterators)------连接容器与算法的"桥梁"

迭代器是一种类似指针的对象,用于遍历容器中的元素。它为不同容器提供统一的访问接口,让算法无需关心容器内部实现。

根据功能强弱,迭代器分为:输入/输出迭代器、前向迭代器、双向迭代器(如list)、随机访问迭代器(如vector)。

④函数对象(Function Objects,仿函数)------可调用的"自定义逻辑"

函数对象是重载了operator()的类或结构体,可作为参数传递给算法,实现自定义操作(如自定义排序规则)。

例如:sort(v.begin(), v.end(), greater<int>())中,greater<int>是一个预定义的函数对象,实现降序排序。

⑤适配器(Adapters)------调整组件行为的"转换器"

适配器通过修改现有组件的接口,使其适配特定场景。例如:

容器适配器:stack(栈,基于deque实现)、queue(队列,基于deque实现);

迭代器适配器:reverse_iterator(反向迭代器,用于逆序遍历)。

①vector------动态数组(最常用)

核心特性:底层用连续内存存储,类似原生数组,但支持自动扩容。当容量不足时,会重新分配更大的内存(通常是原容量的1.5或2倍),并拷贝原有数据。T* data = new Tn;

优势:

随机访问(vi或v.at(i))效率极高(O(1));

尾部插入(push_back())均摊时间复杂度O(1)(仅扩容时O(n));

内存连续,对CPU缓存友好(比链表更快)。

劣势:

中间或头部插入/删除需移动元素(O(n));

扩容时可能导致原有迭代器/指针失效(因内存重新分配)。

典型场景:存储需要频繁访问的数据集(如用户列表、日志数组)。

②deque------双端队列

核心特性:底层由多个"块"(segment)组成的动态数组,每个块内部连续,块之间用指针连接,支持双端高效操作。

优势:

头尾插入/删除(push_front()/pop_front())时间复杂度O(1)(无需移动元素);

随机访问效率接近vector(O(1),通过块索引+块内偏移计算)。

劣势:

中间插入/删除仍需移动元素(O(n));

内存非完全连续,缓存利用率略低于vector。

典型场景:需要双端操作的场景(如任务队列、BFS遍历的队列)。

③list------双向链表

核心特性:底层是双向链表(每个节点包含前驱和后继指针),元素存储在非连续内存中。

优势:

任意位置插入/删除(insert()/erase())时间复杂度O(1)(仅需修改相邻节点指针);

插入/删除不影响其他元素的迭代器(除被删除元素外)。

劣势:

不支持随机访问(无法用\[\]或at(),只能通过迭代器顺序遍历);

内存开销大(每个元素需额外存储两个指针);

遍历效率低(非连续内存,缓存不友好)。

典型场景:需要频繁插入删除中间元素(如文本编辑器的撤销/重做链表)。

④forward_list------单向链表(C++11)

核心特性:底层是单向链表(每个节点仅含后继指针),比list更节省内存(无反向指针)。

优势:

内存占用更小(每个节点仅1个指针);

单向插入/删除操作与list同为O(1)。

劣势:

无法反向遍历(仅支持前向迭代器);

不支持size()操作(获取长度需遍历,O(n));

功能比list更受限(如无push_back(),仅支持push_front())。

典型场景:空间敏感且仅需单向操作(如简单的链式结构)。

⑤array------固定大小数组(C++11)

核心特性:底层是固定大小的连续数组(编译期确定大小),与原生数组(如int arr10)类似,但更安全(封装了STL接口)。

优势:

随机访问O(1),性能与原生数组一致;

支持STL算法(如sort()、copy());

提供size()、front()、back()等安全接口(避免越界)。

劣势:

大小固定(无法动态调整);

不支持插入/删除操作(仅能修改元素值)。

典型场景:已知数据量且需高效访问(如固定长度的配置参数、坐标点数组)。

如何选择序列容器?

需要随机访问 → 优先选vector(数据增长主要在尾部)或deque(头尾需频繁操作);

需要中间频繁插入/删除 → 选list(双向操作)或forward_list(单向+空间敏感);

数据量固定 → 选array(替代原生数组,更安全);

内存效率 → vector(连续内存)> deque(分段连续)> list(双向链表)> forward_list(单向链表)。

常用成员函数(方法):

vector提供了两个函数capacity()和size(),分别用于获取容器容量和容器实际元素个数:

v.capacity();

v.size();

访问某个元素:\[\]或.at()

v.front(); //获取容器头部元素(第一个元素)

v.back(); //获取容器尾部元素(最后一个元素)

从尾部插入和删除元素:push_back(T& e) / pop_back()

      1. deque容器

deque容器与vector容器非常相似,采用的是动态内存管理的方式存储元素的,提供了随机访问的方法,有着和vector容器几乎相同的操作方法。

deque容器的实现是一个双向队列,这是与vector容器最大的区别。deque容器不支持vector容器中的reserve()、capacity()和data()函数,其余函数均支持。deque容器新增的容器操作函数有pop_front()、push_front(),用于从队首和队尾弹出元素,emplace_back()从队尾添加元素。

      1. array容器

array是一个编译阶段确定大小的序列容器,是一个严格按照线性排序的特定数量元素的容器,大小与定义的数组是等效的。与其他容器不同的是,array大小固定,且没有分配容器空间、删除等操作。

创建array容器

array容器由类模板定义,创建array容器的时候需要指定元素类型和元素个数,这是与vector定义不同的地方,示例代码如下:

array<int,3>a1; //定义array容器a1,未初始化

array<int,3>a1={1,2,3}; //定义array容器a2,使用列表初始化方式初始化

#include <array>

#include <iostream>

using namespace std;

int main()

{

array<int, 3>c = { 1,2,3 };

array<int, 3>c1 = { 2,3,4 };

array<int, 3>::iterator pos;

c.swap(c1);

for (pos = c.begin();pos != c.end();++pos) {

cout << *pos << " ";

}

return 0;

}

      1. list容器

list容器是一个双向链表,因为同为序列式容器,所以它的接口大部分都与vector和deque相同,因此我们学习起来也比较容易。

list容器是以双向链表形式实现的,list容器中的元素通过指针将前面的元素和后边的元素链接到一起。与vector容器和array容器相比,list容器通过迭代器获取元素和插入元素,使用list容器可以使用大量的算法,提高编程效率。list容器的不足之处是不能直接通过位置访问元素,只能从迭代器获取的位置获取元素。

#include <list>

#include <iostream>

using namespace std;

template<typename T>

void print(list<T> mylist) //定义函数模板,输出list容器元素

{

typename list<T>::iterator it; //创建list的iterator迭代器

for (it = mylist.begin(); it != mylist.end(); it++)

cout << *it << " ";

cout << endl;

}

int main() {

list<int> lt; //创建空的list容器lt

for (int i = 0; i < 10; i++)

lt.push_back(i + 1); //向容器中添加元素

cout << "输出list容器中的元素:" << endl;

print(lt); lt.pop_back(); //删除最后一个元素

lt.push_front(5); //在头部添加元素5

cout << "再次输出list容器中的元素:" << endl;

print(lt); lt.remove(5);

cout << "删除5之后,输出list容器中的元素:" << endl;

print(lt);

return 0;

}

      1. forward_list容器

forward_list容器由单链表实现。在forward_list容器中,除了最后一个元素,每个元素与下一个元素通过指针链接。由于是单链表实现的,因此forward_list容器只能向后迭代。

forward_list容器不支持insert()函数和erase()函数,但它提供了insert_after()函数和erase_after()函数用于插入和删除元素。

insert_after(pos,val); //将元素val插入到pos位置之后

insert_after(pos,begin,end); //在pos位置插入[begin,end)区间内的元素

erase_after(pos); //删除pos位置之后的元素

erase_after(begin,end); //删除[begin,end)区间内的元素

      1. 关联容器概述

关联型容器所有元素都是经过排序的,关联型容器都是有序的。它的每一个元素都有一个键(key),容器中的元素是按照键的取值升序排列的。

关联型容器内部实现为一个二叉树,在二叉树中,每个元素都有一个父节点和两个子节点,左子树的所有元素都比自己小,右子树的所有元素都比自己大。

      1. set与multiset容器

set与multiset都是集合,用于存储一组相同数据类型的元素。两者的区别是set用来存储一组无重复的元素,而multiset允许有重复的元素。

集合支持插入、删除、查找等操作,但集合中的元素值不可以直接修改,因为这些元素都是自动排序的,如果想修改某一个元素的值,必须先删除原有的元素,再插入新的元素。

set与multiset都重载了多个构造函数,因此创建set和multiset容器的方式有多种。

set与multiset还提供了查找函数find()和统计函数count()。

s.find(elem);

s.count(elem);

set与multiset提供了insert()函数与erase()函数,用于向容器中插入和删除元素。insert()函数主要有三种重载形式,分别如下所示:

s.insert(elem); //在容器中插入元素elem

s.insert(pos, elem); //在pos位置插入元素elem(pos是为了提高效率,如果pos不合适,就退化为普通的插入)

s.insert(begin, end); //在容器中插入[begin, end)区间的元素

#include <set>

#include <iostream>

using namespace std;

int main() {

set<int, greater<int>> s; //创建一个set容器s,元素按降序排列

multiset<char> ms; //创建一个multiset容器ms

//向s中插入元素

pair<set<int>::iterator, bool> ps;

ps = s.insert(12);

if (ps.second == true)

cout << "insert success" << endl;

s.insert(39);

s.insert(32);

s.insert(26);

//向ms中插入元素

ms.insert('a');

ms.insert('z');

ms.insert('T');

ms.insert('u');

ms.insert('u');

//输出两个容器中的元素

set<int>::iterator its; //创建s容器的迭代器,用于获取元素

cout << "s容器中元素:";

for (its = s.begin(); its != s.end(); its++)

cout << *its << " ";

cout << endl;

multiset<char>::iterator itms; //创建ms容器的迭代器

cout << "ms容器中元素:";

for (itms = ms.begin(); itms != ms.end(); itms++)

cout << *itms << " ";

cout << endl;

//查找容器ms中元素u的个数

cout << "ms容器中u元素个数:" << ms.count('u') << endl;

return 0;

}

      1. map和multimap容器

map与multimap中存储的是元素对(key-value),map和multimap容器通常也可理解为关联数组,可以使用键作为下标获取对应的值。关联的本质在于元素的值与某个特定的键相关联,而不是通过位置索引获取元素。

#include <map>

#include <iostream>

using namespace std;

void printm(map<char, double> mymap)//定义printm()函数输出map容器元素

{

pair<char, double> p; //创建pair对象,map中元素是成对的,也要成对输出

map<char, double>::iterator it; //定义迭代器

for (it = mymap.begin(); it != mymap.end(); it++)

{

p = (pair<char, double>) * it; //将迭代器指向的一对元素存放到p中

cout << p.first << "->" << p.second << endl; //输出一对元素

}

}

void printmt(multimap<int, string> mymul) //定义printmt()函数输出multimap容器

{

pair<int, string> p;

multimap<int, string>::iterator it;

for (it = mymul.begin(); it != mymul.end(); it++)

{

p = (pair<int, string>) * it;

cout << p.first << "->" << p.second << endl;

}

}

int main() {

map<char, double> m; //创建一个map容器

m'a' = 1.2;m'b' = 3.6;m'c' = 6.4;

m'd' = 0.8;m'e' = 5.3;m'f' = 3.6;

cout << "map: " << endl;

printm(m);

cout << "map中key = a的值:" << m.at('a') << endl;

cout << "map中key = f的元素出现次数:" << m.count('f') << endl;

multimap<int, string> mt; //创建一个multimap容器

mt.insert(pair<int, string>(1, "chuan"));

mt.insert(make_pair(1, "zhi"));

mt.insert(multimap<int, string>::value_type(3, "bo"));

mt.insert(multimap<int, string>::value_type(4, "ke"));

cout << endl << "multimap: " << endl;

printmt(mt);

cout << "multimap头部元素:";

pair<int, string> p;

p = (pair<int, string>) * mt.begin();

cout << p.first << "->" << p.second << endl;

cout << "multimap尾部元素: ";

p = (pair<int, string>) * (--mt.end());

cout << p.first << "->" << p.second << endl;

return 0;

}

      1. stack适配器

stack存储的元素具有后进先出的特点,stack提供了push()函数向容器中插入元素,同时提供了pop()函数将最后插入的元素从容器中移除。

#include <stack>

#include <vector>

#include <iostream>

using namespace std;

int main() {

vector <int >v = { 1,2,3 };

stack<int, vector <int >> s(v);

s.push(4);

s.emplace(5);

s.pop();

while (!s.empty()) {

cout << " " << s.top();

s.pop();

}

return 0;

}

      1. queue适配器

queue容器适配器是一个先进先出(FIFO)的存储结构,具有队列的特点。容器中的元素只能从一端使用push()函数进行插入,从另一端使用pop()函数进行删除, queue容器适配器不允许一次插入或删除多个元素,且不支持迭代器方法如begin()、rbegin()等。

#include <list>

#include <queue>

#include <iostream>

using namespace std;

int main() {

list <int >l = { 1,2,3 };

queue<int, list <int>> q(l);

q.push(4);

q.emplace(5);

q.pop();

cout << "第一个元素" << q.front() << endl;

cout << "最后一个元素" << q.back() << endl;

while (!q.empty()) {

cout << " " << q.front();

q.pop();

}

return 0;

}

      1. priority queue

priority_queue是优先队列,它是队列的一种,但priority_queue可以按照自定义的方式对队列中的数据进行动态排序。向优先队列中插入或删除元素时,优先队列会动态的调整,以保证队列有序。

#include <list>

#include <queue>

#include <iostream>

using namespace std;

class Comp {

public:

bool operator()(int x, int y) { return x > y; }

};

template<class T>

void print(T& q) {

while (!q.empty()) {

cout << q.top() << " "; q.pop();

}

cout << endl;

}

int main() {

priority_queue<int> q;

for (int n : { 1, 4, 9, 6, 7, 2, 8, 3, 5 }) q.push(n);

print(q);

priority_queue<int, vector<int>, greater<int>> q1;

for (int n : { 1, 4, 9, 6, 7, 2, 8, 3, 5 }) q1.push(n);

print(q1);

priority_queue<int, vector<int>, Comp> q2;

for (int n : { 1, 4, 9, 6, 7, 2, 8, 3, 5 }) q2.push(n);

print(q2);

return 0;

}

      1. 输入迭代器与输出迭代器

输入迭代器(Inputiterator),只能一次一个的向前读取元素,并按此顺序传回元素值,是不可重复的单向遍历。

输出迭代器(Outputiterator)与输入迭代器相反,其作用是将元素值逐个写入,即只能逐个元素赋值。输出迭代器也支持对序列进行单向遍历,当把迭代器移到下一个位置后,也不能保证之前的迭代器是有效的。

前向迭代器(Forwarditerator)是输入迭代器和输出迭代器的集合,具有输入迭代器和输出迭代器的全部功能。前向迭代器支持对序列进行可重复的单向遍历,可以多次解析一个迭代器指定的位置,因此可以对一个值进行多次读写。

双向迭代器是在单向迭代器的基础上增加了一个反向操作,就是它既可以前进,又可以后退,因此它比单向迭代器新增一个功能,进行自减操作,例如it--或者--it。

随机访问迭代器在双向迭代器的基础上,又支持直接将迭代器向前或向后移动n个元素,而且还支持比较运算的操作,因此随机访问迭代器的功能几乎和指针一样。

      1. 算法概述

STL中提供的所有算法都包含在三个头文件中:<algorithm>、<numeric>、<functional>。

● algorithm是最大的一个头文件,它由一大堆函数模板组成,其中涉及到的功能有比较、交换、查找、遍历、复制、修改、删除、合并、排序等。

● 头文件numeric很小,只包括几个在序列中进行简单数学运算的函数模板,以及加法和乘法在序列中的一些操作。

● 头文件functional中则定义了一些类模板,用于声明一些函数对象。

for_each()算法

for_each()属于非可变序列算法,此算法非常灵活,可以同时处理修改每一个元素,其函数定义如下所示:

template<typename InputIterator, typename Function>

for_each(InputIterator begin, InputIterator end, Function func);

for_each()算法对[begin, end)区间中的每个元素都调用func函数(对象)进行操作,它不会对区间中的元素做任何修改,也不会改变原来序列的元素次序。

find()算法

find()也属于非可变序列算法,用于在指定区间查找某一元素是否存在,其函数原型如下所示:

template<typename InputIterator, typename T>

InputIterator find(InputeIterator first, InputIterator last, const T& value);

find()算法用于在[begin, last)区间查找value元素是否存在,如果存在,就返回指向这个元素的迭代器,如果不存在,就返回end。

copy()算法

对于copy()函数,我们并不陌生,在讲解迭代器几次都用到了copy()函数,它的功能是完成元素的复制,其函数原型如下所示:

template<typename InputIterator, typename OutputIterator>

OutputIterator copy(InputIterator first, InputIterator last, OutputIterator DestBeg);

copy()函数实现将[first, last)区间的元素复制到另一个地方,这个地方的起始位置为DestBeg。

sort()算法

sort()属于可变序列算法,它支持对容器中的所有元素进行排序,函数的原型有如下两种形式:

template<typename RanIt> //第一种形式

void sort(RanIt first, RanIt last);

template<typename RanIt, typename Pred> //第二种形式

void sort(RanIt first, RanIt last, Pred op);

accumulate()算法

accumulate()算法属于数值算法,它的原型如下所示:

template<typename InputIterator, typename T> //第一种形式

T accumulate(InputIterator first, InputIterator last, T t);

template<typename InputIterator, typename T,typename Pred> //第二种形式

T accumulate(InputIterator first, InputIterator last, T t, Pred op);

accumulate()函数的功能是将[first, last)区间内的数值累加,累加的初始值为t,它的返回值是元素累加结果。第二种形式可以按照指定的规则将元素相加。

#include <vector>

#include <algorithm>

#include <numeric>

#include <iostream>

using namespace std;

template<class T>

class Multi { //类模板

private:

T value;

public:

Multi(const T& v) :value(v) {} //构造函数

void operator()(T& elem) const { elem *= value; } //重载()运算符

};

void print(int elem) { //打印元素

cout << elem << " ";

}

int main() {

int arr\[\] = { 21, 4, 55, 22, 46, 79, 9, 5, 78, 34, 100 };

vector<int> v;

v.assign(arr, arr + sizeof(arr) / sizeof(int)); //用数组给v容器赋值

//调用for_each()函数将容器中每个元素都乘以2

for_each(v.begin(), v.end(), Multi<int>(2));

//调用copy()构造函数将容器中元素输出

copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));

cout << endl;

//调用find()算法查找容器中是否存在值为200的元素

vector<int>::iterator it = find(v.begin(), v.end(), 200);

if (it != v.end())

cout << "容器中有值为200的元素" << endl;

else

cout << "容器中不存在值为200的元素" << endl;

sort(v.begin(), v.end()); //调用sort()算法将容器中元素从小到大排列

cout << "排序之后:" << endl;

copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));

cout << endl;

int sum = accumulate(v.begin(), v.end(), 0); //累加容器中元素

cout << "sum = " << sum << endl;

return 0;

}

    1. 练习

copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));这个