重载运算符和转换

--转换与类类型【上】

引言:

在前面我们提到过:能够用一个实參调用的非explicit构造函数定义一个隐式转换。当提供了实參类型的对象须要一个类类型的对象时。编译器将使用该转换。

于是:这样的构造函数定义了到类类型的转换。

除了定义到类类型的转换之外,还能够定义从类类型到其它类型的转换。即:我们能够定义转换操作符,给定类类型的对象。操作符将产生其它类型的对象。和其它转换一样,编译器将自己主动应用这个转换。

一、转换为什么实用?

定义一个SmallInt的类,该类实现安全小整数,这个类将使我们能够定义对象以保存与 8位 unsignedchar 相同范围的值,即:0到255。

这个类能够捕获下溢和上溢错误,因此使用起来比内置unsignedchar 更安全

我们希望这个类定义unsignedchar 支持的全部操作。详细而言,我们想定义5个算术操作符(+、-、*、/、%)及其相应的复合赋值操作符,4个关系操作符(<、<=、>、>=),以及相等操作符(==、!=)。显然,须要定义16个操作符%>_<%。

1、支持混合类型表达式

并且,我们希望能够在混合模式表达式中使用这些操作符。比如,应该能够将两个SmallInt对象相加,也能够将随意算术类型加到SmallInt。

通过为每一个操作符定义三个实例来达到目标:

int operator+(int,const SmallInt &);
int operator+(const SmallInt &,int);
SmallInt operator+(const SmallInt &,const SmallInt &);

可是。这个设计仅仅接近内置整数运算的行为,它不能适用于处理浮点类型的混合模式,也不能适当支持longunsignedintunsignedlong的加运算

2、转换降低所需操作符的数目

C++提供了一种机制:一个类能够定义自己的转换,应用于其类类型对象。对SmallInt而言,能够定义一个从SmallInt到 int类型的转换。假设定义了该转换,则无须再定义不论什么算术、关系或相等操作符(不然要定义48个!。给定到int的转换,SmallInt对象能够用在不论什么可用int值的地方。

假设存在一个到int的转换,则下面代码:

    SmallInt si(3);
/**能够存在这样的转换:
*1. 将si转换成为int值
*2. 将所得 int 结果转换为 double 值并与双精度字面值常量 3.14159 相加,
* 得到 double 值
*/
si + 3.1415926;

二、转换操作符

转换操作符是一种特殊的类成员函数((⊙o⊙)真的非常特殊!):它定义将类类型值转变为其它类型值的转换。转换操作符在类定义体内声明,在保留字operator之后紧跟着转换的目标类型:

class SmallInt
{
public:
SmallInt(int i = 0):val(i)
{
if (i < 0 || i > 255)
{
throw std::out_of_range("Bad SmallInt initializer");
}
} operator int() const
{
return val;
} private:
std::size_t val;
};

转换函数採用例如以下通用形式:

    operator type();

这里,type表示内置类型名、类类型名或由类型别名所定义的名字。

对不论什么可作为函数返回类型的类型(除了void之外)都能够定义转换函数。一般而言,不同意转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用类型是能够的。

【注意】

转换函数必须是成员函数,不能指定返回类型,并且形參表必须为空。

尽管转换函数不能指定返回类型,可是每一个转换函数必须显式返回一个指定类型的值。比如,operatorint 返回一个int值;假设定义operatorSales_item,它将返回一个Sales_item对象,诸如此类。

【最佳实践】

转换函数一般不应该改变被转换的对象。因此,转换操作符通常定义为const成员。

1、使用类类型转换

仅仅要存在转换,编译器将在能够使用内置转换的地方自己主动调用它

1)在表达式中:

    SmallInt si;
double dval;
si >= dval; //si转换为int,然后它们转换成为double

2)在条件中:

    if (si) //si转换为int,然后它们转换成为bool
{
//...
}

3)将实參传给函数或从函数返回值:

    int calc(int);
SmallInt si;
calc(si); //si转换为int。然后调用函数calc

4)作为重载操作符的操作数:

    cout << si << endl;   //si转换为int。然后调用opreator<<

5)在显式类型转换中:

    int ival;
SmallInt si = 3.14;
//显式将si转换成为int
ival = static_cast<int>(si) + 3;

2、类类型转换和标准转换

使用转换函数时,被转换的类型不必与所须要的类型全然匹配。必要时能够在类类型转换之跟上标准转换以获得想要的类型。

    SmallInt si;
