第14章重载运算与类型转换

14.1基本概念

 

二元运算符:左侧对象为第一个参数,右侧对象为第二参数

除了重载的函数调用运算符()外,其他重载运算符都不能含有默认实参。

如果一个运算符函数是成员函数,则它的第一个左侧运算符对象隐式绑定到this指针上,因此成员运算符函数的参数数量比运算符的运算对象总数少一个。

重载的运算符函数,或者是类成员,或者至少含有一个类类型参数(不能都是内置类型,因为不能改变内置类型运算符的含义)

//非成员函数运算符的等价调用
data1+data2;
operator+(data1,data2);

//成员函数运算符的等价调用
data1+= data2;
data1.operator+=(data2);

通常情况下,不应该重载逗号,取地址,逻辑与,逻辑或运算符。

赋值和复合赋值运算符:赋值之后,左右两侧数据相等,并且运算符返回它左侧运算对象的一个引用

当把运算符定义成成员函数时,它的左侧运算对象必须是运算符所属类的一个对象,例如:

string  s=  "World";
string t = s + "!";    //正确 =右侧相当于s.operator+("!") s+const char *
string u = "hi" + s;   //错误 =右侧相当于 "hi".operator+(s)  const char * +s  
                       //                                   上为内置类型 错误

如果上述 string 将+定义成了普通的非成员函数,则 operator+("hi",s) 和其他函数调用一样,每个实参都能被转换成形参类型。唯一要求是 至少有一个运算符对象是类类型,并且两个都可以被转换为类类型。

14.2输入和输出运算符

14.2.1重载输出运算符<<

     左侧:非常量ostream引用       <<     右侧:常量的引用

左侧非常量是因为:向输入流写入内容会改变其状态,引用是因为:无法拷贝一个ostream对象

右侧引用:希望避免复制。

ostream & operator<<(ostream &os,const Sales_data &item) //os为非常量引用
{
    os << item.isbn()<< " " << item.units_sold << " "<<item.revenue;
    return os;

}

通常,输出运算符应该主要负责打印对象的内容而非控制格式,输出运算符不应该打印换行符。

14.2.2重载输入运算符

//Sales_data的输入运算符

istream &operator >> (istream &is ,Sales_data &item)
{
    double prices;
    is >> item.bookNo >> item.units_sold >> price;
    if(is)    //检查输入是否成功
        item.revenue = item.unite_sold *price;
    else
        item =Sales_data();    //输入失败,对象还原为默认状态。这一步很关键
    return is;
}



14.3算术和关系运算符

        通常我们把算术和关系运算符定义为非成员函数,以允许对左侧或者右侧的运算对象进行转换。一般不需要改变原对象,所以用常量的引用。

Sales_data operator +(const Sales_data &lhs,const Sales_data &rhs)
{
    Sales_data sum = lhs;
    sum+=rhs;
    return sum;

}

如果类同时定义了算术运算符和相关的复合赋值运算符,则通常情况下应该使用复合赋值来实现算术运算符。

14.3.1 相等运算符

bool operator == (const Sales_data &lhs,const Sales_data &rhs){
    return lhs.isbn()==rhs.isbn() &&
           lhs.units_sold == rhs.units_sold &&
           lhs.revenue == rhs.revenue;

}

//两个取反的函数,只需实现一个

bool operator !=(const Sales_data &lhs,const Sales_data &rhs){
    return !(lhs==rhs);            
}

如果类定义了 operator==,则该运算符应该能判断一组给定的对象中是否含有重复数据

                                             应该具有传递性

                                              实现一个,把另一个委托给这一个。

14.4赋值运算符

除了拷贝赋值和移动赋值运算符外,标准库vector还定义了第三种赋值运算符, 该运算符接受花括号内的元素列表作为参数。

vector<string> v;
v = {"a","an","the"};

class StrVec{
public:
    StrVec &operator = (initializer_list<string>);
    //其他略

}

StrVec &operator = (initializer_list<string> il){
    
    auto data = alloc_n_copy(il.begin(),il.end());
    free();                    //销毁对象中的元素并释放内存空间
    elements = data.first;            //更新数据成员使其指向新空间
    first_free = cap = data.second;
    return *this;            //返回左侧对象,与拷贝赋值和移动赋值保持一致。


}

复合赋值运算符

Sales_data & operator +=(const Sales_data &rhs){
    unit_sold +=rhs.unit_sold;
    revenue +=rhs.revenue;
    return *this;

}

赋值运算符必须是成员函数,而复合赋值不必须,但最好是。两者都返回左侧运算对象的引用。

14.5下标运算符

         下标运算符通常以访问元素的引用为返回值,因为这样做 下标运算符可以出现在赋值运算符的任意一端。而且需要同时定义好 常量版本和非常量版本。

class StrVec{
public:
    string & operator[](size_t n){
        return elements[n];
    }

    const string & operator [](size_t n)const
    {
        return elements[n];    
    }



private:
    string *elements;

}



const StrVec cvec = svec;

if(svec.size() && svec[0].empty()){
    svec[0] = "zero";            //正确,下标运算符返回普通引用
    cvec[0] = "zip";             //错误,返回常量引用 不允许修改。
}

14.6 递增和递减运算符

//前置
class StrBlobPtr{
public:
    StrBlobPtr & operator ++();
    StrBlobPtr & operator --();

};

