C++函数指针和Swift的函数对象
在C++中学习函数指针式非常痛苦的事情,而在Swift里面学习函数指针则是非常愉快的。
基本语法
一个函数的声明由函数名,参数列表,返回值三个部分组成,在实际应用中,函数本身也可以当作参数,这个时候就是函数指针,函数指针赋予C语言一定的动态能力,C++的很多的功能都是通过函数指针来实现的,不可谓不重要,在Swift等函数式编程语言中,函数更是一等公民,下面是一些简单的例子
typedef int (*OPERATOR)(int, int);//使用typedef简化定义,使代码更易懂
OPERATOR op = max;//不使用typedef的话,可以直接int (*op)(int, int) = max
//c++11之后可以auto op = max
op(10,23);
let op: (Int, Int) -> Int = max
上面C++和Swift定义中op是函数指针的名称,指向具体的函数为max,参数是两个Int,返回值也是Int,看起来非常简单吧。
类中的函数指针
请解释一下这段代码:int* (Class::*value[2])(int (*)())
,这个value是什么鬼?
C++中的函数指针需要考虑作用域,比如类成员函数,类静态成员函数,C++的成员函数指针和类本身绑定,类型非常复杂,下面代码中fpr的类型为Number (Number::*)(Number)
,相比普通的函数指针多了一个作用域
class Number {
int x;
public:
Number(int value) {
x = value;
}
Number add(Number a){
return x + a.x;
}
};
Number (Number::*fpr)(Number) = &Number::add;//复杂的类型
//auto fpr = &Number::add;//c++11
Number num1(10);
(num1.*fpr)(20);//成员函数指针的使用方法
在Swift中函数指针的类型就比较简单,Swift中函数和闭包区别不大,都可以使用同样的类型来表示。
class Number {
var x:Int
init(_ v:Int) {
x = v
}
func add(_ b:Number) -> Number {
return Number(x + b.x)
}
}
var fpr = Number.add
//fpr的类型是 (Number) -> (Number) -> Number,其中第一个(Number)就是self指针
let num1 = Number(10)
let num2 = Number(20)
let num3 = fpr(num1)(num2)//num1相当于self
在Swift中,所有的函数指针都是一样的,并不区分普通函数指针和成员函数指针,同时,你也没办法在调用的时候感知到这是一个对象成员函数的调用。
构造函数和析构函数
C++标准规定不能对构造函数和析构函数取地址,另外我们也知道这两种函数没有返回值,也没办法定义类型,只能是作为特殊函数对待,网上也有一些hack的方法,通过汇编取这两种函数的地址,和我们讨论的函数指针关系不大,这里就不多讨论了。
在C++里面如果需要构造函数的函数指针,一般会定义一个静态成员函数返回一个对象来完成,比如Number Number::Create(int value)
这样的,这样的函数指针也常常出现在工厂方法里面替代直接调用构造函数。
Swift里面构造函数相比其他函数没有什么特殊的地方,记住两步构造和安全检查就差不多了,毕竟创造是比较复杂的事情,所以这里可以直接取构造函数的指针,比如Number.init
就是一个类型为(int) -> Number
的函数指针,不过Swift的析构函数因为不能直接调用,所以也不能取地址。
上下文
在C++中函数指针是单纯的不带上下文的,在Swift里面不太一样,比如说,在Swift语言中可以对对象的某个函数取指针,接着上面的代码:
var fpr1 = num1.add
let num4 = fpr1(num2)
这里的fpr1的上下文中就包含了num1,好比是在闭包捕获了这个对象,调用fpr1和num1.add是等价的,根据这个特性,可以简化很多操作,假如我们有一个int数组,需要转换成Number数组,用map就可以了,代码是这样的:
let arr2 = arr1.map(Number.init)
//或者
let arr2 = arr1.map {Number($0)}`
然后我们要给所有的元素都加上num2这个对象,这里明显的一点是num1.add(num2)和num2.add(num1)是没有区别的,满足交换律的,所以这个动作可以这么实现:
arr2.map(num2.add)
//或者
arr2.map {$0.add(num2)}
是不是也可以认为num1.add是函数(Number) -> (Number) -> Number的curry,num1是第一个参数,结果是(Number) -> Number.
重载
overload是现代语言中很重要的一个特性,可以给同一个函数名声明不同的参数,C++中要求参数的个数或类型必须有差异,而Swift中允许相同的参数列表和不同的返回值,根据返回值推断具体的函数调用。前面C++代码里面又一个C++11之后的特性,就是auto类型,编译器自动给你生成类型,免得再去面对鬼一样的函数指针声明,不过这个也不是万能的,一旦遇到重载酒不能工作了,还有就是使用函数指针作参数的时候,肯定还是要一丝不苟的把声明给写出来。那么Swift怎么办呢?默认情况下我们使用let和var的声明变量的时候可以不指定类型,交给编译器来推断,在这种无法推断的地方,编译器也无能为力,这里可以使用明确的类型声明来避免二义性,代码:
func magicNumber() -> Int {return 2017}
func magicNumber() -> Float {return 2.20}
let f: () -> Float = magicNumber
f()//顺利得到2.20
总结
Swift在设计的时候有很多函数式编程语言的思想,充分的利用这个特性会让我们的编码效率更高。
C++的函数指针相对比较复杂,而且经常和数组指针等一起混用变得可读性比较差,需要我们去拆分代码。
int* (Class::*value[2])(int (*)())
这个搞明白了吗?
typedef Paratype = int (*)();
typedef int* (Class::*Cptr)(ParaType);
Cptr value[2];//这就是你要的value,原来是一个数组
//数组元素类型是Class类的成员函数指针
//函数指针的返回值是int*
//函数的参数类型是Paratype也是一个函数指针,是没有参数返回int的函数指针
就这样。