轻松上手C++的标准模板库的使用

文章目录

  • 简介
    • 容器分类
    • 迭代器
    • 向量容器——vector(顺序容器)
      • 向量容器的特性
      • 向量容器的常用方法
      • 向量容器的遍历
    • string 字符串(顺序容器)
      • string 字符串的声明
      • string的常用方法
      • string字符串的操作和遍历
    • stack - 堆栈容器(顺序容器)
      • stack 堆栈容器的声明
      • stack 堆栈容器的常用方法
      • stack 堆栈容器的操作和遍历
    • queue - 队列容器(顺序容器)
      • queue 队列容器的声明
      • queue 队列容器的常用方法
      • queue 队列容器的操作和遍历
    • deque - 双端队列容器(顺序容器)
      • deque 双端队列容器的声明
      • deque 双端队列容器的常用方法
      • deque 双端队列容器的操作和遍历
    • set - 集合容器(有序关联容器)
      • set 集合容器的声明
      • set 集合容器的常用方法
      • set集合容器的操作和遍历
    • map - 映照容器(有序关联容器)
      • map 映照容器的声明
      • map 映照容器的常用方法
      • map 映照容器的操作和遍历
    • 容器通用方法说明
    • 1.empty()方法
      • 2.size()方法
      • 3.clear()方法
      • 4.begin()、end()方法

简介

STL Standard Template Library - 标准模板库又称 C++ 泛型库
C++ STL(标准模板库)是一套功能强大的 C++模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈。
c++标准模板库的核心包括以下三个组件:

组件 描述
容器(Containers) 容器是用来管理某一类对象的集合。c++提供了不同类型的容器,比如queue、vector、map等
算法(Algorithms) 算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换登操作
迭代器(iterators) 迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集

这三个组件都带有丰富的预定义函数,帮助我们通过简单地方式处理复杂的任务。

C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树等,在封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的常用操作,如:插入、排序、删除、查找等。

容器分类

  1. 顺序容器:vector string(不是类模板) list forward_list deque queue stack
  2. 有序关联容器:: map multimap set multist
  3. 无序关联容器: unordered_map unordered_multimap undered_set dundered_multiset

迭代器

迭代器是一种检查容器内元素并遍历元素的数据类型。C++更趋向于使用迭代器而不是下标操作,因为标准库为每一种标准容器(如vector)定义了一种迭代器类型,而只有少数容器(如vector)支持下标操作访问容器元素。

使用方法: 容器类型<变量类型>::iterator 迭代器名;


vector<int>::iterator p;  
//p就是储存int 类型数据的vector容器的迭代器

迭代器的遍历

vector <int> v;
vector<int>::iterator p;
for(p=v.begin();p!=v.end();p++)
cout << *p <<' ';

可以看到迭代器指向的元素是与指针类似,可以使用 “++” 自增运算符。但是注意,迭代器只能使用 p++/p-- 不能使用 ++p/- -p 。因为 p++ 实际上是一个特殊的操作,相当于链表中的 p = p->next 在迭代器操作中的简写。而 p 本身也可以用 *p 的形式来表示指向的元素,包括后面的【映射容器】中有两个元素,也可以使用 p->first 和 p->second 来指向其中的内容,与结构体指针也是有异曲同工之妙,关于多个内容的容器,后续会有具体的例子

迭代器实质上是指针,也就是指向的数据的地址,因此会有【迭代器地址】的概念。

但是这个迭代器是有储存类型的,因此并不与普通的“指针变量”完全通用,只能说“形式上类似”,“实质上相同”。再次强调,使用方法是不完全相同的。

向量容器——vector(顺序容器)

向量(vector)是一个封装了动态大小数组的顺序容器,你可以将 vector 容器理解为一个高级的数组,与数组一样能够通过下标来随机存取,但并不需要提前声明容器容量(当然也可以提前声明)。跟其他类型的容器一样,它能够存放各种类型的对象。可以简单地认为,向量是一个能够存放任意类型的动态数组

向量容器的特性

  1. 顺序序列: 向量容器中的元素按照严格的线性顺序排序。
  2. 动态数组: 支持对序列中的任意元素快速直接访问,可以通过指针操作。提供了在序列末尾相对快速地添加/删除元素操作
  3. 能够感知内存分配: 容器使用一个内存分配器对象来动态的处理他的存储需求

