内容简介:函数包含两个要素:函数签名和函数体。其中函数签名确定了函数的类型;函数体确定了它的功能。说到函数式编程,核心就是我们可以把函数当做“一等公民”:可以声明函数变量、可以赋值、可以当做参数传递给函数、也可以作为函数返回的类型。
函数包含两个要素:函数签名和函数体。
其中函数签名确定了函数的类型;函数体确定了它的功能。
说到函数式编程,核心就是我们可以把函数当做“一等公民”:可以声明函数变量、可以赋值、可以当做参数传递给函数、也可以作为函数返回的类型。
1 函数和函数指针的定义
当我们定义一个函数类型时,函数名、形参列表、返回值、函数体缺一不可。
当我们声明一个函数变量时,则不需要指定函数体,且以 ;
结尾:
// 以下是一个函数定义 int func(int a, int b) { return a + b; } // 以下是函数声明 int func(int a, int b); 复制代码
C++中变量的类型包括:
- 基本类型(整型、浮点型、字符型)
- 自定义结构体或类
- 复合类型(数组)
- 指针
- 函数类型
对于函数的形参和返回值而言,它们可以是除数组类型或函数类型之外其他的任意类型。
那么如果确实要返回数组类型或者函数类型怎么办呢?这就需要借助到指针了:指向数组的指针,和指向函数的指针。
// 定义一个指向int[10]类型的数组的指针 int a[10]; int (*pa) [10] = a; // 定义一个指向 int (int)类型的函数的指针 int (*pf) (int, int) = func; 复制代码
使用数组指针访问数组时,必须写上解指针符号:
(*pa)[0] = 1 复制代码
使用函数指针调用函数时,可以省略解指针符号:
pf(a, b); 复制代码
接下来看看如何定义一个函数,返回一个数组指针:
int (*func1(int val))[10] { int (*pa)[10] = (int(*)[10])(new(int)[10]); for(auto i = 0; i < 10; ++i) { (*pa)[i] = val + i; } return pa; } int main() { auto pa = func1(3); // 因为func1是在堆上分配的数组,所以需要delete它 delete (int *)pa; } 复制代码
再看如何返回一个函数指针:
// func2 形参列表为空,然后返回一个函数指针:需要2个int形参,返回int int (*func2())(int, int) { return func; } 复制代码
当我们把一个函数名称当做值使用时(即除了调用函数之外的其它用法),它会自动转换成函数指针。
tips
- 上面那种定义返回函数指针的函数,用的还是兼容C的写法。在现代C++中,可以使用尾置返回类型的方式来定义:
auto func2() -> int (*)(int, int); 复制代码
-
可以使用
decltype
定义函数指针类型。但是decltype
一个函数名称时,得到的是函数类型,而不是函数指针类型:
// 定义一个函数 int retfunc(const int& a, const int& b); // 定义一个函数,返回指向int(const int&, const int&)函数类型的指针 // 以下两种写法等价 int(*getFunc(const int& x))(const int&, const int&); decltype(retfunc)* getFunc(const int& x); 复制代码
2 lambda表达式
lambda表达式,就是传说中的匿名函数:即没有名字的“函数”。
int main() { int a = 10; auto fl = [&a](int x) -> int { a++; return x > a ? a : x }; std::cout << a << " " << fl(3) << " " << a << std::endl; return 0; } 复制代码
例如,上例中,我们定义了一个lambda对象 fl
:它按引用捕获了调用它的函数的局部变量a,需要传入一个参数,并返回int值。
在lambda表达式中,仅能也是只需要捕获定义它的函数的自动局部变量。对于静态局部变量或函数外部变量,不用捕获也是可以访问的。
对于在类的成员函数中定义的lambda表达式,除了可以捕获局部变量之外,还可以捕获这个类的非静态的成员变量(跟捕获局部变量一样)。对成员变量,还有个额外的规则:如果捕获了 this
指针,那么自动获取所有成员变量的访问权限。
如果需要在lambda表达式中修改按值捕获的变量,需要在参数列表和尾置返回类型之间加上 mutable
关键字:
auto fl = [a](int x) mutable -> int { return x + a; } 复制代码
使用 bind
绑定参数
auto newCallable = bind(callable, arg_list); 复制代码
bind
可以看做是从一个可调用对象到另外一个可调用对象的映射。跟lambda表达式一样, bind
返回的也是一个可调用对象。
callable
和 newCallable
这两个可调用对象的形参列表,以及实参的顺序都是可以随意调整的。
在调用 bind
时,我们在 arg_list
中,不仅可以传入任意具体的实参变量,也可以传入形如 _n
的“占位符”。占位符的作用,就是将调用 newCallable
时的参数,映射到 callable
时的参数: _1
就是映射成 newCallable
的第一个参数, _2
就是第二个参数,依次类推。有多少个“占位符”,就表示在调用 newCallable
时需要传入多少个参数。
举个例子:
// 我们有个需要传入2个参数的函数funcA int funcA(int x, int y); int a; // 有一个占位符,所以调用funcB时,需要传入一个参数 auto funcB = bind(funcA, a, _1); int b; funcB(b); // 等价于 funcA(a, b) 复制代码
而且在 arg_list
中, _n
的顺序和位置是任意的,比如 _2
可以在 _1
前面:
int funcA(int x, int y, int z); int a; auto funcB = bind(funcA, _2, a, -1); int b, c; funcB(b, c); // 等价于 funcA(c, a, b); 复制代码
注: _n
是定义在名字空间 std::placeholders
中的,所以需要先 using namespace std::placeholders
。
绑定引用参数
在使用 bind
做函数映射时,对于那些不是占位符的参数,是将其拷贝到 bind
返回的可调用对象中的。如果某些参数不支持拷贝呢?比如 ostream
。
可以使用标准库里的 ref
函数返回一个变量的引用类型:
ostream& print(ostream& os, const string& s, char c); ostream os; auto f = bind(print, ref(os), _1, ' '); f("hello, world");// 等价于 print(os, "hello, world", ' '); 复制代码
其实这没有改变 bind
的拷贝行为,因为 ref()
返回的就是一个可拷贝的对象,只不过它的内部定义了一个原来参数的引用类型,并且保证拷贝后都引用同一个变量。
不信,我们可以自己实现一个类 myref
(为了简单起见,没有实现成模板类,只能转 ostream
引用):
class myref { public: // 包含了引用类型的成员变量,只能在构造函数里面显式初始化 myref(ostream& os) : os_(os) {} // 保证可以将它转换成一个ostream引用类型 operator ostream& () { return os_; } private: ostream& os_; }; 复制代码
除了 ref
之外,还可以用 cref
返回变量的 const
引用类型。
绑定类成员函数
bind针对成员函数,提供了特别的支持,只要你把指向类实例的指针作为第二个参数传递即可。
class Test { public: int func(int v); }; Test t; auto f = bind(&Test::func, &t, std::placeholders::_1); 复制代码
注意,对普通函数,当我们把函数名字当做值使用时,会自动转换成函数指针;但是对于成员函数,我们必须显式写上取址符。
3 函数对象
如果一个类实现了函数调用运算符 operator()
,那么它的对象就是一个函数对象。如果这个类还定义了其它的成员变量,那么它的对象就是一个有状态的函数对象,比普通的函数拥有更强大的能力。
知识点:lambda表达式就是一个函数对象:
-
它定义了函数调用运算符
operator()
; - 如果它按值捕获了外部变量,那么它就定义了相应的成员变量,并在构造函数中初始化这些成员变量;
-
如果它按引用捕获了外部变量,那么编译器会直接使用这些引用,而不会在类中创建相应的成员变量。所以需要 程序员 保证在
lambda
对象生存期间,它捕获的引用变量要一直可访问; -
默认
operator()
是const
的,如果它被定义成mutable
,那么它的operator()
就不是const
的。
函数/函数指针、bind返回值、lambda表达式、函数对象等,这5种对象都有一个特点就是我们都可以对它执行函数调用。我们将其称为“可调用对象”。
“可调用对象”的一个重要属性,就是它的调用形式(或函数签名):包括返回类型和一个实参类型列表。
虽然这5种可调用对象的类型是不一样的,但是他们可能拥有相同的调用形式。
例如,以下对象都实现了相同的调用形式 int (int, int)
:
// 普通函数和函数指针 int add(int a, int b) { return a + b; } int (*padd)(int, int) = add; // lambda表达式 auto mod = [](int a, int b) -> int { return a - b; } // 函数对象 struct divide { int operator()(int den, int div) { return den / div; } }; 复制代码
如果我们要把这些对象放进同一个容器呢?因为它们类型不同,是没法做到的:
std::map<std::string,int(*)(int,int)> binops; binops.insert(make_pair("add", add)); // OK binops.insert(make_pair("mod", mod)); // 错误,类型不匹配 binops.insert(make_pair("divide", divide())); // 错误,类型不匹配 复制代码
我们需要有一种类型,所有这些可调用对象都能自动转换成这种类型。标准库提供的 function
类就是啦!
function<int(int,int)> f; f = add; // OK f = mod; // OK f = divide(); // OK f = bind(add, _1, _2); // OK 复制代码
只要我们定义一个调用形式一样的 function
对象,就可以保存所有调用形式一样的可调用对象。
Q:如何实现将类A自动转换成类B? A: 有两种方法:在类A中重载类型转换运算符;在类B中重载复制构造函数和赋值运算符。但是不要两种方法同时用,会产生二义性,导致编译失败。
以上所述就是小编给大家介绍的《C++11中的函数》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Python 拓展之特殊函数(lambda 函数,map 函数,filter 函数,reduce 函数)
- Python 函数调用&定义函数&函数参数
- python基础教程:函数,函数,函数,重要的事说三遍
- C++函数中那些不可以被声明为虚函数的函数
- 017.Python函数匿名函数
- 纯函数:函数式编程入门
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Essential C++中文版
[美] Stanley B. Lippman / 侯捷 / 华中科技大学出版社 / 2001-8 / 39.80元
书中以4个面向来表现C++的本质:procedural(程序性的)、generic(泛型的)、object-based(个别对象的)、object-oriented(面向对象的),全书围绕着一系列逐渐繁复的程序问题,以及用以解决这些问题的语言特性。循此方式,读者不只学到C++的函数和结构,也会学习到它们的设计目的和基本原理。一起来看看 《Essential C++中文版》 这本书的介绍吧!