1、谓词
什么谓词,其实就是一个判断式,说白了就是一个返回bool值的函数或者仿函数。(这里说明了谓词可以有2种形式)几元就是函数有几个参数。
(1)一元谓词函数举例如下:
- 判断给出的string对象的长度是否小于6
bool GT6(const string &s)
{
return s.size() >= 6;
}
- 判断给出的int是否在3到8之间
bool Compare( int i )
{
return ( i >= 3 && i <= 8 );
}
(2)二元谓词举例如下:
- 比较两个string对象,返回一个bool值,指出第一个string是否比第二个短
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
2、函数指针(参考)
(1)定义
函数指针是指向函数的指针变量。在C编译时,每一个函数都有一个入口地址,那么这个指向这个函数的函数指针便指向这个地址。函数指针主要有两个作用:用作回调函数和做函数的参数。
(2)声明方法
- 数据类型标志符 (指针变量名) (形参列表);
- 一般函数的声明为: int fun( int x );
- 而一个函数指针的声明方法为:int (*fun) (int x);
前面的那个(*fun)中括号是必要的,这会告诉编译器我们声明的是函数指针而不是声明一个具有返回型为指针的函数,后面的形参要视这个函数指针所指向的函数形参而定。 然而这样声明我们有时觉得非常繁琐,于是 typedef 可以派上用场了,我们也可以这样声明:
typedef int (*pf) (int x);
pf p;
这样 p 便是一个函数指针。当要使用函数指针来调用函数时,fun(x)、(*fun)(x) 就可以了,当然,函数指针也可以指向被重载的函数,编译器会为我们区分这些重载的函数从而使函数指针指向正确的函数。
(3)使用实例:
typedef void (*P) ( char ,int );
void bar(char ch, int i) {
cout<<"bar "<<ch<<' '<<i<<endl;
return ;
}
P p;
p = bar; // 或 p = &bar;
(*p)('e',91); // 或 p('e',91);
函数指针另一个作用便是作为函数的参数,我们可以在一个函数的形参列表中传入一个函数指针,然后便可以在这个函数中使用这个函数指针所指向的函数,这样便可以使程序变得更加清晰和简洁,而且这种用途技巧可以帮助我们解决很多棘手的问题,使用很小的代价就可获得足够大的利益(速度+复杂度)。
// 核心内容,非完整程序
typedef void (*P) ( char ,int );
void bar(char ch, int i) {
cout<<"bar "<<ch<<' '<<i<<endl;
return ;
}
void foo(char ch, int i, P p) {
(*p)(ch,i);
return ;
}
int main() {
P p;
p = bar;
foo('e',12,p);
}
3、仿函数(也叫函数对象)
从一般的函数回调意义上来说,函数对象和函数指针是相同的,但是函数对象却具有许多函数指针不具有的有点,函数对象使程序设计更加灵活,而且能够实现函数的内联(inline)调用,使整个程序实现性能加速。此外,在我们写代码时有时会发现有些功能的实现的代码,会不断的在不同的成员函数中用到,但是又不好将这些代码独立出来成为一个类的一个成员函数。但是又很想复用这些代码。写一个公共的函数,可以,这是一个解决方法,不过函数用到的一些变量,就可能成为公共的全局变量,再说为了复用这么一片代码,就要单立出一个函数,也不是很好维护。这时就可以用仿函数了,写一个简单类,除了那些维护一个类的成员函数外,就只是实现一个operator(),在类实例化时,就将要用的,非参数的元素传入类中。这样就免去了对一些公共变量的全局化的维护了。又可以使那些代码独立出来,以便下次复用。而且这些仿函数,还可以用关联,聚合,依赖的类之间的关系,与用到他们的类组合在一起,这样有利于资源的管理(这点可能是它相对于函数最显著的优点了)。函数对象首先是一个对象,即某个类的实例。其次,函数对象的行为和函数一致,即是说可以像调用函数一样来使用函数对象,如参数传递、返回值等。这种行为是通过重载类的()操作符来实现的,如下:
class Print
{
public:
void operator()(int n)
{
std::cout<<n<<std::endl;
return ;
}
};
int
main(int argc, char **argv)
{
Print print;
print(372);
print.operator()(372); //~ 显式调用
return 0;
}
其实我们早就开始使用函数对象了,当你写下sort(v.begin(), v.end())时(假定v是vector<int>),其实调用的是sort(v.begin(), v.end(), less<int>()),这样sort就会将v从小至大排序。若要逆向排序,你就需要显式地为sort指定一个排序规则,即函数对象greater<int>(). less<T>和greater<T>是STL中的两个模板类,它们使用类型T的<和>操作符。less<T>的一个典型实现可能是这样的:
template <class T>
class less
{
public:
incline bool operator()(const T&l, const T&r)const
{
return l < r;
}
};
4、函数指针和仿函数效率比较
在函数对象的方式中,内联incline有效,而作为函数指针时,一般编译器都不会内联函数指针指向的函数,即使指定了inline,比如:
inline bool doubleGreater(double d1, double d2)
{
return dl > d2;
}
vector<double> v;
...
sort(v.begin(), v.end(), doubleGreater);
这个调用不是真的把doubleGreater传给sort,它传了一个doubleGreater的指针。更好的方式是使用函数对象:
sort(v.begin(), v.end(), greater<double>())
5、内联incline的优点和缺点
- 优点:栈空间就是指放置程式的局部数据也就是函数内数据的内存空间,在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足所造成的程式出错的问题,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。使用inline修饰后,可以避免了频繁调用函数对栈内存重复开辟所带来的消耗。关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。
- 缺点:inline只适合涵数体内代码简单的函数数使用,不能包含复杂的结构控制语句例如while、switch,并且内联函数本身不能是直接递归函数。 内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
注:《effcient c++》中的实验结论,使用函数对象一般是裸函数的1.5倍,最多能快2倍多