概念概述
• C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
• 构造函数前面加 explicit 就不再支持隐式类型转换。
• 类类型的对象之间也可以隐式转换,需要相应的构造函数支持。
单参数构造
什么是单参数构造,顾名思义就是只有一个参数的构造函数
这里我们可以拿日期类来举例单参数构造
比如这里我们计算日期类的日期指之间相差多少的问题
这里的日期类就产生了:单参数构造的问题,因为是一个参数,所以在调用的时候编译器不确定你这里是需要传递一个参数还是需要隐式类型转换转化为类 进行拷贝构造。从而产生因为单参数构造产生的调用歧义的问题。
想要学会这个隐式类型转换的问题,还是需要对日期类有一定的了解,建议观看一下尤其是日期类之间的相差天数,这里就涉及到调用歧义的问题
日期类的实现(C++)-优快云博客
https://blog.youkuaiyun.com/Jason_from_China/article/details/142516352日期类的实现- 计算日期之间相差多少天-解决单参数构造-优快云博客
https://blog.youkuaiyun.com/Jason_from_China/article/details/142590750单参数构造
这里就涉及到隐式类型转换,整因为本来应该传递类类型的数值,但是这里我们传递了一个整数类型,此时就会导致创建一个临时对象,但是临时对象具备常性,就会导致传参的时候需要加上const不然会导致权限的放大,从而导致报错
这里涉及一个比较典型的隐式类型转换,从而导致定义歧义,所以我们加上const或者在构造函数前加上explicit
多参数构造
什么是多参数构造:顾名思义就是有多个参数的构造函数,这里和单参数构造是一样的
下面我们进行多参数构造的调用歧义进行讲解
//初始化列表 class MyClass { public: MyClass(int a1 = 0, char a2 = 0, int a3 = 0) : _a1(a1) , _a2(a2) , _a3(a3) {} void _print() { cout << _a1 << "/" << _a2 << "/" << _a3 << endl; } private: int _a1; int _a2; int _a3; }; class A { public: A(int a1 = 0, char a2 = 0, int a3 = 0) : _a1(a1) , _a2(a2) , _a3(a3) {} void _print() { cout << _a1 << "/" << _a2 << "/" << _a3 << endl; } class B { friend A; public: B(const A aa) : _b1(aa._a1) , _b2(aa._a2) , _b3(aa._a3) {} void _print() { cout << _b1 << "/" << _b2 << "/" << _b3 << endl; } private: int _b1; int _b2; int _b3; }; private: int _a1; int _a2; int _a3; }; //多参数构造 //隐式类型转换 int main() { MyClass aa1 = { 5,5,5 }; aa1._print(); MyClass aa2 = { 5 ,'1'}; aa2._print(); MyClass aa3 = { 5 }; aa3._print(); printf("\n"); A a1 = { 5,5,5 }; a1._print(); A a2 = { 5,5 }; a2._print(); A a3 = { 5 }; a3._print(); printf("\n"); A::B b1 = a3; b1._print(); return 0; }
拿这个代码举例来讲,
一、
MyClass
的构造函数调用
MyClass aa3 = { 5 };
:这里只有一个整数参数,但是构造函数MyClass(int a1 = 0, char a2 = 0, int a3 = 0)
有三个参数,编译器可能不确定是将这个整数赋值给第一个参数_a1
,还是进行某种隐式转换后赋值给其他参数,从而产生调用歧义。- 这里的代码就是类类型的拷贝,直接拷贝给B的类,也就是在隐式类型转换的时候,会 产生这样的情况
A::B b1 = a3;
b1._print();二、
A
的构造函数调用
A a2 = { 5, 5 };
:这里提供了两个参数,但是构造函数A(int a1 = 0, char a2 = 0, int a3 = 0)
有三个参数,编译器可能不确定如何分配这两个参数,可能会尝试一些隐式转换,但具体行为不确定,那么此时代码简单不会产生调用歧义,但是当代码复杂的时候,编译器就不知道是否需要转化为类类型进行拷贝,从而产生调用歧义。
A a3 = { 5 };
:与MyClass aa3
类似,只有一个整数参数,编译器不确定如何将其分配给三个参数,那么此时代码简单不会产生调用歧义,但是当代码复杂的时候,编译器就不知道是否需要转化为类类型进行拷贝,从而产生调用歧义。总的来说,当构造函数的参数数量与提供的初始化参数数量不匹配时,编译器可能会尝试进行隐式转换或不确定的参数分配,从而导致调用歧义。为了避免这种情况,可以考虑使用更明确的初始化方式、提供适当的单参数构造函数或者使用
explicit
关键字来防止不必要的隐式转换。
类型转换的意义 (类类型在构造的时候,可以直接输入整形进行构造,而不是需要一个类才能进行构造)
注意事项:
在接收参数的时候,这里需要加上const,因为本来应该传递类类型的数值,但是这里我们传递了一个整数类型,此时就会导致创建一个临时对象,但是临时对象具备常性,就会导致传参的时候需要加上const不然会导致权限的放大,从而导致报错
为什么产生权限放大的解释:
类型转换的意义 (自定义类型和自定义类型之间转换)(利用成员函数)(const的使用需要注意)
class C { public: C(int c1 = 1, int c2 = 1) :_c1(c1) , _c2(c2) {} //int getC_c1(C* const this)//隐藏的this指针,但是这里隐藏的this指针是修饰指针的,不是修饰内容的 int getC_c1() { return _c1; } int getC_c2() { return _c2; } private: int _c1 = 1; int _c2 = 2; }; class D { public: //在C类的成员函数没有加上const修饰的时候,是不能用const修饰的,因为这里你加上const就会导致,调用之前是不可以修改的,调用的时候C的类的可以修改的左值,回来就会导致c.getC_c1()变成可以修改的左值,但是实际是不可以修改的//D(const C& c) D( C& c) :_d1(c.getC_c1()) , _d2(c.getC_c2()) {} void _print() { cout << _d1 << "/" << _d2 << endl; } private: int _d1; int _d2; }; //多参数构造 //隐式类型转换 int main() { C c1 = { -1,-1}; const D& d1 = c1; //D d1 = c1;//这里是报错的 return 0; }
这里我们需要看的是调用的两行, C c1 = { -1,-1}; const D& d1 = c1;,这里涉及到的是隐式类型转换,绑定的问题。在拷贝的时候,这里是需要加上const的,因为在赋值的时候会产生隐式类型转换,隐式类型转化会创建一个临时变量,存放这个类型从而完成拷贝构造,这里需要注意,临时对象具备常性,所以我们需要用const进行修饰,不然会导致权限放大的问题
在绑定的时候我们也可以发现,c1和d1的地址是不一样的(本来应该是一样的),因为我们绑定的地址不是创建对象的地址,而是临时对象的地址,所以会导致引用(&)之后,地址绑定的是临时变量,所以会导致地址的不同
注意事项;
- 在D的类里面,如果我们直接给构造函数D(C& c)加上const,D(const C& c),此时会报错(权限放大)
因为C的成员函数没有加上const修饰,从而导致权限放大的问题,因为这里是隐藏this指针的
//int getC_c1(C* const this)//此时内容是可以修改的,指针是不可以修改的
const C& c的类被修饰之后C的内容是不可以被修改了,此时int getC_c1()和int getC_c2()是没有修饰的内容是可以修改的,所以一调用就导致了权限放大的问题在利用自定义之间进行转换,我们需要利用成员函数(逻辑明确,封装安全),内部类是可以直接进行转换的,外部类是不能直接进行转换的
如下所示:
class C { public: C(int c1 = 1, int c2 = 1) :_c1(c1) , _c2(c2) {} int getC_c1()const { return _c1; } int getC_c2()const { return _c2; } private: int _c1 = 1; int _c2 = 2; }; class D { public: D(const C& c) :_d1(c.getC_c1()) , _d2(c.getC_c2()) {} void _print() { cout << _d1 << "/" << _d2 << endl; } private: int _d1; int _d2; }; //多参数构造 //隐式类型转换 int main() { C c1 = { -1,-1}; const D& d1 = c1; //D d1 = c1; return 0; }
类型转换的意义 (自定义类型和自定义类型之间转换)(内部类)
- 内部类(这里我们发现是可以直接进行转化的)
转换的方式
转为临时对象:
- 当发生隐式类型转换时,通常会创建一个临时对象。例如,当一个函数期望一个类型为
B
的对象,但传递了一个类型为A
的对象时,编译器会调用B
的转换构造函数创建一个临时的B
对象来进行参数传递。- 这种情况下,是通过调用转换构造函数来创建一个新的临时对象,该临时对象的生命周期由其所在的表达式决定。
直接拷贝构造:
- 如果已经有一个自定义类型的对象,并且要将其转换为另一个自定义类型的对象,同时存在合适的构造函数,可能会进行直接的拷贝构造。
- 例如,如果有一个
A
类型的对象a
,并且B
类有一个接受A
类型对象作为参数的拷贝构造函数,那么可以通过拷贝构造将a
转换为B
类型的对象。这种情况下,有的编译器,会直接使用构造函数,来构造目标对象,而不是创建临时对象。