20- C++ STL标准模板库-8 (C++)

news2024/11/15 19:42:09

第十章

10.1 基本概念

STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称。现然主要出现在C++中,但在被引入C++之前该技术就已经存在了很长的一段时间。

STL的从广义上讲分为三类:algorithm(算法)、container(容器)和iterator(迭代器),容器和算法通过迭代器可以进行无缝地连接。几乎所有的代码都采 用了模板类和模板函数 的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。在C++标准中,STL被组织为下面的13个头文件:

<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack> 和<utility>

使用STL的好处

1)STL是C++的一部分,因此不用额外安装什么,它被内建在你的编译器之内。

2)STL的一个重要特点是数据结构和算法的分离。尽管这是个简单的概念,但是这种分离确实使得STL变得非常通用。

例如,在STL的vector容器中,可以放入元素、基础数据类型变量、元素的地址;

STL的sort()函数可以用来操作vector,list等容器。

3)程序员可以不用思考STL具体的实现过程,只要能够熟练使用STL就OK了。这样他们就可以把精力放在程序开发的别的方面。

4)STL具有高可重用性,高性能,高移植性,跨平台的优点。

  • 高可重用性:STL中几乎所有的代码 都采用了模板类和模版函数的方式 实现,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。

  • 高性能:如map可以高效地从十万条记录里面查找出指定的记录,因为map是采用红黑树的变体实现的。(红黑树是平横二叉树的一种)

  • 高移植性:如在项目A上用 STL编写的模块,可以直接移植 到项目B上。

  • 跨平台:如用windows的Visual Studio编写的代码可以在Mac OS的XCode上直接编译。

10.2 容器

10.2.1 容器的分类

1、序列式容器(Sequence containers)

  • 每个元素都有固定位置--取决于插入时机和地点,和元素值无关。

  • vector、deque、list、stack、queue

2、关联式容器(Associated containers)

  • 元素位置取决于特定的排序准则,和插入顺序无关

  • set、multiset、map、multimap

10.2.2 vector 容器

1、 vector容器简介

  • vector是将元素置于一个动态数组中加以管理的容器

  • vector可以随机存取元素(支持索引值直接存取, 用[]操作符或at()方法,这个等下会详讲)。

  • vector尾部添加或移除元素非常快速。但是在中部或头部插入元素或移除元素比较费时

2、vector对象的默认构造

vector采用模板类实现,vector对象的默认构造形式

vector<T> vecT;
vector<int> vecInt;  //存放int的vector的容器
vector<float> vecFloat;  //存放float的容器
vector<string> vecString;  //存放string的容器

class CA{};
vector<CA*> vecpCA;  //存放CA对象的指针的容器
vector<CA> vecCA;   // 存放CA对象的容器

3、vector对象的带参数构造

理论知识

  • vector(beg,end);   // 构造函数 将[beg, end)区间中的元素拷贝给本身。注意该区间是左闭右开的区间。

  • vector(n,elem);     // 构造函数将n个elem拷贝给本身

  • vector(const vector &vec);    // 拷贝构造函数

int iArray[] = {0, 1, 2, 3, 4};
vector<int> vecIntA(iArray, iArray+5);
// 用构造函数初始化容器
vector<int> vecIntB(vecIntA.begin(), vecIntA.end());
vector<int> vecIntE(vecIntA.begin(), vecIntA.begin() + 3);
vector<int> vecIntC(3, 9);
vector<int> vecIntD(vecIntA);

4、vector的赋值

理论知识

  • vector.assign(beg,end); //将[beg, end)区间中的数据拷贝赋值给本身。注意该区间是左闭右开的区间。

  • vector.assign(n,elem); //将n个elem拷贝赋值给本身。

  • vector& operator=(const vector &vec); //重载等号操作符

  • vector.swap(vec); // 将vec与本身的元素互换。

int main() {
    vector<int> vecIntA, vecIntB, vecIntC;
    int iArray[] = {0, 1, 2, 3, 4};
    vecIntA.assign(iArray, iArray + 5);
    vecIntB.assign(vecIntA.begin(), vecIntA.end());
    vecIntC.assign(3, 9);
    vector<int> vecIntD;
    vecIntD = vecIntA;
    vecIntA.swap(vecIntD);
    return 0;
}

5、vector的大小

理论知识

  • vector.size();   //返回容器中元素的个数

  • vector.empty();   //判断容器是否为空

  • vector.resize(num);   //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。

  • vector.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。

> 例如 vecInt是vector 声明的容器,现已包含1,2,3元素。

> int iSize = vecInt.size();   //iSize == 3;

> bool bEmpty = vecInt.empty();   // bEmpty == false;

> 执行vecInt.resize(5);   //此时里面包含1,2,3,0,0元素。

> 再执行vecInt.resize(8,3);   //此时里面包含1,2,3,0,0,3,3,3元素。

> 再执行vecInt.resize(2);   //此时里面只包含1,2元素。

6、vector末尾的添加移除操作

  • vector vecInt;

  • vecInt.push_back(1);    //在容器尾部加入一个元素

  • vecInt.push_back(3);

  • vecInt.push_back(5);

  • vecInt.push_back(7);

  • vecInt.push_back(9);

  • vecInt.pop_back();

  • vecInt.pop_back();

7、vector的数据存取

理论知识:

  • vec.at(idx);    //返回索引idx所指的数据,如果idx越界,抛出out_of_range异常。

  • vec[idx];    //返回索引idx所指的数据,越界时,运行直接报错

