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

自动类型转换示例:

int a = 6;
a = 7.5 + a;

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

强制类型转换示例:

int n = 100;
int *p1 = &n;
float *p2 = (float*)p1;

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

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

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

#include <iostream>
using namespace std;//复数类
class Complex{public:Complex(): m_real(0.0), m_imag(0.0){ }Complex(double real, double imag): m_real(real), m_imag(imag){ }
public:friend ostream & operator<<(ostream &out, Complex &c);  //友元函数
private:double m_real;  //实部double m_imag;  //虚部
};//重载>>运算符
ostream & operator<<(ostream &out, Complex &c){out << c.m_real <<" + "<< c.m_imag <<"i";;return out;
}int main(){Complex a(10.0, 20.0);a = (Complex)25.5;  //错误,转换失败return 0;
}

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

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

转换构造函数

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

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

#include <iostream>
using namespace std;//复数类
class Complex{public:Complex(): m_real(0.0), m_imag(0.0){ }Complex(double real, double imag): m_real(real), m_imag(imag){ }Complex(double real): m_real(real), m_imag(0.0){ }  //转换构造函数
public:friend ostream & operator<<(ostream &out, Complex &c);  //友元函数
private:double m_real;  //实部double m_imag;  //虚部
};//重载>>运算符
ostream & operator<<(ostream &out, Complex &c){out << c.m_real <<" + "<< c.m_imag <<"i";;return out;
}int main(){Complex a(10.0, 20.0);cout<<a<<endl;a = 25.5;  //调用转换构造函数cout<<a<<endl;return 0;
}

运行结果:

10 + 20i
25.5 + 0i

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

a.Complex(25.5);

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

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

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

Complex c1(26.4);  //创建具名对象
Complex c2 = 240.3;  //以拷贝的方式初始化对象
Complex(15.9);  //创建匿名对象
c1 = Complex(46.9);  //创建一个匿名对象并将它赋值给 c1

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

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

Complex c1(15.6, 89.9);
Complex c2;
c2 = c1 + 29.6;
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 类型。请看下面的例子:

int main(){Complex c1 = 100;  //int --> double --> Complexcout<<c1<<endl;c1 = 'A';  //char --> int --> double --> Complexcout<<c1<<endl;c1 = true;  //bool --> int --> double --> Complexcout<<c1<<endl;Complex c2(25.8, 0.7);//假设已经重载了+运算符c1 = c2 + 'H' + true + 15;  //将char、bool、int都转换为Complex类型再运算cout<<c1<<endl;return 0;
}

运行结果:

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 类定义了以上所有的构造函数,那么下面创建对象的方式都是正确的:

Complex c1();  //调用Complex()
Complex c2(10, 20);  //调用Complex(double real, double imag)
Complex c3(c2);  //调用Complex(const Complex &c)
Complex c4(25.7);  //调用Complex(double real)

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

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

Complex 类的精简

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