向量容器的常用方法

什么是方法
“方法”是面对对象思想中的一个成分,与“函数”类似,也有传入变量,返回变量的定义。

“函数”面对的是整个程序的使用,“方法”针对的是“对象”的操作
“对象”也可以理解为一个“变量”,“对象”可以使用的“专属的函数”就是“方法”
函数:函数名(参数);
方法:对象变量名.方法名(参数);

v.push_back(e);                 //从容器末端追加一个元素,容器末端自动扩张
v.begin();                      //返回向量容器的首地址,也就是第一个元素的迭代器地址
v.end();                        //返回向量容器的尾地址,注意尾地址是最后一个元素的下一个位置的迭代器地址
v.insert(v.begin() + n, e);     //往指定地址的位置插入一个元素,如例:在 n 号元素**前**插入num
v.erase(p0[, p1]);              //删除一个元素,或删除一段元素,p 为迭代器地址
v.size()                        //返回 v 的大小(元素个数)
v.empty()                       //如果容器为空,返回“真”,如果不为空,返回“假”
v.clear()                       //清空容器内容

向量容器的遍历

迭代器遍历,如上例;

string 字符串(顺序容器)

string 是 C++ 中直接提供的一种新的【数据类型】,字符串变量不同于字符数组构成的字符串,string 型的变量不需要提前定义长度,也就是说它的长度是 可变的
它是一种顺序表的结构,元素是char类型的字符使用 null 字符 ‘\0’ 终止的一维字符数组。

string 字符串的声明

string 型变量的使用并不需要特殊的头文件,但是一部分string字符串的操作需要头文件 #include< string >

string的常用方法

s.append(str);                  //从字符串末端追加一个字符串,注意只能是字符串不能是字符
s.begin();                      //返回字符串的首地址,也就是第一个字符的迭代器地址
s.end();                        //返回字符串的尾地址,也就是最后一个字符的下一个位置的迭代器地址
s.insert(p, str);               //往指定位置插入一个字符串,p 是整数下标
s.insert(p, c);                 //往指定迭代器位置插入一个字符,p 是迭代器
s.erase(p0[, p1]);              //删除迭代器所指的元素,或删除一个区间的所有元素,左闭右开
s.replace(step, len, str);      //从 step 开始,将写下来的 len 个字符替换成一个字符串 str。step 和 len 都是整数,str 是字符串。
s.compare(str);                 //比较两个字符串大小,两个字符串相等返回 0 ;s < str 返回一个负数;s > str 返回一个正数
s.find(c/str);                  //返回字符串 s 中第一次出现字符 c 或字符串 str 的下标值。如果没有找到返回 -1
s.length();                     //返回字符串的长度
s.empty();                      //判断字符串是否为空
s.clear();                      //清空字符串,但通常直接赋值空字符串来清空字符串即可: s = "";
s.c_str();                      //返回一个与 C 语言中的字符数组同类型的指针,使 string 类型的变量可以实用 C 中的 printf() 函数和 sscanf() 函数

string字符串的操作和遍历

s1 < / <= / > / >= / == / != s2   //两个字符串可以进行逻辑判
s1 = s2                //字符串之间可以互相赋值
s1 += s2               //在 s1 尾部追加字符串(或字符)
s1 = s2 + s3           //两个字符串可以相加,结果与顺序有关,如例:s2 在前面 s3 在后面

字符数组可以直接赋给string变量,但是反过来不可以

char s2[2000] = "abcd";
string s1 = s2;

string字符串的遍历

string s;

for(i=0;s[i]!='\0';i++)    //这是我常用的遍历的方法
for(char c : s)            //高级for循环遍

