C++中提供了转换函数,用于将本类对象转换成另一类型的对象。其语法为
class CTestClass
{
operator 待转换类型() const
{
//函数体
return (待转换类型)xxx;
}
}
从中可以看出:
- 该转换函数不需要指明返回值类型(已经在operator后说明了)
- 该函数被标记为const,说明其不改变成员变量(毕竟是转换而已)
- 该函数无参。
- 一个类可有多个转换函数,将其转换到不同类型。
一个简单的例子
下面举一个最简单的使用例子:
#include <iostream>
using namespace std;
class CFraction
{
private:
int m_upper;
int m_lower;
public:
CFraction(int upper, int lower = 1) : m_upper(upper), m_lower(lower) { }
double GetValue() const
{
return (double)m_upper / (double)m_lower;
}
operator double() const
{
return GetValue();
}
};
int main()
{
CFraction f1(3, 4);
double d = 4.0 + f1;
cout << d << endl; //结果为4.75
return 0;
}
在上例中,当编译到double d = 4.0 + f1一句时,编译器将做如下尝试:
有没有重载+运算符,其参数从左到右分别为double和CFraction类型。很可惜,这里没有。
能否考虑把CFraction类型的f1转换为double类型,然后参与运算。于是这里CFraction::operator double() const函数就派上用场了。
当+运算符与转换函数同时可用时
在上面例子的基础上,我们稍加改动,加入一个全局的重载+运算符的函数。
#include <iostream>
using namespace std;
class CFraction
{
private:
int m_upper;
int m_lower;
public:
CFraction(int upper, int lower);
double GetValue() const;
operator double() const;
};
double operator+ (const double opr1, const CFraction& opr2)
{
return opr1 + opr2.GetValue() + 22;
}
CFraction::CFraction(int upper, int lower = 1) : m_upper(upper), m_lower(lower) { }
double CFraction::GetValue() const
{
return (double)m_upper / (double)m_lower;
}
CFraction::operator double() const
{
return GetValue();
}
int main()
{
CFraction f1(3, 4);
double d = 4.0 + f1;
cout << d << endl; // 结果为26.75
return 0;
}
很明显,这个例子说明在二者都能行得通的前提下,编译器优先选择了执行全局重载的+运算符。
explicit关键字的加入
在上面的例子中,我们注意到CFraction类的构造函数有如下特点:
- 最少只需要一个参数就能构造出CFraction的对象。
- 没有使用explicit关键字修饰。
基于以上特点,给其取名“non-explicit-one-argument ctor”。在这种情况下,我们对main函数中的代码进行如下修改:
#include <iostream>
using namespace std;
class CFraction
{
private:
int m_upper;
int m_lower;
public:
CFraction(int upper, int lower = 1) : m_upper(upper), m_lower(lower) { }
CFraction operator+ (const CFraction& rhs) const
{
return CFraction(m_upper*rhs.m_lower + m_lower*rhs.m_upper, m_lower * rhs.m_lower);
}
int GetUpper() const { return m_upper; }
int GetLower() const { return m_lower; }
};
ostream& operator<< (ostream& os, const CFraction& rhs)
{
os << rhs.GetUpper() << "//" << rhs.GetLower();
return os;
}
int main()
{
CFraction f1(3, 4);
CFraction d = f1 + 4;
cout << d << endl; // 结果显示为“19//4”
return 0;
}
下面来分析一下CFraction d = f1 + 4对应编译器的行为:首先编译器查找CFraction类型有没有重载运算符+号且返回为CFraction类型的,于是找到了CFraction operator+ (const CFraction& rhs) const函数,然后编译器又尝试将4转换为 CFraction类型,这时候,这个精心设置的构造函数就派上用场了!
编译器会在栈上开辟一个CFraction类型的临时对象大小的空间,并调用其non-explicit-one-argument ctor,传入4,生成该临时对象,参与运算后返回。弄明白了这点后,就不难说明,将代码改成下面这样为什么能顺利编译运行了:
#include <iostream>
using namespace std;
class CFraction
{
private:
int m_upper;
int m_lower;
public:
CFraction(int upper, int lower = 1) : m_upper(upper), m_lower(lower) { }
double operator+ (const CFraction& rhs) const
{
return ((double)m_upper*rhs.m_lower + m_lower*rhs.m_upper / (double)m_lower * rhs.m_lower);
}
int GetUpper() const { return m_upper; }
int GetLower() const { return m_lower; }
};
ostream& operator<< (ostream& os, const CFraction& rhs)
{
os << rhs.GetUpper() << "//" << rhs.GetLower();
return os;
}
int main()
{
CFraction f1(3, 4);
CFraction d = f1 + 4;
cout << d << endl; // 结果为4//1
return 0;
}
要解释原因也很简单,编译器找到了double operator+ (const CFraction& rhs) const函数后,发现可以将4转换成 CFraction,又可以把得到的double型结果先转换为int型,然后扔给non-explicit-one-argument ctor生成一个CFraction的对象。
稍微总结一下,不难得出:non-explicit-one-argument类型的构造函数可能被编译器隐式调用,进行类型转换。如果我们再在上面的例子上加以改动:
#include <iostream>
using namespace std;
class CFraction
{
private:
int m_upper;
int m_lower;
public:
CFraction(int upper, int lower = 1) : m_upper(upper), m_lower(lower) { }
double operator+ (const CFraction& rhs) const
{
return ((double)(m_upper*rhs.m_lower + m_lower*rhs.m_upper) / (double)(m_lower * rhs.m_lower));
}
int GetUpper() const { return m_upper; }
int GetLower() const { return m_lower; }
double GetValue() const
{
return (double)m_upper / (double)m_lower;
}
operator double() const
{
return GetValue();
}
};
ostream& operator<< (ostream& os, const CFraction& rhs)
{
os << rhs.GetUpper() << "//" << rhs.GetLower();
return os;
}
int main()
{
CFraction f1(3, 4);
double d = f1 + 4; //此处报错,编译无法完成
cout << d << endl;
return 0;
}
这样终于把聪明的编译器考倒了,因为此时针对double d = f1 + 4两条路(隐式转换将4转换为CFraction类型对象后参与运算或者将f1转换为double类型后参与运算),于是编译器懵逼了,报错罢工!这时候,如果我们不想让编译器帮我们隐式转换生成CFraction对象(即分配栈内存后调用non-explicit-one-argument ctor),explicit关键字就派上用场了:
#include <iostream>
using namespace std;
class CFraction
{
private:
int m_upper;
int m_lower;
public:
explicit CFraction(int upper, int lower = 1) : m_upper(upper), m_lower(lower) { }
double operator+ (const CFraction& rhs) const
{
return ((double)(m_upper*rhs.m_lower + m_lower*rhs.m_upper) / (double)(m_lower * rhs.m_lower));
}
int GetUpper() const { return m_upper; }
int GetLower() const { return m_lower; }
double GetValue() const
{
return (double)m_upper / (double)m_lower;
}
operator double() const
{
return GetValue();
}
};
ostream& operator<< (ostream& os, const CFraction& rhs)
{
os << rhs.GetUpper() << "//" << rhs.GetLower();
return os;
}
int main()
{
CFraction f1(3, 4);
double d = f1 + 4;
cout << d << endl; // 结果为26.75
return 0;
}
这样编译器由于知道不能隐式转换出CFraction类,就只好老老实实去调用转换函数了。总结一下:
- 严格意义上的转换函数指的是转出函数,即将本类型的对象转换为其他类型对象的函数。这类函数的语法是:
operator 转出类型() const { //… return (转出类型)xxx; } - 广义上的转换函数其实还包括non-explicit-one-argument ctor,该类型的构造函数只需要接收一个参数即可。用于将将收到的参数类型对象转换成本类型对象。当编译器判定可以进行隐式转换的时候,会创建临时对象,自动生成调用non-explicit-one-argument ctor的代码。
- 如果不希望编译器对本类型进行隐式转换,则需在本类型的non-explicit-one-argument ctor前,加上explicit关键字。