c++—STL(六大组件)

一、STL概述

        1.  STL概述

        (1)STL定义:STL(standard template library),标准模板库,是一个高效的c++程序库,重在提高了代码的复用性;主要包含了常用的数据结构和基本算法,为广大c++程序员提供了一个可扩展的应用框架;

        体现了泛型化(模板化)程序设计的思想,在实现方面是以一种类型程序参数化的方式实现的;采用模板类实现,所以定义时需要执行存储元素的类型(基本类型或自定义类型);

      2. STL的六大组件

        (1)容器(container):各种基本数据结构

        (2)迭代器(iterator):常使用在遍历容器,又可以用在连接容器和算法;

        (3)算法(algorithm):提供了各种基本算法如sort、search等;

        (4)适配器(adapter):可改变容器(containers)、迭代器(iterators)或函数对象(function object)接口的一种组件;

        (5)函数对象(function object):用类型包装函数,将函数类型化;

        (6)分配器(allocator):内部调用malloc分配空间;

 二、容器

        1. 分类:可分为三类:

        (1)顺序容器(sequence containers),主要包括array、vector、deque、list、forward_list;

        (2)关联容器(associative containers),内部机理是树、哈希表,元素是键/值对,特别适合做查找,因为其时间复杂度较低;且大都具有自动排序功能;主要包括set、multiset、map、multimap;

        (3)无序(散列)容器(UNordered containers),内部机理是哈希表,c++11里推出:元素的位置不重要,重要的是这个元素是否在这个容器里面,主要适用于查找;主要包括:underde_set、unordered_multiset、unordered_map、unordered_multimap;

        2. vector容器  头文件#include<vector>

        (1)本质与特点

        ①vector是内置有动态数组,可以随机存取元素,可用[ ] 和 at()方法;

        ②vector尾部添加或移除元素非常快速;

        ③因为vector在中间插入会导致后面的所有元素都要重新析构与拷贝构造,效率很低;

        ④vector采用模板类实现,所以定义时需要执行存储元素的类型(基本类型或自定义类型)

        (2)主要操作

vector对象的默认构造 vector<int>v1;
vector对象的带参数构造 vector<int>v2(v1.begin()+1, v1.begin()+4);
vector的插入 push_back、insert
vector数据存取 v1.at(), v1[ ], v1.front(),
vector的删除 pop_back(), clear(), erase(),
vector的迭代器 begin(), end(), rbegin(), rend()
vector的大小 size(), empty(), capacity(), resize(), shrink_to_fit
vector的赋值 assign(), operator=, swap

vector示例:

#include <vector>
#include <iostream>
using namespace std;

class Test
{
public:
    Test()
    {
        // cout << "Test" << endl;        
    }

    Test(int t) : m_t(t)
    {
        // cout << "Test int" << endl;
    }

    Test(const Test &other)
    {
        m_t = other.m_t;
        // cout << "Test copy" << endl;
    }

    ~Test()
    {
        // cout << "~Test" << endl;
    }

    friend ostream& operator<<(ostream&out, Test &t);
    friend bool operator!=(const Test&t, int num);
    friend int operator%(const Test&t, int num);

    int m_t;
};
ostream& operator<<(ostream&out, Test &t)
{
    out<<t.m_t;
    return out;
}
bool operator!=(const Test&t, int num)
{
    return (t.m_t != num);
}
int operator%(const Test&t, int num)
{
    return (t.m_t % num);
}

template <typename T>
void print(vector<T> &vt)
{
    for(int i = 0; i < vt.size(); i++)
    {
        cout<<vt.at(i)<<" ";
    }
    cout<<endl;
}

template <typename T>
void delete_node(vector<T> &vt)
{
    for(auto it = vt.begin(); it != vt.end(); )
    {
        
        if(*it % 2 != 0)
        {
            // it = vt.erase(it);
            vt.erase(it);
        }
        else
        {
            ++it;
        }
    }
}

int main(int argc, char **argv)
{
    vector<Test> vt;
    vt.reserve(20);  //提前预留20个元素值,避免了空间不够时,频繁的拷贝构造
    for(int i = 0; i < 9; i++)
    {
        vt.push_back(Test(i+1));  //连续构造1-10
    }

    print(vt);
    delete_node(vt);  //删除其中是基数的元素值(test的m_t是奇数的)
    print(vt);

    cout<<"vt.capacity() = "<<vt.capacity()<<endl;  //输出vt容器的可容纳元素的大小,20
    vt.shrink_to_fit();  //回收未被利用的空间
    cout<<"vt.capacity() = "<<vt.capacity()<<endl;  //输出vt容器的可容纳元素的大小,4

    vt.assign(7,7);  //将7个7,赋给vt容器,注意是系统默认将vt原有的内容先清空
    print(vt);

    return 0;
}

        3. list 容器  头文件 #include<list>

        (1)本质及特点

        ①list是一个双向链表容器,适合高效地在头尾插入或者删除元素;

        ②list不支持随机存取元素,所以不支持at()方式与[ ]操作符;请注意迭代器方面it++可以,但是it+2这样就不支持了;只可遍历,不可跳跃;

        (2)主要操作