#include <iostream>
using namespace std;//复数类
class Complex{public:Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ }
public:friend ostream & operator<<(ostream &out, Complex &c);  //友元函数
private:double m_real;  //实部double m_imag;  //虚部
};//重载>>运算符
ostream & operator<<(ostream &out, Complex &c){out << c.m_real <<" + "<< c.m_imag <<"i";;return out;
}int main(){Complex a(10.0, 20.0);  //向构造函数传递 2 个实参,不使用默认参数Complex b(89.5);  //向构造函数传递 1 个实参,使用 1 个默认参数Complex c;  //不向构造函数传递实参,使用全部默认参数a = 25.5;  //调用转换构造函数(向构造函数传递 1 个实参,使用 1 个默认参数)return 0;}

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

C++ 转换构造函数相关推荐

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

    1.C++转换构造函数:将其它类型转换为当前类的类型 在C/C++中,不同的数据类型之间可以相互转换.无需用户指明如何转换的称为自动类型转换(隐式类型转换),需要用户显式地指明如何转换的称为强制类型转 ...

  2. C++中的转换构造函数和类型转换函数

    文章目录 1 C++中的转换构造函数 1.1 转换构造函数 1.2 explicit关键字 2 C++中的类型转换函数 2.1 类型转换函数 3 转换构造函数与类型转换函数之间的冲突 3.1 转换构造 ...

  3. [一道搜狗输入法的面试题]C++转换构造函数和类型转换函数

    今天面试遇到一道有关C++转换构造函数的题目,之前经常见到默认构造函数.拷贝构造函数.析构函数,但是从没听说过转换构造函数,隐式转换函数也是一样,C++的确是够博大精深的,学习之路很长啊! 其实我们已 ...

  4. Boost:验证atomic <T>具有来自T的隐式转换构造函数

    Boost:验证atomic 具有来自T的隐式转换构造函数 实现功能 C++实现代码 实现功能 验证atomic 具有来自T的隐式转换构造函数 C++实现代码 #if __cplusplus > ...

  5. C++转换构造函数和类型转换函数

    参考博客:https://blog.csdn.net/feiyanaffection/article/details/79183340 隐式类型转换 如果不同类型的数据在一起操作的时候编译器会自动进行 ...

  6. 转换构造函数与类型构造函数与运算符重载函数

    #include <iostream> using namespace std; class complex { public:complex(){real=0;image=0;}comp ...

  7. C++转换构造函数和隐式转换函数

    今天是第一次听到C++还有个转换构造函数,之前经常见到默认构造函数.拷贝构造函数.析构函数,但是从没听说过转换构造函数,隐式转换函数也是一样,C++的确是够博大精深的,再次叹服! 其实我们已经在C/C ...

  8. 运算符重载和转换构造函数相等的情况

    运算符重载: 发生在类类型中,目的是让表达式看起来更加自然,简化程序员工作量.但本质是函数调用,实质并没有简化. 例如创建复数类Complex,两个复数类进行加法. 这时不能把自定义的两个类直接相加, ...

  9. C++编程法则100条(5)转换构造函数

最新文章

  1. python框架之Flask基础篇(一)
  2. 守护线程不一定执行finally块
  3. #linux进阶#归档压缩
  4. 火锅店市场容量或变化趋势_2020-2026年中国氮肥市场深度研究与投资前景预测报告...
  5. Python基础第六天——函数的使用、函数的参数、函数的返回值、函数的嵌套、命名空间、作用域、函数对象...
  6. 硬盘重新分区后有一个分区表信息丢失的数据恢复
  7. antd tree搜索并定位_自动驾驶定位思考
  8. 2020年计算机视觉综述论文汇总!涵盖14个方向:目标检测/图像分割/医学影像/人脸识别等方向
  9. SpringMVC实现文件的上传与下载
  10. 对于 APM 用户的一次真实调查分析(下)
  11. Android ToolBar 使用完全解析
  12. 来自天国的 kubernetes
  13. SpringMVC读取资源文件的几种方式
  14. Python 之 turtle 库
  15. 《duilib入门到精通》- duilib下载与编译(duilib视频教程)
  16. 【EDA】Mutisim基于Multisim的带通滤波器仿真设计实验
  17. python3.0编程软件_震惊!国内已知第一款Scratch 3.0编程软件KittenBlock推出Python编程功能!...
  18. 快速掌握用户分层模型(RFM)的使用方法|【业务模型】
  19. dataGrid使用deleteRow删除数据时会出现 ‘id‘ of undefined
  20. npm EPERM: operation not permitted, rename解决

热门文章

  1. aspen求理论塔板数_aspen 塔设计
  2. 空间皮肤代码_不废话,看我20行代码搞定色块提取与定位…….
  3. c++new时赋初值_C高级编程精髓之内存管理,万千码农踩过的雷,大神带你走出雷区...
  4. edge浏览器下载位置设置在哪里设置
  5. 如何在金山毒霸软件里安装腾讯视频
  6. linux spoon.sh闪退,解决Linux Kettle出现闪退问题
  7. dev c++调试怎么看变量的值_利用GDB调试 MSQL
  8. 彻底搞懂Cookie、Session、JWT和Token
  9. Service Mesh(服务网格)
  10. JS遍历数组的12种方法