转换构造函数和类型转换函数的作用是相反的:转换构造函数会将其它类型转换为当前类类型,类型转换函数会将当前类类型转换为其它类型。如果没有这两个函数,Complex 类和 int、double、bool 等基本类型的四则运算、逻辑运算都将变得非常复杂,要编写大量的运算符重载函数。
但是,如果一个类同时存在这两个函数,就有可能产生二义性。下面以 Complex 类为例来演示:
#include <iostream>
using namespace std;
//复数类
class Complex
{
public:
Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ } //包含了转换构造函数
public:
friend ostream & operator<<(ostream &out, Complex &c);
friend Complex operator+(const Complex &c1, const Complex &c2);
operator double() const { return m_real; } //类型转换函数
private:
double m_real; //实部
double m_imag; //虚部
};
//重载>>运算符
ostream & operator<<(ostream &out, Complex &c)
{
out << c.m_real <<" + "<< c.m_imag <<"i";;
return out;
}
//重载+运算符
Complex operator+(const Complex &c1, const Complex &c2)
{
Complex c;
c.m_real = c1.m_real + c2.m_real;
c.m_imag = c1.m_imag + c2.m_imag;
return c;
}
int main()
{
Complex c1(24.6, 100);
double f = c1; //①正确,调用类型转换函数
c1 = 78.4; //②正确,调用转换构造函数
f = 12.5 + c1; //③错误,产生二义性
Complex c2 = c1 + 46.7; //④错误,产生二义性
return 0;
}
①和②是正确的,相信大家很容易理解。
对于③,进行加法运算时,有两种转换方案:
1、第一种方案是先将 12.5 转换为 Complex 类型再运算,这样得到的结果也是 Complex 类型,再调用类型转换函数就可以赋值给 f 了。
2、第二种方案是先将 c1 转换为 double 类型再运算,这样得到的结果也是 double 类型,可以直接赋值给 f。
很多读者会认为,既然=
左边是 double 类型,很显然应该选择第二种方案,这样才符合“常理”。其实不然,编译器不会根据=
左边的数据类型来选择转换方案,编译器只关注12.5 + c1
这个表达式本身,站在这个角度考虑,上面的两种转换方案都可以,编译器不知道选择哪一种,所以会抛出二义性错误,让用户自己去解决。
当然,你也可以认为编译器不够智能,没有足够强大的上下文(周边环境)推导能力。反过来说,即使我们假设编译器会根据=
左边的数据类型来选择解决方案,那仍然会存在二义性问题,下面就是一个例子:
Complex c1(24.6, 100);
cout<<c1 + 46.7<<endl;
该语句没有将c1 + 46.7
的结果赋值给其他变量,而是直接输出,这种情况应该将 c1 转换成 double 类型呢,还是应该将 46.7 转换成 Complex 类型呢?很明显都可以,因为转换构造函数和类型转换函数是平级的,没有谁的优先级更高,所以该语句也会产生二义性错误。
解决二义性问题的办法也很简单粗暴,要么只使用转换构造函数,要么只使用类型转换函数。实践证明,用户对转换构造函数的需求往往更加强烈,这样能增加编码的灵活性,例如,可以将一个字符串字面量或者一个字符数组直接赋值给 string 类的对象,可以将一个 int、double、bool 等基本类型的数据直接赋值给 Complex 类的对象。
那么,如果我们想把当前类类型转换为其它类型怎么办呢?很简单,增加一个普通的成员函数即可,例如,string 类使用 c_str() 函数转换为 C 风格的字符串,complex 类使用 real() 和 imag() 函数来获取复数的实部和虚部。
complex 是 C++ 标准库中的复数类,c
是小写的,使用时需要引入complex
头文件。Complex 是我们为了教学而自定义的复数类,C
是大写的,Complex 类尽量模拟 complex 类。
下面是重新编写的 Complex 类,该类只使用了转换构造函数,没有使用类型转换函数,取而代之的是 real() 和 imag() 两个普通成员函数。一个实用的 Complex 类能够进行四则运算和关系运算,需要重载 +、-、*、/、+=、-=、*=、/=、==、!= 这些运算符,不过作为教学演示,这里仅仅重载了 +、+=、==、!= 运算符,其它运算符的重载与此类似。
#include <iostream>
using namespace std;
//复数类
class Complex
{
public: //构造函数
Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ } //包含了转换构造函数
public: //运算符重载
//以全局函数的形式重载
friend ostream & operator<<(ostream &out, Complex &c);
friend istream & operator>>(istream &in, Complex &c);
friend Complex operator+(const Complex &c1, const Complex &c2);
friend bool operator==(const Complex &c1, const Complex &c2);
friend bool operator!=(const Complex &c1, const Complex &c2);
//以成员函数的形式重载
Complex & operator+=(const Complex &c);
public: //成员函数
double real() const{ return m_real; }
double imag() const{ return m_imag; }
private:
double m_real; //实部
double m_imag; //虚部
};
//重载>>运算符
ostream & operator<<(ostream &out, Complex &c)
{
out << c.m_real <<" + "<< c.m_imag <<"i";;
return out;
}
//重载<<运算符
istream & operator>>(istream &in, Complex &c)
{
in >> c.m_real >> c.m_imag;
return in;
}
//重载+运算符
Complex operator+(const Complex &c1, const Complex &c2)
{
Complex c;
c.m_real = c1.m_real + c2.m_real;
c.m_imag = c1.m_imag + c2.m_imag;
return c;
}
//重载+=运算符
Complex & Complex::operator+=(const Complex &c)
{
this->m_real += c.m_real;
this->m_imag += c.m_imag;
return *this;
}
//重载==运算符
bool operator==(const Complex &c1, const Complex &c2)
{
if( c1.m_real == c2.m_real && c1.m_imag == c2.m_imag )
{
return true;
}
else
{
return false;
}
}
//重载!=运算符
bool operator!=(const Complex &c1, const Complex &c2)
{
if( c1.m_real != c2.m_real || c1.m_imag != c2.m_imag )
{
return true;
}
else
{
return false;
}
}
int main()
{
Complex c1(12, 60);
cout<<"c1 = "<<c1<<endl;
//先调用转换构造函数将 22.8 转换为 Complex 类型,再调用重载过的 + 运算符
Complex c2 = c1 + 22.8;
cout<<"c2 = "<<c2<<endl;
//同上
Complex c3 = 8.3 + c1;
cout<<"c3 = "<<c3<<endl;
//先调用转换构造函数将 73 转换为 Complex 类型,再调用重载过的 += 运算符
Complex c4(4, 19);
c4 += 73;
cout<<"c4 = "<<c4<<endl;
//调用重载过的 += 运算符
Complex c5(14.6, 26.2);
c5 += c1;
cout<<"c5 = "<<c5<<endl;
//调用重载过的 == 运算符
if(c1 == c2)
{
cout<<"c1 == c2"<<endl;
}
else
{
cout<<"c1 != c2"<<endl;
}
//先调用转换构造函数将 77 转换为 Complex 类型,再调用重载过的 != 运算符
if(c4 != 77)
{
cout<<"c4 != 77"<<endl;
}
else
{
cout<<"c4 == 77"<<endl;
}
//将 Complex 转换为 double,没有调用类型转换函数,而是调用了 real() 这个普通的成员函数
double f = c5.real();
cout<<"f = "<<f<<endl;
return 0;
}
运行结果:
c1 = 12 + 60i
c2 = 34.8 + 60i
c3 = 20.3 + 60i
c4 = 77 + 19i
c5 = 26.6 + 86.2i
c1 != c2
c4 != 77
f = 26.6