C++ Primer第十四章③
C++ Primer
重载运算与类类型转换
重载、类型转换与运算符
我们先来看看一个叫转换构造函数的东西,其实是老朋友了:
string a = "haha";
Sales_data b;
b.combine(a); //combine接受一个Sales_data对象,但是一个string也行,因为隐式转换了
其实编译器用这个string构造了一个临时的Sales_data对象,用的就是只接受一个string的构造函数,所以搞了个定义叫转换构造函数。
我也有点忘了,权当复习了。
好了,现在来学点新的:
转换构造函数和类型转换运算符共同定义了类类型转换,也叫用户定义的类型转换。
类型转换运算符
是类的一种特殊成员函数,规定也比较多,类型转换函数的形式如下:
operator type() const;
其中type表示某种类型,这种类型只要是能作为函数返回值的就行,也就是说数组类型和函数类型不行,但是可以有指针和引用。
类型转换运算符没有显式的返回类型,没有形参,而且还必须定义成类的成员函数;它不改变待转换对象的内容,因此一般可以被定义为const
定义含有类型转换运算符的类
我们来举个例子,我们定义一个比较简单的类,只能表示0-255之间的整数:
class SmallInt
{
public:
//构造函数
SmallInt(int i = 0) : val(i) //默认实参,初始化列表
{
if(i<0 || i>255){throw std::out_of_range("不要");}
}
//类型转换运算符
operator int() const {return val;}
private:
std::size_t val;
};
我们来用用:
SmallInt si; //si = 0
si + 4; //将si通过类型转换运算符,转换成int
si = 3; //将3隐式转换为SmallInt,然后调用编译器合成的拷贝赋值运算符
SmallInt b = 3.14; //内置类型double先转成int,再调用构造函数
b + 3.14; //b先通过类型转换运算符转换为int,然后int变double
尽管类型转换函数不负责指定返回类型,但实际上每个类型转换函数都会返回一个对应类型的值。
显式的类型转换运算符
为了防止这种转换莫名其妙,我们采用了这样一个概念:
class SmallInt
{
public:
explicit operator int() const {return val}; //多了个关键字
};
现在我们要调用这个类型转换运算符就没那么容易了,必须要亲自指出来才行:
si + 3; //错误
static_cats<int>(si) + 3; //正确:显示地调用函数请求转换
但是有个例外,如果该表达式作为条件的话,这个类型转换还是会自动发生的。
避免有二义性的转换
我们来通过两个例子看一下什么是二义性的转换:
struct B; //声明一个类
struct A
{
A() = default; //强制合成默认构造函数
A(const &B); //转换构造函数B->A
};
struct B //类B的定义
{
operator A() const; //类型转换运算符B->A
};
经过精心定义这两个类之后,我们来看看怎么调用会产生二义性:
A f(const A&); //声明了一个函数,参数为A返回类型也为A
B b;
A a = f(b); //这句话的分析就稍微复杂些
//一种可能是,我通过转换构造函数把参数b直接转换为类型A
//还有一种可能是我通过类型转换运算符把参数b转换为类型A
这两个函数很可能不一样,所以最后的结果也会不同,如果我们想避免这种情况,要么你在类定义的时候就注意,要么就像下面一样显式调用:
A a1 = f(b.operator A()); //调用B的类型转换运算符
A a2 = f(A(b)); //调用A的构造函数
另一个二义性的例子
我们来构造一个不太好的类:
Struct A
{
A(int = 0);
A(double); //转换构造函数,但是俩转换原都是算术类型,这不太好
};
long lg;
A a(lg); //不知道用哪个转换构造函数(类型转换运算符也一样)
重载函数与转换构造函数
来个复杂的:当几个重载函数的参数分属不同的类类型时,这些类恰好也定义了同样的转换构造函数:
struct C
{
C(int);
};
struct D
{
D(int);
};
void manip(const C&); //函数声明
void manip(const D&);
manip(10); //二义性错误:不知道把10转换为C还是D
//可以显式调用
manip(C(10));
//即使是这样的也会二义性
struct E
{
E(double); //因为int到double是标准库转换的,
//很自然,所以还是无法区分
};
函数匹配与重载运算
还是抛代码看:
class SmallInt
{
friend SmallInt operator+
(const SmallInt&, const SmallInt&);
public:
SmallInt(int = 0); //转换源为int的转换构造函数
operator int() const {return val;}
//转换目标为int的类型转换运算符
private:
size_t val;
};
来来来,二义性搞起:
SmallInt s1, s2;
SmallInt s3 = s1 + s2; //调用重载的operator+
int i = s3 + 0; //二义性
//一种是s3转成int
//另一种是0转成SmallInt,把结果再转成int
总之,搞复杂了就要留心二义性,一般你注意我提到的几种情况就差不多了。