double dval;
si >= dval; //si转换成为int然后标准转换成为double

3、仅仅能应用一个类类型转换

类类型转换之后不能再跟还有一个类类型转换。假设须要多个类类型转换,则代码出错!

class Intergral
{
public:
Intergral(int i):val(i){}
operator SmallInt () const
{
return val % 256;
} private:
std::size_t val;
};

能够在须要SmallInt的地方使用Intergral。但不能在须要int的地方使用Intergral:

    int calc(int);
Intergral intVal;
SmallInt si(intVal); //OK:intVal转换成为SmallInt
int i = calc(si); //OK:si转换成为int
int j = call(intVal); //Error

在最后的 calc调用中:没有从Integral到 int的直接转换。int须要两次类类型转换:首先从Integral到 SmallInt,然后从SmallInt到int。

可是,语言仅仅同意一次类类型转换,所以该调用出错

4、标准转换可放在类类型转换之前

使用构造函数运行隐式转换的时候,构造函数的形參类型不必与所提供的类型全然匹配。

    void calc(SmallInt);
short sobj;
/*
*调用 SmallInt类中定义的构造函数(SmallInt(int)),
*将 sobj 转换为 SmallInt 类型
*/
calc(sobj);

假设须要,在调用构造函数运行类类型转换之前,可将一个标准转换序列应用于实參。

为了调用函数calc(),应用标准转换将dobj从 double类型转换为int类型,然后调用构造函数SmallInt(int)将转换结果转换为SmallInt类型。

    void calc(SmallInt);
double dval;
calc(dval);
//作用等同于
//calc(static_cast<int>(dval));
//P457 习题14.40
class Sales_item
{
public:
Sales_item(const std::string &book = ""):
isbn(book), units_sold(0), revenue(0.0) {} /**
*事实上定义string和double的转换操作符并非一个好办法
*由于一般不必在须要string和double的地方使用Sales_item对象
*/ operator string () const
{
return isbn;
}
operator double () const
{
return revenue;
}
//As before... private:
std::string isbn;
unsigned units_sold;
double revenue; };

//习题14.42
class CheckoutRecord
{
public:
typedef unsigned Date; operator bool () const
{
return wait_list.empty();
}
//As Before... private:
//As Before...
vector< pair<string,string> * > wait_list;
};

三、实參匹配和转换

尽管类类型转换可能是实现和使用类的一个优点,但类类型转换也可能是编译时错误的一大来源。当从一个类型转换到还有一个类型有多种方式时,假设有几个类类型转换能够使用,编译器必须决定对给定表达式使用哪一个

【小心地雷】

假设小心使用,类类型转换能够大大简化类代码和用户代码。假设使用得太过自由随意。类类型转换会产生令人迷惑的编译时错误,这些错误难以理解并且难以避免!

1、实參匹配和多个转换操作符

class SmallInt
{
public:
//从int/double转换到SmallInt
SmallInt(int = 0);
SmallInt(double); //从SmallInt转换到int/double
operator int() const
{
return val;
}
operator double () const
{
return val;
} private:
std::size_t val;
};

【小心地雷】

一般而言。给出一个类两个内置类型之间的转换是不好的做法!

考虑最简单的调用非重载函数的情况:

    void compute(int);
void fp_compute(double);
void extended_compute(long double); SmallInt si;
compute(si); //OK
fp_compute(si); //OK
extended_compute(si); //Error

在这段程序中,任一转换操作符都可用于compute调用中:

1)operatorint 产生对形參类型的全然匹配。

2)首先调用operatordouble 进行转换,后跟从double到 int的标准转换与形參类型匹配。

由于全然匹配转换比须要标准转换的其它转换更好。因此,第一个转换序列更好,选择转换函数SmallInt::operatorint()来转换实參。

在第二个调用中,可用任一转换调用fp_compute。

可是,到double的转换是一个全然匹配,不须要额外的标准转换。

最后一个对extended_compute的调用有二义性

能够使用任一转换函数,但每一个都必须跟上一个标准转换来获得longdouble,因此,没有一个转换比其它的更好,调用具有二义性

【小结】

假设两个转换操作符都可用在一个调用中,并且在转换函数之后存在标准转换,则依据该标准转换的类别选择最佳匹配!

2、实參匹配和构造函数转换

正如可能存在两个转换操作符,也可能存在两个构造函数能够用来将一个值转换为目标类型。

    void manip(const SmallInt &);

    double d;
int i;
long l;
manip(d); //OK
manip(i); //OK
manip(l); //Error

在第一个调用中,能够用任一构造函数将d转换为 SmallInt类型的值:int构造函数须要对d的标准转换,double构造函数全然匹配。由于全然匹配比标准转换更好,所以用构造函数SmallInt(double)进行转换。

在第二个调用中,构造函数SmallInt(int)提供全然匹配,调用接受一个double參数的 SmallInt构造函数须要首先将i转换为 double类型。

对于这个调用,则编译器更喜欢使用int构造函数转换实參。

第三个调用具有二义性。没有构造函数全然匹配于long。使用每一个构造函数之前都须要对实參进行转换:

1)标准转换(从long到 double)后跟SmallInt(double)。

2)标准转换(从long到 int)后跟SmallInt(int)。

这些转换序列是不能差别的,所以该调用具有二义性。

【小结】

当两个构造函数定义的转换都能够使用时,假设存在构造函数实參所需的标准转换,就用标准转换的类型选择最佳匹配

3、当两个类定义了转换时的二义性

当两个类定义了相互转换时。非常可能存在二义性:

class Integral;
class SmallInt
{
public:
SmallInt(Integral);
//...
}; class Integral
{
public:
operator SmallInt() const;
//...
}; void compute(SmallInt); Integral int_val;
/**
*Error:调用有二义性
*可是有些编译器还是检測不出来的,比方我所測试的g++编译器
*/
compute(int_val);

实參int_val能够用两种不同的方式转换成为SmallInt对象,编译器能够接受SmallInt对象的构造函数(此处中文版翻译有误,原文为:...Thecompiler could use the SmallInt constructor that takes and Integralobject or it could use the Integral conversion operation thatconverts an Integral to aSmallInt...。所以应该翻译为SmallInt对象的构造函数。而不是Integral对象的构造函数)。也能够使用Integral对象转换为SmallInt对象的Integral转换操作。

由于这两个函数没有高下之分,所以这个调用会出错

在这样的情况下,不能用显式类型转换来解决二义性—— 显式类型转换本身既能够使用转换操作又能够使用构造函数,相反,须要显式调用转换操作符或构造函数:

    compute(SmallInt(int_val)); //OK
compute(int_val.operator SmallInt()); //OK

并且,由于某些似乎微不足道的原因,我们觉得可能有二义性的转换是合法的。比如,SmallInt类构造函数复制它的Integral实參,假设改变构造函数以接受constIntegral 引用:

class SmallInt
{
public:
SmallInt(const Integral &);
//...
};

则对 compute(int_val)的调用不再有二义性!原因在于使用SmallInt构造函数须要将一个引用绑定到int_val,而使用Integral类的转换操作符能够避免这个额外的步骤。这一小小差别足以使我们倾向于使用转换操作符。

【最佳实践】

避免二义性最好的方法是避免编写互相提供隐式转换成对的类

【警告:避免转换函数的过度使用 P460非常精彩的讲述!】

避免二义性最好的方法是,保证最多仅仅有一种途径将一个类型转换为还有一类型。做到这点,最好的办法是限制转换操作符的数目,尤其是,到一内置的类型,你应该只有一个转换。

版权声明:本文博客原创文章,博客,未经同意,不得转载。

