* 引入:
在C++中如果类型不一致进行赋值,编译则不能通过,但是编译器比较灵活,当类型不一致的时候则会去寻找是否有相应的类型转换,比如:
double b=1.0; int a=b;
显然b是double型,a是int,类型不一致但能编译运行,其原因在于在编译器内有一系列的基本类型的强制转换,在int a=b语句中,会把b转换成int型.
同时值得注意的C++是基于对象的,所有对象都是基于类的(比如这里a,则可看成为int的对象),而对于程序员自己编写的类,编译器并不能灵活的进行强制转换.所以这是 就不得不自己编写强制转换的operator了
* 赋值于初始化:
在了解对象的强制转换和隐式转换之前我们先了解两个概念: 赋值于初始化;
有人说赋值不就是”=”吗? 定义不就是”类名”+”变量名”吗? 真的是这样的吗?
下面我们来看一个例子:
int a=10; int b=a;
对于int a=10;毫无疑问为定义变量,那么对于int b=a呢? 其实这两条语句是一样的意思,可能对于int a=10;理解为定义变量并不困难,对于int b=a;其实也是一样的,我们定义了一个int型的变量b使其初始化,让其值为a(注意这里是初始化,并没有说是赋值,下面会区别这两个概念的)!
我们再看一个例子:
int b,a=10; b=a;
如果你觉得这个语句于上面的一个含义的话,那么好好再看下书本吧.
首先,跟上面一样定义了两个int型变量,并将a初始化为10,然后b=a;注意这里与上面的区别(int b=a;) 我们应该明白已经定义了两个int型变量a,b.同时对a已经初始化了,然后b=a;是将b赋值为a,而非初始化!!!
下面我们以类来说明问题:
首先类的申明:
class testA
{
public:
testA(int rhs=0); //默认构造函数
testA(const testA& rhs); //复制构造函数
~testA();
testA& operator=(const testA& rhs); //赋值运算符重载
private:
int a;
};
函数实现:
#include "testclass.h"
testA::testA( const testA& rhs ) //复制构造函数
{
a=rhs.a;
}
testA::testA( int rhs/*=NULL*/ ) //默认构造函数
{
a=rhs;
}
testA::~testA()
{
}
testA& testA::operator=( const testA& rhs ) //opetator=重载
{
a=rhs.a;
return *this;
}
同样,分析与上面类似的语句
testA A(10); testA b=a;
并在main中运行(已在main前声明类了)
int main(int argc,char **argv)
{
testA A(10);
testA B=A;
return 0;
}
并在operator=中和默认构造函数中加入断点,单步执行
我们会发现operator=并未运行;
改写成情况二
int main(int argc,char **argv)
{
testA A(10),B;
B=A;
return 0;
}
同样打入断点,我们会发现在第二条语句的时候执行了类testA的operator=;第一条语句执行了默认的构造函数.
所以我们可以总结为:
* 在定义对象时(包括基本类型),我们对其使用=是对其进行初始化,而不是赋值
* 在定义完对象后,再对其使用=则会调用相应的重载的赋值运算符(operator=)
补充:
对象中的赋值运算符重载(也就是operator=)的参数唯一,并且只能为:
testA& operator=( const testA &rhs );
* 返回testA& 便于连续赋值
* const保证rhs不会被改变
* testA &rhs使用引用形式避免了无限调用默认构造函数的死循环
也就是说其参数不能为其他类型,只能为本类对象的常引用
注意:
如果定义了testA(int rhs=0):a(rhs){};
即相当于定义了: testA(int rhs):a(rhs){}和testA(){}
* 隐式转换:
好了有了上面的基础,我们再来说说隐式转换.同样对于上面的例子,我们将main改写下:
int main(int argc,char **argv)
{
int a=10;
testA A;
A=a;
return 0
}
能够编译运行,是否会有疑问:C++中一切对象都是基于类的,很明显A和a类型并不一样,并且前面说了,对于自己编写的类,编译器并不会相应的强制转换.
但是编译器足够的聪明,但对于赋值的时候(注意是赋值,不是初始化)如果右边的类型跟左边的不一致时,编译器会去找是否有相应的转换.
这里A=a,在=的右边,会临时构建一个testA temp;并调用默认构造函数testA(int a=0);
然后再调用operator=将temp赋值给A;(同样可以在相应成员函数中打断点单步调试)
这些都是编译器自动处理的,所以称之为隐式转换
下面于上面的情况完全一致:
int main(int argc,char **argv)
{
testA A;
A=10;
return 0;
}
我们还可以试验下: 将testA中的默认构造函数注释掉,对于上面的代码会出现编译错误
因为在=的右边并没有相应的参数类型为int型的构造函数,并不能建立临时的testA temp;
* 双向隐式转换有歧义:
如果我们希望能直接对A和int型直接进行比较:
比如:
if(A>10)
Printf(“the object A>10\n”);
在VS2012上能编译运行,同样如果上面的比较运算符需要比较对象的类型一致,这里类型并不一致,这又是编译器在帮忙了.
* 解决方法1:
同前面的赋值一样,会构建临时对象(=右边) testA temp(10);
但是两个testA对象并不能进行比较啊,所以,在testA中我们不妨加入operator>:
bool operator>(const testA &rhs)
{
return a>rhs.a;
}
那么在执行,if(A>10)语句的时候就会执行默认的构造函数testA(int rhs=0);
和operator>(const testA &rhs);
* 解决方法2:
值得注意的是,赋值运算符只能是右边的类型于左边的一致,但是>和其他比较运算符那么就不一样了,只需要两边类型一致那么就可以进行比较.
编译器足够的聪明,所以在两边类型不一致时,会去查找是否有相应的转换使比较运算符两边变成同一种类型,然后进行比较.
所以我们重载运算符类型转换也是可行的:
在testA中加入成员函数:
operator int()const{
return a;
}
那么对于if(A>10)则先调用强制转换(隐式的)
(int)A(即A对象调用operator int()const;)返回对象的a(int型)然后在用基本的>对返回的值和10进行比较.
注意:
如果我们同时写了operator>(cosnt testA &rhs)和类型转换运算符operator int()const那么编译器就不能识别是将A转换成int型,再调用基本的>对int型的两变量进行比较;还是构建一个临时的testA temp(10),调用operator>(const testA &rhs);
这时编译器则报错:发现两个相似的转换函数;
这就是双向隐式转换有歧义.
* 解决方案3:
如果我们直接重写为operator>(const int rhs)
{
return a>rhs;
}
也行可行的;
总结:
* 赋值运算(注意区别初始化和赋值)符必须将=右边的对象转换成左边的类型然后调用operator=即可
* 但是比较运算符,或其他的运算符如果只需要类型一致即可,则当两边类型不一致的时候,编译器会去查找是否有相应的转换或者重载运算符,不过要特别注意双向隐式转换!!!
* explicit
隐式转换能显得编译器的灵活,因为这些转换都是隐藏的,有时在我们不经意间就发生了,所以常常好心办坏事.
这种隐式转换很难发现的,并且出错了很难找到错误在哪,如果我们不想相应的函数被隐式调用的话,在函数前加关键字 explicit,则但编译器隐式调用某些函数的时候,会报错.
eg:
explicit testA(int rhs=0):a(rhs){}
like wind
2013-11-23