C++ 杂记

025 C++ 函数调用运算符

2020-06-29  本文已影响0人  赵者也

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

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);} });

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

上一篇 下一篇

猜你喜欢

热点阅读