[C++]函数对象(一)
2016-04-18 16:42
501 查看
函数对象
定义
重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象。class FuncOdject { public: void operator()() { cout << "hello c++!" << endl; } } // 实例化一个对象 FuncOdject val; val() // cout << "hello c++!" << endl; // 这有点像使用函数 /* void val() { cout<<"Hello C++!"<<endl; } */
使用函数对象的原因
虽然调用函数和使用函数对象有相同的效果,但函数对象有独特的优势,主要体现在STL的使用。1 . 函数对象可以有自己的状态。我们可以在类中定义状态变量,这样一个函数对象在多次的调用中可以共享这个状态。但是函数调用没这种优势,除非它使用全局变量来保存状态。
2 . 函数对象有自己特有的类型,而普通函数无类型可言。这种特性对于使用C++标准库来说是至关重要的。这样我们在使用STL中的函数时,可以传递相应的类型作为参数来实例化相应的模板,从而实现我们自己定义的规则。比如自定义容器的排序规则。
实例
1. 使用到内部状态的函数对象
// 定义一个函数对象 class SuccessiveNumGen { public: SuccessiveNumGen(int origin = 0):m_origin(origin){} int operator() () { return m_origin++; } private: int m_origin; }; vector<int> dest; generate_n(back_inserter(dest),10,SuccessiveNumGen(3));
此处使用generate_n来表示函数自动调用n次。
back_inserter是一个函数适配器,它返回一个函数对象,并且每次都在dest最后存放元素。
这样,最终容器中的内容为 3,4,5,6,……,12。通过使用不同的起始数字来初始化不同的函数对象,可以生成不同的数字序列。
2. 自定义容器set对字符串string的排序规则
class StringSort { public: bool operator() (const string &str1, const string &str2) const { return str1 > str2; } }; int main() { set<string,StringSort> myset; myset.insert("A"); myset.insert("B"); cout << *myset.begin() << endl; return 0; }
3. 谓词函数(predicate)
class NoLess { public: NoLess(int min = 0):m_min(min){} bool operator() (int value) const { return value >= m_min; } private: int m_min; }; int main() { vector<int> myvec; myvec.push_back(1); myvec.push_back(2); myvec.push_back(10); remove_if(myvec.begin(), myvec.end(), NoLess(3)); return 0; }
find_if,remove_if都需要一个谓词函数来实现。而这种函数最好用函数对象来实现。(如果尝试调用函数来实现,会出现错误。而且并不好用,因为每次都要重新改写函数的代码来改变判断的条件。)
意外情况:
有一点需要指出的是,在调用用到函数对象的标准库算法时,除非显式地指定模板类型为“传引用”,否则默认情况下函数对象是“按值传递”的!因此,如果传递的是一个具有内部状态的函数对象,则被改变状态的是函数内部被复制的临时对象,函数结束后随之消失。真正传进来的函数对象状态并为改变。测试如下:
class Nth { public: Nth(int n=0):m_nth(n),m_count(1){} bool operator() (int) { return m_count++ == m_nth; } int GetCount()const { return m_count; } private: int m_nth; int m_count; }; Nth nth(3); vector<int>::iterator nthItr = find_if(dest.begin(),dest.end(),nth); //dest内容为连续数字:3,4,5,6,……,12 cout<<"3rd:"<<*nthItr<<endl; cout<<"State:"<<nth.GetCount()<<endl; // 输出结果为,确实能找到第三个数字(5)。 // 但查看nth的状态时,返回的m_count依然为0。说明nth确实未被修改。
一般情况下,至于是传值还是传引用,不会对我们造成太多影响。
但还是有必要清楚这一点,困为有时候针对特定的实现会出现一些莫名其妙的问题,以下面的例子来说明:
vector<int> vec; for(vector<int>::size_type i=0; i<10; ++i) vec.push_back(i+1); Nth nth(3); remove_if(vec.begin(),vec.end(),nth); for(vector<int>::size_type i=0; i<10; ++i) { cout<<vec[i]<<endl; }
按正常思路来讲,删除第3个元素后输出应该是:1,2,4,5,6,7,8,9,10,10(至于最后为什么会出现两次10,这跟标准算法库设计思想有关:算法库绝不改变容器的大小!所谓的remove只是概念上的remove,被删的元素被丢到了后面,可以通过返回值来确定位置。标准算法库以后会进行说明,不是这部分的重点)。但实际情况却令人吃惊:1,2,4,5,7,8,9,10,9,10。
造成这种情况可能是因为特定的STL实现:在《The C++ Standard Library, A tutorial and reference》这本书上,作者给出一种造成这种情况的可能的实现:
template <class ForwIter, class Predicate> ForwIter remove_if(ForwIter beg, ForwIter end, Predicate op) { beg = std::find_if(beg, end, op); if (beg == end) { return beg; } else { ForwIter next = beg; return remove_copy_if(++next, end, beg, op); } }
其中remove_copy_if:
(简单的说,就是把一个class里面不满足谓词函数的元素放进另一个class里面。)
// remove_copy_if template <class _InputIterator, class _OutputIterator, class _Predicate> inline _LIBCPP_INLINE_VISIBILITY _OutputIterator remove_copy_if(_InputIterator __first, _InputIterator __last, _OutputIterator __result, _Predicate __pred) { for (; __first != __last; ++__first) { if (!__pred(*__first)) { *__result = *__first; ++__result; } } return __result; }
在这个实现中,我们清晰地看出,函数对象op两次作为参数传递到find_if和remove_copy_if函数中,正是由于标准库默认的”按值传递”,导致两次传递进来的op都是最初始的状态,即m_count为0!这样一来,传递到remove_if函数中的op将从+next位置(next是第一次找到的将被删除的第三个位置)算起再次删除第三个元素,于是第六个元素也就被删除了。
可见,不是所有的返回布尔值的函数对象都适合作为谓词函数,比如这个例子,不同标准库具体的实现会造成不同的结果。因此,作为一个好的编程习惯,用作谓词函数的函数对象,其判断结果最好不要依赖内部变动的状态。
相关文章推荐
- 编写高质量的C++代码--01 减少文件之间的编译依赖
- C++多重继承带来的问题
- C/C++内存泄漏检测
- 【c/c++】list
- c++ 字符串连接问题
- Effective C++笔记(三):资源管理
- terminate called after throwing an instance of 'std::bad_alloc
- 基于C语言实现简单的走迷宫游戏
- 输入流对象cin读取输入流的三种方式
- C++虚析构函数
- C++ STL中vector的内存机制和性能分析
- C语言结构体的字节对齐原则
- C++时间,文件及字符常用函数总结
- c++实现mysql数据库数据缓存
- 贪吃蛇C语言代码
- 【C++】日期类+日期万年历+日期计算器
- 【Visual C++】游戏开发笔记四十一 浅墨DirectX教程之九 为三维世界添彩:纹理映射技术(一)
- 介绍一个类型安全的回调库:libsigc++
- C语言中的可变参数列表
- C语言获取汉字拼音首字母