浅析隐式转换

来源:互联网 发布:淘宝u站怎么加入 编辑:程序博客网 时间:2024/05/16 19:14

引入:

C++中如果类型不一致进行赋值,编译则不能通过,但是编译器比较灵活,当类型不一致的时候则会去寻找是否有相应的类型转换,比如:

double b=1.0; int a=b;

显然bdouble,aint,类型不一致但能编译运行,其原因在于在编译器内有一系列的基本类型的强制转换,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::testAconst testArhs ) //复制构造函数

{

a=rhs.a;

}

 

testA::testAint rhs/*=NULL*/ ) //默认构造函数

{

a=rhs;

}

 

testA::~testA()

{

}

 

testAtestA::operator=( const testArhs ) //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;

}

同样打入断点,我们会发现在第二条语句的时候执行了类testAoperator=;第一条语句执行了默认的构造函数.

所以我们可以总结为:

在定义对象时(包括基本类型),我们对其使用=是对其进行初始化,而不是赋值

在定义完对象后,再对其使用=则会调用相应的重载的赋值运算符(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++中一切对象都是基于类的,很明显Aa类型并不一样,并且前面说了,对于自己编写的类,编译器并不会相应的强制转换.

但是编译器足够的聪明,但对于赋值的时候(注意是赋值,不是初始化)如果右边的类型跟左边的不一致时,编译器会去找是否有相应的转换.

这里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;

双向隐式转换有歧义:

如果我们希望能直接对Aint型直接进行比较:

比如:

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

原创粉丝点击