【c++基础】第六章 STL标准模板库

news2024/11/20 14:16:33

第六章 STL标准模板库

  • 认识STL
  • string字符容器库
  • vector容器
  • 迭代器与空间配置器
  • `deque`容器
  • 容器适配置
  • list容器
  • set容器
  • map容器
  • 观察者设计模型

认识STL

STL的概述

STL采用泛型思想,把C中所用到的所有的数据结构,按照一定的标准,全部封装成了一个个类模板。也被称为数据容器。

STL就是用来解决容器中数据元素的操作的问题的。并且他按排标准统一封装了操作容器元素的算法,即一个个的函数模板。

为了配合统一的算法去操作不同的容器,他又按标准统一封装了不同的迭代器,即一个个不同类型的具有指针特性的类模板。

所以容器,算法,迭代器是整个STL的核心。

三者的关系如图所示:

在这里插入图片描述

有了统一的数据结构,即容器,有了统一的算法,每一个容器都使用各自的不同的迭代器,从而实现了对数据结构元素的标准操作。

STL标准模板库都有什么

STL的知识结构如图:

在这里插入图片描述

容器

  1. 常用的数据结构,C++重新封装成了 vector(动态数组),list(链表),deque(双向队列),set(集合) ,map(映射表)等,来实现存放数据,其中 set,map双叫关联容器 。其中的栈,单端队列,优先队列,又都是线性数据结构改造之后的具有各种特性的数据结构,又叫做容器适配器
  2. 序列式容器强调值的排序,序列式容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。vector容器,deque容器,List容器等。
  3. 关联式容器是非线性的树结构,更准确的说是二叉树结构,各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素入容器时的逻辑顺序 。关联式容器另一个显著特点是:在值中选择一个值作为关键字Key,这个关键字对值起到了索引的作用,方便查找。Set/mulitiset容器 Map/multimap容器,并且容器是可以嵌套使用的。

算法

algorithom头文件中的定义相关的操作的一系列的函数模板

STL收录的算法经过了数学上的效能分析与证明,且是经过商业运行考验过的,是极具复用价值 的,包括常用的排序,查找等。

​ 算法又可为为两种,质变算法,与非质变算法。

​ 何为质变算法:是指运算过程中会更改区间内的元素的内容。例如:拷贝,替换,删除等。

​ 何为非质变算法:是指运算过程中不会更改区间内的元素内容,例如:查找,计数,遍历,寻找极值等。

迭代器

迭代器的设计思维-STL 的关键所在,STL 的中心思想在于将容器(container)和算法(algorithms)分开,彼此独立设计,最后再一贴胶着剂将他们撮合在一起。从技术角度来看,容器和算法的泛型化并不困难,c++的 class template 和 functiontemplate 可分别达到目标,如果设计出两这个之间的良好的胶着剂,才是大难题

提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式,迭代器就被发明了出来。

在这里插入图片描述

函数符

在STL中主要用来为泛型算法提供策略。

有四种形式:全局函数指针,成员函数指针,仿函数(函数对象),与匿名函数Lambda表达式。

空间配置器

作用:主是用来解决内存碎片化问题的,以及过多的构造无用对象的问题。

容器的空间配置器的作用是把对象的内存开辟和构造过程分开,对象析构和内存释放分离开 。那为什么要把对象的内存开辟和构造(new的两个作用)分开,对象的析构和内存释放(delete的两个作用)分开呢?

1)因为在使用容器的过程中,构造一个容器,我们只想要容器的内存空间,并不需要给我们在内存上构造一堆无用的对象出来(直接用new没有办法避免这个问题);当从容器中删除一个元素的时候,我们需要析构这个被删除对象,但是它占用的容器内存空间是不能释放的,因为容器还要使用;再比如我们需要释放一个容器的时候,需要先把容器里面有效对象元素析构一遍,而不是把容器所有元素都析构一遍(用delete无法避免这个问题),所以在操作容器的过程中,我们需要有选择性的分别去开辟内存,构造对象,析构对象,所以这就是空间配置器存在的意义。

C++ 标准STL里面提供的allocator空间配置器,默认的内存分配和释放就是依赖malloc和free实现的。SGI STL提供了两个版本的空间配置器,一级空间配置器和二级空间配置器。一级空间配置器的内存管理也是通过malloc和free实现的,但是SGI STL的二级空间配置器提供了一个内存池的实现。第二级配置器的设计思想为:

1.如果配置区块超过128 bytes,则移交给第一级配置器处理(空间配置器);

2.如果配置区块小于128 bytes,则采用内存池管理(memory pool)。每次配置一大块内存,则维护对应的自由链表(free-list),下次若再有相同大小的内存需求,就直接从 free-list 中拨出(没有就继续配置内存,具体后面讲述),如果客端释换小额区块,就有配置器回收到 free-list 中。

对于SGI STL的二级空间配置器的内存池实现机制,还是非常重要的,因为这既涉及了空间配置器,又涉及了一个内存池的实现机制,因此大家需要好好的理解它的原理,大家在手写C++空间配置器的过程中,使用过nginx的内存池作为空间配置器的内存管理方案,这又是一个新的积累。

string字符容器库

字符串容器API接口:

#include <iostream>

using namespace std;

