page contents

C++转换构造函数:将其它类型转换为当前类的类型

在 C/C++ 中,不同的数据类型之间可以相互转换。无需用户指明如何转换的称为自动类型转换(隐式类型转换),需要用户显式地指明如何转换的称为强制类型转换。

在 C/C++ 中,不同的数据类型之间可以相互转换。无需用户指明如何转换的称为自动类型转换(隐式类型转换),需要用户显式地指明如何转换的称为强制类型转换。

自动类型转换示例:



  1. int a = 6;

  2. a = 7.5 + a;

编译器对 7.5 是作为 double 类型处理的,在求解表达式时,先将 a 转换为 double 类型,然后与 7.5 相加,得到和为 13.5。在向整型变量 a 赋值时,将 13.5 转换为整数 13,然后赋给 a。整个过程中,我们并没有告诉编译器如何去做,编译器使用内置的规则完成数据类型的转换。

强制类型转换示例:



  1. int n = 100;

  2. int *p1 = &n;

  3. float *p2 = (float*)p1;

p1 是int *类型,它指向的内存里面保存的是整数,p2 是float *类型,将 p1 赋值给 p2 后,p2 也指向了这块内存,并把这块内存中的数据作为小数处理。我们知道,整数和小数的存储格式大相径庭,将整数作为小数处理非常荒诞,可能会引发莫名其妙的错误,所以编译器默认不允许将 p1 赋值给 p2。但是,使用强制类型转换后,编译器就认为我们知道这种风险的存在,并进行了适当的权衡,所以最终还是允许了这种行为。


不管是自动类型转换还是强制类型转换,前提必须是编译器知道如何转换,例如,将小数转换为整数会抹掉小数点后面的数字,将int *转换为float *只是简单地复制指针的值,这些规则都是编译器内置的,我们并没有告诉编译器。

换句话说,如果编译器不知道转换规则就不能转换,使用强制类型也无用,请看下面的例子:



  1. #include <iostream>

  2. using namespace std;

  3. //复数类

  4. class Complex{

  5. public:

  6. Complex(): m_real(0.0), m_imag(0.0){ }

  7. Complex(double real, double imag): m_real(real), m_imag(imag){ }

  8. public:

  9. friend ostream & operator<<(ostream &out, Complex &c);  //友元函数

  10. private:

  11. double m_real;  //实部

  12. double m_imag;  //虚部

  13. };

  14. //重载>>运算符

  15. ostream & operator<<(ostream &out, Complex &c){

  16. out << c.m_real <<" + "<< c.m_imag <<"i";;

  17. return out;

  18. }

  19. int main(){

  20. Complex a(10.0, 20.0);

  21. a = (Complex)25.5;  //错误,转换失败

  22. return 0;

  23. }

25.5 是实数,a 是复数,将 25.5 赋值给 a 后,我们期望 a 的实部变为 25.5,而虚部为 0。但是,编译器并不知道这个转换规则,这超出了编译器的处理能力,所以转换失败,即使加上强制类型转换也无用。

幸运的是,C++ 允许我们自定义类型转换规则,用户可以将其它类型转换为当前类类型,也可以将当前类类型转换为其它类型。这种自定义的类型转换规则只能以类的成员函数的形式出现,换句话说,这种转换规则只适用于类。

转换构造函数

将其它类型转换为当前类类型需要借助转换构造函数(Conversion constructor)。转换构造函数也是一种构造函数,它遵循构造函数的一般规则。转换构造函数只有一个参数。

仍然以 Complex 类为例,我们为它添加转换构造函数:



  1. #include <iostream>

  2. using namespace std;

  3. //复数类

  4. class Complex{

  5. public:

  6. Complex(): m_real(0.0), m_imag(0.0){ }

  7. Complex(double real, double imag): m_real(real), m_imag(imag){ }

  8. Complex(double real): m_real(real), m_imag(0.0){ }  //转换构造函数

  9. public:

  10. friend ostream & operator<<(ostream &out, Complex &c);  //友元函数

  11. private:

  12. double m_real;  //实部

  13. double m_imag;  //虚部

  14. };

  15. //重载>>运算符

  16. ostream & operator<<(ostream &out, Complex &c){

  17. out << c.m_real <<" + "<< c.m_imag <<"i";;

  18. return out;

  19. }

  20. int main(){

  21. Complex a(10.0, 20.0);

  22. cout<<a<<endl;

  23. a = 25.5;  //调用转换构造函数

  24. cout<<a<<endl;

  25. return 0;

  26. }

运行结果:
10 + 20i
25.5 + 0i

Complex(double real);就是转换构造函数,它的作用是将 double 类型的参数 real 转换成 Complex 类的对象,并将 real 作为复数的实部,将 0 作为复数的虚部。这样一来,a = 25.5;整体上的效果相当于:

a.Complex(25.5);

将赋值的过程转换成了函数调用的过程。

在进行数学运算、赋值、拷贝等操作时,如果遇到类型不兼容、需要将 double 类型转换为 Complex 类型时,编译器会检索当前的类是否定义了转换构造函数,如果没有定义的话就转换失败,如果定义了的话就调用转换构造函数。

转换构造函数也是构造函数的一种,它除了可以用来将其它类型转换为当前类类型,还可以用来初始化对象,这是构造函数本来的意义。下面创建对象的方式是正确的:



  1. Complex c1(26.4);  //创建具名对象

  2. Complex c2 = 240.3;  //以拷贝的方式初始化对象

  3. Complex(15.9);  //创建匿名对象

  4. c1 = Complex(46.9);  //创建一个匿名对象并将它赋值给 c1

