C++ Primer第五版笔记——重载运算符(二)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rest_in_peace/article/details/81565307

函数调用运算符:
如果类重载了函数调用运算符“()”,则可以像调用函数一样使用该类的对象,因为类同时还能存储状态,所以普通函数相比之下更加灵活。
比如:

struct absInt{
    int operator()(int v) const{
        return v > 0? v : -v;
    }
};

int main(){

    int i = -99;
    absInt s;
    int x = s(i);
    cout<<x<<endl;
}

该类接受int类型的实参,返回它的绝对值。调用的方式也很想函数的调用过程。
函数调用运算符必须是成员函数,这个类的对象也被称为“函数对象”,一个类可以定义多种不同版本的调用运算符,像函数重载一样应该在参数数量或类型上有所区别。
含有状态的函数对象类:
和其他类一样,函数对象类也有自己的状态,其也可有自己的数据成员,这些成员通常用于定制调用运算符函数。例如:

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

int main(void){
    PrintStr p;
    string s = "this is a stence";         
    p(s);                       //会打印s和换行
}

函数对象常常作为泛型算法的实参,例如for_each函数中可以用自己的类来打印内容:

for_each(v.cbegin(),v.cend(),PrintStr());

这里的第三个参数是一个临时对象,这里默认初始化了该对象,当调用for_each时,将会把v中的每个元素依次打印到标准输出,以换行符分割。

lambda是函数对象:
刚才将PrintStr作为for_each实参的用法和使用lambda表达式的方式类似。当我们编写一个lambda表达式后,编译器将其翻译为一个未命名类的未命名对象,在这个类中有一个重载的函数调用运算符,例如:

//根据单词长度排序
stable_sort(words.begin(),words.end(),
            [](const string& a,const string& b)
            {return a.size() < b.size();});
//其中的lambda表达式翻译成类似下面的类
class ShorterString{
public:
    bool operator()(const string& a,const string& b) const {
        return a.size() < b.size();
    }
};
//替换后的函数
stable_sort(words.begin(),words.end(),ShorterString());

表达lambda及捕获对象的类:
当一个lambda表达式通过引用捕获变量时,由程序来确保lambda执行时引用所引的对象确实存在,因此,编译器可以直接使用该引用而不需再lambda表达式产生的类中将其存储为数据成员。(我理解的意思是编译器在翻译lambda表达式为类时,将传给lambda表达式的参数自动翻译为该类的数据成员)。例如:

//获得字符长度大于给定值的字符
auto wc = find_if(words.begin(),words.end(),
                [sz](const string& a)
                {return a.size() > sz;});
//lambda将翻译为类似下面的类
class SizeComp{
public:
    SizeComp(size_t n):sz(n){}
    bool operator()(const string& a) const {
        return a.size() > sz;
    }
private:
    size_t sz;
};
//替换后的函数
auto wc = find_if(words.begin(),words.end(),SizeComp(sz));

标准库定义的函数对象:
标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类,每个类分别定义了一个执行命名操作的调用运算符,比如plus类表示+,equal_to表示==。
这些类都被定义为模板的形式,比如plus<string>表示加法运算符作用于string对象。这些类型都被定义在functional头文件中:
这里写图片描述
这样的化,根据字符串长度排序的函数又可以改写为:

//按降序排列
sort(words.begin(),words.end(),greater<string>());

第三个参数是一个greater<string>类型的未命名对象。
需要注意的是,标准库规定其函数对象对于指针同样适用,但是比较两个无关指针将产生未定义的行为,因此,当做希望通过比较指针地址来排序之类的操作时,可以使用标准库函数对象。

vector<string*> nameTalbe;
//错误,nameTable中的指针彼此之间没有关系,调用"<"将产生未定义行为
sort(nameTable.begin(),nameTable.end(),
    [](const NameTable& a,const NameTable& b)
    {return a < b;});
//正确标准库中的less定义时良好的
sort(nameTable.begin(),nameTable.end(),less<string*>());

可调用对象和function
C++语言中有几种可调用对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了的函数调用运算符。
可调用对象也有自己的类型,比如lambda表达式有自己的(未定义)类类型。但可能共享一种调用形式(call signature),调用形式指明调用返回的类型以及传递给调用的实参类型。例如:int(int,int)是一个函数类型,它接受两个int,返回一个int。
不同类型可能具有相同的调用形式:
对于几个可调用对象共享同一种调用形式的情况,有时我们希望把它们看成具有相同的调用形式,例如:

//普通函数
int add(int a,int b){
    return a+b;
}
//lambda
auto mod = [](int a,int b){
    return a % b; 
}
//函数对象类
struct divide{
    int operator()(int a,int b){
        return a/b;
    }
};

上面的可调用对象类型不同,但是共享的一种调用形式:int(int,int).
假设希望通过这些对象来构建啊一个计算器,为了实现这个目的,需要定义一个函数表用于存储指向这些对象的“指针”。当需要执行某个特定操作时,从表中查找对应对象。
在C++语言中,可以使用map来实现函数表,对应这里的map可以如下定义:

//构建从运算符到函数指针的映射关系
map<string,int(*)(int ,int)> binops; 
//添加add
binops.insert({"+",add});

但是不能将后两种操作存入到binops中,因为lambda表达式和函数对象类都不能作为函数指针传递到binops中的值中。
我们可以使用一个名为function的新标准库类型解决这个问题,function定义在functional头文件中。
这里写图片描述
function是一个模板,尖括号中的内容是能够表示的对象的调用形式,例如:

function<int(int,int)>
//可以用这个声明表示以上任意一种类型
function<int(int,int)> f1 = add;
function<int(int,int)> f2 = divide();
function<int(int,int)> f3 = [](int i,int j){return i*j;};

//使用这个function重新定义map,并添加成员
map<string,function<int(int,int)>> binops = {
    {"+",add},
    {"-",std::minus<int>()},
    {"*",[](int i,int j){return i*j;}},
    {"%",divide()},
    {"%",mod}
};

//使用
binops["+"](10,5);      //调用add(10,5)

重载的函数与function:
不能(直接)将重载函数的名字存入function对象中,这可能为产生二义性:

int add(int a,int b);
Sales_date add(const Sales_date& a,const Sales_date& b);

map<string,function<int(int,int)>> binops;
binops.insert({"+",add});                   //错误,不知道是哪个add

解决办法之一可以通过存储函数指针而不是存储函数的名字:

int (*fp)(int,int) = add;       //表示指针所指的函数接受两个int参数
binops.insert({"+",fp});        //正确

同样也可以使用lambda来消除二义性:

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

猜你喜欢

转载自blog.csdn.net/rest_in_peace/article/details/81565307