int main()
{
    int i,j,k;
    string s;   //定义一个字符串
    s.append("lidonghai");  //在字符串末端添加一个字符串,append只能添加字符串
    cout <<s<<endl;
    s.insert(0,"i`m");    //在指定位置前插入一个字符串
    cout <<s<<endl;
    s.insert(s.begin()+3,' ');  //在指定迭代器位置插入一个字符串
    cout <<s<<endl;
    s.erase(s.begin() + 2); //删除指定迭代器位置的字符
    cout << s << endl;
    s.erase(s.begin() + 2, s.begin() + 6);  //删除指定迭代器区间的字符
    cout << s <<endl;
    s.replace(1, 3, "ooo");    //从 1 号元素开始,将连续的 3 个字符替换成一个字符串 "ooo"
    cout <<s <<endl;

    k =s.length();     //获取字符串长度并赋给  k

    cout << "find 'a' = " << s.find('a') << endl;//查找字符返回下标
    cout << "find \"abc\" = " << s.find("5645") << endl;  //查找字符串返回首个下标,若无则返回随机数
    if (s.find("def") == -1) 
    cout << "Not find" << endl; //查找固定
    else cout << "OK" << endl;

    string::iterator p;  //声明一个字符串的迭代器
    for (p = s.begin(); p != s.end(); p++) //迭代器循环遍历
    cout << *p<<' ';    
    cout << endl;

    for(i=0;s[i]!='\0';i++)
    cout << s[i]<<' ';   //下标循环泵遍历

    return 0;
}

运行结果

lidonghai
i`mlidonghai
i`m lidonghai
i` lidonghai
i`onghai
ioooghai
find 'a' = 6
find "abc" = 4294967295
Not find
i o o o g h a i
i o o o g h a i

stack - 堆栈容器(顺序容器)

堆栈是一个线性表,插入和删除只在表的一端进行。就像一个一个桶,只能从桶口往里放或者把最接近桶口的物品拿出。stack堆栈是一个 后进先出的数据存储结构,元素只能从栈顶入栈,从栈顶出栈,只能读取栈顶的元素

stack 堆栈容器的声明

需要头文件 #include < stack >

stack 堆栈容器的常用方法

s.push(e);                      //从栈顶将元素入栈
s.pop();                        //将栈顶元素出栈
s.top();                        //读取栈顶元素
s.empty();                      //判断队列是否为空

stack 堆栈容器的操作和遍历

栈不可迭代遍历!!!所以不能使用begin() 和end()方法

#include 
#include 
using namespace std;
int main() {
        stack<int> s; //定义一个空栈
        s.push(0);
        s.push(1);     //从栈顶入栈 3 个元素
        s.push(2);
        
        cout << s.top() << endl;//读取栈顶元素
       
        s.pop(); //将栈顶出栈
        return 0;
}

queue - 队列容器(顺序容器)

队列是一个【先进先出(First In First Out,FIFO)】的储存结构,元素只能从队尾插入,只能从队首删除。
queue与stack模版非常类似,queue模版也需要定义两个模版参数,一个是元素类型,一个是容器类型,元素类型是必要的,容器类型是可选的,默认为dqueue类型。

queue 队列容器的声明

需要头文件 #include < queue >

queue 队列容器的常用方法

q.push();     //后端入队
q.pop();      //前端出队
q.front();    //读取队首元素
q.back();     //读取队尾元素
q.empty();    //判断队列是否为空

queue 队列容器的操作和遍历

队列不可迭代遍历!!!所以不能使用begin() 和end()方法

#include
#include
using namespace std;
int main ()
{
	queue<int>  q;   //声明一个空的队列容器
    q.push(0);        //从尾部插入元素 0 
	q.push(1);        //从尾部插入元素1
    cout << q.front() << endl;   //读取队首元素
    q.pop();           //最前端的元素出队    
    cout << q.front() << endl;   //读取队首元素
    
   return 0; 
}

运行结果为

0
1

deque - 双端队列容器(顺序容器)

顾名思义是一个可以从两端进行出入操作的队列。内部数据存储结构与 vector 相同,采用线性表顺序存储结构,也可以用下标进行存取。
deque块在头部和尾部都可以插入和删除。而不需要移动任何元素,而不需要移动其他元素(使用push_back()方法在尾部插入元素,会扩张队列,而使用push_front()方法在首部插入元素和使用insert()方法在中间插入元素,只是将原位置上的元素进行覆盖,不会增加新元素)一般来说,当考虑到容器元素的内存分配策略和操作的性能时deque相当于vector更有优势。

