stl概述

chapter1 stl介绍

1 stl四个概念库

  • 容器库:管理和存储数据的容器(数组链表,映射集合)

  • 迭代器库(iterator头文件中)

  • 算法库(algorithm头文件中)通用算法

  • 数值库(数学函数+数值处理高级函数)

2 模板

从模板生成的函数或类的定义是模板的实例或实例化

可以以内联的方式为类模板的成员函数指定一个外部模板

template<typename T>
inline Array<T>::Array(cosnt Array& other)
try:elements{new T[other.count]},count{other.count}
{
    for (size_t i = 0; i < count; i++)
    {
        elements[i] = other.elements[i];
    }
}
catch(bad_alloc&)
{
    cerr<<"memory allocation failed for Array object copy."<<endl;
}

每个类模板类型参数需要一个实参,除非有默认实参

Array<int> data{40};

编译上述 语句时,发生

  1. Array<int> 类的定义被创建,所以确定了参数的类型
  2. 因为必须调用构造器去生成一个对象,所以生成了构造器的定义
  3. 析构函数被用来销毁对象。
//模板别名
template<typename T> using ptr = shared_ptr<T>
//ptr<T> 作为 shared_ptr<T>的别名

3 容器

  • 序列容器:以线性组织的方式存储对象,和数组类似,但不需要连续存储空间。
  • 关联容器:存储了一些和键关联的对象。k-v
  • 容器适配器:提供了替换机制的适配类模板。可以用来访问基础的序列容器和关联容器

所有的stl存储的都是对象的副本(除非对象是右值)

stl要求移动构造器和复制运算符都是noexcept

在存储基类指针或基类智能指针的容器中存储派生类指针(多态技术)而不要存储对象。

4 迭代器

结束迭代器表示一个容器中的所有元素,不会指向具体某个元素,所以不能解引用。

  1. 获取迭代器
begin();//方法,函数均可
end();
cbegin();//返回常量迭代器
cend();
  1. 一个 迭代器必须有一个拷贝构造函数,一个析构函数,一个拷贝赋值运算符

  2. 一个算法可以两种方式使用迭代器参数的类别:

    1. 为了满足操作,他会确立需要满足的最低限度要求
    2. 如果超出了迭代器的最低要求,算法使用扩展的功能可以更高效地执行运算

迭代器类别

  1. 输入迭代器:提供对象的只读访问(*iter)引用他所指向的值

    1. 包含操作 ++iter iter++ iter1==iter2 iter!=iter2
    2. 没有减量运算
    3. 自增后,只能通过使用新的迭代器来访问上一个他指向的元素
    4. iter->num来访问元素
  2. 输出迭代器:提供对象只写访问

    1. 包含操作 ++iter iter++
    2. 没有减量操作
    3. 每次想写一个序列元素时,都需要新创建一个新的迭代器
  3. 正向迭代器:结合了输入输出功能,可以使用多次(可以多次使用和读写)

  4. 双向迭代器:比正向迭代器多了一个iter--操作,可以向后遍历

  5. 随机访问迭代器:比双向迭代器多了随机访问的功能(支持很多操作)

    1. iter+n iter-n iter+=n iter-=n

    2. iter[n] *(iter+n)

    3. iter1-iter2:两个迭代器之间元素的个数

    4. iter1<iter2    iter1>iter2
      

一个随机访问迭代器可以像数组一样访问按下标访问元素

iter[3] ;//*(iter+3);//第四个元素

