欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-11 21:42:17
...

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

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头文件中:
C++ Primer第五版笔记——重载运算符(二)
这样的化,根据字符串长度排序的函数又可以改写为:

//按降序排列
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头文件中。
C++ Primer第五版笔记——重载运算符(二)
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;}});