int main()
{
    string str(15,'g');
    string str10;
    str10.assign(10,'k');
    cout << str << endl;
    string str1(str,5);
    cout << str1 << endl;
    string str2("yaoliang",8);
    cout << str2 << endl;
	const char* s1=str2.data();
    const char* s=str2.c_str();
    cout << s << endl;
    //获取迭代器
    string::iterator it;
    for(it=str2.begin();it!=str2.end();it++){
        cout << *it << "   ";
    }
    cout << endl;
    //获取反向迭代器
    string::reverse_iterator it1;
    for(it1=str2.rbegin();it1!=str2.rend();it1++){
        cout << *it1 << "   ";
    }
    cout << endl;
    cout << str2.size() << endl;
    cout << str2.length() << endl;
    cout << str2.capacity() << endl;//已经开辟好的空间
    cout << str2.max_size() << endl;//最大容量
    //string 容量对象保存字符时,如果是小字符串(小于23个字节),会在栈上开辟空间
    //如果是大对象(大于23个字节),会在堆上开辟空间

    //查看动态开辟空间策略(2倍扩容)
//    string str_test;
//    cout << str_test.capacity() << endl;
//    for(int i=0;i<1024;i++){
//        cout << "string容器对象中的有效元素个数:" << str_test.size() <<
//                ",string容器对象中的容量大小:" << str_test.capacity() <<  endl;//采取2倍扩容机制
//        str_test.push_back('a');
//    }

    cout << str1.insert(0,3,'A') << endl;
    cout << str1 << endl;
    cout << str1.insert(0,"hello") << endl;
    cout << str1.erase(0,6) << endl;
    cout << str2.find("liang") << endl;
    cout << str2.substr(3,5) << endl;
    cout << str2.replace(3,5,"ming");
    return 0;
}

在这里插入图片描述

vector容器

vector容器于array数组容器的区别

vector与array无乎是一样的,连续的存储结构,两者的唯一的区别在于在空间上的灵活,数组需要提前指定长度,不量确定了就不能发生改变了,比较死板,不够灵活。比如出现拷贝长度大于了给定的空间,需要再重新定义一个足够空间的大小,然后把旧空间的内容再一个个拷贝到新的空间,非常麻烦。

c++11引入array主要是用来描述一种支持迭代器的C风格数组的,所以数组怎么用,array就怎么用,他定义在栈上,且长度固定不会涉及动态内存的开辟所以没有push_back,pop_back的相关方法,但是Vector这种动态的数组在开发更为常用,他底层对象在堆上,且长度是可变的。

在这里插入图片描述

vector容器是动态空间,他随着元素的加入,它的内部机制会自动扩充空间以容纳新的元素。vector的运用对于内存的合理利用与运用的灵活性有很大的帮助。我们再也不必害怕空间不足而一开始就定义一个巨大的数组了。

实现分析:

在这里插入图片描述

空间分配策略

#include <iostream>
#include <vector>

using namespace std;

int main()
{  
    //动态扩容的策略(没内容不先开辟空间,不同于string)
    vector<int> v;
    for(int i=0;i<100;i++){
        cout << "vector容器对象中的有效元素个数:" << v.size() << "vector容器容量的大小:" << v.capacity() << endl;
        v.push_back(i);
    }
    return 0;
}

迭代器非法化问题及解决

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<int> v;
    for(int i=0;i<20;i++){
        v.push_back(rand()%100+1);
    }
    for(int i=0;i<v.size();i++){
        cout << v[i] << " ";
    }
    cout << endl;

    //insert后,迭代器非法化的问题
    for(vector<int>::iterator it=v.begin();it!=v.end();it++){
        if(0==*it%2){
            it=v.insert(it,888);//返回值为迭代器类型
            it++;//更新迭代器
        }
    }
    for(int i=0;i<v.size();i++){
        cout << v[i] << " ";
    }
    cout << endl;
    //erase迭代器非法化的问题
    for(auto it=v.begin();it!=v.end();){
        if(0==*it%2){
            it=v.erase(it);//返回值为容器类型
        }else{
            it++;
        }
    }
    for(int i=0;i<v.size();i++){
        cout << v[i] << " ";
    }
    cout << endl;
    return 0;
}

泛型算法

泛型算法 = 函数模板 + 迭代器范围(注意迭代器的类型) + 函数符(策略使用)

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

int main()
{
    vector<int> v;
    for(int i=0;i<20;i++){
        v.push_back(rand()%100+1);
    }
    for(int i=0;i<v.size();i++){
        cout << v[i] << " ";
    }
    cout << endl;

    cout << "----------------使用泛型算法进行遍历-------------" << endl;
    for_each(v.begin(),v.end(),[](int val){cout << val << "-";});
    cout << endl;
    //针对于容器中有迭代器的容器for_each算法,提供了一种变种:枚举for循环
    for(int val:v){//val默认为第0个元素,一次遍历,直到结束为止
        cout << val << "-" ;
    }
    cout << endl;
    return 0;
}

sort:

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

template <class T>
T my_greate(T t1,T t2){
    return t1>t2;
}

