STL应用
一、撰写自己的算法和函数,结合容器和迭代器解决序列变换(如取反、平方、立方),像素变换(二值化、灰度拉伸)
1. 一般的函数方法
首先是简单的取反和求平方的实现。
//取反(简单的方法)
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 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 };
//输出a
outputCont("a", cout, a, a + N);
//存储变换后的数组
int b[N];
//对数组中的元素取反
transInv(a, b, N);
outputCont("Inv a", cout, b, b + N);
//对数组中的元素取求平方
transSqr(a, b, N);
outputCont("Sqr a", cout, b, b + N);
}
运行结果如下,输出了三个结果,分别是数组a,对数组a取反存储在数组b输出,对数组a求平方存储在数组b输出。
2. 模板函数
上述方法显然只能针对int类型,所以我们可以运用模板函数,针对不同 的数据类型,代码如下(只举例取反的模板函数,求平方等等同理):
//对数组取反的函数模板(开始使用模板)
template<typename T>
void transInvT(T a[], T b[], int nNum)
{
for (int i = 0; i < nNum; i++)
{
b[i] = -a[i];
}
}
接下来测试
//对数组元素取反(使用模板)
transInvT(a, b, N);
outputCont("Inv a T", cout, b, b + N);
运行结果如下:
3. 结合容器和迭代器
上图是STL的基本组件及他们之间的联系。
3.1. 容器
什么是容器
- 在C++中,容器被定义为:在数据存储上,有一种对象类型,它可以持有其他对象或指向其他对象的指针,这种对象类型就叫做容器。简单理解,即容器就是保存其他对象的对象。而且,这种“对象”还有处理“其他对象”的方法。
- 容器是随着面向对象语言的诞生而提出的,它甚至被认为是早期面向对象语言的基础。现在几乎所有面向对象语言中都伴随着一个容器,C++中则是标准模版库(STL)。
- C++采用基于模版的方式处理容器,STL中的容器提供了多种数据结构。
容器的优点
- 容器类是一种对特定代码重用问题的良好的解决方案。
- 可以自行扩展。当不知道需要存储多少对象时,就不知道应当开辟多大内存空间,而容器不需要预先设定空间长度,只需要创建一个对象并合理调用其提供的方法,其余的细节则由它自身完成,它自己申请内存或释放内存,并使用最优算法执行所有命令。
- 容器类自动申请和释放内存,因此无需进行new和delete操作。
通用容器的分配
3.2. 迭代器
我们使用容器的时候,迭代器是一个不可分割的部分。迭代器在STL中用来将算法和容器联系起来,起着一种胶着剂的作用。迭代器是一种检查容器内元素并遍历元素的数据类型。迭代器是一种行为类似指针的对象,它提供类似指针的功能,对容器成员的内容进行访问。
迭代器分类
3.3. 结合容器和迭代器解决序列变换
transInvT算法
template <typename inputIter, typename outputIter, typename MyOperator>
void transInvT(inputIter begInput, inputIter endInput,
outputIter begOutPut, MyOperator op)
{
for (; begInput != endInput; begInput++, begOutPut++)
{
//*begOutPut = ‐ (*begInput);
//改为函数的形式,就不需要对取反、平方等每一个算法重写一个函数
*begOutPut = op(*begInput);
}
}
测试函数
void Test()
{
//TestMap();
//TestSet();
//TestVector();
const int N = 5;
int a[N] = { 1,2,4,3,5 };
//输出a
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("Inv a", cout, b, b + N);
//对数组中的元素取求平方
transSqr(a, b, N);
outputCont("Sqr a", cout, b, b + N);
//对数组元素取反(使用模板)
transInvT(a, b, N);
outputCont("Inv a T", cout, b, b + N);
transInvT(a, a + N, b);
transInvT(a, a + N, vb.begin());
transInvT(a, a + N, b, InvT<int>);
transInvT(a, a + N, vb.begin(), InvT<int>);
outputCont("Inv a by iter", cout, vb.begin(), vb.end());
}
运行结果
分析
transInvT算法顺序遍历begInput和endInput两个迭代器所指向的元素,将每个元素的值作为函数对象op的参数,然后将op的返回值通过迭代器begOutPut顺序输出,遍历完成后begOutPut迭代器指向的是输出的最后一个元素的下一个位置,即通过迭代器将数组a中的值经过函数对象op之后,把返回值通过迭代器顺序输出存入容器vector vb(N),最后通过outputCont输出vb。
很显然这样的模式,代码的复用率极高。
3.4. 结合容器和迭代器解决像素变换
template<typename T>
class MyThreshold {
public:
//带参构造函数,后面的则是初始化,这样的初始化方式效率比较高
MyThreshold(int n = 128) : _nThreshold(n)
{
}
int operator()(T val)
{
return val < _nThreshold ? 0 : 1;
}
int _nThreshold;
};
测试函数
transInvT(a, a + N, vb.begin(), MyThreshold<int>(2));
outputCont("Inv a by treshold", cout, vb.begin(), vb.end());
运行结果
分析
MyThreshold(2)设定阈值为2,则1会变为0大于等于2的都会输出1。
3.5. 算法和函数之间的关系
我们 先来看看代码及测试结果:
mycomp排序方法
template < typename T>
bool mycomp(T a, T b)
{
return a > b;
}
void Test()
{
const int N = 5;
int a[N] = { 1,2,4,3,5 };
//输出a
outputCont("a", cout, a, a + N);
//模板函数
sort(a, a + N, mycomp<int>);
outputCont("a sorted", cout, a, a + N);
}
库函数functional中定义了类greater,greater():函数对象类greater中的定义了调用操作符()
void Test()
{
const int N = 5;
int a[N] = { 1,2,4,3,5 };
//输出a
outputCont("a", cout, a, a + N);
//模板类,greater不是一个函数而是一个类所以要加括号
sort(a, a + N, greater<int>());
outputCont("a sorted", cout, a, a + N);
}
操作符()的实现中把>改成<结果为升序。
MyCompc():函数对象类MyCompc中的定义了调用操作符()
template < typename T>
class MyCompC
{
public:
bool operator()(const T& x, const T& y) const
{
return x > y;
}
};
void Test()
{
const int N = 5;
int a[N] = { 1,2,4,3,5 };
//输出a
outputCont("a", cout, a, a + N);
sort(a, a + N, MyCompC<int>());
outputCont("a sorted", cout, a, a + N);
}
操作符()的实现中把>改成<结果为升序。
sort(一个排序的算法),该算法中参数可以是两个,也可以是三个,第一个是要排序的数组的起始地址,第二个是结束的地址(最后一位要排序元素的后一位的地址,第三个参数是排序的方法,可以是从大到小也可是从小到大,还可以不写第三个参数,此时默认的排序方法是升序排序。它使用的排序方法是类似于快排的方法,时间复杂度为O( n*log2(n) )。
其中第三个参数既可以是函数也可以是函数的类的对象,但是必须带有具有双目运算符的比较函数即操作符(),且返回类型为bool。
二、用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"));
students.push_back(studentInfo("10002", "Li si"));
students.push_back(studentInfo("10003", "Wang wu"));
students.push_back(studentInfo("10011", "Wang Liu"));
students.push_back(studentInfo("10010", "Wu Liu"));
set<studentInfo> studentSet(students.begin(), students.end());
outputCont("student set", cout, studentSet.begin(), studentSet.end());
}
运行结果如下,由于集合的性质,集合本身的元素是有序的,我们可以看到学生信息已经按升序的方式进行了排序。
增删改查
- 增:insert(a) 插入某个元素,因为set有序,所以怎么插入无所谓
- 删:erase(it) 该函数用于根据元素的值或元素在集合中的迭代器位置来擦除它
- 改:set的迭代器it有const修饰符,那么对它元素的修改就必然不能成功了。
- 查:find用于检查元素是否属于集合,如果元素在集合容器中找到,则返回指向该元素的迭代器。否则返回set.end()
void TestSet()
{
vector<studentInfo> students;
students.push_back(studentInfo("10021", "Zhang san"));
students.push_back(studentInfo("10002", "Li si"));
students.push_back(studentInfo("10003", "Wang wu"));
students.push_back(studentInfo("10011", "Wang Liu"));
students.push_back(studentInfo("10010", "Wu Liu"));
set<studentInfo> studentSet(students.begin(), students.end());
outputCont("student set", cout, studentSet.begin(), studentSet.end());
//增 insert(a) 插入某个元素 set有序,所以怎么插入无所谓
studentSet.insert(studentInfo("10000", "wjr"));
outputCont("student set added", cout, studentSet.begin(), studentSet.end());
//删 erase(it) 该函数用于根据元素的值或元素在集合中的迭代器位置来擦除它
studentSet.erase(studentInfo("10010", "WuLiu"));
outputCont("student set erased", cout, studentSet.begin(), studentSet.end());
//改,set的迭代器it有const修饰符,那么对它元素的修改就必然不能成功了。
//查 find用于检查元素是否属于集合,如果元素在集合容器中找到,则返回指向该元素的迭代器。否则返回set.end()
//set<studentInfo>::iterator it = studentSet.find(studentInfo("10010", "WuLiu"));
if (studentSet.find(studentInfo("10010", "WuLiu")) != studentSet.end())
{
cout << "find it!" << endl;
}
else
{
cout << "not found!" << endl;
}
}
运行结果
三、输入一个字符串,用map统计每个字符出现的次数并输出字符及对应的次数。
map是STL的一个关联容器,它提供一对一的hash。
第一个可以称为关键字(key),每个关键字只能在map中出现一次;
第二个可能称为该关键字的值(value);
创建对象
map<T1,T2> m;
map<T1,T2, op> m; //op为排序规则,默认规则是less
void TestMap()
{
map<char, int> s; //用来存储字母出现次数的映射
char c; //存储输入字符
do {
cin >> c;//输入下一个字符
if (isalpha(c))//判断是否是字母
{
c = tolower(c);//将字母转换为小写
s[c]++;//将该字母的出现频率加1
}
} while (c != '.');//碰到“.”则结束输入
//输出每个字母出现次数
for (map<char, int>::iterator iter = s.begin(); iter != s.end(); ++iter)
cout << iter->first << "" << iter->second << " ";
cout << endl;
}
运行结果如下,输出结果为key+value,即出现的字母即次数。