StrBlobPtr & operator ++(){
      //递增递减,都要用check函数检验是否有效。
       check(curr,"increment past end of StrBlobPtr");
       ++curr;
       return *this;  

}
StrBlobPtr & operator --(){
      //递增递减,都要用check函数检验是否有效。
       --curr;
       check(curr,"increment past end of StrBlobPtr");
       return *this;  

}
//后置
class StrBlobPtr{
public:
    StrBlobPtr & operator ++(int);
    StrBlobPtr & operator --(int);

};

StrBlobPtr  operator ++(int){
     // check(); 后置不需要检查
       auto temp = *this;
       ++*this;
       return temp; 

}

StrBlobPtr  operator ++(int){
    //  check();
       auto temp = *this;
       --*this;
       return temp; 

}

为了与内置版本保持一致,后置运算符应该返回对象的原值,返回的形式是一个值而非引用。

14.7 成员访问运算符

class StrBlobPtr{
public:
    string & operator *() const
    {
        auto p = check(curr,"dereference past end");
        return (*p)[curr];    //(*p)所指的vector
    }

    string * operator ->()const
    {    //委托给解引用操作符
        return & this->operator*();
    }

};

 14.8 函数调用运算符

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

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

private:
    ostream &o;
    char sep;
}

PrintString printer;
printer(s);
PrintString errors(cerr,'\n');
errors(s);

14.8.1 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)
               {return a.size()<b.size();}
}

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

14.8.2 标准库定义的函数对象

表示运算符的函数对象类常用来替换算法中的默认运算符。

sort(svec.begin().svec.end(),greater<string>());

第三个greater<string>是一个未命名的对象 ,调用greater的函数对象进行比较。

特别注意:标准库规定其函数对象对于指针同样适用,

vector<string *> nameTable;

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

//正确
sort(nameTable.begin(),nameTable.end(),less<string *>);

 14.8.3 可调用对象与function

        C++中有几种可调用的对象:函数,函数指针,lambda表达式,bind创建的对象以及重载了函数调用运算符的类。这些可调用的对象也有类型。

        然而两种不同类型的可调用对象却可能共享同一种调用形式。调用形式指明了调用的返回类型以及传递给调用的实参类型。一种调用形式对应一个函数类型。

        int(int,int)

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

          对于几个可调用对象共享同一种调用形式的情况,有时我们会希望把它们看成具有相同的类型。

//普通函数
int add(int i,int j){  return i+j;  }

//lambda, 其产生一个未命名的函数对象类
auto mod =[](int i,int j){ return i%j;}

//函数对象类
struct divide{
    int operator()(int denominator,int divisor){
        return denominator/divisor;
    }

}

上述这些共享同一种调用形式:int(int,int)

        如果希望使用可调用对象构建一个简单的桌面计算器。为了这一目的,需要定义一个函数表,表中存放不同可调用对象的指针。使用map实现。

//构建从运算符到函数指针的映射关系,其中函数接受两个int,返回一个int
map<string,int(*)(int,int)> binops;

binops.insert({"+",add});    //{"+",add}是一个pair。

但我们不能将mod或者divide存入binops
binops.insert({"%",mod});        //错误:mod不是一个函数指针

//mod是个lambda表达式,而每个lambda有它自己的类类型,该类型与存储在binops中的值的类型不匹配

 标准库function类型

 

function<int(int,int)> //function是一个模板,所谓额外的信息是指该func类型能够表示的对象的调用形式

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

f1(4,2);    //调用
使用function重新定义map:

map<string,function<int<int,int>>> binops = {

    {"+",add},                //函数指针
    {"-",std::minus<int>()},    //标准库函数对象
    {"/",divide()},             //自己定义的函数对象
    {"*",[](int i,int j){return i*j;}},    //lambda
    {"%",mod}                   //命了名的lambda

}
 
//调用
binops["+"](10,5);
binops["-"](10,5);

重载的函数与function

int add(int i,int j){}
Sales_data add(const Sales_data &,const Sales_data &){}

binops.insert({"+",add});    //错误,会产生二义性。

//解决二义性:通过存储函数指针而非 函数名字
int (*fp)(int,int) = add;
binops.insert({"+",fp});

//或者使用lambda表达式

14.9 重载、类型转换与运算符

14.9.1 类型转换运算符

operator type() const;        //        将类类型的值转换为其他类型。

类型转换可以面向任意类型进行定义,只要该类型能作为函数的返回类型,因此,我们不允许转换成数组或者函数类型,但允许转换成指针或者引用类型。

          类型转换运算符没有显示地返回类型,也没有形参,而且必须定义成类的成员函数,不应该改变待转换对象内容,所以应该定义成const。

定义含有类型转换符的类

class SmallInt{
public:
    SmallInt(int i = 0):val(i)        //int转化为类类型
    {
        if(i<0||i>255)
            throw out_of_range("Bad input");
    }

    //类型转换符
    operator int() const {return val;}    //SmallInt对象 转换为 int

private:
    size_t val;
}

SmallInt si;
si = 4;        //首先将4隐式地转换为SmallInt,然后调用SmallInt::operator=
si +3;         //首先将si隐式地转换为int,再加3

显式的类型转换运算符

class SmallInt{
    public:
        //编译器不会自动执行这一类转换
        explicit operator int() const{return val;}
    private:
        size_t val;
};

//此时
SmallInt si =3;    //正确,SmallInt的构造函数不是显式的
si+3;              //错误,此时需要隐式转换,但类类型转换是显示地
static_cast<int>(si)+3;    //正确,显示转换


但如果表达式被用作条件,则编译器会将显式的类型转换隐式的执行

if,whiledo等条件语句 for,! && || 等

后续内容不理解,用到回来补充

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值