template <class T>
class my_Greate{
public:
    bool get_my_Greate(T t1,T t2){
        return t1>t2;
    }
};
int main()
{
    vector<int> v;
    for(int i=0;i<20;i++){
        v.push_back(rand()%100+1);
    }
    for(int val:v){
        cout << val << " " ;
    }
    cout << endl;
    cout << "---------------------1---------------------" << endl;
    //sort,ASC//升序
    sort(v.begin(),v.end());
    for(int k:v){
        cout << k << " ";
    }
    cout << endl;
    cout << "----------------------2--------------------" << endl;
    //sort,DESC,使用lambda表达式作为算法策略
    sort(v.begin(),v.end(),[](int val1,int val2){return val1>val2;});//一直判断到最后
//如果val1>val2为真,就把val1排好序,可以把函数符想象成一个抽象(必须这么理解,因为具体排序要看sort实现了),不是算法的具体实现,算法知道意思,就是降序了
    for(int k:v){
        cout << k << " ";
    }
    cout << endl;
    cout << "----------------------3--------------------" << endl;
    //使用函数对象
    sort(v.begin(),v.end(),greater<int>());//这是调用库函数中的函数对象
    for(int k:v){
        cout << k << " ";
    }
    cout << endl;
    cout << "----------------------4--------------------" << endl;
    //定义一个函数指针,函数符来实现
    sort(v.begin(),v.end(),&my_greate<int>);//这个就是全局函数指针,函数符实现
    for(int k:v){
        cout << k << " ";
    }
    cout << endl;
    //定义一个成员函数指针,函数符来实现
    //因为成员函数指针依赖于对象的调用,所以不可以有直接做为策略使用,需要绑定器进行对象绑定才可以
    cout << "----------------------5--------------------" << endl;
    my_Greate<int> m;
    using namespace placeholders;
    sort(v.begin(),v.end(),bind(&my_Greate<int>::get_my_Greate,&m,_1,_2));
    for(int k:v){
        cout << k << " ";
    }
    cout << endl;

    return 0;
}

binary_find:

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

int main()
{
    vector<int> v;
    for(int i=0;i<20;i++){
        v.push_back(rand()%100+1);
    }
    for(int val:v){
        cout << val << " " ;
    }
    cout << endl;
    //sort,ASC
    sort(v.begin(),v.end());
    for(int k:v){
        cout << k << " ";
    }
    cout << endl;
	//时间复杂度O(logn)
    bool ok=binary_search(v.begin(),v.end(),70);
    if(ok)
    cout << "find succeed!" << endl;
    else
    cout << "find failure!" << endl;
    return 0;
}

find&&find_if&&bind1st(绑定第一个参数)&&bind2nd(绑定第二个参数)&&bind(新式绑定器):

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

int main()
{
    vector<int> v;
    for(int i=0;i<20;i++){
        v.push_back(rand()%100+1);
    }
    for(int val:v){
        cout << val << " " ;
    }
    cout << endl;
    //find对容器中的值没有要求,时间复杂度O(n),顺序遍历一遍,和二分查找不一样,时间复杂度不同
    auto it=find(v.begin(),v.end(),70);
    if(it!=v.end()){
        cout << "find succeed!index:" << it-v.begin() << endl;
    }else{
        cout << "find failure!" << endl;
    }
    //find_if按条件查找,需要把一个2这个值按序插入到序列中
    it=find_if(v.begin(),v.end(),[](int val){return val < 2;});
    if(it!=v.end()){
        it=v.insert(it,2);
        it++;
        cout << "find succeed!" << endl;
    }else{
        cout << "find failure!" << endl;
    }
    for(int val:v){
        cout << val << " " ;
    }
    cout << endl;
    //老式绑定器bind1st,bind2nd
    it=find_if(v.begin(),v.end(),bind1st(greater<int>(),2));
    if(it!=v.end()){
        it=v.insert(it,2);
        it++;
        cout << "find succeed!" << endl;
    }else{
        cout << "find failure!" << endl;
    }
    for(int val:v){
        cout << val << " " ;
    }
    cout << endl;
    //新式绑定器:bind
    using namespace placeholders;
    it=find_if(v.begin(),v.end(),bind(greater<int>(),2,_1));
    if(it!=v.end()){
        it=v.insert(it,2);
        it++;
        cout << "find succeed!" << endl;
    }else{
        cout << "find failure!" << endl;
    }
    for(int val:v){
        cout << val << " " ;
    }
    cout << endl;
    return 0;
}

迭代器与空间配置器

在这里插入图片描述

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



//实现一个自定的空间配置器
template <class T>
struct Allocate{
    //1.开辟空间
    T* allocate(size_t size){
        T* temp=(T*)malloc(sizeof (T)*size);
        if(nullptr==temp){
            throw bad_alloc();
        }
        return temp;
    }
    //2.构造对象
    void constructor(T* p,const T& obj){
        //定位new
        new (p) T(obj);
    }
    //3.析构对象
    void destructor(T* p){
        p->~T();
    }
    //4.释放空间
    void destroy(T* p){
        free(p);
    }
};

