c++标准模板库STL

C++标准模板库 STL

1.C++STL的概述:

STL采用泛型思想,把C中所用到的所有的数据结构,按照一定的标准,全部封装成了一个个类模板。也被称为数据容器。
STL就是用来解决容器中数据元素的操作的问题的。并且他按排标准统一封装了操作容器元素的算法,即一个个的函数模板。
为了配合统一的算法去操作不同的容器,他又按标准统一封装了不同的迭代器,即一个个不同类型的具有指针特性的类模板。
所以容器,算法,迭代器是整个STL的核心。
三者的关系如图所示:

在这里插入图片描述
有了统一的数据结构,即容器,有了统一的算法,每一个容器都使用各自的不同的迭代器,从而实现了对数据结构元素的标准操作。
在实际的开发中,再也不要去自己去写各种数据结构了,也无需再去操作底层元素。因为有很多同学一说去写个链表就头疼,那么现在福音来了,你只要学好STL就行了。
对那些数据结构学的好的,我也想说一句,不要再公司开发中写你认为很牛的C型的数据结构,会被骂的,因为公司不认可。你可用来练手,但在公司项目中,不要出现。
因为STL是经过这多么年商业运营久经考验的共产主义战士。因为他是开源,大家如果有兴趣也可以看一本STL源码剖析这本书。
以后,程序中如果再用到数据结构时一定要第一时间想到C++STL中的容器,算法,迭代器,不要再用C数组之类的方式了,因为太老旧了且不安全。

2.C++STL标准模板库都有什么?

在这里插入图片描述

2.1:

就是我们常用的数据结构,C++重新封装成了 vector(动态数组),list(链表),deque(双向队列),set(集合) ,map(映射表)等,来实现存放数据,其中 set,map双叫关联容器 。其中的栈,单端队列,优先队列,又都是线性数据结构改造之后的具有革种特性的数据结构,又叫做容器适配器。

2.2:

序列式容器强调值的排序,序列式容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。vector容器,deque容器,List容器等。

2.3:

关联式容器是非线性的树结构,更准确的说是二叉树结构,各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素入容器时的逻辑顺序 。关联式容器另一个显著特点是:在值中选择一个值作为关键字Key,这个关键字对值起到了索引的作用,方便查找。Set/mulitiset容器 Map/multimap容器,并且容器是可以嵌套使用的。

3.算法

algorithom头文件中的定义相关的操作的一系列的函数模板
STL收录的算法经过了数学上的效能分析与证明,且是经过商业运行考验过的,是极具复用价值 的,包括常用的排序,查找等。
算法又可为为两种,质变算法,与非质变算法。
何为质变算法:是指运算过程中会更改区间内的元素的内容。例如:拷贝,替换,删除等。
何为非质变算法:是指运算过程中不会更改区间内的元素内容,例如:查找,计数,遍历,寻找极值等。

4.迭代器

迭代器的设计思维-STL 的关键所在,STL 的中心思想在于将容器(container)和算法(algorithms)分开,彼此独立设计,最后再一贴胶着剂将他们撮合在一起。从技术角度来看,容器和算法的泛型化并不困难,c++的 class template 和 functiontemplate 可分别达到目标,如果设计出两这个之间的良好的胶着剂,才是大难题
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式,迭代器就被发明了出来。

在这里插入图片描述

5.函数符

在STL中主要用来为算法提供策略。
有三种形式:函数指针,仿函数,与匿名函数Lambda表达式。

6.空间配置器

