【C++】STL应用(详解)

(一)、泛型程序与STL

1.泛型程序设计的基本概念

  首先先了解一下什么是泛型程序。所谓的泛型程序设计就是编写不依赖与具体数据类型的数据。C++中,模板是泛型程序设计的主要工具。泛型程序设计的主要思想是将算法从特定的数据结构中抽象出来,使算法成为通用的、可以作用于各种不同的数据结构。

2.STL简介

  标准模板库(Standard Template Library,STL)是面向对象程序设计与泛型程序设计思想相结合的一个良好典范。STL提供了一些常用的数据结构和算法。STL更大的意义在于,它定义了一套概念体系,为泛型程序设计提供了逻辑基础。
  STL所涉及的四种基本组件:容器、迭代器、函数对象、算法。
STL基本组件结构图:
在这里插入图片描述


容器(container):
①容器(container)是容纳、包含一组元素的对象。
②容器库类中包括七种基本容器:向量(vector)、双端队列(deque)、列表(list)、集合(set)、多重集合(multiset)、映射(map)、多重映射(multimap)。
③这七种容器可以分为两种基本类型:顺序容器(sequence container)和关联容器(associative container)。
④顺序容器将一组具有相同类型的元素以严格的线性形式组合起来,向量、双端队列和列表容器就属于这一种。
⑤关联容器具有根据一组索引来快速提取元素的能力,集合和映射容器就属于这一种。
⑥使用不同的容器,需要包含不同的头文件。


迭代器(iterator):
①迭代器(iterator)提供顺序访问容器中每个元素的方法。
②指针本身就是一种迭代器,迭代器是泛化的指针。
③使用独立于STL容器的迭代器,需要包含头文件iterator。
④在STL中,迭代器是算法和容器的“中间人”。


函数对象(function object):
  函数对象(function object)是泛化的函数,是一个行为类似函数的对象,可以像调用函数一样调用函数对象。任何普通的函数和任何重载了“()”运算符的类的对象都可以作为函数对象使用。使用STL的函数对象,需要包含头文件 functional。


算法(algorithm):
  STL包括七十多个算法,包括查找算法、排序算法、消除算法、计数算法、比较算法、变换算法、置换算法、容器管理等。这些算法的一个最重要的特性就是它们的统一性,并且可以广泛用于不同的对象和内置的数据类型。使用STL的算法,需要包含头文件algorithm。


(二)、迭代器

  上面初略的了解了一下什么是迭代器,现在让我们更深入地去理解应用迭代器。理解迭代器对理解STL框架并掌握STL的使用至关重要。
  Iterator(迭代器)模式又称Cursor(游标)模式,用于提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。或者这样说可能更容易理解:Iterator模式是运用于聚合对象的一种模式,通过运用该模式,使得我们可以在不知道对象内部表示的情况下,按照一定顺序(由iterator提供的方法)访问聚合对象中的各个元素。
迭代器的作用:能够让迭代器与算法不干扰的相互发展,最后又能无间隙的粘合起来,重载了*++==!=运算符。用以操作复杂的数据结构,容器提供迭代器,算法使用迭代器;常见的一些迭代器类型:iteratorconst_iteratorreverse_iteratorconst_reverse_iterator

1.输入流迭代器

Istream_iterator<T>(istream& in);
T是使用该迭代器从输入流中输入数据的类型。类型T要满足两个条件:
①有默认构造函数
②对该类型的数据可以使用“>>”从输入流输入。

用“++”运算符可以从输入流中读取下一个元素;若类型T是类类型或结构体,用“->”可以直接访问刚刚读取元素的成员。
Istream_iterator类模板有一个默认构造函数,用该函数构造出的迭代器指向的就是输入流的结束位置,将一个输入流与这个迭代器进行比较就可以判断输入流是否结束。

例如:

copy(istream_iterator<string>(cin),//输入流
istream_iterator<string>(), //输入流结束位置
ostream_iterator<string>(cout,"\n") //输出流及元素分隔符
);

注意:由于该程序会从标准输入流中读取数据直到输入流结束,运行该程序时,输入完数据后,在windows下需要按【ctrl+z】和回车键,在linux下需要按【ctrl+D】键,表示标准输入结束。后面凡是通过一对Istream_iterator读入数据的程序皆如此。

2.输出流迭代器

一个输出流迭代器可以用下面两个构造函数来构造:

ostream_iterator<T>(ostream&out);
ostream_iterator<T>(ostream& out,const char * delimiter);//参数delimiter是可选的,表示两个输出数据之间的分隔符

输出流迭代器也支持“++”运算符,但是该运算符实际上不会使该迭代器的状态发生任何改变,支持“++”运算仅仅是为了让它和其他迭代器有统一的接口。
适配器(adapter)是指用于为已有对象提供新的接口的对象,适配器本身一般并不提供新的功能,只为改变对象的接口为存在。输入流迭代器和输出流迭代器将输入流和输出流的接口变更为迭代器的接口,因此它们属于适配器。

(三)、STL应用

1.撰写自己的算法和函数,结合容器和迭代器解决序列变换(如取反、平方、立方),像素变换(二值化、灰度拉伸)

①序列变换(取反,平方)

代码:

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