list对象的默认构造 list<int> l1;也可以是自定义类型;
list对象的带参数构造 list<int> l2 (l1.begin()+1, l1.end());
list的插入

l2.push_back();     l2.pop_back();

l2.push_front();     l2.pop_front();

l2.emplace_back(); l2.emplace_front();

list元素删除

l2.clear(); 

l2.erase(beg,end);

l2.erase(pos);

l2.remove(elem);

l2.remove_if();

list.unique();

list的迭代器

l2.begin();

l2.end();

l2.rbegin();

l2.rend();

list的元素存取

l2.front();

l2.back();

list的赋值

l2.assign();

l2.swap(l1);

list的大小

l2.size();

l2.empty();

l2.resiae();

l2.resize();

list的排列

l2.sort();

l2.reverse();

list对容器的操作

splice();

list示例:

#include <list>
#include <iostream>
#include <vector>

using namespace std;

template <typename T>
class Deleteor
{
public:
    bool operator()(const T &t)
    {
        return (t % 2) == 0;
    }
};

template <typename T>
void print(list<T> & other)
{
    for(auto it = other.begin(); it != other.end(); ++it)
    {
        cout<<*it<<" ";
    }
        cout<<endl;
}

int main(int argc,char **argv)
{
    list<int> l1;
    l1.emplace_back(1);
    l1.emplace_back(2);
    l1.emplace_back(3);
    l1.emplace_back(4);
    l1.emplace_back(5);
    l1.emplace_back(6);
    l1.emplace_back(7);
    l1.emplace_front(8);

    print(l1);  

    l1.insert(std::next(l1.begin(),3),9);  //std::next(l1.begin(),3)是制定插入的位置
    print(l1); 

    l1.remove(4);  //删除值等于4的元素
    print(l1);

    l1.remove_if(Deleteor<int>());  //Deleteor<int>()是删除的规则,返回的是bool(true,false)
    print(l1);                      //这里是一个函数对象,重载了函数运算符()

    l1.sort();  //对list排序,默认是从小到大
    print(l1);

    l1.sort(greater<int>());  //内建函数(对象),实现从大到小排序
    print(l1);

    l1.reverse();  //对list逆序
    print(l1);

    list<int>l2 = {1,3,5,7,9};
    list<int>l3 = {2,4,6,8,10};
    l2.merge(l3);  //将l3合并到l2上,并按照默认的从小到大方式排好序
    cout<<"l2 : ";
    print(l2);
    cout<<"l3 : ";
    print(l3);  //l3会被清空

    list<int>l4 = {7,7,7,7};

    auto it = l2.begin();
    std::advance(it,2);  //将it迭代器后退2个位置,-2就是前进2个位置
    l2.splice(it,l4);
    cout<<"l2 : ";
    print(l2);

    return 0;
}

        4. forward_list容器

        本质及特点:优化list产生的,forward_list是单向链表,这样的话少存储一个指针,节省系统开销;

        ①只提供前向迭代器,不支持反向迭代器;

        ②不提供size()成员函数;

        ③没有指向最末元素的锚点,因此不提供back(),push_back(),pop_back();

        ④不提供随机访问,与list同;

        ⑤插入和删除元素不会造成“指向其他元素的”指针、引用、迭代器失效;

        5. deque 容器,本质及特点:

        ①deque是双端数组,而vector是单端的;

        ②与vector一样,可以随机存取元素;且在头部和尾部移除元素都非常快速,但是在中间插入元素则比较费时;

        ③其他内容以vector一样;

        6. set/multiset 容器  头文件:#include<set>

        (1)本质及特点

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

        ②set采用红黑树变体的数据结构实现,红黑树属于平衡二叉树,在插入操作和删除操作上比vector快;时间复杂度是O(log n);

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

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

        ⑤不可以直接修改set或multiset容器中的元素值,因为这类容器是自动排序的,如果要修改其中的某个元素值,必须先删除原有的元素,再插入新的元素;

程序示例:

#include <iostream>
#include <set>
#include <vector>

using namespace std;

template <typename T>
void print(const set<T> &other)
{
    for(auto it = other.begin(); it != other.end(); ++it)
    {
        cout<<*it<<" ";
    }
    cout<<endl;
}

