文章目录
函数功能
- 功能
- 在本类的旧对象复制新对象
函数使用
在主函数内如何写?
- 定义新对象=旧对象;
- 初始化旧对象
- 再用旧对象对新对象初始化
例:
- 假设定义了一个A类,在该类内定义对象a以及b
int main() { A a(3,6); A b=a; }
- 这样就是正确写法, 调用拷贝构造,将a的值拷贝给新对象b
- 但下面这种没有调用拷贝构造
int main() { A a(3,6); A c; c=a; }
- 这样写不会调用拷贝构造,因为在第一步已经将c对象构造出来,而第二步是用a对c重新赋值
设计函数参数
- 假设一个类为A类,该拷贝构造函数名与类名相同为A
- 1.假设参数类型为值类型,如下代码所示:
A(A s)//拷贝构造函数A,参数为s { } int main() { A a;//调用构造函数定义a A b(a);//调用拷贝构造函数将a复制给b }
调用时发现,值类型不行,因为在将a复制给b,调用拷贝构造函数A(),在实参a对形参s传递时,实参a需要对形参s进行初始化,就还需要调用拷贝构造函数A,这样会产生递归调用。
- 2.假设参数类型为指针类型
A(A *s)//拷贝构造函数A,参数为s { } int main() { A a;//调用构造函数定义a A b=&a;//调用拷贝构造函数将a复制给b }
指针作为参数类型时,实参传递时传的是值,形参s直接作为指针,直接指向实参a的空间即可,不需要对其开辟空间,也就不会产生递归调用。但是这样在主函数内的A b=&a看着会有歧义,就容易看做是把a的地址去初始化b
- 3.假设参数类型为引用类型,这就是正确答案~
A(A &s)//拷贝构造函数A,参数为s { } int main() { A a;//调用构造函数定义a A b =a;//调用拷贝构造函数将a复制给b }
为什么引用不需要对形参开辟空间,因为引用返回的是本身,也就不会产生递归调用。而A(A &s)就是拷贝构造函数的正确设计。
什么情况下需要调用拷贝构造函数?
- 1.用已有对象去初始化本类的对象
class A { public: A(int i=0,int j=0):m_i(i),m_j(j)//构造函数 cout<<"A"<<endl A(const A &s):m_i(s,m_i),m_j(m_j)//拷贝构造函数 cout<<"A(A&)"<<endl; private: int m_i; int m_j; }; void main() { A a(2,6);//调构造函数 A b(a);//调用拷贝构造函数 }
A b(a);//调用拷贝构造函数
- 2.函数传参,类类型的值传递
void Test(A t) { cout<<"Test:"<<endl; } void main() { A c(9,3);//调构造函数 Test(c);//调test函数 }
- 3.是值返回的类类型的函数,从局部对象到临时时调用。
A fn() { A d(10,12); return d; } void main() { A c(9,3);//调构造函数 c=fn(); }
- 有指针作为数据成员时,一般析构和拷贝构造函数程序员都要自己写
深拷贝与浅拷贝
什么是浅拷贝:
- 类提供的默认拷贝构造函数是浅拷贝,只拷贝值,如有右指针作为数据成员,则会导致两个对象的指针指向同一段内存,那么在析构的时候会出问题(因为指向同一段内存,在第一次析构时,把空间释放了,而同一段空间不能被两次释放,就会报错),基于该问题就引出了深拷贝。
什么时候需要深拷贝?
- 由指针作为数据成员,而且用本类的旧对象构造了新对象,则要写拷贝构造,给新对象的指针开辟和旧对象指针同样大小的内存单元,然后拷贝相同的值(不但拷贝值,而且拷贝资源)
如下代码所示:
- 将s进行深拷贝
A(constA &s):m_i(s,m_i),m_j(m_i) { m_name=new char[strlen(s.i)+1];//给新对象指针开辟和旧对象相同大小的内存空间 strcpy(m_i,s,m_i);//拷贝值 }
刨析函数内构造、拷贝构造、析构函数的执行过程
用如下代码举例,分析输出结果:
class A
{
public:
A(int i=0,int j=0):m_i(i),m_j(j)//构造函数
cout<<"A"<<endl
A(const A &s):m_i(s,m_i),m_j(m_j)//拷贝构造函数
cout<<"A(A&)"<<endl;
~A()//析构函数
cout<<"A(A&)"<<endl;
pirint()
{
cout<<m_i<<" "<<m_j<<" "<<endl;
}
private:
int m_i;
int m_j;
};
void Test(A t)
{
cout<<"Test:"<<endl;
t.print();//调用输出函数
}
A fn()
{
A d(10,12);
return d;
}
void main()
{
A a(2,6);//调构造函数
A b(a);//调用拷贝构造函数
A c(9,3);//调构造函数
Test(c);//调test函数
c=fn();
cout<<"main end"<<endl;
}
执行结果:
解释:
- 对象a在定义时依次调用1次构造函数A
- 用a对b复制,调用1次拷贝构造函数A(A&)
- 对象c在定义时依次调用1次构造函数A
- Test函数内,将实参c传给Test内的形参t(形参开辟空间是在函数调用时开辟,接收实参值),从实参c到形参t,调用1次拷贝构造函数A(A&),以及输出一次函数内部的Test:,然后调用printf函数输出t的值,m_i=9,m_j=3;此时,t的作用域消失,析构t~A93
- 在执行fn()时,fn内定义对象d,调用一次构造函数A;return d时,d作为局部变量,出了作用域fn()之后就不起作用了,因此需要用一个临时变量temp保存值,用局部对象temp将值带回给主函数。因此,在该阶段,首先d->temp,调用一次拷贝构造函数A(A&),然后d消失调用一次析构函数~A1012;temp->带回到主函数c内,调用一次拷贝构造函数A(A&)(解释一下这个为什么没有输出,因为现在编译器都优化了,实际是调用了,但随着编译器逐渐高级,编译器对其优化,没有必要再执行拷贝这一步)然后temp消失调用一次析构函数~A1012;然后执行“main end”,最后,主函数内变量作用域消失,根据析构函数的顺序,先构造的后析构,依次是c,b,a,~A93,~A26,~A26