关键词:STL标准模板库,容器
STL概述
STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称。现然主要出现在C++中,但在被引入C++之前该技术就已经存在了很长的一段时间。从广义上讲分为三类:algorithm(算法)、container(容器)和iterator(迭代器),容器和算法通过迭代器可以进行无缝地连接。几乎所有的代码都采 用了模板类和模板函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。在C++标准中,STL被组织为下面的13个头文 件:<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack> 和<utility>。
STL的六大组件
- 容器(Container)
- 算法(Algorithm)
- 迭代器(Iterator)
- 仿函数(Function object)
- 适配器(Adaptor)
- 空间配制器(allocator)
STL的优点
- STL的一个重要特点是数据结构和算法的分离。
- STL具有高可重用性,高性能,高移植性,跨平台的优点
容器
所谓容器,经典的数据结构数量有限,但是我们常常重复着一些为了实现向量、链表等结构而编写的代码,这些代码都十分相似,只是为了适应不同数据的变化而在 细节上有所出入。STL容器就为我们提供了这样的方便,它允许我们重复利用已有的实现构造自己的特定类型下的数据结构,通过设置一些模板,STL容器对最常用的数据结构提供了支持,这些模板的参数允许我们指定容器中元素的数据类型,可以将我们许多重复而乏味的工作简化。
容器的分类:
- 序列式容器(Sequence containers)
每个元素都有固定位置--取决于插入时机和地点,和元素值无关。
vector、deque、list
- 关联式容器(Associated containers)
元素位置取决于特定的排序准则,和插入顺序无关
set、multiset、map、multimap
迭代器:
迭代器从作用上来说是最基本的部分,可是理解起来比前两者都要费力一些。软件设计有一个基本原则,所有的问题都可以通过引进一个间接层来简化, 这种简化在STL中就是用迭代器来完成的。概括来说,迭代器在STL中用来将算法和容器联系起来,起着一种黏和剂的作用。几乎STL提供的所有算法都是通 过迭代器存取元素序列进行工作的,每一个容器都定义了其本身所专有的迭代器,用以存取容器中的元素。
- 迭代器是一个“可遍历STL容器内全部或部分元素”的对象。
- 迭代器指出容器中的一个特定位置。
- 迭代器就如同一个指针。
- 迭代器提供对一个容器中的对象的访问方法,并且可以定义了容器中对象的范围。
这里大概介绍一下迭代器的类别。
- 输入迭代器:也有叫法称之为“只读迭代器”,它从容器中读取元素,只能一次读入一个元素向前移动,只支持一遍算法,同一个输入迭代器不能两遍遍历一个序列。
- 输出迭代器:也有叫法称之为“只写迭代器”,它往容器中写入元素,只能一次写入一个元素向前移动,只支持一遍算法,同一个输出迭代器不能两遍遍历一个序列。
- 正向迭代器:组合输入迭代器和输出迭代器的功能,还可以多次解析一个迭代器指定的位置,可以对一个值进行多次读/写。
- 双向迭代器:组合正向迭代器的功能,还可以通过--操作符向后移动位置。
- 随机访问迭代器:组合双向迭代器的功能,还可以向前向后跳过任意个位置,可以直接访问容器中任何位置的元素。
C++标准库:
C++强大的功能来源于其丰富的类库及库函数资源。C++标准库的内容总共在50个标准头文件中定义。在C++开发中,要尽可能地利用标准库完 成。这样做的直接好处包括:
(1)成本:已经作为标准提供,何苦再花费时间、人力重新开发呢;
(2)质量:标准库的都是经过严格测试的,正确性有保证;
(3)效率:关于人的效率已经体现在成本中了,关于代码的执行效率要相信实现标准库的大牛们的水平;
(4)良好的编程风格:采用行业中普遍的做法进行开发。
几个主要容器
string
string是STL的字符串类型,通常用来表示字符串。
/*为了方便整理,把STL中与String相关的内容分成各个函数*/
//string 的构造函数
void StringInit()
{
string s1; //无参的默认构造函数
string s2("tqn666"); //有参的构造函数
string s3(6, 'w'); //构造N个字符
string s4(s2); //拷贝构造函数
}
/****************************************************************************/
//String中取字符操作
void StringAt()
{
string s1("tqn666");
cout << s1[3] << endl; //用[]取第四个字符‘6’
cout << s1.at(3) << endl; //用at()函数取第四个字符‘6’
try
{
cout << s1.at(7) << endl; //at()与[]的区别就是at()更安全,当越界时能抛出异常
}
catch (exception &e)
{
cout << e.what() << endl;
}
}
/****************************************************************************/
//string与const char *的转换
void StringStr()
{
string s1("tqn666");
const char *ptr = s1.c_str(); //使用自带的c_str()函数
cout << ptr << endl;
}
/****************************************************************************/
//string的拷贝操作
void StringCopy()
{
string s1("tqn666");
char *buf = new char[32];
s1.copy(buf, 3); //直接使用copy()函数,参数依次是目标,长度,从第几个开始拷贝
cout << buf << endl;
}
/****************************************************************************/
//string的求字符串长度,判空操作
void StringLength()
{
string s1("helloworld");
cout << s1.length() << endl;
string s2;
if (s2.empty())
{
cout << "empty" << endl;
}
}
/****************************************************************************/
//string的赋值
void StringAssign()
{
string s1("12345678");
string s2;
s2 = s1;
cout << s2 << endl;
string s3;
s3.assign("12345");
cout << s3 << endl;
s3.assign("134556", 3);
cout << s3 << endl;
s3.assign(10, 'a');
cout << s3 << endl;
s3.assign(s2);
cout << s3 << endl;
s3.assign(s1, 3, 3);
cout << s3 << endl;
}
/****************************************************************************/
//string的字符串连接
void StringAppend()
{
string s1("aaa");
s1 += "bbb";
cout << s1 << endl;
string s2("bbb");
s2.append(s1);
cout << s2 << endl;
s2.append(10, 'x');
cout << s2 << endl;
s2.append(s1, 4, 2);
cout << s2 << endl;
}
/****************************************************************************/
//字符串比较
void StringCompare()
{
string s1("hello");
string s2("world");
if (s1.compare(s2) < 0)
{
cout << "小于" << endl;
}
if (s2.compare("aaaaa") > 0)
{
cout << "大于" << endl;
}
}
/****************************************************************************/
//求子串
void StringSub()
{
string s1("helloworld");
cout << s1.substr(3, 4) << endl;
}
/****************************************************************************/
//查找
void StringFind()
{
string s1("helloworldhelloworldhelloworld");
int index = s1.find('x');
cout << index << endl;
cout << s1.find('x') << endl;;
index = s1.find('w', 6);
cout << index << endl;
index = s1.find("world", 1);
cout << index << endl;
//s1.replace(5, 5, "xx");
//cout << s1 << endl;
index = s1.find("world", 0);
while (index != -1)
{
s1.replace(index, 5, "xx");
index = s1.find("world", index + strlen("xx"));
}
cout << s1 << endl;
}
/****************************************************************************/
//插入
void StringInsert()
{
string s1("helloworld");
s1.insert(5, "xxxx");
cout << s1 << endl;
s1.erase(5, 4);
cout << s1 << endl;
}
string中at()与[ ]的区别
vector
vector是将元素置于一个动态数组中加以管理的容器。
vector可以随机存取元素(支持索引值直接存取, 用[]操作符或at()方法)。
vector尾部添加或移除元素非常快速。但是在中部或头部插入元素或移除元素比较费时
//vector的构造
int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
vector<int> v1;
vector<int> v2(10);
vector<int> v3(array, array + sizeof(array) / sizeof(array[0]));
vector<int> v4(10, 10);
/***********************************************************************/
//vector的赋值
void VectorAssign()
{
int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
vector<int> v1(10);
v1.assign(array, array + sizeof(array) / sizeof(array[0]));
for (int i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
vector<int> v2(10);
v2.assign(10, 10);
for (int i = 0; i < v2.size(); i++)
{
cout << v2[i] << " ";
}
cout << endl;
v2 = v1;
for (int i = 0; i < v2.size(); i++)
{
cout << v2[i] << " ";
}
cout << endl;
vector<int> v3(10);
for (int i = 0; i < v3.size(); i++)
{
v3[i] = i + 1;
}
v2 = v3;
for (int i = 0; i < v2.size(); i++)
{
cout << v2[i] << " ";
}
cout << endl;
v3.swap(v1);
for (int i = 0; i < v3.size(); i++)
{
cout << v3[i] << " ";
}
cout << endl;
}
/***********************************************************************/
//vector的数据存取,通过迭代器
void VectorIterator()
{
vector<int> v1(10);
for (int i = 0; i < v1.size(); i++)
{
v1[i] = i + 1;
}
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
{
cout << *it << " ";
}
cout << endl;
for (vector<int>::reverse_iterator rit = v1.rbegin(); rit != v1.rend(); rit++)
{
cout << *rit << " ";
}
cout << endl;
}
/***********************************************************************/
//vector的插入
void VectorInsert()
{
vector<int> v1(10);
for (int i = 0; i < v1.size(); i++)
{
v1[i] = i + 1;
}
vector<int>::iterator it = v1.begin();
it += 3;
v1.insert(it, 100);
for (it = v1.begin(); it != v1.end(); it++)
{
cout << *it << " ";
}
cout << endl;
it = v1.begin();
it++;
v1.insert(it, 10, 20);
for (it = v1.begin(); it != v1.end(); it++)
{
cout << *it << " ";
}
cout << endl;
vector<int> v2(10);
for (int i = 0; i < v2.size(); i++)
{
v2[i] = i + 20;
}
it = v2.begin();
v1.insert(v1.begin(), it, it + 5);
for (it = v1.begin(); it != v1.end(); it++)
{
cout << *it << " ";
}
cout << endl;
v2.push_back(1000);
for (it = v2.begin(); it != v2.end(); it++)
{
cout << *it << " ";
}
cout << endl;
v2.pop_back();
for (it = v2.begin(); it != v2.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}
/***********************************************************************/
//vector的大小
void VectorSize()
{
vector<int> v1(10);
for (int i = 0; i < v1.size(); i++)
{
v1[i] = i + 1;
}
v1.resize(20, 99);
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
{
cout << *it << " ";
}
cout << endl;
v1.resize(5);
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}
/***********************************************************************/
//vector的删除
void VectorErase()
{
vector<int> v1(10);
for (int i = 0; i < v1.size(); i++)
{
v1[i] = i + 1;
}
vector<int>::iterator it = v1.begin();
v1.erase(it + 1, it + 8);
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
{
cout << *it << " ";
}
cout << endl;
v1.clear();
}
deque
deque是“double-ended queue”的缩写,和vector一样都是STL的容器,deque是双端数组,而vector是单端的。
deque在接口上和vector非常相似,在许多操作的地方可以直接替换。
deque可以随机存取元素(支持索引值直接存取, 用[]操作符或at()方法,这个等下会详讲)。
deque头部和尾部添加或移除元素都非常快速。但是在中部安插元素或移除元素比较费时。
deque与vector用法一致,效率更高,这里不再赘述
priority_queue
最大值优先级队列、最小值优先级队列,优先级队列适配器 STL priority_queue
#include "iostream"
#include "time.h"
#include "queue"
#include "functional"
#include "cstring"
using namespace std;
class Student
{
friend class Compare;
public:
int m_id;
Student(int id);
void print()const;
};
Student::Student(int id)
{
m_id = id;
}
void Student::print()const
{
cout<<m_id<<endl;
}
class Compare
{
public:
bool operator ()(const Student &s1, const Student &s2)
{
return (s1.m_id > s2.m_id);
}
};
int main()
{
srand(time(NULL));
//priority_queue<int, deque<int>, less<int> >q;
/*priority_queue<int, deque<int>, greater<Student> >q;
for (int i = 0; i < 10; i++)
{
q.push(rand()%20);
}
for (int i = 0; i < 10; i++)
{
cout << q.top() << " ";
q.pop();
}*/
Student s1(rand() % 20);
Student s2(rand() % 20);
Student s3(rand() % 20);
priority_queue<Student, deque<Student>, Compare >q1;
q1.push(s1);
q1.push(s2);
q1.push(s3);
int length = q1.size();
for (int i = 0; i < length; i++)
{
q1.top().print();
q1.pop();
}
system("pause");
return 0;
}
最大优先级或最小优先级由最后一个参数决定,这是一个库里封装好的函数对象,less最大优先,greater最小优先,也可以自定义一个函数对象
set/multiset
- set是一个集合容器,其中所包含的元素是唯一的,集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置。
- set采用红黑树变体的数据结构实现,红黑树属于平衡二叉树。在插入操作和删除操作上比vector快。
- set不可以直接存取元素。(不可以使用at.(pos)与[]操作符)。
- multiset与set的区别:set支持唯一键值,每个元素值只能出现一次;而multiset中同一值可以出现多次。
- 不可以直接修改set或multiset容器中的元素值,因为该类容器是自动排序的。如果希望修改一个元素值,必须先删除原有的元素,再插入新的元素。
set的默认构造
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容器。
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
}
//这样子便顺序输出 1 2 3 5。
set的元素排序
set<int,less<int> > setIntA; //该容器是按升序方式排列元素。
set<int,greater<int>> setIntB; //该容器是按降序方式排列元素。
函数对象functor
- 尽管函数指针被广泛用于实现函数回调,但C++还提供了一个重要的实现回调函数的方法,那就是函数对象。
- functor,翻译成函数对象,伪函数,算符,是重载了“()”操作符的普通类对象。从语法上讲,它与普通函数行为类似。
- greater<>与less<>就是函数对象
set的 拷贝构造与赋值,大小,删除,查找
set(const set &st); //拷贝构造函数
set& operator=(const set &st); //重载等号操作符
set.swap(st); //交换两个集合容器
set.size(); //返回容器中元素的数目
set.empty();//判断容器是否为空
set.clear(); //清除所有元素
set.erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
set.erase(beg,end); //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
set.erase(elem); //删除容器中值为elem的元素。
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.equal_range(elem); //返回容器中与elem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)。
multiset与set的区别:set支持唯一键值,每个元素值只能出现一次;而multiset中同一值可以出现多次。其他用法一致。
map/multimap
- map是标准的关联式容器,一个map是一个键值对序列,即(key,value)对。它提供基于key的快速检索能力。
- map中key值是唯一的。集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置。
- map的具体实现采用红黑树变体的平衡二叉树的数据结构。在插入操作和删除操作上比vector快。
- map可以直接存取key所对应的value,支持[]操作符,如map[key]=value。
- multimap与map的区别:map支持唯一键值,每个键只能出现一次;而multimap中相同键可以出现多次。multimap不支持[]操作符。
map封装好了,其各功能函数与set类似,这里不再赘述
最后说几点
- 所有容器提供的都是值(value)语意,而非引用(reference)语意。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被拷贝(必须提供拷贝构造函数)。
- 除了queue与stack外,每个容器都提供可返回迭代器的函数,运用返回的迭代器就可以访问元素。
- 通常STL不会丢出异常。要求使用者确保传入正确的参数。
- 每个容器都提供了一个默认构造函数跟一个默认拷贝构造函数。