int main(int argc, char **argv)
{
    set<int>si;
    si.insert(1);
    si.insert(3);
    si.insert(7);
    si.insert(5);
    si.insert(2);

    print(si);

    // auto node = si.extract(3);  //修改成员值,c++17,本系统不支持
    // node.value = 9;
    // si.insert(move(node));

    // set<int>s2 = {2,4,6,8};
    // si.merge(s2);  //将s2合并到si,c++17,本系统不支持

    cout<<"si.count(5) : "<<si.count(5)<<endl;  //数值是5的元素有几个?

    auto it = si.find(5);
    if(it != si.end())
    {
        cout<<"找到了"<< *it <<endl;
    }

    auto it1 = si.lower_bound(5); //返回首个不小于5的元素的迭代器
    auto it2 = si.upper_bound(5); //返回收个大于5的元素迭代器
    if(it1 != si.end())
    {
        cout<<"it1 = "<<*it1<<endl;
    }
    if(it2 != si.end())
    {
        cout<<"it2 = "<<*it2<<endl;
    }

    return 0;
}

        7. map/multimap 容器  #include<map>

        (1)本质及特点

        ①map是标准的关联式容器,一个map是一个键值对(一个序列号,一个值,相当于数组的索引和对应的值),它提供基于key(键值)的快速检索能力;键值对中第一个元素是first,第二个元素是second,支持迭代器访问输出;

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

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

        ④map可以直接存取key所对应的value,支持[ ] 操作符,如map[key] = value;

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

        (2)示例

#include <iostream>
#include <map>
#include <string>

using namespace std;

template<typename key, typename value>
void print(const map<key, value> &other)
{
    for(auto it = other.begin(); it != other.end(); ++it)
    {
        cout<<"key : "<<it->first<<" value : "<<it->second<<endl;
    } 
}

int main(int argc, char **argv)
{
    map<int, string> m1;  //和数组很像,索引+对应的值
    m1[1] = "hello1";
    m1[2] = "hello2";
    m1[3] = "hello3";

    print(m1);

    return 0;
}

        (3)一个面试题示例:给定一个数组,计算每个数值出现的次数,然后按字符出现次数的升序输出数值及其出现次数;并且单独输出个数大于数组个数一半的元素;

#include <iostream>
#include <map>
#include <set>

using namespace std;

template<typename key, typename value>
void print(const multimap<key, value> &other)
{
    for(auto it = other.rbegin(); it != other.rend(); ++it)
    {
        cout<<"降序遍历 : 值"<<it->second<<"   次数:"<<it->first<<endl;
    } 
}

int main(int argc, char **argv)
{
    int num[]={1,3,3,3,5,3};
    int len  = sizeof(num)/(sizeof(num[0]));
    cout<<"len = "<<len<<endl;  //输出元素的个数
    map<int,int>mc;

    for(auto &temp : num)
    {
        mc[temp]++;
    }
    // cout<<"mc[3] = "<<mc[3]<<endl;  //输出数值3对应的次数

    multimap<int,int>mi;  //注意这里又新建了一个容器,在插入元素时,与mc相反,以次数为键值,这样就会自动根据次数排序,默认是升序,若要降序,则需要采用逆向迭代器
    for(auto it = mc.begin(); it != mc.end(); ++it)
    {
        mi.insert(pair<int,int>(it->second,it->first));
    }

    for(auto &temp : mi)
    {
        cout<<"升序遍历 : 值"<<temp.second<<"   次数:"<<temp.first<<endl;  //注意前后的输出顺序对照上
    }

    print(mi);
    
    for(auto &temp : mc)
    {
        if(temp.second > (len/2))
        {
            cout<<"符合要求:"<<temp.first<<" 出现次数:"<<temp.second<<endl;
        }
    }

    return 0;
}

结果:

        8. 容器的选择原则总结

        ①vector是内置有动态数组,当容量满时会自动扩充,在末尾有定位指针,所以末尾插入十分快速,但中间插入则较慢,因为涉及到原有数组的拷贝;最大的特点是适合高效的随机访问;而deque则优化为可以既可以头插也可以末尾插;

        ②list是一个双向链表容器,访问指定元素的效率较低,不支持随机访问,最大的特点是中间插入的效率较快;

        ③set是一个集合容器,元素具有唯一性,因为排序功能,所以开销较大;若需要支持重复数据则需要使用multiset;

        ④map是关联式容器,内置键值对,且内部按照顺序排序,内部采用红黑树的数据结构,时间复杂度是O(log n ),查找与删除的效率很高;适合字典式存储数据;

猜你喜欢

转载自blog.csdn.net/m0_72814368/article/details/130994549
今日推荐