函数调用运算符
struct test { int operator()(int val) const { return (i > 0 ? i : -i); } };
所谓的函数调用就是一个类重载了函数调用符,类在使用重载函数调用符时接受相应参数。这一过程就像是使用一个函数一样,因此叫做函数调用。
上面的类test,它重载了函数调用符(), 接受一个int类型参数,返回它的绝对值。
我们就可以将一个test类对象当做一个函数来使用:
int main(void) { test t; int val = -3; std::cout << "abs(val) = " << t(val) << std::endl; return 0; }
使用函数重载调用符时,直接在类名后面跟括号,括号里加所需参数即可,看起来就和一个普通的函数调用一样。 我们称这样的类为“行为像函数一样的类”。
如果类定义了函数调用运算符,则该类的对象称作函数对象。函数调用运算符必须是成员函数。
函数对象常常作为泛型算法的实参。我们可以定义一个函数对象,作用为输出实参内容,我们将其作为标准库定义的for_each的实参:
class printString { public: printString(std::ostream& o = std::cout) : os(o) {} void operator()(const std::string& s) { os << "s = " << s << std::endl; } private: std::ostream& os; }; int main(void) { std::vector<std::string> vec = { "Hello", "World" }; printString ps; for_each(vec.begin(), vec.end(), ps); return 0; }
lambda是函数对象
当编写一个lambda表达式时,编译器将该表达式翻译成一个未命名类的未命名对象。例如:
stable_sort(words.begin(), words.end(), [](const std::string& a, const std::string& b) { return a.size() < b.size(); });
编译器会为我们生成一个类似这样的一个类的对象(未命名的):
class Shorter { public: bool operator()(const std::string& a, const std::string& b) const { return a.size() < b.size(); } };
因为默认的lambda是不可以修改捕获参数的,因此上面这个生成的类似的类的函数调用运算符是一个const成员函数。
如果lambda声明捕获参数是可变的(mutable), 则翻译成的类的函数调用运算符就不是const的了。
捕获的变量被拷贝到lambda中,因此, 这种lambda产生的类必须为每个值的变量建立对应的数据成员,同时创建构造函数:
auto wc = find_if(words.begin(), words.end(), [sz](const std::string& a) { return a.size() >= sz; });
该lambda产生的类形如:
class SizeComp { public: SizeComp(size_t n) : sz(n) {} bool operator()(const std::string& a) const { return a.size() >= sz; } private: size_t sz };
lambda产生的类不含默认构造函数、赋值运算符及默认析构函数,它是否有默认的拷贝/移动构造函数则视捕获的数据成员的类型决定。
习题14.38 class CheckString { public: bool operator()(const std::string& s) { return (s.size() >= 1 && s.size() <= 10); } size_t operator()(std::vector<std::string>& vec, size_t length) { size_t num = 0; for (const auto& elmt : vec) (elmt.size() == length) ? ++num : num; return num; } }; int main() { CheckString ck; std::string s{"HelloWorld"}; std::vector<std::string> vec = { "hello", "world", "i", "love", "you", "but", "you", "is", "what" }; std::cout << "s.size() >= 1 && s.size() <= 10 ?" << ck(s) << std::endl; size_t n = 1; std::cout << "size == 1 has " << ck(vec, n) << std::endl; return 0; }
标准库定义的函数对象
在头文件 <functional>中,定义了一组表示算术运算符、关系运算符和逻辑运算符的类,每个类分别定义了一个执行命名操作的调用运算符。 这些类都被定义成模板的形式。
算术: plus<Type> // 加法 minus<Type> // 减法 multiplies<Type> // 乘法 divides<Type> // 除法 modulus<Type> // 取模 negate<Type> // 取反 关系: equal_to<Type> // 等于 not_equal_to<Type> // 不等于 greater<Type> // 大于 greater_equal<Type> // 大于等于 less<Type> // 小于 less_equal<Type> // 小于等于 逻辑: logical_and<Type> // 逻辑与 logical_or<Type> // 逻辑或 logical_not<Type> // 逻辑非
这些函数对象对于指针同样适用。
比较两个无关指针会产生未定义的行为,如果希望通过比较指针的地址来sort指针的vector,直接这样做会产生未定义的行为。不过我们可以使用标准库定义的函数对象来比较,标准库规定指针的less是定义良好的。
std::vector<std::string*> vec; // 未定义行为! sort(vec.begin(), vec.end(), [](std::string* a, std::string* b){ return a < b; }); sort(vec.begin(), vec.end(), less<std::string*>());
关联容器使用less<key_type>对元素排序。
可调用对象与function
标准库function定义在头文件 <functional>中
functional<T> f; f是一个用来存储可调用对象的空function,这些可调用对象的调用形式应该与函数类型T相同 functional<T> f(nullptr); 显示的构造一个空function functional<T> f(obj); 在f中存储可调用对象obj的副本 f 将f做为条件,当f含有一个可调用对象时为真,否则为假 f(args) 调用f中的对象,参数是args 定义为function<T>的成员的类型 result_type 该function类型的可调用对象返回的类型 argument_type 当T有一个或两个实参时定义的类型,当T有一个实参时,argument_type是该类型的同义词 first_argument_type 当有两个实参时,first_argument_type和second_argument_type分别代表两个实参类型 second_argument_type
function是一个模板, 创建一个function时必须指定它的类型。
int add(int i, int j) { return i / j; } auto mod = [](int x, int y){ return x % y; }; struct divide { int operator()(int a, int b) { return a / b; } }; std::function<int(int, int)> f1 = add; std::function<int(int, int)> f2 = divide(); std::function<int(int, int)> f3 = [](int x, int y){ return x % y; }; std::cout << f1(4, 2) << std::endl; std::cout << f2(4, 2) << std::endl; std::cout << f3(4, 2) << std::endl;
对于重载的函数,不能直接放入function类型对象中:
int add(int i, int j) { return i / j; } Sales_data add(const Sales_data&, const Sales_data&);
int add(int i, int j) { return i / j; } Sales_data add(const Sales_data&, const Sales_data&); std:map<std::string, std::function<int(int, int)>> mp; mp.insert({"+", add}); // 错误,哪个add?
int (*fp)(int, int) = add; mp.insert({"+", fp});
或者还可以使用lambda来消除二义性。