C++ Pirmer : 第十四章 : 重载运算符与类型转换之函数调用运算符与标准库的定义的函数对象

时间:2022-09-04 19:10:04

函数调用运算符

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来消除二义性。

C++ Pirmer : 第十四章 : 重载运算符与类型转换之函数调用运算符与标准库的定义的函数对象的更多相关文章

  1. C&plus;&plus; Primer &colon; 第十四章 &colon; 重载运算与类型转换之重载运算符

    重载前须知 重载运算符是特殊的函数,它们的名字由operator和其后要重载的运算符号共同组成. 因为重载运算符时函数, 因此它包含返回值.参数列表和函数体. 对于重载运算符是成员函数时, 它的第一个 ...

  2. C&plus;&plus; Primer &colon; &colon; 第十四章 &colon; 重载运算符与类型转换之类型转换运算符和重载匹配

    类型转换运算符 class SmallInt { public: SmallInt(int i = 0) : val(i) { if (i < 0 || i > 255) throw st ...

  3. 【C&plus;&plus;】《C&plus;&plus; Primer 》第十四章

    第十四章 重载运算与类型转换 一.基本概念 重载运算符是具有特殊名字的函数:由关键字operator和其后要定义的运算符号共同组成.也包含返回类型.参数列表以及函数体. 当一个重载的运算符是成员函数时 ...

  4. 《OpenCL异构并行编程实战》第十二至十四章

    ▶ 第十二章,在其他语言中使用 OpenCL ● JOCL(Java Building for OpenCL),PyOpenCL ● 一个 PyOpenCL 的例子代码,需要 pyopencl 包 i ...

  5. 《Linux命令行与shell脚本编程大全》 第十四章 学习笔记

    第十四章:呈现数据 理解输入与输出 标准文件描述符 文件描述符 缩写 描述 0 STDIN 标准输入 1 STDOUT 标准输出 2 STDERR 标准错误 1.STDIN 代表标准输入.对于终端界面 ...

  6. perl 第十四章 Perl5的包和模块

    第十四章 Perl5的包和模块 by flamephoenix 一.require函数  1.require函数和子程序库  2.用require指定Perl版本二.包  1.包的定义  2.在包间切 ...

  7. Gradle 1&period;12 翻译——第十四章&period; 教程 - 杂七杂八

    有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或訪问:http://gradledoc.qiniudn.com ...

  8. C和指针 (pointers on C)——第十四章:预处理器

    第十四章 预处理器 我跳过了先进的指针主题的章节. 太多的技巧,太学科不适合今天的我.但我真的读,读懂.假设谁读了私下能够交流一下.有的小技巧还是非常有意思. 预处理器这一章的内容.大家肯定都用过.什 ...

  9. CSS3秘笈复习:十三章&amp&semi;十四章&amp&semi;十五章&amp&semi;十六章&amp&semi;十七章

    第十三章 1.在使用浮动时,源代码的顺序非常重要.浮动元素的HTML必须处在要包围它的元素的HTML之前. 2.清楚浮动: (1).在外围div的底部添加一个清除元素:clear属性可以防止元素包围浮 ...

随机推荐

  1. js分页页码算法

    function get_hs_page(cur_page, total_page) { var result = ""; ; i <= total_page; i++) { ...

  2. 查看openssl的版本

    [root@ha01 tengine]# openssl version -a OpenSSL Feb built on: Thu Jul :: UTC platform: linux-x86_64 ...

  3. NK3C系统中ID的汉语名称

    系统中的ID有需要显示给使用者看的,统一用"编号",不要用ID或者编码 例如: SampleId 样本编号 正确 样本编码 错误 样本ID 错误 如果见到系统中有用到的,修正即可. ...

  4. 阿里云maven加速和docker加速

    maven加速 maven仓库用过的人都知道,国内有多么的悲催.还好有比较好用的镜像可以使用,尽快记录下来.速度提升100倍. http://maven.aliyun.com/nexus/#view- ...

  5. 一个简单的web服务器

    写在前面 新的一年了,新的开始,打算重新看一遍asp.net本质论这本书,再重新认识一下,查漏补缺,认认真真的过一遍. 一个简单的web服务器 首先需要引入命名空间: System.Net,关于网络编 ...

  6. WPF中的一些常用类型转换

    1.string和Color的转换: //string转Color (Color)ColorConverter.ConvertFromString((string)str); //Color转stri ...

  7. iOS app闪退的一般原因

    1.函数无限递归爆栈(表视图返回Cell和返回行高的方法互相调用)2.某对象无法解析某个方法(没做类型转换.或者代理没实现某个方法)3.访问了某个已经被释放的对象(ARC之后不太有)4.从Bundle ...

  8. html-----vedio标签(HTML5新标签VIDEO在IOS上默认全屏播放)

    今天做一个app时发现一个问题,应用html5中的video标签加载视频,在Android手机上默认播放大小,但是换成iPhone手机上出问题了,默认弹出全屏播放,查找了好多论坛,都没有谈论这个的.然 ...

  9. leetcode刷题笔记342 4的幂

    题目描述: 给定一个整数 (32位有符整数型),请写出一个函数来检验它是否是4的幂. 示例:当 num = 16 时 ,返回 true . 当 num = 5时,返回 false. 问题进阶:你能不使 ...

  10. Python常用算法(一)

    1.选择排序 不断找到最小的(找最大的也是可以的) 首先拿到第一个,然后发现比它小的,记住下标.循环一轮,找到最小的数的位置 和 最左边的数交换位置 然后从第二个开始....和第二个交换位置,循环最后 ...