//对数组中的元素取反
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];
    }
}
//对数组取反的函数模板
template < typename T>
void transInvT(T a[], T b[], int nNum)
{
    for (int i = 0; i < nNum; i++)
    {
        b[i] = -a[i];
    }
}
template < typename T>
// 返回相反数
T InvT(T a)
{
    return -a;
}
template <typename inputIter, typename outputIter, typename MyOperator>
void transInvT(inputIter begInput, inputIter endInput,
    outputIter begOutPut, MyOperator op) {
    for (; begInput != endInput; begInput++, begOutPut++)
    {
        *begOutPut = op(*begInput);
    }

}
//遍历数组的函数模板
template < typename T>
void outputCont(string strNme, ostream& os, T begin, T end)
{
    os << "数组" + strNme + "的遍历结果为:";
    for (; begin != end; begin++)
    {
        os << *begin << " ";
    }
    os << endl;
}

void Test()
{
    const int N = 5;
    int a[N] = { 1,2,4,3,5 };
    outputCont("a", cout, a, a + N);
    int b[N];
    //Vector(向量)是一个封装了动态大小数组的顺序容器(Sequence Container),
    //跟任意其它类型容器一样,它能够存放各种类型的数据。
    //可以简单的认为,向量是一个能够存放任意类型的动态数组
    vector<double> vb(N);
    vector<double> vc(N);
    //对数组中的元素取反
    transInv(a, b, N);
    outputCont("函数取反", cout, b, b + N);
    //对数组中的元素取求平方
    transSqr(a, b, N);
    outputCont("函数平方", cout, b, b + N);
    //对数组元素取反(使用模板)
    transInvT(a, b, N);
    outputCont("T模板取反", cout, b, b + N);
    //对数组元素迭代取反
    transInvT(a, a + N, vb.begin(), InvT<int>);
    outputCont("iter迭代取反", cout, vb.begin(), vb.end());

}

int main() {
    Test();
}

运行测试截图:
在这里插入图片描述

②像素变换

代码块:

template<typename T>
class MyThreshold {
public:
    //带参构造函数,后面的则是初始化,这样的初始化方式效率比较高
    MyThreshold(int n = 128) : _nThreshold(n)
    {
    }
    int operator()(T val)
    {
        return val < _nThreshold ? 0 : 1;
    }
    int _nThreshold;
};

Test():

    transInvT(a, a + N, vb.begin(), MyThreshold<int>(2));
    outputCont("像素变换", cout, vb.begin(), vb.end());

运行测试截图:
在这里插入图片描述

2.用set存储学生信息,并进行增删改查操作

  构建studentInfo类,其中有一个构造函数包含学号和姓名两个变量,还有两个对运算符的重载,一个是输出,一个是比较学号的大小以便于排序。

class studentInfo {
public:
    studentInfo(string strNo, string strName) {
        _strNo = strNo;
        _strName = strName;
    }
    string _strNo;
    string _strName;
    friend ostream& operator<<(ostream& os, const studentInfo& info)
    {
        os << info._strNo << " " << info._strName;
        return os;
    }
    friend bool operator<(const studentInfo& info1, const studentInfo& info2) {
        return info1._strNo < info2._strNo;

    }

};

  用容器vector创建一个students对象,将学生信息存储在vector当中,再通过遍历vector将其存储在set当中。

void TestSet()
{
    vector<studentInfo> students;
    students.push_back(studentInfo("10021", "Zhang san"));      // 在vector最后添加一个元素
    students.push_back(studentInfo("10002", "Li si"));
    students.push_back(studentInfo("10003", "Wang wu"));
    students.push_back(studentInfo("10011", "zhao liu"));
    students.push_back(studentInfo("10010", "sun qi"));
    set<studentInfo> studentSet(students.begin(), students.end());      // 定义studentSet为students的头部到尾部
    outputCont("student set:", cout, studentSet.begin(), studentSet.end());
 }

学生信息存储情况:
在这里插入图片描述

①对学生信息进行增操作

新增一条学生信息:学号10000,姓名JMU ljp。

    studentSet.insert(studentInfo("10000", "JMU ljp"));	//增加元素
    outputCont("增加后student set", cout, studentSet.begin(), studentSet.end());

运行结果如下:
在这里插入图片描述

②对学生信息进行查操作

查找学号:10002,姓名:Li si的学生。

    set<studentInfo>::iterator iter; 
    set<studentInfo>::iterator iter1;  // 定义迭代下标来判别是否查找成功
    iter = studentSet.find(studentInfo("10002", "Li si"));  
    cout << "查找(10002, Li si):";
    if (iter != studentSet.end())//若找到则输出学生信息
    {
        cout << "find"<<"\t";
        cout << *iter << endl;
    }
    else
    {
        cout << "can not find " << endl;
    }

运行结果如下:
在这里插入图片描述
将查找对象改为1000,Li后运行结果如下:
在这里插入图片描述

③对学生信息进行删操作

把学生Zhang san的信息删除

    studentSet.erase(studentInfo("10021", "Zhang san"));     // 删除
    outputCont("删除后的student set", cout, studentSet.begin(), studentSet.end());

运行结果如下:
在这里插入图片描述

3.输入一个字符串,用map统计每个字符出现的次数并输出字符及对应的次数。

  map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力。

map统计字符串各个英文字母出现的次数并输出:

void TestMap(){
		string str = "linjiapeng";
	int len = str.length();
	map<char, int> _map;
	for (int i = 0; i < len; ++i)
	{
		auto ite1 = _map.find(str[i]);
		if (ite1 != _map.end())
		{
			++((*ite1).second);
		}
		else
		{
			_map.insert(pair<char, int>(str[i], 1));
		}
	}
	map<char, int>::iterator ite = _map.begin();
	for (ite; ite != _map.end(); ++ite)
	{
		cout << (*ite).first << (*ite).second << " ";
	}
}

运行结果:
在这里插入图片描述

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

打代码能当饭吃?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值