1. 转换函数与explicit关键字
1.1 转换函数
下述代码的第5行operator double()即是一个转换函数,通过这个函数,编译器可以在需要的情况下,直接将Fraction类型的对象转换为double类型。这个函数有两个特点:首先因为转换函数的返回类型已经通过函数名double()给定,即返回double类型的结果,所以不需要写返回类型;其次,因为这个转换函数并不会更改对象的数据成员,所以将此函数声明为成const。
编译器在执行第17行代码double d = 4+f时,会有很多的判断。其会考虑代码里有没有定义+号的运算符重载函数,且此重载函数接收double与fraction类型的对象作为第一参数与第二参数。若有此函数,则可以直接使用,下述代码是没有写这个重载函数的。编译器还会考虑是否存在f的转换函数,将f转换为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);
}
private:
int m_numerator; //分子
int m_denominator; //分母
};
int main() {
Fraction f(3, 5);
double d = 4 + f; //调用operator double()将f转为0.6,并执行相加
}
下述代码第3行的构造函数,虽然有两个参数(parameter),但是其最少只需要接收一个,这种构造函数被称为one argument ctor。这种构造函数比较页数,因为其存在隐式转换的特性。在代码的第15行,3通过调用上述的构造函数可以转换为Fraction类型的匿名对象Fraction(3,1),进而调用重载的+运算符完成加法操作。
class Fraction {
public:
Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}
Fraction operator+(const Fraction &f);
private:
int m_numerator; //分子
int m_denominator; //
};
int main() {
Fraction f(3, 5);
Fraction d2 = f + 3; //调用隐式转换的构造函数将4转为Fraction(4,1),再调用operator+
}
上述的分数类已经很好的说明了转换函数的用途,在数学中将一个分数直接转为double类型是一个很自然的事情。
在cpp的标准库中也有转换函数的应用,如下是标准库中一个特化vector的模板,当vector存储bool类型时,在空间存储上或处理逻辑上和别的元素类型都有可能有区别,所以这里是特化的vector<bool, Alloc>类型的模板。
这里vector重载的下标运算符[]可以得到执行下标的元素,但是注意其返回类型并不是bool类型,而是被代理成了_bit_reference类型。但是当用户使用标准库时,其期望的返回类型一定是bool类型,所以这里对_bit_reference类即定义了转换函数,用于将此类型的对象转换为bool类型。

1.2 no-explicit & one-argument 构造函数
隐式转换的功能,有时候会导致代码出现二义性,如下代码的第19行,对于这个加法操作,3可以通过构造函数隐式转为Fraction类型完成相加,而f也可以通过转换函数转为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);
}
Fraction operator+(const Fraction &f);
private:
int m_numerator; //分子
int m_denominator; //
};
int main() {
Fraction f(3, 5);
Fraction d2 = f + 3;
}
为了消除这种二义性,即禁止编译器的隐式转换,需要在构造函数前加上explict关键字,这时3就不能通过这个构造函数隐式转为double类型。
除了构造函数外,也可以对转换函数加上explicit关键字,这时这个类就必须通过显式转换的方式转为指定类型:如下所示,当对转换函数加上explicit后,第23行将无法通过编译,而必须通过24、25行的这种显式转换即可。
这里也定义了bool类型的转换函数,可以发现27行是无法通过编译的,而28行在if判断内,编译器会认为这是一个显式转换,不会报错。
class Fraction {
public:
explicit Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}
explicit operator double() const {
return double(m_numerator / m_denominator);
}
explicit operator bool() const {
return m_denominator != 0;
}
Fraction operator+(const Fraction &f);
private:
int m_numerator; //分子
int m_denominator; //
};
int main() {
Fraction f(3, 5);
// cout << (f + 2.0) << endl;
cout << ((double) f + 2.0) << endl;
cout << (static_cast<double >(f) + 2.0) << endl;
// bool j = f;
if (f) {
}
}
1.3 c++11后的explicit
在1.2部分叙述的禁止隐式转换在c++11之前就已经存在,不过其只针对单一参数的构造函数,这种构造函数也可以被称为no-explicit and one-argument 构造函数。在c++11后出现了统一初始化,所以explicit也可以针对多参数的情况:
若不存在第七行的接收initializer_list的构造函数,则第18行是无法通过编译的。因为第18行的统一初始化方式执行时,编译器将{77, 5, 42}包装成一个initializer_list,因为不存在能够接受initializer_list的构造函数,编译器会将这三个元素依次分解,传递给第10行的构造函数,但是此构造函数禁止隐式转转,所以将无法通过编译。在c++11之后,增加了统一初始化方式,explicit也可以作用于多参的情形了。
class P {
public:
P(int a, int b) {
cout << "P(int a,int b)" << endl;
}
P(initializer_list<int>) {
cout << "P(initializer_list<int>)" << endl;
}
explicit P(int a, int b, int c) {
cout << "explicit P(int a,int b,int c)" << endl;
}
};
int main() {
P p(77, 5); //调用P(int a,int b)
P p2{77, 5}; //调用P(initializer_list<int>)
P p3{77, 5, 42}; //调用P(initializer_list<int>)
P p4 = {77, 5, 42}; //调用P(initializer_list<int>)
}








![[附源码]java毕业设计基于web的球类体育馆预定系统](https://img-blog.csdnimg.cn/bc2629b56f204f0b82a83a7842db9e40.png)









