1. 将标准类型转换成类类型

有C语言基础的都知道,标准类型的是可以进行类型转换的,例如,数值类型中intdouble可以互相进行隐式类型转换。int类型和int *类型就无法进行隐式转换(或称自动类型转换),int *p=10;是错误的,但是我们可以使用显式类型转换(或称强制类型转换),int *p=(int *)10;是正确的。
实际上,C++允许我们给自定义类型(类)和标准类型进行互相转换。
看看下面这个例子:

#include<iostream>
#include<string>
class apple
{
int number;
std::string color;
public:
apple(int n=0,std::string c="no color")
{
number=n;
color=c;
}
void display()const
{
std::cout<<"number: "<<number<<" color: "<<color<<std::endl;
}
void compare(const apple &a)const
{
if(a.number<number)
std::cout<<"大"<<std::endl;
else if(a.number==number)
std::cout<<"相等"<<std::endl;
else
std::cout<<"小于"<<std::endl;
}

};
int main()
{
using namespace std;
apple a;
a.display();
a={100,"red"};
a.display();
a=250;
a.display();
a=300.12312;
a.display();
a.compare(123.0);
}
number: 0 color: no color
number: 100 color: red
number: 250 color: no color
number: 300 color: no color

apple类使用了含有默认参数的构造函数,所以apple a;会创建对象a,并使用显式默认构造函数,进行默认初始化;
再之前,我们说过,构造函数只能用在对象初始化,但是实际上,它还适用于赋值语法
a={100,"red"};这句话是个赋值语句,它使用C++列表赋值语法,调用了构造函数。
a=250;这句话也是个赋值语句,乍一看,250是个int类型,而a是个apple对象,类型不匹配啊。
a=300.12312;这句话就更离谱了,它将double类型赋给了apple对象。
不幸的是,上面这两句都是正确的,因为当C++编译器,发现赋值语句两边类型不匹配时,它就会试着进行隐式类型转换,例如这里的250,300.12312类型均不匹配,那么编译器,就会去调用构造函数,而且这个构造函数一定要是可以接受一个参数且类型匹配或可以转换后匹配的。那么正好,apple类的构造函数,由于使用了默认参数,可以接受一个int类型的参数,那么自然的250可以传参,300.12312隐式转换成300传参,从而创造出新对象,再进行对象成员赋值,完成了赋值语句。
接受一个参数的构造函数为将类型与该参数相同(或转换后相同)的值,转换成类提供了蓝图
a.compare(123.0)更直接的点明了这一点,类方法compare()接受一个类对象参数,但是123.0double类型可以进行隐式转换而完成传参。那么,你完成了一个疯狂的举动–把标准类型参数传参给自定义类型,但是编译器不会发出warning
C++允许标准类型隐式转换成类,是危险的!
我们不希望这种隐式转换,这实在太危险了,最理想的状况是–如何让编译器只接受标准类型显式类型转换成自定义类?

  • explicit关键词

explicit明确的、清晰的意思,正如这个单词的意思,我们需要一个明确的构造函数,我们不希望构造函数称为隐式类型转换的帮凶,那么我们期待的explicit构造函数登场了。

#include<iostream>
#include<string>
class apple
{
int number;
std::string color;
public:
explicit apple(int n=0,std::string c="no color")
{
number=n;
color=c;
}
void display()const
{
std::cout<<"number: "<<number<<" color: "<<color<<std::endl;
}
void compare(const apple &a)const
{
if(a.number<number)
std::cout<<"大"<<std::endl;
else if(a.number==number)
std::cout<<"相等"<<std::endl;
else
std::cout<<"小于"<<std::endl;
}

};
int main()
{
using namespace std;
apple a;
a.display();
a=(apple){100,"red"};
a.display();
a=(apple)250;
a.display();
a=(apple)300.12312;
a.display();
a.compare((apple)123.0);

}

看看上面这段代码,explicit apple(int n=0,std::string c="no color")是个明确的构造函数,它只允许显式类型转换,它告诉编译器,可以把标准类型转化成自定义类型,但是你必须进行显式转化,否则我会报错。
explicit关键词,会提供一个明确的构造函数,只允许标准类型显式类型转化成自定义类