template <class T,class Allocat=Allocate<T>>
class Vector{
private:
    T* _frist;
    T* _last;
    T* _end;
    Allocat _allcater;
public:
    Vector(int size=10){
//        this->_frist=new T[size];
        this->_frist=_allcater.allocate(size);
        this->_last=_frist;
        this->_end=this->_frist+size;
    }
    ~Vector(){
        if(nullptr!=this->_frist){
//            delete [] this->_frist;
            for(T* p=_frist;p!=_last;p++){
                _allcater.destructor(p);
            }
        }
        _allcater.destroy(_frist);
        this->_end=this->_last=this->_frist=nullptr;
    }
    //拷贝构造
    Vector(const Vector& other){
        int size=other._end-other._frist;
//        this->_frist=new T[size];
        this->_frist=_allcater.allocate(size);
        int len=other._end-other._frist;
        memmove(this->_frist,other._frist,sizeof (T)*len);
        this->_last=this->_frist+len;
        this->_end=this->_frist+size;
    }
    //等号运算符重载
    Vector& operator=(const Vector& other){
        if(this==&other){
            return *this;
        }
        int size=other._end-other._frist;
        int len=other._last-other._frist;
        if(nullptr!=this->_frist){
//            delete [] this->_frist;
//            this->_frist=new T[size];
            for(T* p=_frist;p!=_last;p++){
                _allcater.destructor(p);
            }
            _allcater.destroy(_frist);
            this->_frist=_allcater.allcate(size);
        }else{
//            this->_frist=new T[size];
            this->_frist=_allcater.allcate(size);
        }
        memmove(this->_frist,other._frist,sizeof (T)*len);
        this->_last=this->_frist+ len;
        this->_end=this->_frist+size;
        return *this;
    }
    bool full(){
        return this->_last==this->_end;
    }
    bool empty(){
        return  this->_last==this->_frist;
    }
    //2倍扩容
    void expand(){
        int size=this->_end-this->_frist;
//        T* temp=new T[size* 2];
        T* temp=_allcater.allocate(size*2);
        memmove(temp,this->_frist,sizeof (T)*size);
//        delete [] this->_frist;
        for(T* p=_frist;p!=_last;p++){
            _allcater.destructor(p);
        }
        _allcater.destroy(_frist);
        this->_frist=temp;
        this->_last=this->_frist+size;
        this->_end=this->_frist+size*2;
    }
    void push_back(const T& val){
        if(this->full()){
            this->expand();
        }
//        *_last++=val;
        _allcater.constructor(this->_last,val);
        _last++;

    }
    void pop_back(){
        if(this->empty()){
            return;
        }
//        _last--;
        _last--;
        _allcater.destructor(_last);
    }
    int size(){
        return this->_last-this->_frist;
    }
    int capacity(){
        return this->_end-this->_frist;
    }
    T& operator[](int index){
        if(index<0||index>=this->size()){
            throw out_of_range("越界!");
        }
        return this->_frist[index];
    }
    class iterator{
    public:
        //与标准库中的泛型算法匹配类型
        using difference_type=int;
        using value_type= T ;
        using pointer=T*;
        using reference=T&;
        using iterator_category=random_access_iterator_tag;
    private:
        T* ptr;
    public:
        iterator(T* ptr=nullptr){
            this->ptr=ptr;
        }
        //迭代器功能:++运算,!=运算
        iterator& operator++(){
            ++this->ptr;
            return *this;
        }
        iterator& operator++(int){
            this->ptr++;
            return *this;
        }
        iterator& operator--(){
            --this->ptr;
            return *this;
        }
        iterator& operator--(int){
            this->ptr--;
            return *this;
        }
        // !=运算符重载:
        bool operator!=(const iterator& other){
            return this->ptr!=other.ptr;
        }
        bool operator==(const iterator& other){
            return this->ptr==other.ptr;
        }
        T& operator*(){
            return *ptr;
        }
        T* operator->(){
            return ptr;
        }

        iterator& operator+=(int n){
            this->ptr+=n;
            return *this;
        }
        iterator& operator-=(int n){
            this->ptr-=n;
            return *this;
        }
        iterator operator+(int n){
            T* temp=this->ptr+n;
            return iterator(temp);
        }
        iterator operator-(int n){
            T* temp=this->ptr-n;
            return iterator(temp);
        }
        int operator-(const iterator& other){
            return this->ptr-other.ptr;
        }
        bool operator>(const iterator& other){
            return this->ptr - other.ptr > 0;
        }
        bool operator<(const iterator& other){
            return this->ptr - other.ptr < 0;
        }
        bool operator>=(const iterator& other){
            return !(*this < other);
        }
        bool operator<=(const iterator& other){
            return !(*this > other);
        }
        T* get(){
            return ptr;
        }
    };

    iterator begin(){
        return iterator(this->_frist);
    }

    iterator end(){
        return iterator(this->_end);
    }

};

class A{
public:
    A(){
        cout << "A structure" << endl;
    }
    ~A(){
        cout << "A destruct" << endl;
    }
    A(const A&other){
        cout << "A copy" << endl;
    }
};

int main()
{
    Vector<int> v;
    for(int i=0;i<20;i++){
        v.push_back(rand()%100+1);
    }
    for(int i=0;i<v.size();i++){
        cout << v[i] << " " ;
    }
    cout << endl;
    Vector<A> v1;
    Vector<int> v2;
    for(int i=0;i<20;i++){
        v2.push_back(rand()%100+1);
    }
    for(int k:v2){
        cout << k << " " ;
    }
    cout << endl;
    return 0;
}

deque容器

在这里插入图片描述

在这里插入图片描述

#include <iostream>
#include <deque>
#include <algorithm>
using namespace std;

int main()
{
    deque<int> dq;
    for(int i=0;i<20;i++){
        dq.push_front(rand()%100+1);
    }
    for(auto it=dq.begin();it!=dq.end();it++){
        cout << *it << " ";
    }
    cout << endl;
    sort(dq.begin(),dq.end());
    for(int k:dq){
        cout << k << " ";
    }
    cout << endl;

    for(int i=0;i<20;i++){
        cout << dq.back() << " ";
        dq.pop_back();
    }
    cout << endl;

    for(int i=0;i<20;i++){
        cout << dq.front() << " ";
        dq.pop_front();
    }
    cout << endl;
    return 0;
}

