参考书籍:
《C++高级编程》(第4版)》
下面是一个简单的"分数"类:
//Fraction.h
//分数类
#ifndef FRACTION_FRACTION_H
#define FRACTION_FRACTION_H
class Fraction {
public:
Fraction(int num, int den = 1)
: m_numerator{num}, m_denominator{den} {};
private:
int m_numerator; //分子
int m_denominator; //分母
};
#endif //FRACTION_FRACTION_H
复制
考虑下面两行代码:
Fraction x{7, 2}; //3.5
double y = x ; //Error: Cannot convert 'Fraction' to 'double' in initialization
复制
Fraction可以转换为double类型,因此看上去把Fraction赋值给double变量是符合逻辑的。但不能这么做。编译器会不知道如何将Fraction转换为double类型。你可能会通过下述方式迫使编译器进行这种转换:
double y = (double)x ; //Error: invalid cast from type 'Fraction' to type 'double'
复制
首先,上述代码依然无法编译,因为编译器还是不知道如何将Fraction转换为double类型。从这行代码中编译器已知你想让编译器做这种转换,所以如果编译器知道如何转换,就会进行转换。其次一般情况下,最好不要在程序中添加这种无理由的类型转换。如果想允许这类赋值,就必须告诉编译器具体如何去执行。确切地讲,可编写一个把Fraction转换为double类型的 转换运算符 (转换函数) 。原型如下所示:
operator double() const;
复制
函数名为operator double。它没有返回类型,因为返回类型是通过运算符名称确定的:double。这个函数是const的,因为这个函数不会修改被调用的对象,转换不是改变。实现如下所示:
Fraction::operator double() const {
return (double) m_numerator/m_denominator;
}
复制
这就完成了从Fraction到double类型的转换运算符的编写。现在编译器接受下这行代码,并在运行时执行正确的操作。
运行结果:
关于第4行代码,编译器编译时会首先判断是否有全局函数+,没有编译器再次判断代码是否有设计转换运算符。
可用同样的语法编写任何类型的转换运算符。例如,下面是从Fraction到std::string的转换运算符(分子分母小于10):
Fraction::operator std::string() const {
std::string s{""};
s.push_back('0'+m_numerator);
s.push_back('/');
s.push_back('0'+m_denominator);
return s;
}
复制
现在可以编写以下代码:
std::string s = x;
std::cout << s << std::endl; // 7/2
复制
运行结果:
non-explicit-one-argument ctor
现有代码:
//Fraction.h
//分数类
#ifndef FRACTION_FRACTION_H
#define FRACTION_FRACTION_H
#include <string>
#include <iostream>
class Fraction {
public:
Fraction(int num, int den = 1)
: m_numerator{num}, m_denominator{den} {};
Fraction operator+(const Fraction& F){
return Fraction(m_numerator*F.m_denominator + m_denominator*F.m_numerator,
m_denominator*F.m_denominator);
}
void printFraction(){
std::cout << m_numerator << " " << m_denominator << std::endl;
}
private:
int m_numerator; //分子
int m_denominator; //分母
};
#endif //FRACTION_FRACTION_H
复制
one-argument的意思就是,只要一个实参就够了,你也可以给两个实参。Fraction的构造函数(11行)中第二个参数已经有默认值1(对于整数,转为分数分母本就是1),所以可以只要一个实参。又没有加explicit,所以Fraction就是non-explicit-one-arugument cotr。
运行结果:
使用显示转换运算符解决多义性的问题(现有最新编译器已解决)
现有如下代码:
//Fraction.h
//分数类
#ifndef FRACTION_FRACTION_H
#define FRACTION_FRACTION_H
#include <string>
#include <iostream>
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(m_numerator*F.m_denominator + m_denominator*F.m_numerator,
m_denominator*F.m_denominator);
}
private:
int m_numerator; //分子
int m_denominator; //分母
};
#endif //FRACTION_FRACTION_H
复制
注意,为Fraction对象编写double转换运算符时会引入多义性问题。首先我们重载 例如下面第2行代码:
Fraction x{7,2}; // 7/2
double y = x + 4.0;
复制
现在这行代码无法成功编译。在编写operator double() 前,这行代码可编译,那么现在出了什么问题?问题在于,编译器不知道应该通过operator double() 将x 对象转换为double类型,再执行double加法,还是通过double构造函数将4转换为Fraction,再执行Fraction加法。在编写 operator double() 前,编译器只有一个选择:通过double构造函数将4转换为Fraction,再执行Fraction加法。然而,现在编译器可执行两种操作。编译器不想做出让人不喜欢的决定,因此拒绝做出任何决定。在C++11之前,通常解决这个难题的方法是将构造函数标记为explicit,以避免使用这个构造函数进行自动转换(见第9章)。然而,我们不想把这个构造函数标记为explicit,因为通常希望进行从double到Fraction的自动类型转换。自C++11以后,可将double类型转换运算符标记为explicit以解决这个问题:
下面代码演示了这种方法的应用:
Fraction x{7,2}; // 7/2
double y = (double)x; //3.5
double z = (double)(x + 5); //8.5
复制
以上代码并不完善,仅做理解使用
欢迎关注公众号:c_302888524
发送:"C++高级编程(第3版)" 获取电子书