目录
一、STL的基础知识
1、STL简介
2、STL基本组成
2.1 容器
2.2 迭代器
二、应用 1:结合容器和迭代器解决序列变换和像素变换
1、常规方法实现序列变换,如取反、平方、立方
2、特殊方法实现序列变换:模板函数
3、结合容器和迭代器解决序列变换
4、结合容器和迭代器解决像素变换
5、算法和函数对象的使用
(二) 应用 2:用set存储学生信息,实现增删改查操作
1、构建学生信息类StudentInfo
2、使用set容器存储学生信息
3、对学生信息进行增删改查操作
(三) 应用 3:利用map字典统计字符频数并输出
(四) 实验总结
标准模板库(Standard Template Library,简称STL)定义了一套概念体系,为泛型程序设计提供了逻辑基础。STL中的各个类模板、函数模板的参数都是用这个体系中的概念来规定的。使用STL的模板时,类型参数既可以是C++标准库中已有的类型,也可以是自定义的类型——只要这些类型是所要求概念的模型。该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法。
通常认为,STL是由容器、算法、迭代器、函数对象、适配器、内存分配器这 6 部分构成,其中后面 4 部分是为前 2 部分服务的,它们各自的含义下表所示。
STL的组成 | 含义 |
---|---|
容器 | 一些封装数据结构的模板类,例如 vector 向量容器、list 列表容器等。 |
算法 | STL 提供了非常多(大约 100 个)的数据结构算法,它们都被设计成一个个的模板函数,这些算法在 std 命名空间中定义,其中大部分算法都包含在头文件 |
迭代器 | 在C++ STL中,对容器中数据的读写,是通过迭代器完成的,扮演着容器和算法间的胶合剂。 |
函数对象 | 如果一个类将 () 运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象(又称仿函数)。 |
适配器 | 可以使一个类的接口(模板的参数)适配成用户指定的形式,从而让原本不能在一起工作的两个类工作在一起。值得一提的是,容器、迭代器和函数都有适配器。 |
内存分配器 | 为容器类模板提供自定义的内存申请和释放功能,由于往往只有高级用户才有改变内存分配策略的需求,因此内存分配器对于一般用户来说,并不常用。 |
STL中的容器有序列式容器和关联式容器,容器适配器(stack,queue,priority queue),位集(bit_set),串包(string_package)等等。
标准容器类 |
特点 |
顺序性容器 |
|
vector |
从后面快速的插入与删除,直接访问任何元素 |
deque |
从前面或后面快速的插入与删除,直接访问任何元素 |
list |
双链表,从任何地方快速插入与删除 |
关联容器 |
|
set |
快速查找,不允许重复值 |
multiset |
快速查找,允许重复值 |
map |
一对多映射,基于关键字快速查找,不允许重复值 |
multimap |
一对多映射,基于关键字快速查找,允许重复值 |
容器适配器 |
|
stack |
后进先出 |
queue |
先进先出 |
priority_queue |
最高优先级元素总是第一个出列 |
序列式容器:排列次序取决于插入时机和位置
关联式容器:排列顺序取决于特定准则
容器的通用功能
迭代器是算法和容器的桥梁
- 迭代器用作访问容器中的元素
- 算法不直接操作容器中的数据,而是通过迭代器间接操作
算法和容器独立
- 增加新的算法,无需影响容器的实现
- 增加新的容器,原有的算法也能适用
迭代器的分类
迭代器的区间
迭代器的辅助函数
对可逆容器的访问
STL为每个可逆容器都提供了逆向迭代器,逆向迭代器可以通过下面的成员函数得到:
逆向迭代器的类型名的表示方式如下(S表示容器类型):
随机访问容器
随机访问容器支持对容器的元素进行随机访问
对于int整型的数组,我们可以实现如下的代码:
// 序列变换 ———— 取反
void transInv(int a[], int b[], int nNum){
for(int i = 0; i < nNum; i++){
b[i] = - a[i];
}
}
// 序列变换 ———— 平方
void transSqr(int a[], int b[], int nNum){
for(int i = 0; i < nNum; i++){
b[i] = a[i] * a[i];
}
}
// 序列变换 ———— 立方
void transCub(int a[], int b[], int nNum){
for(int i = 0; i < nNum; i++){
b[i] = a[i] * a[i] * a[i];
}
}
// 模板函数:自定义输出内容
template
void OutPutCont(string strName, ostream& os, T begin, T end){
os<
代码测试:
int main()
{
const int N = 5;
int a[N] = {5, 2, 7, 0, 3};
int b[N], c[N], d[N];
transInv(a, b, N); //取反
transSqr(a, c, N); //平方
transCub(a, d, N); //立方
OutPutCont("数组a", cout, a, a + N);
OutPutCont("取反a", cout, b, b + N);
OutPutCont("平方a", cout, c, c + N);
OutPutCont("立方a", cout, d, d + N);
return 0;
}
测试结果:
常规的方法不能代码的复用性不高,对于double型或者float型的数据,不能起到效果,所以我们可以模板函数来解决这个问题。
模板函数的相关知识可以参考我的上一章博客:C++ 程序设计 —— 实验三:模板_DreamWendy的博客-CSDN博客
// 序列变换 ———— 取反
template
void transInvT(T a[], T b[], int nNum){
for(int i = 0; i < nNum; i++){
b[i] = - a[i];
}
}
// 序列变换 ———— 平方
template
void transSqrT(T a[], T b[], int nNum){
for(int i = 0; i < nNum; i++){
b[i] = a[i] * a[i];
}
}
// 序列变换 ———— 立方
template
void transCubT(T a[], T b[], int nNum){
for(int i = 0; i < nNum; i++){
b[i] = a[i] * a[i] * a[i];
}
}
代码测试:
int main()
{
const int N = 5;
float a[N] = {1.5, 2.70, 1.5, 0.7, 3.1};
float b[N], c[N], d[N]; //浮点型
transInvT(a, b, N); //取反
transSqrT(a, c, N); //平方
transCubT(a, d, N); //立方
OutPutCont("数组a", cout, a, a + N);
OutPutCont("取反a", cout, b, b + N);
OutPutCont("平方a", cout, c, c + N);
OutPutCont("立方a", cout, d, d + N);
return 0;
}
测试结果:
// 结合迭代器 ———— 取反
template
void transInvT(InputIter begInput, InputIter endInput, OutputIter begOutput){
for(; begInput != endInput; begInput++, begOutput++){
*begOutPut = ‐ (*begInput)
}
}
上面代码的缺点是对于平方、立方等操作都要写重写函数实现,复用性不好,下面进行改性:
// 取反操作
template
T InvT(T a)
{
return -a;
}
// 平方操作
template
T SqrT(T a)
{
return a * a;
}
// 立方操作
template
T CubT(T a)
{
return a * a * a;
}
// 结合容器和迭代器解决序列变换
template
void transfrom(InputIter begInput, InputIter endInput, OutputIter begOutput, MyOperator op){
for(; begInput != endInput; begInput++, begOutput++){
*begOutput = op(*begInput); // 序列变换,改写为函数的形式
}
}
代码测试:
int main()
{
const int N = 5;
int a[N] = {5, 2, 7, 1, 4}; //整型数组
int b[N];
vector vb(N); //vector(向量)容器
OutPutCont("数组a", cout, a, a+N); //输出数组a
transfrom(a, a+N, b, InvT); //通过迭代器取反数组a
OutPutCont("通过迭代器取反数组 a", cout, b, b + N);
transfrom(a, a+N, vb.begin(), InvT); //结合容器和迭代器取反数组a
OutPutCont("结合容器和迭代器取反", cout, vb.begin(), vb.end());
transfrom(a, a+N, b, SqrT); //通过迭代器平方数组a
OutPutCont("通过迭代器平方数组 a", cout, b, b + N);
transfrom(a, a+N, vb.begin(), SqrT); //结合容器和迭代器平方数组a
OutPutCont("结合容器和迭代器平方", cout, vb.begin(), vb.end());
transfrom(a, a+N, b, CubT); //通过迭代器立方数组a
OutPutCont("通过迭代器立方数组 a", cout, b, b + N);
transfrom(a, a+N, vb.begin(), CubT); //结合容器和迭代器立方数组a
OutPutCont("结合容器和迭代器立方", cout, vb.begin(), vb.end());
return 0;
}
测试结果:
上述的transform算法,顺序遍历begInput和endInput两个迭代器所指向的元素;将每个元素的值作为函数对象op的参数;将op的返回值通过迭代器begOutput顺序输出;遍历完成后begOutput迭代器指向的是输出的最后一个元素的下一个位置,transform会将该迭代器返回,并存储在vector 容器中。
// 像素变换 ———— 二值化
template
class MyThreshold{
public:
MyThreshold(int n = 128):_nThreshold(n){} //参数时默认为128,列表初始化成员变量
int operator()(T val)
{
return val<_nThreshold ? 0 : 1; //_nThreshold设定阈值为n,val小于阈值返回0,否则返回1
}
int _nThreshold;
};
// 像素变换 ———— 灰度拉伸
template
class MyGrayTrans{
public:
MyGrayTrans(int n = 128):nGrayTrans(n){}
int operator()(T val){
return val += nGrayTrans;
}
int nGrayTrans;
};
代码测试:
int main()
const int N = 5;
int a[N] = {1, 0, 5, 2, 4}; //整型数组
vector vb(N); //vector(向量)容器
OutPutCont(" 数组 a ", cout, a, a+N); //输出数组a
transfrom(a, a+N, vb.begin(), MyThreshold(2)); //像素变换——二值化
OutPutCont(" 二值化 ", cout, vb.begin(), vb.end());
transfrom(a, a+N, vb.begin(), MyGrayTrans(2)); //像素变换——灰度拉伸
OutPutCont("灰度拉伸", cout, vb.begin(), vb.end());
return 0;
}
测试结果:
把大于某个临界灰度值的像素灰度设为灰度極大值,把小于这个值的像素灰度设为灰度極小值,从而实现二值化,测试结果把阈值设为2,小于2的值变为0,大于等于2的值变为1。
如下示例sort排序算法:对给定区间所有元素进行排序,默认为升序,也可进行降序排序。sort函数进行排序的时间复杂度为n*log2n,比冒泡之类的排序算法效率要高,sort函数包含在头文件为#include
语法
sort(start,end,cmp)
参数
(1)start表示要排序数组的起始地址;
(2)end表示数组结束地址的下一位;
(3)cmp用于规定排序的方法,可不填,默认升序。既可以是函数也可以是函数对象,但是必须带有具有双目运算符的比较函数即操作符(),且返回类型为bool
功能
sort函数用于C++中,对给定区间所有元素进行排序,默认为升序,也可进行降序排序。
一般是直接对数组进行排序,例如对数组a[10]排序,sort(a,a+10)。而sort函数的强大之处在可与cmp函数结合使用,即排序方法的选择。
库函数functional中定义了类greater,函数对象类greater中的定义了调用操作符(),可用作于cmp。
// 比较函数 ———— 降序
template
bool MyComp(T a, T b){
return a > b;
}
// 比较类
template
class CMyComp{
public:
bool operator()(const T& x, const T& y) const{
return x > y;
}
};
测试代码:
int main(){
const int N = 5;
int a[N] = {1, 0, 5, 2, 4}; //整型数组a
int b[N] = {1, 2, 3, 7, 0}; //整型数组b
int c[N] = {0, 1, 5, 4, 0}; //整型数组c
OutPutCont("数组 a", cout, a, a+N); //输出数组a
sort(a, a+N, MyComp); //调用模板函数排序
OutPutCont("排序后", cout, a, a+N); //输出排序后的数组a
OutPutCont("数组 b", cout, b, b+N); //输出数组b
sort(b, b + N, CMyComp()); //调用自定义模板类排序
OutPutCont("排序后", cout, b, b+N); //输出数组b
OutPutCont("数组 c", cout, c, c+N); //输出数组b
sort(c, c+N, greater()); //调用functional库中模板类排序
OutPutCont("排序后", cout, c, c+N); //输出数组b
return 0;
}
测试结果:
set集合是c++ stl库中自带的一个容器,set具有以下两个特点:
常用操作:
begin() 返回set容器的第一个元素的地址
end() 返回set容器的最后一个元素地址
clear() 删除set容器中的所有的元素
empty() 判断set容器是否为空
max_size() 返回set容器可能包含的元素最大个数
size() 返回当前set容器中的元素个数
erase(it) 删除迭代器指针it处元素
insert() 插入某个元素
// 学生信息类
class StudentInfo{
public:
string c_strNo; //学号
string c_strName; //姓名
StudentInfo(string strNo, string strName){ //构造函数
c_strNo = strNo;
c_strName = strName;
}
// 友元函数,运算符重载————输出
friend ostream& operator<<(ostream& os, const StudentInfo& info){
os << info.c_strNo << info.c_strName;
return os;
}
// 友元函数,运算符重载————比较
friend bool operator<(const StudentInfo& info1, const StudentInfo& info2){
return info1.c_strNo
创建类型为vector的容器students,通过push_back的方法把每一个学生信息StudentInfo加入到vector容器中,再遍历vector容器将全部的学生信息存储在set容器中,这样学生信息有序排列。
// 存储学生信息
void saveStudentInfo(){
vector students; //创建vector容器,存储学生信息
students.push_back(StudentInfo("10011", "Pei Ting")); //添加学生信息
students.push_back(StudentInfo("10001", "Wang Yan"));
students.push_back(StudentInfo("10051", "Zhen Hao"));
students.push_back(StudentInfo("10037", "xin Yun"));
students.push_back(StudentInfo("10025", "Hua Li"));
// 创建set容器,存储学生信息
set StudentSet(students.begin(), students.end());
OutPutCont("Student Set", cout, StudentSet.begin(), StudentSet.end());
}
测试结果:学生信息已经排序输出了
3.1 增添学生信息
//增添学生信息————插入后仍是有序排列的
StudentSet.insert(StudentInfo("10032", "Coco Bela"));
3.2 删除学生信息
//删除学生信息
StudentSet.erase(StudentInfo("10051", "Zhen Hao"));
3.3 修改学生信息
begin() | 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
end() | 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
set类型的迭代器有const修饰符,所以不能直接修改元素,只能先删除旧的,创建新的来实现
//修改学生姓名
bool updateStuName(set& StudentSet, string strNameOld, string strNameNew){
string strNo; //用于保存,需要修改姓名的学生的学号
bool result = false; //返回是修改成功
for(auto it = StudentSet.begin(); it != StudentSet.end(); it++){
if((*it).c_strName == strNameOld)
{
strNo = (*it).c_strNo; //保存学号
StudentSet.erase(*it); //先删除旧的学生信息
StudentSet.insert(StudentInfo(strNo,strNameNew));//再重新添加新的学生信息
result = true;
break;
}
}
return result;
}
测试代码:
updateStuName(StudentSet, "Xin Yun", "Xin Yong"); //修改学生信息
OutPutCont("Student Set:After update", cout, StudentSet.begin(), StudentSet.end());
3.4 查找学生信息
① set自带的find函数
find(val) | 在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
// 查找学生信息
if(StudentSet.find(StudentInfo("10001", "Wang Yan")) != StudentSet.end()){
cout << "Find The Student! "<< endl;
}else{
cout << "Not Find The Student! "<< endl;
}
② 自定义查找
// 按学号查找学生系信息
template
void searStuNo(string strNo, ostream &os, T begin, T end)
{
for(; begin !=end; begin++)
{
if((*begin).c_strNo == strNo)
{
os<<"Find The Student: "<< *begin << endl;
}
}
}
输入一个字符串,用map字典统计每个字符出现的次数并输出字符及对应的次数。
map是STL的一个关联容器,它提供一对一的hash。
- 第一个可以称为关键字(key),每个关键字只能在map中出现一次;
- 第二个可能称为该关键字的值(value);
映射与集合同属于单重关联容器,它们的主要区别在于,集合的元素类型是键本身,而映射的元素类型是由键和附加数据所构成的二元组。 在集合中按照键查找一个元素时,一般只是用来确定这个元素是否存在,而在映射中按照键查找一个元素时,除了能确定它的存在性外,还可以得到相应的附加数据。
// 统计字符串的字符频数
void strCounts(){
map strMap; //创建字典,关键字为char类型,存储字符,键值为int类型,存储字符出现的次数
string str;
cout << "请输入字符串:";
cin >> str;
int len = str.length();
for(int i = 0; i < len; i++){
strMap[str[i]]++;
}
// 遍历输出
for(map::iterator it = strMap.begin(); it != strMap.end(); it++){
cout << "字符:" <first << "\t" << "频数:" << it->second << endl;
}
}
本次实验,学习了解了STL——C++的一个标准模板库,实验过程中可以明显体会到STL 具有高可重用性。常用到的STL容器有vector、list、deque、map、set等等,它们有各自特点,如下:
在实际使用过程中,选择哪容器,可根据以下选择: