概念区分
本篇文章主要是对于隐式类型转换和匿名对象的理解,其次重点是为了区分拷贝构造和赋值的区别,并以此引出对应的编译器优化
1.隐式类型转换
构造函数不仅可以构造与初始化对象,对于接收单个参数的构造函数,还具有类型转换的作用
接收单个参数的构造函数具体表现:
- 构造函数只有一个参数
- 构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值
- 全缺省构造函数
特别注明:C++11支持多参数的
隐式类型转换
class Point
{
public:
Point(int val)
:_val(val)
{
cout << "Point(int val) -- 构造" << endl;
}
Point(const Point& p)
{
cout << "Point(const int& p) -- 拷贝构造" << endl;
_val = p._val;
}
Point& operator=(const Point& p)
{
cout << "Point& operator=(const Point& p) -- 赋值" << endl;
if (this != &p)
{
_val = p._val;
}
}
private:
int _val;
};
int main()
{
// 正常方式 - 有名对象 - 生命周期在当前函数局部域
Point p1(10);
// 隐式类型转换 - C++支持单参数构造函数的隐式类型转换
// 隐式类型转换生成的临时变量具有常性,所以引用要加const,引用传参也需要加const
Point p2 = 10;
const Point& p3 = 10;
return 0;
}
隐式类型的转换作用体现在
string
上
void Push_back(const string& s1)
{
//
}
int main()
{
// 如果不支持隐式类型转换,只能这样传参
string s1("ZCDL");
Push_back(s1);
// 支持隐式类型转换 - 内部走了构造的隐式类型转换
Push_back("ZCDL");
return 0;
}
C++11支持多参数的隐式类型转换,注意:隐式类型转换的过程中会产生临时变量,可以直接传参,但是如果想要
引用
或者引用传参
,那么需要加const
class Point
{
public:
Point(int a1, int a2)
:_a1(a1)
,_a2(a2)
{
cout << "Point(int val) -- 构造" << endl;
}
Point(const Point& p)
{
cout << "Point(const int& p) -- 拷贝构造" << endl;
_a1 = p._a1;
_a2 = p._a2;
}
Point& operator=(const Point& p)
{
cout << "Point& operator=(const Point& p) -- 赋值" << endl;
if (this != &p)
{
_a1 = p._a1;
_a2 = p._a2
}
}
private:
int _a1;
int _a2
};
int main()
{
Point p1(1, 2);
Point p2 = { 1, 2 };
const Point& p3 = { 1, 2 };
return 0;
}
如果想要禁止隐式类型转换,就要在构造函数加关键字
explicit
,禁止类型转换
2.匿名对象
匿名对象是在隐式类型被禁用后有作用,其特点为
生命周期仅在这一行
有名对象特点:生命周期在当前局部域
匿名对象特点:生命周期只在这一行
void Push_back(const string& s1)
{
//
}
int main()
{
// 如果不支持隐式类型转换,只能这样传参
string s1("ZCDL");
Push_back(s1);
// 支持隐式类型转换 - 内部走了构造的隐式类型转换
Push_back("ZCDL");
// 当隐式类型转换被禁用,使用匿名对象
Push_back(string("ZCDL"));
return 0;
}
匿名对象也常用于题目中调用函数
Solution().
- LeetCode
3.拷贝和赋值的区别
拷贝和赋值不能靠是否是=来判断,有等号不一定时赋值
拷贝还是赋值要通过
被赋值对象是否初始化
来确定,用已经初始化的对象去初始化未初始化的对象为拷贝构造
,即使有等号,对象是未初始化的也是拷贝构造
当对象已经初始化,再用已经初始的对象给等号为赋值
class Point
{
public:
Point(int a1)
:_a1(a1)
{
cout << "Point(int val) -- 构造" << endl;
}
Point(const Point& p)
{
cout << "Point(const int& p) -- 拷贝构造" << endl;
_a1 = p._a1;
}
Point& operator=(const Point& p)
{
cout << "Point& operator=(const Point& p) -- 赋值" << endl;
if (this != &p)
{
_a1 = p._a1;
}
}
private:
int _a1;
};
int main()
{
// 构造
Point p1(10);
// 拷贝构造的两种方式:
// 1. 拷贝构造 - p2未初始化,用初始化的p1去初始化p2为拷贝构造(即使有等号也为拷贝构造)
Point p2 = p1;
// 2. 拷贝构造 - 直接方式
Point p3(p1);
// 赋值 - p4已经初始化,再用p1去初始化为赋值
Point p4(20);
p4 = p1;
return 0;
}
结论:判断依据不能仅靠等号去判断是赋值还是拷贝,要看
=对面的对象
是否初始化,如果初始化就为赋值,未初始化就为拷贝
4.编译器优化
编译器优化:
构造+拷贝构造优化成直接构造,拷贝构造+拷贝构造优化成直接拷贝构造(传参返回的深拷贝会出先两次拷贝构造,一次是销毁产生临时对象时会拷贝构造一次,另一次是给赋值的时候会进行一次拷贝构造,两次都是深拷贝,防止内存泄漏)
条件:同时性,要在同一行,即构造和拷贝构造同时进行(同一行即可),才会发生优化(同时性)
赋值:不会发生优化,因为赋值一定是给初始化对象的,所以赋值的过程中不会出现构造(两个不能写在同一行,写在同一行为拷贝构造),所以赋值和构造一定是分开进行的,一定不会出现优化
// 默认生成拷贝构造,对于内置类型完成值拷贝,对于自定义类型(自己写的类就是自定义类型)去调用它的拷贝构造
class Point
{
public:
Point(int a1)
:_a1(a1)
{
cout << "Point(int val) -- 构造" << endl;
}
Point(const Point& p)
{
cout << "Point(const int& p) -- 拷贝构造" << endl;
_a1 = p._a1;
}
Point& operator=(const Point& p)
{
cout << "Point& operator=(const Point& p) -- 赋值" << endl;
if (this != &p)
{
_a1 = p._a1;
}
return *this;
}
private:
int _a1;
};
int main()
{
// 构造 * 2
Point p1(10);
Point p2(20);
// 赋值和构造一定是分开进行的, 先构造后赋值
p1 = p2;
// 拷贝构造 - 因为p1没有进行构造,只有p3的拷贝构造,编译器不会优化
Point p3 = p1;
// 构造+拷贝构造的过程就是隐式类型转换
// 用10调用Point构造函数生成一个临时对象,再用这个对象去拷贝构造p3
// 10进行了构造,生成了临时对象拷贝构造p3,编译器直接优化成构造‘
// 同时性:构造和拷贝构造同时发生 - 优化成直接构造
Point p4 = 10;
return 0;
}
补充点:两次拷贝构造会被优化成直接拷贝构造,减少一次深拷贝,后续还会有更好的优化方法,为
右值引用的移动语义(移动构造和移动赋值),可以将深拷贝直接优化成移动构造,减少内存的使用
End!!!