容器适配置

容器适配器是没有迭代器的,不能实现泛型算法

使用deque容器实现一个stack

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

template <class T,class Container=deque<T>>
class Stack{
private:
    Container container;
public:
    void push(const T& val){
        container.push_back(val);
    }
    void pop(){
        container.pop_back();
    }
    T& top(){
        return  container.back();
    }
    bool empty(){
        return container.empty();
    }
};

int main()
{
    Stack<int> s;
    for(int i=0;i<10;i++){
        s.push(i);
        cout << i << " ";
    }
    cout << endl;
    while (!s.empty()) {
        cout << s.top() << " ";
        s.pop();
    }
    cout << endl;
    return 0;
}

实现一个单端队列

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

template <class T,class Container=deque<T>>
class SimpleQ{
private:
    Container container;
public:
    void push(const T& val){
        container.push_back(val);
    }
    void pop(){
        container.pop_front();
    }
    T& top(){
        return  container.front();
    }
    bool empty(){
        return container.empty();
    }
};

int main()
{
    SimpleQ<int> q;
    for(int i=0;i<10;i++){
        q.push(i);
        cout << i << " ";
    }
    cout << endl;
    while (!q.empty()) {
        cout << q.top() << " ";
        q.pop();
    }
    cout << endl;
    return 0;
}

优先队列

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

template<class T,class Container=vector<T>,class Compair=less<T>>
class Priority_queue{
private:
    Container container;
    Compair compair;
public:
    Priority_queue(){
        make_heap(container.begin(),container.end(),compair);
    }
    void push(const T& val){
        container.push_back(val);
//        sort(container.begin(),container.end(),compair);//快排,但系统中采取的是大顶堆,原因快排,递归过多
        push_heap(container.begin(),container.end(),compair);
    }
    void pop(){
        pop_heap(container.begin(),container.end(),compair);
        container.pop_back();
    }
    T& top(){
        return  container.front();
//        return container.back();
    }
    bool empty(){
        return container.empty();
    }
};

int main()
{
    Priority_queue<int> pq;
    for(int i=0;i<20;i++){
        pq.push(rand()%100+1);
    }
    while (!pq.empty()) {
        cout << pq.top() << " ";
        pq.pop();
    }
    cout << endl;
    return 0;
}

list容器

在这里插入图片描述

#include <iostream>
#include <list>
#include <algorithm>
using namespace std;


int main()
{
    list<int> l;
    for(int i=0;i<20;i++){
        l.push_back(rand()%100+1);
    }
    for(int k:l){
        cout << k << " ";
    }
    cout << endl;
    l.sort();
    for_each(l.begin(),l.end(),[](int val){cout << val << " ";});
    cout << endl;
    l.sort([](int val1,int val2){return val1>val2;});
    for_each(l.begin(),l.end(),[](int val){cout << val << " ";});
    cout << endl;
    return 0;
}

set容器

Set特性是:所有元素都会根据元素的键值自动被排序。(set 与 map都会按照键值来自动排序,只不过set的键与值是一体的相同的)Set的元素不像map那样,可以同时拥有实值与键值,set的元素即是键值又是实值。(你也可以理解为只有一个值,键与值相同)Set不允许两个元素有相同的键值。(即然是自动排序,set与map是不允许有相同的键的存在。这一点与map是共同的。),而multiset是可以存相同键值的元素。(这是set与multiset的唯一区别。)。

所以set容器的迭代器是一个常双向迭代器,只支持什么:++,–,==,!=的操作。

我们可以通过set的迭代器改变set元素的值吗?不行,因为set元素值就是其键值,关系到set元素排序规则,如果任意改变set元素值,会严重破坏set组织结构。换句话说,set的迭代器是一个只读迭代器。

set容器拥有与list某些相同的性质,当对容器中的元素进行插入操作或者删除操作的时候,操作之前所有迭代器,在操作完成之后依

multiset的底层实现是红黑树,红黑树是平衡二叉树的一种。二叉树的相关知识点可以百度一下。

#include <iostream>
#include <set>

using namespace std;

class Stu{
private:
    string name;
    int age;
public:
    Stu(string name,int age){
        this->age=age;
        this->name=name;
    }
//    bool operator<(const Stu& other)const{
//        return this->age<other.age;
//    }
    void showInfo()const{
        cout << "姓名:" << name << ",年龄:" << age << endl;
    }
    int getAge(){
        return  this->age;
    }
};

template <class T>
class Myless{
public:
    bool operator()(T t1,T t2)const{
        return t1.getAge()<t2.getAge();
    }
};

int main()
{
    set<int> s;
    for(int i=0;i<20;i++){
        s.insert(rand()%100+1);
    }
    for(int k:s){
        cout << k << " ";
    }
    cout << endl;

    cout << "*********************自定义类型放入set****************" << endl;
    Stu stu1("yaoliang",19);
    Stu stu2("minmin",18);
    Stu stu3("sun",17);
    
    
//    set<Stu> s1;//调用库中的<预算符重载函数
//    s1.insert(stu1);
//    s1.insert(stu2);
//    s1.insert(stu3);
//    for(const Stu& stu:s1){
//        stu.showInfo();
//    }
    
    set<Stu,Myless<Stu>> s1;//自定义比较策略
    s1.insert(stu1);
    s1.insert(stu2);
    s1.insert(stu3);
    for(const Stu& stu:s1){
        stu.showInfo();
    }
    return 0;
}

