1 − 复 制 构 造 函 数 又 称 拷 贝 构 造 函 数 {\red{1-复制构造函数又称拷贝构造函数}} 1−复制构造函数又称拷贝构造函数
2 − 赋 值 构 造 函 数 必 须 传 引 用 , 若 传 值 则 会 无 限 递 归 , 爆 堆 栈 {\red{2-赋值构造函数必须传引用,若传值则会无限递归,爆堆栈}} 2−赋值构造函数必须传引用,若传值则会无限递归,爆堆栈
C++中,三种调用复制构造函数的情况:
一 个 对 象 以 值 传 递 的 方 式 传 入 函 数 体 {\red{一个对象以值传递的方式传入函数体}} 一个对象以值传递的方式传入函数体
一 个 对 象 以 值 传 递 方 式 从 函 数 返 回 {\orange{一个对象以值传递方式从函数返回}} 一个对象以值传递方式从函数返回
一 个 对 象 需 要 通 过 另 一 个 对 象 初 始 化 {\green{一个对象需要通过另一个对象初始化}} 一个对象需要通过另一个对象初始化
自定义复制构造函数的意义:
当类对象的成员使用堆内存,且可能会出现上述三种情况时,需要自定义复制构造函数。
默认复制构造函数只是指针内容上的复制,但并不会复制堆内存。
复制构造函数与赋值函数的区别:
复
制
构
造
是
用
一
个
对
象
来
初
始
化
一
块
内
存
区
域
{\red{复制构造是用一个对象来初始化一块内存区域}}
复制构造是用一个对象来初始化一块内存区域
赋
值
函
数
是
对
于
一
个
已
经
初
始
化
的
对
象
来
修
改
{\orange{赋值函数是对于一个已经初始化的对象来修改}}
赋值函数是对于一个已经初始化的对象来修改
复
制
构
造
和
析
构
函
数
是
成
对
儿
的
{\red{复制构造和析构函数是成对儿的}}
复制构造和析构函数是成对儿的
赋
值
函
数
不
会
与
析
构
函
数
成
对
儿
{\green{赋值函数不会与析构函数成对儿}}
赋值函数不会与析构函数成对儿
复
制
构
造
大
多
数
情
况
是
深
拷
贝
{\red{复制构造大多数情况是深拷贝}}
复制构造大多数情况是深拷贝
赋
值
函
数
大
多
数
是
引
用
{\blue{赋值函数大多数是引用}}
赋值函数大多数是引用
关于复制构造《编译器版本,Apple clang 11.0.0》:
示 例 一 , 将 参 数 返 回 重 新 赋 值 , 调 用 复 制 构 造 函 数 {\red{示例一,将参数返回重新赋值,调用复制构造函数}} 示例一,将参数返回重新赋值,调用复制构造函数
class A{
public:
int v; // 成员变量
A(int t){ // 构造函数
v = t;
cout<<"A construction "<<v<<endl;
}
A(A &a){ // 复制构造
v = a.v + 5;
cout<<"A copy construction "<<v<<endl;
}
A& operator = (const A &a){ // 赋值函数
this->v = a.v + 7;
cout<<this->v<<" assignment "<<a.v<<endl;
return *this;
}
~A(){ // 析构函数
cout<<"A destruction "<<v<<endl;
}
};
A fun(A a){ return a; }
int main(){
A a(3);
a = fun(a);
return 0;
}
输
出
{\orange{输出}}
输出
A
c
o
n
s
t
r
u
c
t
i
o
n
3
{\orange{A\ construction\ 3}}
A construction 3
A
c
o
p
y
c
o
n
s
t
r
u
c
t
i
o
n
8
{\orange{A\ copy\ construction\ 8}}
A copy construction 8
A
c
o
p
y
c
o
n
s
t
r
u
c
t
i
o
n
13
{\orange{A\ copy\ construction\ 13}}
A copy construction 13
20
a
s
s
i
g
n
m
e
n
t
13
{\orange{20\ assignment\ 13}}
20 assignment 13
A
d
e
s
t
r
u
c
t
i
o
n
13
{\orange{A\ destruction\ 13}}
A destruction 13
A
d
e
s
t
r
u
c
t
i
o
n
8
{\orange{A\ destruction\ 8}}
A destruction 8
A
d
e
s
t
r
u
c
t
i
o
n
20
{\orange{A\ destruction\ 20}}
A destruction 20
说 明 一 下 执 行 顺 序 : {\red{说明一下执行顺序:}} 说明一下执行顺序:
- A a(3); 调用自定义构造函数A(int),成员为3
- main函数变量“a”传入fun函数,复制构造fun函数参数“a”,此时成员为8
- 将fun函数参数“a”返回,复制构造临时变量“b”,此时成员为13,代码变为a = b;
- 由于main函数变量"a"已经初始化,此时赋值函数将b赋值给a,a成员为20
- a = fun(a);执行完毕,fun函数退出函数栈,按栈的方式逐一销毁fun函数变量,临时变量b<成员13>销毁,参数a<成员8>销毁
- main函数执行完毕,退出函数栈,main函数变量a<成员20>销毁
示 例 二 , 将 函 数 内 变 量 返 回 , 不 调 用 复 制 构 造 {\red{示例二,将函数内变量返回,不调用复制构造}} 示例二,将函数内变量返回,不调用复制构造
class A{
public:
int v;
A(int t){
v = t;
cout<<"A construction "<<v<<endl;
}
A(A &a){
v = a.v + 5;
cout<<"A copy construction "<<v<<endl;
}
A& operator = (const A &a){
this->v = a.v + 7;
cout<<this->v<<" assignment "<<a.v<<endl;
return *this;
}
~A(){
cout<<"A destruction "<<v<<endl;
}
};
A fun(){
A a(2);
return a;
}
int main(){
A a(3);
a = fun();
return 0;
}
输
出
{\orange{输出}}
输出
A
c
o
n
s
t
r
u
c
t
i
o
n
3
{\orange{A\ construction\ 3}}
A construction 3
A
c
o
n
s
t
r
u
c
t
i
o
n
2
{\orange{A\ construction\ 2}}
A construction 2
9
a
s
s
i
g
n
m
e
n
t
2
{\orange{9\ assignment\ 2}}
9 assignment 2
A
d
e
s
t
r
u
c
t
i
o
n
2
{\orange{A\ destruction\ 2}}
A destruction 2
A
d
e
s
t
r
u
c
t
i
o
n
9
{\orange{A\ destruction\ 9}}
A destruction 9
这 里 返 回 a 不 调 用 复 制 构 造 感 觉 是 编 译 器 做 的 优 化 , 我 也 不 知 道 为 啥 ? {\red{这里返回a不调用复制构造感觉是编译器做的优化,我也不知道为啥?}} 这里返回a不调用复制构造感觉是编译器做的优化,我也不知道为啥?
情 况 三 , 参 数 返 回 构 造 新 示 例 , 返 回 值 不 生 成 临 时 变 量 {\red{情况三,参数返回构造新示例,返回值不生成临时变量}} 情况三,参数返回构造新示例,返回值不生成临时变量
class A{
public:
static int num;
A(){num++;}
void print(){cout<<num<<endl;}
~A(){num--;print();}
};
int A::num = 0;
A fun(A b){
b.print();
return b;
}
int main(){
A a;
a.print();
A c = fun(a);
c.print();
return 0;
}
输 出 : {\orange{输出:}} 输出: 1 1 0 0 -1 -2
也 是 编 译 器 优 化 ? 哭 晕 在 厕 所 , 搞 不 懂 {\red{也是编译器优化?哭晕在厕所,搞不懂}} 也是编译器优化?哭晕在厕所,搞不懂
2019 − 07 − 04 更 新 : {\green{2019-07-04更新:}} 2019−07−04更新:
当右值是临时变量时,构造可能采用移动构造优化,C++11新特性。
移动构造,浅拷贝且临时变量空间不销毁。