3.隐式类型转换
隐式类型转换会使得编译器能够在暗中修改我们的代码,我们可以通过如下两种方式来声明一个从类型F到类型T的隐式转换:
1.在T中声明一个仅接受一个类型F的参数的构造函数(包括使用的缺省参数值的构造函数,如:T::T(F,int = 0))
2.在F中声明一个operator T的转换函数
如下类Rational:
class Rational
{
public:
Rational(int ,int);
operator double();
};
double sqrt(double);
int main()
{
Rational r(4,1);
double sq = sqrt(r);
}
上面代码中,r就被隐式地转换为一个double值,就好比这样的代码:
double dval = r.operator double();
double sq = sqrt(dval);
3.1带有单个参数的构造函数
带有单个参数的构造函数同时也是一个隐式转换,如下代码:
class String
{
public:
String(const char* = "");
};
void print_heading(const String&);
print_heading("Annual Report");
在调用print_heading时,我们就会得到一个隐式的从char*到String的转换,由于String和char*在概念上来说是同一抽象模型的两种不同表示方法,所有这样做也是合理的。
class Rational
{
public:
Rational(long num = 0, long denom = 1);
};
int operator ==(const Rational&, const Rational&);
int nonzero(const Rational& r)
{
return r ==0;
}
上面的例子虽然不是很清晰,但也能让人明了0在这被隐式转换成一个Rational(z Rationald的构造函数中,第二个参数有着一个缺省值1),上面的显示版本如下:
int nonzero(const Rational& r)
{
return r ==Rational(0,1);
}
上面的代码就可以给我们一个更好的关于该函数的实际性能如何的描述,并且它还可以向我们暗示出如下的优化:
int nonzero(const Rational& r)
{
static const Rational zero(0,1);
return r ==zero;
}
这样使得我们可以节省下每次都创建和摧毁一个新的Rational对象zero所产生的开销。
下面是一个不好的隐式转换:
#include <stdlib.h>
class Random_gen
{
public:
Random_gen(long seed);
};
void play_game(Random_gen);
int main(int argc, char* argv[])
{
if(argc > 1){
play_game(atoi(argv[1]));//让人迷惑的地方
}
return 0;
}
上面的代码本不应该出现隐式转换的,一个用于产生随机数的对象和它用来产生随机数的种子(seed)应该分属不同的对象;尤其当它使用了一个long作为其构造函数参数时,它的类型就和这个种子完全不一样了。在上面的代码中,“传递给play_game的参数并不是atoi函数的输出”这个事实很容易被人所忽略。我们应该显示的调用构造函数的方式来重写:
int main(int argc, char* argv[])
{
if(argc > 1){
Random_gen gen(atoi(argv[1]));
play_game(gen);//这样就好多了
}
return 0;
}
3.2类型转换操作符
有着多个类型转换操作符的类在被使用时更容易产生编译时期的二义性。如果只有一个类型转换操作符的类如下:
class String
{
char* rep;
public:
String(const char* = “”);
operator const char*() const {return rep;}
};
int main()
{
String s(“Hello world”);
cout << s <<endl;
}
上面的代码可以正常工作,由于我们没有声明String的输出操作符,代码中的String对象就被转换成一个const char*用于输出。
如果我们往String中再添加一个类型转换操作符:
class String
{
char* rep;
public:
String(const char* = “”);
operator const char*() const {return rep;}
operator int() const {return atoi(rep);}//新增加的转换操作符
};
int main()
{
String s(“Hello world”);
cout << s <<endl;//编译期错误,因为无法确定进行哪种转换
}
因此我们不得不使用显示转换:
cout << (const char*)s;
如果想把String转换成int,我们也必须使用显示转换:
class String
{
char* rep;
public:
int as_int {return atoi(rep);}
};
void process_key_value(const String& key, const String& val)
{
if(numeric_key(key)){
int value = val.as_int();
}
}
这样明确的使用函数调用会使得程序更容易被人所理解。
3.3内建类型之间的隐式转换
对于内建类型之间的隐式转换,我们应该避开语言中的含义模糊的特性,如果需要对不同的内建类型进行不同的操作,最好是为每个整形都提供一个参数类型和它完全匹配的函数:
void output(long);
void output(unsigned long);
void output(int);
void output(unsigned int);
void output(short);
void output(unsinged short);
void output(char);
void output(unsigned char);
void output(signed char);