multiset与set

#include <iostream>
#include <set>

using namespace std;

class Stu{
private:
    string name;
    int age;
    int id;
public:
    Stu(string name,int age,int id){
        this->age=age;
        this->name=name;
        this->id=id;
    }
    bool operator<(const Stu& other)const{
        return this->id<other.id;
    }
    void showInfo()const{
        cout << "学号:" << id << ",姓名:" << name << ",年龄:" << age << endl;
    }
    int getAge(){
        return  this->id;
    }
};

int main()
{
    Stu stu1("王大锤",19,1);
    Stu stu2("李二狗",18,2);
    Stu stu3("张三丰",17,3);
    Stu stu4("王五子",17,5);
    Stu stu5("刘四思",20,3);
//    set<Stu> s1;//调用库中的<预算符重载函数
    multiset<Stu> s1;
    s1.insert(stu1);
    s1.insert(stu2);
    s1.insert(stu3);
    s1.insert(stu5);
    s1.insert(stu4);
//    pair<set<Stu>::iterator,bool> p;
//    p=s1.insert(stu4);
//    if(p.second){
//        cout << "王五子入学成功!" << endl;
//    }else{
//        cout << " 王五子入学失败!" << "\n";
//    }
    for(const Stu& stu:s1){
        stu.showInfo();
    }

    return 0;
}

map容器

对组pair构建方式:

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

int main()
{
    pair<string,int> p1("zhangsan",1001);
    cout << p1.first << "," << p1.second << endl;

    pair<string,int> p2={"lisi",1002};
    cout << p2.first << "," << p2.second << endl;

    pair<string,int> p3;
    p3.first="maliu";
    p3.second=1003;
    cout << p3.first << "," << p3.second << endl;

    pair<string,int> p4=make_pair("wangwu",1004);
    cout << p4.first << "," << p4.second << endl;
    
    pair<string,int> p5=map<string,int>::value_type("tianqi",1005);
    return 0;
}

Map容器的特性是:所有元素都会根据元素的键的值自动排序。Map所有的元素都是统一的pair对组,同时拥有键值Key实值Value,pair的第一元素被视为键值,第二个元素被视为实值,map不允许两个元素有相同的键。

我们可以通过map迭代器改变map的键值吗?答案是不行,因为map的键值关系到map的元素的排列布局,Map中的键是不可修改的,但是键对应的值是可以修改的。

所以Map的迭代器是一个双向迭代器,只支持++ == !=操作。

Map是可以随时插入或删除键值对的。

Map与multimap的唯一区别是multimap中的键是可以重复的。

Map的底层实现机制是由二叉树中的红黑树进行实现的。

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

int main()
{
    pair<string,int> p1("zhangsan",1001);

    pair<string,int> p2={"lisi",1002};

    pair<string,int> p3;
    p3.first="maliu";
    p3.second=1003;

    pair<string,int> p4=make_pair("wangwu",1004);

    pair<string,int> p5=map<string,int>::value_type("tianqi",1005);

    map<string,int> m1;
    m1.insert(p1);
    m1.insert(p2);
    m1.insert(p3);
    m1.insert(p4);
    m1.insert(p5);

    for(pair<string,int> p:m1){
        cout << p.first << p.second << endl;
    }
    cout <<m1.at("zhangsan") << endl;
//    cout <<m1.at("********") << endl;
    cout << m1["zhangsan"] << endl;
    cout << "------------------map[] not exist throw-------------" << endl;
    cout << m1["**********"] << endl;//副作用,会保存不存在的值
    for(pair<string,int> p:m1){
        cout << p.first << p.second << endl;
    }
    cout << "-----------------find-------------" << endl;
    auto it=m1.find("lisi");
    if(it!=m1.end()){
        cout << it->first << "," << it->second << endl;
    }else{
        cout << "no find!!!" << endl;
    }
    return 0;
}

注意:multimap中是没有【】中括号运算符的:

map中的[]注意事项:

map中的[]运算符具有两个作用:

1.就是可以在map容器直接插入一个键值对。

2.当map容器有同名的key时,使用[]也可修饰key对应的value的值。

3.注意:在multimap中是没有[]中括号运算符的。

4.中括号运算符如果map没有这个键时,将会自动插入这个键值对。

观察者设计模型

引言

用来解决两个不相关对象之间的一对一或者一对多的通信模型。

什么是观察者设计模式

观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系, 当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接受通知。观察者模式不仅被广泛应用于软件界面元素之间的交互,在业务对象之间的交互权限管理等方面也有广泛的应用。

解决的问题

定义了对象间的一种一对多的组合关系,以便一个对象的状态发生时,所有依赖于它的对象都得到通知并自动刷新。

观察者和被观察者之间存在“观察”的逻辑关系,当被观察者发生变化时,观察者就会观察到这样的变化,并作出相应的响应。

