深入探究C++中的仿函数和迭代器——提升你的STL技能
📖作者介绍:22级树莓人(计算机专业),热爱编程<目前在c++阶段>——目标Windows,MySQL,Qt,数据结构与算法,Linux,多线程,会持续分享学习成果和小项目的
📖作者主页:热爱编程的小K
📖专栏链接:c++🎉欢迎各位→点赞👏 + 收藏💞 + 留言🔔
💬总结:希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🐾
文章目录
- 一、仿函数
- 1、介绍
- 2、为什么要有仿函数?
- 3、核心
- A、仿函数的调用
- B、应用
- C、标准库仿函数
- 4、仿函数优点
- 5、仿函数作用
- 二、迭代器
- 1、分类
- 2、辅助函数
- 3、流型迭代器
一、仿函数
1、介绍
仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类,仿函数是定义了一个含有operator()成员函数的对象,可以视为一个一般的函数,只不过这个函数功能是在一个类中的运算符operator()中实现,是一个函数对象,它将函数作为参数传递的方式来使用。
写一个简单类,除了维护类的基本成员函数外,只需要重载 operator() 运算符 。这样既可以免去对一些公共变量的维护,也可以使重复使用的代码独立出来,以便下次复用。
STL 中也大量涉及到仿函数,有时仿函数的使用是为了函数拥有类的性质,以达到安全传递函数指针、依据函数生成对象、甚至是让函数之间有继承关系、对函数进行运算和操作的效果。比如 STL 中的容器 set 就使用了仿函数 less ,而 less 继承的 binary_function,就可以看作是对于一类函数的总体声明,这是函数做不到的。
2、为什么要有仿函数?
1,假如客户有一个需求摆在我们的面前,编写一个函数:函数可以获得斐波拉契数列每项的值;每调用一次便返回一个值;函数可根据需要重复使用。我们之前在 C 语言中也讲过斐波拉契数列,相信这个很好实现了。那么我们就编写的程序如下
int fibonacci()
{
static int a0 = 0; //第一项
static int a1 = 1; //第二项
int ret = a1; //保存
a1 = a0 + a1;
a0 = ret;
return ret;
}
int main()
{
for (size_t i = 0; i < 5; i++)
{
cout << fibonacci() << " "; //1 1 2 3 5
}
cout << endl;
for (size_t i = 0; i < 5; i++)
{
cout << fibonacci() << " "; //8 13 21 34 55
}
return 0;
}
我们就开心的完成任务了,于是交给客户了。过两天,客户又给打回来了。说是存在几个问题:函数一但调用就无法重来,静态局部变量处于函数内部,外界无法改变。函数为全局函数,是唯一的,无法多次独立使用。无法指定某个具体的数列项作为初始值。于是我们想着将静态局部变量改为去全局变量,再次重新调用时,便将全局变量重新初始化,重新如下
int a0 = 0; //第一项
int a1 = 1; //第二项
int fibonacci()
{
int ret = a1;
a1 = a0 + a1;
a0 = ret;
return ret;
}
int main()
{
for (size_t i = 0; i < 5; i++)
{
cout << fibonacci() << " "; //1 1 2 3 5 8
}
cout << endl;
a0 = 0;
a1 = 1;
for (size_t i = 0; i < 5; i++)
{
cout << fibonacci() << " "; //1 1 2 3 5 8
}
return 0;
}
是满足这个需求了,但是要在使用时需要重新初始化全局变量,客户肯定不干啊。所以这个解决方案不可行。于是乎,我们在 C++ 中一个吊炸天的技术来了:函数对象。
先来说说函数对象:
- 使用具体的类对象取代函数;
- 该类的对象具备函数调用的行为;
- 构造函数指定具体数列项的起始位置;
- 多个对象相互独立的求解数列项。
同样函数对象也是通过函数调用操作符(),便是重载操作符了。它只能通过类的成员函数重载,可以定义不同参数的多个重载函数。
下来我们来看看最终的解决方案
class Fibonacci
{
public:
Fibonacci() :_a0(0), _a1(1) {}
Fibonacci(int n) :_a0(0), _a1(1)
{
for (int i = 0; i < n; i++)
{
int ret = _a1;
_a1 = _a0 + _a1;
_a0 = ret;
}
}
int operator()()
{
int ret = _a1;
_a1 = _a0 + _a1;
_a0 = ret;
return ret;
}
private:
int _a0;
int _a1;
};
int main()
{
Fibonacci fib;
for (size_t i = 0; i < 5; i++)
{
cout << fib() << " "; //1 1 2 3 5 8
}
cout << endl;
Fibonacci fib1(9);
for (size_t i = 0; i < 5; i++)
{
cout << fib1() << " "; //55 89 144 233 377
}
return 0;
}
我们看到已经实现了所有需求,并且随时想从哪个数开始都行。
2,比如,有一个简单需求:统计一个vector<int>
中,元素等于3的数量。解决方法可能会是:
int equal_count(const vector<int>::iterator& first,const vector<int>::iterator& last,const int& val)
{
int size = 0;
for (auto it = first; it != last; it++)
{
if (*it == val)
{
size++;
}
}
return size;
}
int main()
{
vector<int> v = { 1,2,3,4,5,6,7,8,1,3,1,4,3,2,1 };
//统计v中元素等于3的元素个数
size_t size = equal_count(v.begin(), v.end(), 3);
cout << size << endl; //output:3
return 0;
}
其实,统计容器中某个元素的数量,C++中有一个函数count
size_t count(const Iter First, const Iter Last, const Ty& Val);
对于上面的统计元素个数没有拓展性。比如:统计v中元素大于于3的元素个数呢?为此我们必须再设计一个greater_count函数:
int great_count(const vector<int>::iterator& first, const vector<int>::iterator& last, const int& val)
{
int size = 0;
for (auto it = first; it != last; it++)
{
if (*it > val)
{
size++;
}
}
return size;
}
这样写就很麻烦,我只需要改变一下规则,就需要多一个函数,咱们可以把里面的比较规则,写成一个函数(可调用的对象),通过传参实现比较。
//using FunType = bool (*)(int, int);
template<typename FunType>
int count_if(const vector<int>::iterator& first, const vector<int>::iterator& last,FunType cmp,const vector<int>::value_type& val)
{
int size = 0;
for (auto it = first; it != last; it++)
{
if (cmp(*it,val))
{
size++;
}
}
return size;
}
bool equal(int a, int b)
{
return a == b;
}
bool great(int a, int b)
{
return a > b;
}
int main()
{
vector<int> v = { 1,2,3,4,5,6,7,8,1,3,1,4,3,2,1 };
//统计v中元素等于3的元素个数
size_t size;
size = count_if(v.begin(), v.end(), equal,3);
cout << size << endl;
//统计v中元素大于3的元素个数
size = count_if(v.begin(), v.end(), great,3);
cout << size << endl;
return 0;
}
这样是不是就轻松很多了,但是这里的统计元素3,我们要通过count_if传到比较函数里面去,非常的丑陋对不对。有一种写法,可以不通过参数传进去。
首先,删掉count_if中的最后一个参数val。
然后,把equal和great稍加修改一下。
template<typename FunType>
int count_if(const vector<int>::iterator& first, const vector<int>::iterator& last, FunType cmp)
{
int size = 0;
for (auto it = first; it != last; it++)
{
if (cmp(*it))
{
size++;
}
}
return size;
}
bool equal(int a)
{
return a == 3;
}
bool great(int a)
{
return a > 3;
}
int main()
{
vector<int> v = { 1,2,3,4,5,6,7,8,1,3,1,4,3,2,1 };
//统计v中元素等于3的元素个数
size_t size;
size = count_if(v.begin(), v.end(), equal);
cout << size << endl;
//统计v中元素大于3的元素个数
size = count_if(v.begin(), v.end(), great);
cout << size << endl;
return 0;
}
或者使用lambda表达式
vector<int> v = { 1,2,3,4,5,6,7,8,1,3,1,4,3,2,1 };
//统计v中元素等于3的元素个数
size_t size;
//size = equal_count(v.begin(), v.end(), 3);
size = count_if(v.begin(), v.end(), [](auto val)
{
return val == 3;
});
cout << size << endl;
//统计v中元素大于3的元素个数
size = count_if(v.begin(), v.end(), [](auto val)
{
return val > 3;
});
cout << size << endl;
其实lamda表达式出现之后(C++11),仿函数(C++98)的作用已经被削弱了,使用lamda会让我们使用STL方便许多。
对于count_if 里有和我们写的一模一样的函数,以后直接使用即可
那么使用仿函数,怎么实现上述功能呢?
struct Equal
{
bool operator()(int val)
{
return val == 3;
}
};
struct Great
{
bool operator()(int val)
{
return val > 3;
}
};
int count_if(const vector<int>::iterator& first, const vector<int>::iterator& last, FunType cmp);
int main()
{
vector<int> v = { 1,2,3,4,5,6,7,8,1,3,1,4,3,2,1 };
//统计v中元素等于3的元素个数
size_t size;
size = count_if(v.begin(), v.end(), Equal());
cout << size << endl;
//统计v中元素大于3的元素个数
size = count_if(v.begin(), v.end(), Great());
cout << size << endl;
return 0;
}
可以继续升级~
struct Equal
{
Equal(int usrVal) :_usrVal(usrVal) {}
bool operator()(int val)
{
return val == _usrVal;
}
int _usrVal;
};
struct Great
{
Great(int usrVal) :_usrVal(usrVal) {}
bool operator()(int val)
{
return val > _usrVal;
}
int _usrVal;
};
int count_if(const vector<int>::iterator& first, const vector<int>::iterator& last, FunType cmp);
int main()
{
vector<int> v = { 1,2,3,4,5,6,7,8,1,3,1,4,3,2,1 };
//统计v中元素等于3的元素个数
size_t size;
size = count_if(v.begin(), v.end(), Equal(2));
cout << size << endl;
//统计v中元素大于3的元素个数
size = count_if(v.begin(), v.end(), Great(5));
cout << size << endl;
return 0;
}
3、核心
仿函数是让类名模仿函数调用的行为---->函数名(参数) ,让类名能够 : 类名(参数) 方式使用
自己写仿函数关键点在于重载()运算符,所谓的模仿函数的行为,本质先构造一个无名对象,然后通过对象隐式调用重载函数
- 自己写仿函数
- 标准库中的仿函数(不需要记,自己会写了,自己创造)
仿函数一般有两个作用:
- 充当比较准则
- 充当算法或者容器构建的参数
A、仿函数的调用
必须加上{},要不然解析不了,分不清楚是构造函数还是啥
sum<int>{}(2, 3)
template <class _Ty> class sum
{
public:
int operator()(const _Ty& one, const _Ty& two) const
{
return one + two;
}
};
void testOne()
{
//调用
sum<int> k;
cout << k.operator()(2, 3) << endl;
cout << k(2, 3) << endl;
//必须加上{},要不然解析不了,分不清楚是构造函数还是啥
cout << sum<int>{}(2, 3) << endl;
}
B、应用
冒泡排序函数准则的写入,使用结构体和类分别进行书写,方便理解
template <class _Ty, class _Pr> void bubble_Sort(_Ty array[], int size)
{
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size - 1 - i; j++)
{
if (_Pr{}(array[j], array[j + 1]))
{
_Ty temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
template <class _Ty> class my_Less
{
public:
bool operator()(const _Ty& one, const _Ty& two) const
{
return one < two;
}
};
template <typename _Ty> struct my_Greater
{
bool operator()(const _Ty& one, const _Ty& two) const
{
return one > two;
}
};
void testTwo()
{
int a[] = { 1,2,9,8,0,2,6,89,98,102,23 };
bubble_Sort<int, my_Greater<int>>(a, 11);
for (auto v : a) {
cout << v << " ";
}
cout << endl;
bubble_Sort<int, my_Less<int>>(a, 11);
for (auto v : a) {
cout << v << " ";
}
cout << endl;
}
C、标准库仿函数
我的评价是不用记,用到的时候自己写一个或者查一下,标准库的太多了,还比较难记
void test3()
{
//算术类
cout << plus<int>{}(2, 3) << endl;
cout << minus<int>{}(5, 2) << endl;
cout << multiplies<int>{}(3, 2) << endl;
//关系类
cout << less<int>{}(4, 3) << endl;
//逻辑类
cout << logical_and<int>{}(3, 2) << endl;
//位运算
cout << bit_and<int>{}(2, 1) << endl;
}
4、仿函数优点
如果可以用仿函数实现,那么你应该用仿函数,而不要用CallBack(CallBack技术是一种编程技术,它允许将一个函数作为参数传递给另一个函数,以便在需要时执行该函数。这种技术通常用于事件处理程序和异步编程中。)。原因在于:
-
仿函数可以不带痕迹地传递上下文参数。而CallBack技术通常使用一个额外的void*参数传递。这也是多数人认为CallBack技术丑陋的原因。
-
仿函数技术可以获得更好的性能,这点直观来讲比较难以理解。
5、仿函数作用
仿函数通常有下面四个作用:
- 作为排序规则,在一些特殊情况下排序是不能直接使用运算符<或者>时,可以使用仿函数。
- 作为判别式使用,即返回值为bool类型。
- 同时拥有多种内部状态,比如返回一个值得同时并累加。
- 作为算法for_each的返回值使用。
二、迭代器
1、分类
迭代器是一个类中类,让类中类对象模仿指针的行为,迭代器通常是用来访问容器的
分类 | 迭代器类型 | 开始位置 | 结束位置 |
---|---|---|---|
正向迭代器 | 容器名::iterator iter; | begin() | end() |
反向迭代器 | 容器名::reverse_iterator iter; | rbegin() | rend() |
常正向迭代器 | 容器名::const_iterator iter; | cbegin() | cend() |
常反向迭代器 | 容器名::const_reverse_iterator iter; | crbegin() | crend() |
按照功能上来说分为三类:
- 正向迭代器
- 双向迭代器:list set map
- 随机访问迭代器: array,vector,deque
注意点: stack与queue以及priority_queue 不支持迭代器访问
void testOne()
{
vector<int> test = { 1,5,8,9,75,88 };
for (vector<int>::reverse_iterator iter = test.rbegin(); iter != test.rend(); iter++)
{
cout << *iter << " ";
}
cout << endl;
for (vector<int>::const_reverse_iterator iter = test.crbegin(); iter != test.crend(); iter++)
{
cout << *iter << " ";
}
cout << endl;
}
2、辅助函数
- advance(iter,n):移动
- distance(beginPos ,endPos): 元素个数
- iter_swap(first,second):交换
void testTwo()
{
list<int> data = { 1,2,3,4,5,6 };
list<int>::iterator iter = data.begin();
//cout << *(iter + 2) << endl; //错误,链表中没有这个操作
advance(iter, 2);
cout << *iter << endl;
cout << distance(data.begin(), data.end()) << endl;
}
3、流型迭代器
输出流型迭代器
- ostream_iterator object(ostream& out);
- ostream_iterator object(ostream& out,const char* str);
- object=value : 等效 cout<<value
输入流型迭代器
- istream_iterator object; //End-of-stream
- istream_iterator object(istream& in);
- *object 等效 cin操作
void test3()
{
ostream_iterator<int> object(cout);
object = 1234;
cout << endl;
vector<int> king = { 1,2,3,4,5,6,7,8 };
copy(king.begin(), king.end(), ostream_iterator<int>(cout, " "));
cout <<endl<< "输入型流迭代器:" << endl;
vector<int> tql;
istream_iterator<int> end;
istream_iterator<int> test(cin);
while(test != end)
{
tql.push_back(*test);
test++;
}
for (auto v : tql) {
cout << v << " ";
}
}