(1)隐式转换发生的时机:当传递给
caozuofu.html" target="_blank">操作符或者函数的参数与指定类型不匹配时,编译器将会“偷偷地”进行转换,此时就是发生了隐式转换。
(2)隐式转换发生的条件:
(a)编译器原来就支持地转换:比如float型向int型转换,子类的对象(指针)向父类的对角(指针)转换,非const对象(指针)向const对象(指针)转换等等。
(b)当有
类型转换操作符的时候。如果类型匹配且有必要,编译器会自动调用它。
(c)当类有一个参数个数为1的
构造函数的时候。如果类型匹配有必要,编译器会自动调用它。
现在主要说明(b),(c)两点:
类型转换操作符,是一个拥有奇怪名字的member funtion:
关键字operator之后加上一个类型名称。如下:
class Rational
{
public:
Rational(int x,int y):m_x(x),m_y(y){}
operator double()const;//将Rational转换为double.
private:
int m_x;
int m_y;
};
Rational::operator double()const //实现
{
return static_cast<double>(m_x)/m_y;
}
如果在main函数中这样调用:
Rational rat(1,2);
cout<<rat<<endl;//打印0.5
同于编译器
发现不存在任何operator<<可以接受一个Rational类型,但它会想尽办法让函数调用成功。在本例中,编译器发现只要调用Rational::double操作符便可以将rat转换为double,调用动作便会成功。于是上述代码将rat以double输出。
当类有一个参数个数为1的构造函数的时候,也可能发生隐式类型转换。
考虑以下代码:
Template<class T>
class Array
{
public:
Array(int lowBound,int highBound);
Array(int size);
T& operator[](int index);
......
};
上述class的第一个constructor允许clients指定某个范围内的数组索。身为一个双自变量constructor,此函数没有资格成为类型转换函数。第二个constructor允许用户只指定数组的元素个数,便可以定义出Array
objects。它可以被用来作为一个类型转换函数,结果导致一些意外的结果。
例如:考虑一个用来对Array<int>对象进行比较的函数,以及一小段代码:
bool operator == (const Array<int>& lhs,const Array<int>& rhs);
Array<int> a(10);
Array<int> b(10);
......
//初始化
for(int i = 0;i < 10; ++i)
{
if( a == b[i]) //“a”应该是“a[i]”才对
{
do something ......
}
当试图将a的每一个元素拿来和b的对应元素比较时,当键入a时却意外地遗漏了下标。然而编译器却没有报错,原因是它看到一个operator ==,夹带着类型为Array<int>的自变量a和类型为int的自变量b[i],虽然没有这样的operator == 可以调用,但是编译器却注意到,只要调用Array<int> constructor(需要一个int作为自变量),它就可以将int转为Array<int> object,从而可以调用operator == (const Array<int>&
lhs,const Array<int>& rhs)。于是拿a的内容来和一个大小为b[i]的临时数组做比较,从而可能导致不可预料的结果。
(3)解决办法:
对于因类型转换操作符引起的最好办法就是提供转换的成员函数来代替转换操作符。如标准库中string类转换到char*时提供函数:c_str()。通过显式地调用成员函数而不是“默默地”调用转换操作符。
对于第二种情况说两种办法。第一是使用关键字explicit来修饰构造函数,如:
template<class T>
class Array
{
public:
......
explicit Array(int size);
......
};
这样这个构造函数便只能显式地调用了,上述if( a == b[i])语句便会报错,因为编译器不会把b[i]作为参数而调用Array(int size)这个构造函数,从而会因为“==”两边的类型不匹配而报错。
第二个办法是使用内部类。如下:
template<class T>
class Array
{
public:
class ArraySize
{
public:
ArraySize(int numElements):theSize(numElements){}
int size()const{ return theSize;}
private:
int theSize;
};
Array(int lowBound,int highBound);
Array(ArraySize size);//注意这个新的声明
......
};
在这里,把ArraySize嵌套放进Array内,用来修饰Array构造函数参数。
现在考虑当我们通过Array的“单自变量构造函数”定义一个对象时,会发生什么事情:
Array<int> a(10);
编译器被要求调用Array<int>class 中的一个自变量为int的构造函数,但其实并不存在这样的构造函数。编译器知道它能够将int自变量转换为一个临时的ArraySize对象,而该对象正是Array<int>构造函数需要的,所以编译器便执行了这样的转换。于是“以一个int自变量构造起一个Array对象”这个事实依然有效。再次考虑这段代码:
Array<int> a(10);
Array<int> b(10);
......
for(int i = 0;i < 10;++i)
if( a == b[i]) //"a"应该是“a[i]” 如今这形成了一个
错误。
编译器需要一个类型为Array<int>的对象在“==”右手边,得以针对Array<int> 对象调用operator == ,但是此刻并没有“单一自变量,类型为int”这样的构造函数。此外,编译器不能考虑将一个int转换为一个临时性的ArraySize对象,然后再根据这个临时对象产生必要的Array<int>对象,因为那将调用两个用户制定转换行为(这是不允许的),一个将int转换为ArraySize,另一个将ArraySize转换为Array<int>。如此的转换程序是禁止的,所以编译器对以上代码发出错误消息。
(4)隐式转换与临时对象
首先声明一下,这里我们所讲的临时对象,并不是指一个短暂需要的对象(如这里的temp): int temp = a,a = b,b = temp 。C++真正的临时对象是不可见的---不会出现在你的源代码中。只要你产生了一个non-heap object而没有为它命名,便诞生了一个临时对象。此等匿名对象通常发生于两种情况:一是当隐式类型转换被施行起来以求函数调用能够成功;二是当
函数返回对象的时候。这里主要讲第一种情况。看以下代码:
size_t countChar(const string& str,char ch);
char buffer[MAX_STRING_LEN];
char c;
//读入一个char和一个string
cin>>c>>setw>>(MAX_STRING_LEN)>>buffer;
cout<<"There are "<<countChar(buffer,c)<<"occurrences of the character"<<c<<" in "<<buffer<<endl;
看countChar的调用动作。其第一自变量是个char数组,但是相应的函数参数类型却是const string&。之所以会调用成功,是因为编译器以buffer作为自变量,调用string constructor。于是countChar的str参数会被绑定于此string临时对象上。当countChar返回,此临时对象会被自动销毁。这样的转换很方便,但也很
危险(上面已经说到)。
但是这样的转换还是有条件的:只有当对象以bu value(传值)方式传递,或是当对象被传递给一个reference-to-const参数时,这些转换才会发会。如果对象被传递给一个reference-to-non-const参数,并不会发生此类转换。考虑这个函数:
void uppercasify(string& str);
在上一个(计算字符个数)
例子中,我们可以成功地将一个字符数组传递给一个countChar,但是在这里,将一个字符数组交给uppercasify会导致调用失败:
char subtleBookPlug[] = "Effective C++";
uppercasify(subtleBookPlug);//错误!
不再有任何临时对象被产生出来以成全此函数的调用。
假设编译器为此产生了一个临时对象,然后此临时对象被传递给uppercasify,以便将其中的字符全部修改成大写。此函数的实元-subtleBookPlug-并未受到影响,只有以subtleBookPlug为本所产生的那个string临时对象受到影响。这当然不是
程序员所企盼的结果。程序员将subtleBookPlug传递给uppercasify,就是希望subtleBookPlug被修改。当程序员期望“非临时对象”被修改,此时如果编译器针对“references-to-non-const”对象进行隐式转换,会允许临时对象被修改。这就是为什么
C++语言禁止为non-const reference参数产生临时对象的原因。Reference-to-const参数不需要为此承担问题,因为此等参数由于const之故,无法被修改。