C++ Primer 学习笔记_63_重载运算符和转换 --转换和类类型【上】的更多相关文章

  1. C++ Primer 学习笔记_60_重载操作符与转换 --赋值、下标、成员訪问操作符

    重载操作符与转换 --赋值.下标.成员訪问操作符 一.赋值操作符 类赋值操作符接受类类型形參,通常该形參是对类类型的const引用,但也能够是类类型或对类类型的非const引用.假设未定义这个操作符, ...

  2. C++ Primer 学习笔记_62_重载操作符与转换 --调用操作符和函数对象

    重载操作符与转换 --调用操作符和函数对象 引言: 能够为类类型的对象重载函数调用操作符:一般为表示操作的类重载调用操作符! struct absInt { int operator() (int v ...

  3. C++ Primer 学习笔记_69_面向对象编程 --继承情况下的类作用域

    面向对象编程 --继承情况下的类作用域 引言: 在继承情况下,派生类的作用域嵌套在基类作用域中:假设不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义. 正是这样的类作用域的层次嵌套使 ...

  4. C++ Primer 学习笔记_61_重载操作符与转换 --自增/自减操作符

    重载操作符与转换 --自增/自减操作符 引言: 自增,自减操作符常常由诸如迭代器这种类实现,这种类提供相似于指针的行为来訪问序列中的元素.比如,能够定义一个类,该类指向一个数组并为该数组中的元素提供訪 ...

  5. C++ Primer学习笔记(三) C++中函数是一种类型!!!

    C++中函数是一种类型!C++中函数是一种类型!C++中函数是一种类型! 函数名就是变量!函数名就是变量!函数名就是变量! (---20160618最新消息,函数名不是变量名...囧) (---201 ...

  6. C++ Primer学习笔记(二)

    题外话:一工作起来就没有大段的时间学习了,如何充分利用碎片时间是个好问题. 接  C++ Primer学习笔记(一)   27.与 vector 类型相比,数组的显著缺陷在于:数组的长度是固定的,无法 ...

  7. C#.NET学习笔记7--11---算术运算符,变量赋值,变量的交换,布尔表达式1,布尔表达式2

    C#.NET学习笔记7---算术运算符 2013/9/6 技术qq交流群:JavaDream:251572072  教程下载,在线交流:创梦IT社区:www.credream.com 1.Consol ...

  8. Go语言学习笔记四: 运算符

    Go语言学习笔记四: 运算符 这章知识好无聊呀,本来想跨过去,但没准有初学者要学,还是写写吧. 运算符种类 与你预期的一样,Go的特点就是啥都有,爱用哪个用哪个,所以市面上的运算符基本都有. 算术运算 ...

  9. JavaScript:学习笔记(6)——New运算符

    JavaScript:学习笔记(6)——New运算符 new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例. 快速开始 当你使用new关键字的时候,会 创建一个新的对象 将th ...

随机推荐

  1. HDU 4832(DP+计数问题)

    HDU 4832 Chess 思路:把行列的情况分别dp求出来,然后枚举行用几行,竖用几行.然后相乘累加起来就是答案 代码: #include <stdio.h> #include < ...

  2. zepto.js 源码注释备份

    /* Zepto v1.0-1-ga3cab6c - polyfill zepto detect event ajax form fx - zeptojs.com/license */ ;(funct ...

  3. 怎么样sourceforge开源项目发现,centos安装-同htop安装案例

    一个.htop什么? top是linux下经常使用的监控程序.htop相当于其加强版,颜色显示不同參数.且支持鼠标操作. 详细介绍參看此说明文档. watermark/2/text/aHR0cDovL ...

  4. Bye,IE!服务互联网20年IE终于要退役了

    美国当地时间16日中午,Microsoft Edge官微发表了祝词:Internet Explorer 20岁生日快乐!你在过去做出了巨大贡献,今后由我继续发扬光大.服务互联网20年之后,IE终于要退 ...

  5. 深入理解Linux修改hostname(转)

    当我觉得对Linux系统下修改hostname已经非常熟悉的时候,今天碰到了几个个问题,这几个问题给我好好上了一课,很多知识点,当你觉得你已经掌握的时候,其实你了解的还只是皮毛.技术活,切勿浅尝则止! ...

  6. SQLServer表变量对IO及内存影响测试

    原文:SQLServer表变量对IO及内存影响测试 1. 测试创建表变量对IO的影响 测试创建表变量前后,tempdb的空间大小,目前使用sp_spaceused得到大小,也可以使用视图sys.dm_ ...

  7. cygwin的163镜像(转)

    国内的cygwin源镜像: 1.163源 http://mirrors.163.com/.help/cygwin.html 收录架构 x86 x86_64 收录版本 所有版本 更新时间 每天更新一次 ...

  8. SyntaxHighlighter代码高亮插件

    SyntaxHighlighter它是Google Code在一个开源项目,主要用于对代码着色页, 使用十分方便,效果也不错,并且差点儿支持常见的全部语言. 使用步骤: 一.下载并解压缩SyntaxH ...

  9. js中不同的height, top的对比

    每次看到js中的clientHeight(clientTop), offsetHeight(offsetTop),scrollHeight(scrollTop)就头大,根本分不清这几种的区别,然而碰到 ...

  10. JQuery插件开发初探——结构熟悉

    工作之余,对Jquery插件做了一点尝试,想着之前总用别人写的插件,自己要是也写一个用岂不是很cool.于是说干就干,动手开始写. 首先是模仿,从一个简单的功能进行入手,了解一下插件开发的流程和结构. ...