作用:主是用来解决内存碎片化问题的,以及过多的构造无用对象的问题。
容器的空间配置器的作用是把对象的内存开辟和构造过程分开,对象析构和内存释放分离开 。那为什么要把对象的内存开辟和构造(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的内存池作为空间配置器的内存管理方案,这又是一个新的积累。

7.字符容器库:

相关API的接口的使用:
string本质是一个类,内部维护了一个char*指针,封装了很多函数,不用担心越界。
2.1构造函数
原型:
string();
string(const char*s);
string(const string& str);
string(int n,char c);
2.2string赋值操作
原型:
string &operator =(const char*s);
string &operator =(const string &s);
string &operator =(char c );
 
string &assign(const char *s);
string &assign(const char *s,int n);//提取子串前n个
string &assign(const string &s);
string &assign(int n,char c);
2.3 字符串拼接
string& operator+=(const char *str);
string& operator+=(const string &str;
string& operator+=(const char c);
 
string & apped(const char *s);
string & apped(const char *s,int n);
string & apped(const string &s);
string & apped(const string &s ,int pos,int n);//从pos位置截取n个
2.4查找替换
fing("char *s");//返回值int 有 返回起始位置,无 返回-1; 默认从左往右//rfind 从右往左
2.5替换
replace(int pos,int n,char *s);//从下标pos位置n个字符,替换为s;
2.6字符串比较
compare(char *s1,char*s2);
=返回0
>返回1
<返回-1
2.7字符串存取
//通过[ ]访问单个字符,也可修改单个字符
//通过at访问单个字符,
2.8插入删除 
insert(int pos,char  *s);//第pos位置插入s
erase(int pos, int n);//pos位置删除n个字符
2.9获取子串
substr(int pos,int n);//从下标pos开始截取n个字符
#include <iostream>
using namespace std;
int main()
{
    string str(10,'w');
    cout << str <<endl;
    string str1("wanxi",1);
    cout << str1 << endl;
    char arr[] = "gaowanxi";
    string str2(arr,arr+3);
    cout << str2 << endl;

    string name = "gaowanxi";
    cout << name << endl;
    //at这个接口是有边界检查的。而[]中括号运算符是没有的。
    cout << name.at(0) << endl;
    //cout << name.at(100) << endl;
    cout << name[100] << endl;
    //name[0] = 'o';
    cout << name << endl;
    cout << name.front() << endl;
    cout << name.back() << endl;

    const char* s = name.data();
    const char* s1 = name.c_str();
    //清空字符容器。
    //name.clear();


    cout << "---------------------------" << endl;
    for(string::reverse_iterator it = name.rbegin(); it != name.rend(); it++)
    {
        cout << *it << endl;
    }

    name.insert(name.begin()+ 3,3,'h' );
    cout << name << endl;
    name.erase(3,3);
    cout << name << endl;
    name.push_back('s');
    cout << name << endl;
    name.pop_back();
    cout << name << endl;

    name.replace(0,3,"li");
    cout << name << endl;
    cout << "----------------------------" << endl;
    string sub = name.substr(0,2);
    cout << sub << endl;
    cout << "----------------------------" << endl;
    size_t  a = name.find("wan");
    cout << a << endl;
    string number = "123456";

    cout << stoi(number) + 4 << endl;
    string in_str;
    //string 的全局接口getline的用法。
    getline(cin,in_str);
    cout << in_str << endl;
    return 0;
}

8.动态数组vector

8.1基本概念:vector与array无乎是一样的,连续的存储结构,两者的唯一的区别在于在空间上的灵活,数组需要提前指定长度,不量确定了就不能发生改变了,比较死板,不够灵活。比如出现拷贝长度大于了给定的空间,需要再重新定义一个足够空间的大小,然后把旧空间的内容再一个个拷贝到新的空间,非常麻烦。
c++11引入array主要是用来描述一种支持迭代器的C风格数组的,所以数组怎么用,array就怎么用,他定义在栈上,且长度固定不会涉及动态内存的开辟所以没有push_back,pop_back的相关方法,但是Vector这种动态的数组在开发更为常用,他底层对象在堆上,且长度是可变的。所以也较为常用。
在这里插入图片描述
而vector容器是动态空间,他随着元素的加入,它的内部机制会自动扩充空间以容纳新的元素。因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助。我们再也不必害怕空间不足而一开始就定义一个巨大的数组了。
vector的空间配置策略。

#include <vector>
using namespace std;
int main()
{
    vector<int> v;
    v.reserve(100);
    //如果,你对所使用容器心中有数,可以使用保留接口函数提前指定具体长度。
    //这样vector就是不再进行二倍扩容了。
    for(int i = 0; i < 120; i++)
    {
        v.push_back(i);
        cout << "vector容量:" << v.capacity() << ",实际有效元素个数:" << v.size() << endl;
    }
    return 0;
}

vector的空间分配策略是2倍扩容的。

8.2vector的常用接口API:

学习API接口的套路:
1.先看类的构造函数,了解构造一个类对象
2.再看成员方法:(针对STL)增删改查CRUD
3.再看一些相关算法的操作。三大件:函数功能,参数,返回值。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
    //无参空构造
    vector<int> v;
    //使用有参构造容器对象。
    vector<int> v2(10,8);
    //使用普通数组的方式进行遍历。
    for(int i = 0; i < int(v2.size()); i++)
    {
        cout << v2[i] << " ";
    }
    cout << endl;
    int arr[5] = {2,9,8,10,1};


    vector<int> v3(arr,arr+5);
    for(int i = 0; i < int(v3.size()); i++)
    {
        cout << v3[i] << " ";
    }
    cout << endl;
    cout << "-----------------------------------------------" << endl;
    //迭代器遍历vector容器:
    for(vector<int>::iterator it = v3.begin(); it != v3.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
    // 枚举for循环:
    cout << "-----------------------------------------------" << endl;
    for(int k : v3)
    {
        cout << k << " ";
    }
    cout << endl;
    cout << "---------------------------------------------" << endl;
    
    for_each(v3.begin(),v3.end(),[](int val){
        cout << val << " ";
    });
    cout << endl;

    return  0;
}

8.3 vector中的迭代器非法化问题(迭代器失效问题):

在这里插入图片描述

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


int main()
{
    vector<int> v;
    for(int i = 0; i < 30; i++)
    {
        v.push_back(rand() %100 + 1);
    }
    //使用枚举for循环来打印输出
    for(int k : v)
    {
        cout << k << " ";
    };
    cout << endl;
#if 0
    cout << "------------------------------------------" << endl;
    //擦除后需要更新迭代器
    for(auto it = v.begin(); it != v.end();)
    {
        if(*it % 2 == 0)
        {
            it = v.erase(it);


        }
        else {
            it++;
        }
    }
#endif
    //插入后同样需要更新迭代器
    for(auto it = v.begin(); it != v.end(); it++)
    {
        if(*it %2 == 0)
        {
            it = v.insert(it,*it -1);
            it++;
        }
    }


    //使用枚举for循环来打印输出
    for(int k : v)
    {
        cout << k << " ";
    };
    cout << endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值