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

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

重载、类型转换与运算符:
我们可以通过定义对于类类型的类型转换,通过定义类型转换运算符可以做到这点,转换构造函数和类型转换运算符共同定义了类类型转换,这样的转化有时也被称为用户自定义的类型转换。
类型转换运算符:
类型转换运算符是类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型,一般形式如下:
operator type() const;
其中type表示某种类型。类型转换运算符可以面向任意类型(除了void外)进行定义,只要该类型能作为函数的返回值,也就是说不能转换成数组或是函数类型,但允许被转换成指针(包括数组指针和函数指针)或是引用等类型。
类型转换运算符没有显式的返回类型,也没有形参,而且必须定义成类的成员函数,类型转换运算符通常不应该改变待转换对象的内容,因此被定义为const成员。
定义含有类型转换运算符的类:
假设定义一个表示0-255之间的整数的类:

class SmallInt{
public:
    SmallInt(int i = 0):val(i){
        if(i > 255 || i < 0){
            throw std::out_of_range("Bad Int");
        }
    }
    operator int() const{
        return &val;      //错误,返回值不是int
        return val;
    } 
private:
    std::size_t val;
};

以上的类中,构造函数就相当于将一个int类型的值转换成了SmallInt类类型的对象,而类型转换运算符则将SmallInt对象转换成了int类型的值。而且因为内置类型转换的存在,可以将更多算术类型传递给SmallInt的构造函数,类似的,也能将SmallInt转换成其他算术类型,例如:

SmallInt si = 3.14; //内置类型转换将double转换为int后再调用构造函数
si + 3.14;          //类型转换运算符将si转换成int,内置类型转换再将它转换为double

因为类型转换是隐式执行的,所以无法为其传递参数,因此在声明的时候也不需要定义任何形参,同时,尽管类型转换函数不负责制定返回类型,但实际上每个类型转换函数都会返回一个对应类型的值。
类型转换运算符可能产生意外结果:
在实践中,类通常很少提供类型转换运算符,一种例外情况是:对于类来说,定义bool的类型转换还是比较普遍的。
在早期的C++标准中,如果类想定义一个想bool类型的转换,会遇到一个问题:bool是一种算术类型,那么类类型在被转换成bool类型后将能用在算术运算上,比如,假设在istream中定义了向bool类型的转换,以下代码仍能通过编译:

int i = 42;
cin << i;

该程序将输出运算符用到了输入流上,因为istream本身没有定义<<,所以这段代码本来应该产生错误,但是定义的类型转换运算符将cin转换成bool,而这个bool值紧接着被转换成int类型。这样一来,这行代码相当于将一个int类型的值(这里应该是0或1)左移了42位。
显式的类型转换运算符:
为防止这样的异常情况发生,在新标准中引入了显式的类型转换运算符:

class SmallInt{
public:
    //编译器不会自动执行这一类型转换
    explicit operator int() const {return val;}
    //其他定义与之前一样
};

编译器通常不会将一个显式的类型转换运算符用于隐式转换:

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

这种情况的例外就是,当表达式出现在以下位置时,显式的类型转换将被自动隐式执行:
1.if、while以及do的条件部分;
2.for语句中的条件部分;
3.逻辑非运算符(!)、逻辑或运算符(||)、逻辑与运算符(&&)的运算对象;
4.条件运算符(? :)的条件表达式。
转换为bool:
当我们在条件中使用流对象时,都会使用IO类型定义的operator bool。例如:

while(cin >> value)

while语句的条件执行输入操作,它负责将数据读入到value中,并返回cin。为了对条件求值,cin被istream operator bool隐式的执行转换。如果cin的条件状态是good,则函数返回为真,否则返回为假。

避免有二义性的转换:
如果类中包含一个或多个类型转换,则必须确保在类类型和目标类型之间只存在唯一的一种转换方式。
有两种情况可能会造成二义性:第一种情况是两个类提供相同的类型转换。例如在A类中有将B类转换为A类的构造函数,在B类中也有将B类转换成A类的类型转换运算符函数,例如:

struct B;
struct A{
    A() = default;
    A(const B&);           //能将B转换成A
    ...
};
struct B{
    operator A() const;    //也能将B转换成A
    ...
};

A a(const A&);
B b;
A c = a(b);                 //在这里发生二义性错误,不知道应该是
                            //a(B::operator A()),还是
                            //a(A::A(const B&))

//不得不使用显示来调用类型转换运算符或构造函数
A a1 = a(b.operator A());
A a2 = a(A(b));

第二种是类定义了多个类型转换规则,而这些转换设计的类型本身可以通过其他类型联系在一起。最典型的例子是算术运算符,对某个给定的类来说,最好只定义最多一个与算术类型有关的转换规则。例如:

struct A{
    A(int i = 0);       //最好不要定义多个转换源都是算术类型的类型转换
    A(double);
    operator int() const;   //最好不要定义多个转换对象都是算术类型的类型转换
    operator double() const;
    ...
};
void f(long double);
A a1;
f(a1);          //发生二义性错误,不知道是让a1转换成int还是double

long l;
A a2(l);        //发生二义性错误,不知道该调用A(int)还是A(double)

这里不论是long double还是long都不能被A精确匹配,而不论int还是double都需要调用内置的类型转换,并且他们所需的转换级别相同,编译器无法分辨出优劣,产生了二义性,如果把long换成short,则会优先匹配A(int),因为把short提升为int优于把short转换为double的操作。

重载函数与转换构造函数:
当我们调用重载的函数时,从多个类型转换中进行选择将变得更为复杂。因为当两个或多个类型转换都提供了同一种可行匹配时,这些类型转换往往一样好。例如:

struct A{
    A(int);
    ...
};
struct B{
    B(int);
    ...
};
//重载函数的参数为不同类型的类,但这两个类都有同样的转换构造函数
void f(const A&);       
void f(const B&);

f(10);              //二义性错误,不知道是f(C(10))还是f(D(10))
f(C(10));           //通过显式地构造正确类型消除二义性

重载函数与用户定义的类型转换:
当调用重载函数做类型转换时,如果两个或多个用户定义的类中都有可行的类型转换,也会出现二义性,比如:

struct A{
    A(int);
    ...
};
struct B{
    B(double);
    ...
};
void mapip2(const A&);
void mapip2(const B&);

mapip2(10);             //二义性错误,不知道应该是mapip2(A(10))
                        //还是mapip2(B(double(10)))

即使10看起来和int是精确匹配的,但编译器还是会将该调用标记为错误。
函数匹配和重载运算符:
重载运算符也是重载的函数,所以通用的函数匹配规则也同样适用于判断在给定的表达式中到底是使用内置运算符还是重载的运算符。当然这也有可能带来二义性错误,比如:

class SmallInt{
    friend SmallInt operator+(const SmallInt&,const SmallInt&);
public:
    SmallInt(int i = 0);                 //转换源为int的类型转换
    operator int() const { return val; } //转换目标为int的类型转换
private:
    size_t val;
};

SmallInt s1,s2;
SmallInt s3 = s1 + s2;                  //正确,调用operator+
int i = s3 + 1;                         //二义性错误

第一条加法语句接受两个SmallInt值并执行+运算符的重载版本。第二条语句具有二义性:因为可以将s3转换成int,然后执行内置的整数相加;或者把1转换成SmallInt,然后使用重载的+。

猜你喜欢

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