编程思路

  1. 设定两者类,一个为观察者类,一个为被观察者类

  2. 观察者类中,定义一个对某个事件感兴趣的处理函数,一般也叫做槽函数

  3. 被观察者类中,定义一个数据结构,用来保存观察者对某一个事件id(信号)感兴趣,使用数据结构建立信号与对象之间的映射关系

  4. 被观察者类中,定义两个方法函数:

    一个方法为:添加观察者与其感兴趣的事件id(信号)加入到容器中

    另一个方法为:信号函数:通知事件函数执行逻辑:首先遍历容器中,有没有感兴趣的事件ID,如果有,则代表一系列的观察者,对这个事件感兴趣,那么再次遍历观察者列表,让每一个观察者执行相应的槽函数

#include <iostream>
#include <map>
#include <list>

using namespace std;

class RecvBase
{
public:
    RecvBase(){
        cout << "--------------RecvBase structure------------------" << endl;
    }
    virtual void slotFunctions(int msgid)=0;
    virtual~RecvBase()
    {
        cout << "--------------RecvBase destruct------------" << endl;
    }
};

class Recv:public RecvBase
{
public:
    void slotFunctions(int msgid)override
    {
        switch(msgid)
        {
        case 1:
            cout << "接收到1信号,执行信号1对应槽函数逻辑" << endl;
            break;
        case 2:
            cout << "接收到2信号,执行信号2对应槽函数逻辑" << endl;
            break;
        case 3:
            cout << "接收到3信号,执行信号3对应槽函数逻辑" << endl;
            break;
        case 4:
            cout << "接收到4信号,执行信号4对应槽函数逻辑" << endl;
            break;
        }
    }
    Recv()
    {
        cout << "--------------structure--------------" << endl;
    }
    ~Recv()override
    {
        cout << "--------------destruct------------" << endl;
    }
};

class Sender
{
public:
    map<int,list<RecvBase*>> recvMap;
    void addRecvToMap(int msgid,RecvBase* recv)
    {
        this->recvMap[msgid].push_back(recv);
    }
    void signals(int msgid)
    {
        auto it=recvMap.find(msgid);
        if(it!=recvMap.end())
        {
            for(RecvBase* p:it->second)
                p->slotFunctions(msgid);
        }else
        {
            cout << "接受到未知信号,没有信号对应的槽函数逻辑" << endl;
        }
    }
};

int main(){
    Sender sender;
    RecvBase* r1=new Recv();
    RecvBase* r2=new Recv();
    RecvBase* r3=new Recv();
    RecvBase* r4=new Recv();

    sender.addRecvToMap(1,r1);
    sender.addRecvToMap(2,r2);
    sender.addRecvToMap(3,r3);
    sender.addRecvToMap(4,r4);


    int msgid;
    while(true)
    {
        cin >> msgid;
        if(-1==msgid)break;
        sender.signals(msgid);
    }
    delete r1;
    delete r2;
    delete r3;
    delete r4;
    return 0;
}

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

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

相关文章

虚拟机docker网络问题处理

问题 我们有2台设备&#xff0c;ip 为 172.20.30.1 172.20.30.2 &#xff0c;虚拟机上的服务需要连接这2台设备&#xff0c;网络已经做通了&#xff0c;可以正常连接虚拟机异常关闭&#xff0c;重新开启后。发现服务有些问题&#xff0c;就打算将docker服务重新部署&#xff0…

Vue后台项目的记录 (一)

一、下载模板 简洁版: https://github.com/PanJiaChen/vue-admin-template 下载下来的文件介绍 build ----index.js webpack配置文件【很少修改这个文件】 mock ----mock数据的文件夹【模拟一些假的数据mockjs实现的】&#xff0c;因为咱们实际开发的时候&#xff0c;利用的是…

STM32——FATFS文件系统

可裁剪意味着可以选择部分功能&#xff0c;减小占用的空间。 与Windows兼容意味着可以在电脑上直接读取文件。 ①底层接口&#xff0c;包括存储媒介读&#xff0f;写接口&#xff08;disk I/O&#xff09;和供给文件创建修改时间的实时时钟&#xff0c;需要我们根据平台和存储…

深度学习笔记(1)| 导数、偏导数、梯度和方向导数的理解

1. 梯度&#xff08;Gradient&#xff09;的理解 深度学习尝试在权重空间中找到一个方向&#xff0c;沿着该方向能降低损失函数的损失值。其实不需要随机寻找方向&#xff0c;因为可以直接计算出最好的方向&#xff0c;这就是从数学上计算出最陡峭的方向。这个方向就是损失函数…

单页扒手-基于Node的实现

做网站的朋友经常遇见别人的好看的页面&#xff0c;想保存到本地自己用&#xff0c;可是用以前的老办法网页另存为&#xff0c;发现很不好&#xff0c;规则不好处理&#xff0c;路径也不好处理&#xff0c;用这个网页克隆&#xff08;单页模板扒手&#xff09;就很好处理了&…

阿里「杀手锏」级语音识别模型来了!推理效率较传统模型提升10倍,已开源

阿里达摩院&#xff0c;又搞事儿了。 这两天&#xff0c;它们发布了一个全新的语音识别模型&#xff1a; Paraformer。 开发人员直言不讳&#xff1a;这是我们“杀手锏”级的作品。 ——不仅识别准确率“屠榜”几大权威数据集&#xff0c;一路SOTA&#xff0c;推理效率上相比…

Zookeeper 4 Zookeeper JavaAPI 操作 4.7 Curator API 常用操作【Watch 事件监听】