在以拷贝的方式初始化对象时,编译器先调用转换构造函数,将 240.3 转换为 Complex 类型(创建一个 Complex 类的匿名对象),然后再拷贝给 c2。

如果已经对
+运算符进行了重载,使之能进行两个 Complex 类对象的相加,那么下面的语句也是正确的:



  1. Complex c1(15.6, 89.9);

  2. Complex c2;

  3. c2 = c1 + 29.6;

  4. cout<<c2<<endl;

在进行加法运算符时,编译器先将 29.6 转换为 Complex 类型(创建一个 Complex 类的匿名对象)再相加。

需要注意的是,为了获得目标类型,编译器会“不择手段”,会综合使用内置的转换规则和用户自定义的转换规则,并且会进行多级类型转换,例如:

  • 编译器会根据内置规则先将 int 转换为 double,再根据用户自定义规则将 double 转换为 Complex(int --> double --> Complex);

  • 编译器会根据内置规则先将 char 转换为 int,再将 int 转换为 double,最后根据用户自定义规则将 double 转换为 Complex(char --> int --> double --> Complex)。


从本例看,只要一个类型能转换为 double 类型,就能转换为 Complex 类型。请看下面的例子:



  1. int main(){

  2. Complex c1 = 100;  //int --> double --> Complex

  3. cout<<c1<<endl;

  4. c1 = 'A';  //char --> int --> double --> Complex

  5. cout<<c1<<endl;

  6. c1 = true;  //bool --> int --> double --> Complex

  7. cout<<c1<<endl;

  8. Complex c2(25.8, 0.7);

  9. //假设已经重载了+运算符

  10. c1 = c2 + 'H' + true + 15;  //将char、bool、int都转换为Complex类型再运算

  11. cout<<c1<<endl;

  12. return 0;

  13. }

运行结果:
100 + 0i
65 + 0i
1 + 0i
113.8 + 0.7i

再谈构造函数

构造函数的本意是在创建对象的时候初始化对象,编译器会根据传递的实参来匹配不同的(重载的)构造函数。回顾一下以前的章节,到目前为止我们已经学习了以下几种构造函数。

1) 默认构造函数。就是编译器自动生成的构造函数。以 Complex 类为例,它的原型为:

Complex();  //没有参数


2) 普通构造函数。就是用户自定义的构造函数。以 Complex 类为例,它的原型为:

Complex(double real, double imag);  //两个参数


3) 拷贝构造函数。在以拷贝的方式初始化对象时调用。以 Complex 类为例,它的原型为:

Complex(const Complex &c);


4) 转换构造函数。将其它类型转换为当前类类型时调用。以 Complex 为例,它的原型为:

Complex(double real);


不管哪一种构造函数,都能够用来初始化对象,这是构造函数的本意。假设 Complex 类定义了以上所有的构造函数,那么下面创建对象的方式都是正确的:



  1. Complex c1();  //调用Complex()

  2. Complex c2(10, 20);  //调用Complex(double real, double imag)

  3. Complex c3(c2);  //调用Complex(const Complex &c)

  4. Complex c4(25.7);  //调用Complex(double real)

这些代码都体现了构造函数的本意——在创建对象时初始化对象。

除了在创建对象时初始化对象,其他情况下也会调用构造函数,例如,以拷贝的的方式初始化对象时会调用拷贝构造函数,将其它类型转换为当前类类型时会调用转换构造函数。这些在其他情况下调用的构造函数,就成了特殊的构造函数了。特殊的构造函数并不一定能体现出构造函数的本意。

对 Complex 类的进一步精简

上面的 Complex 类中我们定义了三个构造函数,其中包括两个普通的构造函数和一个转换构造函数。其实,借助函数的默认参数,我们可以将这三个构造函数简化为一个,请看下面的代码:



  1. #include <iostream>

  2. using namespace std;

  3. //复数类

  4. class Complex{

  5. public:

  6. Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ }

  7. public:

  8. friend ostream & operator<<(ostream &out, Complex &c);  //友元函数

  9. private:

  10. double m_real;  //实部

  11. double m_imag;  //虚部

  12. };

  13. //重载>>运算符

  14. ostream & operator<<(ostream &out, Complex &c){

  15. out << c.m_real <<" + "<< c.m_imag <<"i";;

  16. return out;

  17. }

  18. int main(){

  19. Complex a(10.0, 20.0);  //向构造函数传递 2 个实参,不使用默认参数

  20. Complex b(89.5);  //向构造函数传递 1 个实参,使用 1 个默认参数

  21. Complex c;  //不向构造函数传递实参,使用全部默认参数

  22. a = 25.5;  //调用转换构造函数(向构造函数传递 1 个实参,使用 1 个默认参数)

  23. return 0;

  24. }

精简后的构造函数包含了两个默认参数,在调用它时可以省略部分或者全部实参,也就是可以向它传递 0 个、1 个、2 个实参。转换构造函数就是包含了一个参数的构造函数,恰好能够和其他两个普通的构造函数“融合”在一起。

attachments-2021-05-i9xac6Tt609a4928371c4.jpg

  • 发表于 2021-05-11 17:07
  • 阅读 ( 609 )
  • 分类:C/C++开发

0 条评论

请先 登录 后评论
小柒
小柒

1470 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1470 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章