deque采用分块的线性存储结构来存储数据,每块的大小一般为512B,将其之称为deque块,所有的deque块使用一个map块进行管理,每个map数据项记录各个deque块的首地址,这样的话,deque块在头部和尾部都可以插入和删除。

deque 双端队列容器的声明

deque <int> d;   //int是容器内元素变量的类型,也可以是其他
vector<double> d(8, 2.33);      //定义一个 d ,容量是 8 ,每个元素的值是2.33

deque 双端队列容器的常用方法

d.push_back(e);     //后端插入元素e,尾部扩充
d.push_front(e)     //前端插入元素e,头部扩充
d.pop_back(e);      //尾部出队
d.pop_front()       //前端出队
d.back();           //读取队尾元素
d,front();          //读取队首元素
d.insert(p,e);      //在迭代器位置之前插入,p是迭代器
d.erase(p,[p1]);    //删除迭代器位置或一段区间中的元素
 

deque 双端队列容器的操作和遍历

int main()
{
    deque <int> d;
    d.push_back(0);
    d.push_back(1);    //后端插入
    d.push_back(2);
    
    for(int i=0;i<d.size();i++)cout <<d[i]<<' ';   //下标方式遍历查看
    cout <<'\n';
    d.push_front(4);
    d.push_front(5);   //前端插入    
    d.insert(d.begin()+2,2);    //中间插入

    deque<int>::iterator p;  //定义迭代器
    for(p=d.begin();p!=d.end();p++)   //迭代器遍历查看
    {
        cout << *p << ' '; cout << endl;
    }

    d.erase(d.begin()+2);   //删除第二个元素;
    
}

运行结果

0 1 2
5 4 2 0 1 2
5 4 0 1 2

set - 集合容器(有序关联容器)

set是关联式容器,set作为一个容器也是个用来存储同一类数据类型的数据集。
在其中每个元素的值都唯一,而且系统能根据元素的值自动排序

set 集合容器实现了红黑树(Red-Black Tree)的平衡二叉检索树的数据结构,在插入元素时,set 容器会自动调整二叉树,生成一个左子节点小于根节点小于右子节点的排序树,并且保证左右子树高度相同,以获得最快的检索速度。

set 容器中不能储存重复的元素,如果插入已经存在的元素,将会没有效果,multiset可以插入重复的元素。

C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树

set 集合容器的声明

需要头文件 #include < set >

set<T> s;                  // T 是容器内元素的变量类型
multiset<T> ms;            // T 是容器内元素的变量类型

set 集合容器的常用方法

//set 容器的方法
s.insert(e);           //将 e 加入集合,自动排序,默认升序,不可重复插入
s.erase(k);            //删除键值为 k 的元素,返回删除元素总数(只有 0 或 1 )
s.find(k);            //查找键值为 k 的元素的位置,返回迭代器地址,若没找到返回 s.end()
//multiset 容器的方法
ms.insert(e);         //将 e 加入集合,自动排序,默认升序,可重复插入
ms.erase(k);          //删除所有键值为 k 的元素,返回删除元素总数
ms.find(k);           //查找第一个键值为 k 的元素的位置,返回迭代器地址,若没找到返回 ms.end()

find会挨个查找set,当到达set.end()时,也就是一个也没找到,返回end

set集合容器的操作和遍历

int main()
{
    set<int> s;  //声明一个set容器 s 
    s.insert(2);   //插入元素
    s.insert(1);   //插入元素
    s.insert(5);   //插入元素
    s.insert(2);   //重复插入,无效

    set<int>::iterator p;  //定义set的迭代器 p
    for(p=s.begin();p!=s.end();p++)
    {
        cout <<*p<<' ';   //迭代器遍历
    }
    cout <<endl;
    cout << s.erase(3)<<endl;   //删除元素3,若存在并删除返回1,否则返回0
    if(s.find(2)==s.end()) 
      cout <<"not found 2"<<endl; //查找元素2,找到返回迭代器位置,没找到返回 set 容器尾地址
    else cout <<"found "<<*s.find(2)<<endl;

    return 0;
}

运行结果

1 2 5
0
found 2

