025 C++ 函数调用运算符

如果类重载了函数调用运算符,则可以像使用函数一样使用该类的对象,因为这样的类同时也能存储状态,所以与普通函数相比它们更加灵活。

struct absInt{
    int operator()(int val) const{
        return val < 0 ? -val : val;
    }
};

上面的类只定义了一种操作:函数调用运算符,它负责接受一个 int 类型的形参,然后返回该实参的绝对值。

int i = -42;
absInt absObj;
int ui = absObj(i); // i 被传递给 absObj.operator()

即使 absObj 是一个对象而非函数,也能调用该对象,调用对象实际上是在运行重载的调用运算符。

函数调用运算符必须是成员函数,一个类可以定义多个不同版本的调用运算符,相互之间在参数数量和参数类型上应该有所区别。

如果类定义了调用运算符,则该类的对象称作 函数对象 ,因为可以调用这种对象,所以这些对象的行为像函数一样。

含有状态的函数对象类

函数对象除了 operator() 之外也可以包含其他成员。

class PrintString{
public:
    PrintString(ostream &o = cout,char c = ' '):os(o),sep(c){ }
    void operator()(const string &s)const{os<<s<<sep;}
private:
    ostream &os;
    char sep;
};

使用:

PrintString printer;
printer(s);     // cout 中打印 s,后面跟一个空格
PrintString errors(cerr, '\n');
errors(s);      // cerr 中打印 s,后面跟一个换行符

函数对象通常是作为泛型算法的实参:

for_each(vs.begin(), vs.end(), PrintString(cerr, "\n"));

lambda 是函数对象

编写了一个 lambda 后,编译器将该表达式翻译成一个未命名类的未命名对象。

stable_sort(words.begin(), words.end(),
            [](const string &a, const string &b) {
    return a.size() < b.size();
});

其行为类似下面这个类的一个未命名对象:

class ShorterString{
public:
    bool operator()(const string &a,const string &b)
    {return a.size() < b.size();}
};

使用上面的类重写 stable_sort :

stable_sort(words.begin(), words.end(), ShorterString());

表示 lambda 及相应捕获行为的类

auto wc = find_if(words.begin(),words.end(),
                  [sz](const string &a) {
    return a.size() > = sz;
});

该 lambda 表达式产生的类将形如:

class SizeComp
{
    SizeComp(size_t n):sz(n) { }
    bool operator()(const string &s) const {return s.size() >= sz;}
private:
    size_t sz;
};

auto wc = find_if(words.begin(), words.end(), SizeComp(sz));

标准库定义的函数对象

标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类,每个类分别定义了一个执行命名操作的调用运算符。

plus<int> intAdd;   // 可执行 int 加法的函数对
negate<int> intNegate;  // 可对 int 值取反的函数对象
int sum = intAdd(10, 20);   // sum = 30
sum = intAdd(10, intNegate(10));     // sum = 0

定义在 functional 头文件中的函数对象:

function 的操作 说明
function<T> f; f 是一个用来存储可调用对象的空 function,这些可调用对象的调用形式应该与函数类型 T 相同(即 T 是 retType(args))
function<T> f(nullptr); 显式地构造一个空 function
function<T> f(obj); 在 f 中存储可调用对象 obj 的副本
f 将 f 作为条件:当 f 含有一个可调用对象时为真;否则为假
f(args) 调用 f 中的对象,参数是 args
定义为 function<T> 的成员的类型 说明
result_type 该 function 类型的可调用对象返回的类型
argument_type
first_argument_type
second_argument_type
当 T 有一个或两个实参时定义的类型。如果 T 只有一个实参,则 argument_type 是该类型的同义词;如果 T 有两个实参,则 first_argument_type 和 second_argument_type 分别代表两个实参的类型

在算法中使用标准库函数对象

// 传入一个临时函数对象用于执行两个 string 对象的比较运算
sort(svec.begin(),svec.end(),greater<string>());

标准库规定其函数对象对于指针同样适用:

vector<string *> nameTable; // 指针的 vector
// 错误:nameTable 中的指针彼此之间没有任何关系,所以 < 将产生未定义的行为
sort(nameTable.begin(),nameTable.end(),[](string *a, string *b){return a < b;}) ;

//正确,标准库规定指针的 less 定义是良好的
sort(nameTable.begin(), nameTable.end(), less<string*>());

可调用对象与 function

C++ 中的可调用对象包括:函数、函数指针、lambda 表达式、bind 创建的对象以及重载了函数调用运算符的类。

可调用对象也有类型,labmda 有自己唯一的未命名类型,函数及函数指针的类型由其返回值类型和实参类型决定。两个不同类型的可调用对象却可能共享同一种调用形式,调用形式指明了返回类型以及传递给调用的实参类型,一种调用形式对应一个函数类型:

int (int,int)   // 是一个函数类型,它接受两个 int,返回一个 int

不同的类型可能具有相同类型的调用方式

int add(int i,int j){return i + j;}
auto mod = [](int i,int j){return i % j;};
struct divide{
    int operator()(int denminator,int divisor){
        return denminator / divisor;
    }
};

尽管这些可调用对象对其参数执行了不同的算术运算,尽管它们的类型各不相同,但是共享一种调用形式:

int (int,int)

构建实现不同运算的函数表:

divide div;
map<string,int(*)(int,int)> binops;
binops.insert({"+",add});

binops.insert({"/", div});  //错误,div 不是函数指针

标准库 function 类型

function 定义在 functional 头文件中。 function 是一个模板,创建具体的 function 类型时需要提供额外的信息。

function<int(int,int)>
divide div;
std::function<int(int,int)> f1 = add;       // 函数指针
std::function<int(int,int)> f2 = div;       // 函数对象类的对象
std::function<int(int,int)> f3 = [](int i,int j){return i * j;};    // lambda
std::function<int(int,int)> f4 = mod; // lambda

//调用
std::cout<<f1(7,3)<< std::endl;
std::cout<<f2(7,3)<< std::endl;
std::cout<<f3(7,3)<< std::endl;
std::cout<<f4(7,3)<< std::endl;

使用 function 重新定义上面的函数表:

divide div;
std::map<std::string, std::function<int(int,int)>> binops =
{
    {"+", add},     //函数指针
    {"-", std::minus<int>()}, // 标准库函数对象
    {"/", div},     // 自定义函数对象
    {"*", [](int i,int j){return i * j;}},          // 未命名 lambda 表达式
    {"%", mod}, // 命名 lambda 表达式
};

调用:

std::cout << binops["+"](19,5) << std::endl;
std::cout << binops["-"](19,5) << std::endl;
std::cout << binops["/"](19,5) << std::endl;
std::cout << binops["*"](19,5) << std::endl;
std::cout << binops["%"](19,5) << std::endl;

重载的函数与 function

不能直接将重载函数的名字存入 function 类型的对象中。

struct Sales_data;
int add(int i,int j){return i + j;}
Sales_data add(const Sales_data& lhd, const Sales_data& rhd);

int main() {
    std::map<std::string, std::function<int(int,int)>> binops;
    binops.insert({"+", add}); // 错误,不能区分是哪个 add
    return 0;
}

解决上面问题的有效途径是存储函数指针而非函数名字:

int (*fp) (int, int) = add;
binops.insert({"+", fp});   // 正确,fp 指向正确的 add 版本

同样,也可以使用 lambda 来指定希望使用的 add 版本:

binops.insert({"+", [](int a, int b){ return add(a,b);} });

注意:本文非原创,内容摘录自《函数调用运算符》,感谢原作者的付出和无私分享。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352