Conversion function个人理解为是一个跟重载配合使用的重要结构,我们经常会对一些对象进行类型转换,比如把一个Double类型转为Int类型,或是float类型转Double类型,那么当我们构建自己的类的时候,我们希望当它与某种类型进行相加相减之类的操作时,能够自动将自己转换为对应的类型,亦是将另外一种类型转换成跟自己相同的类型,然后进行你设定好的操作。
一般来说,设定这种转化函数,必须是符合逻辑的,不能随意改变,否则会对程序整体造成不好的影响。
就拿分数类来进行解释。
class Fraction { public: Fraction(int num,int den = 1):m_numerator(num),m_denominator(den){} operator double() const { return (double)m_numerator / m_denominator; } private: int m_numerator; // 分子 int m_denominator; //分母 };
比如上面的代码,我们分数类有两个属性,一个分子,一个分母,且都是int类型,那么假如,我们想把这个分数类的对象与某个整数,或者是浮点数相加,那么该怎么做呢,比如下面这个操作:
int main() { Fraction f(3, 5); double d = 4 + f; ... }
很明显,我们想要得到的结果是 3/5 + 4的结果,可是 4 是一个整形,而f是你自己构建的类的对象,两种完全不相同的东西,如何相加呢。
可以再回到我们最上面那一段代码:
operator double() const { return (double)m_numerator / m_denominator; }
可能之前对重载有些许过了解的人,会对这段代码产生疑惑,一般是重载一个运算符,那么operator double() 是什么意思呢,其实很简单,意思就是,当对象与其他类型的变量进行运算时,编译器会先找你的类里面,有没有相关的设定,可以让你的对象变为一个类型并且能够符合当前的状况,比如运行到 double d = 4 + f;这里,编译器就开始找你的Fraction类里有什么可以使用的类型,operator double()就是表示,我这个类的对象,可以作为一个double类型使用,并且作为double类型使用时,应该变成一个什么样的double值,很明显,一个分数如何变成double值,当然是分子除以分母。
好了,那么新的问题又来了,万一它其中有多个重载类型呢,那将会发生什么。
class Fraction { public: Fraction(int num,int den = 1):m_numerator(num),m_denominator(den){} operator double() const { return (double)m_numerator / m_denominator; } operator int() const { return (int)m_numerator / m_denominator; } private: int m_numerator; // 分子 int m_denominator; //分母 };
看到这段代码,我们发现,又多了一个operator int(),最开始说了,C++能处理的,都会自己处理,因为遇到的都是它觉得是这样,或者只能是这样的状况,那么当前这种情况下, 它再次遇到 double d = 4 + f;时,它找你的类中相应的转换函数,发现既可转换成 double,也可以转换成int,而且这两个都能与4相加,C++并不知道你到底想要f以什么样的类型面对这个4,所以就会报错。
遇到这种状况很简单,要么你告诉编译器你想做什么,要么就把可能性排掉只剩下一种(逻辑正确性就体现在这,如果逻辑思路正确,一般是不会分出很多种情况)。
我们当然可以告诉编译器我们想做什么,只需要:
int main() { Fraction f(3, 5); double d = 4 + (int)f; ... }
或者
int main() { Fraction f(3, 5); double d = 4 + (double)f; ... }
这样编译器就知道你想要f以什么样的姿势面对这个整形变量了。
我们也能得到想要的值。
那么Fraction类对象能转化为其他的类型,那么是否能将这个"4"转换为Fraction类型呢,答案是可以的.
non-explicit-one-argument ctor
看下面这一段代码:
class Fraction { public: Fraction(int num,int den = 1):m_numerator(num),m_denominator(den){} Fraction operator+ (const Fraction& f) { return Fraction(...); } private: int m_numerator; // 分子 int m_denominator; //分母 };
此时再遇到
Fraction f(3,5); Fraction d2 = f + 4;
会如何,我们想得到一个Fraction类的对象,并且得到相应的值,是f + 4,此时应该怎么做,很明显,我们应该把4变成Fraction类,理所当然4就是 4/1,所以在 operator+ 中,我们直接调用构造函数,创建并返回一个分子为4,分母为1的Fraction类对象,因为Fraction(int num,int den = 1)可以看出,构造函数分母初始化为1,传值为4会赋值给num,所以得到4/1,具体相加的操作可以继续在重载函数中实现,这里就不具体操作了。
刚刚可以看到上面有个non-explicit-one-argument ctor ,这是什么意思呢,其实就是构造函数是非explicit类型,explicit是用来干什么的,之前说了,编译器觉得你想要干什么,会帮你去做,不加explicit就是让编译器能干就干,如果加上explicit就是让编译器不要去揣测,放这别去碰这个构造函数。
由于我们之前没有加explicit,所以当你把4变成一个Fraction类对象时,编译器看到分母den = 1 有初始值,所以会把4当做没有初始值的num,并调用构造函数。
而如果我们加上explicit,编译器就不会做这个动作,并且也我们也就无法实现上面那个操作了。
这里可以用两个例子来说明:
class Fraction { public: Fraction(int num,int den = 1):m_numerator(num),m_denominator(den){} operator double() const { return (double)m_numerator / m_denominator; } Fraction operator+ (const Fraction& f) { return Fraction(...); } private: int m_numerator; // 分子 int m_denominator; //分母 };
比如说这串代码,当它遇到
Fraction f(3,5); Fraction d2 = f + 4;
它也是无法执行的,因为编译器不知道你是要将4转为Fraction类,还是要将f转为double型,编译器就会报错:
Error:ambiguous
而如果你加上 explicit ,也就是
class Fraction { public: explicit Fraction(int num,int den = 1):m_numerator(num),m_denominator(den){} operator double() const { return (double)m_numerator / m_denominator; } Fraction operator+ (const Fraction& f) { return Fraction(...); } private: int m_numerator; // 分子 int m_denominator; //分母 };
此时,编译器就不会把 4 变为 4/1 了,因为编译器不会去揣测你的意思了,它不会去调用这个构造函数去把4变成4/1,然后由于我们要得到的d2 是 Fraction类型的,f变为double类型与4相加,会出现什么情况,
想象一样 Fraction d2 = 4.6 ; 这样能行吗,肯定不行,所以我们会看到这种报错:
Error:conversion from "double" to "Fraction" requested
以上就是对Conversion function转换函数的一点粗略理解。