目录
1 . 拷贝构造(浅拷贝)
1.1形式
复制构造函数是构造函数的一种,也称拷贝构造函数,它只有一个参数,参数类型是本类的引用。
复制构造函数的参数可以是 const 引用,也可以是非 const 引用。 一般使用前者,这样既能以常量对象(初始化后值不能改变的对象)作为参数,也能以非常量对象作为参数去初始化其他对象。一个类中写两个复制构造函数,一个的参数是 const 引用,另一个的参数是非 const 引用,也是可以的。
如果类的设计者不写复制构造函数,编译器就会自动生成复制构造函数。大多数情况下,其作用是实现从源对象到目标对象逐个字节的复制,即使得目标对象的每个成员变量都变得和源对象相等。编译器自动生成的复制构造函数称为“默认复制构造函数”。
class CStu
{
public:
CStu (const CStu&) //以常引用类的参数
{
}
}
1.2何时调用
一、新建一个对象,并将其初始化为同类现有对象时如:
class CStu
{
public:
};
CStu stu1; //申请一个对象
CStu stu2(stu1);
CStu stu3=stu1;
CStu* stu4=new CStu(stu1);
//以上语句都是新建一个对象,并将新建对象初始化为stu1的内容,都将调用默认拷贝构造函数
注意赋值不会调用拷贝构造如:
CStu s1,s2; //新建对象s1,s2
s1=s2;
//s1为以建好的对象,这只是将s2赋值给s1,不会调用拷贝构造 这与CStu s1=s2; 不同
二、当程序生成对象副本时(弊端)
1. 函数传递对象值
如果函数 F 的参数是类 A 的对象,那么当 F 被调用时,类 A 的复制构造函数将被调用。换句话说,作为形参的对象,是用复制构造函数初始化的,而且调用复制构造函数时的参数,就是调用函数时所给的实参。
#include<iostream>
using namespace std;
class A
{
public:
A(A & a) //拷贝构造函数
{
cout<<"Copy constructor called"<<endl;
}
};
void Func(A x) //函数形参为类
{
;
}
int main()
{
A a;
Func(a); //传递对象 a
return 0;
}
运行结果 输出了"Copy constructor called",说明调用了拷贝构造函数。这是因为 Func 函数的形参 x 在初始化时调用了复制构造函数。
前面说过,函数的形参的值等于函数调用时对应的实参,现在可以知道这不一定是正确的。如果形参是一个对象,那么形参的值是否等于实参,取决于该对象所属的类的复制构造函数是如何实现的。例如上面的例子,Func 函数的形参 a 的值在进入函数时是随机的,未必等于实参,因为复制构造函数没有做复制的工作。
以对象作为函数的形参,在函数被调用时,生成的形参要用复制构造函数初始化,这会带来时间上的开销。如果用对象的引用而不是对象作为形参,就没有这个问题了。但是以引用作为形参有一定的风险,因为这种情况下如果形参的值发生改变,实参的值也会跟着改变。
2. 函数返回对象
如果函数的返冋值是类 A 的对象,则函数返冋时,类 A 的复制构造函数被调用。换言之,作为函数返回值的对象是用复制构造函数初始化 的,而调用复制构造函数时的实参,就是 return 语句所返回的对象。例如下:
#include<iostream>
using namespace std;
class A
{
public:
int v;
A(int n)
{
v = n;
}
A(const A & a)
{
v = a.v;
cout << "Copy constructor called" << endl;
}
};
A Func()
{
A a(4);
return a; //返回该对象a
}
int main()
{
cout << Func().v << endl; // Func().v等价于a.v
return 0;
}
运行结果如下
程序的输出结果是:
Copy constructor called
4
调用 Func 函数,其返回值是一个对象,该对象就是用复制构造函数初始化的, 而且调用复制构造函数时,实参就是 return 语句所返回的 a。复制构造函数确实完成了复制的工作,所以 Func 函数的返回值和 a 相等。
需要说明的是,有些编译器出于程序执行效率的考虑,编译的时候进行了优化,函数返回值对象就不用复制构造函数初始化了,这并不符合 C++ 的标准。上面的程序,用 Visual Studio 2013 编译后的输出结果如上所述,但是在 Dev C++ 4.9 中不会调用复制构造函数。把a 变成全局变量,才会调用复制构造函数。对这一点,读者不必深究。
1.3有何功能
下面示例拷贝构造函数作用,新建c2时调用了拷贝构造将c2初始化为c1
#include<iostream >
using namespace std;
class Complex
{
public:
double real, imag;
Complex(double r, double i)
{
real = r; imag = i;
}
};
int main(){
Complex cl(1, 2);
Complex c2(cl); //用复制构造函数初始化c2
cout << c2.real << "," << c2.imag << endl;; //输出 1,2 和对象c1中的值一模一样
return 0;
}
1.4示例问题及解决方案
当我们类中有动态开辟内存时,新建的对象调用了拷贝构造,最后在释放内存时会导致多次释放同一块内存,导致程序崩溃。
#include<iostream>
using namespace std;
class CStu
{
public:
int* a;
CStu()
{
a = new int[1]; //动态开辟个数为1的int型数组
a[0] = 10;
}
CStu(const CStu&b)
{
this->a = b.a; //将s1中指针a的值赋值给s2对象中指针a值
//浅拷贝,在释放内存时会造成 二次释放,导致程序崩溃
}
~CStu() //析构函数释放内存,每个对象作用域结束时自动调用析构函数
{
delete[]a;
}
};
int main()
{
CStu s1;
CStu s2 = s1;
cout << s1.a[0] << endl;
cout << s2.a[0] << endl;
return 0;
}
解决以上问题我们要尽量避免调用拷贝构造
具体方法有:使用深拷贝、传引用 。当我们使用引用时,就可以避免生成对象副本而引起调用拷贝构造
1.如下func函数的形参使用对象的引用便不会调用拷贝构造函数
#include<iostream>
using namespace std;
class A
{
public:
A(A & a) //拷贝构造函数
{
cout<<"Copy constructor called"<<endl;
}
};
void Func(A& x) //函数形参为对象的引用 不会生成对象副本
{
;
}
int main()
{
A a;
Func(a); //传递对象 a
return 0;
}
没有输出"Copy constructor called"说明没有调用拷贝构造
2.函数返回值时加引用
#include<iostream>
using namespace std;
class A
{
public:
int v;
A(int n)
{
v = n;
}
A(const A & a)
{
v = a.v;
cout << "Copy constructor called" << endl;
}
};
A& Func()
{
A a(4);
return a; //返回该对象a的引用
}
int main()
{
Func();
return 0;
}
没有输出"Copy constructor called" 不会调用拷贝构造函数
2 . 深拷贝
如果一个类拥有指针类型的成员变量,那么绝大部分情况下就需要深拷贝,因为只有这样,才能将指针指向的内容再复制出一份来,让原有对象和新生对象相互独立,彼此之间不受影响。如果类的成员变量没有指针,一般浅拷贝足以。
#include<iostream>
using namespace std;
class CStu
{
public:
int* a;
CStu()
{
a = new int[1];
a[0] = 10;
}
CStu(const CStu&b)
{
//this->a = b.a; //浅拷贝,在释放内存时会造成 二次释放,导致程序崩溃
//下面实现深拷贝
this->a = new int[1];//重新动态开辟一个数组空间,避免两次都释放同一块空间
memcpy(this->a, b.a, sizeof(int) * 1);//使用memcpy函数将s1.a中的值拷贝到s2.a中
}
~CStu()
{
delete[]a;
}
};
int main()
{
CStu s1;
cout << s1.a[0] << endl;
CStu s2 = s1;
cout << s2.a[0] << endl;
return 0;
}