map - 映照容器(有序关联容器)

映照容器是居于数据对的,可以通过 键 ,来索引到 值 的容器,其中的元素是以 键-值对 的形式存储的(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值).
这里说下map内部数据的组织,map内部是自建一颗红黑树(一种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的(默认升序)
对于map中的每个节点存储的是一对信息,包括一个键和一个值,各个节点之间的键值不能重复。

什么是“对(pair)”
pair是将2个数据组合成一组数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存。另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。 pair的实现是一个结构体,主要的两个成员变量是first second 因为是使用struct不是class,所以可以直接使用pair的成员变量。

map 映照容器的声明

需要头文件 #include < map >

//因为 map 储存的是 [键-值对] 所以要定义两个数据类型
map<T, T> m;                // T 是容器内元素的变量类型
multimap<T, T> mm;          // T 是容器内元素的变量类型

map 映照容器的常用方法

//map 容器的方法
m.insert(pair<T, T>(k, v));   //将一个键-值对 k: v 加入集合,自动按键值排序,默认升序,不可重复插入
m.erase(k);                   //删除键值为 k 的元素,返回删除元素总数(只有 0 或 1 )
m.find(k);                    //查找键值为 k 的元素的位置,返回迭代器地址,若没找到返回 m.end()
//multimap 容器的方法
mm.insert(pair<T, T>(k, v));  //将一个键-值对 k: v 加入集合,自动按键值排序,默认升序,可重复插入
mm.erase(k);                  //删除所有键值为 k 的元素,返回删除元素总数
mm.find(k);                   //查找第一个键值为 k 的元素的位置,返回迭代器地址,若没找到返回 mm.end()

map 映照容器的操作和遍历

m[k] = v;                       //可以直接使用这种方式来插入新的键值对,如果此键已经存在则会修改此键对应的值

int main()
{
    map <int ,string> m;  //声明一个map类型的容器m
    m.insert(pair<int,string>(2000,"ldh"));
    m.insert(pair<int,string>(915,"ngup"));  //insert() 方法插入 4 个 键-值对
    m.insert(pair<int, string>(10, "Shell"));
    m.insert(pair<int,string>(2000,"ldh"));   //在map中重复插入无效

    m[12] = "ace";     //使用 键 - 值 的方式直接添加新元素

    m[915] = "ng";      //通过键坐下标,访问元素并直接改变对应值

    map<int,string>::iterator p;   //声明一个map的迭代器
    for (p=m.begin();p!=m.end();p++)    //迭代器类型要对应
    cout <<(*p).first<<" : "<<(*p).second<<endl;//使用迭代器遍历查看元素

    cout << m.erase(915) << endl;//删除键值为 915 的元素,返回删除元素总数(只有 0 或 1 )

    //通过键值查找元素,找到返回迭代器位置,没找到返回 map 容器尾地址
    if (m.find(16) == m.end()) cout << "Not found 16" << endl;
    else cout << "Found " << (*m.find(16)).second << endl;
    if (m.find(12) == m.end()) cout << "Not found 12" << endl;
    else cout << "Found " << (*m.find(12)).second << endl;
    return 0;
}

运行结果

10 : Shell
12 : ace
915 : ng
2000 : ldh
1
Not found 16
Found ace

容器通用方法说明

1.empty()方法

上面介绍的所有容器都适用,使用方法 容器变量名.empty() 如果容器为空,返回布尔值(bool)真(true),如果容器不为空返回假(false).

2.size()方法

上面介绍的所有容器都适用,使用方法 容器变量名.size() 可以获取容器内的元素的个数,字符串变量 string 可以获取字符串长度。

3.clear()方法

上面介绍的所有容器除了 栈和队列 都适用,使用方法 容器变量名.clear() 可以将容器内的内容全部清空。

4.begin()、end()方法

上面介绍的所有容器除了 栈和队列 都适用,使用方法 容器变量名.begin() 容器变量名.end() 可以获得容器的首地址,尾地址。

栈和队列不能使用有些方法的原因是,它们并不是【可迭代对象】。

STL基础部分完结,感谢H_on学长提供的模板~

你可能感兴趣的:(c++,stl,数据结构)