Zookeeper 【黑马程序员Zookeeper视频教程&#xff0c;快速入门zookeeper技术】 文章目录Zookeeper4 Zookeeper JavaAPI 操作4.7 Curator API 常用操作4.7.1 Watch 事件监听4 Zookeeper JavaAPI 操作 4.7 Curator API 常用操作 4.7.1 Watch 事件监听 【基本概念】 ZooKeep…

异常见闻录-Java.lang.UnsupportedClassVersionError涨知识啦

今天有学生说自己的Eclipse的Java代码只能在新建的项目中运行&#xff0c;运行导入项目中类就报错 异常信息 java.lang.UnsupportedClassVersionError: com/demo1/Demo1Application has been compiled by a more recent version of the Java Runtime (class file version 5…

CMake入门

1. CMake的介绍&#xff1a; 当多人开发同一个项目时&#xff0c;最终要输出一个可执行文件或者共享库&#xff08;dll、so等&#xff09;&#xff0c;就可以使用cmake了 所有操作都是通过 CMakeLists.txt&#xff08;严格区分大小写&#xff09; 来完成的 ----> 简单 2. …

C51——超声波测距 函数封装和舵机代码结合

要注意舵机转动中 延时函数要放在哪里 #include "reg52.h" sbit D5 P3^7;// sbit D6 P3^6;// sbit Trig P1^5; sbit Echo P1^6; sbit sg90_con P1^1; int cnt; int jd; double time; void Delay10us() //11.0592MHz { unsigned ch…

操作系统管程-地址-重定位-内存管理与存储管理

管程的基本概念&#xff1a;为什么会出现管程&#xff1f;信号量机制的不足&#xff1a;程序编写困难、易出错解决&#xff1a;Brinch Hansen(1973)Boare(1974)方案&#xff1a;在程序设计语言中引入管程成分一种高级同步机制管程的定义&#xff1a;是一个特殊的模块有一个名字…

使用MAXScript脚本编写圣诞树建模插件教程

一、前言 2022年圣诞节到来啦&#xff0c;很高兴这次我们又能一起度过~ 今年的圣诞节为大家分享用MAXScript脚本编写圣诞树建模插件的技术创意&#xff0c;喜欢的同学别忘记在下面点个赞&#xff01; 二、创意名 一键圣诞树插件 三、效果展示 四、实现步骤 1.制作圣诞树的设计稿…

智能巡检系统:企业安全生产管理的智能助手

智能巡检是一种高效代替传统人工巡检的新方式&#xff0c;其依靠物联感知技术&#xff0c;通过物联网采集获取信息&#xff0c;自动记录巡检信息&#xff0c;及时发现问题&#xff0c;实现巡检科学化。 工业4.0带来的技术革新加速了企业的转型升级进程&#xff0c;传统企业的运…

F5张振伦:让应用安全、快速、可靠地交付到需要的地方丨2022首届全球数字生态大会

科技云报道原创。 日前&#xff0c;由杭州市人民政府和浙江省商务厅主办的“2022首届全球数字生态大会”在杭州国际博览中心成功举办。 本次大会以“新技术、新业态、新模式”为主题&#xff0c;邀请到国内外20余位演讲嘉宾和超300位专业观众线下参会。 与此同时&#xff0c;…

IB成绩换成GPA,美国大学是如何算的?

IB课程体系是全球公认的难度大、结构强的课程体系。 IB课程可以通过IB文凭课程&#xff08;也称为IBDP&#xff09;进行系统研究。IB课程体系是国际公认的基础文凭&#xff0c;世界上几乎所有大学都认可IB课程体系。这是否意味着IB在申请美国大学时会有优势&#xff1f; 事实上…

【vue系列-02】vue的核心属性,数据代理,事件

vue的核心属性一&#xff0c;vue的核心属性1&#xff0c;模板语法1.1&#xff0c;插值语法1.2&#xff0c;指令语法2&#xff0c;数据绑定2.1&#xff0c;单向绑定2.2&#xff0c;双向绑定3.mvvm模型4&#xff0c;数据代理4.1&#xff0c;数据代理的基本使用4.2&#xff0c;数据…

pytorch实现好莱坞明星识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考文章地址&#xff1a; 365天深度学习训练营-第P6周&#xff1a;好莱坞明星识别&#x1f356; 作者&#xff1a;K同学啊一、前期准备 1.设置GPU import torch from torch import nn …

获取rdp保存的凭证

获取用户保存的rdp凭证 当获取到一台windows服务器&#xff0c;可以尝试获取本地远程连接的信息&#xff0c;如果用户在登入rdp时勾选了 允许为我保存凭证的选项&#xff0c;则在该用户本地会生成一个凭证文件&#xff0c;我们只需要破解该凭证文件即可获取其明文密码。 通过注…

[含文档+PPT+源码等]基于SSM框架图书借阅管理系统开发与设计

博主介绍&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 项目名称 [含文档PPT源码等]基于SSM框架图书借阅管理系统开发与设计 系统介绍 《基于SSM框架图书管理系统开发与设计》 该项目含有源码、配套开发软件、软件安…

元数据相关的术语,你知道几个?

元数据被认为是数据治理的基石&#xff0c;但关于元数据相关的概念&#xff0c;很多人不是那么清楚&#xff0c;今天就和大家详解元数据相关的术语。当然&#xff0c;与元数据相关的概念非常多&#xff0c;以下仅罗列几个常见的。 01 元数据 1.名词解释 元数据最简单的定义…