apple b;//正确,显式默认初始化
b=12;//错误
b=(apple)12;//正确,显式类型转化
b=apple(12);//正确,等式右边是个构造函数的调用
apple c=13;//错误,不允许采用隐式类型转化,初始化apple对象
  • 类型转换与运算符重载。
#include<iostream>
#include<string>
class apple
{
int number;
std::string color;
public:
apple(int n=0,std::string c="no color")
{
number=n;
color=c;
}
void display()const
{
std::cout<<"number: "<<number<<" color: "<<color<<std::endl;
}
void compare(const apple &a)const
{
if(a.number<number)
std::cout<<"大"<<std::endl;
else if(a.number==number)
std::cout<<"相等"<<std::endl;
else
std::cout<<"小于"<<std::endl;
}
friend apple operator+(const apple &a,const apple &b)
{
apple ret;
ret.number=a.number+b.number;
return ret;
}

};
int main()
{
using namespace std;
apple a{120,"red"};
apple b;
b.display();
int x=250;
b=x+a;
b.display();

}
number: 0 color: no color  
number: 370 color: no color

上面这段代码,对+的重载,采用的友元函数的方式。b=x+a;首先x会调用构造函数自动转换成apple对象,再调用+的重载。但是如果我们采用的是类成员函数的方式重载+

apple apple::operator+(const apple & a)
{
return (number+a.number);
}

就会出现问题,x+a,中xint类型,它无法调用类成员函数完成+重载。所以说,友元函数重载运算符相对来说,更适应自动类型转换,这是因为友元函数的参数比类成员函数多,所以更容易自动转换。

2.将类类型转化成标准类型

double n=a;这里a是一个apple对象。
这句话会报错,因为构造函数只用于从某种类型到类类型的转换,如果你想进行反方向的转换,必须使用转换函数

  • 转换函数

如果编译器发现,赋值语句右边是类类型,左边是标准类型,那么就会查看程序有没有定义了相应的转换函数。
转换函数的作用是,把类类型转换成标准类型。
转换函数的原型是:
operator typeName();

注意!,转换函数必须是类方法,不能指定返回类型,不能有参数
例如,你想把类类型转换成double类型,那么转换函数原型是:
operator double();

#include<iostream>
#include<string>
class apple
{
int number;
std::string color;
public:
apple(int n=0,std::string c="no color")
{
number=n;
color=c;
}
void display()const
{
std::cout<<"number: "<<number<<" color: "<<color<<std::endl;
}
void compare(const apple &a)const
{
if(a.number<number)
std::cout<<"大"<<std::endl;
else if(a.number==number)
std::cout<<"相等"<<std::endl;
else
std::cout<<"小于"<<std::endl;
}
operator int()
{
return number*100;
}
operator double()
{
return number/3.14;
}

};
int main()
{
using namespace std;
apple a{120,"red"};
int x=a;
double y=a;
cout<<x<<endl;
cout<<y<<endl;

}
12000
38.2166

int x=a;double y=a;都是正确的,因为apple类中存在转换成intdouble的转换函数。
同样的,为了强调显式类型转换的重要性,转换函数也可以使用explicit修饰

3.小结

  • 接受一个参数的构造函数可以将其他类型的值转换成类类型。
  • 转换函数可以让类对象转换成其他类型的值。
  • 实际上不光是标准类型,我们甚至可以完成类与类之间的转换。总之,我们现在完成了类和任何类型的互相转换。
  • 实现运算符重载中,友元函数的方式可能更好。
  • explicit关键词,虽然规避了自动转换的危险性,但是可能要多敲很多代码。
  • 转换函数可能会引发重载解析失败,例如,我如果重载了+,并且我有转换函数operator int();,那么apple a=b+17;(b也是apple对象。)
  • 那么一种看法是:将17自动转换成apple对象,再调用类中+运算。
  • 另一种看法是:把b自动转换成int,然后调用int的加法。