int main() {
    vector<int> vecInt;
    vecInt.at(2) == vecInt[2];
    vecInt.at(2) = 8;
    int iF = vecInt.front();
    int iB = vecInt.back();
    vecInt.front() = 11;
    vecInt.back() = 19;
    
    return 0;
}

8、vector的插入

理论知识

  • vector.insert(pos, elem);    //在pos位置插入一个elem元素的拷贝,返回新数据的位置。

  • vector.insert(pos, n, elem);    //在pos位置插入n个elem数据,无返回值。

  • vector.insert(pos, beg, end);    //在pos位置插入[beg,end)区间的数据,无返回值

简单案例:

int main() {
    vector<int> vecA;
    vector<int> vecB;
    
    vecA.push_back(1);
    vecA.push_back(3);
    vecA.push_back(5);
    vecA.push_back(7);
    vecA.push_back(9);
    
    vecB.push_back(2);
    vecB.push_back(4);
    vecB.push_back(6);
    vecB.push_back(8);
    
    vecA.insert(vecA.begin(), 11);
    vecA.insert(vecA.begin() + 1, 2, 33);
    vecA.insert(vecA.begin(), vecB.begin(), vecB.end());
    return 0;
}

9、vector的删除

理论知识

  • vector.clear();    // 移除容器的所有数据

  • vec.erase(beg,end);    // 删除[beg,end)区间的数据,返回下一个数据的位置。

  • vec.erase(pos);    // 删除pos位置的数据,返回下一个数据的位置。

简单案例:

vector<int> vecInt;
vecInt.erase(vecInt.begin(), vecInt.begin() + 3);
vecInt.clear();

10、迭代器

1)迭代器的基本概念

  • 什么是迭代器:

    • 迭代器是一种 检查容器内元素并且遍历容器内元素 的数据类型。

  • 迭代器的作用:

    • 迭代器提供 对一个容器中的对象的访问方法,并且定义了容器中对象的范围。

  • 为什么需要迭代器:

    • STL提供了多种容器,每种容器的实现原理各不相同,如果没有迭代器我们需要记住每一种容器中对象的访问方法,很显然这样会变得非常麻烦。

    • STL提供的许多容器中都实现了一个迭代器用于对容器中对象的访问,虽然每个容器中的迭代器的实现方式不一样,但是对于用户来说操作方法是一致的,也就说通过迭代器统一了对所有容器的访问方式。例如:访问当前元素的下一个元素我们可以通过迭代器自增进行访问。

  • 迭代器是为了提高编程效率而开发的。

  • 迭代器的本质:

    • 迭代器是容器类中专门实现的一个访问容器中数据的内嵌类(类中类)

为了统一每个容器中对于迭代器的操作,在容器类中会使用typedef将迭代器类进行别名定义,别名为:iterator

迭代器类对容器中元素的访问方式:指针

迭代器类的具体实现:为了隐藏每个容器中迭代器的具体实现,也为了统一用户对于每个容器中迭代器的访问方式,用户可以把迭代器当成一个指针对容器中的元素进行访问。但是因为迭代器不是指针,因此在迭代器类中我们需要对 * 、->、前置++/--、后置++/--等操作符进行重载。

template <typename T>
class list_iterator
{
public:
    T &operator() const {}
    node<T>*operator->() const {}
    list_iterator &operator++() {}
    list_iterator operator++(int) {}
    bool operator==(const list_iterator &t) const {}
    bool operator!=(const list_iterator &t) const {}
};

2)vector容器的迭代器

每种容器类型都定义了自己的迭代器类型,如vector:

vector<int>::iterator iter;

3)vector容器迭代器类中的成员函数

vector容器的迭代器属于随机访问迭代器”:迭代器一次可以移动多个位置

4)begin和end操作

每种容器都定义了一队命名为begin和end的函数,用于返回迭代器。如果容器中有元素的话,由begin返回的元素指向第一个元素。

vector<int>::iterator iter=v.begin(); 

由end返回的迭代器指向最后一个元素的下一个, 若v为空,begin和end返回的相同。

  • ++iter;    //使迭代器自增指向下一个元素

  • ==和!=操作符来比较两个迭代器,若两个迭代器指向同一个元素,则它们相等,否则不想等。

迭代器使用举例:

for(vector<int>::iterator iter = v.begin(); iter!=v.end(); iter++)
    *iter = 0;

5)迭代器的算术操作

  • iter+n;   //迭代器iter加上n,指在当前迭代器所在的位置i(如在vector第一个元素位置)之前加上n个元素后的位置。

  • iter-n;   //迭代器iter减去n,指在当前迭代器的所在位置之后减n个元素的位置

5)迭代器失效

  • 插入元素导致迭代器失效

我们先看这么一段代码:

int main() {
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);

    vector<int>::iterator it1 = v.begin() + 3;
    v.insert(it1, 8);
    cout << *it1 << endl;
    return 0;
}

运行上面这段代码,我们会发现输出的结果并不是8,甚至有可能会导致程序崩溃。这是为什么呢?

因为在insert时,vector可能需要进行扩容,而扩容的本质是new一块新的空间,再将数据迁移过去。而我们知道,迭代器的内部是通过指针访问容器中的元素的,而插入后,若vector扩容,则原有的数据被释放,指向原有数据的迭代器就成了野指针,所以迭代器失效了。

而解决的办法很简单,insert函数提供了返回值,这个返回值是指向插入之后的val的迭代器。我们只需要保存返回的迭代器,并使用这个新的迭代器即可。

int main() {
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);

    vector<int>::iterator it1 = v.begin() + 3;
    it1 = v.insert(it1, 8);
    cout << *it1 << endl;
    return 0;
}
  • 删除元素导致迭代器失效