accumulate() 根据迭代器指针,计算容器中值的和(#include<numeric>)

案例

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

int main()
{
    double data[] {2.5, 4.5, 6.5, 5.5, 8.5};
    cout<<"the array contains :"<<endl;
    for(auto iter = begin(data);iter!=end(data);iter++)
    {
        cout<<*iter<<' ';
    }
    auto total = accumulate(begin(data),end(data),0.0);
    //初始   末尾    sum的初始值(确保输入的类型要一致)
    cout<<" the total  of array is "<<total<<endl;
    return 0;
}

流迭代器:

cout<<"enter an array number split with space"<<endl;
    cout<<accumulate(istream_iterator<double>(cin),istream_iterator<double>(),0.0);

istream_iterator就是一个标准的流输入迭代器,可以是文件流,也可以是cin标准输入流

istream_iterator() 无参构造器可以创建一个结束标志(ctrl+z)

迭代器适配器

  1. 反向迭代器:与常规迭代器完全相反

    1. rbegin()指向最后一个元素
    2. rend()指向第一个元素之前
    3. ++表示从最后向前走一位
  2. 插入迭代器:不能别运用到array上

    1. 后向插入迭代器:push_back(),将元素添加到容器尾部
      1. vector
      2. list
      3. deque
    2. 前向插入迭代器:push_front(),将元素添加到容器头部
      1. list
      2. foword_list
      3. deque
    3. 插入迭代器:向任何有insert()函数的容器中插入新元素
      1. string
  3. 移动迭代器:将某个范围的类对象移动到目标范围,而不需要通过拷贝去移动。可以将移动迭代器作为输入迭代器,将所指向的对象转换为右值引用,如此,可以实现移动对象,而不是拷贝对象。

迭代器上的运算

  1. advance(iter,n); 相当于iter+3
  2. distance(begin(data),end(data)); 返回两个指针间元素的个数
  3. next(iter,n); 正向偏移(iter+3)
  4. prev(); 反向偏移(iter-3)

5 智能指针

智能指针只能用来保存堆上分配的内存的地址

不能自增自减

unique_ptr<T>

一个指向类型T的指针,排他(不可能有其他的unique_ptr<T>指向同一个地址)

但是当一个对象被一个unique_ptr<T>指向时,也可以通过生成一个原生指针来访问对象

  • //生成这种指针的最好的方式是使用make_unique<T>()函数
    auto pname = make_unique<string>("hhw");
    //通过解引用的方式来使用该对象
    cout<<*pnanme<<endl;
    
    //指向数组:
    unique_ptr<int[]> pnumbers = make_unique<int[]>(10);
    //按索引访问
    for(int i=0;i<10;i++)
    {
        pnumbers[i] = i*i;
    }
    
  • 不能以传值的方式将一个unique_ptr<T> 对象传入函数中,因为他们不支持拷贝,必须使用引用的方式。

  • 类的get成员函数可以返回一个unique_ptr<T>所包含的原生指针

auto unique_p = make_unique<string>(6,"*");
string pstr{unique_p.get()};
//当需要访问一个对象时,他的智能指针存储在一个类中
  1. 重置unique_ptr<T>对象
  • 调用reset()之后,由unique_ptr<T>生成的原生指针会被置空,指向的地址空间被析构。

  • auto pname = make_unique<string>("hhe");
    pname = reset();//释放string对象的内存
    pname.reset(new string({"1234rt"}));
    //将之前指定的对象释放,内存中生成一个新的字符串,被pname保存
    
    
  • 不要将其他unique_ptr<T>所指向的一个对象的地址传给reset(),或者去生成一个新的unique_ptr<T>对象

    • 比如释放ptr1,指向ptr2
    • 当ptr2不在使用时,会再次释放ptr1
  • 可以调用release()方法去释放一个unique_ptr所指向的对象(可以在不释放对象内存的情况下,将指向他内部的原生指针置空

  • 比较和检查unique_ptr<T>对象---就是比较两个对象get()函数返回的地址值

  • 当对unique_ptr指针对象调用reset或者release时,需要先检查是否为空,解引用需要确保非空

shared_ptr<T>

用法大致和unique_ptr相同,不过shared_ptr 维护了一个控制块,用于记录当前有多少个shared_ptr指向当前地址空间,没增加一个,控制块会增加

使用成员函数get()可以获得一个原生指针

只能通过赋值运算符或者拷贝构造函数去复制一个shared_ptr对象,不可以使用一个shared_ptr的get()返回的指针来生成新的shared_ptr

  • 重置shared_ptr对象

    auto pname = make_shared<string>("hhw");
    pname = nullptr;
    //cnt会减少
    pname.reset();//同样效果,重置对象
    
    
  • reset()可以传入一个原生指针来改变共享指针指向的对象

pname.reset(new string("bjbf"));
  • 比较和检查shared_ptr对象
  1. unique() //对象只有一个实例返回true,其他为false
  2. use_count() //返回当前被调用对象的实例个数
pname = nullptr;
if (pname.unique())
{
    cout<<"only one "<<endl;
}
else{
    cout<<pname.use_count()<<endl;
}  

weak_ptr<T>

作用:

判断他所指向的对象是否存在(仍有共享指针指向他)

从一个weak_ptr中创建共享指针

//创建
auto pdata = make_shared<string>();
weak_ptr<string> pwdata{pdata};
weak_ptr<string> pwdata2{pwdata};
if (pwdata.expired())
{
    //对象不存在
}
else{
    //对象存在
}

5 函数对象

案例

重写operator() 即可实现自动调用

//BOX.H
#include<iostream>
class Box
{
private:
    double length;
    double height;
    double width;
public:
    Box(double lv,double hv,double wv);
    ~Box();
    double volume() const {return length*height*width;}
    double getLength() const{return length;}
    double getHeight() const {return height;}
    double getWidth() const {return width;}
};

Box::Box(double lv,double hv,double wv)
{
    length = lv;
    height = hv;
    width = wv;
}

Box::~Box()
{
}

//VOLUME.H
#include"Box.h"
class Volume
{
private:
    /* data */
public:
    Volume(/* args */);
    ~Volume();
    double operator()(double x,double y,double z){return x*y*z;}
    double operator()(const Box& box){return box.getLength()*box.getHeight()*box.getWidth();}
};


//main()
#include"volume.h"
using namespace std;
int main() 
{
    Volume volume;
    Box box{1,2,3};
    cout<<volume(box)<<endl;
    return 0;
}


lambda表达式传递给函数

因为类型不明确,所以要建立一个接受lambda表达式为参数的函数模板

案例:

#include<iostream>
using namespace std;
//接受lambda表达式为参数的函数模板F fun
template<typename ForwardIter,typename F>
void change(ForwardIter begin,ForwardIter end,F fun)
{
    for(auto iter=begin;iter!=end;iter++)
    {
        *iter  = fun(*iter);
    }
}

int main()
{
    int data[]{1,2,3,4,5,6,7,8};
    for (auto &&i : data)
    {
        cout<<i<<' ';
    }
    cout<<endl;
    //传入lambda表达式
    change(begin(data),end(data),[](int value){
        return value*value;
    });
    for (auto &i : data)
    {
        cout<<i<<' ';
    }
    
    return 0;
}

标准库的function头文件定义了一个模板类型的functional<> 这是对任意类型函数指针的封装,有给定的返回类型和形参类型

//对任意类型函数指针的封装
functional<double(double)> op {
    [](double value){return value*value*value;}
};

  1. generate(be,end,lambda)函数模板:

用函数对象计算值,初始化一段元素

unsigned int height{};
//lambda每次调用都会增加height
generate{
    begin(data),end(data),
    [height,&min_ht,&ht_step]()mutable
    {return height+=height==0?min_ht:ht_step;}
    };
  1. itoa(be,end,value)函数模板

只要支持operator++()的类型,都可以使用此方法进行连续递增初始化,第一个元素从value开始

array<double,10> values;
iota(begin(values),end(values),10.0);
//values数值为 10 11 12 13 ----- 19.0
  1. transform(be,end,指定结果存放起始位置的迭代器,应用到输入序列的函数)

该算法将be->end的所有元素应用fun操作,存储值新的迭代器中

transform(
    begin(),end(),
    ostream_iterator<double>(cout,""),
    []double x {return x*x;}
);
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容