我们先看这们一段代码:

int main() {
    vector<int> cont = {1, 2 ,3 ,4 ,5, 6, 5};
    for (iter = cont.begin(); iter != cont.end(); iter++)
    {
        if (*iter == 3)
            cont.erase(iter);
    }
    return 0;
}

对于序列式容器(如vector,deque),序列式容器就是数组式容器,删除当前的iterator会使后面所有元素的iterator都失效。这是因为vetor,deque使用了连续分配的内存,删除一个元素导致后面所有的元素会向前移动一个位置。所以不能使用erase(iter++)的方式,还好erase方法可以返回下一个有效的iterator。

解决办法:

int main() {
    vector<int> cont = {1, 2 ,3 ,4 ,5, 6, 5};
    vector<int>::iterator iter; // 声明迭代器的类型
    for (iter = cont.begin(); iter != cont.end(); iter++)
    {
        if (*iter == 3)
            iter = cont.erase(iter);
        else
            iter++;
    }
    return 0;
}

10.2.3 deque容器

deque简介:

  • deque 是“double-ended queue”的缩写,和vector一样都是STL的容器,deque是双端数组,而vector是单端的。

  • deque在接口上和vector非常相似,在许多操作的地方可以直接替换。

  • deque可以随机存取元素(支持索引值直接存取, 用[]操作符或at()方法,

  • deque头部和尾部添加或移除元素都非常快速。但是在中部安插元素或移除元素比较费时。

deque与vector在操作上几乎一样,deque多两个函数:

  • deque.push_front(elem);    //在容器头部插入一个数据

  • deque.pop_front();    //删除容器第一个数据

10.2.4 list容器

1、list简介

  • list是一个双向链表容器,可高效地进行插入删除元素。

  • list不可以随机存取元素,所以不支持at.(pos)函数与[]操作符。It++(ok) it+5(err)

2、list对象的默认构造

list采用模板类实现,对象的默认构造形式:list &#x3c;T> lst 如:

list<int> lstInt;            //定义一个存放int的list容器。
list<float> lstFloat;        //定义一个存放float的list容器。
list<string> lstString;      //定义一个存放string的list容器。

3、list头尾的添加移除操作

  • list.push_back(elem);   // 在容器尾部加入一个元素

  • list.pop_back();    // 删除容器中最后一个元素

  • list.push_front(elem);    // 在容器开头插入一个元素

  • list.pop_front();    // 从容器开头移除第一个元素

list<int> lstInt;
lstInt.push_back(1);
lstInt.push_back(3);
lstInt.push_back(5);
lstInt.push_back(7);
lstInt.push_back(9);
lstInt.pop_front();
lstInt.pop_front();
lstInt.push_front(11);
lstInt.push_front(13);
lstInt.pop_back();
lstInt.pop_back();

4、list的数据存取

  • list.front();    // 返回第一个元素。

  • list.back();   // 返回最后一个元素。

list<int> lstInt;
lstInt.push_back(1);
lstInt.push_back(3);
lstInt.push_back(5);
lstInt.push_back(7);
lstInt.push_back(9);
 
int iFront = lstInt.front(); //1
int iBack = lstInt.back(); //9
lstInt.front() = 11; //11
lstInt.back() = 19; //19

5、list与迭代器

list 容器的迭代器是“双向迭代器”:双向迭代器从两个方向读写容器。除了提供前向迭代器的全部操作之外,双向迭代器还提供前置和后置的自减运算

  • list.begin();    // 返回容器中第一个元素的迭代器。

  • list.end();      // 返回容器中最后一个元素之后的迭代器。

  • list.rbegin();  // 返回容器中倒数第一个元素的迭代器。

  • list.rend();    // 返回容器中倒数最后一个元素的后面的迭代器。

list<int> lstInt;
lstInt.push_back(1);
lstInt.push_back(3);
lstInt.push_back(5);
lstInt.push_back(7);
lstInt.push_back(9);
 
for (list<int>::iterator it=lstInt.begin(); it!=lstInt.end(); ++it)
{
    cout << *it;
    cout << " ";
}
 
for (list<int>::reverse_iterator rit=lstInt.rbegin(); rit!=lstInt.rend(); ++rit)
{
    cout << *rit;
    cout << " ";
}

6、list对象的带参数构造

  • list(n,elem);     // 构造函数将n个elem拷贝给本身。

  • list(beg,end);   // 构造函数将[beg,end]区间中的元素拷贝给本身

  • list(const list &lst);   // 拷贝构造函数。

list<int> lstIntA;
lstIntA.push_back(1);
lstIntA.push_back(3);
lstIntA.push_back(5);
lstIntA.push_back(7);
lstIntA.push_back(9);
 
list<int> lstIntB(lstIntA.begin(),lstIntA.end()); //1 3 5 7 9
list<int> lstIntC(5,8); //8 8 8 8 8
list<int> lstIntD(lstIntA); //1 3 5 7 9

7、list的赋值

  • list.assign(beg,end);    // 将[beg, end)区间中的数据拷贝赋值给本身。注意该区间是左闭右开的区间。

  • list.assign(n,elem);     // 将n个elem拷贝赋值给本身。

  • list& operator=(const list &lst);    // 重载等号操作符

  • list.swap(lst);    // 将lst与本身的元素互换。

list<int> lstIntA,lstIntB,lstIntC,lstIntD;
lstIntA.push_back(1);
lstIntA.push_back(3);
lstIntA.push_back(5);
lstIntA.push_back(7);
lstIntA.push_back(9);
 
lstIntB.assign(lstIntA.begin(),lstIntA.end()); //1 3 5 7 9
lstIntC.assign(5,8); //8 8 8 8 8
lstIntD = lstIntA; //1 3 5 7 9
lstIntC.swap(lstIntD); //互换

8、list的大小

  • list.size();       // 返回容器中元素的个数

  • list.empty();    // 判断容器是否为空

  • list.resize(num);   // 重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。

  • list.resize(num, elem);   // 重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。

list<int> lstIntA;
lstIntA.push_back(1);
lstIntA.push_back(3);
lstIntA.push_back(5);
 
if (!lstIntA.empty())
{
    int iSize = lstIntA.size(); //3
    lstIntA.resize(5); //1 3 5 0 0
    lstIntA.resize(7,1); //1 3 5 0 0 1 1
    lstIntA.resize(2); //1 3
}

9、list的插入

  • list.insert(pos,elem);    // 在pos位置插入一个elem元素的拷贝,返回新数据的位置。

  • list.insert(pos,n,elem);     // 在pos位置插入n个elem数据,无返回值。

  • list.insert(pos,beg,end);   // 在pos位置插入[beg,end)区间的数据,无返回值。

list<int> lstA;
list<int> lstB;
 
lstA.push_back(1);
lstA.push_back(3);
lstA.push_back(5);
lstA.push_back(7);
lstA.push_back(9);
 
lstB.push_back(2);
lstB.push_back(4);
lstB.push_back(6);
lstB.push_back(8);
 
lstA.insert(lstA.begin(), 11); //{11, 1, 3, 5, 7, 9}
lstA.insert(++lstA.begin(),2,33); //{11,33,33,1,3,5,7,9}
lstA.insert(lstA.begin() , lstB.begin() , lstB.end() );

10、list的删除

  • list.clear();    // 移除容器的所有数据

  • list.erase(beg,end);   // 删除[beg,end)区间的数据,返回下一个数据的位置。

  • list.erase(pos);    // 删除pos位置的数据,返回下一个数据的位置。

  • lst.remove(elem);   // 删除容器中所有与elem值匹配的元素。

list<int>::iterator itBegin=lstInt.begin();
++itBegin;
list<int>::iterator itEnd=lstInt.begin();
++itEnd;
++itEnd;
++itEnd;
lstInt.erase(itBegin,itEnd);
//此时容器lstInt包含按顺序的1,6,9三个元素。

lstA.push_back(3);
lstA.push_back(3);
lstA.remove(3); //将list中所有的3删除
lstA.clear();//容器为空

11、list的反序排列

  • lst.reverse();   // 反转链表,比如lst包含1,3,5元素,运行此方法后,lst就包含5,3,1元素。

list<int> lstA;
lstA.push_back(1);
lstA.push_back(3);
lstA.push_back(5);
lstA.push_back(7);
lstA.push_back(9);
 
lstA.reverse(); //9 7 5 3 1

12、list迭代器失效

  • 删除结点导致迭代器失效

for(list<int>::iterator it=lstInt.being(); it!=lstInt.end(); )    //小括号里不需写  ++it
{
   if(*it == 3)
   {
       lstInt.erase(it);  //以迭代器为参数,删除元素3,并把数据删除后的下一个元素位置返回给迭代器。
   }
}

10.2.5 stack容器

1、Stack简介

  • stack是 堆栈容器,是一种“先进后出”的容器。

  • stack是简单地装饰deque容器而成为另外的一种容器。

2、stack对象的默认构造

> stack采用模板类实现, stack对象的默认构造形式:

stack <int> stkInt;            //一个存放int的stack容器。
stack <float> stkFloat;        //一个存放float的stack容器。
stack <string> stkString;      //一个存放string的stack容器。

3、stack的 push() pop() 方法

stack.push(elem);   //往栈头添加元素
stack.pop();   //从栈头移除第一个元素
 
stack<int> stkInt;  
stkInt.push(1);stkInt.push(3);stkInt.pop();   
stkInt.push(5);stkInt.push(7);  
stkInt.push(9);stkInt.pop();   
stkInt.pop();  
//此时stkInt存放的元素是1,5  

4、 stack对象的拷贝构造与赋值

  • stack(const stack &stk);   // 拷贝构造函数

  • stack& operator=(const stack &stk);   // 重载等号操作符

stack<int> stkIntA;
stkIntA.push(1);
stkIntA.push(3);
stkIntA.push(5);
stkIntA.push(7);
stkIntA.push(9);
 
stack<int> stkIntB(stkIntA); //拷贝构造
stack<int> stkIntC;
stkIntC = stkIntA; //赋值

5、 stack的数据存取

stack.top();   //返回最后一个压入栈元素
stack<int> stkIntA;
stkIntA.push(1);
stkIntA.push(3);
stkIntA.push(5);
stkIntA.push(7);
stkIntA.push(9);
 
int iTop = stkIntA.top(); //9
stkIntA.top() = 19; //19

6、stack的大小

  • stack.empty();    // 判断堆栈是否为空

  • stack.size();       // 返回堆栈的大小

stack<int> stkIntA;
stkIntA.push(1);
stkIntA.push(3);
stkIntA.push(5);
stkIntA.push(7);
stkIntA.push(9);
 
if (!stkIntA.empty())
{
    int iSize = stkIntA.size(); //5
}

10.2.6 queue 容器

1、Queue简介

  • queue是 队列容器,是一种“先进先出”的容器

2、queue对象的默认构造

queue采用模板类实现,queue对象的默认构造形式:queue &#x3c;T> q; 如:

queue<int> queInt;            //一个存放int的queue容器。
queue<float> queFloat;        //一个存放float的queue容器。
queue<string> queString;      //一个存放string的queue容器。

3、queue的 push()pop() 方法

  • queue.push(elem);   // 往队尾添加元素

  • queue.pop();    // 从队头移除第一个元素

queue<int> queInt;
queInt.push(1);queInt.push(3);
queInt.push(5);queInt.push(7);
queInt.push(9);queInt.pop();
queInt.pop();

4、queue对象的拷贝构造与赋值

  • queue(const queue &que);    // 拷贝构造函数

  • queue& operator=(const queue &que);    // 重载等号操作符

queue<int> queIntA;
queIntA.push(1);
queIntA.push(3);
queIntA.push(5);
queIntA.push(7);
queIntA.push(9);
 
queue<int> queIntB(queIntA); //拷贝构造
queue<int> queIntC;
queIntC = queIntA; //赋值

5、queue的数据存取

  • queue.back();   // 返回最后一个元素

  • queue.front();   // 返回第一个元素

queue<int> queIntA;
queIntA.push(1);
queIntA.push(3);
queIntA.push(5);
queIntA.push(7);
queIntA.push(9);
 
int iFront = queIntA.front(); //1
int iBack = queIntA.back(); //9
 
queIntA.front() = 11; //11
queIntA.back() = 19; //19

6、queue的大小

  • queue.empty();   // 判断队列是否为空

  • queue.size();      // 返回队列的大小

queue<int> queIntA; 
queIntA.push(1);   
queIntA.push(3);  
queIntA.push(5);
queIntA.push(7);
queIntA.push(9);
 
if (!queIntA.empty())
{
    int iSize = queIntA.size(); //5
}

10.2.7 Set和multiset容器

1、set/multiset的简介

  • set是一个集合容器,其中所包含的元素是唯一的,集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置。

  • set采用红黑树变体的数据结构实现,红黑树属于平衡二叉树。在插入操作和删除操作上比vector快。

  • set不可以直接存取元素。(不可以使用at.(pos)与[]操作符)。

  • multiset与set的区别:set支持唯一键值,每个元素值只能出现一次;而multiset中同一值可以出现多次

  • 不可以直接修改set或multiset容器中的元素值,因为该类容器是自动排序的。如果希望修改一个元素值,必须先删除原有的元素,再插入新的元素。

2、set/multiset对象的默认构造

set<int> setInt;            //一个存放int的set容器。
set<float> setFloat;     //一个存放float的set容器。
set<string> setString;     //一个存放string的set容器。
multiset<int> mulsetInt;            //一个存放int的multi set容器。
multi set<float> multisetFloat;     //一个存放float的multi set容器。
multi set<string> multisetString;     //一个存放string的multi set容器。

3、set对象的拷贝构造与赋值

  • set(const set &st);   // 拷贝构造函数

  • set& operator=(const set &st);    // 重载等号操作符

  • set.swap(st);    // 交换两个集合容器

set<int> setIntA;
setIntA.insert(3);
setIntA.insert(1);
setIntA.insert(7);
setIntA.insert(5);
setIntA.insert(9);
 
set<int> setIntB(setIntA);  //1 3 5 7 9
set<int> setIntC;
setIntC = setIntA; //1 3 5 7 9
 
setIntC.insert(6);
setIntC.swap(setIntA);   //交换

4、set的大小

  • set.size();       // 返回容器中元素的数目

  • set.empty();   // 判断容器是否为空

set<int> setIntA;
setIntA.insert(3);
setIntA.insert(1);
setIntA.insert(7);
setIntA.insert(5);
setIntA.insert(9);
 
if (!setIntA.empty())
{
    int iSize = setIntA.size(); //5
}

5、set的插入与迭代器

  • set.insert(elem);    // 在容器中插入元素。

  • set.begin();     // 返回容器中第一个数据的迭代器。

  • set.end();    // 返回容器中最后一个数据之后的迭代器。

  • set.rbegin();   // 返回容器中倒数第一个元素的迭代器。

  • set.rend();     // 返回容器中倒数最后一个元素的后面的迭代器。

set<int> setInt;
setInt.insert(3); 
setInt.insert(1);
setInt.insert(5);
setInt.insert(2);
for(set<int>::iterator it=setInt.begin(); it!=setInt.end(); ++it)
{
      int iItem = *it;
      cout << iItem;    //或直接使用cout << *it
}

5、set的删除

  • set.clear();    // 清除所有元素

  • set.erase(pos);    // 删除pos迭代器所指的元素,返回下一个元素的迭代器。

  • set.erase(beg,end);    // 删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。

  • set.erase(elem);         // 删除容器中值为elem的元素。

//删除区间内的元素
//setInt是用set<int>声明的容器,现已包含按顺序的1,3,5,6,9,11元素。
set<int>::iterator itBegin=setInt.begin();
++itBegin;
set<int>::iterator itEnd=setInt.begin();
++itEnd;
++itEnd;
++itEnd;
setInt.erase(itBegin,itEnd);
//此时容器setInt包含按顺序的1,6,9,11四个元素。
 
//删除容器中第一个元素
setInt.erase(setInt.begin()); //6,9,11
 
//删除容器中值为9的元素
set.erase(9);    
 
//删除setInt的所有元素
setInt.clear(); //容器为空

6、set集合的元素排序

set<int,greater<int>> setIntB;   
setIntB.insert(3);
setIntB.insert(1);
setIntB.insert(5);
setIntB.insert(2);
// 此时容器setIntB就包含了按顺序的5,3,2,1元素

函数对象functor的用法

  • 尽管函数指针被广泛用于实现函数回调,但C++还提供了一个重要的实现回调函数的方法,那就是函数对象。

  • functor,翻译成函数对象,伪函数,算符,是重载了“()”操作符的普通类对象。从语法上讲,它与普通函数行为类似。

  • greater<>与less<>就是函数对象。

下面举出greater的简易实现原理:

class greater
{
    bool operator() (const int& iLeft, const int& iRight)
    {
           return (iLeft>iRight);    //如果是实现less<int>的话,这边是写return (iLeft<iRight);
    }
}

容器就是调用函数对象的 operator() 方法去比较两个值的大小

思考:学生包含学号,姓名属性,现要求任意插入几个学生对象到set容器中,使得容器中的学生按学号的升序排序。

//学生类
class CStudent
{
    public:
    CStudent(int iID, string strName)
    {
          m_iID = iID;
          m_strName = strName;
     }
     int m_iID; //学号
     string m_strName; //姓名
}
//本类不写拷贝构造函数。但大家仍要有考虑拷贝构造函数的习惯。
//函数对象
class StuFunctor
{
    bool operator()  (const CStudent &stu1, const CStudent &stu2)
    {
        return (stu1.m_iID<stu2.m_iID);
    }
}
 
//main函数
int main()
{
   set<CStudent, StuFunctor> setStu;
   setStu.insert(CStudent(3,"小张"));
   setStu.insert(CStudent(1,"小李"));
   setStu.insert(CStudent(5,"小王"));
   setStu.insert(CStudent(2,"小刘"));
   //此时容器setStu包含了四个学生对象,分别是按姓名顺序的“小李”,“小刘”,“小张”,“小王” 
}

7、set的查找

  • set.find(elem);    // 查找elem元素,返回指向elem元素的迭代器。

  • set.count(elem);   // 返回容器中值为elem的元素个数。对set来说,要么是0,要么是1。对multiset来说,值可能大于1。

  • set.lower_bound(elem);   // 返回第一个 >=elem元素的迭代器。

  • set.upper_bound(elem);  // 返回第一个>elem元素的迭代器。

set<int> setInt;
setInt.insert(3);
setInt.insert(1);
setInt.insert(7);
setInt.insert(5);
setInt.insert(9);
 
set<int>::iterator itA = setInt.find(5);
int iA = *itA; //iA == 5
int iCount = setInt.count(5); //iCount == 1
 
set<int>::iterator itB = setInt.lower_bound(5);
set<int>::iterator itC = setInt.upper_bound(5);
int iB = *itB; //iB == 5
int iC = *itC; //iC == 7

set.equal_range(elem);    // 返回容器中与elem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)。

  • 函数返回两个迭代器,而这两个迭代器被封装在pair中。

pair< set<int>::iterator, set<int>::iterator > pairIt = setInt.equal_range(5);  
//pair是什么?

//pair译为对组,可以将两个值视为一个单元。
//pair<T1,T2>存放的两个值的类型,可以不一样,如T1为int,T2为float。T1,T2也可以是自定义类型。
//pair.first是pair里面的第一个值,是T1类型。
//pair.second是pair里面的第二个值,是T2类型。

10.2.8 map和multimap容器

1、map/multimap的简介

  • map是标准的关联式容器,一个map是一个键值对序列,即(key,value)对。它提供基于key的快速检索能力。

  • map中key值是唯一的。集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置。

  • map的具体实现采用红黑树变体的平衡二叉树的数据结构。在插入操作和删除操作上比vector快。

  • map可以直接存取key所对应的value,支持[]操作符,如map[key]=value(将key键所对应的值修改为value)

  • multimap与map的区别:map支持唯一键值,每个键只能出现一次;而multimap中相同键可以出现多次。multimap不支持[]操作符。

2、map/multimap对象的默认构造

//map/multimap采用模板类实现,对象的默认构造形式:
map<T1,T2> mapTT;
multimap<T1,T2>  multimapTT;  
//如:
map<int, char> mapA;
map<string,float> mapB;
//其中T1,T2还可以用各种指针类型或自定义类型

3、map的插入与迭代器

map.insert(...);    //往容器插入元素,返回pair

在map中插入元素的三种方式:

假设 map mapStu;

一、通过pair的方式插入对象

mapStu.insert(pair(3,"小张") );

二、通过 value_type 的方式插入对象

mapStu.insert(  map<int,string>::value_type(1,"小李"));

三、通过数组的方式插入值

mapStu[3] = “小刘";

mapStu[5] = “小王";

前两种方法,采用的是 insert()方法,该方法返回值为pair

第三种方法非常直观,但存在一个性能的问题。插入3时,先在mapStu中查找主键为3的项,若没发现,则将一个键为3,值为“小刘”的键值对插入到map中。若发现已存在3这个键,则修改这个键对应的value为“小刘”。

如果键存在则修改,如果不存在则插入

string strName = mapStu[2];   // 取操作或插入操作

只有当mapStu存在2这个键时才是正确的取操作,否则会自动插入一个实例,键为2,值为初始化值。

map<int, string> mapA;
pair<map<int,string>::iterator, bool> pairResult = 
                        mapA.insert(pair<int,string>(3,"小张")); //插入方式一
 
int iFirstFirst = (pairResult.first)->first; //iFirst == 3;
string strFirstSecond = (pairResult.first)->second; //strFirstSecond为"小张"
bool bSecond = pairResult.second; //bSecond == true;
mapA.insert(map<int,string>::value_type(1,"小李")); //插入方式二
mapA[3] = "小刘"; //修改value
mapA[5] = "小王"; //插入方式三
 
string str1 = mapA[2]; //执行插入 string() 操作,返回的str1的字符串内容为空。
string str2 = mapA[3]; //取得value,str2为"小刘"

使用迭代器遍历:

for (map<int,string>::iterator it=mapA.begin(); it!=mapA.end(); ++it)
{
    pair<int, string> pr = *it;
    int iKey = pr.first;
    string strValue = pr.second;
}

4、map容器或者键所对应的值

方法一:使用[]

方法二:使用 find()函数:成功返回对应的迭代器,失败返回end()的返回值

map<int, string>::iterator it = mapS.find(3);

方法三:使用 at() 函数,如果键值对不存在会抛出“out_of_range 异常”

5、map对象的拷贝构造与赋值

  • map(const map &mp);    // 拷贝构造函数

  • map& operator=(const map &mp);   // 重载等号操作符

  • map.swap(mp);    // 交换两个集合容器

map<int, string> mapA;
mapA.insert(pair<int,string>(3,"小张"));
mapA.insert(pair<int,string>(1,"小杨"));
mapA.insert(pair<int,string>(7,"小赵"));
mapA.insert(pair<int,string>(5,"小王"));
 
map<int ,string> mapB(mapA); //拷贝构造
map<int, string> mapC;
mapC = mapA; //赋值
 
mapC[3] = "老张";
mapC.swap(mapA); //交换

6、map的大小

  • map.size();   // 返回容器中元素的数目

  • map.empty();  // 判断容器是否为空

map<int, string> mapA;
mapA.insert(pair<int,string>(3,"小张"));
mapA.insert(pair<int,string>(1,"小杨"));
mapA.insert(pair<int,string>(7,"小赵"));
mapA.insert(pair<int,string>(5,"小王"));
 
if (mapA.empty())
{
    int iSize = mapA.size(); //iSize == 4
}

7、map的删除

  • map.clear();    // 删除所有元素

  • map.erase(pos);    // 删除pos迭代器所指的元素,返回下一个元素的迭代器。

  • map.erase(beg,end);     // 删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。

  • map.erase(keyElem);    // 删除容器中key为keyElem的对组。

map<int, string> mapA;
mapA.insert(pair<int,string>(3,"小张"));
mapA.insert(pair<int,string>(1,"小杨"));
mapA.insert(pair<int,string>(7,"小赵"));
mapA.insert(pair<int,string>(5,"小王"));

删除区间内的元素:

map<int,string>::iterator itBegin=mapA.begin();
++ itBegin;
++ itBegin;
map<int,string>::iterator itEnd=mapA.end();
mapA.erase(itBegin,itEnd); //此时容器mapA包含按顺序的{1,"小杨"}{3,"小张"}两个元素。
 
mapA.insert(pair<int,string>(7,"小赵"));
mapA.insert(pair<int,string>(5,"小王"));

删除容器中指定 的元素:

mapA.erase(5);    

删除容器中指定位置的元素:

mapA.erase(mapA.begin());

8、map的查找

  • map.find(key);   // 查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回map.end();

  • map.count(keyElem);    // 返回容器中key为keyElem的对组个数

  • map.lower_bound(elem);   // 返回第一个>=elem元素的迭代器

  • map.upper_bound(elem);   // 返回第一个>elem元素的迭代器

  • map.equal_range(elem);    // 返回容器中与elem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)。

map<int,string>::iterator it=mapStu.find(3);
if(it == mapStu.end())
{
//没找到
}
else
{
    //找到了
    pair<int, string> pairStu = *it;
    int iID = pairStu.first; //或   int  iID = it->first;
    string strName = pairStu.second; //或   string strName = it->second;
}

10.2.9 总结

特点对比:

底层实现:  

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/823651.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【NLP初级概念】 01-稀疏文档表示(第 1/30 部分)

一、介绍 自然语言处理(NLP)是计算方法的应用,不仅可以从文本中提取信息,还可以在其上对不同的应用程序进行建模。所有基于语言的文本都有系统的结构或规则,通常被称为形态学,例如“跳跃”的过去时总是“跳跃”。对于人类来说,这种形态学的理解是显而易见的。 在这篇介…

kafka总结

Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff08;消息引擎系统&#xff09;&#xff0c;它可以处理消费者在网站中的所有动作流数据。 消息队列应用场景 缓存/削峰 :处理突然激增的大量数据&#xff0c;先放入消息队列&#xff0c;再按照速度去处理&#xff0c; 解…

技术等级 TRL 定义

“不同环境、不同目标下TRL表述不一样” 技术等级 TRL 定义 TRL1 基本原理提出和发现 TRL2 技术应用研究 TRL3 完成概念验证&#xff0c;如叶栅试验、燃烧室头部试验等 TRL4 完成模拟部件试验&#xff0e;如压气机性能试验&#xff0c;燃烧室扇形试验 TRL5 完…

3ds MAX 绘制喷泉

首先绘制一个小圆柱体当做喷头&#xff1a; 在粒子系统内选择【超级喷射】&#xff0c;并设置对应的参数&#xff1a; 轴和平面的扩散代表了我们看到的水柱能占据多大的面积 另外比较重要的参数是粒子运动和粒子计时 前者是粒子移动的时间也就是喷射的速度 后者代表了喷射出的…

渗透测试:Linux提权精讲(三)之sudo方法第三期

目录 写在开头 sudo jjs sudo journalctl sudo knife sudo less sudo man sudo more sudo mount sudo mysql sudo nano sudo neofetch sudo nice sudo nmap sudo node sudo nohup sudo openvpn sudo passwd sudo perl sudo php sudo pico sudo pkexec su…

网络知识介绍

一、TCP 传输控制协议&#xff0c;Transmission Control Protocol。 面向广域网的通信协议&#xff0c;跨域多个网络通信时&#xff0c;为两个通信端点之间提供一条具有如下特点的通信方式&#xff1a; 基于流、面向连接、可靠通信方式、网络状况不佳时尽量降低系统由于重传带…

二十三种设计模式第二十三篇--状态模式

状态模式&#xff0c;是一种行为模式&#xff0c;在软件开发过程中&#xff0c;对象按照不同的情况做出不同的行为&#xff0c;我们把这样的对象称为具有状态的对象&#xff0c;而把影响对象行为的一个或者多个动态变化的属性称为状态。 对这种具有状态的对象变成&#xff0c;…

《Java面向对象程序设计》学习笔记——第 1 章 Java入门

专栏&#xff1a;《Java面向对象程序设计》学习笔记

第28天-Kubernetes架构,集群部署,Ingress,项目部署,Dashboard

1.K8S集群部署 1.1.k8s快速入门 1.1.1.简介 Kubernetes简称k8s&#xff0c;是用于自动部署&#xff0c;扩展和管理容器化应用程序的开源系统。 中文官网&#xff1a;https://kubernetes.io/zh/中文社区&#xff1a;https://www.kubernetes.org.cn/官方文档&#xff1a;https…

git管理工具学习(图解使用git工作流程)

目录 GIT 简介一个最简单的GIT操作流程git的工作流程&命令 GIT 简介 git是什么&#xff0c;在维基百科上是这么介绍的&#xff1a;git是一个分布式的版本控制软件 分布式是相对于集中式而言的&#xff0c;分布式即每一个git库都是一个完整的库。 每个库的地位都是平等的&am…

STM32存储左右互搏 I2C总线读写EEPROM ZD24C1MA

STM32存储左右互搏 I2C总线读写EEPROM ZD24C1MA 在较低容量存储领域&#xff0c;EEPROM是常用的存储介质&#xff0c;不同容量的EEPROM的地址对应位数不同&#xff0c;在发送字节的格式上有所区别。EEPROM是非快速访问存储&#xff0c;因为EEPROM按页进行组织&#xff0c;在连…

c 语言解析 时间字符串

#include <iostream> #include <ctime>int main(int argc, char *argv[]) {struct tm timeinfo;char cur_time[] "current time: 2021-09-06 23:50:13";// 解析时间到timeinfo中strptime(cur_time, "current time: %Y-%m-%d %H:%M:%S", &…

数据库管理员知识图谱

初入职场的程序猿&#xff0c;需要为自己做好职业规划&#xff0c;在职场的赛道上&#xff0c;需要保持学习&#xff0c;并不断点亮自己的技能树。  成为一名DBA需要掌握什么技能呢&#xff0c;先让Chat-GPT为我们回答一下&#xff1a; 数据库管理系统 (DBMS)知识&#xff…

加强Web应用程序安全:防止SQL注入

数据库在Web应用程序中存储和组织数据时起着至关重要的作用&#xff0c;它是存储用户信息、内容和其他应用程序数据的中央存储库。而数据库实现了高效的数据检索、操作和管理&#xff0c;使Web应用程序能够向用户提供动态和个性化的内容。然而&#xff0c;数据库和网络应用程序…

微信小程序原生写法传递参数

微信小程序原生写法传递参数 data-xxx 自定义参数名 &#xff0c;接收参数&#xff1a;方法&#xff08;变量名&#xff09; checkVip:function(event) {let that thisconsole.log(event,event)console.log(event.currentTarget.dataset.idx,index)let index Number(eve…

SpringBoot复习:(13)Banner是怎么打印出来的?

SpringApplication的run方法代码&#xff1a; public ConfigurableApplicationContext run(String... args) {long startTime System.nanoTime();DefaultBootstrapContext bootstrapContext createBootstrapContext();ConfigurableApplicationContext context null;configur…

<C++> 三、内存管理

1.C/C内存分布 我们先来看下面的一段代码和相关问题 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] {1, 2, 3, 4};char char2[] "abcd";const char *pChar3 "abcd";int *ptr1…

重学C++系列之异常

一、什么是异常 异常一般是指程序运行期发生的非正常情况。异常一般是不可预测的&#xff0c;如&#xff1a;内存不足&#xff0c;打开文件失败&#xff0c;数组越界&#xff0c;范围溢出等。 在某段程序发生无法继续正常执行的情况时&#xff0c;C允许程序进行所谓抛出异常&am…

实现Feed流的三种模式:拉模式、推模式和推拉结合模式

在互联网产品中&#xff0c;Feed流是一种常见的功能&#xff0c;它可以帮助我们实时获取我们关注的用户的最新动态。Feed流的实现有多种模式&#xff0c;包括拉模式、推模式和推拉结合模式。在本文中&#xff0c;我们将详细介绍这三种模式&#xff0c;并通过Java代码示例来实现…

0801|IO进程线程day4(文件IO函数)

作业1&#xff1a;从终端获取一个文件的路径以及名字 若该文件是目录文件&#xff0c;则将该文件下的所有文件的属性显示到终端&#xff0c;类似ls -l该文件夹若该文件不是目录文件&#xff0c;则显示该文件的属性到终端上&#xff0c;类似ls -l这单个文件 以下代码只能跑本目录…